import { Router, type NextFunction, type Request, type Response } from 'express'; import { getProvider } from '../config/providers.js'; import { verifySignature } from '../lib/signatures.js'; import { enqueue } from '../lib/queue.js'; import { normalize, NormalizationError } from '../services/normalize.service.js'; import { markDone, markFailed, markProcessing, recordDelivery, } from '../db/ingest_events.dao.js'; import { createNotes } from '../db/notes.dao.js'; import { isProviderSlug, type NormalizedDelivery } from '../types/integration.js'; const router = Router(); const parseJson = (raw: Buffer): unknown => { if (raw.length === 0) return {}; return JSON.parse(raw.toString('utf8')); }; router.post('/:provider', async (req: Request, res: Response, next: NextFunction) => { try { const slug = req.params.provider; if (!isProviderSlug(slug)) { res.status(404).json({ error: `Unknown provider "${slug}"` }); return; } const config = getProvider(slug); const rawBody = Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0); let payload: unknown; try { payload = parseJson(rawBody); } catch { res.status(400).json({ error: 'Body is not valid JSON' }); return; } const challenge = config.challenge?.(payload); if (challenge) { res.status(challenge.status).json(challenge.body); return; } if (config.signatureScheme !== 'none') { const secret = config.secretEnvVar ? process.env[config.secretEnvVar] : undefined; if (!secret) { console.error(`No signing secret configured for provider "${slug}"`); res.status(500).json({ error: 'Provider is not configured' }); return; } const result = verifySignature({ provider: slug, rawBody, headers: req.headers, secret, }); if (!result.ok) { res.status(401).json({ error: 'Invalid signature' }); return; } } if (!config.normalize) { res.status(501).json({ error: `No ingestion mapping for provider "${slug}"` }); return; } // Normalizing before the ack costs nothing (it is pure) and lets a malformed // payload fail loudly here rather than silently in a background job. let delivery: NormalizedDelivery; try { delivery = normalize(slug, payload); } catch (err) { if (!(err instanceof NormalizationError)) throw err; res.status(400).json({ error: err.message }); return; } const eventId = await recordDelivery({ provider: slug, externalId: delivery.externalId, }); if (eventId === null) { res.status(200).json({ duplicate: true }); return; } const { notes } = delivery; enqueue(`${slug}:${delivery.externalId}`, async () => { await markProcessing(eventId); try { await createNotes(notes); await markDone(eventId); } catch (err) { await markFailed(eventId, err instanceof Error ? err.message : String(err)); throw err; } }); res.status(200).json({ accepted: true, notes: notes.length }); } catch (err) { next(err); } }); export default router;