import { Injectable } from '@nestjs/common'; import { BaseRepository } from '@edr/api-common'; import { InjectRepository } from '@nestjs/typeorm'; import { Between, FindOptionsWhere, LessThanOrEqual, MoreThanOrEqual, Repository } from 'typeorm'; import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; import { AuditLog } from './entities/audit-log.entity'; export interface AuditLogQuery { type?: string; userId?: string; method?: string; isSuccess?: boolean; resourceId?: string; from?: Date; to?: Date; skip: number; take: number; } @Injectable() export class AuditLogRepository extends BaseRepository { constructor( @InjectRepository(AuditLog) private readonly auditLogRepository: Repository, ) { super(auditLogRepository); } /** * Insert one audit row. * * `insert` rather than `save`: save would issue a SELECT first to decide * between insert and update, which is wasted work for a table that is only * ever appended to. */ async record(entry: Partial): Promise { await this.auditLogRepository.insert( entry as QueryDeepPartialEntity, ); } /** * Paginated, filtered read. Newest first — every index on this table is * ordered `created_at DESC` to match. */ async search(query: AuditLogQuery): Promise<[AuditLog[], number]> { const where: FindOptionsWhere = {}; if (query.type) where.type = query.type; if (query.userId) where.userId = query.userId; if (query.method) where.method = query.method; if (query.resourceId) where.resourceId = query.resourceId; if (query.isSuccess !== undefined) where.isSuccess = query.isSuccess; // Date range: either bound may be supplied alone. if (query.from && query.to) where.createdAt = Between(query.from, query.to); else if (query.from) where.createdAt = MoreThanOrEqual(query.from); else if (query.to) where.createdAt = LessThanOrEqual(query.to); return this.auditLogRepository.findAndCount({ where, order: { createdAt: 'DESC' }, skip: query.skip, take: query.take, }); } /** Distinct entity types present, for populating a filter dropdown. */ async distinctTypes(): Promise { const rows = await this.auditLogRepository .createQueryBuilder('audit_log') .select('DISTINCT audit_log.type', 'type') .orderBy('audit_log.type', 'ASC') .getRawMany<{ type: string }>(); return rows.map((row) => row.type); } }