fixed migration
This commit is contained in:
parent
799eba7306
commit
040300f3d1
@ -1,5 +1,9 @@
|
||||
import { DataTypes, type QueryInterface } from 'sequelize';
|
||||
import { ROLE_SCOPE_VALUES } from '@/shared/constants/roles';
|
||||
import {
|
||||
ROLE_SCOPE_VALUES,
|
||||
ROLE_DEFINITIONS,
|
||||
ROLE_SCOPES,
|
||||
} from '@/shared/constants/roles';
|
||||
|
||||
/**
|
||||
* Workstream 3 §3.1 foundation: add the authorization `scope` to `roles` (NOT
|
||||
@ -7,8 +11,9 @@ import { ROLE_SCOPE_VALUES } from '@/shared/constants/roles';
|
||||
* and a nullable `campusId` to `users` (campus scope for campus-bound roles;
|
||||
* null for system/organization scopes, so legitimately optional).
|
||||
*
|
||||
* Pre-launch with no production data, the `scope` column is added NOT NULL
|
||||
* against the (empty, freshly-migrated) `roles` table; seeders populate it.
|
||||
* Handles existing data: adds column as nullable, backfills scope based on
|
||||
* role name using ROLE_DEFINITIONS mapping, then makes it NOT NULL.
|
||||
*
|
||||
* `campusId` is a plain UUID (no DB-level FK), matching how
|
||||
* `users.organizationId` is modeled — associations use `constraints: false`.
|
||||
*
|
||||
@ -26,17 +31,54 @@ async function columnExists(
|
||||
return (results as unknown[]).length > 0;
|
||||
}
|
||||
|
||||
async function columnIsNullable(
|
||||
queryInterface: QueryInterface,
|
||||
table: string,
|
||||
column: string,
|
||||
): Promise<boolean> {
|
||||
const [results] = await queryInterface.sequelize.query(`
|
||||
SELECT is_nullable FROM information_schema.columns
|
||||
WHERE table_name = '${table}' AND column_name = '${column}'
|
||||
`);
|
||||
const row = (results as { is_nullable: string }[])[0];
|
||||
return row?.is_nullable === 'YES';
|
||||
}
|
||||
|
||||
export default {
|
||||
up: async (queryInterface: QueryInterface) => {
|
||||
// Create enum type if not exists, then add column if not exists
|
||||
// Create enum type if not exists
|
||||
await queryInterface.sequelize.query(`
|
||||
DO 'BEGIN
|
||||
CREATE TYPE "public"."enum_roles_scope" AS ENUM(${ROLE_SCOPE_VALUES.map((v) => `''${v}''`).join(', ')});
|
||||
EXCEPTION WHEN duplicate_object THEN null; END';
|
||||
`);
|
||||
|
||||
if (!(await columnExists(queryInterface, 'roles', 'scope'))) {
|
||||
const scopeExists = await columnExists(queryInterface, 'roles', 'scope');
|
||||
|
||||
if (!scopeExists) {
|
||||
// Step 1: Add column as nullable first
|
||||
await queryInterface.addColumn('roles', 'scope', {
|
||||
type: DataTypes.ENUM(...ROLE_SCOPE_VALUES),
|
||||
allowNull: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Step 2: Backfill existing rows with correct scope based on role name
|
||||
for (const def of ROLE_DEFINITIONS) {
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE roles SET scope = :scope WHERE name = :name AND scope IS NULL`,
|
||||
{ replacements: { scope: def.scope, name: def.name } },
|
||||
);
|
||||
}
|
||||
// Fallback for any roles not in ROLE_DEFINITIONS (e.g., custom roles)
|
||||
await queryInterface.sequelize.query(
|
||||
`UPDATE roles SET scope = :scope WHERE scope IS NULL`,
|
||||
{ replacements: { scope: ROLE_SCOPES.CAMPUS } },
|
||||
);
|
||||
|
||||
// Step 3: Make column NOT NULL if still nullable
|
||||
if (!scopeExists || (await columnIsNullable(queryInterface, 'roles', 'scope'))) {
|
||||
await queryInterface.changeColumn('roles', 'scope', {
|
||||
type: DataTypes.ENUM(...ROLE_SCOPE_VALUES),
|
||||
allowNull: false,
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user