import type { ServiceLayerClient } from '../service-layer-client.ts'; import { generateCode } from '../lib/codes.ts'; import { hhmmss } from '../lib/timestamps.ts'; import { userTableEntity } from '../lib/service-layer-paths.ts'; const ENTITY = userTableEntity('EVN1'); export interface Evn1Row { code: string; eventId: string; webhookId: string; status: string; retry: number; } function toRow(raw: Record): Evn1Row { return { code: raw.Code as string, eventId: raw.U_EventID as string, webhookId: raw.U_WebhookID as string, status: raw.U_Status as string, retry: (raw.U_Retry as number) ?? 0, }; } export async function findExisting(client: ServiceLayerClient, eventId: string, webhookId: string): Promise { const filter = encodeURIComponent(`U_EventID eq '${eventId}' and U_WebhookID eq '${webhookId}'`); const res = await client.get(`/${ENTITY}?$filter=${filter}`); // Un GET fallido no es "no existe": tratarlo así lleva a un POST duplicado. if (res.status !== 200) { throw new Error(`Service Layer respondió HTTP ${res.status} buscando @EVN1 de ${eventId}: ${JSON.stringify(res.data)}`); } const rows = (res.data?.value as Record[]) ?? []; return rows.length > 0 ? toRow(rows[0]) : null; } export async function create(client: ServiceLayerClient, eventId: string, webhookId: string): Promise { await client.post(`/${ENTITY}`, { Code: generateCode(), Name: `${eventId}/${webhookId}`, U_EventID: eventId, U_WebhookID: webhookId, U_Status: 'F', U_Retry: 0, }); } export async function listPending(client: ServiceLayerClient): Promise { const rows = await client.getAll(`/${ENTITY}?$filter=${encodeURIComponent("U_Status eq 'F'")}`); return rows.map(toRow); } export async function listForEvent(client: ServiceLayerClient, eventId: string): Promise { const rows = await client.getAll(`/${ENTITY}?$filter=${encodeURIComponent(`U_EventID eq '${eventId}'`)}`); return rows.map(toRow); } export async function markDelivered(client: ServiceLayerClient, code: string, returnCode: number): Promise { const now = new Date(); await client.patch(`/${ENTITY}('${code}')`, { U_Status: 'D', U_ReturnCode: returnCode, U_LastSentDT: now.toISOString(), U_LastSentTM: hhmmss(now), }); } export async function markFailedAttempt(client: ServiceLayerClient, row: Evn1Row, returnCode: number): Promise { const now = new Date(); await client.patch(`/${ENTITY}('${row.code}')`, { U_Retry: row.retry + 1, U_ReturnCode: returnCode, U_LastSentDT: now.toISOString(), U_LastSentTM: hhmmss(now), }); } // Pone en 0 el contador de reintentos de las entregas pendientes de una // suscripción. Se usa al reactivarla (State X -> A): sin esto, las filas // siguen con Retry >= RETRY_LIMIT y la suscripción vuelve a X en el // siguiente tick sin intentar enviar nada. export async function resetRetries(client: ServiceLayerClient, webhookId: string): Promise { const filter = encodeURIComponent(`U_WebhookID eq '${webhookId}' and U_Status eq 'F'`); const rows = (await client.getAll(`/${ENTITY}?$filter=${filter}`)).map(toRow).filter((r) => r.retry > 0); for (const row of rows) await client.patch(`/${ENTITY}('${row.code}')`, { U_Retry: 0 }); return rows.length; } // Entregas fallidas/pendientes (Status='F') de una suscripción. export async function listPendingForWebhook(client: ServiceLayerClient, webhookId: string): Promise { const filter = encodeURIComponent(`U_WebhookID eq '${webhookId}' and U_Status eq 'F'`); return (await client.getAll(`/${ENTITY}?$filter=${filter}`)).map(toRow); } export async function deleteRow(client: ServiceLayerClient, code: string): Promise { await client.delete(`/${ENTITY}('${code}')`); }