From fb7d3f704322233f187ce867fa58af65e1d60b3f Mon Sep 17 00:00:00 2001 From: david Date: Sat, 29 Aug 2026 15:52:28 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20mejorar=20la=20b=C3=BAsqueda=20de=20aud?= =?UTF-8?q?itor=C3=ADa=20al=20agregar=20soporte=20para=20filtros=20y=20ord?= =?UTF-8?q?enamiento?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/infrastructure/audit/audit-reader.ts | 75 ++++++++++++------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/apps/server/src/infrastructure/audit/audit-reader.ts b/apps/server/src/infrastructure/audit/audit-reader.ts index 5564a84c..a27aa15f 100644 --- a/apps/server/src/infrastructure/audit/audit-reader.ts +++ b/apps/server/src/infrastructure/audit/audit-reader.ts @@ -1,5 +1,5 @@ import type { AuditValue } from "@repo/rdx-audit"; -import type { Criteria } from "@repo/rdx-criteria/server"; +import type { Criteria, FiltersPrimitives } from "@repo/rdx-criteria/server"; import { Op, Sequelize } from "sequelize"; import { AuditChangeModel, AuditEventModel } from "./sequelize"; @@ -59,6 +59,7 @@ export interface IAuditReader { search(query: AuditSearchQuery, access: AuditReadAccess): Promise; findById(id: string, access: AuditReadAccess): Promise; } + const FIELD_MAP: Record = { occurredAt: "occurred_at", kind: "kind", @@ -80,31 +81,10 @@ export class SequelizeAuditReader implements IAuditReader { async search(query: AuditSearchQuery, access: AuditReadAccess): Promise { const primitives = query.criteria.toPrimitives(); const filters = primitives.filters ?? []; + this.assertSupportedFilters(filters); - for (const filter of filters) { - if (!FIELD_MAP[filter.field]) throw new Error(`Unsupported audit filter: ${filter.field}`); - } - - const orderBy = primitives.orderBy ?? "occurredAt"; - if (!SORTABLE.has(orderBy)) throw new Error(`Unsupported audit order: ${orderBy}`); - - const where: Record = { ...this.accessWhere(access) }; - - for (const filter of filters) { - const column = FIELD_MAP[filter.field]; - if (filter.operator === ">=" || filter.operator === "<") { - where[column] = { - ...(typeof where[column] === "object" && where[column] !== null ? where[column] : {}), - [filter.operator === ">=" ? Op.gte : Op.lt]: filter.value, - }; - } else { - where[column] = filter.value; - } - } - - if (query.quickSearch) { - where[Op.and] = [{ [Op.or]: this.quickSearch(query.quickSearch) }]; - } + const orderBy = this.resolveOrderBy(primitives.orderBy); + const where = this.buildWhere(access, filters, query.quickSearch); const pageSize = primitives.pageSize ?? 50; const pageNumber = primitives.pageNumber ?? 0; @@ -136,6 +116,51 @@ export class SequelizeAuditReader implements IAuditReader { pageSize, }; } + + private assertSupportedFilters(filters: readonly FiltersPrimitives[]): void { + for (const filter of filters) { + if (!FIELD_MAP[filter.field]) { + throw new Error(`Unsupported audit filter: ${filter.field}`); + } + } + } + + private resolveOrderBy(orderBy?: string): string { + const resolvedOrderBy = orderBy ?? "occurredAt"; + if (!SORTABLE.has(resolvedOrderBy)) { + throw new Error(`Unsupported audit order: ${resolvedOrderBy}`); + } + + return resolvedOrderBy; + } + + private buildWhere( + access: AuditReadAccess, + filters: readonly FiltersPrimitives[], + quickSearch: string | null + ): Record { + const where: Record = { ...this.accessWhere(access) }; + + for (const filter of filters) { + const column = FIELD_MAP[filter.field]; + if (filter.operator === ">=" || filter.operator === "<") { + where[column] = { + ...(typeof where[column] === "object" && where[column] !== null ? where[column] : {}), + [filter.operator === ">=" ? Op.gte : Op.lt]: filter.value, + }; + continue; + } + + where[column] = filter.value; + } + + if (quickSearch) { + where[Op.and] = [{ [Op.or]: this.quickSearch(quickSearch) }]; + } + + return where; + } + async findById(id: string, access: AuditReadAccess): Promise { const row = await AuditEventModel.findOne({ where: { id, ...this.accessWhere(access) } }); if (!row) {