// Package domain contains the core entities of the application. It has no // dependencies on transport, storage or database layers so that the business // rules can be reasoned about in isolation. package domain import "time" // 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"` } // Coordinate is a WGS-84 geographic point. type Coordinate struct { Latitude float64 `json:"latitude" gql:"latitude"` Longitude float64 `json:"longitude" gql:"longitude"` } // Valid reports whether the coordinate is a real point on Earth. func (c Coordinate) Valid() bool { if c.Latitude < -90 || c.Latitude > 90 { return false } return c.Longitude >= -180 && c.Longitude <= 180 } // AnalysisStatus tracks the progress of the image analysis pipeline. Analysis // runs out of band, so a picture is visible to the API before its badges exist. type AnalysisStatus string const ( AnalysisPending AnalysisStatus = "pending" AnalysisCompleted AnalysisStatus = "completed" AnalysisFailed AnalysisStatus = "failed" ) // Badge is a recognisable attribute detected in a picture, for example an // object ("dog"), a scene ("beach") or a property ("low-light"). type Badge struct { // Label is the stable machine-readable name, e.g. "dog" or "beach". Label string `json:"label" gql:"label"` // Display is a human-friendly rendering of the label, e.g. "Dog". Display string `json:"display" gql:"display"` // Confidence is a 0..1 score. Heuristic badges tend to be lower confidence // than those returned by a trained model. Confidence float64 `json:"confidence" gql:"confidence"` // Source records which analyzer produced the badge, which lets the UI // distinguish machine-derived facts from heuristics. 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 not // exposed to clients; 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. It is taken from the EXIF GPS // tag when present, and falls back to the parent Place otherwise. Coordinate *Coordinate `json:"coordinate" gql:"coordinate"` // TakenAt is when the picture was captured, from the EXIF timestamp. TakenAt *time.Time `json:"takenAt" gql:"takenAt"` // CameraMake and CameraModel come from EXIF and identify the capture device. CameraMake string `json:"cameraMake" gql:"cameraMake"` CameraModel string `json:"cameraModel" gql:"cameraModel"` Badges []Badge `json:"badges" gql:"badges"` AnalysisStatus AnalysisStatus `json:"analysisStatus" gql:"analysisStatus"` AnalysisError string `json:"analysisError" gql:"analysisError"` // LinkedImages are other pictures taken at the same location, resolved by // the linking pass rather than stored. LinkedImages []Image `json:"linkedImages" gql:"linkedImages"` CreatedAt time.Time `json:"createdAt" gql:"createdAt"` } // Place is a location the user visited and shared. Pictures and a free-text // description hang off it, and it is the anchor for 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 the location of the place. Required for linking to work; // when a picture carries its own EXIF GPS tag that value takes precedence. Coordinate *Coordinate `json:"coordinate" gql:"coordinate"` // LinkRadiusMeters is how close two pictures must be to count as having // been taken at the same place. 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"` } // Link is a single edge produced by the linking pass, connecting one picture to // another that was taken at the same place. type Link struct { ImageID string `json:"imageId" gql:"imageId"` RelatedImageID string `json:"relatedImageId" gql:"relatedImageId"` // DistanceMeters is the great-circle distance between the two capture points. DistanceMeters float64 `json:"distanceMeters" gql:"distanceMeters"` // SamePlace is true when the two pictures belong to the same Place record. SamePlace bool `json:"samePlace" gql:"samePlace"` // Reason explains the evidence, e.g. "same-place" or "within-radius". Reason string `json:"reason" gql:"reason"` } // 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"` } // PlaceInput is the payload for creating a Place. Latitude and Longitude are // pointers so that an omitted coordinate can be told apart from the origin, // which is a legitimate coordinate. 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"` } // Coordinate builds a Coordinate from the optional latitude and longitude, // returning nil when they are absent or do not form a valid point. func (p PlaceInput) Coordinate() *Coordinate { if p.Latitude == nil || p.Longitude == nil { return nil } c := Coordinate{Latitude: *p.Latitude, Longitude: *p.Longitude} if !c.Valid() { return nil } return &c }