mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
ready to test
Testing Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('users', {
|
||||
user_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
email: { type: Sequelize.STRING(255), allowNull: false, unique: true },
|
||||
password: { type: Sequelize.TEXT },
|
||||
is_active: { type: Sequelize.BOOLEAN, defaultValue: true },
|
||||
is_verified: { type: Sequelize.BOOLEAN, defaultValue: false },
|
||||
reg_type: { type: Sequelize.ENUM('google', 'system'), defaultValue: 'system' },
|
||||
acc_type: { type: Sequelize.ENUM('admin', 'staff', 'user'), defaultValue: 'user' },
|
||||
personal_info: { type: Sequelize.JSONB, defaultValue: null },
|
||||
must_change_password: { type: Sequelize.BOOLEAN, defaultValue: false, allowNull: false },
|
||||
password_expires_at: { type: Sequelize.DATE, allowNull: true },
|
||||
otp_code: { type: Sequelize.STRING(6), allowNull: true },
|
||||
otp_expires_at: { type: Sequelize.DATE, allowNull: true },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('users');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('assets', {
|
||||
asset_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
uuid: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, allowNull: false, unique: true },
|
||||
original_name: { type: Sequelize.STRING(255), allowNull: false },
|
||||
display_name: { type: Sequelize.STRING(255), allowNull: false },
|
||||
file_url: { type: Sequelize.STRING(512), allowNull: false },
|
||||
file_size: { type: Sequelize.BIGINT, allowNull: false },
|
||||
mime_type: { type: Sequelize.STRING(100), allowNull: false },
|
||||
extension: { type: Sequelize.STRING(20) },
|
||||
checksum: { type: Sequelize.STRING(64) },
|
||||
file_type: { type: Sequelize.ENUM('avatar', 'document', 'video', 'image'), allowNull: false, defaultValue: 'image' },
|
||||
width: { type: Sequelize.INTEGER },
|
||||
height: { type: Sequelize.INTEGER },
|
||||
duration: { type: Sequelize.FLOAT },
|
||||
resolution: { type: Sequelize.STRING(20) },
|
||||
frame_rate: { type: Sequelize.FLOAT },
|
||||
bitrate: { type: Sequelize.BIGINT },
|
||||
video_codec: { type: Sequelize.STRING(50) },
|
||||
audio_codec: { type: Sequelize.STRING(50) },
|
||||
thumbnail_url: { type: Sequelize.STRING(512) },
|
||||
description: { type: Sequelize.TEXT },
|
||||
storage_provider: { type: Sequelize.ENUM('local', 's3', 'gcs', 'cloudinary', 'chibisafe', 'other'), defaultValue: 'local' },
|
||||
storage_bucket: { type: Sequelize.STRING(255) },
|
||||
storage_key: { type: Sequelize.STRING(512) },
|
||||
is_public: { type: Sequelize.BOOLEAN, defaultValue: false },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('assets', ['uuid']);
|
||||
await queryInterface.addIndex('assets', ['createdBy']);
|
||||
await queryInterface.addIndex('assets', ['file_type']);
|
||||
await queryInterface.addIndex('assets', ['deletedAt']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('assets');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('user_sessions', {
|
||||
session_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
user_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'users', key: 'user_id' }, onUpdate: 'CASCADE', onDelete: 'CASCADE' },
|
||||
login_info: { type: Sequelize.JSONB, allowNull: true },
|
||||
logout_info: { type: Sequelize.JSONB, allowNull: true },
|
||||
refresh_token_hash: { type: Sequelize.TEXT, allowNull: true },
|
||||
is_active: { type: Sequelize.BOOLEAN, defaultValue: true },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('user_sessions', ['user_id']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('user_sessions');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('user_groups', {
|
||||
group_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
name: { type: Sequelize.STRING(50), allowNull: false },
|
||||
group_code: { type: Sequelize.STRING(50), allowNull: true, unique: true },
|
||||
description: { type: Sequelize.TEXT },
|
||||
is_active: { type: Sequelize.BOOLEAN, defaultValue: true },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('user_groups');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('user_group_members', {
|
||||
group_id: { type: Sequelize.BIGINT, primaryKey: true, references: { model: 'user_groups', key: 'group_id' }, onDelete: 'CASCADE' },
|
||||
user_id: { type: Sequelize.BIGINT, primaryKey: true, references: { model: 'users', key: 'user_id' }, onDelete: 'CASCADE' },
|
||||
joined_at: { type: Sequelize.DATE, defaultValue: Sequelize.NOW },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('user_group_members', ['user_id']);
|
||||
await queryInterface.addIndex('user_group_members', ['group_id']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('user_group_members');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('achievements', {
|
||||
achievement_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
user_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'users', key: 'user_id' }, onDelete: 'CASCADE' },
|
||||
type: { type: Sequelize.ENUM('badge', 'milestone'), allowNull: false, defaultValue: 'badge' },
|
||||
key: { type: Sequelize.STRING(100), allowNull: false },
|
||||
label: { type: Sequelize.STRING(255), allowNull: false },
|
||||
description: { type: Sequelize.TEXT, allowNull: true },
|
||||
granted_by: { type: Sequelize.BIGINT, allowNull: true },
|
||||
granted_at: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.NOW },
|
||||
metadata: { type: Sequelize.JSONB, allowNull: true, defaultValue: {} },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('achievements', ['user_id']);
|
||||
await queryInterface.addIndex('achievements', { fields: ['user_id', 'key'], unique: true, name: 'uq_achievement_user_key' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('achievements');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('courses', {
|
||||
course_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
uuid: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, allowNull: false, unique: true },
|
||||
title: { type: Sequelize.STRING(255), allowNull: false },
|
||||
description: { type: Sequelize.TEXT, allowNull: true },
|
||||
course_code: { type: Sequelize.STRING(50), allowNull: true, unique: true },
|
||||
order_index: { type: Sequelize.INTEGER, allowNull: false, defaultValue: 0 },
|
||||
level: { type: Sequelize.ENUM('beginner', 'intermediate', 'advanced'), allowNull: true },
|
||||
subscription: { type: Sequelize.ENUM('free', 'premium'), allowNull: false, defaultValue: 'free' },
|
||||
duration_seconds: { type: Sequelize.INTEGER, allowNull: true, defaultValue: 0 },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('courses', ['uuid']);
|
||||
await queryInterface.addIndex('courses', ['subscription']);
|
||||
await queryInterface.addIndex('courses', ['deletedAt']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('courses');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('course_products', {
|
||||
id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
course_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'courses', key: 'course_id' }, onDelete: 'CASCADE' },
|
||||
product_id: { type: Sequelize.BIGINT, allowNull: false },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.createTable('course_roles', {
|
||||
id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
course_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'courses', key: 'course_id' }, onDelete: 'CASCADE' },
|
||||
role_id: { type: Sequelize.BIGINT, allowNull: false },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.createTable('course_product_categories', {
|
||||
id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
course_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'courses', key: 'course_id' }, onDelete: 'CASCADE' },
|
||||
category_id: { type: Sequelize.BIGINT, allowNull: false },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('course_product_categories');
|
||||
await queryInterface.dropTable('course_roles');
|
||||
await queryInterface.dropTable('course_products');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('course_objectives', {
|
||||
objective_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
course_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'courses', key: 'course_id' }, onDelete: 'CASCADE' },
|
||||
text: { type: Sequelize.TEXT, allowNull: false },
|
||||
order_index: { type: Sequelize.INTEGER, defaultValue: 0 },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('course_objectives', ['course_id']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('course_objectives');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('course_prerequisites', {
|
||||
prereq_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
course_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'courses', key: 'course_id' }, onDelete: 'CASCADE' },
|
||||
ref_type: { type: Sequelize.ENUM('course', 'unit', 'lesson'), allowNull: false },
|
||||
ref_id: { type: Sequelize.BIGINT, allowNull: false },
|
||||
order_index: { type: Sequelize.INTEGER, defaultValue: 0 },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('course_prerequisites', ['course_id']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('course_prerequisites');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('course_assessments', {
|
||||
assessment_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
uuid: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, unique: true },
|
||||
course_id: { type: Sequelize.BIGINT, allowNull: false, unique: true, references: { model: 'courses', key: 'course_id' }, onDelete: 'CASCADE' },
|
||||
title: { type: Sequelize.STRING(255), allowNull: true },
|
||||
is_required: { type: Sequelize.BOOLEAN, defaultValue: false },
|
||||
passing_score: { type: Sequelize.INTEGER, defaultValue: 70 },
|
||||
time_limit_minutes: { type: Sequelize.INTEGER, allowNull: true },
|
||||
max_questions: { type: Sequelize.INTEGER, allowNull: true },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('course_assessments');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('units', {
|
||||
unit_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
uuid: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, allowNull: false, unique: true },
|
||||
course_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'courses', key: 'course_id' }, onDelete: 'CASCADE' },
|
||||
title: { type: Sequelize.STRING(255), allowNull: false },
|
||||
description: { type: Sequelize.TEXT, allowNull: true },
|
||||
order_index: { type: Sequelize.INTEGER, allowNull: false, defaultValue: 0 },
|
||||
duration_seconds: { type: Sequelize.INTEGER, allowNull: true, defaultValue: 0 },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('units', ['uuid']);
|
||||
await queryInterface.addIndex('units', ['course_id']);
|
||||
await queryInterface.addIndex('units', ['order_index']);
|
||||
await queryInterface.addIndex('units', ['deletedAt']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('units');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('unit_quizzes', {
|
||||
quiz_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
uuid: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, unique: true },
|
||||
unit_id: { type: Sequelize.BIGINT, allowNull: false, unique: true, references: { model: 'units', key: 'unit_id' }, onDelete: 'CASCADE' },
|
||||
title: { type: Sequelize.STRING(255), allowNull: true },
|
||||
is_required: { type: Sequelize.BOOLEAN, defaultValue: false },
|
||||
passing_score: { type: Sequelize.INTEGER, defaultValue: 70 },
|
||||
max_questions: { type: Sequelize.INTEGER, allowNull: true },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('unit_quizzes');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('lessons', {
|
||||
lesson_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
uuid: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, allowNull: false, unique: true },
|
||||
unit_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'units', key: 'unit_id' }, onDelete: 'CASCADE' },
|
||||
title: { type: Sequelize.STRING(255), allowNull: false },
|
||||
description: { type: Sequelize.TEXT, allowNull: true },
|
||||
duration_seconds: { type: Sequelize.INTEGER, allowNull: true, defaultValue: 0 },
|
||||
order_index: { type: Sequelize.INTEGER, allowNull: false, defaultValue: 0 },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('lessons', ['uuid']);
|
||||
await queryInterface.addIndex('lessons', ['unit_id']);
|
||||
await queryInterface.addIndex('lessons', ['order_index']);
|
||||
await queryInterface.addIndex('lessons', ['deletedAt']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('lessons');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('lesson_pages', {
|
||||
page_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
lesson_id: { type: Sequelize.BIGINT, allowNull: false, unique: true, references: { model: 'lessons', key: 'lesson_id' }, onDelete: 'CASCADE' },
|
||||
blocks: { type: Sequelize.JSONB, allowNull: false, defaultValue: [] },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('lesson_pages');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('lesson_objectives', {
|
||||
objective_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
lesson_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'lessons', key: 'lesson_id' }, onDelete: 'CASCADE' },
|
||||
text: { type: Sequelize.TEXT, allowNull: false },
|
||||
order_index: { type: Sequelize.INTEGER, defaultValue: 0 },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('lesson_objectives', ['lesson_id']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('lesson_objectives');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('quiz_questions', {
|
||||
question_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
uuid: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, unique: true },
|
||||
quiz_id: { type: Sequelize.BIGINT, allowNull: true, references: { model: 'unit_quizzes', key: 'quiz_id' }, onDelete: 'CASCADE' },
|
||||
assessment_id: { type: Sequelize.BIGINT, allowNull: true, references: { model: 'course_assessments', key: 'assessment_id' }, onDelete: 'CASCADE' },
|
||||
type: { type: Sequelize.ENUM('true_false', 'multiple_choice', 'multi_select'), allowNull: false },
|
||||
question: { type: Sequelize.TEXT, allowNull: false },
|
||||
explanation: { type: Sequelize.TEXT, allowNull: true },
|
||||
order_index: { type: Sequelize.INTEGER, defaultValue: 0 },
|
||||
points: { type: Sequelize.INTEGER, defaultValue: 1 },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('quiz_questions', ['quiz_id']);
|
||||
await queryInterface.addIndex('quiz_questions', ['assessment_id']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('quiz_questions');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('quiz_options', {
|
||||
option_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
question_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'quiz_questions', key: 'question_id' }, onDelete: 'CASCADE' },
|
||||
text: { type: Sequelize.TEXT, allowNull: false },
|
||||
is_correct: { type: Sequelize.BOOLEAN, defaultValue: false },
|
||||
order_index: { type: Sequelize.INTEGER, defaultValue: 0 },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('quiz_options', ['question_id']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('quiz_options');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('quiz_attempts', {
|
||||
attempt_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
uuid: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, unique: true },
|
||||
user_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'users', key: 'user_id' }, onDelete: 'CASCADE' },
|
||||
quiz_id: { type: Sequelize.BIGINT, allowNull: true, references: { model: 'unit_quizzes', key: 'quiz_id' }, onDelete: 'SET NULL' },
|
||||
assessment_id: { type: Sequelize.BIGINT, allowNull: true, references: { model: 'course_assessments', key: 'assessment_id' }, onDelete: 'SET NULL' },
|
||||
answers: { type: Sequelize.JSONB, allowNull: false, defaultValue: {} },
|
||||
total_points: { type: Sequelize.INTEGER, allowNull: false, defaultValue: 0 },
|
||||
earned_points: { type: Sequelize.INTEGER, allowNull: false, defaultValue: 0 },
|
||||
score: { type: Sequelize.INTEGER, allowNull: false, defaultValue: 0 },
|
||||
passed: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: false },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('quiz_attempts', ['user_id']);
|
||||
await queryInterface.addIndex('quiz_attempts', ['quiz_id']);
|
||||
await queryInterface.addIndex('quiz_attempts', ['assessment_id']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('quiz_attempts');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('tier_plans', {
|
||||
plan_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
tier: { type: Sequelize.ENUM('premium', 'exclusive'), allowNull: false },
|
||||
label: { type: Sequelize.STRING(100), allowNull: false },
|
||||
duration_days: { type: Sequelize.INTEGER, allowNull: false },
|
||||
price: { type: Sequelize.DECIMAL(10, 2), allowNull: false },
|
||||
currency: { type: Sequelize.CHAR(3), allowNull: false, defaultValue: 'USD' },
|
||||
is_active: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('tier_plans');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('plan_courses', {
|
||||
id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
plan_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'tier_plans', key: 'plan_id' }, onDelete: 'CASCADE' },
|
||||
course_id: { type: Sequelize.BIGINT, allowNull: false, unique: true, references: { model: 'courses', key: 'course_id' }, onDelete: 'CASCADE' },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('plan_courses', ['plan_id']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('plan_courses');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('user_tiers', {
|
||||
tier_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
user_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'users', key: 'user_id' }, onDelete: 'CASCADE' },
|
||||
tier: { type: Sequelize.ENUM('free', 'premium', 'exclusive'), allowNull: false, defaultValue: 'free' },
|
||||
status: { type: Sequelize.ENUM('active', 'expired', 'revoked'), allowNull: false, defaultValue: 'active' },
|
||||
starts_at: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.NOW },
|
||||
expires_at: { type: Sequelize.DATE, allowNull: true },
|
||||
granted_by: { type: Sequelize.BIGINT, allowNull: true },
|
||||
revoked_by: { type: Sequelize.BIGINT, allowNull: true },
|
||||
revoked_at: { type: Sequelize.DATE, allowNull: true },
|
||||
notes: { type: Sequelize.TEXT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('user_tiers', ['user_id']);
|
||||
await queryInterface.addIndex('user_tiers', ['status']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('user_tiers');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('payments', {
|
||||
payment_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
user_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'users', key: 'user_id' }, onDelete: 'CASCADE' },
|
||||
plan_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'tier_plans', key: 'plan_id' }, onDelete: 'RESTRICT' },
|
||||
tier_id: { type: Sequelize.BIGINT, allowNull: true, references: { model: 'user_tiers', key: 'tier_id' }, onDelete: 'SET NULL' },
|
||||
status: { type: Sequelize.ENUM('pending', 'completed', 'failed', 'cancelled', 'expired', 'refunded'), allowNull: false, defaultValue: 'pending' },
|
||||
amount: { type: Sequelize.DECIMAL(10, 2), allowNull: false },
|
||||
currency: { type: Sequelize.STRING(10), allowNull: false, defaultValue: 'USD' },
|
||||
promo_code: { type: Sequelize.STRING(50), allowNull: true },
|
||||
discount: { type: Sequelize.DECIMAL(10, 2), allowNull: false, defaultValue: 0.00 },
|
||||
provider: { type: Sequelize.STRING(50), allowNull: false, defaultValue: 'paypal' },
|
||||
provider_payload: { type: Sequelize.JSONB, allowNull: true, defaultValue: {} },
|
||||
paid_at: { type: Sequelize.DATE, allowNull: true },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('payments', ['user_id']);
|
||||
await queryInterface.addIndex('payments', ['status']);
|
||||
await queryInterface.addIndex('payments', ['plan_id']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('payments');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('advertisements', {
|
||||
advertisement_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
uuid: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, allowNull: false, unique: true },
|
||||
type: { type: Sequelize.ENUM('hero', 'banner', 'popup', 'sidebar'), allowNull: false },
|
||||
status: { type: Sequelize.ENUM('draft', 'active', 'scheduled', 'expired', 'archived'), allowNull: false, defaultValue: 'draft' },
|
||||
badge_label: { type: Sequelize.STRING(100) },
|
||||
headline: { type: Sequelize.STRING(255) },
|
||||
description: { type: Sequelize.TEXT },
|
||||
image_url: { type: Sequelize.STRING(512) },
|
||||
image_asset_id: { type: Sequelize.BIGINT, allowNull: true, references: { model: 'assets', key: 'asset_id' }, onDelete: 'SET NULL' },
|
||||
ctas: { type: Sequelize.JSONB, allowNull: false, defaultValue: [] },
|
||||
start_date: { type: Sequelize.DATE },
|
||||
end_date: { type: Sequelize.DATE },
|
||||
order: { type: Sequelize.INTEGER, allowNull: false, defaultValue: 0 },
|
||||
is_active: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true },
|
||||
size: { type: Sequelize.ENUM('sm', 'md', 'lg'), allowNull: true },
|
||||
click_count: { type: Sequelize.INTEGER, allowNull: false, defaultValue: 0 },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('advertisements', ['uuid']);
|
||||
await queryInterface.addIndex('advertisements', ['type']);
|
||||
await queryInterface.addIndex('advertisements', ['status']);
|
||||
await queryInterface.addIndex('advertisements', ['is_active']);
|
||||
await queryInterface.addIndex('advertisements', ['deletedAt']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('advertisements');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('task_lists', {
|
||||
task_list_id: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, primaryKey: true },
|
||||
name: { type: Sequelize.STRING, allowNull: false },
|
||||
description: { type: Sequelize.TEXT, allowNull: true },
|
||||
createdBy: { type: Sequelize.INTEGER, allowNull: true },
|
||||
updatedBy: { type: Sequelize.INTEGER, allowNull: true },
|
||||
deletedBy: { type: Sequelize.INTEGER, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('task_lists');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('task_list_groups', {
|
||||
id: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, primaryKey: true },
|
||||
task_list_id: { type: Sequelize.UUID, allowNull: false, references: { model: 'task_lists', key: 'task_list_id' }, onDelete: 'CASCADE' },
|
||||
group_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'user_groups', key: 'group_id' }, onDelete: 'CASCADE' },
|
||||
assignedAt: { type: Sequelize.DATE, defaultValue: Sequelize.NOW, allowNull: false },
|
||||
assignedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('task_list_groups', { fields: ['task_list_id', 'group_id'], unique: true, name: 'uq_task_list_group' });
|
||||
await queryInterface.addIndex('task_list_groups', { fields: ['task_list_id'], name: 'idx_tlg_task_list_id' });
|
||||
await queryInterface.addIndex('task_list_groups', { fields: ['group_id'], name: 'idx_tlg_group_id' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('task_list_groups');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('tasks', {
|
||||
task_id: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, primaryKey: true },
|
||||
task_list_id: { type: Sequelize.UUID, allowNull: false, references: { model: 'task_lists', key: 'task_list_id' }, onDelete: 'CASCADE' },
|
||||
name: { type: Sequelize.STRING, allowNull: false },
|
||||
description: { type: Sequelize.TEXT, allowNull: true },
|
||||
deadline: { type: Sequelize.DATE, allowNull: true },
|
||||
status: { type: Sequelize.ENUM('pending', 'in_progress', 'completed', 'overdue'), defaultValue: 'pending', allowNull: false },
|
||||
createdBy: { type: Sequelize.INTEGER, allowNull: true },
|
||||
updatedBy: { type: Sequelize.INTEGER, allowNull: true },
|
||||
deletedBy: { type: Sequelize.INTEGER, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('tasks', ['task_list_id']);
|
||||
await queryInterface.addIndex('tasks', ['status']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('tasks');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('task_requirements', {
|
||||
requirement_id: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, primaryKey: true },
|
||||
task_id: { type: Sequelize.UUID, allowNull: false, references: { model: 'tasks', key: 'task_id' }, onDelete: 'CASCADE' },
|
||||
type: { type: Sequelize.ENUM('visit_link', 'upload_file', 'read_course', 'read_unit', 'read_lesson'), allowNull: false },
|
||||
link_url: { type: Sequelize.STRING, allowNull: true },
|
||||
link_label: { type: Sequelize.STRING, allowNull: true },
|
||||
allowed_file_types: { type: Sequelize.JSONB, allowNull: true },
|
||||
max_file_count: { type: Sequelize.INTEGER, allowNull: true, defaultValue: 1 },
|
||||
reference_id: { type: Sequelize.UUID, allowNull: true },
|
||||
reference_label: { type: Sequelize.STRING, allowNull: true },
|
||||
order: { type: Sequelize.INTEGER, defaultValue: 0 },
|
||||
createdBy: { type: Sequelize.INTEGER, allowNull: true },
|
||||
updatedBy: { type: Sequelize.INTEGER, allowNull: true },
|
||||
deletedBy: { type: Sequelize.INTEGER, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('task_requirements', ['task_id']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('task_requirements');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('task_completions', {
|
||||
completion_id: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, primaryKey: true },
|
||||
task_id: { type: Sequelize.UUID, allowNull: false, references: { model: 'tasks', key: 'task_id' }, onDelete: 'CASCADE' },
|
||||
user_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'users', key: 'user_id' }, onDelete: 'CASCADE' },
|
||||
note: { type: Sequelize.TEXT, allowNull: true },
|
||||
submitted_at: { type: Sequelize.DATE, defaultValue: Sequelize.NOW, allowNull: false },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('task_completions', { fields: ['task_id'], name: 'idx_tc_task_id' });
|
||||
await queryInterface.addIndex('task_completions', { fields: ['user_id'], name: 'idx_tc_user_id' });
|
||||
await queryInterface.addIndex('task_completions', { fields: ['task_id', 'user_id'], name: 'idx_tc_task_user' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('task_completions');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('task_completion_files', {
|
||||
file_id: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, primaryKey: true },
|
||||
completion_id: { type: Sequelize.UUID, allowNull: false, references: { model: 'task_completions', key: 'completion_id' }, onDelete: 'CASCADE' },
|
||||
file_url: { type: Sequelize.STRING, allowNull: false },
|
||||
file_name: { type: Sequelize.STRING, allowNull: false },
|
||||
file_size: { type: Sequelize.INTEGER, allowNull: true },
|
||||
mime_type: { type: Sequelize.STRING, allowNull: true },
|
||||
storage_key: { type: Sequelize.STRING(500), allowNull: true },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
deletedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('task_completion_files', { fields: ['completion_id'], name: 'idx_tcf_completion_id' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('task_completion_files');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('task_link_visits', {
|
||||
visit_id: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, primaryKey: true },
|
||||
task_id: { type: Sequelize.UUID, allowNull: false, references: { model: 'tasks', key: 'task_id' }, onDelete: 'CASCADE' },
|
||||
requirement_id: { type: Sequelize.UUID, allowNull: false, references: { model: 'task_requirements', key: 'requirement_id' }, onDelete: 'CASCADE' },
|
||||
user_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'users', key: 'user_id' }, onDelete: 'CASCADE' },
|
||||
visited_at: { type: Sequelize.DATE, defaultValue: Sequelize.NOW, allowNull: false },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('task_link_visits', { fields: ['requirement_id', 'user_id'], unique: true, name: 'uq_tlv_requirement_user' });
|
||||
await queryInterface.addIndex('task_link_visits', { fields: ['task_id'], name: 'idx_tlv_task_id' });
|
||||
await queryInterface.addIndex('task_link_visits', { fields: ['user_id'], name: 'idx_tlv_user_id' });
|
||||
await queryInterface.addIndex('task_link_visits', { fields: ['requirement_id'], name: 'idx_tlv_requirement_id' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('task_link_visits');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('task_progress', {
|
||||
progress_id: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, primaryKey: true },
|
||||
task_id: { type: Sequelize.UUID, allowNull: false, references: { model: 'tasks', key: 'task_id' }, onDelete: 'CASCADE' },
|
||||
requirement_id: { type: Sequelize.UUID, allowNull: false, references: { model: 'task_requirements', key: 'requirement_id' }, onDelete: 'CASCADE' },
|
||||
user_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'users', key: 'user_id' }, onDelete: 'CASCADE' },
|
||||
reference_id: { type: Sequelize.UUID, allowNull: false },
|
||||
type: { type: Sequelize.ENUM('read_course', 'read_unit', 'read_lesson'), allowNull: false },
|
||||
completed: { type: Sequelize.BOOLEAN, defaultValue: false, allowNull: false },
|
||||
completed_at: { type: Sequelize.DATE, allowNull: true },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('task_progress', { fields: ['requirement_id', 'user_id', 'reference_id'], unique: true, name: 'uq_tp_requirement_user_reference' });
|
||||
await queryInterface.addIndex('task_progress', { fields: ['task_id'], name: 'idx_tp_task_id' });
|
||||
await queryInterface.addIndex('task_progress', { fields: ['user_id'], name: 'idx_tp_user_id' });
|
||||
await queryInterface.addIndex('task_progress', { fields: ['requirement_id'], name: 'idx_tp_requirement_id' });
|
||||
await queryInterface.addIndex('task_progress', { fields: ['type'], name: 'idx_tp_type' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('task_progress');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('admin_notifications', {
|
||||
notification_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
type: { type: Sequelize.STRING(64), allowNull: false },
|
||||
title: { type: Sequelize.STRING(255), allowNull: false },
|
||||
message: { type: Sequelize.TEXT, allowNull: false },
|
||||
data: { type: Sequelize.JSONB, allowNull: true, defaultValue: null },
|
||||
seen: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: false },
|
||||
seen_at: { type: Sequelize.DATE, allowNull: true, defaultValue: null },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('admin_notifications', { fields: ['seen'], name: 'idx_an_seen' });
|
||||
await queryInterface.addIndex('admin_notifications', { fields: ['type'], name: 'idx_an_type' });
|
||||
await queryInterface.addIndex('admin_notifications', { fields: ['createdAt'], name: 'idx_an_created_at' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('admin_notifications');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('user_notifications', {
|
||||
notification_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
user_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'users', key: 'user_id' }, onDelete: 'CASCADE' },
|
||||
type: { type: Sequelize.STRING(64), allowNull: false },
|
||||
title: { type: Sequelize.STRING(255), allowNull: false },
|
||||
message: { type: Sequelize.TEXT, allowNull: false },
|
||||
data: { type: Sequelize.JSONB, allowNull: true, defaultValue: null },
|
||||
seen: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: false },
|
||||
seen_at: { type: Sequelize.DATE, allowNull: true, defaultValue: null },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('user_notifications', { fields: ['user_id'], name: 'idx_un_user_id' });
|
||||
await queryInterface.addIndex('user_notifications', { fields: ['user_id', 'seen'], name: 'idx_un_seen' });
|
||||
await queryInterface.addIndex('user_notifications', { fields: ['type'], name: 'idx_un_type' });
|
||||
await queryInterface.addIndex('user_notifications', { fields: ['createdAt'], name: 'idx_un_created_at' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('user_notifications');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('categories', {
|
||||
id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
name: { type: Sequelize.STRING(100), allowNull: false, unique: true },
|
||||
slug: { type: Sequelize.STRING(120), allowNull: false, unique: true },
|
||||
description: { type: Sequelize.TEXT, allowNull: true },
|
||||
is_active: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('categories');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('products', {
|
||||
id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
course_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'courses', key: 'course_id' }, onDelete: 'CASCADE' },
|
||||
name: { type: Sequelize.STRING(200), allowNull: false },
|
||||
description: { type: Sequelize.TEXT, allowNull: true },
|
||||
price: { type: Sequelize.DECIMAL(10, 2), allowNull: false },
|
||||
currency: { type: Sequelize.STRING(10), allowNull: false, defaultValue: 'USD' },
|
||||
access_days: { type: Sequelize.INTEGER, allowNull: true }, // null = lifetime access
|
||||
is_active: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('products', ['course_id']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('products');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('course_purchases', {
|
||||
id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
user_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'users', key: 'user_id' }, onDelete: 'CASCADE' },
|
||||
product_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'products', key: 'id' }, onDelete: 'RESTRICT' },
|
||||
amount: { type: Sequelize.DECIMAL(10, 2), allowNull: false },
|
||||
currency: { type: Sequelize.STRING(10), allowNull: false, defaultValue: 'USD' },
|
||||
status: { type: Sequelize.ENUM('pending', 'completed', 'failed', 'cancelled', 'refunded'), allowNull: false, defaultValue: 'pending' },
|
||||
provider: { type: Sequelize.STRING(50), allowNull: false, defaultValue: 'paypal' },
|
||||
provider_payload: { type: Sequelize.JSONB, allowNull: true, defaultValue: {} },
|
||||
expires_at: { type: Sequelize.DATE, allowNull: true }, // null = lifetime access
|
||||
paid_at: { type: Sequelize.DATE, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
deletedAt: { type: Sequelize.DATE, allowNull: true },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('course_purchases', ['user_id']);
|
||||
await queryInterface.addIndex('course_purchases', ['product_id']);
|
||||
await queryInterface.addIndex('course_purchases', ['status']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('course_purchases');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
// Wires up the bare product_id and category_id columns that were left
|
||||
// as plain integers in migration 000008.
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
await queryInterface.addConstraint('course_products', {
|
||||
fields: ['product_id'],
|
||||
type: 'foreign key',
|
||||
name: 'fk_course_products_product_id',
|
||||
references: { table: 'products', field: 'id' },
|
||||
onDelete: 'CASCADE',
|
||||
});
|
||||
|
||||
await queryInterface.addConstraint('course_product_categories', {
|
||||
fields: ['category_id'],
|
||||
type: 'foreign key',
|
||||
name: 'fk_course_product_categories_category_id',
|
||||
references: { table: 'categories', field: 'id' },
|
||||
onDelete: 'CASCADE',
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.removeConstraint('course_products', 'fk_course_products_product_id');
|
||||
await queryInterface.removeConstraint('course_product_categories', 'fk_course_product_categories_category_id');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
await queryInterface.dropTable('file_uploads', { cascade: true });
|
||||
await queryInterface.dropTable('course_roles', { cascade: true });
|
||||
await queryInterface.dropTable('course_products', { cascade: true });
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('file_uploads', {
|
||||
id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
await queryInterface.createTable('course_roles', {
|
||||
id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
course_id: { type: Sequelize.BIGINT, allowNull: false },
|
||||
role_id: { type: Sequelize.BIGINT, allowNull: false },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
await queryInterface.createTable('course_products', {
|
||||
id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
course_id: { type: Sequelize.BIGINT, allowNull: false },
|
||||
product_id: { type: Sequelize.BIGINT, allowNull: false },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('course_reading_progress', {
|
||||
progress_id: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, primaryKey: true },
|
||||
user_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'users', key: 'user_id' }, onDelete: 'CASCADE' },
|
||||
course_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'courses', key: 'course_id' }, onDelete: 'CASCADE' },
|
||||
reference_id: { type: Sequelize.UUID, allowNull: false },
|
||||
type: { type: Sequelize.ENUM('course', 'unit', 'lesson'), allowNull: false },
|
||||
status: { type: Sequelize.ENUM('in_progress', 'completed'), allowNull: false, defaultValue: 'in_progress' },
|
||||
completed_at: { type: Sequelize.DATE, allowNull: true },
|
||||
last_accessed_at: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') },
|
||||
createdBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
updatedBy: { type: Sequelize.BIGINT, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('course_reading_progress', {
|
||||
fields: ['user_id', 'type', 'reference_id'],
|
||||
unique: true,
|
||||
name: 'uq_crp_user_type_reference',
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('course_reading_progress', { fields: ['user_id'], name: 'idx_crp_user_id' });
|
||||
await queryInterface.addIndex('course_reading_progress', { fields: ['course_id'], name: 'idx_crp_course_id' });
|
||||
await queryInterface.addIndex('course_reading_progress', { fields: ['type'], name: 'idx_crp_type' });
|
||||
await queryInterface.addIndex('course_reading_progress', { fields: ['status'], name: 'idx_crp_status' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('course_reading_progress');
|
||||
},
|
||||
};
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.addColumn('quiz_attempts', 'attempt_number', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 1,
|
||||
comment: '1-based counter per user + quiz (or user + assessment). Derived at insert time from prior attempt count.',
|
||||
});
|
||||
|
||||
await queryInterface.addColumn('quiz_attempts', 'passing_score', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: 'Snapshot of the required passing score at the time of submission.',
|
||||
});
|
||||
|
||||
await queryInterface.addColumn('quiz_attempts', 'course_id', {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: true,
|
||||
references: { model: 'courses', key: 'course_id' },
|
||||
onDelete: 'SET NULL',
|
||||
comment: 'Denormalized course reference for fast per-course attempt queries.',
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('quiz_attempts', { fields: ['course_id'], name: 'idx_qa_course_id' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.removeIndex('quiz_attempts', 'idx_qa_course_id');
|
||||
await queryInterface.removeColumn('quiz_attempts', 'course_id');
|
||||
await queryInterface.removeColumn('quiz_attempts', 'passing_score');
|
||||
await queryInterface.removeColumn('quiz_attempts', 'attempt_number');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('pages', {
|
||||
id: {
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
type: {
|
||||
type: Sequelize.ENUM('landing_page', 'lesson_page', 'course_page'),
|
||||
allowNull: false,
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('pages', { fields: ['type'], name: 'idx_pages_type' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('pages');
|
||||
await queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_pages_type";');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('landing_pages', {
|
||||
id: {
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
page_id: {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
references: { model: 'pages', key: 'id' },
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
title: {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: false,
|
||||
},
|
||||
slug: {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
meta_title: {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: true,
|
||||
},
|
||||
meta_description: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.ENUM('draft', 'published', 'archived'),
|
||||
allowNull: false,
|
||||
defaultValue: 'draft',
|
||||
},
|
||||
published_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
created_by: {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: true,
|
||||
references: { model: 'users', key: 'user_id' },
|
||||
onDelete: 'SET NULL',
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('landing_pages', { fields: ['slug'], name: 'idx_landing_pages_slug' });
|
||||
await queryInterface.addIndex('landing_pages', { fields: ['status'], name: 'idx_landing_pages_status' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('landing_pages');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('page_sections', {
|
||||
id: {
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
page_id: {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: false,
|
||||
references: { model: 'pages', key: 'id' },
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
type: {
|
||||
type: Sequelize.ENUM('hero', 'features', 'cta', 'testimonials', 'faq', 'pricing', 'gallery', 'custom'),
|
||||
allowNull: false,
|
||||
},
|
||||
label: {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: true,
|
||||
},
|
||||
position: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
settings: {
|
||||
type: Sequelize.JSON,
|
||||
allowNull: true,
|
||||
comment: 'Background, padding, layout, and other section-level style overrides.',
|
||||
},
|
||||
is_visible: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('page_sections', { fields: ['page_id'], name: 'idx_page_sections_page_id' });
|
||||
await queryInterface.addIndex('page_sections', { fields: ['page_id', 'position'], name: 'idx_page_sections_order' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('page_sections');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('page_blocks', {
|
||||
id: {
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
page_section_id: {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: false,
|
||||
references: { model: 'page_sections', key: 'id' },
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
type: {
|
||||
type: Sequelize.ENUM('text', 'image', 'button', 'video', 'form', 'spacer'),
|
||||
allowNull: false,
|
||||
},
|
||||
content: {
|
||||
type: Sequelize.JSON,
|
||||
allowNull: true,
|
||||
comment: 'Block payload — varies by type (e.g. src + alt for image, href + label for button).',
|
||||
},
|
||||
position: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
settings: {
|
||||
type: Sequelize.JSON,
|
||||
allowNull: true,
|
||||
comment: 'Block-level style overrides: alignment, color, font size, etc.',
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('page_blocks', { fields: ['page_section_id'], name: 'idx_page_blocks_section_id' });
|
||||
await queryInterface.addIndex('page_blocks', { fields: ['page_section_id', 'position'], name: 'idx_page_blocks_order' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('page_blocks');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('page_templates', {
|
||||
id: {
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: false,
|
||||
},
|
||||
thumbnail_url: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
structure: {
|
||||
type: Sequelize.JSON,
|
||||
allowNull: true,
|
||||
comment: 'Full snapshot of sections and blocks for this template.',
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('page_templates');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('announcements', {
|
||||
id: {
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
title: {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: false,
|
||||
},
|
||||
body: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
scope: {
|
||||
type: Sequelize.ENUM('global', 'course', 'role'),
|
||||
allowNull: false,
|
||||
defaultValue: 'global',
|
||||
},
|
||||
priority: {
|
||||
type: Sequelize.ENUM('low', 'normal', 'urgent'),
|
||||
allowNull: false,
|
||||
defaultValue: 'normal',
|
||||
},
|
||||
published_by: {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: true,
|
||||
references: { model: 'users', key: 'user_id' },
|
||||
onDelete: 'SET NULL',
|
||||
},
|
||||
starts_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
ends_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
is_active: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
updated_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('announcements', { fields: ['scope'], name: 'idx_announcements_scope' });
|
||||
await queryInterface.addIndex('announcements', { fields: ['is_active'], name: 'idx_announcements_is_active' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('announcements');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('announcement_courses', {
|
||||
id: {
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
announcement_id: {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: false,
|
||||
references: { model: 'announcements', key: 'id' },
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
course_id: {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: false,
|
||||
references: { model: 'courses', key: 'course_id' },
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
});
|
||||
|
||||
await queryInterface.addConstraint('announcement_courses', {
|
||||
fields: ['announcement_id', 'course_id'],
|
||||
type: 'unique',
|
||||
name: 'uq_announcement_courses',
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('announcement_courses', { fields: ['announcement_id'], name: 'idx_announcement_courses_ann_id' });
|
||||
await queryInterface.addIndex('announcement_courses', { fields: ['course_id'], name: 'idx_announcement_courses_course_id' });
|
||||
|
||||
await queryInterface.createTable('announcement_roles', {
|
||||
id: {
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
announcement_id: {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: false,
|
||||
references: { model: 'announcements', key: 'id' },
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
role: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
},
|
||||
});
|
||||
|
||||
await queryInterface.addConstraint('announcement_roles', {
|
||||
fields: ['announcement_id', 'role'],
|
||||
type: 'unique',
|
||||
name: 'uq_announcement_roles',
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('announcement_roles', { fields: ['announcement_id'], name: 'idx_announcement_roles_ann_id' });
|
||||
|
||||
await queryInterface.createTable('announcement_reads', {
|
||||
id: {
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
announcement_id: {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: false,
|
||||
references: { model: 'announcements', key: 'id' },
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
user_id: {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: false,
|
||||
references: { model: 'users', key: 'user_id' },
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
read_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
});
|
||||
|
||||
await queryInterface.addConstraint('announcement_reads', {
|
||||
fields: ['announcement_id', 'user_id'],
|
||||
type: 'unique',
|
||||
name: 'uq_announcement_reads',
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('announcement_reads', { fields: ['announcement_id'], name: 'idx_announcement_reads_ann_id' });
|
||||
await queryInterface.addIndex('announcement_reads', { fields: ['user_id'], name: 'idx_announcement_reads_user_id' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('announcement_reads');
|
||||
await queryInterface.dropTable('announcement_roles');
|
||||
await queryInterface.dropTable('announcement_courses');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
// NOTE: This table was created manually before migration support was added.
|
||||
// This migration reflects the final column set including entity_type and entity_id
|
||||
// which were added via ALTER TABLE on 2026-06-21.
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('user_activity', {
|
||||
activity_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
user_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'users', key: 'user_id' }, onDelete: 'CASCADE' },
|
||||
session_id: { type: Sequelize.BIGINT, allowNull: true },
|
||||
action: { type: Sequelize.STRING(100), allowNull: false },
|
||||
entity_type: { type: Sequelize.STRING(50), allowNull: true },
|
||||
entity_id: { type: Sequelize.BIGINT, allowNull: true },
|
||||
details: { type: Sequelize.JSONB, allowNull: true },
|
||||
created_at: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('user_activity', { fields: ['user_id'], name: 'idx_ua_user_id' });
|
||||
await queryInterface.addIndex('user_activity', { fields: ['action'], name: 'idx_ua_action' });
|
||||
await queryInterface.addIndex('user_activity', { fields: ['created_at'], name: 'idx_ua_created_at' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('user_activity');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('page_user_groups', {
|
||||
page_id: {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: { model: 'pages', key: 'id' },
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
group_id: {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: { model: 'user_groups', key: 'group_id' },
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
created_at: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
},
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('page_user_groups', { fields: ['group_id'], name: 'idx_page_user_groups_group_id' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('page_user_groups');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.addColumn('assets', 'thumbnail_storage_key', {
|
||||
type: Sequelize.STRING(512),
|
||||
allowNull: true,
|
||||
after: 'thumbnail_url',
|
||||
});
|
||||
},
|
||||
down: async (queryInterface) => {
|
||||
await queryInterface.removeColumn('assets', 'thumbnail_storage_key');
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user