mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
units,lesson as standalone
This commit is contained in:
@@ -8,7 +8,7 @@ const Lesson = sequelize.define("Lesson", {
|
||||
uuid: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, allowNull: false, unique: true, 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 },
|
||||
description: { type: DataTypes.TEXT, allowNull: true, label: "Description", hidden: true, order: 2 },
|
||||
|
||||
duration_seconds: { type: DataTypes.INTEGER, allowNull: true, defaultValue: 0 }, // computed from blocks on save
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ 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 },
|
||||
title: { type: DataTypes.STRING(255), allowNull: false, label: "Title", hidden: false, order: 1, filterable: true },
|
||||
subscription: { type: DataTypes.STRING(50), allowNull: true, label: "Subscription", hidden: false, order: 1.5, filterable: true, comment: "Optional direct tier gate for standalone units — null means open (or gated only via an attached course)." },
|
||||
description: { type: DataTypes.TEXT, allowNull: true, label: "Description", hidden: true, order: 0, 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 },
|
||||
|
||||
@@ -29,6 +29,19 @@ const AdminNotification = sequelize.define('AdminNotification', {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
// Controls where this notification is rendered in the admin UI.
|
||||
show_in_notifications: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
show_in_sticky: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
|
||||
data: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true,
|
||||
|
||||
@@ -13,6 +13,13 @@ const NotificationBroadcast = sequelize.define("NotificationBroadcast", {
|
||||
title: { type: DataTypes.STRING(255), allowNull: false, label: "Title", order: 1 },
|
||||
message: { type: DataTypes.TEXT, allowNull: false, label: "Message", order: 2 },
|
||||
|
||||
// ─── Visibility ──────────────────────────────────────────────────────────
|
||||
// Determines where a delivered announcement shows up for recipients.
|
||||
// - show_in_sticky: client sticky banner (fixed top)
|
||||
// - show_in_notifications: client + admin notifications lists/bells
|
||||
show_in_sticky: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false, label: "Show in Sticky Announcements", order: 2.5 },
|
||||
show_in_notifications: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true, label: "Show in Notifications", order: 2.6 },
|
||||
|
||||
// ─── Targeting ────────────────────────────────────────────────────────────
|
||||
target_type: {
|
||||
type: DataTypes.ENUM("admin", "user", "both", "task_list", "course", "tier_plan"),
|
||||
|
||||
@@ -33,6 +33,21 @@ const UserNotification = sequelize.define('UserNotification', {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
// Controls where this notification is rendered.
|
||||
// - show_in_notifications: appears in the /notifications list + bell badge.
|
||||
// - show_in_sticky: appears in the fixed "sticky announcement" banner.
|
||||
show_in_notifications: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
show_in_sticky: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
|
||||
data: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true,
|
||||
|
||||
+12
-3
@@ -41,6 +41,8 @@ const Task = sequelize.define('Task', {
|
||||
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 — drives sequencing lock.' },
|
||||
is_required: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true, order: 2.2, filterable: true, comment: 'Optional tasks do not block later tasks in the sequencing lock.' },
|
||||
status: { type: DataTypes.ENUM('pending', 'in_progress', 'completed', 'overdue'), defaultValue: 'pending', allowNull: false, filterable: true },
|
||||
|
||||
// ── Audit trails ────────────────────────────────────────────────────────
|
||||
@@ -56,7 +58,7 @@ const Task = sequelize.define('Task', {
|
||||
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'), allowNull: false, filterable: true },
|
||||
type: { type: DataTypes.ENUM('visit_link', 'upload_file', 'read_course', 'read_unit', 'read_lesson', 'submit_text', 'pass_quiz'), allowNull: false, filterable: true },
|
||||
|
||||
// ── visit_link ──────────────────────────────────────────────────────────
|
||||
link_url: { type: DataTypes.STRING, allowNull: true, filterable: false },
|
||||
@@ -66,9 +68,16 @@ const TaskRequirement = sequelize.define('TaskRequirement', {
|
||||
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 },
|
||||
// ── read_course / read_unit / read_lesson / pass_quiz ─────────────────────
|
||||
reference_id: { type: DataTypes.UUID, allowNull: true, comment: 'course_id | unit_id | lesson_id | quiz_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 ────────────────────────────────────────────────────────
|
||||
|
||||
@@ -42,13 +42,31 @@ const TaskCompletion = sequelize.define('TaskCompletion', {
|
||||
comment: 'Optional note the user attaches when submitting.',
|
||||
order: 1,
|
||||
},
|
||||
response_text: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'The learner\'s free-text answer, for submit_text requirements.',
|
||||
order: 1.5,
|
||||
},
|
||||
submitted_at: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
allowNull: false,
|
||||
order: 2,
|
||||
},
|
||||
|
||||
|
||||
// ── Review workflow (requires_review requirements only) ──────────────────
|
||||
status: {
|
||||
type: DataTypes.ENUM('submitted', 'approved', 'rejected'),
|
||||
allowNull: false,
|
||||
defaultValue: 'submitted',
|
||||
order: 2.1,
|
||||
filterable: true,
|
||||
},
|
||||
reviewed_by: { type: DataTypes.BIGINT, allowNull: true, order: 2.2 },
|
||||
reviewed_at: { type: DataTypes.DATE, allowNull: true, order: 2.3 },
|
||||
review_note: { type: DataTypes.TEXT, allowNull: true, comment: 'Optional note the admin leaves when approving/rejecting.', order: 2.4 },
|
||||
|
||||
// ── Audit trails ────────────────────────────────────────────────────────
|
||||
createdBy: { type: DataTypes.BIGINT, allowNull: true, order: 6 },
|
||||
updatedBy: { type: DataTypes.BIGINT, allowNull: true, order: 7 },
|
||||
|
||||
@@ -99,10 +99,10 @@ const TaskProgress = sequelize.define('TaskProgress', {
|
||||
reference_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
comment: 'course_id | unit_id | lesson_id depending on type.',
|
||||
comment: 'course_id | unit_id | lesson_id | quiz_id depending on type.',
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.ENUM('read_course', 'read_unit', 'read_lesson'),
|
||||
type: DataTypes.ENUM('read_course', 'read_unit', 'read_lesson', 'pass_quiz'),
|
||||
allowNull: false,
|
||||
},
|
||||
completed: {
|
||||
|
||||
@@ -15,6 +15,7 @@ const mdl_TierPlans = sequelize.define('TierPlan', {
|
||||
tier: { type: DataTypes.STRING(50), allowNull: false, label: 'Tier' },
|
||||
label: { type: DataTypes.STRING(100), allowNull: false, label: 'Plan Label' },
|
||||
description: { type: DataTypes.TEXT, allowNull: true, label: 'Description' },
|
||||
features: { type: DataTypes.JSONB, allowNull: true, label: 'Features', comment: 'Array of {text} — admin-authored "what\'s included" bullets shown on the client Plans page.' },
|
||||
duration_days: { type: DataTypes.FLOAT, allowNull: false, label: 'Duration (Days)' },
|
||||
duration_unit: { type: DataTypes.STRING(10), allowNull: false, defaultValue: 'day', label: 'Duration Unit' },
|
||||
price: { type: DataTypes.DECIMAL(10, 2), allowNull: false, label: 'Price' },
|
||||
|
||||
Reference in New Issue
Block a user