Events can run over several sessions: per-session check-in with a whole-day or session-time window, late joining with pro-rata or fixed late pricing, an attendance register (Excel/PDF), session reminders, and completion certificates (four templates, public verify page). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
57 lines
2.1 KiB
JavaScript
57 lines
2.1 KiB
JavaScript
const { createEvent, createEvents } = require('ics');
|
|
|
|
// Event.startDate/endDate are stored as true UTC instants (the admin event form's
|
|
// datetime-local input is parsed in the browser's local time before being sent as an
|
|
// ISO string), so serializing them as UTC here requires no timezone math and lets every
|
|
// viewer's calendar app localize correctly to *their own* timezone.
|
|
function toUtcArray(date) {
|
|
const d = new Date(date);
|
|
return [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes()];
|
|
}
|
|
|
|
function icsEntry(event, eventUrl, { start, end, title, location }) {
|
|
return {
|
|
title,
|
|
start: toUtcArray(start),
|
|
end: toUtcArray(end),
|
|
startInputType: 'utc',
|
|
endInputType: 'utc',
|
|
startOutputType: 'utc',
|
|
endOutputType: 'utc',
|
|
location: location || event.location || undefined,
|
|
description: event.description || undefined,
|
|
url: eventUrl,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Build an RFC 5545 .ics file (as a string) for an event. A multi-session event gets one
|
|
* calendar entry per session that isn't cancelled (not a repeat rule, so moved or skipped
|
|
* sessions are exact).
|
|
*
|
|
* @param {object} event - Prisma Event row: title, description?, startDate, endDate, location?,
|
|
* scheduleType?, sessions?[]
|
|
* @param {string} eventUrl - absolute URL to the event's public page
|
|
* @returns {string}
|
|
*/
|
|
function buildEventIcs(event, eventUrl) {
|
|
const sessions = event.scheduleType === 'sessions'
|
|
? (event.sessions || []).filter(s => !s.cancelledAt).sort((a, b) => new Date(a.startsAt) - new Date(b.startsAt))
|
|
: [];
|
|
if (sessions.length > 0) {
|
|
const { error, value } = createEvents(sessions.map((s, i) => icsEntry(event, eventUrl, {
|
|
start: s.startsAt,
|
|
end: s.endsAt,
|
|
title: `${event.title} — ${s.label || `Session ${i + 1}`}`,
|
|
location: s.location,
|
|
})));
|
|
if (error) throw error;
|
|
return value;
|
|
}
|
|
const { error, value } = createEvent(icsEntry(event, eventUrl, { start: event.startDate, end: event.endDate, title: event.title }));
|
|
if (error) throw error;
|
|
return value;
|
|
}
|
|
|
|
module.exports = { buildEventIcs };
|