diff --git a/modules/revents/landing.ts b/modules/revents/landing.ts new file mode 100644 index 00000000..1189689f --- /dev/null +++ b/modules/revents/landing.ts @@ -0,0 +1,89 @@ +/** + * rEvents landing page — event aggregation & discovery. + */ +export function renderLanding(): string { + return ` + +
+ + Event Aggregator + +

+ All your events. One place. +

+

+ Aggregate events from Luma, Meetup, iCal feeds, and more into a unified stream for your community. +

+

+ rEvents is the event ingestion layer for the rStack. + Connect external event platforms, parse unstructured text into structured events, + and keep your community's calendar in sync — automatically. +

+
+ + Try the Demo + + Learn More +
+
+ + +
+
+
+
+
+ 🔗 +
+

Aggregate

+

Pull events from Luma, Meetup, iCal feeds, and RSS into one unified stream. Sync on demand or on a schedule.

+
+
+
+ +
+

Parse

+

Paste unstructured text — emails, messages, flyers — and extract structured event data with dates, locations, and more.

+
+
+
+ 📅 +
+

Connect

+

Push events to rCal, surface them in rInbox, coordinate across the entire rStack ecosystem seamlessly.

+
+
+
+
+ + +
+
+

Supported Sources

+
+
+
🎄
+

Luma

+

lu.ma calendar events

+
+
+
🏅
+

Meetup

+

Meetup.com groups

+
+
+
📅
+

iCal / ICS

+

Any .ics feed URL

+
+
+
+

Manual / Text

+

Paste & parse anything

+
+
+
+
+`; +} diff --git a/modules/revents/mod.ts b/modules/revents/mod.ts new file mode 100644 index 00000000..39285eec --- /dev/null +++ b/modules/revents/mod.ts @@ -0,0 +1,164 @@ +/** + * rEvents module — event aggregation & parsing. + * + * Aggregates events from Luma, Meetup, iCal feeds, and unstructured text. + * The functional app runs as a standalone Next.js container (revents-online:3000) + * and is embedded via externalApp iframe in rSpace. + */ + +import { Hono } from "hono"; +import { renderShell, renderExternalAppShell, escapeHtml } from "../../server/shell"; +import { getModuleInfoList } from "../../shared/module"; +import type { RSpaceModule } from "../../shared/module"; +import { renderLanding } from "./landing"; + +const REVENTS_URL = process.env.REVENTS_URL || "http://revents-online:3000"; +const REVENTS_PUBLIC_URL = process.env.REVENTS_PUBLIC_URL || "https://revents.online"; + +const routes = new Hono(); + +// ── Proxy API calls to revents-online ── + +routes.get("/api/events", async (c) => { + const qs = new URL(c.req.url).search; + try { + const res = await fetch(`${REVENTS_URL}/api/events${qs}`); + const data = await res.json(); + return c.json(data); + } catch { + return c.json({ count: 0, results: [] }); + } +}); + +routes.get("/api/sources", async (c) => { + try { + const res = await fetch(`${REVENTS_URL}/api/sources`); + const data = await res.json(); + return c.json(data); + } catch { + return c.json([]); + } +}); + +routes.post("/api/parse", async (c) => { + try { + const body = await c.req.json(); + const res = await fetch(`${REVENTS_URL}/api/parse`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + const data = await res.json(); + return c.json(data); + } catch (e: any) { + return c.json({ error: e.message }, 500); + } +}); + +// ── Explore page (embedded) ── + +routes.get("/explore", (c) => { + const space = c.req.param("space") || "demo"; + return c.html(renderExternalAppShell({ + title: `Explore Events — rEvents | rSpace`, + moduleId: "revents", + spaceSlug: space, + modules: getModuleInfoList(), + appUrl: `${REVENTS_PUBLIC_URL}/explore`, + appName: "rEvents", + theme: "dark", + })); +}); + +// ── Sources page (embedded) ── + +routes.get("/sources", (c) => { + const space = c.req.param("space") || "demo"; + return c.html(renderExternalAppShell({ + title: `Event Sources — rEvents | rSpace`, + moduleId: "revents", + spaceSlug: space, + modules: getModuleInfoList(), + appUrl: `${REVENTS_PUBLIC_URL}/sources`, + appName: "rEvents", + theme: "dark", + })); +}); + +// ── Hub page ── + +routes.get("/", (c) => { + const space = c.req.param("space") || "demo"; + const base = `/${escapeHtml(space)}/revents`; + return c.html(renderShell({ + title: `rEvents — ${space} | rSpace`, + moduleId: "revents", + spaceSlug: space, + modules: getModuleInfoList(), + styles: ``, + body: `
+

rEvents

+

Aggregate events from Luma, Meetup, iCal, and more

+ +
`, + })); +}); + +export const eventsModule: RSpaceModule = { + id: "revents", + name: "rEvents", + icon: "🎪", + description: "Event aggregation & discovery from Luma, Meetup, iCal and more", + scoping: { defaultScope: "space", userConfigurable: true }, + routes, + standaloneDomain: "revents.online", + landingPage: renderLanding, + externalApp: { url: REVENTS_PUBLIC_URL, name: "rEvents" }, + outputPaths: [ + { path: "explore", name: "Explore", icon: "🔍", description: "Browse upcoming events" }, + { path: "sources", name: "Sources", icon: "🔗", description: "Connected event sources" }, + ], + feeds: [ + { + id: "events", + name: "Events", + kind: "data", + description: "Aggregated events from external platforms and manual input", + filterable: true, + }, + ], + acceptsFeeds: ["data"], +};