Data Model¶
Tomoda's persistent state lives in a single Postgres database (PostGIS-enabled). GORM models in backend/internal/models/ declare the schema; in non-production environments db.AutoMigrate keeps it in sync at boot. This page groups the core tables by domain and shows how they relate.
Source of truth
The GORM struct tags in backend/internal/models/*.go are the authoritative schema. The diagram below shows logical relationships; not every join column, soft-delete flag, or audit timestamp is rendered.
Entity-relationship diagram¶
erDiagram
User ||--o{ Session : "has"
User ||--o{ RefreshToken : "has"
User ||--o{ WebAuthnCredential : "registers"
User ||--o{ APIKey : "owns"
User ||--o{ Event : "creates"
User ||--o{ EventParticipant : "joins"
Event ||--o{ EventParticipant : "has"
Event ||--|| ChatRoom : "spawns"
Event ||--o{ ChatMessage : "scopes"
ChatRoom ||--o{ ChatRoomParticipant : "members"
ChatRoom ||--o{ ChatMessage : "contains"
User ||--o{ ChatRoomParticipant : "is_in"
User ||--o{ ChatMessage : "authors"
User ||--o{ Friendship : "requester"
User ||--o{ Friendship : "addressee"
User ||--o{ Moment : "posts"
Moment ||--o{ MomentLike : "receives"
User ||--o{ MomentLike : "gives"
Event }o--|| Category : "tagged"
Event }o--o| Location : "located_at"
Moment }o--o| Location : "located_at"
User ||--o{ OTP : "verifies_via"
User ||--o{ AuditLog : "subject_of"
User ||--o{ UserActivityLog : "produces"
Auth & identity¶
| Entity | Role |
|---|---|
User |
Canonical account record — email, username, hashed password, role, OAuth linkage flags, profile fields, map preferences. |
Session |
Active login session, indexed by device. Drives the "Active sessions" screen and supports per-session revocation. |
RefreshToken |
Long-lived opaque token used to mint new JWT access tokens. Mirrored into Redis for fast validation. |
WebAuthnCredential |
Stored passkey public key + counter for one user. Multiple credentials per user are supported. |
OTP |
Short-lived one-time codes (signup, password reset, email change, phone verification). Purpose-tagged. |
APIKey |
Encrypted programmatic key with a scope list. Authenticated via the X-API-Key header. |
See Authentication for how these compose into login and session flows.
Events & chat¶
| Entity | Role |
|---|---|
Event |
Core social object — title, time range, category, location, capacity, visibility, owner. Roughly a "plan to meet up". |
EventParticipant |
Join table — user × event with role (owner, host, guest) and status (pending, approved, rejected, left). |
ChatRoom |
Either an event-scoped group room or a direct/group DM (type = event | dm | group). Holds metadata: title, avatar, settings. |
ChatRoomParticipant |
Membership row — user × room with read state (last_read_at), nickname, mute, and last-seen. |
ChatMessage |
A message in a room. Supports text, image, sticker/GIF, share, system, with optional expires_at for disappearing messages. |
The relationship is "every Event has a ChatRoom", but ChatRoom is independent enough to back direct messages and ad-hoc group chats that don't belong to any event.
Social graph¶
| Entity | Role |
|---|---|
Friendship |
Symmetric friendship modelled with requester_id, addressee_id, and status (pending, accepted, blocked). |
The friendship row is the source of truth for who can see whose live location and who shows up on the friends map.
Moments & locations¶
| Entity | Role |
|---|---|
Moment |
Lightweight, ephemeral post anchored to a location. Has visibility (public, friends, private), optional media, and TTL. |
MomentLike |
Like edge — user × moment, unique constraint on the pair. |
Location |
Canonical place record. Carries lat/lng, H3 cell index (resolution 12, ~5m), Photon/Google IDs, and timezone derived via tzf. |
Location is shared by events and moments — it is the "place layer" the app pins everything to. H3 indices enable fast proximity dedupe and clustering for the discovery map.
Ops & audit¶
| Entity | Role |
|---|---|
Category |
Curated set of event tags; seeded at boot from CategoryHandler.SeedCategories. |
AuditLog |
Append-only record of privileged or sensitive actions (admin changes, account deletions, etc.). |
UserActivityLog |
Higher-volume behavioural log — login, logout, profile edits — used for security insights and admin tooling. |
LoginHistory |
Per-login record exposed to users via the "Login history" screen. |
Naming and lifecycle conventions¶
- UUIDs are used as primary keys throughout (
uuid.UUID,gen_random_uuid()). - Timestamps are stored UTC;
created_at/updated_atare standard. - Soft delete is applied to high-value records (
User,Moment,ChatMessage) via GORM'sDeletedAt; the dailypurgeandmoment_purgejobs hard-delete after a grace window. See System Overview. - Spatial columns use PostGIS (
geography(Point, 4326)); H3 indices live alongside for cell-level operations that don't need PostGIS.