Logs de la api como middleware

This commit is contained in:
2026-03-18 17:14:54 +01:00
parent e9c0549b7c
commit a5e1761acf
6 changed files with 211 additions and 18 deletions

View File

@@ -0,0 +1,14 @@
/**
* Loguear las operaciones de entrada-salida de la api para trazabilidad
* de errores cliente-servidor
*/
CREATE TABLE log_operations (
id BIGINT GENERATED ALWAYS AS IDENTITY,
date TIMESTAMPTZ default now(),
origin TEXT NOT NULL,
endpoint TEXT,
data JSONB
)

View File

@@ -0,0 +1,49 @@
import { httpclient } from "config/httpclient.config";
import { pgClient } from "config/pgclient.config";
import type { ServerContext } from "domain/ServerContext";
import { after, before, describe, test } from "node:test";
import { LogRepository, type LogCreateDTO } from "./Log.repository";
import assert from "node:assert";
describe("LogRepository Integration Tests", async () => {
const startDate = new Date();
const serverContext: ServerContext = {
PostgresClient: pgClient,
HttpClient: httpclient
};
const repo = new LogRepository(serverContext);
// Clean up before and after tests to ensure isolation
const cleanup = async () => {
await pgClient.query("DELETE FROM log_operations WHERE date >= $1 AND date <= $2",
[startDate.toISOString(), new Date().toISOString()]);
};
before(async () => {
await cleanup();
});
after(async () => {
await cleanup();
});
test("Log must log", async () => {
const newLog: LogCreateDTO = {
data: {
card_id: "019cdd39-fc08-7417-b16d-a78794a24c01",
override: false
},
endpoint: "/nfc/generate",
origin: "serverSide"
}
const res = await repo.createLog(newLog)
assert.ok(res)
})
})

View File

@@ -0,0 +1,95 @@
import { tryCatch, type Result } from "domain/Result";
import type { ServerContext } from "domain/ServerContext";
export type Log = {
id: string,
date: Date,
origin?: string,
endpoint: string,
data: Record<string, any>,
}
export type LogCreateDTO = Pick<Log, "origin" | "endpoint" | "data">
export class LogRepository {
private ctx: ServerContext;
constructor(serverContext: ServerContext) {
this.ctx = serverContext
}
public async readBetween(start: Date, end: Date): Promise<Result<string, Log[]>> {
const query = `
SELECT id, date, origin, endpint, data
FROM log_operations
WHERE date >= $1 AND date <= $2
ORDER BY date DESC
`;
const values = [
start.toISOString(), // ISOString asegura compatibilidad con TIMESTAMPTZ
end.toISOString(),
];
const prom = this.ctx.PostgresClient.query<Log>(query, values)
const res = await tryCatch(prom)
if (res.error != undefined) {
return {
error: res.error.msg.message
}
}
return {
data: res.data.rows
}
}
public async createLog(newLog: LogCreateDTO): Promise<Result<string, Log>> {
const conn = await this.ctx.PostgresClient.connect()
await conn.query("BEGIN")
const { origin, endpoint, data } = newLog
const query = `
INSERT INTO log_operations (
origin,
endpoint,
data
) VALUES (
$1,
$2,
$3
) RETURNING
id, date, origin, endpoint, data
`
const values = [origin, endpoint, JSON.stringify(data)]
const promiseRes = conn.query<Log>(query, values)
const res = await tryCatch(promiseRes)
if (res.error != undefined) {
console.error("[x] Error logueando", res.error.msg)
await conn.query("ROLLBACK")
conn.release();
return {
error: String(res.error.msg)
}
}
const createdLogs = res.data.rows
if (createdLogs.length == 0 || createdLogs[0] == undefined) {
await conn.query("ROLLBACK")
conn.release();
return {
error: "Error creating the Log, insert response was empty"
}
}
await conn.query("COMMIT")
conn.release()
return {
data: createdLogs[0]
}
}
}

View File

@@ -73,7 +73,6 @@ await describe("NfcRepository Integration Tests", async () => {
assert.fail(`findActivationCodes failed: ${result.error}`);
}
assert.ok(result.data, "Data should be returned");
assert.strictEqual(result.data.length, 0);
});
@@ -114,7 +113,6 @@ await describe("NfcRepository Integration Tests", async () => {
}
assert.ok(result.data, "Data should be returned");
assert.ok(Array.isArray(result.data));
const first = result.data[0];

View File

@@ -1,7 +1,6 @@
import type { ActivationCodeDTO } from "domain/NfcDTOs.js";
import type { Result } from "domain/Result.js";
import type { ServerContext } from "domain/ServerContext.js";
import { constrainedMemory } from "node:process";
// TODO: Pasar a Result<E,T>

View File

@@ -11,27 +11,13 @@ import { pgClient } from 'config/pgclient.config.js';
import { NfcController } from 'aplication/Nfc.controller.js';
import { NfcUsecases } from 'aplication/Nfc.usecases.js';
import { NfcRepository } from 'infrastructure/Nfc.repository.js';
import { LogRepository, type LogCreateDTO } from 'infrastructure/Log.repository.js';
const PORT = env.PORT
const HOSTNAME = env.HOST
assert(HOSTNAME != undefined)
// test
const requestLogger = (req: Request, res: Response, next: NextFunction) => {
const timestamp = new Date().toISOString();
const method = req.method;
const url = req.url;
const body = req.body;
// 'origin' comes from the request headers
const origin = req.get('origin') || 'No Origin (likely direct request/Server-side)';
console.log(`[${timestamp}] ${method} ${url} - Origin: ${origin} - Body: ${JSON.stringify(body)}`);
// Crucial: Call next() so the request doesn't hang!
next();
};
// Instancias de las dependencias
@@ -41,6 +27,7 @@ const serverContext: ServerContext = {
}
const nfcRepository = new NfcRepository(serverContext)
const logRepository = new LogRepository(serverContext)
const nfcUsecases = new NfcUsecases({
serverContext,
@@ -52,6 +39,56 @@ const ncfControllers = new NfcController({
nfcUsecases
})
// middleware
const requestLogger = (req: Request, res: Response, next: NextFunction) => {
const timestamp = new Date().toISOString();
const method = req.method;
const url = req.url;
const body = req.body;
// 'origin' comes from the request headers
const origin = req.get('origin') || 'No Origin (likely direct request/Server-side)';
const log: LogCreateDTO = {
data: req.body,
endpoint: method + " " + req.url,
origin: origin
}
logRepository.createLog(log).then().catch(e => console.error("Error logueando ", log, e))
console.log(`[${timestamp}] ${method} ${url} - Origin: ${origin} - Body: ${JSON.stringify(body)}`);
// Crucial: Call next() so the request doesn't hang!
next();
};
const responseLogger = (req: Request, res: Response, next: NextFunction) => {
const timestamp = new Date().toISOString();
const start = Date.now();
const oldSend = res.send;
res.send = function (data: any): Response {
// @ts-ignore: Si, es poco ortodoxo
res.sendBody = data; // Store the result data
return oldSend.apply(res, [data]);
};
// Listen for the 'finish' event to log after the response is sent
res.on('finish', () => {
const duration = Date.now() - start;
const log: LogCreateDTO = {
// @ts-ignore: Por lo de antes
data: res.sendBody,
endpoint: req.method + " " + req.originalUrl,
origin: "SERVER"
}
logRepository.createLog(log)
console.log(`[${timestamp} ${req.method} ${req.originalUrl} ${res.statusCode} - ${duration}ms`);
});
next();
}
// Rutas
const router = Router();
@@ -76,6 +113,7 @@ app.use(cors({
}))
app.use(requestLogger)
app.use(responseLogger)
// Routes
app.use('/', router);