// CLI de administración de suscripciones (@OEVC/@EVC1) contra el Service // Layer configurado en .env. Requiere que las 4 UserTables ya existan // (`npm run provision`). // // Uso: // npx tsx scripts/manage-subscriptions.ts subscription:add [--hmac-key ] // npx tsx scripts/manage-subscriptions.ts subscription:list // npx tsx scripts/manage-subscriptions.ts subscription:update-url // npx tsx scripts/manage-subscriptions.ts subscription:pause // npx tsx scripts/manage-subscriptions.ts subscription:resume // npx tsx scripts/manage-subscriptions.ts subscription:delete // // npx tsx scripts/manage-subscriptions.ts filter:add // npx tsx scripts/manage-subscriptions.ts filter:remove // npx tsx scripts/manage-subscriptions.ts filter:list import { config } from '../src/config.ts'; import { ServiceLayerClient } from '../src/service-layer-client.ts'; import * as evn1 from '../src/dispatcher/evn1-repository.ts'; import * as oevn from '../src/dispatcher/oevn-repository.ts'; import { generateCode } from '../src/lib/codes.ts'; import { userTableEntity } from '../src/lib/service-layer-paths.ts'; const client = new ServiceLayerClient(config.serviceLayer); const OEVC = userTableEntity('OEVC'); const EVC1 = userTableEntity('EVC1'); function fail(message: string): never { console.error(`Error: ${message}`); process.exit(1); } function flag(args: string[], name: string): string | undefined { const index = args.indexOf(`--${name}`); return index === -1 ? undefined : args[index + 1]; } async function main() { const [command, ...args] = process.argv.slice(2); switch (command) { case 'subscription:add': { const [webhookId, webhookUrl] = args; if (!webhookId || !webhookUrl) fail('subscription:add [--hmac-key ]'); const hmacKey = flag(args, 'hmac-key'); await client.post(`/${OEVC}`, { Code: webhookId, Name: webhookId, U_WebhookURL: webhookUrl, U_AuthType: hmacKey ? 'H' : 'N', U_AuthCred: hmacKey ? JSON.stringify({ Key: hmacKey, Algorithm: 'hmacsha256' }) : null, U_State: 'A', U_WorkMode: 'S', }); console.log(`Suscripción ${webhookId} creada (${webhookUrl}).`); break; } case 'subscription:list': { const rows = await client.getAll(`/${OEVC}`); console.table( rows.map((row) => ({ webhookId: row.Code, url: row.U_WebhookURL, authType: row.U_AuthType, state: row.U_State, })), ); break; } case 'subscription:update-url': { const [webhookId, webhookUrl] = args; if (!webhookId || !webhookUrl) fail('subscription:update-url '); await client.patch(`/${OEVC}('${webhookId}')`, { U_WebhookURL: webhookUrl }); console.log(`Suscripción ${webhookId} actualizada -> ${webhookUrl}.`); break; } case 'subscription:pause': { const [webhookId] = args; if (!webhookId) fail('subscription:pause '); await client.patch(`/${OEVC}('${webhookId}')`, { U_State: 'I' }); console.log(`Suscripción ${webhookId} pausada (State='I').`); break; } case 'subscription:resume': { const [webhookId] = args; if (!webhookId) fail('subscription:resume '); await client.patch(`/${OEVC}('${webhookId}')`, { U_State: 'A' }); const reset = await evn1.resetRetries(client, webhookId); console.log(`Suscripción ${webhookId} reactivada (State='A'); ${reset} entrega(s) con U_Retry reiniciado a 0.`); break; } case 'subscription:delete': { const [webhookId] = args; if (!webhookId) fail('subscription:delete '); // 1) Entregas fallidas/pendientes (Status='F') de esta suscripción. Las ya // entregadas (Status='D') se conservan como historial. const failed = await evn1.listPendingForWebhook(client, webhookId); for (const row of failed) await evn1.deleteRow(client, row.code); // 2) Eventos de @OEVN que quedaron sin ninguna entrega asociada (solo // existían para esta suscripción) se borran; los demás se recalculan. let eventsDeleted = 0; for (const eventId of new Set(failed.map((r) => r.eventId))) { const remaining = await evn1.listForEvent(client, eventId); if (remaining.length === 0) { await oevn.deleteEvent(client, eventId); eventsDeleted++; } else { await oevn.setEventStatus(client, eventId, remaining.every((r) => r.status === 'D') ? 'D' : 'P'); } } // 3) Filtros de @EVC1. const filters = await client.getAll(`/${EVC1}?$filter=${encodeURIComponent(`U_WebhookID eq '${webhookId}'`)}`); for (const f of filters) await client.delete(`/${EVC1}('${f.Code}')`); // 4) La suscripción en sí. await client.delete(`/${OEVC}('${webhookId}')`); console.log( `Suscripción ${webhookId} eliminada: ${filters.length} filtro(s), ${failed.length} entrega(s) fallida(s), ` + `${eventsDeleted} evento(s) huérfano(s) de @OEVN.`, ); break; } case 'filter:add': { const [webhookId, bizObject, transType, objectType] = args; if (!webhookId || !bizObject || !transType || !objectType) { fail('filter:add '); } await client.post(`/${EVC1}`, { Code: generateCode(), Name: `${webhookId}/${bizObject}/${transType}`, U_WebhookID: webhookId, U_BizObject: bizObject, U_TransType: transType, U_ObjectType: objectType, }); console.log(`Filtro agregado: ${webhookId} -> ${bizObject}/${transType} (ObjectType ${objectType}).`); break; } case 'filter:remove': { const [webhookId, bizObject, transType] = args; if (!webhookId || !bizObject || !transType) { fail('filter:remove '); } const filter = encodeURIComponent( `U_WebhookID eq '${webhookId}' and U_BizObject eq '${bizObject}' and U_TransType eq '${transType}'`, ); const res = await client.get(`/${EVC1}?$filter=${filter}`); const rows = (res.data?.value as { Code: string }[]) ?? []; if (rows.length === 0) fail(`no existe el filtro ${webhookId} -> ${bizObject}/${transType}`); await client.delete(`/${EVC1}('${rows[0].Code}')`); console.log(`Filtro eliminado: ${webhookId} -> ${bizObject}/${transType}.`); break; } case 'filter:list': { const rows = await client.getAll(`/${EVC1}`); console.table( rows.map((row) => ({ webhookId: row.U_WebhookID, bizObject: row.U_BizObject, transType: row.U_TransType, objectType: row.U_ObjectType, })), ); break; } default: fail( `comando desconocido: ${command ?? '(ninguno)'}\nComandos disponibles: subscription:add, subscription:list, ` + 'subscription:update-url, subscription:pause, subscription:resume, subscription:delete, filter:add, filter:remove, filter:list', ); } } main().catch((err) => { console.error(err); process.exitCode = 1; });