import { parseApiEvents } from "@/lib/events"; import { matchesRange, sortEvents } from "@/lib/filter"; import type { ApiEvent, CalendarEvent } from "@/lib/types"; /** * The public calendar as the landing page shows it: subscription links and * the next few events. Pure, apart from `fetchUpcomingPublicEvents`. * * The public feed needs no credential at all - nachklang.art reads the same * endpoint anonymously - so this runs on the server without a session. */ const PUBLIC_PATH = "/calendar/events/public"; /** https://api.nachklang.art/calendar/events/public/ical */ export const icalUrl = (apiBaseUrl: string): string => `${apiBaseUrl}${PUBLIC_PATH}/ical`; /** * Any iCal URL as a `webcal://` link, which Apple Calendar (and most desktop * clients) open as "subscribe" rather than "download". The scheme replaces * http(s) whichever the API uses - calendar apps fetch it over https for an * https host. */ export const toWebcal = (ical: string): string => ical.replace(/^https?:\/\//, "webcal://"); /** Google Calendar's "add by URL" deep link for any iCal URL. It wants the webcal form, URL-encoded. */ export const toGoogleCalendar = (ical: string): string => `https://calendar.google.com/calendar/r?cid=${encodeURIComponent(toWebcal(ical))}`; export const webcalUrl = (apiBaseUrl: string): string => toWebcal(icalUrl(apiBaseUrl)); export const googleCalendarUrl = (apiBaseUrl: string): string => toGoogleCalendar(icalUrl(apiBaseUrl)); /** Upcoming (not yet over, Berlin days) public events, earliest first. */ export const selectUpcoming = (rows: ApiEvent[], now: Date, limit: number): CalendarEvent[] => sortEvents( parseApiEvents(rows).filter((e) => matchesRange(e, "future", now)), "start_asc", now, ).slice(0, limit); /** * Server-side fetch, cached for five minutes (Next's `revalidate`), so the * landing page is static HTML that refreshes itself rather than a request to * the API per visitor. * * Null on any failure. The subscription links are the page's real job and * must not depend on the API answering - that includes `next build` on a * machine that cannot reach it. */ export const fetchUpcomingPublicEvents = async ( apiBaseUrl: string, now: Date, limit: number, ): Promise => { try { const res = await fetch(`${apiBaseUrl}${PUBLIC_PATH}/json`, { next: { revalidate: 300 } }); if (!res.ok) { return null; } return selectUpcoming((await res.json()) as ApiEvent[], now, limit); } catch { return null; } };