Files
edr-platform/apps/edr-freight-api/src/seed/edr-org.seeder.ts

378 lines
11 KiB
TypeScript

import { Injectable, Logger } from "@nestjs/common";
import {
Organization,
OrganizationConfiguration,
Permission,
Position,
PositionPermission,
PositionType,
Role,
RolePermission,
Unit,
} from "@tria-plc/iamapi-common";
import { DataSource, EntityManager, In } from "typeorm";
import { ERoleKey } from "@tria-plc/api-common/utils/enums/seed.enum";
import { BOOKING_RULE_ENGINE_PERMISSION_KEYS } from "./freight-permissions.registry";
import {
EDR_FREIGHT_POSITIONS,
EDR_FREIGHT_ROLES,
type FreightSeedPosition,
type FreightSeedRole,
} from "./edr-freight.seed";
const EDR_UNIT_KEY = "edr_freight_hq";
const EDR_UNIT_NAME = { en: "EDR Freight HQ" };
const EDR_POSITION_TYPE_KEY = "edr_freight_role";
const EDR_POSITION_TYPE_NAME = { en: "EDR Freight Role" };
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.ensureRoles(manager, EDR_FREIGHT_ROLES);
await this.ensureRolePermissions(manager, EDR_FREIGHT_ROLES);
await this.ensureSuperAdminPermissions(manager);
// Positions-as-roles: seed operational positions and grant their
// permissions via PositionPermission (not Role/RolePermission).
const unit = await this.ensureDefaultUnit(manager, organization.id);
const positionType = await this.ensureDefaultPositionType(manager, unit.id);
await this.ensurePositions(
manager,
organization.id,
unit.id,
positionType.id,
EDR_FREIGHT_POSITIONS,
);
await this.ensurePositionPermissions(manager, unit.id, EDR_FREIGHT_POSITIONS);
});
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<SeedOrganization> {
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 ensureRoles(manager: EntityManager, seedRoles: FreightSeedRole[]) {
await manager.getRepository(Role).upsert(
seedRoles.map(({ key, name }) => ({ key, name })),
{
conflictPaths: { key: true },
},
);
this.logger.log(
`Ensured EDR roles '${seedRoles.map((role) => role.key).join("', '")}'`,
);
}
private async ensureRolePermissions(
manager: EntityManager,
seedRoles: FreightSeedRole[],
) {
const permissionKeys = [...new Set(seedRoles.flatMap((role) => role.permissionKeys))];
if (!permissionKeys.length) {
this.logger.log("No EDR role permissions configured; skipping role-permission links");
return;
}
const roleRepository = manager.getRepository(Role);
const rolePermissionRepository = manager.getRepository(RolePermission);
const roles = await roleRepository.find({
where: { key: In(seedRoles.map((role) => role.key)) },
select: { id: true, key: true },
});
const seededPermissions = await manager.getRepository(Permission).find({
where: { key: In(permissionKeys) },
select: { id: true, key: true },
});
const roleByKey = new Map(roles.map((role) => [role.key, role]));
const permissionByKey = new Map(
seededPermissions.map((permission) => [permission.key, permission]),
);
const rolePermissions = seedRoles.flatMap((role) => {
const seededRole = roleByKey.get(role.key);
if (!seededRole) {
throw new Error(`missing_role:${role.key}`);
}
return role.permissionKeys.map((permissionKey) => {
const seededPermission = permissionByKey.get(permissionKey);
if (!seededPermission) {
throw new Error(`missing_permission:${permissionKey}`);
}
return {
roleId: seededRole.id,
permissionId: seededPermission.id,
};
});
});
await rolePermissionRepository.upsert(rolePermissions, {
conflictPaths: { roleId: true, permissionId: true },
});
this.logger.log(`Ensured ${rolePermissions.length} EDR role-permission links`);
}
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 booking/rule-engine super_admin links`,
);
return;
}
const permissions = await manager.getRepository(Permission).find({
where: { key: In(BOOKING_RULE_ENGINE_PERMISSION_KEYS) },
select: { id: true, key: true },
});
if (!permissions.length) {
this.logger.warn('No booking/rule-engine permissions found for super_admin');
return;
}
await manager.getRepository(RolePermission).upsert(
permissions.map((permission) => ({
roleId: role.id,
permissionId: permission.id,
})),
{ conflictPaths: { roleId: true, permissionId: true } },
);
this.logger.log(
`Ensured ${permissions.length} booking+rule-engine permissions on super_admin`,
);
}
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 ensureDefaultPositionType(
manager: EntityManager,
unitId: string,
): Promise<{ id: string }> {
const positionTypeRepository = manager.getRepository(PositionType);
// PositionType has no unique constraint on (key, unitId); find-then-insert.
let positionType = await positionTypeRepository.findOne({
where: { key: EDR_POSITION_TYPE_KEY, unitId },
select: { id: true },
});
if (!positionType) {
const insertResult = await positionTypeRepository.insert({
key: EDR_POSITION_TYPE_KEY,
name: EDR_POSITION_TYPE_NAME,
isSystem: true,
unitId,
});
this.logger.log(`Seeded EDR position type '${EDR_POSITION_TYPE_KEY}'`);
return { id: insertResult.identifiers[0]?.id as string };
}
this.logger.log(`Ensured EDR position type '${EDR_POSITION_TYPE_KEY}'`);
return { id: positionType.id };
}
private async ensurePositions(
manager: EntityManager,
organizationId: string,
unitId: string,
positionTypeId: string,
seedPositions: FreightSeedPosition[],
) {
await manager.getRepository(Position).upsert(
seedPositions.map(({ key, name, rank }) => ({
key,
name,
rank,
organizationId,
unitId,
positionTypeId,
})),
{
conflictPaths: { key: true, unitId: true },
},
);
this.logger.log(
`Ensured ${seedPositions.length} EDR positions '${seedPositions
.map((position) => position.key)
.join("', '")}'`,
);
}
private async ensurePositionPermissions(
manager: EntityManager,
unitId: string,
seedPositions: FreightSeedPosition[],
) {
const permissionKeys = [
...new Set(seedPositions.flatMap((position) => position.permissionKeys)),
];
if (!permissionKeys.length) {
this.logger.log(
"No EDR position permissions configured; skipping position-permission links",
);
return;
}
const positions = await manager.getRepository(Position).find({
where: { key: In(seedPositions.map((position) => position.key)), unitId },
select: { id: true, key: true },
});
const seededPermissions = await manager.getRepository(Permission).find({
where: { key: In(permissionKeys) },
select: { id: true, key: true },
});
const positionByKey = new Map(
positions.map((position) => [position.key, position]),
);
const permissionByKey = new Map(
seededPermissions.map((permission) => [permission.key, permission]),
);
const positionPermissions = seedPositions.flatMap((position) => {
const seededPosition = positionByKey.get(position.key);
if (!seededPosition) {
throw new Error(`missing_position:${position.key}`);
}
return position.permissionKeys.map((permissionKey) => {
const seededPermission = permissionByKey.get(permissionKey);
if (!seededPermission) {
throw new Error(`missing_permission:${permissionKey}`);
}
return {
positionId: seededPosition.id as string,
permissionId: seededPermission.id,
};
});
});
await manager.getRepository(PositionPermission).upsert(positionPermissions, {
conflictPaths: { positionId: true, permissionId: true },
});
this.logger.log(
`Ensured ${positionPermissions.length} EDR position-permission links`,
);
}
}