Skip to content

feat: add /events endpoints serving Luma calendar events - #54

Open
mrdarrengriffin wants to merge 3 commits into
mainfrom
claude/luma-events-endpoint-a9vvsa
Open

feat: add /events endpoints serving Luma calendar events#54
mrdarrengriffin wants to merge 3 commits into
mainfrom
claude/luma-events-endpoint-a9vvsa

Conversation

@mrdarrengriffin

Copy link
Copy Markdown
Member

Serves the foundation's Luma community events so the project websites can show them without each talking to Luma.

Endpoints

  • GET /events — every configured calendar with its events, in configuration order.
  • GET /events/:slug — one calendar (e.g. /events/home-assistant-meetups); 404 for an unknown slug, without echoing the slug back.

Events are everything the calendar's iCalendar feed advertises — past ones included, sorted soonest first — so the consumer decides the window it shows. Each event carries id, summary, start/end (UTC ISO 8601, or a bare date for all-day events), description, location, latitude/longitude, status, and url — the event's Luma page link, which the feed only embeds in the description text, so the service lifts it out.

Design

Mirrors the livestream module's architecture: one Nest module (src/events), a controller over in-memory state, no database.

  • Configuration: a new EVENTS_CALENDARS variable of calendarId:slug pairs, the same contract as LIVESTREAM_CHANNELS — slugs pinned in config so a Luma rename cannot change public URLs, display names read from each feed's X-WR-CALNAME at runtime, malformed config fails startup.
  • Polling: each calendar's public iCalendar export (api.luma.com/ics/get?entity=calendar&id=…) is fetched at startup and every 15 minutes — unauthenticated and unmetered, so no quota to design around. A calendar whose fetch (or a 200 that isn't a calendar) fails keeps serving its last good content.
  • Parsing: a small dependency-free iCalendar parser handling line folding, text escaping, quoted parameters, and the UTC / all-day / TZID date forms (TZID converted to UTC via Intl, no timezone dependency).
  • updatedAt moves only when the served content changes, so it is safe for caching and change detection.

Testing

  • Unit specs for the config parser, service (parsing, date forms, refresh cycle, failure modes) and controller; an events e2e suite booting a real Nest app with Luma stubbed at the fetch boundary; Swagger e2e assertions extended to the new endpoints and schemas.
  • Full gate green: format, lint, typecheck, LikeC4 diagrams, 230 unit + 93 e2e tests.
  • Smoke-tested against the real Home Assistant Meetups calendar (cal-6Tm2FkWzoBpLXWr): 38 events parsed end to end.

README, example.env and the C4 architecture model are updated to match.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Vp21Nq4X8pV68GfLeBwJ3e


Generated by Claude Code

Serve the foundation's Luma community events so the project websites can
show them without each talking to Luma. GET /events lists every
configured calendar with its events; GET /events/:slug serves one
calendar and 404s for an unknown slug.

Calendars are configured through EVENTS_CALENDARS as calendarId:slug
pairs, mirroring LIVESTREAM_CHANNELS: slugs are pinned in config,
display names come from each feed's X-WR-CALNAME at runtime, and
malformed config fails startup. Each calendar's public iCalendar feed
(api.luma.com/ics/get) is re-fetched every 15 minutes and parsed in
place — folding, text escaping, UTC/all-day/TZID date forms — with the
event's Luma page link lifted from the description, since the feed
carries no URL property. A calendar whose fetch fails keeps serving its
last good content, and updatedAt moves only when the served content
changes.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vp21Nq4X8pV68GfLeBwJ3e
Copilot AI lite review requested due to automatic review settings August 19, 2026 12:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Events feature area to the NestJS web API that periodically fetches and parses Luma iCalendar feeds into in-memory state, and exposes that state via /events endpoints (mirroring the existing livestream module pattern).

Changes:

  • Introduces src/events module (config parsing, polling service, controller, and Swagger response models) to serve Luma calendar events.
  • Extends application wiring and Swagger docs to include the new endpoints and schemas.
  • Adds unit + e2e coverage for config parsing, feed parsing/refresh behavior, controller behavior, and Swagger contract assertions.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/test-app.fixture.ts Boots test app with EventsModule and stubs Luma ICS fetch.
test/swagger.e2e-spec.ts Asserts /events paths and event/calendar schemas are documented.
test/events.e2e-spec.ts End-to-end tests for events endpoints against stubbed ICS upstream.
src/main.ts Updates Swagger description to include Luma events.
src/events/index.ts Barrel exports for the events feature.
src/events/events.service.ts Polling + parsing + in-memory state and read APIs for calendars/events.
src/events/events.service.spec.ts Unit tests for parsing, date handling, refresh cycle, and error cases.
src/events/events.response.ts Swagger response DTOs + schema documentation for events/calendars.
src/events/events.module.ts DI wiring and startup parsing of EVENTS_CALENDARS.
src/events/events.controller.ts Implements GET /events and GET /events/:slug endpoints.
src/events/events.controller.spec.ts Unit tests for controller delegation and error propagation.
src/events/events.calendars.ts Parses/validates EVENTS_CALENDARS and builds Luma ICS URLs.
src/events/events.calendars.spec.ts Unit tests for calendar config parsing/validation and URL building.
src/app.module.ts Registers EventsModule in the main app module.
README.md Documents new endpoints, response shape, and config variable.
example.env Adds EVENTS_CALENDARS documentation and example value.
docs/architecture/views.c4 Updates views to include Luma as an upstream.
docs/architecture/specification.c4 Adds iCalendar relationship type.
docs/architecture/model.c4 Adds Luma model elements and events components/relationships.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/events/events.service.ts Outdated
Comment thread src/events/events.service.ts Outdated
…tus duplication

Compare events field by field instead of JSON-serialising both arrays on
every refresh, with the field list compile-checked against EventInfo so a
new field cannot be silently left out of the comparison. Move the allowed
STATUS values into a single EVENT_STATUSES source of truth in the service,
which the response schema now derives its enum and descriptions from.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vp21Nq4X8pV68GfLeBwJ3e
Copilot AI review requested due to automatic review settings August 19, 2026 13:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/events/events.service.ts:508

  • LUMA_LINK_PATTERN extraction will include trailing sentence punctuation (e.g. a trailing "."), producing an invalid url value that is not actually the event's Luma page link. Consider post-processing the regex match to trim common trailing punctuation, and update the corresponding unit test that currently expects the period to be part of the URL.
      // A URL property when the feed grows one; today the Luma page link only
      // appears inside the description text.
      url:
        text('URL') ??
        (description ? LUMA_LINK_PATTERN.exec(description)?.[0] : undefined),

Luma's templated description carries the host as its final "Hosted by
{NAME}" line — the only place the feed exposes it, and the one piece of
that template not already served as its own field. Lift it into an
optional "host" on each event, kept verbatim so a multi-host line
("AIsling Krewer & Liam Krewer") stays intact. Matching is anchored to
the description's final line, so events without the line (or with
"Hosted by" mid-text) simply have no host.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vp21Nq4X8pV68GfLeBwJ3e
Copilot AI review requested due to automatic review settings August 19, 2026 13:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants