Location & Nearby — Technical Reference
Where this lives
Backend
apps/backend/graphql/types/user-location.type.js/graphql/resolvers/user-location.resolver.js—myLocation,nearbyUsers,updateMyLocation,deleteMyLocation. Built in this pass — this was the single biggest "ghost" feature found in the whole codebase audit: full manager logic existed with zero GraphQL exposure anywhere.apps/backend/managers/user-managers/user-location.manager.js—updateLocation,getLocation,deleteLocation,getNearbyUsers, Haversine distance calculationapps/backend/data-access-services/user/user-location.access-service.js— location DB accessapps/backend/database/models/UserLocation.js—UserLocationmodel (latitude,longitude,city,country,isPublic)
Frontend
apps/frontend-nextjs/src/page-components/settings/LocationSharingPage.tsx— share/update location via the browser Geolocation API, toggle public visibility, stop sharing, plus an inline "People nearby" list (nearbyUsers). At Settings → Location (/settings/location).apps/frontend-nextjs/src/page-components/DiscoverPeoplePage.tsx— full-page discovery screen at/discover-people; its "Cerca de ti" section callsnearbyUsers, with an empty-state link to/settings/locationwhen the caller hasn't shared their own location yetapps/frontend-nextjs/src/components/discovery/RecommendationsSection.tsx— has anearbyUsers-backed "Nearby" tab, reachable via a real, clickable category tab bar (Recommended/You may know/Similar/Trending/Nearby) — this doc previously said the tab bar had been removed andactiveTabwas hardcoded; corrected
Real bugs fixed while building this
user-location.manager.js called four methods on user-location.access-service.js — findByUserId, getAll, getByCity, getByCountry — that did not exist on that class at all (it only had findById/findByUser/create/update/updateByUser/delete). Every manager method that touched location data (getLocation, updateLocation, deleteLocation, getNearbyUsers, searchByLocation, getPopularLocations) would have thrown TypeError: ...is not a function on first real call. Added the four missing methods as thin wrappers.
getNearbyUsers returned every user's exact coordinates with no privacy filter, even though UserLocation.isPublic defaults to false specifically so a user's location isn't shared until they opt in — a real privacy leak if this had shipped as originally written. Fixed to filter on isPublic === true.
canSeeLocation() and setLocationPrivacy() are unfinished — canSeeLocation is three // TODO comments that always return true, and setLocationPrivacy writes to a privacy_level column that doesn't exist on the UserLocation model (Sequelize silently drops unknown attributes on .update(), so it was a silent no-op). Neither is exposed by this GraphQL layer — isPublic (a plain boolean) is the only location-privacy control the model actually persists and enforces. A three-tier public/friends/private level, if wanted later, needs a real column and real enforcement in canSeeLocation first.
updateLocation previously never touched isPublic at all (always left it at the model's false default on create, never updated it) and wrote to a nonexistent accuracy column. Fixed to accept and persist isPublic.
Technical implementation checklist
-
myLocation/updateMyLocation/deleteMyLocation— wired;LocationSharingPage.tsx -
nearbyUsers— wired, filtered toisPublic: trueopt-ins only; used byLocationSharingPage.tsx's "People nearby" list andDiscoverPeoplePage.tsx's "Cerca de ti" section (RecommendationsSection.tsxalso has a nearby tab, but its tab bar is currently hidden so that path isn't reachable from the UI) - Local feed by radius (
getLocalFeedin the manager) — not exposed in GraphQL - Search by city/country or coordinates (
searchByLocationin the manager) — not exposed in GraphQL - Popular locations & location stats (
getPopularLocations/getLocationStatsin the manager) — not exposed in GraphQL - Tiered location privacy (
public/friends/private) —setLocationPrivacy/canSeeLocationare unfinished stubs; only a binaryisPublictoggle is real today - Static location sharing within chat messages (
shareLocation) is a separate feature that's now wired — the composer's "share location" quick action inChatView.tsx(handleShareLocation) calls it, andMessageBubble.tsxrenders the resulting location message. Live location broadcasting (updateLiveLocation/stopLiveLocation) resolvers also exist inmessage.resolver.js, but nothing in the frontend calls them yet andLocationInputstill has nolocationTypefield to mark a message as live; see Messages & Conversations
UserLocation model
| Field | Description |
|---|---|
latitude / longitude | Coordinates (WGS84), DECIMAL(10,8)/DECIMAL(11,8) |
city / country | Human-readable location (optional, not reverse-geocoded server-side — the frontend doesn't send them today) |
isPublic | Boolean, defaults to false. The only privacy control that's actually enforced. |
Sharing and updating location
mutation UpdateMyLocation($input: UpdateLocationInput!) {
updateMyLocation(input: $input) { id city country latitude longitude isPublic updatedAt }
}
query MyLocation { myLocation { id city country latitude longitude isPublic updatedAt } }
mutation DeleteMyLocation { deleteMyLocation }
UpdateLocationInput: latitude: Float!, longitude: Float!, city: String, country: String, isPublic: Boolean (defaults to false/private on first share if omitted). Call updateMyLocation again with the same coordinates to just flip isPublic without re-requesting the browser's geolocation permission.
Nearby users
query NearbyUsers($radiusKm: Float, $limit: Int) {
nearbyUsers(radiusKm: $radiusKm, limit: $limit) {
user { id username profilePicture isVerified }
distanceKm
city
}
}
Requires the caller to have shared their own location first (myLocation must be non-null) — nearbyUsers returns [] otherwise. Only returns other users with isPublic: true, sorted by distance, computed via the Haversine formula in user-location.manager.js#calculateDistance.