teqo/src/utilities/notification/createCampaignNotification.ts
Cursor Agent fb75f7235b
fix(P5-P1): schedule campaign notification push after txn commit
queueMicrotask under req.transactionID raced withPayloadTransaction
commit. Register after-commit callbacks, soft-fail missing notification
rows on send, and pin the schedule with a convention unit spec.
2026-08-03 04:40:38 +00:00

87 lines
2.4 KiB
TypeScript

import 'server-only'
/** Trusted server writes in this module use admin bypass (`overrideAccess: true`). */
import type { NotificationPayload, NotificationType } from '@/lib/notificationContract'
import type { Notification } from '@/payload-types'
import type { Payload, PayloadRequest } from 'payload'
import { sendCampaignPushForNotification } from '@/utilities/notification/sendCampaignPush'
import { onPayloadTransactionCommit } from '@/utilities/payloadTransaction'
export type NotificationWriteRequest = Pick<PayloadRequest, 'transactionID'>
type CreateCampaignNotificationInput = {
recipientID: number
type: NotificationType
payload: NotificationPayload
municipalityID?: number | null
}
const scheduleCampaignNotificationPush = (
payload: Payload,
notificationID: number,
req?: NotificationWriteRequest,
): void => {
const run = (): void => {
void sendCampaignPushForNotification(payload, notificationID)
}
// Pass 5 P1: never `queueMicrotask` under an open transaction — microtasks
// flush before `withPayloadTransaction` reaches `commitTransaction`.
const transactionID = req?.transactionID
if (typeof transactionID === 'string' || typeof transactionID === 'number') {
onPayloadTransactionCommit(transactionID, run)
return
}
queueMicrotask(run)
}
const createCampaignNotification = async (
payload: Payload,
input: CreateCampaignNotificationInput,
req?: NotificationWriteRequest,
): Promise<Notification> => {
const notification = await payload.create({
collection: 'notification',
data: {
recipient: input.recipientID,
type: input.type,
payload: input.payload,
municipality: input.municipalityID ?? undefined,
},
depth: 0,
overrideAccess: true,
req,
})
scheduleCampaignNotificationPush(payload, notification.id, req)
return notification
}
export const createCampaignNotifications = async (
payload: Payload,
recipientIDs: number[],
input: Omit<CreateCampaignNotificationInput, 'recipientID'>,
req?: NotificationWriteRequest,
): Promise<void> => {
const uniqueRecipients = [...new Set(recipientIDs)]
if (uniqueRecipients.length === 0) return
await Promise.all(
uniqueRecipients.map((recipientID) =>
createCampaignNotification(
payload,
{
recipientID,
type: input.type,
payload: input.payload,
municipalityID: input.municipalityID,
},
req,
),
),
)
}