mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 15:18:11 +00:00
enhance user management and booking features with permissions and real-time updates
This commit is contained in:
@@ -1,30 +1,20 @@
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import {
|
||||
Application,
|
||||
Organization,
|
||||
OrganizationConfiguration,
|
||||
Permission,
|
||||
Position,
|
||||
PositionPermission,
|
||||
PositionType,
|
||||
Role,
|
||||
RolePermission,
|
||||
Unit,
|
||||
} from "@tria-plc/iamapi-common";
|
||||
import { DataSource, EntityManager, In } from "typeorm";
|
||||
import { DataSource, EntityManager } 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,
|
||||
EDR_FREIGHT_APPLICATION,
|
||||
EDR_FREIGHT_PERMISSIONS,
|
||||
} 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_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" };
|
||||
@@ -51,22 +41,15 @@ export class EdrOrgSeeder {
|
||||
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);
|
||||
await this.ensureDefaultUnit(manager, organization.id);
|
||||
|
||||
// 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);
|
||||
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}'`);
|
||||
@@ -128,111 +111,6 @@ export class EdrOrgSeeder {
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -258,120 +136,51 @@ export class EdrOrgSeeder {
|
||||
return { id: unit.id };
|
||||
}
|
||||
|
||||
private async ensureDefaultPositionType(
|
||||
private async ensureApplication(
|
||||
manager: EntityManager,
|
||||
unitId: string,
|
||||
): Promise<{ id: string }> {
|
||||
const positionTypeRepository = manager.getRepository(PositionType);
|
||||
const applicationRepository = manager.getRepository(Application);
|
||||
|
||||
// PositionType has no unique constraint on (key, unitId); find-then-insert.
|
||||
let positionType = await positionTypeRepository.findOne({
|
||||
where: { key: EDR_POSITION_TYPE_KEY, unitId },
|
||||
const application = await applicationRepository.findOne({
|
||||
where: { key: EDR_FREIGHT_APPLICATION.key },
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
if (!positionType) {
|
||||
const insertResult = await positionTypeRepository.insert({
|
||||
key: EDR_POSITION_TYPE_KEY,
|
||||
name: EDR_POSITION_TYPE_NAME,
|
||||
isSystem: true,
|
||||
unitId,
|
||||
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 position type '${EDR_POSITION_TYPE_KEY}'`);
|
||||
this.logger.log(`Seeded EDR application '${EDR_FREIGHT_APPLICATION.key}'`);
|
||||
return { id: insertResult.identifiers[0]?.id as string };
|
||||
}
|
||||
|
||||
this.logger.log(`Ensured EDR position type '${EDR_POSITION_TYPE_KEY}'`);
|
||||
return { id: positionType.id };
|
||||
this.logger.log(`Ensured EDR application '${EDR_FREIGHT_APPLICATION.key}'`);
|
||||
return { id: application.id };
|
||||
}
|
||||
|
||||
private async ensurePositions(
|
||||
private async ensurePermissions(
|
||||
manager: EntityManager,
|
||||
organizationId: string,
|
||||
unitId: string,
|
||||
positionTypeId: string,
|
||||
seedPositions: FreightSeedPosition[],
|
||||
applicationId: string,
|
||||
) {
|
||||
await manager.getRepository(Position).upsert(
|
||||
seedPositions.map(({ key, name, rank }) => ({
|
||||
key,
|
||||
name,
|
||||
rank,
|
||||
organizationId,
|
||||
unitId,
|
||||
positionTypeId,
|
||||
const permissionRepository = manager.getRepository(Permission);
|
||||
|
||||
// Upsert by key so reruns are idempotent; applicationId ties every
|
||||
// permission to the EDR Freight application (also backfills rows that
|
||||
// were previously seeded without the relation).
|
||||
await permissionRepository.upsert(
|
||||
EDR_FREIGHT_PERMISSIONS.map((permission) => ({
|
||||
id: permission.id,
|
||||
key: permission.key,
|
||||
name: { ...permission.name },
|
||||
applicationId,
|
||||
})),
|
||||
{
|
||||
conflictPaths: { key: true, unitId: true },
|
||||
},
|
||||
{ conflictPaths: { key: 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`,
|
||||
`Ensured ${EDR_FREIGHT_PERMISSIONS.length} permissions on application '${EDR_FREIGHT_APPLICATION.key}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user