mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 02:58:11 +00:00
feat: add wagon usage computation and maintenance logging features
- Implemented utility to calculate wagon usage metrics for train schedules. - Created for sending wagons to maintenance with optional notes. - Added unit tests for train builder maintenance functionalities, including formatting train run labels and building maintenance notes. - Developed component for merging train schedules with detailed previews and reasons for merging. - Introduced component for selecting wagons with search functionality and selection limits. - Created for displaying and filtering audit logs, including detailed views of individual log entries. - Added for handling API interactions related to audit logs, including fetching logs and entity types.
This commit is contained in:
84
apps/edr-freight-api/src/modules/audit/audit-actor.ts
Normal file
84
apps/edr-freight-api/src/modules/audit/audit-actor.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Who acted, and does this API audit them?
|
||||
*
|
||||
* The staff/customer split reuses the exact discriminator the permission guards
|
||||
* already apply (`freight-permission.guard.ts`): `userType === 'employee'` is
|
||||
* backoffice, `individual` / `external_organization` are customers. Restating
|
||||
* the rule instead of importing it would let the two drift apart silently.
|
||||
*/
|
||||
|
||||
const EMPLOYEE_USER_TYPE = 'employee';
|
||||
const SUPER_ADMIN_ROLE = 'super_admin';
|
||||
|
||||
/** The subset of the JWT payload this module reads. */
|
||||
export interface AuditActorSource {
|
||||
id?: string;
|
||||
sub?: string;
|
||||
userType?: string;
|
||||
username?: string;
|
||||
name?: string | { en?: string; am?: string };
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
roles?: { key?: string; name?: string }[];
|
||||
employee?: unknown;
|
||||
}
|
||||
|
||||
export interface AuditActor {
|
||||
userId: string | null;
|
||||
userName: string | null;
|
||||
userRole: string | null;
|
||||
}
|
||||
|
||||
function isSuperAdmin(user: AuditActorSource): boolean {
|
||||
return Boolean(user.roles?.some((role) => role.key === SUPER_ADMIN_ROLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this caller a backoffice user whose actions are audited?
|
||||
*
|
||||
* Only employees qualify. Customers are excluded by request, and unauthenticated
|
||||
* callers are excluded too — which means failed logins, OTP sends and password
|
||||
* resets produce no audit rows. That was a deliberate call: those endpoints are
|
||||
* not backoffice actions. Note the trade-off, since failed-auth attempts are
|
||||
* often what an incident review looks for first.
|
||||
*/
|
||||
export function isAuditableActor(user: AuditActorSource | null | undefined): boolean {
|
||||
if (!user) return false;
|
||||
// Super admins may not carry an `employee` userType on every token, but are
|
||||
// unambiguously staff — the permission guards treat them the same way.
|
||||
return user.userType === EMPLOYEE_USER_TYPE || isSuperAdmin(user);
|
||||
}
|
||||
|
||||
/** Best-effort display name, tolerating the several shapes tokens use. */
|
||||
function resolveUserName(user: AuditActorSource): string | null {
|
||||
if (typeof user.name === 'string' && user.name.trim()) return user.name.trim();
|
||||
|
||||
if (user.name && typeof user.name === 'object') {
|
||||
const localized = user.name.en ?? user.name.am;
|
||||
if (localized?.trim()) return localized.trim();
|
||||
}
|
||||
|
||||
const composed = [user.firstName, user.lastName].filter(Boolean).join(' ').trim();
|
||||
if (composed) return composed;
|
||||
|
||||
return user.username?.trim() || user.email?.trim() || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshot the actor at the moment of the action.
|
||||
*
|
||||
* Name and role are copied, never referenced: resolving them from IAM at read
|
||||
* time would rewrite history whenever someone is renamed, changes role or is
|
||||
* deleted. An audit row from last year must still say who acted and with what
|
||||
* authority *then*.
|
||||
*/
|
||||
export function resolveAuditActor(user: AuditActorSource): AuditActor {
|
||||
const roleKey = user.roles?.[0]?.key ?? user.roles?.[0]?.name ?? null;
|
||||
|
||||
return {
|
||||
userId: user.id ?? user.sub ?? null,
|
||||
userName: resolveUserName(user),
|
||||
userRole: roleKey,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user