mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 08:25:43 +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:
71
apps/edr-freight-api/src/modules/audit/audit.service.ts
Normal file
71
apps/edr-freight-api/src/modules/audit/audit.service.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||
import { PaginatedResponse } from '@edr/types';
|
||||
|
||||
import { AuditLog } from './entities/audit-log.entity';
|
||||
import { AuditLogRepository } from './audit-log.repository';
|
||||
import { AuditLogQueryDto } from './dto/audit-log-query.dto';
|
||||
import {
|
||||
buildPaginationMeta,
|
||||
normalizePagination,
|
||||
} from '../../common/utils/pagination.util';
|
||||
|
||||
@Injectable()
|
||||
export class AuditService {
|
||||
private readonly logger = new Logger(AuditService.name);
|
||||
|
||||
constructor(private readonly auditLogRepository: AuditLogRepository) {}
|
||||
|
||||
/**
|
||||
* Persist one audit row, swallowing any failure.
|
||||
*
|
||||
* An audit write must never turn a successful business action into an error
|
||||
* for the user: if this table is full, misconfigured or mid-migration,
|
||||
* contract approvals still need to work. Failures are logged so the gap is
|
||||
* visible in application logs rather than silent.
|
||||
*/
|
||||
async record(entry: Partial<AuditLog>): Promise<void> {
|
||||
try {
|
||||
await this.auditLogRepository.record(entry);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to write audit log for ${entry.method} ${entry.routePath}: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Paginated, filtered audit history, newest first. */
|
||||
async search(query: AuditLogQueryDto): Promise<PaginatedResponse<AuditLog>> {
|
||||
const { page, pageSize, skip, take } = normalizePagination(query);
|
||||
|
||||
const from = query.from ? new Date(query.from) : undefined;
|
||||
const to = query.to ? new Date(query.to) : undefined;
|
||||
|
||||
// A reversed range silently returns zero rows, which reads as "nothing
|
||||
// happened" rather than "your filter is wrong" — reject it explicitly.
|
||||
if (from && to && from > to) {
|
||||
throw new BadRequestException('`from` must be earlier than `to`');
|
||||
}
|
||||
|
||||
const [items, total] = await this.auditLogRepository.search({
|
||||
type: query.type,
|
||||
userId: query.userId,
|
||||
method: query.method,
|
||||
resourceId: query.resourceId,
|
||||
isSuccess:
|
||||
query.isSuccess === undefined ? undefined : query.isSuccess === 'true',
|
||||
from,
|
||||
to,
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
|
||||
return { items, meta: buildPaginationMeta(total, page, pageSize) };
|
||||
}
|
||||
|
||||
/** Distinct entity types, for the filter dropdown on the audit screen. */
|
||||
async listTypes(): Promise<string[]> {
|
||||
return this.auditLogRepository.distinctTypes();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user