mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('unit_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' },
|
||||
unit_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'units', key: 'unit_id' }, onDelete: 'CASCADE' },
|
||||
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('unit_reading_progress', {
|
||||
fields: ['user_id', 'unit_id'],
|
||||
unique: true,
|
||||
name: 'uq_urp_user_unit',
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('unit_reading_progress', { fields: ['user_id'], name: 'idx_urp_user_id' });
|
||||
await queryInterface.addIndex('unit_reading_progress', { fields: ['course_id'], name: 'idx_urp_course_id' });
|
||||
await queryInterface.addIndex('unit_reading_progress', { fields: ['unit_id'], name: 'idx_urp_unit_id' });
|
||||
await queryInterface.addIndex('unit_reading_progress', { fields: ['status'], name: 'idx_urp_status' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('unit_reading_progress');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('lesson_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' },
|
||||
unit_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'units', key: 'unit_id' }, onDelete: 'CASCADE' },
|
||||
lesson_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'lessons', key: 'lesson_id' }, onDelete: 'CASCADE' },
|
||||
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('lesson_reading_progress', {
|
||||
fields: ['user_id', 'lesson_id'],
|
||||
unique: true,
|
||||
name: 'uq_lrp_user_lesson',
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('lesson_reading_progress', { fields: ['user_id'], name: 'idx_lrp_user_id' });
|
||||
await queryInterface.addIndex('lesson_reading_progress', { fields: ['course_id'], name: 'idx_lrp_course_id' });
|
||||
await queryInterface.addIndex('lesson_reading_progress', { fields: ['unit_id'], name: 'idx_lrp_unit_id' });
|
||||
await queryInterface.addIndex('lesson_reading_progress', { fields: ['lesson_id'], name: 'idx_lrp_lesson_id' });
|
||||
await queryInterface.addIndex('lesson_reading_progress', { fields: ['status'], name: 'idx_lrp_status' });
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('lesson_reading_progress');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
// CockroachDB stores ENUM columns as VARCHAR + check constraint.
|
||||
// The original check_subscription constraint only allows 'free' | 'premium'.
|
||||
// This migration drops and recreates it to include 'exclusive'.
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE courses DROP CONSTRAINT IF EXISTS check_subscription`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE courses ADD CONSTRAINT check_subscription
|
||||
CHECK (subscription IN ('free', 'premium', 'exclusive'))`
|
||||
);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE courses DROP CONSTRAINT IF EXISTS check_subscription`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE courses ADD CONSTRAINT check_subscription
|
||||
CHECK (subscription IN ('free', 'premium'))`
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.addColumn('user_tiers', 'plan_id', {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: true,
|
||||
references: { model: 'tier_plans', key: 'plan_id' },
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL',
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.removeColumn('user_tiers', 'plan_id');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('plan_policies', {
|
||||
policy_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
plan_id: { type: Sequelize.BIGINT, allowNull: false, unique: true,
|
||||
references: { model: 'tier_plans', key: 'plan_id' },
|
||||
onUpdate: 'CASCADE', onDelete: 'CASCADE' },
|
||||
badge_asset_id: { type: Sequelize.BIGINT, allowNull: true,
|
||||
references: { model: 'assets', key: 'asset_id' },
|
||||
onUpdate: 'CASCADE', onDelete: 'SET NULL' },
|
||||
badge_label: { type: Sequelize.STRING(100), allowNull: false, defaultValue: '' },
|
||||
badge_description: { type: Sequelize.TEXT, allowNull: true },
|
||||
badge_information: { type: Sequelize.TEXT, allowNull: true },
|
||||
access_rules: { type: Sequelize.JSONB, allowNull: false, defaultValue: [] },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('plan_policies');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('system_badges', {
|
||||
badge_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
key: { type: Sequelize.STRING(50), allowNull: false, unique: true },
|
||||
asset_id: { type: Sequelize.BIGINT, allowNull: true,
|
||||
references: { model: 'assets', key: 'asset_id' },
|
||||
onUpdate: 'CASCADE', onDelete: 'SET NULL' },
|
||||
label: { type: Sequelize.STRING(100), allowNull: false },
|
||||
description: { type: Sequelize.TEXT, allowNull: true },
|
||||
information: { type: Sequelize.TEXT, allowNull: true },
|
||||
active_from: { type: Sequelize.DATEONLY, allowNull: true },
|
||||
active_until: { type: Sequelize.DATEONLY, allowNull: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('system_badges');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('tier_categories', {
|
||||
tier_category_id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
slug: { type: Sequelize.STRING(50), allowNull: false, unique: true },
|
||||
name: { type: Sequelize.STRING(100), allowNull: false },
|
||||
description: { type: Sequelize.TEXT, allowNull: true },
|
||||
rank: { type: Sequelize.INTEGER, allowNull: false, defaultValue: 0 },
|
||||
badge_asset_id: {
|
||||
type: Sequelize.BIGINT, allowNull: true,
|
||||
references: { model: 'assets', key: 'asset_id' },
|
||||
onUpdate: 'CASCADE', onDelete: 'SET NULL',
|
||||
},
|
||||
badge_label: { type: Sequelize.STRING(100), allowNull: true },
|
||||
is_default: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: false },
|
||||
is_active: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
// Seed the three baseline tier categories
|
||||
await queryInterface.sequelize.query(`
|
||||
INSERT INTO tier_categories (slug, name, rank, is_default, is_active, "createdAt", "updatedAt")
|
||||
VALUES
|
||||
('free', 'Free', 0, true, true, NOW(), NOW()),
|
||||
('premium', 'Premium', 1, false, true, NOW(), NOW()),
|
||||
('exclusive', 'Exclusive', 2, false, true, NOW(), NOW())
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('tier_categories');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
// Add the FK column
|
||||
await queryInterface.addColumn('tier_plans', 'tier_category_id', {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: true,
|
||||
references: { model: 'tier_categories', key: 'tier_category_id' },
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL',
|
||||
});
|
||||
|
||||
// Populate from the existing tier slug → tier_categories row
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE tier_plans tp
|
||||
SET tier_category_id = tc.tier_category_id
|
||||
FROM tier_categories tc
|
||||
WHERE tc.slug = tp.tier
|
||||
`);
|
||||
|
||||
// CockroachDB stores Sequelize ENUMs as VARCHAR + CHECK constraint.
|
||||
// Drop the constraint so `tier` becomes a free-form slug that can hold
|
||||
// any value matching a tier_categories.slug (including admin-created ones).
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE tier_plans DROP CONSTRAINT IF EXISTS check_tier`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE tier_plans DROP CONSTRAINT IF EXISTS "tier_plans_tier_check"`
|
||||
);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.removeColumn('tier_plans', 'tier_category_id');
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE tier_plans ADD CONSTRAINT check_tier CHECK (tier IN ('premium', 'exclusive'))`
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
// Badge config moves to tier_categories — plan_policies now only holds access_rules.
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.sequelize.query(`SET sql_safe_updates = false`);
|
||||
await queryInterface.sequelize.query(`ALTER TABLE plan_policies DROP COLUMN IF EXISTS badge_asset_id`);
|
||||
await queryInterface.sequelize.query(`ALTER TABLE plan_policies DROP COLUMN IF EXISTS badge_label`);
|
||||
await queryInterface.sequelize.query(`ALTER TABLE plan_policies DROP COLUMN IF EXISTS badge_description`);
|
||||
await queryInterface.sequelize.query(`ALTER TABLE plan_policies DROP COLUMN IF EXISTS badge_information`);
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.addColumn('plan_policies', 'badge_asset_id', { type: Sequelize.BIGINT, allowNull: true });
|
||||
await queryInterface.addColumn('plan_policies', 'badge_label', { type: Sequelize.STRING(100), allowNull: false, defaultValue: '' });
|
||||
await queryInterface.addColumn('plan_policies', 'badge_description', { type: Sequelize.TEXT, allowNull: true });
|
||||
await queryInterface.addColumn('plan_policies', 'badge_information', { type: Sequelize.TEXT, allowNull: true });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
// user_tiers.tier was ENUM('free','premium','exclusive').
|
||||
// Drop the check constraint so it can hold any tier_categories.slug value.
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface) {
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE user_tiers DROP CONSTRAINT IF EXISTS check_tier`
|
||||
);
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE user_tiers DROP CONSTRAINT IF EXISTS "user_tiers_tier_check"`
|
||||
);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.sequelize.query(
|
||||
`ALTER TABLE user_tiers ADD CONSTRAINT check_tier CHECK (tier IN ('free', 'premium', 'exclusive'))`
|
||||
);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user