import { Injectable, Logger } from "@nestjs/common"; import { Application, Organization, OrganizationConfiguration, Permission, Unit, } from "@tria-plc/iamapi-common"; import { DataSource, EntityManager } from "typeorm"; import { EDR_FREIGHT_APPLICATION, EDR_FREIGHT_PERMISSIONS, } from "./edr-freight.seed"; const EDR_UNIT_KEY = "edr_freight_app"; const EDR_UNIT_NAME = { en: "EDR Freight App" }; const EDR_ORG_KEY = "edr_freight"; const EDR_ORG_NAME = { en: "EDR Freight" }; const SEED_FLAG = "SEED_EDR_ORG"; type SeedOrganization = { id: string; key: string; }; @Injectable() export class EdrOrgSeeder { private readonly logger = new Logger(EdrOrgSeeder.name); constructor(private readonly dataSource: DataSource) { } async run() { if (!this.shouldSeed()) { this.logger.log( `Skipping EDR org seed because ${SEED_FLAG} is not enabled`, ); return; } await this.dataSource.transaction(async (manager) => { const organization = await this.ensureOrganization(manager); await this.ensureOrganizationConfiguration(manager, organization.id); await this.ensureDefaultUnit(manager, organization.id); const application = await this.ensureApplication(manager); await this.ensurePermissions(manager, application.id); // Roles, positions and their permission links are intentionally NOT // seeded for now — only the application-scoped permission catalog, // mirroring how the default IAM seed relates permissions to their // application. Grants are assigned later through the IAM UI. }); this.logger.log(`Ensured EDR organization seed for '${EDR_ORG_KEY}'`); } private shouldSeed() { return process.env[SEED_FLAG]?.trim().toLowerCase() === "true"; } private async ensureOrganization( manager: EntityManager, ): Promise { const organizationRepository = manager.getRepository(Organization); let organization = await organizationRepository.findOne({ where: { key: EDR_ORG_KEY }, select: { id: true, key: true }, }); if (!organization) { const insertResult = await organizationRepository.insert({ key: EDR_ORG_KEY, name: EDR_ORG_NAME, isGovernmentOrganization: true, }); this.logger.log(`Seeded EDR organization '${EDR_ORG_KEY}'`); return { id: insertResult.identifiers[0]?.id as string, key: EDR_ORG_KEY, }; } this.logger.log(`Ensured EDR organization '${EDR_ORG_KEY}'`); return { id: organization.id as string, key: EDR_ORG_KEY, }; } private async ensureOrganizationConfiguration( manager: EntityManager, organizationId: string, ) { const organizationConfigurationRepository = manager.getRepository( OrganizationConfiguration, ); await organizationConfigurationRepository.upsert( { organizationId, canCreateBranchByItself: true, canStartReceivingRecord: true, }, { conflictPaths: { organizationId: true }, }, ); this.logger.log(`Ensured organization configuration for '${EDR_ORG_KEY}'`); } private async ensureDefaultUnit( manager: EntityManager, organizationId: string, ): Promise<{ id: string }> { const unitRepository = manager.getRepository(Unit); let unit = await unitRepository.findOne({ where: { key: EDR_UNIT_KEY, organizationId }, select: { id: true }, }); if (!unit) { const insertResult = await unitRepository.insert({ key: EDR_UNIT_KEY, name: EDR_UNIT_NAME, organizationId, }); this.logger.log(`Seeded EDR unit '${EDR_UNIT_KEY}'`); return { id: insertResult.identifiers[0]?.id as string }; } this.logger.log(`Ensured EDR unit '${EDR_UNIT_KEY}'`); return { id: unit.id }; } private async ensureApplication( manager: EntityManager, ): Promise<{ id: string }> { const applicationRepository = manager.getRepository(Application); const application = await applicationRepository.findOne({ where: { key: EDR_FREIGHT_APPLICATION.key }, select: { id: true }, }); if (!application?.id) { const insertResult = await applicationRepository.insert({ id: EDR_FREIGHT_APPLICATION.id, key: EDR_FREIGHT_APPLICATION.key, name: { ...EDR_FREIGHT_APPLICATION.name }, }); this.logger.log( `Seeded EDR application '${EDR_FREIGHT_APPLICATION.key}'`, ); return { id: insertResult.identifiers[0]?.id as string }; } this.logger.log(`Ensured EDR application '${EDR_FREIGHT_APPLICATION.key}'`); return { id: application.id }; } private async ensurePermissions( manager: EntityManager, applicationId: string, ) { // iam.permissions has TWO unique columns (PK id, UQ key) but ON CONFLICT // can only target one. Seeding a hand-minted id that some older/retired key // already owns in an environment slips past ON CONFLICT (key) and dies on // the PK. The key is the identity every consumer resolves by (positions // seeder maps key -> id at runtime), so ids are left to the column default // and never sent — no id can collide. await manager .createQueryBuilder() .insert() .into(Permission) .values( EDR_FREIGHT_PERMISSIONS.map((permission) => ({ key: permission.key, name: { ...permission.name }, applicationId, })), ) .orIgnore() // .orUpdate(["name", "application_id"], ["key"]) .execute(); this.logger.log( `Ensured ${EDR_FREIGHT_PERMISSIONS.length} permissions on application '${EDR_FREIGHT_APPLICATION.key}'`, ); } }