This commit is contained in:
rgrgogu
2026-05-20 13:22:29 +08:00
parent 6907e9bb2d
commit 6a5145f553
26 changed files with 2615 additions and 221 deletions
+22
View File
@@ -0,0 +1,22 @@
const { DataTypes } = require("sequelize");
const sequelize = require("../../config/db.config");
const CourseAssessment = sequelize.define("CourseAssessment", {
assessment_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
uuid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, unique: true },
course_id: { type: DataTypes.BIGINT, allowNull: false, unique: true }, // one per course
title: { type: DataTypes.STRING(255), allowNull: true },
is_required: { type: DataTypes.BOOLEAN, defaultValue: false },
passing_score: { type: DataTypes.INTEGER, defaultValue: 70 },
time_limit_minutes: { type: DataTypes.INTEGER, allowNull: true }, // null = no limit
max_questions: { type: DataTypes.INTEGER, allowNull: true }, // null = show all
createdBy: { type: DataTypes.BIGINT, allowNull: true },
updatedBy: { type: DataTypes.BIGINT, allowNull: true },
deletedBy: { type: DataTypes.BIGINT, allowNull: true },
}, {
tableName: "course_assessments",
timestamps: true,
paranoid: true,
});
module.exports = CourseAssessment
+15
View File
@@ -0,0 +1,15 @@
const { DataTypes } = require("sequelize");
const sequelize = require("../../config/db.config");
const CourseObjective = sequelize.define("CourseObjective", {
objective_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
course_id: { type: DataTypes.BIGINT, allowNull: false },
text: { type: DataTypes.TEXT, allowNull: false },
order_index: { type: DataTypes.INTEGER, defaultValue: 0 },
}, {
tableName: "course_objectives",
timestamps: true,
paranoid: true,
});
module.exports = CourseObjective
+15
View File
@@ -0,0 +1,15 @@
const { DataTypes } = require("sequelize");
const sequelize = require("../../config/db.config");
const CoursePrerequisite = sequelize.define("CoursePrerequisite", {
prereq_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
course_id: { type: DataTypes.BIGINT, allowNull: false }, // the course that HAS this prereq
ref_type: { type: DataTypes.ENUM("course", "unit", "lesson"), allowNull: false },
ref_id: { type: DataTypes.BIGINT, allowNull: false }, // FK to courses/units/lessons
order_index:{ type: DataTypes.INTEGER, defaultValue: 0 },
}, {
tableName: "course_prerequisites",
timestamps: true,
});
module.exports = CoursePrerequisite
+67
View File
@@ -0,0 +1,67 @@
// models/courses/associations.js
const { Course, CourseProduct, CourseRole, CourseProductCategory } = require("./courses.mdl");
const Unit = require("./units.mdl");
const Lesson = require("./lessons.mdl");
const LessonPage = require("./lesson_page.mdl");
const CourseObjective = require("./course_objective.mdl");
const LessonObjective = require("./lesson_objective.mdl");
const CoursePrerequisite = require("./course_prerequisite.mdl");
const CourseAssessment = require("./course_assessment.mdl");
const UnitQuiz = require("./unit_quiz.mdl");
const QuizQuestion = require("./quiz_question.mdl");
const QuizOption = require("./quiz_option.mdl");
const mdl_Users = require("../users/users.mdl");
// ── 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" });
Course.hasMany(CourseProduct, { as: "products", foreignKey: "course_id" });
Course.hasMany(CourseRole, { as: "roles", foreignKey: "course_id" });
Course.hasMany(CourseProductCategory, { as: "categories", foreignKey: "course_id" });
// ── 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" });
Lesson.hasMany(LessonObjective, { as: "objectives", foreignKey: "lesson_id" });
// ── UnitQuiz ──────────────────────────────────────────────────────────────────
UnitQuiz.belongsTo(Unit, { as: "unit", foreignKey: "unit_id" });
UnitQuiz.belongsTo(mdl_Users, { as: "creator", foreignKey: "createdBy" });
UnitQuiz.belongsTo(mdl_Users, { as: "updater", foreignKey: "updatedBy" });
UnitQuiz.hasMany(QuizQuestion, { as: "questions", foreignKey: "quiz_id" });
// ── CourseAssessment ──────────────────────────────────────────────────────────
CourseAssessment.belongsTo(Course, { as: "course", foreignKey: "course_id" });
CourseAssessment.belongsTo(mdl_Users, { as: "creator", foreignKey: "createdBy" });
CourseAssessment.belongsTo(mdl_Users, { as: "updater", foreignKey: "updatedBy" });
CourseAssessment.hasMany(QuizQuestion, { as: "questions", foreignKey: "assessment_id" });
// ── QuizQuestion ──────────────────────────────────────────────────────────────
QuizQuestion.belongsTo(UnitQuiz, { as: "quiz", foreignKey: "quiz_id" });
QuizQuestion.belongsTo(CourseAssessment, { as: "assessment", foreignKey: "assessment_id" });
QuizQuestion.hasMany(QuizOption, { as: "options", foreignKey: "question_id" });
// ── QuizOption ────────────────────────────────────────────────────────────────
QuizOption.belongsTo(QuizQuestion, { as: "question", foreignKey: "question_id" });
module.exports = {
Course, CourseProduct, CourseRole, CourseProductCategory,
Unit, Lesson, LessonPage,
CourseObjective, LessonObjective,
CoursePrerequisite, CourseAssessment,
UnitQuiz, QuizQuestion, QuizOption,
};
+51
View File
@@ -0,0 +1,51 @@
const excludeAttributes = [
];
const jsonbSchemas = {
// Add here
};
// Different exclude sets per role
const adminExclude = [
...excludeAttributes,
// admins can see audit fields, so nothing extra excluded
];
const userExclude = [
...excludeAttributes,
// regular users cannot see audit trails
"created_by", "updated_by", "deleted_by",
"deleted_at",
];
const computedAttributes = [
{
key: "unitCount",
label: "Units",
type: "number",
order: 5,
literal: `(
SELECT CAST(COUNT(*) AS INTEGER)
FROM "units"
WHERE "units"."course_id" = "Course"."course_id"
AND "units"."deletedAt" IS NULL
)`,
filterable: false,
},
{
key: "lessonCount",
label: "Lessons",
type: "number",
order: 6,
literal: `(
SELECT CAST(COUNT(*) AS INTEGER)
FROM "lessons"
INNER JOIN "units" ON "lessons"."unit_id" = "units"."unit_id"
WHERE "units"."course_id" = "Course"."course_id"
AND "lessons"."deletedAt" IS NULL
)`,
filterable: false,
},
];
module.exports = { excludeAttributes, adminExclude, userExclude, jsonbSchemas, computedAttributes };
+37 -21
View File
@@ -1,32 +1,48 @@
const { DataTypes } = require("sequelize");
const sequelize = require("../../config/db.config");
const mdl_Users = require("../users/users.mdl");
const Course = sequelize.define("Course", {
course_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
uuid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, allowNull: false, unique: true },
title: { type: DataTypes.STRING(255), allowNull: false, label: "Title" },
description: { type: DataTypes.TEXT, allowNull: true, label: "Description" },
order_index: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0, label: "Order" },
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" },
course_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true, hidden: true, order: 0, filterable: true },
uuid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, allowNull: false, unique: true, hidden: true, order: 0, filterable: true },
title: { type: DataTypes.STRING(255), allowNull: false, hidden: false, order: 2, filterable: true },
description: { type: DataTypes.TEXT, allowNull: true, hidden: true, order: 0, filterable: true },
course_code: { type: DataTypes.STRING(50), allowNull: true, unique: true, hidden: false, order: 1, filterable: true },
order_index: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0, hidden: false, order: 7, filterable: false },
level: { type: DataTypes.ENUM("beginner", "intermediate", "advanced"), allowNull: true, hidden: false, order: 3, filterable: true },
subscription: { type: DataTypes.ENUM("free", "premium"), allowNull: false, defaultValue: "free", hidden: false, order: 4, filterable: true },
duration_seconds: { type: DataTypes.INTEGER, allowNull: true, defaultValue: 0, hidden: false, order: 8, filterable: false },
createdBy: { type: DataTypes.BIGINT, allowNull: true },
updatedBy: { type: DataTypes.BIGINT, allowNull: true },
deletedBy: { type: DataTypes.BIGINT, allowNull: true },
}, {
tableName: "courses",
timestamps: true,
paranoid: true,
indexes: [
{ fields: ["uuid"] },
{ fields: ["order_index"] },
{ fields: ["deletedAt"] },
],
});
Course.belongsTo(mdl_Users, { as: "creator", foreignKey: "createdBy" });
Course.belongsTo(mdl_Users, { as: "updater", foreignKey: "updatedBy" });
// ── Junction Tables ───────────────────────────────────────────────────────────
module.exports = Course;
const CourseProduct = sequelize.define("CourseProduct", {
id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
course_id: { type: DataTypes.BIGINT, allowNull: false },
product_id: { type: DataTypes.BIGINT, allowNull: false },
}, { tableName: "course_products", timestamps: true });
const CourseRole = sequelize.define("CourseRole", {
id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
course_id: { type: DataTypes.BIGINT, allowNull: false },
role_id: { type: DataTypes.BIGINT, allowNull: false },
}, { tableName: "course_roles", timestamps: true });
const CourseProductCategory = sequelize.define("CourseProductCategory", {
id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
course_id: { type: DataTypes.BIGINT, allowNull: false },
category_id: { type: DataTypes.BIGINT, allowNull: false },
}, { tableName: "course_product_categories", timestamps: true });
module.exports = {
Course,
CourseProduct,
CourseRole,
CourseProductCategory,
};
+15
View File
@@ -0,0 +1,15 @@
const { DataTypes } = require("sequelize");
const sequelize = require("../../config/db.config");
const LessonObjective = sequelize.define("LessonObjective", {
objective_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
lesson_id: { type: DataTypes.BIGINT, allowNull: false },
text: { type: DataTypes.TEXT, allowNull: false },
order_index: { type: DataTypes.INTEGER, defaultValue: 0 },
}, {
tableName: "lesson_objectives",
timestamps: true,
paranoid: true,
});
module.exports = LessonObjective
@@ -1,7 +1,5 @@
const { DataTypes } = require("sequelize");
const sequelize = require("../../config/db.config");
const mdl_Users = require("../users/users.mdl");
const Lesson = require("./lessons.mdl");
const LessonPage = sequelize.define("LessonPage", {
page_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
@@ -15,9 +13,4 @@ const LessonPage = sequelize.define("LessonPage", {
paranoid: false,
});
LessonPage.belongsTo(Lesson, { as: "lesson", foreignKey: "lesson_id" });
LessonPage.belongsTo(mdl_Users, { as: "creator", foreignKey: "createdBy" });
LessonPage.belongsTo(mdl_Users, { as: "updater", foreignKey: "updatedBy" });
Lesson.hasOne(LessonPage, { as: "page", foreignKey: "lesson_id" });
module.exports = LessonPage;
+16 -19
View File
@@ -1,22 +1,24 @@
const { DataTypes } = require("sequelize");
const sequelize = require("../../config/db.config");
const mdl_Users = require("../users/users.mdl");
const Unit = require("./units.mdl");
const sequelize = require("../../config/db.config");
const Lesson = sequelize.define("Lesson", {
lesson_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
uuid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, allowNull: false, unique: true },
unit_id: { type: DataTypes.BIGINT, allowNull: false },
title: { type: DataTypes.STRING(255), allowNull: false, label: "Title" },
description: { type: DataTypes.TEXT, allowNull: true, label: "Description" },
order_index: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0, label: "Order" },
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" },
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" },
}, {
tableName: "lessons",
tableName: "lessons",
timestamps: true,
paranoid: true,
paranoid: true,
indexes: [
{ fields: ["uuid"] },
{ fields: ["unit_id"] },
@@ -25,9 +27,4 @@ const Lesson = sequelize.define("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" });
Unit.hasMany(Lesson, { as: "lessons", foreignKey: "unit_id" });
module.exports = Lesson;
+15
View File
@@ -0,0 +1,15 @@
const { DataTypes } = require("sequelize");
const sequelize = require("../../config/db.config");
const QuizOption = sequelize.define("QuizOption", {
option_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
question_id: { type: DataTypes.BIGINT, allowNull: false },
text: { type: DataTypes.TEXT, allowNull: false },
is_correct: { type: DataTypes.BOOLEAN, defaultValue: false },
order_index: { type: DataTypes.INTEGER, defaultValue: 0 },
}, {
tableName: "quiz_options",
timestamps: true,
});
module.exports = QuizOption
+28
View File
@@ -0,0 +1,28 @@
const { DataTypes } = require("sequelize");
const sequelize = require("../../config/db.config");
const QuizQuestion = sequelize.define("QuizQuestion", {
question_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
uuid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, unique: true },
// polymorphic: belongs to either unit_quiz OR course_assessment
quiz_id: { type: DataTypes.BIGINT, allowNull: true }, // → unit_quizzes
assessment_id: { type: DataTypes.BIGINT, allowNull: true }, // → course_assessments
type: {
type: DataTypes.ENUM("true_false", "multiple_choice", "multi_select"),
allowNull: false,
},
question: { type: DataTypes.TEXT, allowNull: false },
explanation: { type: DataTypes.TEXT, allowNull: true }, // shown after answer
order_index: { type: DataTypes.INTEGER, defaultValue: 0 },
points: { type: DataTypes.INTEGER, defaultValue: 1 },
createdBy: { type: DataTypes.BIGINT, allowNull: true },
deletedBy: { type: DataTypes.BIGINT, allowNull: true },
}, {
tableName: "quiz_questions",
timestamps: true,
paranoid: true,
});
module.exports = QuizQuestion
+21
View File
@@ -0,0 +1,21 @@
const { DataTypes } = require("sequelize");
const sequelize = require("../../config/db.config");
const UnitQuiz = sequelize.define("UnitQuiz", {
quiz_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
uuid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, unique: true },
unit_id: { type: DataTypes.BIGINT, allowNull: false, unique: true }, // one quiz per unit
title: { type: DataTypes.STRING(255), allowNull: true },
is_required: { type: DataTypes.BOOLEAN, defaultValue: false },
passing_score: { type: DataTypes.INTEGER, defaultValue: 70 }, // percentage
max_questions: { type: DataTypes.INTEGER, allowNull: true }, // null = show all
createdBy: { type: DataTypes.BIGINT, allowNull: true },
updatedBy: { type: DataTypes.BIGINT, allowNull: true },
deletedBy: { type: DataTypes.BIGINT, allowNull: true },
}, {
tableName: "unit_quizzes",
timestamps: true,
paranoid: true,
});
module.exports = UnitQuiz
+16 -19
View File
@@ -1,22 +1,24 @@
const { DataTypes } = require("sequelize");
const sequelize = require("../../config/db.config");
const mdl_Users = require("../users/users.mdl");
const Course = require("./courses.mdl");
const sequelize = require("../../config/db.config");
const Unit = sequelize.define("Unit", {
unit_id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
uuid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, allowNull: false, unique: true },
course_id: { type: DataTypes.BIGINT, allowNull: false },
title: { type: DataTypes.STRING(255), allowNull: false, label: "Title" },
description: { type: DataTypes.TEXT, allowNull: true, label: "Description" },
order_index: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0, label: "Order" },
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" },
unit_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 },
course_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 },
order_index: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0, label: "Order", hidden: false, order: 3 },
duration_seconds: { type: DataTypes.INTEGER, allowNull: true, defaultValue: 0 },
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" },
}, {
tableName: "units",
tableName: "units",
timestamps: true,
paranoid: true,
paranoid: true,
indexes: [
{ fields: ["uuid"] },
{ fields: ["course_id"] },
@@ -25,9 +27,4 @@ const Unit = sequelize.define("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" });
Course.hasMany(Unit, { as: "units", foreignKey: "course_id" });
module.exports = Unit;