All files / apps/audit-service/src/audit audit-ingest.service.ts

100% Statements 12/12
100% Branches 8/8
100% Functions 2/2
100% Lines 10/10

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 483x 3x 3x     3x     3x 5x       5x                 4x                                       4x 1x        
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
import { WorkOrderEvent } from '@app/contracts';
import { AuditEvent } from './audit-event.entity';
 
@Injectable()
export class AuditIngestService {
  private readonly logger = new Logger(AuditIngestService.name);
 
  constructor(
    @InjectRepository(AuditEvent)
    private readonly repository: Repository<AuditEvent>,
  ) {}
 
  /**
   * Idempotent by construction: ON CONFLICT (event_id) DO NOTHING turns the
   * duplicates inherent to at-least-once delivery into no-ops, so replaying
   * the topic (or a consumer crash-and-retry) can never double-record.
   */
  async record(event: WorkOrderEvent): Promise<void> {
    const result = await this.repository
      .createQueryBuilder()
      .insert()
      .into(AuditEvent)
      .values({
        eventId: event.eventId,
        eventType: event.type,
        workOrderId: event.data.workOrderId,
        propertyId: event.data.propertyId,
        correlationId: event.correlationId,
        actorId: event.actorId ?? null,
        occurredAt: new Date(event.occurredAt),
        // The jsonb column defeats QueryDeepPartialEntity's mapped type
        // (nullable fields inside the payload are rejected); the cast is the
        // standard TypeORM escape hatch for json inserts.
        payload: { ...event.data },
      } as QueryDeepPartialEntity<AuditEvent>)
      .orIgnore()
      .execute();
 
    if (result.identifiers.length === 0) {
      this.logger.debug(`duplicate event ${event.eventId} ignored`);
    }
  }
}