'use strict'; // Tier Plans v2: item-specific entitlement. Until now a Tier Plan purchase // only recorded `user_tiers.plan_id` + tier rank; the actual courses/units/ // lessons granted were re-resolved LIVE off plan_courses/plan_units/ // plan_lessons on every access check, so editing a plan's bundle later // silently changed what past purchasers could access, and a Unit-only bundle // couldn't be distinguished from its parent Course's bundle. // // This table is the purchase-time snapshot: one row per exact item granted // by a given user_tiers purchase. Access checks now look up rows here // instead of comparing tier rank against item subscription (see hasItemGrant() // in controllers/client/courses.controller.js). // // item_type is STRING + CHECK, not a real ENUM, mirroring // products.purchasable_type (20270101000078) — CockroachDB can't create a // new enum type via addColumn the way createTable can. module.exports = { async up(queryInterface, Sequelize) { await queryInterface.createTable('user_tier_grants', { id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true }, user_tier_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'user_tiers', key: 'tier_id' }, onDelete: 'CASCADE' }, user_id: { type: Sequelize.BIGINT, allowNull: false, references: { model: 'users', key: 'user_id' }, onDelete: 'CASCADE' }, plan_id: { type: Sequelize.BIGINT, allowNull: true, references: { model: 'tier_plans', key: 'plan_id' }, onUpdate: 'CASCADE', onDelete: 'SET NULL' }, item_type: { type: Sequelize.STRING(10), allowNull: false }, item_id: { type: Sequelize.BIGINT, allowNull: false }, granted_at: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.NOW }, createdAt: { type: Sequelize.DATE, allowNull: false }, updatedAt: { type: Sequelize.DATE, allowNull: false }, }); await queryInterface.addConstraint('user_tier_grants', { fields: ['item_type'], type: 'check', name: 'check_user_tier_grants_item_type', where: { item_type: { [Sequelize.Op.in]: ['course', 'unit', 'lesson'] } }, }); await queryInterface.addIndex('user_tier_grants', ['user_id', 'item_type', 'item_id'], { name: 'user_tier_grants_user_item_idx', }); await queryInterface.addIndex('user_tier_grants', ['user_tier_id']); }, async down(queryInterface) { await queryInterface.dropTable('user_tier_grants'); }, };