Skip to content

Events — Domain Spec

An event is a hosted plan with a start, an end, a place, a capacity, and a chat room. This page is the rules other domains trust about event state, who can join, and how events expire.

Mental model

Three shapes:

  1. An event has a phase. upcoming → ongoing → completed → archived, with cancelled as a sideways exit. The phase is a function of wall-clock time and host action.
  2. An event has a host. Exactly one user. Host can transfer ownership; on transfer, the old host becomes a participant.
  3. An event has a chat room. Created on event creation, shared with all approved participants, retained until the event archives.
  4. An event may carry a event-items board. When an event is promoted from a plan, the plan's curated items are copied onto the event as a read-only board. The copy is self-contained: it never reaches back into the plan.

Event phases

Phase Entered when Exit
upcoming CreateEvent now > start_timeongoing, or CancelEventcancelled
ongoing now > start_time (auto, scheduler) or Start (host force-start) now > end_timecompleted, or CancelEventcancelled
completed now > end_time (auto, scheduler) now > end_time + retention_daysarchived
archived now > end_time + retention_days (auto, scheduler) PurgeArchivedEvents after retention horizon → row deleted
cancelled CancelEvent (host) or auto-cancel when host leaves and other participants remain Terminal until purge

Phase transitions are evaluated every 5 minutes by the status_update scheduler job. cancelled removes the event from the Redis geo index.

Visibility modes

Mode Who can find the event Who can join
public Anyone with the URL or via map / search Anyone (subject to capacity, access code, attestations)
friends Anyone, but only friends of the host see it in discovery Only friends of the host
invite_only Only people the host invites or who have the access code Same

Join eligibility (in order)

  1. Access code matches, if access_code is set
  2. Capacity not exceeded — overflow goes to waitlist only if waitlist_enabled
  3. Required attestations present on the user
  4. Visibility rule: friends requires accepted friendship with host
  5. If auto_accept = true, status becomes approved and the joiner is added to the chat room immediately. Otherwise status is pending and the host must approve.

Participant statuses

Status Meaning
pending Joined but awaiting host approval
approved In; can see event detail; is in chat room
waitlist Over capacity; promoted on a spot opening (host-driven for now)

Host vs. participant rules

Action Who can
Update event metadata Host
Approve / remove participant Host
Force-start Host
Cancel event Host
Transfer ownership Host (unilateral; old host becomes participant, chat-room admin role moves)
Send messages in chat Host + approved participants
Read public event detail Anyone (no auth required)
Read chat_room_id on event detail Approved participants only

Event items board

An event promoted from a plan carries a snapshot of the plan's selected board items. Each item is copied into an event-owned row at promotion time: title, body, tags, place reference, and JSON snapshots of its photos and links. GET /events/:id/items renders the board entirely from these copies.

Rule Effect
The snapshot is taken once, at promotion The board reflects the plan as it stood when it became an event
The board is event-owned Editing or deleting the source plan item never changes it
Photos and links are stored as JSON snapshots The board renders self-contained, with no read into the plan / item domain
A gated item is hidden behind joining Only approved participants see gated cards

Invariants

Invariant Why
Every event has exactly one host Single source of authority
The event-items board is a snapshot, not a live view It must survive the source plan being edited, archived, or deleted
Every event has exactly one chat room Created on event creation, type group, host added as admin
Phase progression is wall-clock-driven Cancellation is the only sideways move
Capacity is a hard ceiling Waitlist absorbs overflow only if enabled
Event with no host + no participants is dropped CleanupOrphanedEvents removes orphans on host hard-delete
Event with no host but with participants is cancelled, not deleted History sticks around so participants can land
Sponsorship tier never changes the social contract It only affects discovery clustering / priority

Lifecycle / state machine (prose)

Events transition upcoming → ongoing → completed → archived along the wall-clock axis. The status_update scheduler (5-minute cadence) walks every event and calls determineStatus(now) to roll the state forward. Two host actions can override: CancelEvent sets cancelled from upcoming or ongoing; Start advances upcoming → ongoing immediately. Archival is a separate scheduler pass that flips completed → archived once end_time + retention_days is past, after which the daily purge job hard- deletes the row.

Two cleanup paths run independently. MessagePurgerService (24h cadence) bulk-deletes messages from event chat rooms that have archived. CleanupOrphanedEvents removes events whose host no longer exists and which have no active participants.

Cross-domain interactions

Other domain Interaction
Chat One chat_rooms row per event (type group, event_id set). Host is admin. Messages retained per event retention rule.
Discovery Event coordinates indexed into Redis GEO; events ranked by RecommendationService in GetEvents. Sponsorship tier influences clustering.
Friends friends visibility checks accepted friendship with host.
Locations Optional location_id foreign key; raw coordinates also supported.
Plans Promotion creates the event, carries accepted collaborators over as approved participants, and snapshots the curated items onto the event-items board.
Identity On host hard delete, EventLifecycleService.CleanupUserEvents cleans up owned events.

Privacy / visibility

Field on event detail public friends invite_only
Title, location, time Public Friends of host Anyone with URL
Participant list Approved-only Approved-only Approved-only
chat_room_id Approved-only Approved-only Approved-only
Access code Never returned Never returned Never returned

See also