import { buildNotificationJob, enqueueJob, queueNames, resolveRedisConnectionOptions, resolveRedisConnectionOptionsFromProcessEnv, type RedisConnectionEnv, type TypedQueue, } from "@hivegrid/queue"; /** * AK-P21 (AGENTS.md DoD C15) — privacy notices go through the M011 * notification queue instead of calling a mail provider from the route. The * notification handler owns preference evaluation, the NotificationDelivery * record and the provider call. * * These notices ride the locked "critical" category on purpose: a GDPR * deletion confirmation carries a one-time link with a hard deadline and must * not be suppressible by a notification preference. */ export const PRIVACY_NOTIFICATION_CATEGORY = "critical"; export type EnqueuePrivacyNotificationInput = { tenantId: string; userId: string; email: string; messageKey: string; sourceKey: string; subject: string; text: string; metadata?: Record; }; export type EnqueuePrivacyNotificationResult = { enqueued: true; jobId?: string } | { enqueued: false; reason: "queue-unavailable" }; type Dependencies = { env?: RedisConnectionEnv; queue?: TypedQueue; }; export async function enqueuePrivacyNotification( input: EnqueuePrivacyNotificationInput, dependencies: Dependencies = {} ): Promise { let queue = dependencies.queue; let shouldCloseQueue = false; const connection = queue ? undefined : dependencies.env ? resolveRedisConnectionOptions({ env: dependencies.env }) : resolveRedisConnectionOptionsFromProcessEnv(); if (!queue && !connection) { return { enqueued: false, reason: "queue-unavailable" }; } if (!queue && connection) { const queueRuntimeModule = "@hivegrid/queue/runtime"; const { createQueue } = (await import(/* @vite-ignore */ queueRuntimeModule)) as typeof import("@hivegrid/queue/runtime"); queue = createQueue(queueNames.notifications, { connection }); shouldCloseQueue = true; } if (!queue) { return { enqueued: false, reason: "queue-unavailable" }; } try { const descriptor = buildNotificationJob({ tenantId: input.tenantId, userId: input.userId, email: input.email, channel: "email", category: PRIVACY_NOTIFICATION_CATEGORY, messageKey: input.messageKey, sourceKey: input.sourceKey, subject: input.subject, text: input.text, ...(input.metadata ? { metadata: input.metadata } : {}), }); const job = await enqueueJob(queue, descriptor, { tenantId: input.tenantId }); return { enqueued: true, jobId: typeof job.id === "string" ? job.id : undefined }; } finally { if (shouldCloseQueue && queue) { await queue.close().catch(() => {}); } } }