159 lines
4.2 KiB
TypeScript
159 lines
4.2 KiB
TypeScript
/**
|
|
* event-reminders — the hourly cron task that sends reminder e-mails to
|
|
* confirmed registrations whose event starts within the configured lead time.
|
|
*
|
|
* Semantics are at-least-once: `reminderSentAt` is written ONLY after the
|
|
* e-mail sent successfully, so a failure on one run is retried on the next.
|
|
* One failing registration never stops the batch — every registration is
|
|
* wrapped in its own try/catch.
|
|
*
|
|
* Registered from src/index.ts only when `server.cron.enabled` is true.
|
|
*/
|
|
|
|
import type { Core } from '@strapi/strapi';
|
|
import { sendEventReminderMail } from './event-mail';
|
|
|
|
const REGISTRATION_UID = 'api::event-registration.event-registration';
|
|
const REMINDER_SETTINGS_UID = 'api::reminder-settings.reminder-settings';
|
|
|
|
const LOG_PREFIX = '[event-reminders]';
|
|
|
|
const DEFAULT_LEAD_HOURS = 48;
|
|
const DEFAULT_REMINDER_ENABLED = true;
|
|
|
|
const PAGE_SIZE = 50;
|
|
|
|
type ReminderSettings = {
|
|
enabled: boolean;
|
|
leadHours: number;
|
|
};
|
|
|
|
/**
|
|
* Read the dashboard-controlled settings, falling back to the seed defaults
|
|
* when the single type is missing or its values are empty.
|
|
*/
|
|
async function readReminderSettings(
|
|
strapi: Core.Strapi,
|
|
): Promise<ReminderSettings> {
|
|
try {
|
|
const settings = await strapi
|
|
.documents(REMINDER_SETTINGS_UID)
|
|
.findFirst({});
|
|
|
|
if (!settings) {
|
|
return { enabled: DEFAULT_REMINDER_ENABLED, leadHours: DEFAULT_LEAD_HOURS };
|
|
}
|
|
|
|
const leadHours =
|
|
typeof settings.leadHours === 'number' && settings.leadHours >= 0
|
|
? settings.leadHours
|
|
: DEFAULT_LEAD_HOURS;
|
|
|
|
return {
|
|
enabled: settings.enabled !== false,
|
|
leadHours,
|
|
};
|
|
} catch (error) {
|
|
strapi.log.warn(
|
|
`${LOG_PREFIX} could not read reminder settings, using defaults: ${String(error)}`,
|
|
);
|
|
return { enabled: DEFAULT_REMINDER_ENABLED, leadHours: DEFAULT_LEAD_HOURS };
|
|
}
|
|
}
|
|
|
|
export async function runEventReminders({
|
|
strapi,
|
|
}: {
|
|
strapi: Core.Strapi;
|
|
}): Promise<void> {
|
|
const settings = await readReminderSettings(strapi);
|
|
|
|
if (!settings.enabled) {
|
|
strapi.log.debug(`${LOG_PREFIX} disabled in reminder settings, skipping.`);
|
|
return;
|
|
}
|
|
|
|
const now = new Date();
|
|
const horizon = new Date(now.getTime() + settings.leadHours * 3_600_000);
|
|
|
|
strapi.log.info(
|
|
`${LOG_PREFIX} running for events starting between ${now.toISOString()} and ${horizon.toISOString()}.`,
|
|
);
|
|
|
|
let page = 1;
|
|
let sent = 0;
|
|
let failed = 0;
|
|
|
|
while (true) {
|
|
const registrations = await strapi
|
|
.documents(REGISTRATION_UID)
|
|
.findMany({
|
|
filters: {
|
|
registrationState: { $eq: 'confirmed' },
|
|
reminderSentAt: { $null: true },
|
|
event: {
|
|
startDateTime: {
|
|
$gte: now.toISOString(),
|
|
$lte: horizon.toISOString(),
|
|
},
|
|
},
|
|
},
|
|
populate: { event: true },
|
|
pagination: { page, pageSize: PAGE_SIZE },
|
|
});
|
|
|
|
for (const registration of registrations) {
|
|
try {
|
|
const event = registration.event as
|
|
| { documentId?: string; id?: number }
|
|
| null
|
|
| undefined;
|
|
|
|
const reference = event?.documentId
|
|
? { documentId: event.documentId }
|
|
: event?.id !== undefined
|
|
? { id: event.id }
|
|
: undefined;
|
|
|
|
if (!event || !reference) {
|
|
strapi.log.warn(
|
|
`${LOG_PREFIX} registration ${registration.documentId} has no event, skipping.`,
|
|
);
|
|
continue;
|
|
}
|
|
|
|
const ok = await sendEventReminderMail({
|
|
strapi,
|
|
to: registration.email as string,
|
|
eventReference: reference,
|
|
});
|
|
|
|
if (!ok) {
|
|
failed += 1;
|
|
continue;
|
|
}
|
|
|
|
await strapi
|
|
.documents(REGISTRATION_UID)
|
|
.update({
|
|
documentId: registration.documentId as string,
|
|
data: { reminderSentAt: new Date().toISOString() },
|
|
});
|
|
|
|
sent += 1;
|
|
} catch (error) {
|
|
failed += 1;
|
|
strapi.log.error(
|
|
`${LOG_PREFIX} failed for registration ${registration.documentId}: ${String(error)}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
if (registrations.length < PAGE_SIZE) break;
|
|
page += 1;
|
|
}
|
|
|
|
strapi.log.info(
|
|
`${LOG_PREFIX} finished: ${sent} reminder(s) sent, ${failed} failed.`,
|
|
);
|
|
} |