# --- Shared Data Models (Go source of truth) --- # # These structs are documentation of the domain layer; the executable GraphQL # schema lives in `api/internal/transport/schema.go` and is what clients get # from an introspection query. The two are kept in step: every field below has a # matching field in the executable schema, and vice versa. # # Note the `json` tag on each field. The Go handlers serialise the domain types # directly, so these tags are also the REST field names, and the REST client in # `web/src/types.ts` mirrors them. // Coordinate is a WGS-84 geographic point. type Coordinate struct { Latitude float64 `json:"latitude" gql:"latitude"` Longitude float64 `json:"longitude" gql:"longitude"` } // User represents a logged-in user. Identity is owned by the external go-loose // SSO service; this service only stores the identifier it asserts. type User struct { ID string `json:"id" gql:"id"` Username string `json:"username" gql:"username"` Email string `json:"email" gql:"email"` } // Badge is a recognisable attribute detected in a picture, for example an object // ("dog"), a scene ("beach") or a property ("low-light"). Label is the stable // machine-readable name; Display is the human-facing rendering. Confidence is a // 0..1 score, and Source records which analyzer produced the badge. type Badge struct { Label string `json:"label" gql:"label"` Display string `json:"display" gql:"display"` Confidence float64 `json:"confidence" gql:"confidence"` Source string `json:"source" gql:"source"` } // Image is a picture attached to a Place. type Image struct { ID string `json:"id" gql:"id"` PlaceID string `json:"placeId" gql:"placeId"` // StorageKey is the location of the bytes inside the blob store. It is never // exposed: URL is the public rendering of this key. StorageKey string `json:"-" gql:"-"` URL string `json:"url" gql:"url"` Caption string `json:"caption" gql:"caption"` ContentType string `json:"contentType" gql:"contentType"` ByteSize int64 `json:"byteSize" gql:"byteSize"` Width int `json:"width" gql:"width"` Height int `json:"height" gql:"height"` // Coordinate is where the picture was taken: the EXIF GPS tag when present, // and the parent Place otherwise. Coordinate *Coordinate `json:"coordinate" gql:"coordinate"` // TakenAt, CameraMake and CameraModel come from EXIF. TakenAt *time.Time `json:"takenAt" gql:"takenAt"` CameraMake string `json:"cameraMake" gql:"cameraMake"` CameraModel string `json:"cameraModel" gql:"cameraModel"` Badges []Badge `json:"badges" gql:"badges"` AnalysisStatus string `json:"analysisStatus" gql:"analysisStatus"` AnalysisError string `json:"analysisError" gql:"analysisError"` // LinkedImages are other pictures taken at the same location. They are // resolved by the linking pass on read, never stored. LinkedImages []Image `json:"linkedImages" gql:"linkedImages"` CreatedAt time.Time `json:"createdAt" gql:"createdAt"` } // Place is a location the user visited and shared. It anchors geographic linking. type Place struct { ID string `json:"id" gql:"id"` Title string `json:"title" gql:"title"` Description string `json:"description" gql:"description"` // Coordinate is required for linking to work. LinkRadiusMeters is how close // two pictures must be to count as the same place. Coordinate *Coordinate `json:"coordinate" gql:"coordinate"` LinkRadiusMeters float64 `json:"linkRadiusMeters" gql:"linkRadiusMeters"` Creator User `json:"creator" gql:"creator"` Images []Image `json:"images" gql:"images"` Comments []Comment `json:"comments" gql:"comments"` // RelatedPlaces are other Places whose pictures overlap this one. RelatedPlaces []RelatedPlace `json:"relatedPlaces" gql:"relatedPlaces"` CreatedAt time.Time `json:"createdAt" gql:"createdAt"` } // RelatedPlace records the link between two Places discovered by the linking // pass, along with the evidence for it. type RelatedPlace struct { Place Place `json:"place" gql:"place"` DistanceMeters float64 `json:"distanceMeters" gql:"distanceMeters"` SharedImageIDs []string `json:"sharedImageIds" gql:"sharedImageIds"` } // Comment is a user reaction on a Place. type Comment struct { ID string `json:"id" gql:"id"` PlaceID string `json:"placeId" gql:"placeId"` Content string `json:"content" gql:"content"` Creator User `json:"creator" gql:"creator"` CreatedAt time.Time `json:"createdAt" gql:"createdAt"` } // Link is a single edge produced by the linking pass, connecting one picture to // another taken at the same place. Links are a diagnostic view and are not // exposed over the API. type Link struct { ImageID string `json:"imageId" gql:"-"` RelatedImageID string `json:"relatedImageId" gql:"-"` DistanceMeters float64 `json:"distanceMeters" gql:"-"` // SamePlace is true when both pictures belong to the same Place record. SamePlace bool `json:"samePlace" gql:"-"` Reason string `json:"reason" gql:"-"` } // PlaceInput is the payload for creating a Place, and the argument to the // createPlace mutation. Latitude and Longitude are pointers so an omitted // coordinate can be told apart from the origin, which is a legitimate point. type PlaceInput struct { Title string `json:"title" gql:"title"` Description string `json:"description" gql:"description"` Latitude *float64 `json:"latitude" gql:"latitude"` Longitude *float64 `json:"longitude" gql:"longitude"` LinkRadiusMeters *float64 `json:"linkRadiusMeters" gql:"linkRadiusMeters"` // Caption is the description of the first picture in the upload, if any. Caption string `json:"caption" gql:"caption"` } // --- Queries --- type Query struct { GetPlace(id string) *Place `gql:"place"` GetAllPlaces() []*Place `gql:"places"` GetImages() []*Image `gql:"images"` GetRelatedPlaces() []*RelatedPlace `gql:"relatedPlaces"` } // --- Mutations --- type Mutation struct { CreatePlace(input PlaceInput) *Place `gql:"createPlace"` AddComment(placeID, content) *Comment `gql:"addComment"` UpdateImageCaption(imageID, caption) *Image `gql:"updateImageCaption"` DeletePlace(id string) bool `gql:"deletePlace"` DeleteImage(id string) bool `gql:"deleteImage"` } // --- REST surface --- // // The React client uses these instead of GraphQL; both serialise the same // domain structs, so the shapes match field for field. Enabled with // ENABLE_REST=true. // // GET /api/places -> []Place // POST /api/places -> Place // GET /api/places/{placeId} -> Place (with relatedPlaces) // GET /api/places/{placeId}/images -> []Image // POST /places/{placeId}/images -> {"image": Image} multipart