mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -0,0 +1,32 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: course_units.mdl.js
|
||||
* Type of Program: Model (junction)
|
||||
* Description: Attaches a standalone Unit to a Course. A Unit can live in many
|
||||
* Courses; per-course ordering lives here (order_index), not on the
|
||||
* Unit itself. Detaching removes the row — the Unit survives.
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jul. 7, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
const { DataTypes } = require("sequelize");
|
||||
const sequelize = require("../../config/db.config");
|
||||
|
||||
const CourseUnit = sequelize.define("CourseUnit", {
|
||||
course_unit_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
course_id: { type: DataTypes.BIGINT, allowNull: false },
|
||||
unit_id: { type: DataTypes.BIGINT, allowNull: false },
|
||||
order_index: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0 },
|
||||
createdBy: { type: DataTypes.BIGINT, allowNull: true },
|
||||
updatedBy: { type: DataTypes.BIGINT, allowNull: true },
|
||||
}, {
|
||||
tableName: "course_units",
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{ unique: true, fields: ["course_id", "unit_id"], name: "uq_course_units_course_unit" },
|
||||
{ fields: ["course_id"], name: "idx_course_units_course_id" },
|
||||
{ fields: ["unit_id"], name: "idx_course_units_unit_id" },
|
||||
{ fields: ["order_index"], name: "idx_course_units_order" },
|
||||
],
|
||||
});
|
||||
|
||||
module.exports = CourseUnit;
|
||||
@@ -3,6 +3,8 @@
|
||||
const { Course, CourseProductCategory } = require("./courses.mdl");
|
||||
const Unit = require("./units.mdl");
|
||||
const Lesson = require("./lessons.mdl");
|
||||
const CourseUnit = require("./course_units.mdl");
|
||||
const UnitLesson = require("./unit_lessons.mdl");
|
||||
const LessonPage = require("./lesson_page.mdl");
|
||||
const CourseObjective = require("./course_objective.mdl");
|
||||
const LessonObjective = require("./lesson_objective.mdl");
|
||||
@@ -44,10 +46,27 @@ LessonReadingProgress.belongsTo(Lesson, { foreignKey: 'lesson_id', as: 'lesso
|
||||
mdl_Users.hasMany(LessonReadingProgress, { foreignKey: 'user_id', as: 'lessonReadingProgress' });
|
||||
Lesson.hasMany(LessonReadingProgress, { foreignKey: 'lesson_id', as: 'readingProgress' });
|
||||
|
||||
// ── Course ⇄ Unit / Unit ⇄ Lesson (junctions) ─────────────────────────────────
|
||||
// Units and Lessons are standalone entities. Membership + ordering live on the
|
||||
// course_units / unit_lessons junction rows (order_index).
|
||||
Course.belongsToMany(Unit, { through: CourseUnit, foreignKey: "course_id", otherKey: "unit_id", as: "units" });
|
||||
Unit.belongsToMany(Course, { through: CourseUnit, foreignKey: "unit_id", otherKey: "course_id", as: "courses" });
|
||||
Unit.belongsToMany(Lesson, { through: UnitLesson, foreignKey: "unit_id", otherKey: "lesson_id", as: "lessons" });
|
||||
Lesson.belongsToMany(Unit, { through: UnitLesson, foreignKey: "lesson_id", otherKey: "unit_id", as: "units" });
|
||||
|
||||
// Direct junction access (attach / detach / reorder / count queries)
|
||||
Course.hasMany(CourseUnit, { as: "unitLinks", foreignKey: "course_id" });
|
||||
CourseUnit.belongsTo(Course, { as: "course", foreignKey: "course_id" });
|
||||
CourseUnit.belongsTo(Unit, { as: "unit", foreignKey: "unit_id" });
|
||||
Unit.hasMany(CourseUnit, { as: "courseLinks", foreignKey: "unit_id" });
|
||||
Unit.hasMany(UnitLesson, { as: "lessonLinks", foreignKey: "unit_id" });
|
||||
UnitLesson.belongsTo(Unit, { as: "unit", foreignKey: "unit_id" });
|
||||
UnitLesson.belongsTo(Lesson, { as: "lesson", foreignKey: "lesson_id" });
|
||||
Lesson.hasMany(UnitLesson, { as: "unitLinks", foreignKey: "lesson_id" });
|
||||
|
||||
// ── Course ────────────────────────────────────────────────────────────────────
|
||||
Course.belongsTo(mdl_Users, { as: "creator", foreignKey: "createdBy" });
|
||||
Course.belongsTo(mdl_Users, { as: "updater", foreignKey: "updatedBy" });
|
||||
Course.hasMany(Unit, { as: "units", foreignKey: "course_id" });
|
||||
Course.hasMany(CourseObjective, { as: "objectives", foreignKey: "course_id" });
|
||||
Course.hasMany(CoursePrerequisite, { as: "prerequisites", foreignKey: "course_id" });
|
||||
Course.hasOne(CourseAssessment, { as: "assessment", foreignKey: "course_id" });
|
||||
@@ -61,14 +80,11 @@ Course.belongsToMany(mdl_Category, { through: CourseProductCategory, foreignKey:
|
||||
mdl_Category.belongsToMany(Course, { through: CourseProductCategory, foreignKey: 'category_id', otherKey: 'course_id', as: 'courses' });
|
||||
|
||||
// ── Unit ──────────────────────────────────────────────────────────────────────
|
||||
Unit.belongsTo(Course, { as: "course", foreignKey: "course_id" });
|
||||
Unit.belongsTo(mdl_Users, { as: "creator", foreignKey: "createdBy" });
|
||||
Unit.belongsTo(mdl_Users, { as: "updater", foreignKey: "updatedBy" });
|
||||
Unit.hasMany(Lesson, { as: "lessons", foreignKey: "unit_id" });
|
||||
Unit.hasOne(UnitQuiz, { as: "quiz", foreignKey: "unit_id" });
|
||||
|
||||
// ── Lesson ────────────────────────────────────────────────────────────────────
|
||||
Lesson.belongsTo(Unit, { as: "unit", foreignKey: "unit_id" });
|
||||
Lesson.belongsTo(mdl_Users, { as: "creator", foreignKey: "createdBy" });
|
||||
Lesson.belongsTo(mdl_Users, { as: "updater", foreignKey: "updatedBy" });
|
||||
Lesson.hasOne(LessonPage, { as: "page", foreignKey: "lesson_id" });
|
||||
@@ -114,6 +130,7 @@ UnitQuiz.hasMany(QuizSession, { as: "sessions", foreignKey: "quiz_id" });
|
||||
module.exports = {
|
||||
Course, CourseProductCategory,
|
||||
Unit, Lesson, LessonPage,
|
||||
CourseUnit, UnitLesson,
|
||||
CourseObjective, LessonObjective,
|
||||
CoursePrerequisite, CourseAssessment,
|
||||
UnitQuiz, QuizQuestion, QuizOption, QuizAttempt,
|
||||
|
||||
@@ -27,7 +27,8 @@ const computedAttributes = [
|
||||
literal: `(
|
||||
SELECT CAST(COUNT(*) AS INTEGER)
|
||||
FROM "units"
|
||||
WHERE "units"."course_id" = "Course"."course_id"
|
||||
INNER JOIN "course_units" ON "course_units"."unit_id" = "units"."unit_id"
|
||||
WHERE "course_units"."course_id" = "Course"."course_id"
|
||||
AND "units"."deletedAt" IS NULL
|
||||
)`,
|
||||
filterable: false,
|
||||
@@ -38,10 +39,12 @@ const computedAttributes = [
|
||||
type: "number",
|
||||
order: 6,
|
||||
literal: `(
|
||||
SELECT CAST(COUNT(*) AS INTEGER)
|
||||
SELECT CAST(COUNT(DISTINCT "lessons"."lesson_id") AS INTEGER)
|
||||
FROM "lessons"
|
||||
INNER JOIN "units" ON "lessons"."unit_id" = "units"."unit_id"
|
||||
WHERE "units"."course_id" = "Course"."course_id"
|
||||
INNER JOIN "unit_lessons" ON "unit_lessons"."lesson_id" = "lessons"."lesson_id"
|
||||
INNER JOIN "units" ON "unit_lessons"."unit_id" = "units"."unit_id" AND "units"."deletedAt" IS NULL
|
||||
INNER JOIN "course_units" ON "course_units"."unit_id" = "units"."unit_id"
|
||||
WHERE "course_units"."course_id" = "Course"."course_id"
|
||||
AND "lessons"."deletedAt" IS NULL
|
||||
)`,
|
||||
filterable: false,
|
||||
|
||||
@@ -28,13 +28,13 @@ const LessonReadingProgress = sequelize.define('LessonReadingProgress', {
|
||||
},
|
||||
course_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
allowNull: true, // NULL when the lesson is read standalone (outside any course)
|
||||
references: { model: 'courses', key: 'course_id' },
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
unit_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
allowNull: true, // NULL when the lesson is read standalone (outside any unit)
|
||||
references: { model: 'units', key: 'unit_id' },
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
const { DataTypes } = require("sequelize");
|
||||
const sequelize = require("../../config/db.config");
|
||||
|
||||
// Standalone entity — no unit_id / order_index here. A Lesson is attached to
|
||||
// zero or more Units through unit_lessons, where per-unit ordering lives.
|
||||
const Lesson = sequelize.define("Lesson", {
|
||||
lesson_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true, hidden: true, order: 0 },
|
||||
uuid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, allowNull: false, unique: true, hidden: true, order: 0 },
|
||||
unit_id: { type: DataTypes.BIGINT, allowNull: false, hidden: true, order: 0 },
|
||||
|
||||
title: { type: DataTypes.STRING(255), allowNull: false, label: "Title", hidden: false, order: 1 },
|
||||
description: { type: DataTypes.TEXT, allowNull: true, label: "Description", hidden: false, order: 2 },
|
||||
|
||||
duration_seconds: { type: DataTypes.INTEGER, allowNull: true, defaultValue: 0 }, // computed from blocks on save
|
||||
|
||||
order_index: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0, label: "Order", hidden: false, order: 3 },
|
||||
|
||||
createdBy: { type: DataTypes.BIGINT, allowNull: true, label: "Created By" },
|
||||
updatedBy: { type: DataTypes.BIGINT, allowNull: true, label: "Updated By" },
|
||||
deletedBy: { type: DataTypes.BIGINT, allowNull: true, label: "Deleted By" },
|
||||
@@ -21,10 +21,8 @@ const Lesson = sequelize.define("Lesson", {
|
||||
paranoid: true,
|
||||
indexes: [
|
||||
{ fields: ["uuid"] },
|
||||
{ fields: ["unit_id"] },
|
||||
{ fields: ["order_index"] },
|
||||
{ fields: ["deletedAt"] },
|
||||
],
|
||||
});
|
||||
|
||||
module.exports = Lesson;
|
||||
module.exports = Lesson;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: unit_lessons.mdl.js
|
||||
* Type of Program: Model (junction)
|
||||
* Description: Attaches a standalone Lesson to a Unit. A Lesson can live in many
|
||||
* Units; per-unit ordering lives here (order_index), not on the
|
||||
* Lesson itself. Detaching removes the row — the Lesson survives.
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jul. 7, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
const { DataTypes } = require("sequelize");
|
||||
const sequelize = require("../../config/db.config");
|
||||
|
||||
const UnitLesson = sequelize.define("UnitLesson", {
|
||||
unit_lesson_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
unit_id: { type: DataTypes.BIGINT, allowNull: false },
|
||||
lesson_id: { type: DataTypes.BIGINT, allowNull: false },
|
||||
order_index: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0 },
|
||||
createdBy: { type: DataTypes.BIGINT, allowNull: true },
|
||||
updatedBy: { type: DataTypes.BIGINT, allowNull: true },
|
||||
}, {
|
||||
tableName: "unit_lessons",
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{ unique: true, fields: ["unit_id", "lesson_id"], name: "uq_unit_lessons_unit_lesson" },
|
||||
{ fields: ["unit_id"], name: "idx_unit_lessons_unit_id" },
|
||||
{ fields: ["lesson_id"], name: "idx_unit_lessons_lesson_id" },
|
||||
{ fields: ["order_index"], name: "idx_unit_lessons_order" },
|
||||
],
|
||||
});
|
||||
|
||||
module.exports = UnitLesson;
|
||||
@@ -28,7 +28,7 @@ const UnitReadingProgress = sequelize.define('UnitReadingProgress', {
|
||||
},
|
||||
course_id: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
allowNull: true, // NULL when the unit is read standalone (outside any course)
|
||||
references: { model: 'courses', key: 'course_id' },
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
const { DataTypes } = require("sequelize");
|
||||
const sequelize = require("../../config/db.config");
|
||||
|
||||
// Standalone entity — no course_id / order_index here. A Unit is attached to
|
||||
// zero or more Courses through course_units, where per-course ordering lives.
|
||||
const Unit = sequelize.define("Unit", {
|
||||
unit_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true, hidden: true, order: 0, filterable: false },
|
||||
uuid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, allowNull: false, unique: true, hidden: true, order: 0, filterable: false },
|
||||
course_id: { type: DataTypes.BIGINT, allowNull: false, hidden: true, order: 0, filterable: false },
|
||||
title: { type: DataTypes.STRING(255), allowNull: false, label: "Title", hidden: false, order: 1, filterable: true },
|
||||
description: { type: DataTypes.TEXT, allowNull: true, label: "Description", hidden: true, order: 0, filterable: false },
|
||||
order_index: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0, label: "Order", hidden: false, order: 2, filterable: false },
|
||||
duration_seconds: { type: DataTypes.INTEGER, allowNull: true, defaultValue: 0, hidden: false, order: 3, filterable: false },
|
||||
duration_seconds: { type: DataTypes.INTEGER, allowNull: true, defaultValue: 0, hidden: false, order: 2, filterable: false },
|
||||
createdBy: { type: DataTypes.BIGINT, allowNull: true, filterable: true },
|
||||
updatedBy: { type: DataTypes.BIGINT, allowNull: true, filterable: true },
|
||||
deletedBy: { type: DataTypes.BIGINT, allowNull: true, filterable: true },
|
||||
@@ -18,10 +18,8 @@ const Unit = sequelize.define("Unit", {
|
||||
paranoid: true,
|
||||
indexes: [
|
||||
{ fields: ["uuid"] },
|
||||
{ fields: ["course_id"] },
|
||||
{ fields: ["order_index"] },
|
||||
{ fields: ["deletedAt"] },
|
||||
],
|
||||
});
|
||||
|
||||
module.exports = Unit;
|
||||
module.exports = Unit;
|
||||
|
||||
Reference in New Issue
Block a user