import type { Request, Response, NextFunction } from 'express'; import { z } from 'zod'; import * as connectorService from '../services/connector.service.js'; import { AppError } from '../middlewares/error.middleware.js'; const heartbeatSchema = z.object({ version: z.string(), uptimeSeconds: z.number().optional().default(0), postgresPingMs: z.number().optional().default(0), pgVersion: z.string().optional(), lastMigration: z.string().optional(), status: z.string().optional(), errorMsg: z.string().optional(), }); // Called by the connector Docker container, NOT by browser users export async function heartbeat(req: Request, res: Response, next: NextFunction) { try { const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { return res.status(401).json({ message: 'Token requerido' }); } const token = authHeader.split(' ')[1]; const tenantId = await connectorService.verifyConnectorToken(token); if (!tenantId) { return res.status(401).json({ message: 'Token inválido' }); } const data = heartbeatSchema.parse(req.body); await connectorService.recordHeartbeat(tenantId, data); return res.json({ ok: true }); } catch (err: any) { if (err instanceof z.ZodError) return next(new AppError(400, err.errors[0].message)); return next(err); } } // Called by authenticated tenant owner to provision or check connector export async function provision(req: Request, res: Response, next: NextFunction) { try { const tenantId = req.viewingTenantId || req.user!.tenantId; const result = await connectorService.provisionConnector(tenantId); return res.status(201).json(result); } catch (err: any) { if (err.message?.includes('no encontrado')) return next(new AppError(404, err.message)); return next(err); } } export async function status(req: Request, res: Response, next: NextFunction) { try { const tenantId = req.viewingTenantId || req.user!.tenantId; const result = await connectorService.getConnectorStatus(tenantId); return res.json(result); } catch (err) { return next(err); } }