mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
123 lines
8.8 KiB
JavaScript
123 lines
8.8 KiB
JavaScript
const { DataTypes } = require("sequelize");
|
|
const sequelize = require("../../config/db.config");
|
|
const { mdl_UserGroups } = require('../users/user_groups.mdl');
|
|
|
|
const TaskList = sequelize.define('TaskList', {
|
|
task_list_id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true, hidden: true },
|
|
name: { type: DataTypes.STRING, allowNull: false, validate: { notEmpty: true }, filterable: true },
|
|
description: { type: DataTypes.TEXT, allowNull: true, hidden: true, filterable: false },
|
|
|
|
// ── Audit trails ────────────────────────────────────────────────────────
|
|
createdBy: { type: DataTypes.INTEGER, allowNull: true, filterable: true },
|
|
updatedBy: { type: DataTypes.INTEGER, allowNull: true, filterable: true },
|
|
deletedBy: { type: DataTypes.INTEGER, allowNull: true, filterable: true },
|
|
}, {
|
|
tableName: 'task_lists',
|
|
paranoid: true,
|
|
timestamps: true,
|
|
});
|
|
|
|
// ─── Junction: TaskList ↔ UserGroups ──────────────────────────────────────────
|
|
const TaskListGroup = sequelize.define('TaskListGroup', {
|
|
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
|
|
task_list_id: { type: DataTypes.UUID, allowNull: false, references: { model: 'task_lists', key: 'task_list_id' }, onDelete: 'CASCADE', },
|
|
group_id: { type: DataTypes.BIGINT, allowNull: false, references: { model: 'user_groups', key: 'group_id' }, onDelete: 'CASCADE', },
|
|
assignedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW, allowNull: false, filterable: true },
|
|
assignedBy: { type: DataTypes.BIGINT, allowNull: true, filterable: true },
|
|
}, {
|
|
tableName: 'task_list_groups',
|
|
timestamps: false,
|
|
indexes: [
|
|
{ unique: true, fields: ['task_list_id', 'group_id'], name: 'uq_task_list_group' },
|
|
{ fields: ['task_list_id'], name: 'idx_tlg_task_list_id' },
|
|
{ fields: ['group_id'], name: 'idx_tlg_group_id' },
|
|
],
|
|
});
|
|
|
|
|
|
const Task = sequelize.define('Task', {
|
|
task_id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true, hidden: true },
|
|
task_list_id: { type: DataTypes.UUID, allowNull: false, references: { model: 'task_lists', key: 'task_list_id' }, hidden: true, filterable: false },
|
|
name: { type: DataTypes.STRING, allowNull: false, validate: { notEmpty: true }, order: 1, filterable: true },
|
|
description: { type: DataTypes.TEXT, allowNull: true, hidden: true, filterable: false },
|
|
deadline: { type: DataTypes.DATE, allowNull: true, order: 2, filterable: true },
|
|
order_index: { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0, order: 2.1, filterable: true, comment: 'Position within the task list — display order only. Locking is driven solely by explicit task_prerequisites.' },
|
|
accepts_submissions: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true, order: 2.25, filterable: true, comment: 'When false, no new TaskCompletion submissions are accepted for this task (existing completions are unaffected).' },
|
|
status: { type: DataTypes.ENUM('pending', 'in_progress', 'completed', 'overdue'), defaultValue: 'pending', allowNull: false, filterable: true },
|
|
auto_marked_at: { type: DataTypes.DATE, allowNull: true, filterable: true, comment: 'Set only by the taskOverdue cron sweep when it auto-flips status; never touched by user-driven completion.' },
|
|
|
|
// ── Audit trails ────────────────────────────────────────────────────────
|
|
createdBy: { type: DataTypes.INTEGER, allowNull: true, filterable: true },
|
|
updatedBy: { type: DataTypes.INTEGER, allowNull: true, filterable: true },
|
|
deletedBy: { type: DataTypes.INTEGER, allowNull: true, filterable: true },
|
|
}, {
|
|
tableName: 'tasks',
|
|
paranoid: true,
|
|
timestamps: true,
|
|
});
|
|
|
|
const TaskRequirement = sequelize.define('TaskRequirement', {
|
|
requirement_id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
|
|
task_id: { type: DataTypes.UUID, allowNull: false, references: { model: 'tasks', key: 'task_id' } },
|
|
type: { type: DataTypes.ENUM('visit_link', 'upload_file', 'read_course', 'read_unit', 'read_lesson', 'submit_text'), allowNull: false, filterable: true },
|
|
|
|
// ── visit_link ──────────────────────────────────────────────────────────
|
|
link_url: { type: DataTypes.STRING, allowNull: true, filterable: false },
|
|
link_label: { type: DataTypes.STRING, allowNull: true, filterable: false },
|
|
|
|
// ── upload_file ─────────────────────────────────────────────────────────
|
|
allowed_file_types: { type: DataTypes.JSONB, allowNull: true, filterable: false },
|
|
max_file_count: { type: DataTypes.INTEGER, allowNull: true, defaultValue: 1, filterable: false },
|
|
|
|
// ── read_course / read_unit / read_lesson ──────────────────────────────────
|
|
reference_id: { type: DataTypes.UUID, allowNull: true, comment: 'course_id | unit_id | lesson_id depending on type', filterable: false },
|
|
reference_label: { type: DataTypes.STRING, allowNull: true, comment: 'Cached display name so we do not always join', filterable: false },
|
|
|
|
// ── submit_text ─────────────────────────────────────────────────────────
|
|
prompt: { type: DataTypes.TEXT, allowNull: true, comment: 'Instructions shown above the free-text response box.', filterable: false },
|
|
|
|
// ── upload_file / submit_text ──────────────────────────────────────────
|
|
requires_review: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false, filterable: true, comment: 'If true, a submission only counts as complete once an admin approves it.' },
|
|
|
|
order: { type: DataTypes.INTEGER, defaultValue: 0, filterable: true },
|
|
|
|
// ── Audit trails ────────────────────────────────────────────────────────
|
|
createdBy: { type: DataTypes.INTEGER, allowNull: true },
|
|
updatedBy: { type: DataTypes.INTEGER, allowNull: true },
|
|
deletedBy: { type: DataTypes.INTEGER, allowNull: true },
|
|
}, {
|
|
tableName: 'task_requirements',
|
|
paranoid: true,
|
|
timestamps: true,
|
|
});
|
|
|
|
// ─── Junction: Task ↔ Task (explicit prerequisite graph) ──────────────────────
|
|
const TaskPrerequisite = sequelize.define('TaskPrerequisite', {
|
|
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
|
|
task_id: { type: DataTypes.UUID, allowNull: false, references: { model: 'tasks', key: 'task_id' }, onDelete: 'CASCADE', },
|
|
prerequisite_task_id: { type: DataTypes.UUID, allowNull: false, references: { model: 'tasks', key: 'task_id' }, onDelete: 'CASCADE', },
|
|
}, {
|
|
tableName: 'task_prerequisites',
|
|
timestamps: true,
|
|
indexes: [
|
|
{ unique: true, fields: ['task_id', 'prerequisite_task_id'], name: 'uq_task_prerequisite' },
|
|
{ fields: ['task_id'], name: 'idx_tp_task_id' },
|
|
{ fields: ['prerequisite_task_id'], name: 'idx_tp_prerequisite_task_id' },
|
|
],
|
|
});
|
|
|
|
TaskList.hasMany(Task, { foreignKey: 'task_list_id', as: 'tasks' });
|
|
Task.belongsTo(TaskList, { foreignKey: 'task_list_id', as: 'taskList' });
|
|
Task.hasMany(TaskRequirement, { foreignKey: 'task_id', as: 'requirements' });
|
|
TaskRequirement.belongsTo(Task, { foreignKey: 'task_id', as: 'task' });
|
|
|
|
// TaskList ↔ UserGroups (many-to-many through TaskListGroup)
|
|
TaskList.belongsToMany(mdl_UserGroups, { through: TaskListGroup, foreignKey: 'task_list_id', otherKey: 'group_id', as: 'groups', });
|
|
mdl_UserGroups.belongsToMany(TaskList, { through: TaskListGroup, foreignKey: 'group_id', otherKey: 'task_list_id', as: 'taskLists', });
|
|
|
|
// Task ↔ Task (self-referential many-to-many through TaskPrerequisite)
|
|
// 'prerequisites' — tasks THIS task depends on. 'dependents' — tasks that depend on THIS task.
|
|
Task.belongsToMany(Task, { through: TaskPrerequisite, foreignKey: 'task_id', otherKey: 'prerequisite_task_id', as: 'prerequisites' });
|
|
Task.belongsToMany(Task, { through: TaskPrerequisite, foreignKey: 'prerequisite_task_id', otherKey: 'task_id', as: 'dependents' });
|
|
|
|
module.exports = { Task, TaskList, TaskRequirement, TaskListGroup, TaskPrerequisite, mdl_UserGroups }; |