import { Injectable, Logger } from '@nestjs/common'; import { Application, Organization, OrganizationConfiguration, Permission, Role, RolePermission, } from '@tria-plc/iamapi-common'; import { InjectDataSource } from '@nestjs/typeorm'; import { DataSource, EntityManager, In } from 'typeorm'; import { ERoleKey } from '@tria-plc/api-common/utils/enums/seed.enum'; import { PASSENGER_PERMISSIONS, PASSENGER_PERMISSION_KEYS, } from './passenger-permissions.registry'; import { EDR_PASSENGER_APPLICATION, EDR_PASSENGER_ROLES, type PassengerSeedRole } from './edr-passenger.seed'; const EDR_ORG_KEY = 'edr'; const EDR_ORG_NAME = { am: 'EDR', en: 'EDR' }; const SEED_FLAG = 'SEED_EDR_PASSENGER_ORG'; type SeedOrganization = { id: string; key: string }; @Injectable() export class EdrPassengerOrgSeeder { private readonly logger = new Logger(EdrPassengerOrgSeeder.name); constructor(@InjectDataSource() private readonly dataSource: DataSource) {} async run() { if (process.env[SEED_FLAG]?.trim().toLowerCase() !== 'true') { this.logger.log(`Skipping passenger org seed because ${SEED_FLAG} is not enabled`); return; } await this.dataSource.transaction(async (manager) => { await this.ensureApplication(manager); await this.ensurePermissions(manager); const organization = await this.ensureOrganization(manager); await this.ensureOrganizationConfiguration(manager, organization.id); await this.ensureRoles(manager, EDR_PASSENGER_ROLES); await this.ensureRolePermissions(manager, EDR_PASSENGER_ROLES); await this.ensureSuperAdminPermissions(manager); await this.ensurePositions(manager, organization.id); }); this.logger.log(`Ensured EDR passenger organization seed for '${EDR_ORG_KEY}'`); } private async ensureApplication(manager: EntityManager) { await manager.getRepository(Application).upsert( { id: EDR_PASSENGER_APPLICATION.id, key: EDR_PASSENGER_APPLICATION.key, name: EDR_PASSENGER_APPLICATION.name, }, { conflictPaths: { key: true } }, ); this.logger.log(`Ensured application '${EDR_PASSENGER_APPLICATION.key}'`); } private async ensurePermissions(manager: EntityManager) { await manager.getRepository(Permission).upsert( PASSENGER_PERMISSIONS.map((p) => ({ id: p.id, key: p.key, name: p.name, applicationId: EDR_PASSENGER_APPLICATION.id, })), { conflictPaths: { key: true } }, ); this.logger.log(`Ensured ${PASSENGER_PERMISSIONS.length} passenger permissions`); } private async ensureOrganization(manager: EntityManager): Promise { const repo = manager.getRepository(Organization); let org = await repo.findOne({ where: { key: EDR_ORG_KEY }, select: { id: true, key: true } }); if (!org) { const result = await repo.insert({ key: EDR_ORG_KEY, name: EDR_ORG_NAME, isGovernmentOrganization: true, }); this.logger.log(`Seeded EDR passenger organization '${EDR_ORG_KEY}'`); return { id: result.identifiers[0]?.id as string, key: EDR_ORG_KEY }; } this.logger.log(`Ensured EDR passenger organization '${EDR_ORG_KEY}'`); return { id: org.id as string, key: EDR_ORG_KEY }; } private async ensureOrganizationConfiguration(manager: EntityManager, organizationId: string) { await manager.getRepository(OrganizationConfiguration).upsert( { organizationId, canCreateBranchByItself: true, canStartReceivingRecord: true }, { conflictPaths: { organizationId: true } }, ); this.logger.log(`Ensured organization configuration for '${EDR_ORG_KEY}'`); } private async ensureRoles(manager: EntityManager, seedRoles: PassengerSeedRole[]) { await manager.getRepository(Role).upsert( seedRoles.map(({ key, name }) => ({ key, name })), { conflictPaths: { key: true } }, ); this.logger.log(`Ensured passenger roles: ${seedRoles.map((r) => r.key).join(', ')}`); } private async ensureRolePermissions(manager: EntityManager, seedRoles: PassengerSeedRole[]) { const allPermissionKeys = [...new Set(seedRoles.flatMap((r) => r.permissionKeys))]; if (!allPermissionKeys.length) return; const roles = await manager.getRepository(Role).find({ where: { key: In(seedRoles.map((r) => r.key)) }, select: { id: true, key: true }, }); const permissions = await manager.getRepository(Permission).find({ where: { key: In(allPermissionKeys) }, select: { id: true, key: true }, }); const roleByKey = new Map(roles.map((r) => [r.key, r])); const permByKey = new Map(permissions.map((p) => [p.key, p])); let totalUpserted = 0; let totalPruned = 0; for (const seedRole of seedRoles) { const role = roleByKey.get(seedRole.key); if (!role) throw new Error(`missing_role:${seedRole.key}`); const desiredPermissionIds = new Set( seedRole.permissionKeys.map((key) => { const perm = permByKey.get(key); if (!perm) throw new Error(`missing_permission:${key}`); return perm.id as string; }), ); // Remove links that are no longer in this role's preset const existing = await manager.getRepository(RolePermission).find({ where: { roleId: role.id as string }, select: { permissionId: true }, }); const toRemove = existing .map((rp) => rp.permissionId as string) .filter((permId) => !desiredPermissionIds.has(permId)); if (toRemove.length > 0) { await manager.getRepository(RolePermission).delete( toRemove.map((permissionId) => ({ roleId: role.id as string, permissionId })), ); totalPruned += toRemove.length; } // Upsert the full desired set const links = [...desiredPermissionIds].map((permissionId) => ({ roleId: role.id as string, permissionId, })); await manager.getRepository(RolePermission).upsert(links, { conflictPaths: { roleId: true, permissionId: true }, }); totalUpserted += links.length; } this.logger.log( `Synced passenger role-permission links: ${totalUpserted} upserted, ${totalPruned} pruned`, ); } private async ensureSuperAdminPermissions(manager: EntityManager) { const role = await manager.getRepository(Role).findOne({ where: { key: ERoleKey.SUPER_ADMIN }, select: { id: true, key: true }, }); if (!role) { this.logger.warn(`Role ${ERoleKey.SUPER_ADMIN} not found; skipping super_admin permission links`); return; } const permissions = await manager.getRepository(Permission).find({ where: { key: In(PASSENGER_PERMISSION_KEYS) }, select: { id: true, key: true }, }); if (!permissions.length) return; await manager.getRepository(RolePermission).upsert( permissions.map((p) => ({ roleId: role.id, permissionId: p.id })), { conflictPaths: { roleId: true, permissionId: true } }, ); this.logger.log(`Ensured ${permissions.length} passenger permissions on super_admin`); } private async ensurePositions(manager: EntityManager, organizationId: string) { const positions = [ { key: 'edr_passenger_director', name: { en: 'Director', am: 'ዳይሬክተር' }, rank: 1 }, { key: 'edr_passenger_finance_manager', name: { en: 'Finance Manager', am: 'የፋይናንስ ሥራ አስኪያጅ' }, rank: 2 }, { key: 'edr_passenger_finance_officer', name: { en: 'Finance Officer', am: 'የፋይናንስ ኦፊሰር' }, rank: 3 }, { key: 'edr_passenger_team_leader', name: { en: 'Team Leader', am: 'ቡድን መሪ' }, rank: 4 }, { key: 'edr_passenger_station_master', name: { en: 'Station Master', am: 'ጣቢያ ሃላፊ' }, rank: 5 }, { key: 'edr_passenger_station_supervisor', name: { en: 'Station Supervisor', am: 'ጣቢያ ተቆጣጣሪ' }, rank: 6 }, { key: 'edr_passenger_ticket_officer', name: { en: 'Passenger Ticket Officer', am: 'የተሳፋሪ ቲኬት ኦፊሰር' }, rank: 7 }, { key: 'edr_passenger_operational_staff', name: { en: 'Operational Staff', am: 'ስራ ሰራተኛ' }, rank: 8 }, ]; let created = 0; for (const pos of positions) { const existing = await manager.query>( `SELECT id FROM iam.positions WHERE key = $1 AND organization_id = $2 LIMIT 1`, [pos.key, organizationId], ); if (existing.length === 0) { await manager.query( `INSERT INTO iam.positions (id, name, key, rank, organization_id, unit_id, position_type_id, created_at, updated_at) VALUES (gen_random_uuid(), $1::jsonb, $2, $3, $4::uuid, NULL, NULL, NOW(), NOW())`, [JSON.stringify(pos.name), pos.key, pos.rank, organizationId], ); created++; } } this.logger.log(`Ensured ${positions.length} passenger positions (${created} newly created)`); } }