Files
edr-platform/apps/edr-freight-api/src/modules/audit/audit.service.ts
marshalyordanos 5da36eb128 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.
2026-08-12 09:36:50 +03:00

72 lines
2.4 KiB
TypeScript

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();
}
}