mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 09:35:44 +00:00
- 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.
82 lines
3.2 KiB
TypeScript
82 lines
3.2 KiB
TypeScript
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
|
|
/**
|
|
* Backoffice audit trail for every state-changing freight endpoint.
|
|
*
|
|
* No `updated_at` / `deleted_at` columns, unlike every other table here: audit
|
|
* rows are insert-only evidence. A soft-delete column would let an actor erase
|
|
* their own trail and TypeORM would then hide those rows from default queries
|
|
* silently — see the entity comment.
|
|
*
|
|
* `user_id` intentionally carries NO foreign key to the `iam` schema.
|
|
* Cross-schema FKs are forbidden platform-wide, and one here would let user
|
|
* deletion cascade away the record of what that user did.
|
|
*
|
|
* DDL is idempotent (`IF NOT EXISTS`) because watch-mode API instances race
|
|
* `migrationsRun` against each other on the shared dev database.
|
|
*/
|
|
export class CreateAuditLogs3390000000000 implements MigrationInterface {
|
|
name = "CreateAuditLogs3390000000000";
|
|
|
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`
|
|
CREATE TABLE IF NOT EXISTS freight.audit_logs (
|
|
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
title varchar(255) NOT NULL,
|
|
method varchar(10) NOT NULL,
|
|
url text NOT NULL,
|
|
route_path varchar(255),
|
|
type varchar(50) NOT NULL,
|
|
is_success boolean NOT NULL,
|
|
user_id uuid,
|
|
resource_id varchar(64),
|
|
request jsonb,
|
|
status_code smallint,
|
|
error_message text,
|
|
user_name varchar(150),
|
|
user_role varchar(100),
|
|
ip_address inet,
|
|
user_agent text,
|
|
request_id varchar(64),
|
|
duration_ms integer,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
CONSTRAINT "PK_audit_logs" PRIMARY KEY (id)
|
|
)
|
|
`);
|
|
|
|
// Every audit query is time-bounded, so created_at leads each index.
|
|
// DESC matches the "newest first" read path the controller exposes.
|
|
await queryRunner.query(`
|
|
CREATE INDEX IF NOT EXISTS "IDX_audit_logs_created_at"
|
|
ON freight.audit_logs (created_at DESC)
|
|
`);
|
|
await queryRunner.query(`
|
|
CREATE INDEX IF NOT EXISTS "IDX_audit_logs_user_id_created_at"
|
|
ON freight.audit_logs (user_id, created_at DESC)
|
|
`);
|
|
await queryRunner.query(`
|
|
CREATE INDEX IF NOT EXISTS "IDX_audit_logs_type_created_at"
|
|
ON freight.audit_logs (type, created_at DESC)
|
|
`);
|
|
await queryRunner.query(`
|
|
CREATE INDEX IF NOT EXISTS "IDX_audit_logs_type_resource_id"
|
|
ON freight.audit_logs (type, resource_id)
|
|
`);
|
|
await queryRunner.query(`
|
|
CREATE INDEX IF NOT EXISTS "IDX_audit_logs_route_path_created_at"
|
|
ON freight.audit_logs (route_path, created_at DESC)
|
|
`);
|
|
// Failures are a small slice of the table but carry the security signal
|
|
// (403s especially), so they get their own partial index.
|
|
await queryRunner.query(`
|
|
CREATE INDEX IF NOT EXISTS "IDX_audit_logs_failures"
|
|
ON freight.audit_logs (created_at DESC)
|
|
WHERE is_success = false
|
|
`);
|
|
}
|
|
|
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`DROP TABLE IF EXISTS freight.audit_logs`);
|
|
}
|
|
}
|