Files
hope-events/backend/src/utils/icsUtils.js
T
joshuaandClaude Opus 5.5 c79b74bc67 Add multi-day event sessions (backend)
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>
2026-10-22 14:20:52 +02:00

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 };