/** * Paperclip Service — Queries ao PostgreSQL do Paperclip * @author Descomplicar® | @link descomplicar.pt | @copyright 2026 * * NOTA SOBRE ESQUEMA DA BD: * Os nomes de tabelas e colunas abaixo são baseados na spec (SPEC-dashboard-expansion-q2-2026.md §4.3). * Se o esquema real da BD Paperclip diferir, ajustar as queries: * - Tabela de agentes: 'agents' (colunas: name, role, status, last_heartbeat) * - Tabela de routines: 'routines' (colunas: name, cron_expression, enabled, last_run_at, last_run_status) * - Tabela de issues: 'issues' (colunas: status, closed_at) * Verificar schema real com: \dt e \d no psql. */ import pool from './paperclip-db.js' // --------------------------------------------------------------------------- // Types (espelham a spec) // --------------------------------------------------------------------------- export interface PaperclipAgent { id: string name: string role: string status: 'active' | 'idle' | 'error' | 'archived' last_heartbeat: string | null last_run: string | null total_runs: number } export interface PaperclipRoutine { id: string name: string cron: string active: boolean last_run: string | null last_status: 'success' | 'error' | null next_run: string | null } export interface PaperclipDashboard { agents: { total: number active: number idle: number error: number list: PaperclipAgent[] } routines: { total: number active: number list: PaperclipRoutine[] } issues: { open: number in_progress: number closed_7d: number } } // --------------------------------------------------------------------------- // Dados de fallback (quando BD não está configurada ou inacessível) // --------------------------------------------------------------------------- const FALLBACK: PaperclipDashboard = { agents: { total: 0, active: 0, idle: 0, error: 0, list: [], }, routines: { total: 0, active: 0, list: [], }, issues: { open: 0, in_progress: 0, closed_7d: 0, }, } // --------------------------------------------------------------------------- // Função principal — todas as queries em paralelo // --------------------------------------------------------------------------- export async function getPaperclipDashboard(): Promise { // Se pool não foi configurado (credenciais em falta), retornar fallback if (!pool) { return { ...FALLBACK } } try { const [agentsResult, routinesResult, issuesOpenResult, issuesInProgressResult, issuesClosedResult] = await Promise.all([ // Query A: Agentes activos (excluindo archived) // NOTA: colunas last_run e total_runs podem não existir — ajustar se necessário pool.query<{ id: string name: string role: string status: string last_heartbeat: string | null last_run: string | null total_runs: number }>( `SELECT id, name, role, status, last_heartbeat, last_run_at AS last_run, COALESCE(total_runs, 0) AS total_runs FROM agents WHERE status != 'archived' ORDER BY role, name` ), // Query B: Routines ordenadas por nome pool.query<{ id: string name: string cron: string active: boolean last_run: string | null last_status: string | null next_run: string | null }>( `SELECT id, name, cron_expression AS cron, enabled AS active, last_run_at AS last_run, last_run_status AS last_status, next_run_at AS next_run FROM routines ORDER BY name` ), // Query C1: Issues abertas pool.query<{ count: string }>( `SELECT COUNT(*) AS count FROM issues WHERE status = 'open'` ), // Query C2: Issues em progresso pool.query<{ count: string }>( `SELECT COUNT(*) AS count FROM issues WHERE status = 'in_progress'` ), // Query C3: Issues fechadas nos últimos 7 dias pool.query<{ count: string }>( `SELECT COUNT(*) AS count FROM issues WHERE status = 'closed' AND closed_at > NOW() - INTERVAL '7 days'` ), ]) const agents = agentsResult.rows const routines = routinesResult.rows // Contagens de agentes por estado const activeCount = agents.filter(a => a.status === 'active').length const idleCount = agents.filter(a => a.status === 'idle').length const errorCount = agents.filter(a => a.status === 'error').length // Contagens de routines activas const activeRoutines = routines.filter(r => r.active).length return { agents: { total: agents.length, active: activeCount, idle: idleCount, error: errorCount, list: agents.map(a => ({ id: a.id ?? '', name: a.name, role: a.role, status: a.status as PaperclipAgent['status'], last_heartbeat: a.last_heartbeat ?? null, last_run: a.last_run ?? null, total_runs: Number(a.total_runs) || 0, })), }, routines: { total: routines.length, active: activeRoutines, list: routines.map(r => ({ id: r.id ?? '', name: r.name, cron: r.cron, active: Boolean(r.active), last_run: r.last_run ?? null, last_status: (r.last_status as PaperclipRoutine['last_status']) ?? null, next_run: r.next_run ?? null, })), }, issues: { open: parseInt(issuesOpenResult.rows[0]?.count ?? '0', 10), in_progress: parseInt(issuesInProgressResult.rows[0]?.count ?? '0', 10), closed_7d: parseInt(issuesClosedResult.rows[0]?.count ?? '0', 10), }, } } catch (err) { console.error('[paperclip] Erro ao obter dados da BD Paperclip:', (err as Error).message) // Retornar fallback em caso de falha de BD (sem rebentar a API) return { ...FALLBACK } } }