mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 15:18:11 +00:00
184 lines
6.5 KiB
TypeScript
184 lines
6.5 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
import { WagonStatus } from '@edr/types';
|
|
import { hashPassword } from '@tria-plc/api-common/utils/argon';
|
|
import { EUserStatus } from '@tria-plc/api-common/utils/enums/user.enum';
|
|
import {
|
|
Employee,
|
|
Organization,
|
|
Role,
|
|
User,
|
|
UserCredential,
|
|
UserRole,
|
|
} from '@tria-plc/iamapi-common';
|
|
import { DataSource, EntityManager } from 'typeorm';
|
|
|
|
import { Wagon } from '../modules/wagons/entities/wagon.entity';
|
|
import { WagonType } from '../modules/wagon-types/entities/wagon-type.entity';
|
|
import { ApprovalRule } from '../modules/rule-engine/entities/approval-rule.entity';
|
|
import { DEFAULT_APPROVAL_RULE_ROWS } from '../modules/rule-engine/approval-rules.defaults';
|
|
|
|
const EDR_ORG_KEY = 'edr_freight';
|
|
const MIN_WAGONS_PER_TYPE = 100;
|
|
|
|
/** The demo staff users, each mapped to a seeded freight role. */
|
|
const DEMO_STAFF_USERS = [
|
|
{ email: 'marketing@edr.local', username: 'marketing', roleKey: 'edr_marketing' },
|
|
{ email: 'operations@edr.local', username: 'operations', roleKey: 'edr_operations_officer' },
|
|
{ email: 'director@edr.local', username: 'director', roleKey: 'edr_director' },
|
|
{ email: 'ceo@edr.local', username: 'ceo', roleKey: 'edr_ceo' },
|
|
{ email: 'gl-et@edr.local', username: 'gl_et', roleKey: 'edr_gl_ethiopia' },
|
|
{ email: 'gl-dj@edr.local', username: 'gl_dj', roleKey: 'edr_gl_djibouti' },
|
|
] as const;
|
|
|
|
/**
|
|
* One-shot demo data: at least 100 wagons per wagon type, the default approval
|
|
* chains, and four staff users with distinct permissions. Every block guards on
|
|
* an "is it already populated?" check, so this is safe to run on every boot and
|
|
* does nothing once the data exists.
|
|
*/
|
|
@Injectable()
|
|
export class DemoFreightDataSeeder {
|
|
private readonly logger = new Logger(DemoFreightDataSeeder.name);
|
|
|
|
constructor(private readonly dataSource: DataSource) {}
|
|
|
|
async run() {
|
|
await this.dataSource.transaction(async (manager) => {
|
|
// Demo freight data (wagons + approval rules) disabled — keep only the
|
|
// 4 staff users. The seeders are retained for easy re-enabling; flip
|
|
// SEED_DEMO_FREIGHT_DATA=true to run them.
|
|
if (process.env.SEED_DEMO_FREIGHT_DATA === 'true') {
|
|
await this.seedWagons(manager);
|
|
await this.seedApprovalRules(manager);
|
|
}
|
|
await this.seedStaffUsers(manager);
|
|
});
|
|
}
|
|
|
|
/** Ensure every wagon type has at least MIN_WAGONS_PER_TYPE wagons. */
|
|
private async seedWagons(manager: EntityManager) {
|
|
const wagonTypeRepo = manager.getRepository(WagonType);
|
|
const wagonRepo = manager.getRepository(Wagon);
|
|
|
|
const wagonTypes = await wagonTypeRepo.find();
|
|
if (wagonTypes.length === 0) {
|
|
this.logger.warn('No wagon types found; skipping wagon seed');
|
|
return;
|
|
}
|
|
|
|
for (const type of wagonTypes) {
|
|
const existing = await wagonRepo.count({ where: { wagonTypeId: type.id } });
|
|
if (existing >= MIN_WAGONS_PER_TYPE) {
|
|
this.logger.log(
|
|
`Wagon type ${type.code} already has ${existing} wagons; skipping`,
|
|
);
|
|
continue;
|
|
}
|
|
|
|
const toCreate = MIN_WAGONS_PER_TYPE - existing;
|
|
const rows = Array.from({ length: toCreate }, (_, i) => {
|
|
const seq = existing + i + 1;
|
|
return wagonRepo.create({
|
|
wagonNumber: `${type.code}-${String(seq).padStart(4, '0')}`,
|
|
wagonTypeId: type.id,
|
|
status: WagonStatus.Available,
|
|
});
|
|
});
|
|
await wagonRepo.save(rows);
|
|
this.logger.log(`Seeded ${toCreate} wagons for type ${type.code}`);
|
|
}
|
|
}
|
|
|
|
/** Seed the default approval chains when the table is empty. */
|
|
private async seedApprovalRules(manager: EntityManager) {
|
|
const repo = manager.getRepository(ApprovalRule);
|
|
const count = await repo.count();
|
|
if (count > 0) {
|
|
this.logger.log(`Approval rules already populated (${count}); skipping`);
|
|
return;
|
|
}
|
|
await repo.save(DEFAULT_APPROVAL_RULE_ROWS.map((row) => repo.create(row)));
|
|
this.logger.log(`Seeded ${DEFAULT_APPROVAL_RULE_ROWS.length} approval rules`);
|
|
}
|
|
|
|
/** Create the four demo staff users with their roles (idempotent per email). */
|
|
private async seedStaffUsers(manager: EntityManager) {
|
|
const organization = await manager.getRepository(Organization).findOne({
|
|
where: { key: EDR_ORG_KEY },
|
|
select: { id: true, key: true },
|
|
});
|
|
if (!organization) {
|
|
this.logger.warn(`Missing organization ${EDR_ORG_KEY}; skipping staff users`);
|
|
return;
|
|
}
|
|
|
|
const roleRepo = manager.getRepository(Role);
|
|
const userRepo = manager.getRepository(User);
|
|
const credentialRepo = manager.getRepository(UserCredential);
|
|
const userRoleRepo = manager.getRepository(UserRole);
|
|
const employeeRepo = manager.getRepository(Employee);
|
|
|
|
const password = process.env.DEFAULT_PASSWORD?.trim() || '12345678';
|
|
const hashedPassword = await hashPassword(password);
|
|
|
|
for (const staff of DEMO_STAFF_USERS) {
|
|
const role = await roleRepo.findOne({
|
|
where: { key: staff.roleKey },
|
|
select: { id: true, key: true },
|
|
});
|
|
if (!role) {
|
|
this.logger.warn(`Missing role ${staff.roleKey}; skipping ${staff.email}`);
|
|
continue;
|
|
}
|
|
|
|
let user = await userRepo.findOne({
|
|
where: { email: staff.email },
|
|
select: { id: true, email: true },
|
|
});
|
|
if (!user) {
|
|
user = await userRepo.save(
|
|
userRepo.create({
|
|
email: staff.email,
|
|
username: staff.username,
|
|
name: { en: staff.username },
|
|
isActive: true,
|
|
hasSetPassword: true,
|
|
status: EUserStatus.ACCEPTED,
|
|
}),
|
|
);
|
|
this.logger.log(`Seeded staff user ${staff.email}`);
|
|
}
|
|
|
|
const hasCredential = await credentialRepo.exists({
|
|
where: { userId: user.id, isActive: true },
|
|
});
|
|
if (!hasCredential) {
|
|
await credentialRepo.insert({
|
|
userId: user.id,
|
|
password: hashedPassword,
|
|
isActive: true,
|
|
});
|
|
}
|
|
|
|
await userRoleRepo.upsert(
|
|
{ userId: user.id, roleId: role.id, organizationId: organization.id },
|
|
{ conflictPaths: { userId: true, roleId: true } },
|
|
);
|
|
|
|
const hasEmployee = await employeeRepo.exists({
|
|
where: { userId: user.id, organizationId: organization.id, isCurrent: true },
|
|
});
|
|
if (!hasEmployee) {
|
|
await employeeRepo.insert({
|
|
userId: user.id,
|
|
organizationId: organization.id,
|
|
isCurrent: true,
|
|
name: { en: staff.username },
|
|
});
|
|
}
|
|
}
|
|
|
|
this.logger.log('Ensured demo staff users (marketing@, operations@, director@, ceo@)');
|
|
}
|
|
}
|