Database
Status: Scaffolded 2026-09-24
One MongoDB Atlas M0 cluster in us-east-1, accessed only by the API through Mongoose 9. The data model page describes the shape and why; this page is the operational view: what collections exist, what indexes are declared, and which fields the data inventory cares about.
Conventions
- Every reference is an
ObjectId. No string ids, no DBRefs, no mixed types. This was The Trick Book's worst debt and the Mongoose schema makes it structurally impossible here. - Every index is declared in the schema, and
db/callssyncIndexes()on boot so the database matches the code. An index that exists only in Atlas does not exist. timestamps: trueon every schema.- Field names are what the API returns; there is no mapping layer to hide a leaked field behind.
Collections
| Domain | Collections | Privacy-relevant fields |
|---|---|---|
| Identity | users, sessions, magic_links, friend_invites | users.email (login only, never returned to another member), users.homeArea (fixed list, never shown outside the owner), providers[].subject, sessions.tokenHash (sha256, the raw token is never stored) |
| Social graph | friendships, conversations, messages, reports | friendships are visible only to the two parties; messages.ciphertext, iv, keyId (AES-256-GCM at rest); reports.reportedBy never exposed to the reported |
| Places | places, place_reviews, place_lists, organizations | places.submittedBy (nullable, detached on account deletion), place_reviews.userId with showHandle=false default, place_lists.isPublic=false default, organizations.adminUserIds never in a response |
| Community | groves, grove_members, events, event_rsvps | event_rsvps readable by the organizer only, counts for everyone; events.detailsAfterRsvp withholds address until RSVP |
| Content | posts, comments, reactions, saved_posts, media_items, guides | posts.visibility defaults to friends; saved_posts are private |
| Personal | action_log, companion_conversations | both private to the owner, both hard-deleted with the account |
| Notifications | push_tokens, notification_preferences, scheduled_notifications | push_tokens.token is a device address and is deleted on logout and on account deletion |
Unique indexes
users.email, users.handle, sessions.tokenHash, magic_links.tokenHash, friend_invites.code, places.slug, organizations.slug, groves.slug, events.slug, media_items.slug, guides.slug, push_tokens.token, notification_preferences.userId, scheduled_notifications.idempotencyKey. Compound unique: friendships (userA, userB) with the pair sorted before write, grove_members (groveId, userId), event_rsvps (eventId, userId), reactions (postId, userId), saved_posts (postId, userId), conversations.participantIds sorted.
TTL indexes
| Collection | Field | Expiry |
|---|---|---|
sessions | expiresAt | SESSION_TTL_DAYS, default 30, refreshed on use |
magic_links | expiresAt | 15 minutes |
friend_invites | expiresAt | set per invite |
messages | expiresAt | DM_RETENTION_DAYS, default 90 |
companion_conversations | expiresAt | 24 hours when not pinned; the field is unset on pin so the TTL skips it |
push_tokens | deadAt | 30 days after a delivery failure marks it dead |
TTL deletion is MongoDB's background job and runs about once a minute, so "expired" and "deleted" differ by up to a minute. Queries filter on expiresAt as well, so the gap is not observable through the API.
Geo indexes
places.location and events.location are GeoJSON Points with a 2dsphere index. GET /api/places?bbox= and the events list query with $geoWithin a $box; the bounding box comes from the client's viewport and is never stored or logged (privacy rule 3).
Atlas M0 limits that shape the code
512 MB of storage, shared CPU, no automated backups, and a connection cap. The API opens one Mongoose connection pool per process, keeps documents small (keys in S3, video ids at Bunny, no embedded arrays that grow without bound except companion_conversations.messages, which is capped by COMPANION_HISTORY_LIMIT), and treats a backup as a roadmap item rather than an assumption. Upgrading the tier changes nothing in the repo.