feat(contrail): integrate Contrail appview for AT Protocol event queries#574
Merged
tompscanlan merged 21 commits intomainfrom Apr 5, 2026
Merged
feat(contrail): integrate Contrail appview for AT Protocol event queries#574tompscanlan merged 21 commits intomainfrom
tompscanlan merged 21 commits intomainfrom
Conversation
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.
This was referenced Apr 16, 2026
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.
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.
Summary
ScheduleModule.forRoot()to AppModule — was called in 4 modules causing 3-4x duplicate interval/cron firingsTest plan