Skip to content

feat(contrail): integrate Contrail appview for AT Protocol event queries#574

Merged
tompscanlan merged 21 commits intomainfrom
feature/contrail-integration
Apr 5, 2026
Merged

feat(contrail): integrate Contrail appview for AT Protocol event queries#574
tompscanlan merged 21 commits intomainfrom
feature/contrail-integration

Conversation

@tompscanlan
Copy link
Copy Markdown
Contributor

Summary

  • Contrail integration: ContrailQueryService for querying events from the Contrail appview (AT Protocol), with geo sync service for spatial indexing and lexicon type generation
  • Enhanced AT Protocol publishing: EnrichedEvent types, AtprotoEnrichmentService, improved RSVP sync, geo coordinate publishing, ATProto-only events through VisibilityGuard
  • Event query refactor: Wire event query endpoints to Contrail for public events, unify event delete and RSVP through publisher service, remove dead AtprotoSyncScheduler and unused event-attendee methods
  • ScheduleModule fix: Centralize ScheduleModule.forRoot() to AppModule — was called in 4 modules causing 3-4x duplicate interval/cron firings
  • Test infrastructure: ATProto e2e test helpers with Contrail data seeding, Contrail container in devnet/CI, mjml mock for Node 24 Jest compatibility

Test plan

  • 149/149 unit test suites passing (2,087 tests)
  • ATProto event query e2e tests (18 tests) — list, single, geo, search, pagination, visibility, RSVP counts
  • Event visibility compliance e2e — public/unlisted/private access rules
  • Search visibility compliance e2e — Contrail search with tsvector
  • Guards e2e — permissions, visibility guard for ATProto-only events
  • Past events, group DID follow, group role permissions e2e — all passing
  • Live verification: geo queries return correct results (NYC=2, Tokyo=0), geo sync runs single interval with no duplicates

Add @atcute/lex-cli integration for generating TypeScript types from
AT Protocol lexicons. Implement collection-generic ContrailQueryService
that queries the Contrail ATProto data plane with type-safe access to
calendar events, RSVPs, and location records.

Includes ContrailModule wiring and Contrail config for local/CI/prod.
Add atproto_geo_index table for spatial queries on ATProto events.
Implement ContrailGeoSyncService that periodically syncs location
coordinates from Contrail records into PostGIS-indexed rows, filtering
records that lack valid coordinates.
Add discriminated union types for tracking event source (tenant vs
ATProto). Implement AtprotoEnrichmentService with batch enrichment,
Redis-cached handle resolution, category filtering, and deduplication
against tenant events.
Extend RSVP status maps with Pending/Waitlist/Invited/Rejected states.
Add deleteRsvp to AtprotoPublisherService with PDS session support.
Unify RSVP sync into a single syncRsvpToAtproto path and route event
deletion through AtprotoPublisherService, removing duplicated logic.
Delete AtprotoSyncScheduler (replaced by Contrail ingestion pipeline).
Remove deprecated cancelEventAttendance, dead showDashboardEvents,
and unused UserService/forwardRef from EventAttendeeService.
Events that exist only in Contrail (no tenant record) need to pass
visibility checks. Update VisibilityGuard to handle ATProto-sourced
events without a tenant context.
Route showAllEvents, searchAllEvents, getHomePageFeaturedEvents,
findEventsForGroup, and findGroupEvents through ContrailQueryService.
Add Contrail fallback for single event lookup via ATProto slug with
~ delimiter. Merge and deduplicate results with tenant events, handle
pagination, category filtering, and private event UNION queries.
Route event deletion through AtprotoPublisherService for consistent
ATProto record cleanup. Update event-management and edge-cases specs
to match new service dependencies.
Add Contrail service to docker-compose-devnet.yml for live ATProto
ingestion during local development. Update CI devnet config and
startup script for Contrail availability.
Add MJML mock to fix 74 test suites crashing on prettier ESM import.
Create ATProto test helper with schema setup, seeding, and 7-event
scenario builder for e2e tests. Add jest-unit.json config and update
test mocks for Contrail integration.
Add comprehensive e2e test suite covering event listing (dedup,
enrichment, geo, search, pagination), single-event lookup (slug
formats, visibility, groups), cancelled events, RSVP counts, and
ATProto-only visibility compliance. Align existing e2e tests with
Contrail schema and seeding patterns.
Replace fixed 5s sleep with polling loops: up to 30s for atprotoUri
to be set after async ATProto publish, then up to 20s for Contrail
to ingest via Jetstream. Falls back to manual seeding if pipeline
doesn't complete in time. Wrap search_vector UPDATE in try-catch
since it may be a generated column.
CI now runs with PDS, Jetstream, and Contrail alongside the standard
test stack. Events flow through the full ATProto pipeline in e2e tests.
The ATProto publisher only included geo coordinates in the locations
array when event.location (text address) was also set. Users who set
a map pin without typing an address got empty locations, breaking
Contrail geo sync indexing.

Now publishes community.lexicon.location.geo whenever lat/lon exist,
with name as optional (matching the lexicon schema).
mjml-core bundles prettier@3.8+ which uses dynamic import() in its CJS
entry, crashing Jest's VM sandbox on Node 24 with
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG. The mock already existed
but wasn't configured in jest-unit.json.
ScheduleModule.forRoot() was called in 4 modules, each creating a
ScheduleExplorer that re-registered every @Interval/@Cron — causing
3-4x duplicate firings per tick. Centralize to AppModule only.

ContrailGeoSyncService's @interval was silently skipped because
EventModule has Scope.REQUEST providers, making the scheduler consider
it non-static. Replace @interval with a manual setInterval in
onApplicationBootstrap, which guarantees exactly one interval per
process and only starts after the app is fully bootstrapped.
The CI compose builds the Contrail container from source. Add a
checkout step for flo-bit/contrail (our postgres adapter PR was
merged to their main).
In CI unit tests, no database is running. The getPublicDataSource()
call would hang waiting for a connection that never comes, adding
minutes to the test run. Add a 3-second race timeout so the setup
fails fast and unit tests proceed without DB.
In CI, contrail-postgres-adapter is checked out inside the workspace,
not as a sibling directory. Use relative path without ../ so docker
compose can find it.
The contrail checkout in CI lands inside the workspace. NestJS's
nest build picks it up and fails on its Cloudflare Workers types.
Non-members and unauthenticated users could see unlisted events via
GET /api/groups/:slug/events. Now filters unlisted events unless the
requesting user is a group member.
@tompscanlan tompscanlan merged commit 4df590e into main Apr 5, 2026
4 checks passed
tompscanlan added a commit that referenced this pull request Apr 23, 2026
* revert: remove unreleased features from main, align with prod (v1.5.1)

Reverts unreleased features to bring main back in sync with production:

- #577 perf(db): query fingerprint + home page optimization
- #576 feat: unified AttendanceService
- #574 feat(contrail): integrate Contrail appview

Hotfix changes (v1.5.1) are retained — they exist in the baseline.

All work is preserved on its respective feature branch:
- feature/contrail-integration
- feature/attendance-service
- feature/db-query-performance
- feature/contrail-rsvp

Direction shift: adopting Contrail/atmo as the new stack rather than
integrating Contrail into the OpenMeet API.

* fix(test): use future dates in edge-cases spec to avoid past-event guard

The hotfix (v1.5.1) added a past-event RSVP guard that rejects RSVPs
to events with past start dates. The edge-cases spec used 2024 dates,
hitting the guard before reaching the race condition logic under test.
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.

1 participant