/** * Single source of truth for meetup data used by the public /events page, the * /events.md mirror, and the /llms.txt summary line. Centralizing the fetch + * upcoming/past partition guarantees those three can never disagree on the count. * * Fetched with `no-store` so every render reflects the live backend — this is * what keeps crawlers (which don't run JS) and llms.txt consumers from seeing a * stale or build-time-empty list. */ import { apiUrl } from "./api-base"; import { getMeetupStartUtc } from "./meetupEventTime"; export interface Meetup { id: string; title: string; date: string; time?: string; location?: string; description?: string; status?: string; organizer?: { name?: string; slug?: string } | null; [key: string]: unknown; } /** Fetch all publicly-visible meetups from the backend, live (uncached). */ export async function fetchMeetupsLive(): Promise { try { const res = await fetch(apiUrl("/meetups"), { cache: "no-store" }); if (!res.ok) return []; const data = await res.json(); return Array.isArray(data) ? (data as Meetup[]) : []; } catch { return []; } } export interface PartitionedMeetups { upcoming: Meetup[]; past: Meetup[]; } /** * Split meetups into upcoming (start >= now, soonest first) and past (start < * now, most recent first). Meetups with an unparseable date are dropped. */ export function partitionMeetups( meetups: Meetup[], now: Date = new Date(), ): PartitionedMeetups { const upcoming: Meetup[] = []; const past: Meetup[] = []; for (const m of meetups) { const start = getMeetupStartUtc(m.date, m.time || "00:00"); if (Number.isNaN(start.getTime())) continue; if (start >= now) upcoming.push(m); else past.push(m); } const startMs = (m: Meetup) => getMeetupStartUtc(m.date, m.time || "00:00").getTime(); upcoming.sort((a, b) => startMs(a) - startMs(b)); past.sort((a, b) => startMs(b) - startMs(a)); return { upcoming, past }; } /** Number of upcoming meetups — the single value llms.txt and events.md share. */ export function countUpcoming(meetups: Meetup[], now: Date = new Date()): number { return partitionMeetups(meetups, now).upcoming.length; }