89 lines
2.7 KiB
TypeScript
89 lines
2.7 KiB
TypeScript
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<string, unknown>;
|
|
};
|
|
|
|
export type EnqueuePrivacyNotificationResult = { enqueued: true; jobId?: string } | { enqueued: false; reason: "queue-unavailable" };
|
|
|
|
type Dependencies = {
|
|
env?: RedisConnectionEnv;
|
|
queue?: TypedQueue<typeof queueNames.notifications>;
|
|
};
|
|
|
|
export async function enqueuePrivacyNotification(
|
|
input: EnqueuePrivacyNotificationInput,
|
|
dependencies: Dependencies = {}
|
|
): Promise<EnqueuePrivacyNotificationResult> {
|
|
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(() => {});
|
|
}
|
|
}
|
|
}
|