Support naddr/nevent/note slugs for unindexed posts, cache naddr lookups, render Nostr embeds in markdown, and add a consistency check for events mirrors. Co-authored-by: Cursor <cursoragent@cursor.com>
72 lines
2.2 KiB
TypeScript
72 lines
2.2 KiB
TypeScript
/**
|
|
* 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<Meetup[]> {
|
|
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;
|
|
}
|