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()callsConvertToPostGIS()so the stored geometry stays in sync withLatitudeandLongitude.- 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 == nilmeans the moment is standalone and should be rendered separately from location pins.UserLikedis derived at request time; it is not persisted.
Important behavior¶
BeforeCreate()generates an ID if needed.BeforeCreate()setsExpiresAtwhen the moment is not journaled.BeforeCreate()also callsConvertToPostGIS().
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¶
Locationis 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:
geo_clustermarkers for low zoom levels.- Individual
event,location, andstandalone_momentmarkers for higher zoom. active_friendmarkers 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:
Spatialas the shared location primitive.Locationas the long-lived place record.Eventas the scheduled experience record.Momentas the ephemeral social content record.MapMarkeras 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 (
StatusonEvent, especially because handler/service logic already computes or overrides parts of it). - Vendor-specific location enrichment fields (
GooglePlaceID,Photos,OpeningHours,PlaceTypes,GoogleMapsURI).