Skip to content

Backend map shapes

This document summarizes the backend structs that shape the map, discovery, events, moments, and location detail payloads. It is meant to help with redesign planning: what is persisted, what is derived, and what the frontend should treat as a stable contract.

1) Shared geometry foundation

Spatial

Defined in backend/internal/models/spatial.go.

This is the shared geospatial mixin embedded by Event, Moment, Location, and Plan.

Field Type Notes
Coordinates interface{} PostGIS geometry column (geometry(Point,4326)), not exposed in JSON. The spatial-index source of truth, derived from lat/lng on save.
Latitude float64 Real column (lat) — map queries read coordinates without a PostGIS projection.
Longitude float64 Real column (lng)
Address string Human-readable address
City string City name
Country string Country name
CountryCode string ISO-3166 alpha-2 country code

Behavior: - ConvertToPostGIS() converts Latitude and Longitude into a PostGIS point before save/create. - The map service (backend/internal/services/maps/service.go) queries the canonical Location/Event/Moment models directly; there are no separate map-only models. Location additionally carries the map-detail fields (sub_category, verification_status, neighborhood_name, save_count, moment_count, is_active) and the personalization tables are user_affinity, user_saves, and user_dismissals. Viewport marker data comes from DiscoveryService (bbox + zoom against the geometry index).

2) Core persisted models

Event

Defined in backend/internal/models/event.go.

Represents a scheduled event pin or a chat-room-backed event. It is the heaviest map-related model and includes lifecycle, access control, sponsorship, and customization fields.

Shape

Field Type Notes
ID string UUID primary key
Title string Required
Description string Free text
DescriptionFormat string text, markdown, html, etc.
StartTime time.Time Required
EndTime time.Time Required
Duration int Minutes; 0 means not specified
LocationID *string Optional FK to Location
Location *Location Loaded relation
Spatial embedded Spatial Shared map coordinates/address fields
Category string Event category
MaxCapacity int Participant cap
HostID string Host user ID
AutoAccept *bool Whether requests auto-approve
CoverImage string Custom artwork
AccentColor string UI color token
ExternalLink string CTA link
JoinButtonText string CTA label
Type string Usually event; can be chat_room
PartnerID *string Partner/cross-promo relationship
SponsorshipTier *int 0 none, 1 global, 2 city, 3 neighborhood
IsSponsored *bool Sponsored flag
ChatRoomID string Associated room
RequiredBadges *string Comma-separated requirements
AccessCode *string PIN/code gate
Metadata *string Flexible JSON text blob
Status string Lifecycle state
RetentionDays int Days after end time before archival
ArchivedAt *time.Time Archive timestamp
IsRecurring *bool Recurrence toggle
RecurrenceRule *string RRULE or JSON rule
Visibility *string public, private, circle_restricted, etc.
MinCapacity *int Optional lower bound
WaitlistEnabled *bool Waitlist toggle
JoinRequirements *string Additional join constraints
MaxGuestsPerUser *int Guest cap
NotificationConfig *string JSON notification config
Reminders *string Comma-separated reminder minutes
CreatedAt time.Time DB timestamp
UpdatedAt time.Time DB timestamp
Host User Loaded host relation

Why it matters

  • This is the source of truth for event persistence.
  • Several fields are presentation-driven and likely candidates for redesign normalization: CoverImage, AccentColor, JoinButtonText, Metadata, NotificationConfig.
  • Event visibility and participation are partially enforced in the service layer, not just in the struct.

Important behavior

  • BeforeSave() calls ConvertToPostGIS() so the stored geometry stays in sync with Latitude and Longitude.
  • The handler/service layer often derives a computed status for responses instead of exposing raw DB state as-is.

EventParticipant

Defined in backend/internal/models/event.go.

Join/attendance row linking a user to an event.

Field Type Notes
ID string UUID primary key
EventID string Event FK
UserID string User FK
Status string pending, approved, rejected
CreatedAt time.Time Timestamp
User User Loaded relation
Event Event Loaded relation

Moment

Defined in backend/internal/models/moment.go.

Represents an ephemeral or journaled media pin, optionally attached to a Location.

Shape

Field Type Notes
ID uuid.UUID Primary key
UserID uuid.UUID Author
LocationID *uuid.UUID Optional attachment to a location
Spatial embedded Spatial Coordinates/address fields
MediaURL string Required media asset
Caption string Optional text
Rating float64 User rating applied to parent location
Category string Classification
Visibility string Usually public or friends_only
Duration int Lifetime in hours
IsJournaled bool If true, never expires
LikesCount int Cached like count
CreatedAt time.Time Timestamp
ExpiresAt *time.Time Null for journaled moments
DeletedAt *time.Time Soft delete; nullable deleted_at column, filtered with WHERE deleted_at IS NULL
UserLiked bool Virtual field for API responses
User User Loaded author relation
Location *Location Loaded location relation

Why it matters

  • Moments are the main ephemeral content layer on the map.
  • LocationID == nil means the moment is standalone and should be rendered separately from location pins.
  • UserLiked is derived at request time; it is not persisted.

Important behavior

  • BeforeCreate() generates an ID if needed.
  • BeforeCreate() sets ExpiresAt when the moment is not journaled.
  • BeforeCreate() also calls ConvertToPostGIS().

MomentLike

Defined in backend/internal/models/moment.go.

Tracks per-user likes on moments.

Field Type Notes
ID uuid.UUID Primary key
MomentID uuid.UUID Moment FK
UserID uuid.UUID User FK
CreatedAt time.Time Timestamp

Location

Defined in backend/internal/models/location.go.

Represents a persistent place on the discovery map. This is the central aggregation point for moments, location metadata, and upcoming events.

Shape

Field Type Notes
ID uuid.UUID Primary key
GooglePlaceID *string Optional external place ID
OsmID *int64 Optional OpenStreetMap ID
OsmType string N, W, or R
Name string Required display name
Localizations map[string]LocationTranslation Per-language translations
Spatial embedded Spatial Coordinates/address fields
Category string Primary category
SubCategory string Secondary category
AverageRating float64 Cached average rating
TotalRatings int Rating count
ReviewCount int Review count
TotalVisits int Visit count
VerificationStatus string user_reported, google_verified, tomoda_official
LastActivityAt *time.Time Recent activity timestamp
Website *string External website
PhoneNumber *string Local phone number
InternationalPhoneNumber *string International phone number
PriceLevel *int 0-4 price scale
BusinessStatus string Operational state
GoogleMapsURI *string Maps deep link
Photos *string JSON array of photo URLs
OpeningHours *string JSON opening-hours payload
PlaceTypes *string JSON array of place types
FullAddress *string Canonical formatted address
CreatedAt time.Time Timestamp
UpdatedAt time.Time Timestamp
DeletedAt *time.Time Soft delete; nullable deleted_at column, filtered with WHERE deleted_at IS NULL

Why it matters

  • Location is the stable anchor for places that can accumulate moments and upcoming events.
  • This is the most important object to preserve if you want continuity across a redesign.
  • It blends canonical place identity, localization, activity metrics, and vendor-enrichment data.

LocationTranslation

Nested in backend/internal/models/location.go.

Field Type Notes
Name string Localized name
Address string Localized address
City string Localized city
Country string Localized country

ActiveLocationSession

Defined in backend/internal/models/active_location.go.

Represents a live location-sharing session used by proximity/radar features.

Field Type Notes
ID uuid.UUID Primary key
UserID uuid.UUID Owner
IntervalMinutes int Sharing interval
StartedAt time.Time Session start
ExpiresAt time.Time Session expiry
IsActive bool Active flag
CreatedAt time.Time Timestamp
UpdatedAt time.Time Timestamp
DeletedAt *time.Time Soft delete; nullable deleted_at column, filtered with WHERE deleted_at IS NULL

PublicUser

Defined in backend/internal/models/public_user.go.

Minimal public user object used in responses that should not expose private account data.

Field Type
ID string
Name string
Country string
Username string
AvatarURL string
Bio string

3) Discovery and map DTOs

These are the API-facing shapes the frontend should treat as the real map contract. They are not the same thing as the database models.

DiscoveryPayload

Defined in backend/internal/services/discovery/types.go.

Field Type Notes
Markers []MapMarker Unified list of pins/clusters returned to the map

MapMarker

Defined in backend/internal/services/discovery/types.go.

This is the canonical map shape used by the frontend cluster layer.

Field Type Notes
ID string Stable marker ID
Type string event, location, standalone_moment, active_friend, geo_cluster
Latitude float64 Marker position
Longitude float64 Marker position
Title string Display title
Description string Optional subtitle/preview
Color string Visual accent
Category string Category label
IsPriority bool If true, frontend should bypass clustering
Metadata any Free-form extra data
MomentCount int Used for location badges
UpcomingEventCount int Used for location badges
ClusterCounts *ClusterCounts Only for geo_cluster markers

ClusterCounts

Field Type Notes
Events int Event count in cluster
Locations int Location count in cluster
Moments int Moment count in cluster
Total int Sum of all content types

LocationDetailPayload

Used when the user opens a location detail card or sheet.

Field Type Notes
ID string Location ID
Name string Display name
Address string Base address
FullAddress *string Optional formatted address
Category string Category
SubCategory string Optional subcategory
AverageRating float64 Rating summary
TotalRatings int Rating count
LastActivityAt *time.Time Freshness signal
Website *string Website
PhoneNumber *string Phone
InternationalPhoneNumber *string International phone
PriceLevel *int Price scale
BusinessStatus string Operational state
GoogleMapsURI *string Deep link
Photos []string Parsed photo URLs
OpeningHours *OpeningHours Parsed hours payload
PlaceTypes []string Parsed place types
Highlights []MomentHighlight Curated recent moments
UpcomingEvents []EventBrief Upcoming events at this place
Latitude float64 Location position
Longitude float64 Location position

OpeningHours

Field Type Notes
WeekdayText []string Human-readable hours
IsOpen *bool Optional live open state

MomentHighlight

Compact moment card used inside location and profile payloads.

Field Type Notes
ID string Moment ID
MediaURL string Primary image/video URL
Caption string Optional caption
LikesCount int Like total
IsFriend bool Whether author is a friend
UserLiked bool Whether current user liked it
CreatedAt string RFC3339 timestamp
CountryCode string Author/location country code
Author UserBrief Minimal author card

UserBrief

Field Type
ID string
Name string
Country string
AvatarURL string
Username string

LocationBrief

Field Type
ID string
Name string

EventBrief

Short event card used in location and profile payloads.

Field Type Notes
ID string Event ID
Title string Event title
Description string Truncated preview
Category string Event category
StartTime string RFC3339 timestamp
EndTime string RFC3339 timestamp
Status string upcoming, ongoing, completed, etc.
Latitude float64 Event pin position
Longitude float64 Event pin position
Timezone string Derived from coordinates
Host *UserBrief Optional host summary
ParticipantCount int Approved participants
ParticipationStatus string Current user’s relationship to the event

EventDetailPayload

Full event detail response used when opening a specific event.

Field Type Notes
ID string Event ID
Title string Event title
Description string Full description
Category string Category
Latitude float64 Event position
Longitude float64 Event position
Timezone string Derived timezone
StartTime string RFC3339 timestamp
EndTime string RFC3339 timestamp
Visibility string Event access level
SponsorshipTier *int Sponsorship tier
Host UserBrief Host summary
ParticipantCount int Approved participant total
IsJoined bool Convenience flag
ParticipationStatus string host, approved, pending, etc.
ChatRoomID string Returned only when eligible
LocationID string Linked location
LocationName string Linked location name
LocationAddress string Linked location address
City string Event city
Country string Event country

MomentDetailPayload

Full moment detail response.

Field Type Notes
ID string Moment ID
MediaURL string Asset URL
Caption string Caption
LikesCount int Like total
UserLiked bool Whether the viewer liked it
CountryCode string Country code
CreatedAt string RFC3339 timestamp
Location *LocationBrief Optional linked location
Author UserBrief Author summary

4) How the backend uses these shapes

The discovery service assembles the map in layers:

  1. geo_cluster markers for low zoom levels.
  2. Individual event, location, and standalone_moment markers for higher zoom.
  3. active_friend markers appended on top of everything else.

Important implementation notes: - Events tied to a location_id are intentionally not duplicated as standalone event pins in the map feed. - Location markers carry MomentCount and UpcomingEventCount so the frontend can show activity badges. - Moments attached to a location are surfaced through the location detail payload, not as separate standalone map pins. - Some response fields are derived at request time (UserLiked, IsJoined, ParticipationStatus, Timezone, ClusterCounts).

5) Redesign / migration guidance

If you are planning a redesign, these are the safest things to preserve:

  • Spatial as the shared location primitive.
  • Location as the long-lived place record.
  • Event as the scheduled experience record.
  • Moment as the ephemeral social content record.
  • MapMarker as the canonical frontend map contract.

These are the most likely migration candidates:

  • Presentation fields embedded inside Event (CoverImage, AccentColor, JoinButtonText).
  • Free-form text blobs (Metadata, NotificationConfig, AccessCode, Reminders).
  • Status naming and lifecycle semantics (Status on Event, especially because handler/service logic already computes or overrides parts of it).
  • Vendor-specific location enrichment fields (GooglePlaceID, Photos, OpeningHours, PlaceTypes, GoogleMapsURI).

6) Files to inspect next