feat(caldav): enhance eventToICS function to include join links and organizer details
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m24s

This commit is contained in:
2026-03-03 12:13:36 +01:00
parent ddc0c684ec
commit f3ef490012

View File

@@ -89,7 +89,12 @@ function getICSProp(ics, key) {
return v.trim(); return v.trim();
} }
function eventToICS(event) { function eventToICS(event, base, user) {
// Determine the most useful join URL
const joinUrl = event.federated_join_url
|| (event.room_uid ? `${base}/join/${event.room_uid}` : null);
const roomUrl = event.room_uid ? `${base}/rooms/${event.room_uid}` : null;
const lines = [ const lines = [
'BEGIN:VCALENDAR', 'BEGIN:VCALENDAR',
'VERSION:2.0', 'VERSION:2.0',
@@ -103,14 +108,40 @@ function eventToICS(event) {
`DTSTAMP:${toICSDate(event.updated_at || event.created_at)}`, `DTSTAMP:${toICSDate(event.updated_at || event.created_at)}`,
`LAST-MODIFIED:${toICSDate(event.updated_at || event.created_at)}`, `LAST-MODIFIED:${toICSDate(event.updated_at || event.created_at)}`,
]; ];
if (event.description) {
lines.push(`DESCRIPTION:${escapeICS(event.description)}`); // LOCATION: show join link so calendar apps display "where" the meeting is
if (joinUrl) {
lines.push(`LOCATION:${escapeICS(joinUrl)}`);
lines.push(`URL:${joinUrl}`);
} else if (roomUrl) {
lines.push(`LOCATION:${escapeICS(roomUrl)}`);
lines.push(`URL:${roomUrl}`);
} }
// DESCRIPTION: combine user description + join link hint
const descParts = [];
if (event.description) descParts.push(event.description);
if (joinUrl) {
descParts.push(`Join meeting: ${joinUrl}`);
}
if (roomUrl && roomUrl !== joinUrl) {
descParts.push(`Room page: ${roomUrl}`);
}
if (descParts.length > 0) {
lines.push(`DESCRIPTION:${escapeICS(descParts.join('\n'))}`);
}
// ORGANIZER
if (user) {
const cn = user.display_name || user.name || user.email;
lines.push(`ORGANIZER;CN=${escapeICS(cn)}:mailto:${user.email}`);
}
if (event.room_uid) { if (event.room_uid) {
lines.push(`X-REDLIGHT-ROOM-UID:${event.room_uid}`); lines.push(`X-REDLIGHT-ROOM-UID:${event.room_uid}`);
} }
if (event.federated_join_url) { if (joinUrl) {
lines.push(`X-REDLIGHT-JOIN-URL:${escapeICS(event.federated_join_url)}`); lines.push(`X-REDLIGHT-JOIN-URL:${escapeICS(joinUrl)}`);
} }
lines.push('END:VEVENT', 'END:VCALENDAR'); lines.push('END:VEVENT', 'END:VCALENDAR');
return lines.map(foldICSLine).join('\r\n'); return lines.map(foldICSLine).join('\r\n');
@@ -340,7 +371,7 @@ router.all('/:username/calendar/', caldavAuth, async (req, res) => {
responses.push( responses.push(
propResponse(`${calendarHref}${ev.uid}.ics`, { propResponse(`${calendarHref}${ev.uid}.ics`, {
'd:getetag': escapeXml(etag(ev)), 'd:getetag': escapeXml(etag(ev)),
'c:calendar-data': escapeXml(ics), 'c:calendar-data': escapeXml(eventToICS(ev, baseUrl(req), req.caldavUser)),
}), }),
); );
} }
@@ -368,7 +399,7 @@ router.all('/:username/calendar/', caldavAuth, async (req, res) => {
const responses = events.map(ev => const responses = events.map(ev =>
propResponse(`${calendarHref}${ev.uid}.ics`, { propResponse(`${calendarHref}${ev.uid}.ics`, {
'd:getetag': escapeXml(etag(ev)), 'd:getetag': escapeXml(etag(ev)),
'c:calendar-data': escapeXml(eventToICS(ev)), 'c:calendar-data': escapeXml(eventToICS(ev, baseUrl(req), req.caldavUser)),
}), }),
); );
setDAVHeaders(res); setDAVHeaders(res);
@@ -391,7 +422,7 @@ router.get('/:username/calendar/:filename', caldavAuth, async (req, res) => {
setDAVHeaders(res); setDAVHeaders(res);
res.set('ETag', etag(ev)); res.set('ETag', etag(ev));
res.set('Content-Type', 'text/calendar; charset=utf-8'); res.set('Content-Type', 'text/calendar; charset=utf-8');
res.send(eventToICS(ev)); res.send(eventToICS(ev, baseUrl(req), req.caldavUser));
}); });
// ── PUT /{username}/calendar/{uid}.ics — create or update ───────────────── // ── PUT /{username}/calendar/{uid}.ics — create or update ─────────────────