Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM, PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source files covering clients, berths, interests/pipeline, documents/EOI, expenses/invoices, email, notifications, dashboard, admin, and client portal. CI/CD via Gitea Actions with Docker builds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
74
src/lib/services/webhook-dispatch.ts
Normal file
74
src/lib/services/webhook-dispatch.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { and, eq, sql } from 'drizzle-orm';
|
||||
|
||||
import { db } from '@/lib/db';
|
||||
import { webhooks, webhookDeliveries } from '@/lib/db/schema/system';
|
||||
import { getQueue } from '@/lib/queue';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { INTERNAL_TO_WEBHOOK_MAP } from '@/lib/services/webhook-event-map';
|
||||
|
||||
/**
|
||||
* Translates an internal socket event to the outbound webhook event name,
|
||||
* queries all active webhooks for the given port that are subscribed to that
|
||||
* event, and enqueues a BullMQ delivery job for each one.
|
||||
*
|
||||
* This function is fire-and-forget — callers should use `void dispatchWebhookEvent(...)`.
|
||||
*/
|
||||
export async function dispatchWebhookEvent(
|
||||
portId: string,
|
||||
internalEvent: string,
|
||||
payload: Record<string, unknown>,
|
||||
): Promise<void> {
|
||||
const webhookEvent = INTERNAL_TO_WEBHOOK_MAP[internalEvent];
|
||||
if (!webhookEvent) {
|
||||
// No mapping for this event — skip silently
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Query active webhooks for this port that subscribe to this event
|
||||
const matchingWebhooks = await db
|
||||
.select({ id: webhooks.id })
|
||||
.from(webhooks)
|
||||
.where(
|
||||
and(
|
||||
eq(webhooks.portId, portId),
|
||||
eq(webhooks.isActive, true),
|
||||
// Check if events array contains the webhook event
|
||||
sql`${webhooks.events} @> ARRAY[${webhookEvent}]::text[]`,
|
||||
),
|
||||
);
|
||||
|
||||
if (matchingWebhooks.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queue = getQueue('webhooks');
|
||||
|
||||
for (const webhook of matchingWebhooks) {
|
||||
// Create a pending delivery record before enqueueing
|
||||
const [delivery] = await db
|
||||
.insert(webhookDeliveries)
|
||||
.values({
|
||||
webhookId: webhook.id,
|
||||
eventType: webhookEvent,
|
||||
payload,
|
||||
status: 'pending',
|
||||
})
|
||||
.returning({ id: webhookDeliveries.id });
|
||||
|
||||
await queue.add('deliver', {
|
||||
webhookId: webhook.id,
|
||||
portId,
|
||||
event: webhookEvent,
|
||||
deliveryId: delivery!.id,
|
||||
payload,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
// Never block callers — log and swallow
|
||||
logger.error(
|
||||
{ portId, internalEvent, webhookEvent, err },
|
||||
'Failed to dispatch webhook event',
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user