notifkit 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard/README.md +29 -0
- package/dashboard/dist/assets/index-7mvXb4bS.js +310 -0
- package/dashboard/dist/assets/index-o7L-f3-c.css +1 -0
- package/dashboard/dist/favicon.svg +4 -0
- package/dashboard/dist/index.html +14 -0
- package/dist/index.d.mts +447 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{main-CRPxM-O3.mjs → main-4Wyge9ms.mjs} +2 -2
- package/dist/{main-CRPxM-O3.mjs.map → main-4Wyge9ms.mjs.map} +1 -1
- package/dist/{main-Bgi2wWck.mjs → main-AO0JaY5z.mjs} +2 -2
- package/dist/{main-Bgi2wWck.mjs.map → main-AO0JaY5z.mjs.map} +1 -1
- package/dist/{main-DFMHcN_d.mjs → main-BMYSYcv2.mjs} +2 -2
- package/dist/{main-DFMHcN_d.mjs.map → main-BMYSYcv2.mjs.map} +1 -1
- package/dist/{main-Yorxzw0Z.mjs → main-BPbPnBIG.mjs} +2 -2
- package/dist/{main-Yorxzw0Z.mjs.map → main-BPbPnBIG.mjs.map} +1 -1
- package/dist/{main-pFAfCkVX.mjs → main-CyeQ_yDt.mjs} +2 -2
- package/dist/{main-pFAfCkVX.mjs.map → main-CyeQ_yDt.mjs.map} +1 -1
- package/dist/{main-CjbVdUqa.mjs → main-D19Wxs0x.mjs} +2 -2
- package/dist/{main-CjbVdUqa.mjs.map → main-D19Wxs0x.mjs.map} +1 -1
- package/dist/{main-D6SG3Isk.mjs → main-DSmh1e-V.mjs} +2 -2
- package/dist/{main-D6SG3Isk.mjs.map → main-DSmh1e-V.mjs.map} +1 -1
- package/dist/{main-CFSukWm8.mjs → main-JdOvJ70b.mjs} +495 -41
- package/dist/main-JdOvJ70b.mjs.map +1 -0
- package/dist/{src-CPMwsUCJ.mjs → src-CGmXRfsM.mjs} +86 -20
- package/dist/src-CGmXRfsM.mjs.map +1 -0
- package/drizzle/0005_admin_users.sql +11 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +8 -3
- package/scripts/create-admin.mjs +67 -0
- package/src/config/index.ts +12 -0
- package/src/db/schema.ts +13 -0
- package/src/repositories/index.ts +82 -1
- package/src/services/api/admin-static.ts +225 -0
- package/src/services/api/handlers.ts +224 -33
- package/src/services/api/main.ts +183 -29
- package/src/services/api/router.ts +7 -1
- package/src/services/auth/index.ts +111 -0
- package/dist/main-CFSukWm8.mjs.map +0 -1
- package/dist/src-CPMwsUCJ.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"main-CFSukWm8.mjs","names":["dbUsers","sql"],"sources":["../src/services/api/http.ts","../src/services/api/router.ts","../src/services/api/handlers.ts","../src/services/api/main.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { ZodError } from \"zod\";\n\nexport async function readRawBody(req: IncomingMessage): Promise<string> {\n const raw = await new Promise<string>((resolve, reject) => {\n const chunks: Buffer[] = [];\n let totalLength = 0;\n const MAX_PAYLOAD_SIZE = 5 * 1024 * 1024; // 5MB limit\n req.on(\"data\", (chunk: Buffer) => {\n totalLength += chunk.length;\n if (totalLength > MAX_PAYLOAD_SIZE) {\n req.destroy();\n reject(new HttpError(413, \"payload_too_large\"));\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString()));\n req.on(\"error\", reject);\n });\n return raw;\n}\n\nexport async function readJsonBody(req: IncomingMessage): Promise<unknown> {\n const raw = await readRawBody(req);\n if (raw.trim() === \"\") return undefined;\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n throw new HttpError(400, \"malformed_json\");\n }\n}\n\nexport function sendJson(res: ServerResponse, status: number, body: unknown): void {\n const payload = JSON.stringify(body);\n res.writeHead(status, {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(payload),\n });\n res.end(payload);\n}\n\nexport function sendNoContent(res: ServerResponse): void {\n res.writeHead(204).end();\n}\n\n/** Turn a Zod error into a 400 response with field-level issues. */\nexport function sendValidationError(res: ServerResponse, error: ZodError): void {\n sendJson(res, 400, {\n error: \"validation_error\",\n issues: error.issues.map((i) => ({ path: i.path.join(\".\"), message: i.message })),\n });\n}\n\n/** A thrown HttpError short-circuits a handler with a specific status/body. */\nexport class HttpError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message?: string,\n ) {\n super(message ?? code);\n }\n}\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\n\nexport interface RouteContext {\n params: Record<string, string>;\n query: URLSearchParams;\n projectId?: string;\n role?: \"admin\" | \"read_only\";\n}\n\nexport type RouteHandler = (\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n) => Promise<void> | void;\n\ntype Method = \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\";\n\ninterface Route {\n method: Method;\n segments: string[];\n handler: RouteHandler;\n}\n\n/**\n * Minimal path router with `:param` capture — enough for the REST surface here\n * without pulling in a framework. First match wins.\n */\nexport class Router {\n private readonly routes: Route[] = [];\n\n add(method: Method, pattern: string, handler: RouteHandler): this {\n this.routes.push({ method, segments: split(pattern), handler });\n return this;\n }\n\n get(p: string, h: RouteHandler) {\n return this.add(\"GET\", p, h);\n }\n post(p: string, h: RouteHandler) {\n return this.add(\"POST\", p, h);\n }\n patch(p: string, h: RouteHandler) {\n return this.add(\"PATCH\", p, h);\n }\n put(p: string, h: RouteHandler) {\n return this.add(\"PUT\", p, h);\n }\n delete(p: string, h: RouteHandler) {\n return this.add(\"DELETE\", p, h);\n }\n\n /** Returns the matched handler + captured params, or null. */\n match(\n method: string,\n pathname: string,\n ): { handler: RouteHandler; params: Record<string, string> } | null {\n const parts = split(pathname);\n for (const route of this.routes) {\n if (route.method !== method) continue;\n if (route.segments.length !== parts.length) continue;\n\n const params: Record<string, string> = {};\n let ok = true;\n for (let i = 0; i < route.segments.length; i++) {\n const seg = route.segments[i]!;\n const part = parts[i]!;\n if (seg.startsWith(\":\")) {\n params[seg.slice(1)] = decodeURIComponent(part);\n } else if (seg !== part) {\n ok = false;\n break;\n }\n }\n if (ok) return { handler: route.handler, params };\n }\n return null;\n }\n}\n\nfunction split(path: string): string[] {\n return path.split(\"/\").filter((s) => s.length > 0);\n}\n","import { randomUUID } from \"node:crypto\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { Logger } from \"@/logger/index.js\";\nimport type { RedisClient } from \"@/redis/index.js\";\nimport type { StreamProducer } from \"@/queue/index.js\";\nimport type {\n UserRepository,\n ContactRepository,\n TemplateRepository,\n ProjectRepository,\n WorkflowRepository,\n SegmentRepository,\n} from \"@/repositories/index.js\";\nimport type { Preferences } from \"@/contracts/index.js\";\nimport {\n AddUserSchema,\n UpdateUserSchema,\n BatchAddContactsSchema,\n type UserContactInput,\n SyncTemplatesSchema,\n NotifyRequestSchema,\n ContactChannelSchema,\n type ContactChannel,\n PreferencesSchema,\n type NotificationRequestedPayload,\n type NotificationTarget,\n buildStreamEvent,\n CreateWorkflowSchema,\n TriggerWorkflowSchema,\n STREAMS,\n PUBSUB_CHANNELS,\n} from \"@/contracts/index.js\";\nimport { readJsonBody, sendJson, sendNoContent, sendValidationError } from \"./http.js\";\nimport type { RouteContext } from \"./router.js\";\nimport { globalEmitter, getPriorityBucket, normaliseTarget } from \"@/shared/index.js\";\nimport { metrics } from \"@/metrics/index.js\";\nimport { eq, desc, and, lt, lte, gte, sql, like, or, isNotNull } from \"drizzle-orm\";\nimport {\n messageLogs,\n workflowDefinitions,\n scheduledPayloads,\n suppressions,\n users as dbUsers,\n userTopicPreferences,\n} from \"@/db/schema.js\";\nimport { readBaseConfig } from \"@/config/index.js\";\nimport { verifyUnsubscribeToken } from \"@/unsubscribe/index.js\";\nimport type { Db } from \"@/db/index.js\";\nimport { z } from \"zod\";\n\nconst IngestEventSchema = z.object({\n name: z.string().min(1),\n properties: z.record(z.string(), z.unknown()),\n});\n\nexport interface Deps {\n logger: Logger;\n redis: RedisClient;\n producers: Record<string, StreamProducer>;\n userRepo: UserRepository;\n contactRepo: ContactRepository;\n templateRepo: TemplateRepository;\n projectRepo: ProjectRepository;\n workflowRepo: WorkflowRepository;\n segmentRepo: SegmentRepository;\n db: Db;\n}\n\n// Inline user shape (email/phone/pushToken already normalised to string[] by Zod).\ninterface InlineUserLike {\n id: string;\n language?: string;\n timezone?: string;\n contacts?: UserContactInput[];\n email?: string[];\n phone?: string[];\n pushToken?: string[];\n segments?: string[];\n preferences?: Preferences;\n}\n\ninterface ResolvedContact {\n channel: ContactChannel;\n target: string;\n label?: string;\n isPrimary?: boolean;\n enabled?: boolean;\n preferences?: Preferences;\n}\n\n/** Map an inline or user input's contacts and legacy arrays into resolved contact records. */\nfunction contactsOf(user: InlineUserLike): ResolvedContact[] {\n const list: ResolvedContact[] = [];\n\n if (user.contacts && Array.isArray(user.contacts)) {\n for (const c of user.contacts) {\n list.push({\n channel: c.channel,\n target: c.target,\n label: c.label,\n isPrimary: c.isPrimary,\n enabled: c.enabled,\n preferences: c.preferences,\n });\n }\n }\n\n if (user.email) {\n for (const target of user.email) {\n list.push({ channel: \"email\", target });\n }\n }\n if (user.phone) {\n for (const target of user.phone) {\n list.push({ channel: \"sms\", target });\n }\n }\n if (user.pushToken) {\n for (const target of user.pushToken) {\n list.push({ channel: \"push\", target });\n }\n }\n\n return list;\n}\n\n/** Persist user records + their contacts in bulk. Shared by addUser and inline notify. */\nasync function persistUsers(deps: Deps, users: InlineUserLike[], projectId: string): Promise<void> {\n const usersList = users.map((u) => {\n const primaryEmail =\n u.email?.[0] ?? u.contacts?.find((c) => c.channel === \"email\")?.target ?? null;\n\n return {\n userId: u.id,\n language: u.language ?? \"en\",\n timezone: u.timezone ?? \"UTC\",\n email: primaryEmail,\n segments: u.segments ?? [],\n preferences: u.preferences ?? {},\n };\n });\n\n const contactsList: any[] = [];\n for (const u of users) {\n for (const c of contactsOf(u)) {\n contactsList.push({\n userId: u.id,\n channel: c.channel,\n target: c.target,\n preferences: c.preferences ?? {},\n });\n }\n }\n\n await deps.db.transaction(async (tx) => {\n const txUserRepo =\n deps.userRepo.constructor.name === \"Object\"\n ? deps.userRepo\n : new (deps.userRepo.constructor as any)(tx);\n const txContactRepo =\n deps.contactRepo.constructor.name === \"Object\"\n ? deps.contactRepo\n : new (deps.contactRepo.constructor as any)(tx);\n await txUserRepo.upsertManyFull(projectId, usersList);\n await txContactRepo.upsertMany(projectId, contactsList);\n });\n}\n\nfunction getQueryParam(ctx: RouteContext, key: string): string | undefined {\n if (!ctx.query) return undefined;\n if (typeof ctx.query.get === \"function\") {\n return ctx.query.get(key) ?? undefined;\n }\n return (ctx.query as any)[key] ?? undefined;\n}\n\nexport function createHandlers(deps: Deps) {\n const { logger } = deps;\n\n // ── PUT /v1/templates — initializeApp({ templates }) ──────────────────────\n async function syncTemplates(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const parsed = SyncTemplatesSchema.safeParse(await readJsonBody(req));\n if (!parsed.success) return sendValidationError(res, parsed.error);\n\n const synced = await deps.templateRepo.upsertMany(\n ctx.projectId!,\n parsed.data.templates.map((t) => ({\n id: t.id,\n channel: t.channel,\n topics: t.topic ?? [],\n content: t.content,\n aiPrompts: (t as any).aiPrompts,\n })),\n );\n\n // Invalidate caches\n for (const t of parsed.data.templates) {\n await deps.redis.native.publish(\"template.invalidated\", `${ctx.projectId}:${t.id}`);\n }\n\n logger.info({ synced }, \"templates synced\");\n sendJson(res, 200, { synced });\n }\n\n // ── POST /v1/users — addUser ──────────────────────────────────────────────\n async function addUser(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const parsed = AddUserSchema.safeParse(await readJsonBody(req));\n if (!parsed.success) return sendValidationError(res, parsed.error);\n\n await persistUsers(deps, [parsed.data], ctx.projectId!);\n logger.info({ userId: parsed.data.id }, \"user upserted\");\n sendJson(res, 201, { id: parsed.data.id });\n }\n\n // ── PATCH /v1/users/:id — updateUser ──────────────────────────────────────\n async function updateUser(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const userId = ctx.params.id!;\n const parsed = UpdateUserSchema.safeParse(await readJsonBody(req));\n if (!parsed.success) return sendValidationError(res, parsed.error);\n const patch = parsed.data;\n\n const updated = await deps.userRepo.updatePartial(ctx.projectId!, userId, {\n language: patch.language,\n timezone: patch.timezone,\n email: patch.email?.[0] ?? patch.contacts?.find((c) => c.channel === \"email\")?.target,\n segments: patch.segments,\n preferences: patch.preferences,\n });\n if (!updated) return sendJson(res, 404, { error: \"user_not_found\", id: userId });\n\n // New contact values in the patch are added (existing ones are kept).\n for (const c of contactsOf({ id: userId, ...patch })) {\n if (c.preferences) {\n await deps.contactRepo.upsert(ctx.projectId!, userId, c.channel, c.target, c.preferences);\n } else {\n await deps.contactRepo.upsert(ctx.projectId!, userId, c.channel, c.target);\n }\n }\n logger.info({ userId }, \"user updated\");\n sendJson(res, 200, { id: userId });\n }\n\n // ── DELETE /v1/users/:id — deleteUser ─────────────────────────────────────\n async function deleteUser(\n _req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const userId = ctx.params.id!;\n const deleted = await deps.userRepo.delete(ctx.projectId!, userId);\n if (!deleted) return sendJson(res, 404, { error: \"user_not_found\", id: userId });\n logger.info({ userId }, \"user deleted\");\n sendNoContent(res);\n }\n\n // ── POST /v1/users/:id/contacts — addUserContact / batch contacts ─────────\n async function addContact(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const userId = ctx.params.id!;\n const body = await readJsonBody(req);\n const parsed = BatchAddContactsSchema.safeParse(body);\n if (!parsed.success) return sendValidationError(res, parsed.error);\n\n const user = await deps.userRepo.findById(ctx.projectId!, userId);\n if (!user) return sendJson(res, 404, { error: \"user_not_found\", id: userId });\n\n if (Array.isArray(parsed.data)) {\n const contactsList = parsed.data.map((c) => ({\n userId,\n channel: c.channel,\n target: c.target,\n preferences: c.preferences ?? {},\n }));\n await deps.contactRepo.upsertMany(ctx.projectId!, contactsList);\n logger.info({ userId, count: contactsList.length }, \"contacts batch added\");\n sendJson(res, 201, {\n userId,\n contacts: contactsList.map((c) => ({ channel: c.channel, target: c.target })),\n });\n } else {\n await deps.contactRepo.upsert(\n ctx.projectId!,\n userId,\n parsed.data.channel,\n parsed.data.target,\n parsed.data.preferences ?? {},\n );\n logger.info({ userId, channel: parsed.data.channel }, \"contact added\");\n sendJson(res, 201, { userId, channel: parsed.data.channel, target: parsed.data.target });\n }\n }\n\n // ── DELETE /v1/users/:id/contacts/:channel/:target — deleteUserContact ────\n async function deleteContact(\n _req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const userId = ctx.params.id!;\n const channelResult = ContactChannelSchema.safeParse(ctx.params.channel);\n if (!channelResult.success)\n return sendJson(res, 400, { error: \"invalid_channel\", channel: ctx.params.channel });\n\n const deleted = await deps.contactRepo.delete(\n ctx.projectId!,\n userId,\n channelResult.data,\n ctx.params.target!,\n );\n if (!deleted) return sendJson(res, 404, { error: \"contact_not_found\" });\n logger.info({ userId, channel: channelResult.data }, \"contact deleted\");\n sendNoContent(res);\n }\n\n // ── POST /v1/notify — notify ──────────────────────────────────────────────\n async function notify(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const parsed = NotifyRequestSchema.safeParse(await readJsonBody(req));\n if (!parsed.success) return sendValidationError(res, parsed.error);\n const body = parsed.data;\n\n // Resolve the (unresolved) target. Inline users are synced first.\n let targets: NotificationTarget[] = [];\n if (body.user !== undefined) {\n const users = Array.isArray(body.user) ? body.user : [body.user];\n const inlineUsersToSync: InlineUserLike[] = [];\n for (const u of users) {\n if (typeof u === \"string\") {\n targets.push({ type: \"user\", userId: u });\n } else {\n inlineUsersToSync.push(u);\n targets.push({ type: \"user\", userId: u.id });\n }\n }\n\n if (inlineUsersToSync.length > 0) {\n try {\n await persistUsers(deps, inlineUsersToSync, ctx.projectId!);\n } catch (err) {\n deps.logger.error({ err }, \"inline user bulk sync failed\");\n }\n }\n } else if (body.segment !== undefined) {\n targets.push({ type: \"segment\", segment: body.segment });\n } else {\n targets.push({ type: \"topic\", topic: body.topic! });\n }\n\n const baseNotificationId = (req.headers[\"x-idempotency-key\"] as string) || randomUUID();\n\n const priority = body.priority ?? \"normal\";\n const p = getPriorityBucket(priority);\n const producer = deps.producers[p] ?? deps.producers.normal!;\n\n const messageIds: string[] = [];\n let lastNotificationId = \"\";\n\n const CHUNK_SIZE = 1000;\n for (let chunkStart = 0; chunkStart < targets.length; chunkStart += CHUNK_SIZE) {\n const chunk = targets.slice(chunkStart, chunkStart + CHUNK_SIZE);\n\n const events = chunk.map((target, idx) => {\n const i = chunkStart + idx;\n const notificationId =\n targets.length > 1 ? `${baseNotificationId}-${i}` : baseNotificationId;\n lastNotificationId = notificationId;\n\n const payload: NotificationRequestedPayload = {\n projectId: ctx.projectId!,\n target,\n templateId: body.template,\n priority,\n channels: body.channels,\n data: body.data ?? {},\n aiPrompts: body.aiPrompts,\n fallback: body.fallback ?? false,\n scheduledAt: body.sendAt,\n idempotencyKey: notificationId,\n campaignId: body.campaign,\n };\n\n return buildStreamEvent(\n \"notification.requested\",\n payload as Record<string, unknown>,\n \"api\",\n notificationId,\n );\n });\n\n const { messageIds: chunkMessageIds } = await producer.publishBatch(events);\n messageIds.push(...chunkMessageIds);\n }\n\n logger.info({ count: targets.length, priority: p }, \"notification requested\");\n metrics.messagesPublished.inc({ channel: \"api\", priority: p }, targets.length);\n\n if (targets.length === 1) {\n sendJson(res, 202, {\n messageId: messageIds[0],\n notificationId: lastNotificationId,\n target: targets[0],\n ...(body.campaign ? { campaign: body.campaign } : {}),\n });\n } else {\n sendJson(res, 202, {\n messageIds,\n notificationIdsBase: baseNotificationId,\n batchSize: targets.length,\n ...(body.campaign ? { campaign: body.campaign } : {}),\n });\n }\n }\n\n // ── DELETE /v1/notifications/:taskId — cancelNotification ─────────────────\n async function cancelNotification(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const { taskId } = ctx.params;\n if (!taskId) return sendJson(res, 400, { error: \"missing_task_id\" });\n\n const rows = await deps.db\n .select({ payload: scheduledPayloads.payload })\n .from(scheduledPayloads)\n .where(eq(scheduledPayloads.taskId, taskId))\n .limit(1);\n\n if (rows.length === 0) {\n return sendJson(res, 404, {\n error: \"not_found\",\n message: \"Task not found or already processed\",\n });\n }\n\n const payload = rows[0]!.payload as any;\n if (payload.projectId !== ctx.projectId) {\n return sendJson(res, 404, {\n error: \"not_found\",\n message: \"Task not found or already processed\",\n });\n }\n\n const deletedRows = await deps.db\n .delete(scheduledPayloads)\n .where(eq(scheduledPayloads.taskId, taskId))\n .returning({ taskId: scheduledPayloads.taskId });\n\n if (deletedRows.length > 0) {\n globalEmitter.emit(\"notification:canceled\", { projectId: ctx.projectId, taskId });\n sendJson(res, 200, { success: true });\n } else {\n sendJson(res, 404, { error: \"not_found\", message: \"Task not found or already processed\" });\n }\n }\n\n // ── POST /v1/workflows/trigger — triggerWorkflow ──────────────────────────\n async function triggerWorkflow(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const parsed = TriggerWorkflowSchema.safeParse(await readJsonBody(req));\n if (!parsed.success) return sendValidationError(res, parsed.error);\n\n const input: Record<string, unknown> = { ...(parsed.data.input ?? {}) };\n if (parsed.data.user) {\n if (typeof parsed.data.user === \"string\") {\n input.user = {\n id: parsed.data.user,\n ...(typeof input.user === \"object\" && input.user\n ? (input.user as Record<string, unknown>)\n : {}),\n };\n } else {\n try {\n await persistUsers(deps, [parsed.data.user], ctx.projectId!);\n } catch (err) {\n deps.logger.error({ err }, \"inline user sync for workflow failed\");\n }\n input.user = {\n id: parsed.data.user.id,\n ...(typeof input.user === \"object\" && input.user\n ? (input.user as Record<string, unknown>)\n : {}),\n };\n }\n }\n\n const instanceId = randomUUID();\n const messageId = await deps.producers.workflow!.publish(\n buildStreamEvent(\n \"workflow.triggered\",\n {\n projectId: ctx.projectId!,\n instanceId,\n name: parsed.data.name,\n input,\n },\n \"api\",\n instanceId,\n ),\n );\n logger.info({ messageId, instanceId, workflowName: parsed.data.name }, \"workflow triggered\");\n sendJson(res, 202, { messageId, instanceId });\n }\n\n // ── POST /v1/events — ingestEvent ─────────────────────────────────────────\n async function ingestEvent(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const tsStr = req.headers[\"x-timestamp\"];\n const expiryStr = req.headers[\"x-expiry\"];\n\n if (tsStr && expiryStr) {\n const now = Date.now();\n const eventTime = new Date(tsStr as string).getTime();\n const expMs = parseInt(expiryStr as string, 10) * 1000;\n\n if (now > eventTime + expMs) {\n sendJson(res, 400, { error: \"event_expired\", message: \"Webhook event is expired\" });\n return;\n }\n }\n\n const parsed = IngestEventSchema.safeParse(await readJsonBody(req));\n if (!parsed.success) return sendValidationError(res, parsed.error);\n\n const eventId = randomUUID();\n const messageId = await deps.producers.events!.publish(\n buildStreamEvent(\n \"event.received\",\n {\n projectId: ctx.projectId!,\n eventName: parsed.data.name,\n payload: parsed.data.properties,\n },\n \"api\",\n eventId,\n ),\n );\n logger.info({ messageId, eventId, eventName: parsed.data.name }, \"event received\");\n sendJson(res, 202, { messageId, eventId });\n }\n // ── GET /v1/events/stream — getEventsStream ──────────────────────────────────\n async function getEventsStream(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n res.write(\"retry: 10000\\n\\n\");\n\n const onDelivered = (\n taskId: string,\n providerMessageId: string,\n channel: string,\n eventProjectId: string,\n ) => {\n if (eventProjectId && eventProjectId !== ctx.projectId) return;\n res.write(`event: delivery:delivered\\n`);\n res.write(`data: ${JSON.stringify({ taskId, providerMessageId, channel })}\\n\\n`);\n };\n\n const onFailed = (taskId: string, error: string, channel: string, eventProjectId: string) => {\n if (eventProjectId && eventProjectId !== ctx.projectId) return;\n res.write(`event: delivery:failed\\n`);\n res.write(`data: ${JSON.stringify({ taskId, error, channel })}\\n\\n`);\n };\n\n globalEmitter.on(\"delivery:delivered\", onDelivered);\n globalEmitter.on(\"delivery:failed\", onFailed);\n\n req.on(\"close\", () => {\n globalEmitter.off(\"delivery:delivered\", onDelivered);\n globalEmitter.off(\"delivery:failed\", onFailed);\n });\n }\n\n // ── GET /v1/notifications/logs — getNotificationLogs ────────────────────\n async function getNotificationLogs(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const limitParam = parseInt(ctx.query.get(\"limit\") || \"50\", 10);\n const limit = isNaN(limitParam) ? 50 : Math.min(limitParam, 100);\n const cursor = ctx.query.get(\"cursor\");\n\n const templateId = getQueryParam(ctx, \"templateId\");\n const workflowInstanceId = getQueryParam(ctx, \"workflowInstanceId\");\n const channel = getQueryParam(ctx, \"channel\");\n const status = getQueryParam(ctx, \"status\");\n const taskId = getQueryParam(ctx, \"taskId\");\n const campaign = getQueryParam(ctx, \"campaign\") || getQueryParam(ctx, \"campaignId\");\n const search = getQueryParam(ctx, \"search\");\n\n const conditions = [eq(messageLogs.projectId, ctx.projectId!)];\n if (templateId) conditions.push(eq(messageLogs.templateId, templateId));\n if (workflowInstanceId) conditions.push(eq(messageLogs.workflowInstanceId, workflowInstanceId));\n if (channel) conditions.push(eq(messageLogs.channel, channel as any));\n if (status) conditions.push(eq(messageLogs.status, status as any));\n if (taskId) conditions.push(eq(messageLogs.taskId, taskId));\n if (campaign) conditions.push(eq(messageLogs.campaignId, campaign));\n if (search) {\n conditions.push(\n or(\n like(messageLogs.taskId, `%${search}%`),\n like(messageLogs.templateId, `%${search}%`),\n like(messageLogs.providerMessageId, `%${search}%`),\n like(messageLogs.campaignId, `%${search}%`),\n )!,\n );\n }\n\n if (cursor) {\n const cursorDate = new Date(parseInt(cursor, 10));\n conditions.push(lt(messageLogs.timestamp, cursorDate));\n }\n\n const logs = await deps.db\n .select()\n .from(messageLogs)\n .where(and(...conditions))\n .orderBy(desc(messageLogs.timestamp))\n .limit(limit * 2);\n\n // Deduplicate by taskId preserving the latest status per taskId\n const deduplicatedMap = new Map<string, (typeof logs)[0]>();\n for (const log of logs) {\n if (!deduplicatedMap.has(log.taskId)) {\n deduplicatedMap.set(log.taskId, log);\n } else {\n const existing = deduplicatedMap.get(log.taskId)!;\n if (existing.status === \"dispatched\" && log.status !== \"dispatched\") {\n deduplicatedMap.set(log.taskId, log);\n }\n }\n }\n const deduplicatedLogs = Array.from(deduplicatedMap.values()).slice(0, limit);\n\n const nextCursor =\n logs.length === limit * 2 ? logs[logs.length - 1]!.timestamp.getTime().toString() : null;\n\n sendJson(res, 200, { logs: deduplicatedLogs, nextCursor });\n }\n\n // ── GET /v1/notifications/:taskId — getNotificationStatus ───────────────\n async function getNotificationStatus(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const { taskId } = ctx.params;\n if (!taskId) return sendJson(res, 400, { error: \"missing_task_id\" });\n\n const logs = await deps.db\n .select()\n .from(messageLogs)\n .where(and(eq(messageLogs.taskId, taskId), eq(messageLogs.projectId, ctx.projectId!)))\n .orderBy(desc(messageLogs.timestamp));\n\n if (logs.length === 0) {\n // It might still be pending in Redis or scheduled\n return sendJson(res, 404, {\n error: \"not_found\",\n message: \"Task not found or not processed yet\",\n });\n }\n\n // Newest first, so `status` is the latest known state rather than whichever\n // row Postgres happened to return first.\n sendJson(res, 200, { status: logs[0]?.status, logs });\n }\n\n // ── GET /v1/users/:id — getUser ───────────────────────────────────────────\n async function getUser(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const userId = ctx.params.id!;\n const user = await deps.userRepo.findRecordById(ctx.projectId!, userId);\n if (!user) return sendJson(res, 404, { error: \"user_not_found\", id: userId });\n\n const contacts = await deps.contactRepo.findByUserId(ctx.projectId!, userId);\n sendJson(res, 200, { ...user, contacts });\n }\n\n // ── GET /v1/users/:id/preferences — getUserPreferences ────────────────────\n async function getUserPreferences(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const userId = ctx.params.id!;\n const user = await deps.userRepo.findRecordById(ctx.projectId!, userId);\n if (!user) return sendJson(res, 404, { error: \"user_not_found\", id: userId });\n\n sendJson(res, 200, user.preferences);\n }\n\n // ── PATCH /v1/users/:id/preferences — updateUserPreferences ───────────────\n async function updateUserPreferences(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const userId = ctx.params.id!;\n const parsed = PreferencesSchema.safeParse(await readJsonBody(req));\n if (!parsed.success) return sendValidationError(res, parsed.error);\n\n const updated = await deps.userRepo.updatePartial(ctx.projectId!, userId, {\n preferences: parsed.data,\n });\n if (!updated) return sendJson(res, 404, { error: \"user_not_found\", id: userId });\n\n logger.info({ userId }, \"user preferences updated\");\n sendJson(res, 200, { id: userId, preferences: parsed.data });\n }\n\n // ── POST /v1/workflows — createWorkflow ───────────────────────────────────\n async function createWorkflow(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const body = await readJsonBody(req);\n const parsed = CreateWorkflowSchema.safeParse(body);\n if (!parsed.success) return sendValidationError(res, parsed.error);\n const input = parsed.data;\n\n try {\n await deps.db\n .insert(workflowDefinitions)\n .values({\n projectId: ctx.projectId!,\n name: input.name,\n steps: input.steps,\n })\n .onConflictDoUpdate({\n target: [workflowDefinitions.projectId, workflowDefinitions.name],\n set: {\n steps: input.steps,\n },\n });\n sendJson(res, 201, { name: input.name });\n } catch (error: any) {\n deps.logger.error({ err: error }, \"Failed to create workflow definition\");\n sendJson(res, 500, { error: \"internal_error\", message: error.message });\n }\n }\n\n // ── GET /v1/templates — listTemplates ─────────────────────────────────────\n async function listTemplates(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const limitParam = getQueryParam(ctx, \"limit\");\n const limit = limitParam ? Math.min(parseInt(limitParam, 10), 100) : undefined;\n const channel = getQueryParam(ctx, \"channel\");\n const topic = getQueryParam(ctx, \"topic\");\n let templates = await deps.templateRepo.list(ctx.projectId!);\n if (channel) {\n templates = templates.filter((t: any) => t.channel === channel);\n }\n if (topic) {\n templates = templates.filter((t: any) => t.topics?.includes(topic));\n }\n if (limit) {\n templates = templates.slice(0, limit);\n }\n sendJson(res, 200, { templates });\n }\n\n // ── GET /v1/templates/:id — getTemplate ───────────────────────────────────\n async function getTemplate(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const templateId = ctx.params.id!;\n const template = await deps.templateRepo.findById(ctx.projectId!, templateId);\n if (!template) return sendJson(res, 404, { error: \"template_not_found\", id: templateId });\n\n sendJson(res, 200, template);\n }\n\n // ── DELETE /v1/templates/:id — deleteTemplate ───────────────────────────\n async function deleteTemplate(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const templateId = ctx.params.id!;\n const deleted = await deps.templateRepo.delete(ctx.projectId!, templateId);\n if (!deleted) return sendJson(res, 404, { error: \"template_not_found\", id: templateId });\n\n await deps.redis.native.publish(\"template.invalidated\", `${ctx.projectId}:${templateId}`);\n logger.info({ templateId }, \"template deleted\");\n sendNoContent(res);\n }\n\n // ── GET /v1/workflows — listWorkflows ─────────────────────────────────────\n async function listWorkflows(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const limitParam = getQueryParam(ctx, \"limit\");\n const limit = limitParam ? Math.min(parseInt(limitParam, 10), 100) : undefined;\n const search = getQueryParam(ctx, \"search\")?.trim().toLowerCase();\n let workflows = await deps.workflowRepo.listDefinitions(ctx.projectId!);\n if (search) {\n workflows = workflows.filter((w: any) => w.name?.toLowerCase().includes(search));\n }\n if (limit) {\n workflows = workflows.slice(0, limit);\n }\n sendJson(res, 200, { workflows });\n }\n\n // ── GET /v1/workflows/instances/:id — getWorkflow ─────────────────────────\n async function getWorkflow(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const instanceId = ctx.params.id!;\n const workflow = await deps.workflowRepo.getInstance(ctx.projectId!, instanceId);\n if (!workflow) return sendJson(res, 404, { error: \"workflow_not_found\", id: instanceId });\n\n sendJson(res, 200, workflow);\n }\n\n // ── DELETE /v1/workflows/instances/:id — cancelWorkflow ───────────────────\n async function cancelWorkflow(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const instanceId = ctx.params.id!;\n const canceled = await deps.workflowRepo.cancelInstance(ctx.projectId!, instanceId);\n if (!canceled) return sendJson(res, 400, { error: \"workflow_not_cancelable\", id: instanceId });\n\n logger.info({ instanceId }, \"workflow canceled\");\n sendNoContent(res);\n }\n\n // ── GET /v1/users — listUsers ─────────────────────────────────────────────\n async function listUsers(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const limitParam = parseInt(getQueryParam(ctx, \"limit\") || \"50\", 10);\n const limit = isNaN(limitParam) ? 50 : Math.min(limitParam, 100);\n const cursor = getQueryParam(ctx, \"cursor\");\n const search = getQueryParam(ctx, \"search\");\n const segment = getQueryParam(ctx, \"segment\");\n const language = getQueryParam(ctx, \"language\");\n const timezone = getQueryParam(ctx, \"timezone\");\n const channel = getQueryParam(ctx, \"channel\");\n\n const filters =\n search || segment || language || timezone || channel\n ? { search, segment, language, timezone, channel }\n : undefined;\n\n const result = await deps.userRepo.list(ctx.projectId!, limit, cursor, filters);\n sendJson(res, 200, result);\n }\n\n // ── GET /v1/users/:id/contacts — getUserContacts ──────────────────────────\n async function getUserContacts(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const userId = ctx.params.id!;\n const contacts = await deps.contactRepo.findByUserId(ctx.projectId!, userId);\n sendJson(res, 200, { contacts });\n }\n\n // ── GET /v1/segments — listSegments ───────────────────────────────────────\n async function listSegments(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const segments = await deps.segmentRepo.listSegments(ctx.projectId!);\n sendJson(res, 200, { segments });\n }\n\n // ── GET /v1/projects — listProjects ───────────────────────────────────────\n async function listProjects(\n req: IncomingMessage,\n res: ServerResponse,\n _ctx: RouteContext,\n ): Promise<void> {\n const projects = await deps.projectRepo.list();\n sendJson(res, 200, { projects });\n }\n\n // ── DELETE /v1/projects/:id — deleteProject ───────────────────────────────\n async function deleteProject(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const id = ctx.params.id!;\n const deleted = await deps.projectRepo.delete(id);\n if (!deleted) return sendJson(res, 404, { error: \"project_not_found\" });\n\n sendNoContent(res);\n }\n\n // ── PATCH /v1/projects/:id — updateProject ────────────────────────────────\n async function updateProject(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const id = ctx.params.id!;\n const parsed = z\n .object({\n rateLimitRpm: z.number().nullable().optional(),\n throttleLimit: z.number().nullable().optional(),\n throttleWindowHours: z.number().nullable().optional(),\n })\n .safeParse(await readJsonBody(req));\n\n if (!parsed.success) return sendValidationError(res, parsed.error);\n\n const updated = await deps.projectRepo.updateSettings(id, parsed.data);\n if (!updated) return sendJson(res, 404, { error: \"project_not_found\" });\n\n // Drop the engine's cached throttle overrides so the change applies to the\n // next notification instead of waiting out the cache TTL.\n await deps.redis.native.publish(PUBSUB_CHANNELS.PROJECT_INVALIDATED, id);\n\n // rateLimitRpm is read through the API's auth cache, which is keyed by token\n // hash rather than project — dropping the whole cache is the only way to\n // pick up a new limit promptly. Project updates are rare admin operations.\n if (parsed.data.rateLimitRpm !== undefined) {\n await deps.redis.native.publish(PUBSUB_CHANNELS.API_KEY_INVALIDATED, \"*\");\n }\n\n logger.info({ projectId: id }, \"project settings updated\");\n sendJson(res, 200, { id });\n }\n\n // ── POST /v1/projects/:id/keys — createProjectKey ───────────────────\n async function createProjectKey(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const id = ctx.params.id!;\n const parsed = z\n .object({ role: z.enum([\"admin\", \"read_only\"]).default(\"admin\") })\n .safeParse(await readJsonBody(req));\n const role = parsed.success ? parsed.data.role : \"admin\";\n\n const { randomBytes, createHash } = await import(\"node:crypto\");\n const apiKey = `nk_live_${randomBytes(32).toString(\"hex\")}`;\n const apiKeyHash = createHash(\"sha256\").update(apiKey).digest(\"hex\");\n\n const key = await deps.projectRepo.createApiKey(id, apiKeyHash, role);\n sendJson(res, 201, { id: key.id, apiKey, role });\n }\n\n // ── GET /v1/projects/:id/keys — listProjectKeys ─────────────────────\n async function listProjectKeys(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const id = ctx.params.id!;\n const keys = await deps.projectRepo.listApiKeys(id);\n sendJson(res, 200, { keys });\n }\n\n // ── DELETE /v1/projects/:id/keys/:keyId — deleteProjectKey ──────────\n async function deleteProjectKey(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const { id, keyId } = ctx.params;\n const deleted = await deps.projectRepo.deleteApiKey(id!, keyId!);\n if (!deleted) return sendJson(res, 404, { error: \"key_not_found\" });\n\n await deps.redis.native.publish(\"apikey.invalidated\", \"*\");\n sendJson(res, 204, {});\n }\n\n // ── GET /v1/system/health — getSystemHealth ──────────────────────────────\n async function getSystemHealth(\n _req: IncomingMessage,\n res: ServerResponse,\n _ctx: RouteContext,\n ): Promise<void> {\n const keys = [\"enricher\", \"engine\", \"scheduler\", \"delivery\", \"ai\", \"workflow\", \"events\"];\n const workers: Record<string, any> = {};\n let redisOk = false;\n let dbOk = false;\n let redisLatency = 0;\n let dbLatency = 0;\n\n const startRedis = Date.now();\n try {\n await deps.redis.native.ping();\n redisOk = true;\n redisLatency = Date.now() - startRedis;\n } catch {\n redisOk = false;\n }\n\n const startDb = Date.now();\n try {\n await deps.db.execute(sql`SELECT 1`);\n dbOk = true;\n dbLatency = Date.now() - startDb;\n } catch {\n dbOk = false;\n }\n\n if (redisOk) {\n try {\n const vals = await deps.redis.native.mget(keys.map((k) => `notif:health:${k}`));\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i] as string;\n const val = vals[i];\n if (val) {\n workers[key] = JSON.parse(val);\n } else {\n workers[key] = { status: \"unknown\", message: \"No heartbeat\" };\n }\n }\n } catch (err: any) {\n for (const key of keys) {\n workers[key] = { status: \"error\", error: err.message };\n }\n }\n }\n\n sendJson(res, 200, {\n status: redisOk && dbOk ? \"healthy\" : \"degraded\",\n redis: { ok: redisOk, latencyMs: redisLatency },\n db: { ok: dbOk, latencyMs: dbLatency },\n workers,\n });\n }\n\n // ── GET /v1/system/metrics — getSystemMetrics ────────────────────────────\n async function getSystemMetrics(\n _req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const streamMap: Record<string, string> = {\n INBOUND_CRITICAL: STREAMS.INBOUND_CRITICAL,\n INBOUND_NORMAL: STREAMS.INBOUND_NORMAL,\n INBOUND_LOW: STREAMS.INBOUND_LOW,\n ENRICHED_NORMAL: STREAMS.ENRICHED_NORMAL,\n OUTBOUND_CRITICAL: STREAMS.OUTBOUND_CRITICAL,\n OUTBOUND_NORMAL: STREAMS.OUTBOUND_NORMAL,\n OUTBOUND_LOW: STREAMS.OUTBOUND_LOW,\n WORKFLOW_INBOUND: STREAMS.WORKFLOW_INBOUND,\n EVENTS_INBOUND: STREAMS.EVENTS_INBOUND,\n DEAD_LETTER: STREAMS.DEAD_LETTER,\n };\n\n const streamDepths: Record<string, number> = {};\n for (const [key, realRedisKey] of Object.entries(streamMap)) {\n try {\n const len = await deps.redis.native.xlen(realRedisKey);\n streamDepths[key] = len;\n } catch {\n streamDepths[key] = 0;\n }\n }\n\n // Scoped to the caller's project: an unscoped count reports every tenant's\n // traffic to whoever asks.\n const totalTasksRes = await deps.db\n .select({ count: sql<number>`count(distinct ${messageLogs.taskId})` })\n .from(messageLogs)\n .where(eq(messageLogs.projectId, ctx.projectId!));\n const deliveredTasksRes = await deps.db\n .select({ count: sql<number>`count(distinct ${messageLogs.taskId})` })\n .from(messageLogs)\n .where(and(eq(messageLogs.projectId, ctx.projectId!), eq(messageLogs.status, \"delivered\")));\n\n const total = Number(totalTasksRes[0]?.count ?? 0);\n const delivered = Number(deliveredTasksRes[0]?.count ?? 0);\n const failed = streamDepths.DEAD_LETTER || 0;\n const successRate = total > 0 ? Number(((delivered / total) * 100).toFixed(2)) : 100;\n\n sendJson(res, 200, {\n streams: streamDepths,\n deliveryStats: {\n total,\n delivered,\n failed,\n successRate,\n },\n });\n }\n\n // ── GET /v1/dlq — getDLQMessages ─────────────────────────────────────────\n async function getDLQMessages(\n _req: IncomingMessage,\n res: ServerResponse,\n _ctx: RouteContext,\n ): Promise<void> {\n try {\n const rawEntries = await deps.redis.native.xrevrange(\n STREAMS.DEAD_LETTER,\n \"+\",\n \"-\",\n \"COUNT\",\n \"50\",\n );\n const messages = (rawEntries || []).map(([id, fields]: [string, string[]]) => {\n const fieldMap: Record<string, string> = {};\n for (let i = 0; i < fields.length; i += 2) {\n fieldMap[fields[i]!] = fields[i + 1]!;\n }\n return {\n id,\n eventType: fieldMap.eventType || fieldMap.event_type || \"unknown\",\n payload: fieldMap.payload ? JSON.parse(fieldMap.payload) : fieldMap,\n error: fieldMap.error || fieldMap.reason || \"Dead letter payload\",\n timestamp: fieldMap.timestamp || new Date().toISOString(),\n };\n });\n sendJson(res, 200, { messages });\n } catch {\n sendJson(res, 200, { messages: [] });\n }\n }\n\n // ── POST /v1/dlq/replay — replayDLQMessage ────────────────────────────────\n async function replayDLQMessage(\n req: IncomingMessage,\n res: ServerResponse,\n _ctx: RouteContext,\n ): Promise<void> {\n const body = (await readJsonBody(req).catch(() => ({}))) as any;\n const messageId = body?.id;\n\n if (!messageId) {\n sendJson(res, 400, { error: \"missing_message_id\" });\n return;\n }\n\n try {\n const rawEntries = await deps.redis.native.xrange(STREAMS.DEAD_LETTER, messageId, messageId);\n if (!rawEntries || rawEntries.length === 0 || !rawEntries[0]) {\n sendJson(res, 404, { error: \"dlq_message_not_found\" });\n return;\n }\n\n const fields = rawEntries[0][1];\n const fieldMap: Record<string, string> = {};\n for (let i = 0; i < fields.length; i += 2) {\n fieldMap[fields[i]!] = fields[i + 1]!;\n }\n\n const priority = fieldMap.priority || \"normal\";\n const p = getPriorityBucket(priority);\n const targetStream =\n STREAMS[`INBOUND_${p.toUpperCase()}` as keyof typeof STREAMS] || STREAMS.INBOUND_NORMAL;\n\n const xaddArgs: string[] = [];\n for (const [k, v] of Object.entries(fieldMap)) {\n xaddArgs.push(k, v);\n }\n await deps.redis.native.xadd(targetStream, \"*\", ...xaddArgs);\n await deps.redis.native.xdel(STREAMS.DEAD_LETTER, messageId);\n\n sendJson(res, 200, { success: true, replayedId: messageId });\n } catch (err: any) {\n sendJson(res, 500, { error: \"replay_failed\", message: err.message });\n }\n }\n\n // ── DELETE /v1/dlq/:id — deleteDLQMessage ────────────────────────────────\n async function deleteDLQMessage(\n _req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const messageId = ctx.params.id;\n if (!messageId) return sendJson(res, 400, { error: \"missing_id\" });\n\n try {\n await deps.redis.native.xdel(STREAMS.DEAD_LETTER, messageId);\n sendJson(res, 200, { success: true });\n } catch (err: any) {\n sendJson(res, 500, { error: \"delete_failed\", message: err.message });\n }\n }\n\n // ── GET /v1/notifications/scheduled — getScheduledMessages ──────────────\n async function getScheduledMessages(\n _req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const limitParam = getQueryParam(ctx, \"limit\");\n const limit = limitParam ? Math.min(parseInt(limitParam, 10), 100) : 50;\n const cursor = getQueryParam(ctx, \"cursor\");\n const channel = getQueryParam(ctx, \"channel\");\n\n const conditions = [sql`(payload->>'projectId') = ${ctx.projectId!}`];\n if (cursor) {\n conditions.push(sql`task_id > ${cursor}`);\n }\n if (channel) {\n conditions.push(sql`(payload->>'channel') = ${channel}`);\n }\n\n const rows = await deps.db\n .select()\n .from(scheduledPayloads)\n .where(and(...conditions))\n .orderBy(scheduledPayloads.taskId)\n .limit(limit + 1);\n\n const hasNext = rows.length > limit;\n const scheduled = hasNext ? rows.slice(0, limit) : rows;\n const nextCursor =\n hasNext && scheduled.length > 0 ? scheduled[scheduled.length - 1]!.taskId : null;\n\n sendJson(res, 200, { scheduled, nextCursor });\n }\n\n // ── GET /v1/users/:id/details — getUserDetails ────────────────────────────\n async function getUserDetails(\n _req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const userId = ctx.params.id!;\n const user = await deps.userRepo.findRecordById(ctx.projectId!, userId);\n if (!user) return sendJson(res, 404, { error: \"user_not_found\", id: userId });\n\n const contacts = await deps.contactRepo.findByUserId(ctx.projectId!, userId);\n const logs = await deps.db\n .select()\n .from(messageLogs)\n .where(eq(messageLogs.projectId, ctx.projectId!))\n .orderBy(desc(messageLogs.timestamp))\n .limit(50);\n\n sendJson(res, 200, { ...user, contacts, logs });\n }\n\n // ── Unsubscribe ───────────────────────────────────────────────────────────\n //\n // Both routes are unauthenticated: they are reached from a mail client, which\n // has no API key. The signed token in the URL is the credential and it\n // authorises exactly one thing for one address.\n\n function sendHtml(res: ServerResponse, status: number, body: string): void {\n res.writeHead(status, {\n \"Content-Type\": \"text/html; charset=utf-8\",\n // Nothing here should be cached by an inbox proxy or shared cache.\n \"Cache-Control\": \"no-store\",\n \"X-Content-Type-Options\": \"nosniff\",\n \"Referrer-Policy\": \"no-referrer\",\n });\n res.end(body);\n }\n\n function escapeHtml(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n }\n\n function page(title: string, message: string): string {\n return `<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>${escapeHtml(title)}</title>\n<style>body{font:16px/1.6 system-ui,sans-serif;max-width:34rem;margin:12vh auto;padding:0 1.5rem;color:#111}\nh1{font-size:1.4rem;margin:0 0 .75rem}p{margin:0 0 1rem;color:#444}\nbutton{font:inherit;padding:.6rem 1.2rem;border:0;border-radius:6px;background:#111;color:#fff;cursor:pointer}\ncode{background:#f4f4f5;padding:.1rem .35rem;border-radius:4px}</style>\n</head><body><h1>${escapeHtml(title)}</h1>${message}</body></html>`;\n }\n\n /**\n * GET — the page a human lands on after clicking the link in the mail body.\n *\n * Deliberately does not unsubscribe anything. Corporate mail scanners and\n * link prescanners issue a GET against every URL in a message; performing the\n * opt-out here would unsubscribe people who never clicked. The mutation lives\n * behind the POST.\n */\n async function unsubscribePage(\n _req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const cfg = readBaseConfig();\n const token = ctx.query.get(\"token\") ?? \"\";\n if (!cfg.UNSUBSCRIBE_SECRET) {\n deps.logger.warn(\n \"UNSUBSCRIBE_SECRET is not configured — unable to verify unsubscribe tokens\",\n );\n }\n const claim = cfg.UNSUBSCRIBE_SECRET\n ? verifyUnsubscribeToken(token, cfg.UNSUBSCRIBE_SECRET)\n : null;\n\n if (!claim) {\n return sendHtml(\n res,\n 400,\n page(\n \"This link is not valid\",\n \"<p>It may have been altered in transit, or this server may have been reconfigured since the message was sent. Replying to the message and asking to be removed will still work.</p>\",\n ),\n );\n }\n\n const what =\n claim.topics.length > 0\n ? `<code>${claim.topics.map(escapeHtml).join(\"</code>, <code>\")}</code> messages`\n : \"all messages\";\n\n sendHtml(\n res,\n 200,\n page(\n \"Unsubscribe\",\n `<p>Stop sending ${what} to <code>${escapeHtml(claim.target)}</code>?</p>\n<form method=\"post\" action=\"/v1/unsubscribe?token=${encodeURIComponent(token)}\">\n<button type=\"submit\">Unsubscribe</button></form>`,\n ),\n );\n }\n\n /**\n * POST — the real opt-out.\n *\n * Serves two callers with one handler: a mail client doing RFC 8058 one-click\n * (body `List-Unsubscribe=One-Click`, token in the query string, no\n * confirmation possible), and the form on the page above. Neither can carry a\n * CSRF token, and the one-click spec forbids an interstitial, so the signed\n * token is the whole of the authorisation.\n */\n async function unsubscribe(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n // The one-click POST carries `List-Unsubscribe=One-Click` as its body and\n // the token in the query string, so there is nothing here worth reading —\n // but an unread body leaves bytes on a keep-alive socket. Discard it.\n req.resume();\n\n const cfg = readBaseConfig();\n const token = ctx.query.get(\"token\") ?? \"\";\n if (!cfg.UNSUBSCRIBE_SECRET) {\n deps.logger.warn(\n \"UNSUBSCRIBE_SECRET is not configured — unable to verify unsubscribe tokens\",\n );\n }\n const claim = cfg.UNSUBSCRIBE_SECRET\n ? verifyUnsubscribeToken(token, cfg.UNSUBSCRIBE_SECRET)\n : null;\n\n if (!claim) {\n return sendHtml(\n res,\n 400,\n page(\n \"This link is not valid\",\n \"<p>It may have been altered in transit, or this server may have been reconfigured since the message was sent.</p>\",\n ),\n );\n }\n\n const target = normaliseTarget(claim.target);\n\n try {\n const userRows = await deps.db\n .select({ id: dbUsers.id })\n .from(dbUsers)\n .where(and(eq(dbUsers.projectId, claim.projectId), eq(dbUsers.externalId, claim.userId)))\n .limit(1);\n const internalId = userRows[0]?.id;\n\n if (claim.topics.length > 0 && internalId) {\n // Scoped opt-out: silence this kind of mail and leave the rest,\n // including anything transactional, working.\n await deps.db\n .insert(userTopicPreferences)\n .values(claim.topics.map((topic) => ({ userId: internalId, topic, enabled: false })))\n .onConflictDoUpdate({\n target: [userTopicPreferences.userId, userTopicPreferences.topic],\n set: { enabled: sql`excluded.enabled` },\n });\n } else {\n // No topic to scope to, or the user record is gone. Either way the\n // request has to be honoured, so fall back to suppressing the address\n // outright — over-honouring an unsubscribe is the safe direction.\n await deps.db\n .insert(suppressions)\n .values({\n projectId: claim.projectId,\n channel: claim.channel as any,\n target,\n reason: \"unsubscribed\",\n source: \"unsubscribe-link\",\n })\n .onConflictDoNothing();\n }\n\n logger.info(\n {\n projectId: claim.projectId,\n channel: claim.channel,\n topics: claim.topics,\n scoped: claim.topics.length > 0 && Boolean(internalId),\n },\n \"unsubscribe honoured\",\n );\n } catch (err) {\n // A 5xx here means the mail client reports failure and, worse, the person\n // believes they have opted out when they have not.\n logger.error({ err, projectId: claim.projectId }, \"unsubscribe failed to apply\");\n return sendHtml(\n res,\n 500,\n page(\n \"Something went wrong\",\n \"<p>We could not record that just now. Please try again in a moment.</p>\",\n ),\n );\n }\n\n sendHtml(\n res,\n 200,\n page(\n \"You're unsubscribed\",\n `<p>We've stopped sending ${\n claim.topics.length > 0 ? \"these messages\" : \"messages\"\n } to <code>${escapeHtml(claim.target)}</code>. It may take a few minutes to take effect for mail already on its way.</p>`,\n ),\n );\n }\n\n // ── GET /v1/campaigns — listCampaigns ─────────────────────────────────────\n //\n // One row per campaign label, newest activity first, with support for filtering\n // by search keyword, channel, date range, and minimum message counts.\n async function listCampaigns(\n _req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const limitParam = parseInt(getQueryParam(ctx, \"limit\") || \"20\", 10);\n const limit = isNaN(limitParam) ? 20 : Math.min(Math.max(limitParam, 1), 100);\n const search = getQueryParam(ctx, \"search\")?.trim();\n const channel = getQueryParam(ctx, \"channel\")?.trim();\n const since = getQueryParam(ctx, \"since\")?.trim();\n const until = getQueryParam(ctx, \"until\")?.trim();\n const minMessagesParam = parseInt(getQueryParam(ctx, \"minMessages\") || \"\", 10);\n const minMessages = isNaN(minMessagesParam) ? undefined : Math.max(minMessagesParam, 1);\n\n const conditions = [\n eq(messageLogs.projectId, ctx.projectId!),\n isNotNull(messageLogs.campaignId),\n ];\n\n if (search) {\n conditions.push(like(messageLogs.campaignId, `%${search}%`));\n }\n if (channel) {\n conditions.push(eq(messageLogs.channel, channel as any));\n }\n if (since) {\n const sinceDate = new Date(since);\n if (!isNaN(sinceDate.getTime())) {\n conditions.push(gte(messageLogs.timestamp, sinceDate));\n }\n }\n if (until) {\n const untilDate = new Date(until);\n if (!isNaN(untilDate.getTime())) {\n conditions.push(lte(messageLogs.timestamp, untilDate));\n }\n }\n\n let query = deps.db\n .select({\n campaign: messageLogs.campaignId,\n messages: sql<number>`count(distinct ${messageLogs.taskId})`,\n firstSentAt: sql<string>`min(${messageLogs.timestamp})`,\n lastActivityAt: sql<string>`max(${messageLogs.timestamp})`,\n })\n .from(messageLogs)\n .where(and(...conditions))\n .groupBy(messageLogs.campaignId)\n .orderBy(desc(sql`max(${messageLogs.timestamp})`))\n .limit(limit);\n\n if (minMessages !== undefined) {\n query = query.having(gte(sql`count(distinct ${messageLogs.taskId})`, minMessages)) as any;\n }\n\n const rows = await query;\n\n sendJson(res, 200, {\n campaigns: rows.map((r: any) => ({\n campaign: r.campaign,\n messages: Number(r.messages ?? 0),\n firstSentAt: r.firstSentAt,\n lastActivityAt: r.lastActivityAt,\n })),\n });\n }\n\n // ── GET /v1/campaigns/:campaign/stats — getCampaignStats ──────────────────\n //\n // The delivery funnel for one campaign, in a single grouped scan.\n //\n // Every figure counts *distinct tasks*, not rows: a task accumulates a\n // dispatch row, an attempt row, and any number of engagement rows, so\n // counting rows would inflate every number by a different factor. Opening the\n // same email twice is one person who opened it.\n async function getCampaignStats(\n _req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const campaign = ctx.params.campaign;\n if (!campaign) return sendJson(res, 400, { error: \"missing_campaign\" });\n\n const rows = await deps.db\n .select({\n channel: messageLogs.channel,\n kind: messageLogs.kind,\n status: messageLogs.status,\n tasks: sql<number>`count(distinct ${messageLogs.taskId})`,\n })\n .from(messageLogs)\n .where(and(eq(messageLogs.projectId, ctx.projectId!), eq(messageLogs.campaignId, campaign)))\n .groupBy(messageLogs.channel, messageLogs.kind, messageLogs.status);\n\n if (rows.length === 0) {\n return sendJson(res, 404, {\n error: \"not_found\",\n message: `No messages recorded for campaign '${campaign}'`,\n });\n }\n\n const ENGAGEMENT = [\"opened\", \"clicked\", \"bounced\", \"complained\", \"unsubscribed\"] as const;\n type Engagement = (typeof ENGAGEMENT)[number];\n\n const blank = () => ({\n sent: 0,\n delivered: 0,\n failed: 0,\n opened: 0,\n clicked: 0,\n bounced: 0,\n complained: 0,\n unsubscribed: 0,\n });\n\n const byChannel = new Map<string, ReturnType<typeof blank>>();\n for (const row of rows as any[]) {\n const bucket = byChannel.get(row.channel) ?? blank();\n const n = Number(row.tasks ?? 0);\n\n if (ENGAGEMENT.includes(row.kind as Engagement)) {\n bucket[row.kind as Engagement] += n;\n } else {\n // A delivery row. `sent` is every task that reached a provider at all,\n // which is the denominator a bounce rate is measured against.\n bucket.sent += n;\n if (row.status === \"delivered\") bucket.delivered += n;\n if (row.status === \"failed\") bucket.failed += n;\n }\n byChannel.set(row.channel, bucket);\n }\n\n const total = blank();\n for (const bucket of byChannel.values()) {\n for (const key of Object.keys(total) as (keyof ReturnType<typeof blank>)[]) {\n total[key] += bucket[key];\n }\n }\n\n const pct = (n: number, d: number) => (d > 0 ? Number(((n / d) * 100).toFixed(2)) : null);\n\n // Engagement only exists where a provider reports it back. Saying \"0% open\n // rate\" for an SMS blast, or for email with no webhook wired, reads as a\n // failed campaign rather than an untracked one.\n const channels = [...byChannel.keys()];\n const engagementCapable = channels.some((c) => c === \"email\");\n const sawEngagement = ENGAGEMENT.some((k) => total[k] > 0);\n\n const warnings: string[] = [];\n if (!engagementCapable) {\n warnings.push(\n `Opens and clicks are not reported on ${channels.join(\", \")} — those figures are not tracked, not zero.`,\n );\n } else if (!sawEngagement && total.delivered > 0) {\n warnings.push(\n \"No engagement events recorded. If the provider webhook is not configured, opens and clicks cannot be tracked and will stay at zero.\",\n );\n }\n\n sendJson(res, 200, {\n campaign,\n totals: {\n ...total,\n deliveryRate: pct(total.delivered, total.sent),\n openRate: pct(total.opened, total.delivered),\n clickRate: pct(total.clicked, total.delivered),\n bounceRate: pct(total.bounced, total.sent),\n complaintRate: pct(total.complained, total.delivered),\n unsubscribeRate: pct(total.unsubscribed, total.delivered),\n },\n byChannel: Object.fromEntries(byChannel),\n engagementTracked: engagementCapable && sawEngagement,\n warnings,\n });\n }\n\n // ── GET /v1/suppressions — listSuppressions ───────────────────────────────\n async function listSuppressions(\n _req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const limitParam = parseInt(getQueryParam(ctx, \"limit\") || \"100\", 10);\n const limit = isNaN(limitParam) ? 100 : Math.min(Math.max(limitParam, 1), 500);\n const channel = getQueryParam(ctx, \"channel\");\n const reason = getQueryParam(ctx, \"reason\");\n const target = getQueryParam(ctx, \"target\");\n\n const conditions = [eq(suppressions.projectId, ctx.projectId!)];\n if (channel) conditions.push(eq(suppressions.channel, channel as any));\n if (reason) conditions.push(eq(suppressions.reason, reason));\n if (target) conditions.push(like(suppressions.target, `%${target.toLowerCase().trim()}%`));\n\n const rows = await deps.db\n .select()\n .from(suppressions)\n .where(and(...conditions))\n .orderBy(desc(suppressions.createdAt))\n .limit(limit);\n\n sendJson(res, 200, { suppressions: rows });\n }\n\n // ── POST /v1/suppressions — createSuppression ─────────────────────────────\n async function createSuppression(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const parsed = z\n .object({\n channel: ContactChannelSchema,\n target: z.string().min(1),\n reason: z.enum([\"unsubscribed\", \"complained\", \"bounced\", \"manual\"]).default(\"manual\"),\n })\n .safeParse(await readJsonBody(req));\n if (!parsed.success) return sendValidationError(res, parsed.error);\n\n const { channel, target, reason } = parsed.data;\n await deps.db\n .insert(suppressions)\n .values({\n projectId: ctx.projectId!,\n channel,\n target: normaliseTarget(target),\n reason,\n source: \"api\",\n })\n .onConflictDoNothing();\n\n sendJson(res, 201, { channel, target: normaliseTarget(target), reason });\n }\n\n // ── DELETE /v1/suppressions/:channel/:target — deleteSuppression ──────────\n //\n // Re-enables sending to an address. Intentionally manual: a suppression\n // records that somebody asked not to be contacted, and undoing that should be\n // a decision rather than a retry.\n async function deleteSuppression(\n _req: IncomingMessage,\n res: ServerResponse,\n ctx: RouteContext,\n ): Promise<void> {\n const { channel, target } = ctx.params;\n if (!channel || !target) return sendJson(res, 400, { error: \"missing_channel_or_target\" });\n\n await deps.db\n .delete(suppressions)\n .where(\n and(\n eq(suppressions.projectId, ctx.projectId!),\n eq(suppressions.channel, channel as any),\n eq(suppressions.target, normaliseTarget(decodeURIComponent(target))),\n ),\n );\n\n sendNoContent(res);\n }\n\n return {\n syncTemplates,\n addUser,\n updateUser,\n deleteUser,\n addContact,\n deleteContact,\n notify,\n cancelNotification,\n triggerWorkflow,\n ingestEvent,\n getEventsStream,\n getNotificationLogs,\n getNotificationStatus,\n getUser,\n getUserDetails,\n unsubscribePage,\n unsubscribe,\n listCampaigns,\n getCampaignStats,\n listSuppressions,\n createSuppression,\n deleteSuppression,\n getUserPreferences,\n updateUserPreferences,\n listTemplates,\n getTemplate,\n deleteTemplate,\n createWorkflow,\n listWorkflows,\n getWorkflow,\n cancelWorkflow,\n listUsers,\n getUserContacts,\n listSegments,\n listProjects,\n deleteProject,\n updateProject,\n createProjectKey,\n listProjectKeys,\n deleteProjectKey,\n getSystemHealth,\n getSystemMetrics,\n getDLQMessages,\n replayDLQMessage,\n deleteDLQMessage,\n getScheduledMessages,\n };\n}\n","import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { loadEnv, readBaseConfig } from \"@/config/index.js\";\nimport { createLogger } from \"@/logger/index.js\";\nimport { RedisClient } from \"@/redis/index.js\";\nimport { StreamProducer } from \"@/queue/index.js\";\nimport { createDatabase } from \"@/db/index.js\";\nimport {\n UserRepository,\n ContactRepository,\n TemplateRepository,\n ProjectRepository,\n WorkflowRepository,\n SegmentRepository,\n} from \"@/repositories/index.js\";\nimport { STREAMS } from \"@/contracts/index.js\";\nimport { readJsonBody, readRawBody, sendJson, HttpError } from \"./http.js\";\nimport { Router } from \"./router.js\";\nimport { createHandlers } from \"./handlers.js\";\nimport { projects, messageLogs, projectApiKeys, suppressions } from \"@/db/schema.js\";\nimport { eq, inArray } from \"drizzle-orm\";\nimport { randomBytes, timingSafeEqual, createHash } from \"node:crypto\";\nimport { LRUCache, normaliseTarget } from \"@/shared/index.js\";\nimport type { Redis } from \"@/redis/index.js\";\nimport { z } from \"zod\";\nimport { getMetricsRegistry } from \"@/metrics/index.js\";\n\n/** Pub/sub channel used to drop a cached API key across every API process. */\nexport const API_KEY_INVALIDATION_CHANNEL = \"apikey.invalidated\";\n\n// ─── Bootstrap ─────────────────────────────────────────────────────────────\n\nloadEnv();\nconst config = readBaseConfig();\n\nlet logger: ReturnType<typeof createLogger>;\nlet redis: RedisClient;\nlet sql: any;\nlet db: any;\nlet producers: Record<string, StreamProducer>;\nlet deps: any;\nlet h: any;\n// Short-TTL LRU so a hot key does not hit the DB on every request. Kept small\n// and invalidated on key change so a revoked key stops working promptly.\nconst AUTH_CACHE_TTL_MS = 60_000;\nlet authCache = new LRUCache<string, string>(1000, AUTH_CACHE_TTL_MS);\nlet authSubscriber: Redis | null = null;\nlet router: Router;\n\nexport function extractAuthToken(req: Pick<IncomingMessage, \"headers\">): string | undefined {\n const authHeader = req.headers[\"authorization\"];\n if (typeof authHeader === \"string\" && authHeader.toLowerCase().startsWith(\"bearer \")) {\n return authHeader.slice(7).trim();\n }\n const apiKeyHeader = req.headers[\"x-api-key\"];\n if (typeof apiKeyHeader === \"string\") {\n return apiKeyHeader.trim();\n }\n return undefined;\n}\n\nasync function handleCreateProject(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const parsed = z.object({ name: z.string().min(1) }).safeParse(await readJsonBody(req));\n if (!parsed.success) {\n sendJson(res, 400, { error: \"validation_error\", issues: parsed.error.issues });\n return;\n }\n const apiKey = `nk_live_${randomBytes(32).toString(\"hex\")}`;\n const apiKeyHash = createHash(\"sha256\").update(apiKey).digest(\"hex\");\n const rows = await db.insert(projects).values({ name: parsed.data.name }).returning();\n const projectId = rows[0]!.id;\n await db.insert(projectApiKeys).values({ projectId, keyHash: apiKeyHash, role: \"admin\" });\n sendJson(res, 201, { id: projectId, apiKey });\n}\n\nlet cachedHealth: { response: any; statusCode: number; expiresAt: number } | null = null;\n\nasync function handleHealth(_req: IncomingMessage, res: ServerResponse): Promise<void> {\n if (cachedHealth && cachedHealth.expiresAt > Date.now()) {\n sendJson(res, cachedHealth.statusCode, cachedHealth.response);\n return;\n }\n\n const [redisOk, dbOk] = await Promise.all([redis.healthCheck(), dbHealthCheck()]);\n\n const workers: Record<string, any> = {};\n let overallOk = redisOk && dbOk;\n\n if (redisOk) {\n const keys = [\"enricher\", \"engine\", \"scheduler\", \"delivery\", \"ai\", \"workflow\", \"events\"];\n try {\n const vals = await redis.native.mget(keys.map((k) => `notif:health:${k}`));\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i] as string;\n const val = vals[i];\n if (val) {\n const parsed = JSON.parse(val);\n workers[key] = parsed;\n if (parsed.redis === false || parsed.state === \"error\") {\n overallOk = false;\n }\n } else {\n workers[key] = { status: \"unknown\", message: \"No report received from worker\" };\n }\n }\n } catch (err) {\n for (const key of keys) {\n workers[key] = { status: \"error\", error: err instanceof Error ? err.message : String(err) };\n }\n }\n }\n\n const statusCode = overallOk ? 200 : 503;\n const response = {\n service: \"api\",\n status: overallOk ? \"ok\" : \"degraded\",\n redis: redisOk,\n database: dbOk,\n workers,\n };\n\n cachedHealth = {\n response,\n statusCode,\n expiresAt: Date.now() + 1000,\n };\n\n sendJson(res, statusCode, response);\n}\n\nasync function handleMetrics(_req: IncomingMessage, res: ServerResponse): Promise<void> {\n const registry = getMetricsRegistry();\n res.writeHead(200, { \"Content-Type\": registry.contentType });\n res.end(await registry.metrics());\n}\n\nasync function handleLive(_req: IncomingMessage, res: ServerResponse): Promise<void> {\n sendJson(res, 200, { status: \"ok\" });\n}\n\nasync function handleReady(_req: IncomingMessage, res: ServerResponse): Promise<void> {\n if (cachedHealth && cachedHealth.expiresAt > Date.now()) {\n sendJson(res, cachedHealth.statusCode === 200 ? 200 : 503, {\n status: cachedHealth.statusCode === 200 ? \"ready\" : \"unready\",\n });\n return;\n }\n const [redisOk, dbOk] = await Promise.all([redis.healthCheck(), dbHealthCheck()]);\n const isReady = redisOk && dbOk;\n sendJson(res, isReady ? 200 : 503, { status: isReady ? \"ready\" : \"unready\" });\n}\n\nasync function dbHealthCheck(): Promise<boolean> {\n try {\n await sql`SELECT 1`;\n return true;\n } catch {\n return false;\n }\n}\n\n// ─── HTTP server ────────────────────────────────────────────────────────────\n\nlet server: ReturnType<typeof createServer>;\n\nexport async function startApiServer() {\n logger = createLogger({ name: \"api\", level: config.LOG_LEVEL });\n redis = new RedisClient({ url: config.REDIS_URL, name: \"api\", logger });\n const dbData = createDatabase({ url: config.DATABASE_URL, applicationName: \"api\", logger });\n sql = dbData.sql;\n db = dbData.db;\n\n producers = {\n critical: new StreamProducer({ redis: redis.native, stream: STREAMS.INBOUND_CRITICAL, logger }),\n normal: new StreamProducer({ redis: redis.native, stream: STREAMS.INBOUND_NORMAL, logger }),\n low: new StreamProducer({ redis: redis.native, stream: STREAMS.INBOUND_LOW, logger }),\n workflow: new StreamProducer({ redis: redis.native, stream: STREAMS.WORKFLOW_INBOUND, logger }),\n events: new StreamProducer({ redis: redis.native, stream: STREAMS.EVENTS_INBOUND, logger }),\n };\n\n deps = {\n logger,\n redis,\n producers,\n userRepo: new UserRepository(db),\n contactRepo: new ContactRepository(db),\n templateRepo: new TemplateRepository(db),\n projectRepo: new ProjectRepository(db),\n workflowRepo: new WorkflowRepository(db),\n segmentRepo: new SegmentRepository(db),\n db,\n };\n h = createHandlers(deps);\n\n router = new Router();\n router\n .put(\"/v1/templates\", h.syncTemplates)\n .get(\"/v1/templates\", h.listTemplates)\n .get(\"/v1/templates/:id\", h.getTemplate)\n .delete(\"/v1/templates/:id\", h.deleteTemplate)\n .post(\"/v1/users\", h.addUser)\n .get(\"/v1/users\", h.listUsers)\n .get(\"/v1/users/:id\", h.getUser)\n .get(\"/v1/users/:id/details\", h.getUserDetails)\n .patch(\"/v1/users/:id\", h.updateUser)\n .delete(\"/v1/users/:id\", h.deleteUser)\n .post(\"/v1/users/:id/contacts\", h.addContact)\n .get(\"/v1/users/:id/contacts\", h.getUserContacts)\n .delete(\"/v1/users/:id/contacts/:channel/:target\", h.deleteContact)\n .get(\"/v1/users/:id/preferences\", h.getUserPreferences)\n .patch(\"/v1/users/:id/preferences\", h.updateUserPreferences)\n .post(\"/v1/notify\", h.notify)\n .get(\"/v1/notifications/scheduled\", h.getScheduledMessages)\n .get(\"/v1/notifications/logs\", h.getNotificationLogs)\n .get(\"/v1/notifications/:taskId\", h.getNotificationStatus)\n .delete(\"/v1/notifications/:taskId\", h.cancelNotification)\n .get(\"/v1/unsubscribe\", h.unsubscribePage)\n .post(\"/v1/unsubscribe\", h.unsubscribe)\n .get(\"/v1/campaigns\", h.listCampaigns)\n .get(\"/v1/campaigns/:campaign/stats\", h.getCampaignStats)\n .get(\"/v1/suppressions\", h.listSuppressions)\n .post(\"/v1/suppressions\", h.createSuppression)\n .delete(\"/v1/suppressions/:channel/:target\", h.deleteSuppression)\n .get(\"/v1/system/health\", h.getSystemHealth)\n .get(\"/v1/system/metrics\", h.getSystemMetrics)\n .get(\"/v1/dlq\", h.getDLQMessages)\n .post(\"/v1/dlq/replay\", h.replayDLQMessage)\n .delete(\"/v1/dlq/:id\", h.deleteDLQMessage)\n .post(\"/v1/workflows\", h.createWorkflow)\n .get(\"/v1/workflows\", h.listWorkflows)\n .get(\"/v1/workflows/instances/:id\", h.getWorkflow)\n .delete(\"/v1/workflows/instances/:id\", h.cancelWorkflow)\n .post(\"/v1/workflows/trigger\", h.triggerWorkflow)\n .get(\"/v1/segments\", h.listSegments)\n .post(\"/v1/events\", h.ingestEvent)\n .get(\"/v1/events/stream\", h.getEventsStream)\n .get(\"/v1/projects\", h.listProjects)\n .post(\"/v1/projects\", handleCreateProject)\n .delete(\"/v1/projects/:id\", h.deleteProject)\n .patch(\"/v1/projects/:id\", h.updateProject)\n .post(\"/v1/projects/:id/keys\", h.createProjectKey)\n .get(\"/v1/projects/:id/keys\", h.listProjectKeys)\n .delete(\"/v1/projects/:id/keys/:keyId\", h.deleteProjectKey)\n .get(\"/health\", handleHealth)\n .get(\"/metrics\", handleMetrics)\n .get(\"/live\", handleLive)\n .get(\"/ready\", handleReady);\n\n server = createServer((req, res) => {\n void handleRequest(req, res);\n });\n server.requestTimeout = 30_000;\n server.headersTimeout = 10_000;\n server.keepAliveTimeout = 5_000;\n\n async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n // Add CORS headers for browser clients (like the Next.js dashboard)\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, PUT, PATCH, DELETE, OPTIONS\");\n res.setHeader(\n \"Access-Control-Allow-Headers\",\n \"Content-Type, Authorization, x-api-key, x-project-id\",\n );\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host ?? \"localhost\"}`);\n\n let projectId: string | undefined = undefined;\n let projectRateLimitRpm = 600;\n let keyRole: \"admin\" | \"read_only\" = \"admin\";\n\n const isProjectManagement =\n url.pathname === \"/v1/projects\" || url.pathname.startsWith(\"/v1/projects/\");\n\n // Unsubscribe is reached from a mail client, which has no API key and never\n // will. The signed token in the URL is the credential, and it authorises\n // exactly one action for one address.\n const isPublicUnsubscribe = url.pathname === \"/v1/unsubscribe\";\n\n if (url.pathname.startsWith(\"/v1/\") && !isProjectManagement && !isPublicUnsubscribe) {\n let token = extractAuthToken(req);\n if (!token && url.searchParams.has(\"token\")) {\n token = url.searchParams.get(\"token\") || undefined;\n }\n\n if (!token) {\n sendJson(res, 401, { error: \"unauthorized\", message: \"Invalid or missing API key\" });\n return;\n }\n\n let isAdminToken = false;\n if (config.ADMIN_API_KEY) {\n const expectedBuffer = Buffer.from(config.ADMIN_API_KEY);\n const providedBuffer = Buffer.from(token);\n if (\n expectedBuffer.length === providedBuffer.length &&\n timingSafeEqual(expectedBuffer, providedBuffer)\n ) {\n isAdminToken = true;\n }\n }\n\n if (isAdminToken) {\n const headerProjectId =\n (req.headers[\"x-project-id\"] as string | undefined) ||\n url.searchParams.get(\"projectId\") ||\n undefined;\n if (!headerProjectId) {\n sendJson(res, 400, {\n error: \"bad_request\",\n message: \"x-project-id header or projectId query param required when using admin token\",\n });\n return;\n }\n projectId = headerProjectId;\n projectRateLimitRpm = 6000;\n keyRole = \"admin\";\n } else {\n const tokenHash = createHash(\"sha256\").update(token).digest(\"hex\");\n const cached = authCache.get(tokenHash);\n if (cached) {\n const parts = cached.split(\":\");\n projectId = parts[0];\n projectRateLimitRpm = parseInt(parts[1] || \"600\", 10);\n keyRole = parts[2] as \"admin\" | \"read_only\";\n } else {\n const rows = await db\n .select({\n id: projects.id,\n rateLimitRpm: projects.rateLimitRpm,\n role: projectApiKeys.role,\n })\n .from(projectApiKeys)\n .innerJoin(projects, eq(projects.id, projectApiKeys.projectId))\n .where(eq(projectApiKeys.keyHash, tokenHash))\n .limit(1);\n if (!rows.length) {\n sendJson(res, 401, { error: \"unauthorized\", message: \"Invalid or missing API key\" });\n return;\n }\n projectId = rows[0]!.id;\n projectRateLimitRpm = rows[0]!.rateLimitRpm ?? 600;\n keyRole = rows[0]!.role;\n authCache.set(tokenHash, `${projectId}:${projectRateLimitRpm}:${keyRole}`);\n }\n }\n\n // Role Check\n if (keyRole === \"read_only\") {\n const isMutating = req.method !== \"GET\" && req.method !== \"OPTIONS\";\n if (isMutating) {\n sendJson(res, 403, { error: \"forbidden\", message: \"API key is read-only\" });\n return;\n }\n }\n\n // Project rate limit - Sliding Window\n const LUA_LIMIT = `\n local key = KEYS[1]\n local now = tonumber(ARGV[1])\n local window = tonumber(ARGV[2])\n local maxReqs = tonumber(ARGV[3])\n local cutoff = now - window\n redis.call(\"ZREMRANGEBYSCORE\", key, \"-inf\", cutoff)\n local count = redis.call(\"ZCARD\", key)\n if count < maxReqs then\n redis.call(\"ZADD\", key, now, now .. \"-\" .. ARGV[4])\n redis.call(\"EXPIRE\", key, math.ceil(window / 1000))\n return count + 1\n end\n return -1\n `;\n const rlKey = `rate-limit:api:req:${projectId}`;\n const nowMs = Date.now();\n const count = (await redis.native.eval(\n LUA_LIMIT,\n 1,\n rlKey,\n nowMs,\n 60000,\n projectRateLimitRpm,\n randomBytes(4).toString(\"hex\"),\n )) as number;\n if (count === -1) {\n if (!res.headersSent) {\n res.setHeader(\"Retry-After\", \"60\");\n sendJson(res, 429, {\n error: \"too_many_requests\",\n message: `Project rate limit exceeded (max ${projectRateLimitRpm} req/min)`,\n });\n }\n return;\n }\n } else if (isProjectManagement) {\n const token = extractAuthToken(req);\n\n if (!config.ADMIN_API_KEY) {\n sendJson(res, 403, {\n error: \"forbidden\",\n message: \"Project management disabled (no ADMIN_API_KEY set)\",\n });\n return;\n }\n if (!token) {\n sendJson(res, 401, { error: \"unauthorized\", message: \"Missing admin token\" });\n return;\n }\n\n const expectedBuffer = Buffer.from(config.ADMIN_API_KEY);\n const providedBuffer = Buffer.from(token);\n if (\n expectedBuffer.length !== providedBuffer.length ||\n !timingSafeEqual(expectedBuffer, providedBuffer)\n ) {\n sendJson(res, 401, { error: \"unauthorized\", message: \"Invalid admin token\" });\n return;\n }\n }\n\n // The unsubscribe routes skip the block above, and with it the per-project\n // rate limit — they are public by necessity. Cap them per client instead so\n // an open endpoint cannot be used to hammer the process. Generous, because\n // a shared corporate egress IP can legitimately produce a burst.\n if (isPublicUnsubscribe) {\n const clientIp =\n (req.headers[\"x-forwarded-for\"] as string | undefined)?.split(\",\")[0]?.trim() ||\n req.socket.remoteAddress ||\n \"unknown\";\n try {\n const key = `rate-limit:api:unsub:${clientIp}`;\n const count = await redis.native.incr(key);\n if (count === 1) await redis.native.expire(key, 60);\n if (count > 60) {\n res.setHeader(\"Retry-After\", \"60\");\n sendJson(res, 429, { error: \"too_many_requests\" });\n return;\n }\n } catch (err) {\n // Redis being down must not stop someone unsubscribing — failing open\n // here is the lesser evil against a recipient who cannot opt out.\n logger.warn({ err }, \"unsubscribe rate limit unavailable — allowing request\");\n }\n }\n\n const route = router.match(req.method ?? \"GET\", url.pathname);\n\n if (!route) {\n sendJson(res, 404, { error: \"not_found\" });\n return;\n }\n\n const ctx = { params: route.params, query: url.searchParams, projectId, role: keyRole };\n\n void Promise.resolve(route.handler(req, res, ctx)).catch((err: unknown) => {\n if (err instanceof HttpError) {\n if (!res.headersSent) sendJson(res, err.status, { error: err.code, message: err.message });\n return;\n }\n logger.error({ err, path: url.pathname }, \"unhandled request error\");\n if (!res.headersSent) sendJson(res, 500, { error: \"internal_error\" });\n });\n }\n\n // Let key rotation take effect immediately instead of waiting out the TTL.\n authSubscriber = redis.native.duplicate();\n await authSubscriber.subscribe(API_KEY_INVALIDATION_CHANNEL);\n authSubscriber.on(\"message\", (channel: string, tokenHash: string) => {\n if (channel !== API_KEY_INVALIDATION_CHANNEL) return;\n if (tokenHash === \"*\") authCache.clear();\n else authCache.delete(tokenHash);\n logger.info({ tokenHash }, \"api key cache invalidated\");\n });\n\n const { transportRegistry } = await import(\"../../transport/index.js\");\n for (const channel of transportRegistry.registeredChannels()) {\n const transport = transportRegistry.get(channel as any);\n if (!transport?.webhookPath) continue;\n\n if (transport.verifyWebhookChallenge) {\n router.get(transport.webhookPath, async (_req, res, ctx) => {\n try {\n const challenge = await transport.verifyWebhookChallenge!(ctx.query);\n if (challenge === undefined) {\n res.writeHead(403, { \"Content-Type\": \"text/plain\" });\n res.end(\"Forbidden\");\n return;\n }\n res.writeHead(200, { \"Content-Type\": \"text/plain\" });\n res.end(challenge);\n } catch (err) {\n logger.error({ err, channel }, \"webhook verification challenge error\");\n if (!res.headersSent) {\n res.writeHead(500, { \"Content-Type\": \"text/plain\" });\n res.end(\"internal_error\");\n }\n }\n });\n logger.info(`Mounted webhook verification GET for ${channel} at ${transport.webhookPath}`);\n }\n\n if (transport.parseWebhook) {\n router.post(transport.webhookPath, async (req, res) => {\n try {\n await Promise.race([\n (async () => {\n const rawBody = await readRawBody(req);\n if (!transport.verifyWebhook) {\n if (!res.headersSent)\n sendJson(res, 501, {\n error: \"not_implemented\",\n message: \"Webhook signature verification is not implemented for this provider\",\n });\n return;\n }\n\n const isValid = await transport.verifyWebhook(rawBody, req.headers);\n if (!isValid) {\n if (!res.headersSent)\n sendJson(res, 401, {\n error: \"unauthorized\",\n message: \"Invalid webhook signature\",\n });\n return;\n }\n let body;\n try {\n body = rawBody ? JSON.parse(rawBody) : {};\n } catch {\n body = {};\n }\n const events = await transport.parseWebhook!(body, rawBody, req.headers);\n\n if (events.length > 0) {\n const providerIds = events\n .map((e) => e.providerMessageId)\n .filter(Boolean) as string[];\n const logsToProject = new Map<string, string>();\n const logsToTask = new Map<string, string>();\n const logsToCampaign = new Map<string, string | null>();\n if (providerIds.length > 0) {\n const existing = await db\n .select({\n providerMessageId: messageLogs.providerMessageId,\n projectId: messageLogs.projectId,\n taskId: messageLogs.taskId,\n campaignId: messageLogs.campaignId,\n })\n .from(messageLogs)\n .where(inArray(messageLogs.providerMessageId, providerIds));\n for (const row of existing) {\n if (row.providerMessageId) {\n logsToProject.set(row.providerMessageId, row.projectId);\n logsToTask.set(row.providerMessageId, row.taskId);\n // Inherit the campaign from the delivery this event is\n // about, or an open never counts toward the send that\n // earned it.\n logsToCampaign.set(row.providerMessageId, row.campaignId);\n }\n }\n }\n\n // Only record events we can attribute to a real message. An\n // unresolvable providerMessageId used to be written against a\n // zero-UUID project, which is unjoinable and leaks across tenants.\n const attributable = events.filter(\n (e) => e.providerMessageId && logsToProject.has(e.providerMessageId),\n );\n\n const rows = attributable.map((e) => ({\n projectId: logsToProject.get(e.providerMessageId)!,\n taskId: logsToTask.get(e.providerMessageId)!,\n providerMessageId: e.providerMessageId,\n channel: transport.channel,\n // Engagement events are not delivery attempts.\n attempt: 0,\n kind: e.status,\n status: e.status,\n campaignId: logsToCampaign.get(e.providerMessageId) ?? null,\n metadata: e.metadata ?? null,\n }));\n\n const skipped = events.length - rows.length;\n if (skipped > 0) {\n logger.warn(\n { channel, skipped },\n \"webhook events skipped: unknown providerMessageId\",\n );\n }\n\n if (rows.length > 0) {\n // Providers retry on any non-2xx, so redelivery of an event we have\n // already recorded must be a no-op rather than a constraint error.\n await db.insert(messageLogs).values(rows).onConflictDoNothing();\n }\n\n // An unsubscribe, a spam complaint, or a permanently dead\n // address must stop future sends — logging it is not enough.\n // Soft bounces are excluded: the address is still good.\n const suppressionRows = attributable\n .filter(\n (e) =>\n e.recipient &&\n (e.status === \"unsubscribed\" ||\n e.status === \"complained\" ||\n (e.status === \"bounced\" && e.bounceType === \"hard\")),\n )\n .map((e) => ({\n projectId: logsToProject.get(e.providerMessageId)!,\n channel: transport.channel,\n target: normaliseTarget(e.recipient!),\n reason: e.status,\n source: channel,\n taskId: logsToTask.get(e.providerMessageId)!,\n }));\n\n if (suppressionRows.length > 0) {\n // First reason wins: a later bounce should not overwrite the\n // record that the person actively unsubscribed.\n await db.insert(suppressions).values(suppressionRows).onConflictDoNothing();\n logger.info(\n { channel, count: suppressionRows.length },\n \"addresses suppressed from provider webhook\",\n );\n }\n\n const unsuppressable = attributable.filter(\n (e) =>\n !e.recipient &&\n (e.status === \"unsubscribed\" ||\n e.status === \"complained\" ||\n e.status === \"bounced\"),\n ).length;\n if (unsuppressable > 0) {\n // Worth shouting about: the event arrived, we logged it, and\n // we still cannot stop mailing the person.\n logger.warn(\n { channel, count: unsuppressable },\n \"suppression events carried no recipient address — transport must set WebhookEvent.recipient\",\n );\n }\n }\n if (!res.headersSent) sendJson(res, 200, { success: true });\n })(),\n new Promise((_, reject) =>\n setTimeout(() => reject(new Error(\"Webhook processing timeout\")), 15000),\n ),\n ]);\n } catch (err: any) {\n logger.error({ err, channel }, \"webhook processing error or timeout\");\n if (!res.headersSent) {\n sendJson(res, 500, { error: \"internal_error\", message: err.message });\n }\n }\n });\n logger.info(`Mounted webhook for ${channel} at ${transport.webhookPath}`);\n }\n }\n\n server.listen(config.PORT, config.HOST, () => {\n logger.info(\n { port: config.PORT, host: config.HOST, env: config.NODE_ENV },\n \"api server listening\",\n );\n });\n}\n\n// ─── Shutdown ──────────────────────────────────────────────────────────────\n\nexport async function stopApiServer(): Promise<void> {\n logger?.info(\"api shutdown initiated\");\n if (server) {\n await new Promise<void>((resolve, reject) => {\n server.close((err) => (err ? reject(err) : resolve()));\n });\n server.closeIdleConnections?.();\n server = null as any;\n }\n if (authSubscriber) {\n authSubscriber.disconnect();\n authSubscriber = null;\n }\n // These are module-scoped, so a restart in the same process must not inherit\n // the previous instance's cached auth or health.\n authCache = new LRUCache<string, string>(1000, AUTH_CACHE_TTL_MS);\n cachedHealth = null;\n if (sql) await sql.end();\n if (redis) await redis.disconnect();\n logger?.info(\"api stopped\");\n}\n"],"mappings":"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"}
|