From f3ef490012e4e8838d006e0499031d8c72e2e1f8 Mon Sep 17 00:00:00 2001 From: Michelle Date: Tue, 3 Mar 2026 12:13:36 +0100 Subject: [PATCH] feat(caldav): enhance eventToICS function to include join links and organizer details --- server/routes/caldav.js | 47 ++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/server/routes/caldav.js b/server/routes/caldav.js index dbc9bb9..7316e4d 100644 --- a/server/routes/caldav.js +++ b/server/routes/caldav.js @@ -89,7 +89,12 @@ function getICSProp(ics, key) { 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 = [ 'BEGIN:VCALENDAR', 'VERSION:2.0', @@ -103,14 +108,40 @@ function eventToICS(event) { `DTSTAMP:${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) { lines.push(`X-REDLIGHT-ROOM-UID:${event.room_uid}`); } - if (event.federated_join_url) { - lines.push(`X-REDLIGHT-JOIN-URL:${escapeICS(event.federated_join_url)}`); + if (joinUrl) { + lines.push(`X-REDLIGHT-JOIN-URL:${escapeICS(joinUrl)}`); } lines.push('END:VEVENT', 'END:VCALENDAR'); return lines.map(foldICSLine).join('\r\n'); @@ -340,7 +371,7 @@ router.all('/:username/calendar/', caldavAuth, async (req, res) => { responses.push( propResponse(`${calendarHref}${ev.uid}.ics`, { '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 => propResponse(`${calendarHref}${ev.uid}.ics`, { 'd:getetag': escapeXml(etag(ev)), - 'c:calendar-data': escapeXml(eventToICS(ev)), + 'c:calendar-data': escapeXml(eventToICS(ev, baseUrl(req), req.caldavUser)), }), ); setDAVHeaders(res); @@ -391,7 +422,7 @@ router.get('/:username/calendar/:filename', caldavAuth, async (req, res) => { setDAVHeaders(res); res.set('ETag', etag(ev)); 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 ─────────────────