160 lines
6.9 KiB
GraphQL
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
|