Files
edr-platform/apps/edr-freight-api/src/seed/freight-positions.seeder.ts
2026-07-07 12:28:47 +00:00

172 lines
4.9 KiB
TypeScript

import { Injectable, Logger } from '@nestjs/common';
import {
Organization,
Permission,
Position,
PositionPermission,
Unit,
} from '@tria-plc/iamapi-common';
import { DataSource, EntityManager, In } from 'typeorm';
import { EDR_FREIGHT_POSITIONS } from './edr-freight.seed';
const SEED_FLAG = 'SEED_EDR_ORG';
const EDR_ORG_KEY = 'edr_freight';
const EDR_UNIT_KEY = 'edr_freight_app';
/**
* Seeds the operational freight positions (CEO, Chief, Director, Marketer,
* Operation, Ethiopian GL, Djibouti GL) as Position + PositionPermission rows
* on the `edr_freight_app` unit. Positions-as-roles: users get their freight
* access by being assigned to a Position (via EmployeePosition), and the
* position's PositionPermission grants come from EDR_FREIGHT_POSITIONS.
*
* Gated behind the same SEED_EDR_ORG flag as EdrOrgSeeder and depends on the
* org/unit/permission catalog it seeds, so it must run AFTER EdrOrgSeeder.
* Idempotent: positions upsert by (key, unitId); grants insert only the
* permission ids a position is still missing.
*/
@Injectable()
export class FreightPositionsSeeder {
private readonly logger = new Logger(FreightPositionsSeeder.name);
constructor(private readonly dataSource: DataSource) {}
async run() {
if (process.env[SEED_FLAG]?.trim().toLowerCase() !== 'true') {
this.logger.log(
`Skipping freight positions seed because ${SEED_FLAG} is not enabled`,
);
return;
}
await this.dataSource.transaction(async (manager) => {
const organization = await manager.getRepository(Organization).findOne({
where: { key: EDR_ORG_KEY },
select: { id: true },
});
if (!organization) {
throw new Error(`missing_organization:${EDR_ORG_KEY}`);
}
const unit = await manager.getRepository(Unit).findOne({
where: { key: EDR_UNIT_KEY, organizationId: organization.id },
select: { id: true },
});
if (!unit) {
throw new Error(`missing_unit:${EDR_UNIT_KEY}`);
}
const permissionKeyToId = await this.loadPermissionIds(manager);
for (const seed of EDR_FREIGHT_POSITIONS) {
const positionId = await this.ensurePosition(
manager,
seed,
unit.id as string,
organization.id as string,
);
await this.ensurePositionPermissions(
manager,
positionId,
seed,
permissionKeyToId,
);
}
});
this.logger.log(
`Ensured ${EDR_FREIGHT_POSITIONS.length} freight positions on unit '${EDR_UNIT_KEY}'`,
);
}
/** Resolve every permission key referenced by any position to its id. */
private async loadPermissionIds(
manager: EntityManager,
): Promise<Map<string, string>> {
const keys = [
...new Set(EDR_FREIGHT_POSITIONS.flatMap((p) => p.permissionKeys)),
];
const permissions = await manager.getRepository(Permission).find({
where: { key: In(keys) },
select: { id: true, key: true },
});
const map = new Map(permissions.map((p) => [p.key, p.id as string]));
const missing = keys.filter((key) => !map.has(key));
if (missing.length > 0) {
throw new Error(`missing_permissions:${missing.join(',')}`);
}
return map;
}
private async ensurePosition(
manager: EntityManager,
seed: (typeof EDR_FREIGHT_POSITIONS)[number],
unitId: string,
organizationId: string,
): Promise<string> {
const positionRepository = manager.getRepository(Position);
const existing = await positionRepository.findOne({
where: { key: seed.key, unitId },
select: { id: true },
});
if (existing) {
return existing.id as string;
}
const inserted = await positionRepository.insert({
key: seed.key,
name: { ...seed.name },
rank: seed.rank,
unitId,
organizationId,
});
this.logger.log(`Seeded freight position '${seed.key}'`);
return inserted.identifiers[0]?.id as string;
}
private async ensurePositionPermissions(
manager: EntityManager,
positionId: string,
seed: (typeof EDR_FREIGHT_POSITIONS)[number],
permissionKeyToId: Map<string, string>,
) {
const positionPermissionRepository =
manager.getRepository(PositionPermission);
const existing = await positionPermissionRepository.find({
where: { positionId },
select: { permissionId: true },
});
const existingPermissionIds = new Set(
existing.map((row) => row.permissionId),
);
const rowsToInsert = seed.permissionKeys
.map((key) => permissionKeyToId.get(key) as string)
.filter((permissionId) => !existingPermissionIds.has(permissionId))
.map((permissionId) => ({ positionId, permissionId }));
if (rowsToInsert.length === 0) {
return;
}
await positionPermissionRepository.insert(rowsToInsert);
this.logger.log(
`Granted ${rowsToInsert.length} permissions to position '${seed.key}'`,
);
}
}