feat(sat): add API endpoints for FIEL and SAT sync (Phase 7)
- Add FIEL controller with upload, status, and delete endpoints - Add SAT controller with sync start, status, history, and retry - Add admin endpoints for cron job info and manual execution - Register new routes in app.ts - All endpoints protected with authentication middleware Endpoints added: - POST /api/fiel/upload - GET /api/fiel/status - DELETE /api/fiel - POST /api/sat/sync - GET /api/sat/sync/status - GET /api/sat/sync/history - GET /api/sat/sync/:id - POST /api/sat/sync/:id/retry - GET /api/sat/cron - POST /api/sat/cron/run Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
82
apps/api/src/controllers/fiel.controller.ts
Normal file
82
apps/api/src/controllers/fiel.controller.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import { uploadFiel, getFielStatus, deleteFiel } from '../services/fiel.service.js';
|
||||
import type { FielUploadRequest } from '@horux/shared';
|
||||
|
||||
/**
|
||||
* Sube y configura las credenciales FIEL
|
||||
*/
|
||||
export async function upload(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Tenant ID requerido' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { cerFile, keyFile, password } = req.body as FielUploadRequest;
|
||||
|
||||
if (!cerFile || !keyFile || !password) {
|
||||
res.status(400).json({ error: 'cerFile, keyFile y password son requeridos' });
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await uploadFiel(tenantId, cerFile, keyFile, password);
|
||||
|
||||
if (!result.success) {
|
||||
res.status(400).json({ error: result.message });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({
|
||||
message: result.message,
|
||||
status: result.status,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('[FIEL Controller] Error en upload:', error);
|
||||
res.status(500).json({ error: 'Error interno del servidor' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el estado de la FIEL configurada
|
||||
*/
|
||||
export async function status(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Tenant ID requerido' });
|
||||
return;
|
||||
}
|
||||
|
||||
const fielStatus = await getFielStatus(tenantId);
|
||||
res.json(fielStatus);
|
||||
} catch (error: any) {
|
||||
console.error('[FIEL Controller] Error en status:', error);
|
||||
res.status(500).json({ error: 'Error interno del servidor' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina las credenciales FIEL
|
||||
*/
|
||||
export async function remove(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Tenant ID requerido' });
|
||||
return;
|
||||
}
|
||||
|
||||
const deleted = await deleteFiel(tenantId);
|
||||
|
||||
if (!deleted) {
|
||||
res.status(404).json({ error: 'No hay FIEL configurada' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({ message: 'FIEL eliminada correctamente' });
|
||||
} catch (error: any) {
|
||||
console.error('[FIEL Controller] Error en remove:', error);
|
||||
res.status(500).json({ error: 'Error interno del servidor' });
|
||||
}
|
||||
}
|
||||
177
apps/api/src/controllers/sat.controller.ts
Normal file
177
apps/api/src/controllers/sat.controller.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import {
|
||||
startSync,
|
||||
getSyncStatus,
|
||||
getSyncHistory,
|
||||
retryJob,
|
||||
} from '../services/sat/sat.service.js';
|
||||
import { getJobInfo, runSatSyncJobManually } from '../jobs/sat-sync.job.js';
|
||||
import type { StartSyncRequest } from '@horux/shared';
|
||||
|
||||
/**
|
||||
* Inicia una sincronización manual
|
||||
*/
|
||||
export async function start(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Tenant ID requerido' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { type, dateFrom, dateTo } = req.body as StartSyncRequest;
|
||||
|
||||
const jobId = await startSync(
|
||||
tenantId,
|
||||
type || 'daily',
|
||||
dateFrom ? new Date(dateFrom) : undefined,
|
||||
dateTo ? new Date(dateTo) : undefined
|
||||
);
|
||||
|
||||
res.json({
|
||||
jobId,
|
||||
message: 'Sincronización iniciada',
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('[SAT Controller] Error en start:', error);
|
||||
|
||||
if (error.message.includes('FIEL') || error.message.includes('sincronización en curso')) {
|
||||
res.status(400).json({ error: error.message });
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(500).json({ error: 'Error interno del servidor' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el estado actual de sincronización
|
||||
*/
|
||||
export async function status(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Tenant ID requerido' });
|
||||
return;
|
||||
}
|
||||
|
||||
const syncStatus = await getSyncStatus(tenantId);
|
||||
res.json(syncStatus);
|
||||
} catch (error: any) {
|
||||
console.error('[SAT Controller] Error en status:', error);
|
||||
res.status(500).json({ error: 'Error interno del servidor' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el historial de sincronizaciones
|
||||
*/
|
||||
export async function history(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Tenant ID requerido' });
|
||||
return;
|
||||
}
|
||||
|
||||
const page = parseInt(req.query.page as string) || 1;
|
||||
const limit = parseInt(req.query.limit as string) || 10;
|
||||
|
||||
const result = await getSyncHistory(tenantId, page, limit);
|
||||
res.json({
|
||||
...result,
|
||||
page,
|
||||
limit,
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('[SAT Controller] Error en history:', error);
|
||||
res.status(500).json({ error: 'Error interno del servidor' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene detalle de un job específico
|
||||
*/
|
||||
export async function jobDetail(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Tenant ID requerido' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const { jobs } = await getSyncHistory(tenantId, 1, 100);
|
||||
const job = jobs.find(j => j.id === id);
|
||||
|
||||
if (!job) {
|
||||
res.status(404).json({ error: 'Job no encontrado' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json(job);
|
||||
} catch (error: any) {
|
||||
console.error('[SAT Controller] Error en jobDetail:', error);
|
||||
res.status(500).json({ error: 'Error interno del servidor' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reintenta un job fallido
|
||||
*/
|
||||
export async function retry(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
res.status(400).json({ error: 'Tenant ID requerido' });
|
||||
return;
|
||||
}
|
||||
|
||||
const id = req.params.id as string;
|
||||
const newJobId = await retryJob(id);
|
||||
|
||||
res.json({
|
||||
jobId: newJobId,
|
||||
message: 'Job reintentado',
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('[SAT Controller] Error en retry:', error);
|
||||
|
||||
if (error.message.includes('no encontrado') || error.message.includes('Solo se pueden')) {
|
||||
res.status(400).json({ error: error.message });
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(500).json({ error: 'Error interno del servidor' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene información del job programado (solo admin)
|
||||
*/
|
||||
export async function cronInfo(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const info = getJobInfo();
|
||||
res.json(info);
|
||||
} catch (error: any) {
|
||||
console.error('[SAT Controller] Error en cronInfo:', error);
|
||||
res.status(500).json({ error: 'Error interno del servidor' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ejecuta el job de sincronización manualmente (solo admin)
|
||||
*/
|
||||
export async function runCron(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
// Ejecutar en background
|
||||
runSatSyncJobManually().catch(err =>
|
||||
console.error('[SAT Controller] Error ejecutando cron manual:', err)
|
||||
);
|
||||
|
||||
res.json({ message: 'Job de sincronización iniciado' });
|
||||
} catch (error: any) {
|
||||
console.error('[SAT Controller] Error en runCron:', error);
|
||||
res.status(500).json({ error: 'Error interno del servidor' });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user