Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/utils/privacy/privacyNotificationQueue.server.ts
T

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(() => {});
}
}
}