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:
- 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.
- An event has a host. Exactly one user. Host can transfer
ownership; on transfer, the old host becomes a participant.
- An event has a chat room. Created on event creation, shared
with all approved participants, retained until the event archives.
- 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_time → ongoing, or CancelEvent → cancelled |
ongoing |
now > start_time (auto, scheduler) or Start (host force-start) |
now > end_time → completed, or CancelEvent → cancelled |
completed |
now > end_time (auto, scheduler) |
now > end_time + retention_days → archived |
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)
- Access code matches, if
access_code is set
- Capacity not exceeded — overflow goes to
waitlist only if
waitlist_enabled
- Required attestations present on the user
- Visibility rule:
friends requires accepted friendship with host
- 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