From 57776c4fef0e5e5528aab547900f0d880fccd6bf Mon Sep 17 00:00:00 2001 From: Michael Abebe Date: Mon, 1 Jun 2026 16:28:04 +0300 Subject: [PATCH] feat(freight:backoffice): role and permission seeder --- .gitignore | 7 +- .../src/seed/edr-org.seeder.ts | 181 ++++++++++++++++-- apps/edr-freight-web/backoffice/src/App.tsx | 1 + 3 files changed, 169 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 132db3f2a..13865633d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,9 @@ coverage/ # OS/editor .DS_Store .idea/ -.vscode/ \ No newline at end of file +.vscode/ + +# emacs cache files +*~ +\#*\# +.\#* diff --git a/apps/edr-freight-api/src/seed/edr-org.seeder.ts b/apps/edr-freight-api/src/seed/edr-org.seeder.ts index 63b464090..d03b9ef7f 100644 --- a/apps/edr-freight-api/src/seed/edr-org.seeder.ts +++ b/apps/edr-freight-api/src/seed/edr-org.seeder.ts @@ -2,21 +2,48 @@ import { Injectable, Logger } from "@nestjs/common"; import { Organization, OrganizationConfiguration, + Permission, Role, + RolePermission, } from "@tria-plc/iamapi-common"; -import { DataSource } from "typeorm"; +import { DataSource, EntityManager, In } from "typeorm"; const EDR_ORG_KEY = "edr_freight"; const EDR_ORG_NAME = { en: "EDR Freight" }; const SEED_FLAG = "SEED_EDR_ORG"; -const EDR_ROLES = [ + +type SeedPermission = { + key: string; + name: { en: string }; +}; + +type SeedRole = { + key: string; + name: { en: string }; + permissions: SeedPermission[]; +}; + +type SeedOrganization = { + id: string; + key: string; +}; + +const SEED_ROLES: SeedRole[] = [ { key: "edr_employee", name: { en: "EDR Employee" }, + permissions: [ + // { key: "permission:key", name: { en: "Permission Name" } }, + { key: "permission:key", name: { en: "Permission Name" } }, + ], }, { key: "edr_customer", name: { en: "EDR Customer" }, + permissions: [ + // { key: "permission:key", name: { en: "Permission Name" } }, + { key: "permission:key", name: { en: "Permission Name" } }, + ], }, ]; @@ -27,24 +54,31 @@ export class EdrOrgSeeder { constructor(private readonly dataSource: DataSource) {} async run() { - const shouldSeed = process.env[SEED_FLAG]?.trim().toLowerCase() === "true"; - - if (!shouldSeed) { + if (!this.shouldSeed()) { this.logger.log(`Skipping EDR org seed because ${SEED_FLAG} is not enabled`); return; } - const roleRepository = this.dataSource.getRepository(Role); - const organizationRepository = this.dataSource.getRepository(Organization); - const organizationConfigurationRepository = - this.dataSource.getRepository(OrganizationConfiguration); + await this.dataSource.transaction(async (manager) => { + const organization = await this.ensureOrganization(manager); - await roleRepository.upsert(EDR_ROLES, { - conflictPaths: { key: true }, + await this.ensureOrganizationConfiguration(manager, organization.id); + await this.ensurePermissions(manager, SEED_ROLES); + await this.ensureRoles(manager, SEED_ROLES); + await this.ensureRolePermissions(manager, SEED_ROLES); }); - this.logger.log("Ensured EDR roles 'edr_employee' and 'edr_customer'"); + 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 }, @@ -57,18 +91,31 @@ export class EdrOrgSeeder { isGovernmentOrganization: true, }); - organization = { + this.logger.log(`Seeded EDR organization '${EDR_ORG_KEY}'`); + + return { id: insertResult.identifiers[0]?.id as string, key: EDR_ORG_KEY, - } as Organization; - - this.logger.log(`Seeded EDR organization '${EDR_ORG_KEY}'`); - } else { - this.logger.log(`Ensured EDR organization '${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: organization.id, + organizationId, canCreateBranchByItself: true, canStartReceivingRecord: true, }, { @@ -79,4 +126,100 @@ export class EdrOrgSeeder { `Ensured organization configuration for '${EDR_ORG_KEY}'`, ); } + + private collectPermissions(seedRoles: SeedRole[]) { + const permissionByKey = new Map(); + + for (const role of seedRoles) { + for (const permission of role.permissions) { + permissionByKey.set(permission.key, permission); + } + } + + return [...permissionByKey.values()]; + } + + private async ensurePermissions(manager: EntityManager, seedRoles: SeedRole[]) { + const permissions = this.collectPermissions(seedRoles); + + if (!permissions.length) { + this.logger.log("No EDR role permissions configured; skipping permission seed"); + return; + } + + await manager.getRepository(Permission).upsert(permissions, { + conflictPaths: { key: true }, + }); + + this.logger.log(`Ensured ${permissions.length} EDR permissions`); + } + + private async ensureRoles(manager: EntityManager, seedRoles: SeedRole[]) { + 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: SeedRole[], + ) { + const permissions = this.collectPermissions(seedRoles); + + if (!permissions.length) { + return; + } + + const roleRepository = manager.getRepository(Role); + const permissionRepository = manager.getRepository(Permission); + 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 permissionRepository.find({ + where: { key: In(permissions.map((permission) => permission.key)) }, + 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.permissions.map((permission) => { + const seededPermission = permissionByKey.get(permission.key); + + if (!seededPermission) { + throw new Error(`missing_permission:${permission.key}`); + } + + 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`); + } } diff --git a/apps/edr-freight-web/backoffice/src/App.tsx b/apps/edr-freight-web/backoffice/src/App.tsx index 3ab4f2e54..85e14d9eb 100644 --- a/apps/edr-freight-web/backoffice/src/App.tsx +++ b/apps/edr-freight-web/backoffice/src/App.tsx @@ -13,6 +13,7 @@ import FileUploadSettingsPage from "./pages/documents/FileUploadSettingsPage"; import DropdownSettingsPage from "./pages/dropdown_settings/DropdownSettingsPage"; import DemoUser1Page from "./pages/dashboard/demo/DemoUser1Page"; import DemoUser2Page from "./pages/dashboard/demo/DemoUser2Page"; +import { RuleEnginePage } from "./pages/ruleEngine/RuleEngine"; const sidebarItems: SidebarItem[] = [ {