go-places/shared-interfaces/schema/models.graphql
2026-10-07 15:14:50 +02:00

160 lines
6.9 KiB
GraphQL

# --- 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