mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
assets and tier plans revamp
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
// Tracks background container-remux jobs for video assets uploaded as .mov/
|
||||
// .mkv — those formats often load slowly in-browser (moov/Cues index at the
|
||||
// end of the file) compared to faststart .mp4. See services/ffmpeg.service.js
|
||||
// + services/assetTranscode.service.js.
|
||||
//
|
||||
// STRING + explicit CHECK instead of a real ENUM — same CockroachDB
|
||||
// constraint noted in 20270101000032-create-advertisements.js (addColumn
|
||||
// can't create a new enum type the way createTable can).
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.addColumn('assets', 'transcode_status', {
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: false,
|
||||
defaultValue: 'none',
|
||||
});
|
||||
|
||||
await queryInterface.addColumn('assets', 'transcode_error', {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
});
|
||||
|
||||
await queryInterface.addConstraint('assets', {
|
||||
fields: ['transcode_status'],
|
||||
type: 'check',
|
||||
name: 'check_assets_transcode_status',
|
||||
where: { transcode_status: { [Sequelize.Op.in]: ['none', 'pending', 'processing', 'done', 'failed'] } },
|
||||
});
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
CREATE INDEX idx_assets_transcode_status
|
||||
ON assets (transcode_status)
|
||||
WHERE transcode_status IN ('pending', 'processing');
|
||||
`);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.removeConstraint('assets', 'check_assets_transcode_status');
|
||||
await queryInterface.removeColumn('assets', 'transcode_status');
|
||||
await queryInterface.removeColumn('assets', 'transcode_error');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
// Standalone tier gate for Lessons, mirroring units.subscription (see
|
||||
// 20270101000012-create-units.js) — null means open (or gated only via an
|
||||
// attached unit/course). Full parity with the Unit-level gate added for the
|
||||
// standalone Units/Lessons tier-gating extension.
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.addColumn('lessons', 'subscription', {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: true,
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.removeColumn('lessons', 'subscription');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
'use strict';
|
||||
|
||||
// Generalizes `products` from Course-only (`course_id`) to a polymorphic
|
||||
// target (`purchasable_type` + `purchasable_id`) so standalone Units and
|
||||
// Lessons can also be sold individually, alongside plan-tier gating — see
|
||||
// utils/purchasable.util.js for the resolver used by every consumer.
|
||||
//
|
||||
// purchasable_type is STRING + explicit CHECK, not a real ENUM (CockroachDB
|
||||
// can't create a new enum type via addColumn the way createTable can — same
|
||||
// caveat as 20270101000076-add-assets-transcode-status.js).
|
||||
//
|
||||
// Dropping `course_id` also drops its FK (`onDelete: 'CASCADE'` from
|
||||
// 20270101000019-create-products.js) — referential integrity across the three
|
||||
// possible targets is now enforced at the application layer only, same as
|
||||
// course_purchases.product_id already is today.
|
||||
//
|
||||
// Touches a table with live production rows (real courses have real
|
||||
// products/course_purchases). NOT auto-run — test against a local/staging
|
||||
// copy of the DB first; do not execute against the shared dev/prod database
|
||||
// without explicit confirmation.
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.addColumn('products', 'purchasable_type', {
|
||||
type: Sequelize.STRING(10),
|
||||
allowNull: false,
|
||||
defaultValue: 'course',
|
||||
});
|
||||
|
||||
await queryInterface.addConstraint('products', {
|
||||
fields: ['purchasable_type'],
|
||||
type: 'check',
|
||||
name: 'check_products_purchasable_type',
|
||||
where: { purchasable_type: { [Sequelize.Op.in]: ['course', 'unit', 'lesson'] } },
|
||||
});
|
||||
|
||||
await queryInterface.addColumn('products', 'purchasable_id', {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: true, // filled by the backfill below, then locked to NOT NULL
|
||||
});
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE products SET purchasable_type = 'course', purchasable_id = course_id;
|
||||
`);
|
||||
|
||||
await queryInterface.changeColumn('products', 'purchasable_id', {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: false,
|
||||
});
|
||||
|
||||
await queryInterface.removeIndex('products', ['course_id']);
|
||||
await queryInterface.removeColumn('products', 'course_id');
|
||||
|
||||
await queryInterface.addIndex('products', ['purchasable_type', 'purchasable_id'], {
|
||||
name: 'products_purchasable_type_id',
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.removeIndex('products', 'products_purchasable_type_id');
|
||||
|
||||
await queryInterface.addColumn('products', 'course_id', {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: true,
|
||||
});
|
||||
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE products SET course_id = purchasable_id WHERE purchasable_type = 'course';
|
||||
`);
|
||||
|
||||
await queryInterface.changeColumn('products', 'course_id', {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: false,
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('products', ['course_id']);
|
||||
|
||||
await queryInterface.removeColumn('products', 'purchasable_id');
|
||||
await queryInterface.removeConstraint('products', 'check_products_purchasable_type');
|
||||
await queryInterface.removeColumn('products', 'purchasable_type');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('plan_units', {
|
||||
id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
plan_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'tier_plans', key: 'plan_id' }, onDelete: 'CASCADE' },
|
||||
unit_id: { type: Sequelize.BIGINT, allowNull: false, unique: true, references: { model: 'units', key: 'unit_id' }, onDelete: 'CASCADE' },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('plan_units', ['plan_id']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('plan_units');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('plan_lessons', {
|
||||
id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true },
|
||||
plan_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'tier_plans', key: 'plan_id' }, onDelete: 'CASCADE' },
|
||||
lesson_id: { type: Sequelize.BIGINT, allowNull: false, unique: true, references: { model: 'lessons', key: 'lesson_id' }, onDelete: 'CASCADE' },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('plan_lessons', ['plan_id']);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.dropTable('plan_lessons');
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user