Files
sf-monitorizacion-health/src/application/monitor-usecases.ts
2026-04-29 17:22:07 +02:00

58 lines
2.4 KiB
TypeScript

import axios from 'axios';
import { type Pool } from 'pg';
import { type Proyecto, RepositoryError } from '../domain/common.js';
export class MonitorUsecases {
constructor(private db: Pool) {}
async checkAllProjectsHealth(): Promise<void> {
try {
const { rows: projects } = await this.db.query<Proyecto>('SELECT * FROM proyectos');
const checks = projects.map(async (p) => {
let status: Proyecto['estado_codigo'] = 'OK';
try {
// Solo 200 es válido: cualquier otro código (301, 403, 5xx) indica un problema real del servicio.
await axios.get(p.url_health, { timeout: 5000, validateStatus: (s) => s === 200 });
} catch (e) {
const axiosError = e as { code?: string };
status = axiosError.code === 'ECONNABORTED' ? 'TIMEOUT' : 'ERROR';
}
return this.db.query(
'INSERT INTO estados (proyecto_id, estado_codigo, fecha) VALUES ($1, $2, NOW())',
[p.id, status]
);
});
// allSettled en lugar de all: un fallo al insertar en BD no debe cancelar el resto de checks.
await Promise.allSettled(checks);
} catch (err) {
throw new RepositoryError('Fallo masivo en el chequeo de salud', { cause: err instanceof Error ? err.message : String(err) });
}
}
async getDashboardData(): Promise<Proyecto[]> {
// LEFT JOIN LATERAL nos deja calcular "la última fila de estados" para cada proyecto.
// Usamos LEFT JOIN para no perder proyectos que todavía no tienen estados registrados.
const query = `
SELECT p.id, p.nombre, p.url_health, e.estado_codigo, e.fecha
FROM proyectos p
LEFT JOIN LATERAL (
SELECT estado_codigo, fecha
FROM estados
WHERE proyecto_id = p.id
ORDER BY fecha DESC
LIMIT 1
) e ON true
ORDER BY p.nombre ASC;
`;
try {
const { rows } = await this.db.query<Proyecto>(query);
return rows;
} catch (err) {
throw new RepositoryError('Error al recuperar datos del dashboard', { cause: err instanceof Error ? err.message : String(err) });
}
}
}