'use strict'; const { v4: uuid } = require('uuid'); const { QueryTypes } = require('sequelize'); const BUSINESS_ROLES = { SUPER_ADMIN: 'Super Administrator', ADMIN: 'Administrator', CONCIERGE: 'Concierge Coordinator', CUSTOMER: 'Customer', PLATFORM_OWNER: 'Platform Owner', OPERATIONS_DIRECTOR: 'Operations Director', RESERVATIONS_LEAD: 'Reservations Lead', FINANCE_CONTROLLER: 'Finance Controller', }; const rolePermissionMatrix = { [BUSINESS_ROLES.ADMIN]: [ 'CREATE_BOOKING_REQUESTS', 'READ_BOOKING_REQUESTS', 'UPDATE_BOOKING_REQUESTS', 'READ_APPROVAL_STEPS', 'UPDATE_APPROVAL_STEPS', 'CREATE_RESERVATIONS', 'READ_RESERVATIONS', 'UPDATE_RESERVATIONS', 'CREATE_SERVICE_REQUESTS', 'READ_SERVICE_REQUESTS', 'UPDATE_SERVICE_REQUESTS', 'READ_INVOICES', 'CREATE_DOCUMENTS', 'READ_DOCUMENTS', 'UPDATE_DOCUMENTS', 'READ_ORGANIZATIONS', 'READ_TENANTS', 'READ_PROPERTIES', 'READ_UNITS', 'READ_NEGOTIATED_RATES', ], [BUSINESS_ROLES.CONCIERGE]: [ 'CREATE_BOOKING_REQUESTS', 'READ_BOOKING_REQUESTS', 'UPDATE_BOOKING_REQUESTS', 'READ_RESERVATIONS', 'CREATE_SERVICE_REQUESTS', 'READ_SERVICE_REQUESTS', 'UPDATE_SERVICE_REQUESTS', 'CREATE_DOCUMENTS', 'READ_DOCUMENTS', 'UPDATE_DOCUMENTS', 'READ_PROPERTIES', 'READ_UNITS', ], [BUSINESS_ROLES.CUSTOMER]: [ 'CREATE_BOOKING_REQUESTS', 'READ_BOOKING_REQUESTS', 'UPDATE_BOOKING_REQUESTS', 'READ_RESERVATIONS', 'CREATE_SERVICE_REQUESTS', 'READ_SERVICE_REQUESTS', 'UPDATE_SERVICE_REQUESTS', 'READ_DOCUMENTS', ], [BUSINESS_ROLES.PLATFORM_OWNER]: [ 'CREATE_BOOKING_REQUESTS', 'READ_BOOKING_REQUESTS', 'UPDATE_BOOKING_REQUESTS', 'READ_APPROVAL_STEPS', 'UPDATE_APPROVAL_STEPS', 'CREATE_RESERVATIONS', 'READ_RESERVATIONS', 'UPDATE_RESERVATIONS', 'CREATE_SERVICE_REQUESTS', 'READ_SERVICE_REQUESTS', 'UPDATE_SERVICE_REQUESTS', 'READ_INVOICES', 'CREATE_DOCUMENTS', 'READ_DOCUMENTS', 'UPDATE_DOCUMENTS', 'READ_ORGANIZATIONS', 'READ_PROPERTIES', 'READ_UNITS', 'READ_NEGOTIATED_RATES', ], [BUSINESS_ROLES.OPERATIONS_DIRECTOR]: [ 'CREATE_BOOKING_REQUESTS', 'READ_BOOKING_REQUESTS', 'UPDATE_BOOKING_REQUESTS', 'READ_APPROVAL_STEPS', 'UPDATE_APPROVAL_STEPS', 'CREATE_RESERVATIONS', 'READ_RESERVATIONS', 'UPDATE_RESERVATIONS', 'CREATE_SERVICE_REQUESTS', 'READ_SERVICE_REQUESTS', 'UPDATE_SERVICE_REQUESTS', 'CREATE_DOCUMENTS', 'READ_DOCUMENTS', 'UPDATE_DOCUMENTS', 'READ_PROPERTIES', 'READ_UNITS', 'READ_NEGOTIATED_RATES', ], [BUSINESS_ROLES.RESERVATIONS_LEAD]: [ 'CREATE_BOOKING_REQUESTS', 'READ_BOOKING_REQUESTS', 'UPDATE_BOOKING_REQUESTS', 'READ_APPROVAL_STEPS', 'UPDATE_APPROVAL_STEPS', 'CREATE_RESERVATIONS', 'READ_RESERVATIONS', 'UPDATE_RESERVATIONS', 'CREATE_SERVICE_REQUESTS', 'READ_SERVICE_REQUESTS', 'UPDATE_SERVICE_REQUESTS', 'READ_DOCUMENTS', 'READ_PROPERTIES', 'READ_UNITS', 'READ_NEGOTIATED_RATES', ], [BUSINESS_ROLES.FINANCE_CONTROLLER]: [ 'READ_BOOKING_REQUESTS', 'READ_RESERVATIONS', 'READ_INVOICES', 'UPDATE_INVOICES', 'READ_DOCUMENTS', ], }; async function findRoleByName(queryInterface, name) { const rows = await queryInterface.sequelize.query( 'SELECT "id", "name" FROM "roles" WHERE "name" = :name LIMIT 1', { replacements: { name }, type: QueryTypes.SELECT, }, ); return rows[0] || null; } async function ensureRole(queryInterface, name, now) { const existingRole = await findRoleByName(queryInterface, name); if (existingRole) { return existingRole.id; } const id = uuid(); await queryInterface.bulkInsert('roles', [ { id, name, globalAccess: false, createdAt: now, updatedAt: now, }, ]); return id; } async function getPermissionIdMap(queryInterface, permissionNames) { const permissions = await queryInterface.sequelize.query( 'SELECT "id", "name" FROM "permissions" WHERE "name" IN (:permissionNames)', { replacements: { permissionNames }, type: QueryTypes.SELECT, }, ); return new Map(permissions.map((permission) => [permission.name, permission.id])); } module.exports = { async up(queryInterface) { const now = new Date(); const customerRoleId = await ensureRole(queryInterface, BUSINESS_ROLES.CUSTOMER, now); await queryInterface.bulkUpdate('roles', { globalAccess: true, updatedAt: now }, { name: BUSINESS_ROLES.SUPER_ADMIN }); await queryInterface.sequelize.query( 'UPDATE "roles" SET "globalAccess" = false, "updatedAt" = :updatedAt WHERE "name" IN (:roleNames)', { replacements: { updatedAt: now, roleNames: [ BUSINESS_ROLES.ADMIN, BUSINESS_ROLES.CONCIERGE, BUSINESS_ROLES.CUSTOMER, BUSINESS_ROLES.PLATFORM_OWNER, BUSINESS_ROLES.OPERATIONS_DIRECTOR, BUSINESS_ROLES.RESERVATIONS_LEAD, BUSINESS_ROLES.FINANCE_CONTROLLER, ], }, }, ); const roleIds = {}; for (const roleName of Object.keys(rolePermissionMatrix)) { const role = await findRoleByName(queryInterface, roleName); if (!role) { throw new Error(`Role '${roleName}' was not found while aligning the business role matrix.`); } roleIds[roleName] = role.id; } roleIds[BUSINESS_ROLES.CUSTOMER] = customerRoleId; const permissionNames = [...new Set(Object.values(rolePermissionMatrix).flat())]; const permissionIdMap = await getPermissionIdMap(queryInterface, permissionNames); const missingPermissions = permissionNames.filter((permissionName) => !permissionIdMap.get(permissionName)); if (missingPermissions.length > 0) { throw new Error(`Missing permissions for role matrix alignment: ${missingPermissions.join(', ')}`); } const impactedRoleIds = Object.values(roleIds); await queryInterface.sequelize.query( 'DELETE FROM "rolesPermissionsPermissions" WHERE "roles_permissionsId" IN (:roleIds)', { replacements: { roleIds: impactedRoleIds }, }, ); const rows = Object.entries(rolePermissionMatrix).flatMap(([roleName, permissions]) => permissions.map((permissionName) => ({ createdAt: now, updatedAt: now, roles_permissionsId: roleIds[roleName], permissionId: permissionIdMap.get(permissionName), })), ); if (rows.length > 0) { await queryInterface.bulkInsert('rolesPermissionsPermissions', rows); } await queryInterface.bulkUpdate('users', { app_roleId: customerRoleId, updatedAt: now }, { email: 'client@hello.com' }); await queryInterface.bulkUpdate('users', { app_roleId: roleIds[BUSINESS_ROLES.CONCIERGE], updatedAt: now }, { email: 'john@doe.com' }); }, async down() { // Intentionally left blank. This seeder aligns live business roles and should not blindly revert production data. }, };