feat: mejorar la búsqueda de auditoría al agregar soporte para filtros y ordenamiento

This commit is contained in:
David Arranz 2026-08-29 15:52:28 +02:00
parent ce9f9029e3
commit fb7d3f7043

View File

@ -1,5 +1,5 @@
import type { AuditValue } from "@repo/rdx-audit"; 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 { Op, Sequelize } from "sequelize";
import { AuditChangeModel, AuditEventModel } from "./sequelize"; import { AuditChangeModel, AuditEventModel } from "./sequelize";
@ -59,6 +59,7 @@ export interface IAuditReader {
search(query: AuditSearchQuery, access: AuditReadAccess): Promise<AuditSearchResult>; search(query: AuditSearchQuery, access: AuditReadAccess): Promise<AuditSearchResult>;
findById(id: string, access: AuditReadAccess): Promise<AuditDetail | null>; findById(id: string, access: AuditReadAccess): Promise<AuditDetail | null>;
} }
const FIELD_MAP: Record<string, string> = { const FIELD_MAP: Record<string, string> = {
occurredAt: "occurred_at", occurredAt: "occurred_at",
kind: "kind", kind: "kind",
@ -80,31 +81,10 @@ export class SequelizeAuditReader implements IAuditReader {
async search(query: AuditSearchQuery, access: AuditReadAccess): Promise<AuditSearchResult> { async search(query: AuditSearchQuery, access: AuditReadAccess): Promise<AuditSearchResult> {
const primitives = query.criteria.toPrimitives(); const primitives = query.criteria.toPrimitives();
const filters = primitives.filters ?? []; const filters = primitives.filters ?? [];
this.assertSupportedFilters(filters);
for (const filter of filters) { const orderBy = this.resolveOrderBy(primitives.orderBy);
if (!FIELD_MAP[filter.field]) throw new Error(`Unsupported audit filter: ${filter.field}`); const where = this.buildWhere(access, filters, query.quickSearch);
}
const orderBy = primitives.orderBy ?? "occurredAt";
if (!SORTABLE.has(orderBy)) throw new Error(`Unsupported audit order: ${orderBy}`);
const where: Record<PropertyKey, unknown> = { ...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 pageSize = primitives.pageSize ?? 50; const pageSize = primitives.pageSize ?? 50;
const pageNumber = primitives.pageNumber ?? 0; const pageNumber = primitives.pageNumber ?? 0;
@ -136,6 +116,51 @@ export class SequelizeAuditReader implements IAuditReader {
pageSize, 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<PropertyKey, unknown> {
const where: Record<PropertyKey, unknown> = { ...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<AuditDetail | null> { async findById(id: string, access: AuditReadAccess): Promise<AuditDetail | null> {
const row = await AuditEventModel.findOne({ where: { id, ...this.accessWhere(access) } }); const row = await AuditEventModel.findOne({ where: { id, ...this.accessWhere(access) } });
if (!row) { if (!row) {