feat: add /events endpoints serving Luma calendar events - #54
Open
mrdarrengriffin wants to merge 3 commits into
Open
feat: add /events endpoints serving Luma calendar events#54mrdarrengriffin wants to merge 3 commits into
mrdarrengriffin wants to merge 3 commits into
Conversation
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
Contributor
There was a problem hiding this comment.
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/eventsmodule (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.
…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
Contributor
There was a problem hiding this comment.
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_PATTERNextraction will include trailing sentence punctuation (e.g. a trailing "."), producing an invalidurlvalue 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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);404for 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, andurl— 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.EVENTS_CALENDARSvariable ofcalendarId:slugpairs, the same contract asLIVESTREAM_CHANNELS— slugs pinned in config so a Luma rename cannot change public URLs, display names read from each feed'sX-WR-CALNAMEat runtime, malformed config fails startup.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.Intl, no timezone dependency).updatedAtmoves only when the served content changes, so it is safe for caching and change detection.Testing
fetchboundary; Swagger e2e assertions extended to the new endpoints and schemas.cal-6Tm2FkWzoBpLXWr): 38 events parsed end to end.README,
example.envand the C4 architecture model are updated to match.🤖 Generated with Claude Code
https://claude.ai/code/session_01Vp21Nq4X8pV68GfLeBwJ3e
Generated by Claude Code