116 lines
5 KiB
Go
116 lines
5 KiB
Go
// Package store defines persistence for the domain entities. The interface
|
|
// exists so that the query implementation can be swapped (a different SQL
|
|
// dialect, or an in-memory fake in tests) without touching the service layer.
|
|
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"go-places-api/internal/domain"
|
|
"go-places-api/internal/geo"
|
|
)
|
|
|
|
var (
|
|
// ErrNotFound is returned when a lookup matches no row.
|
|
ErrNotFound = errors.New("not found")
|
|
// ErrConflict is returned when a unique constraint would be violated.
|
|
ErrConflict = errors.New("conflict")
|
|
)
|
|
|
|
// ImageFilter narrows a picture search.
|
|
type ImageFilter struct {
|
|
// PlaceID restricts results to one place when non-empty.
|
|
PlaceID string
|
|
// CreatorID restricts results to one author when non-empty.
|
|
CreatorID string
|
|
// Within restricts results to pictures inside this box and radius. A nil
|
|
// Coordinate disables the geographic restriction.
|
|
Within *NearbyQuery
|
|
// Limit caps the number of rows. Zero means unlimited.
|
|
Limit int
|
|
// Offset skips rows for pagination.
|
|
Offset int
|
|
}
|
|
|
|
// NearbyQuery selects entities within a radius of a point.
|
|
type NearbyQuery struct {
|
|
Center domain.Coordinate
|
|
RadiusMeters float64
|
|
}
|
|
|
|
// Store is the persistence contract required by the service layer.
|
|
type Store interface {
|
|
// CreatePlace inserts a place and returns it as persisted.
|
|
CreatePlace(ctx context.Context, p domain.Place) (domain.Place, error)
|
|
// GetPlace loads a place, its pictures, comments and author.
|
|
GetPlace(ctx context.Context, id string) (domain.Place, error)
|
|
// UpdatePlace replaces the mutable fields of a place.
|
|
UpdatePlace(ctx context.Context, p domain.Place) (domain.Place, error)
|
|
// ListPlaces returns places newest first.
|
|
ListPlaces(ctx context.Context, limit, offset int) ([]domain.Place, error)
|
|
// DeletePlace removes a place and everything attached to it.
|
|
DeletePlace(ctx context.Context, id string) error
|
|
|
|
// CreateImage inserts a picture record.
|
|
CreateImage(ctx context.Context, img domain.Image) (domain.Image, error)
|
|
// GetImage loads one picture.
|
|
GetImage(ctx context.Context, id string) (domain.Image, error)
|
|
// GetImageByStorageKey loads the picture a blob key belongs to. The media
|
|
// route needs it to learn the declared content type and size of a stored
|
|
// object without trusting the file's own bytes.
|
|
GetImageByStorageKey(ctx context.Context, key string) (domain.Image, error)
|
|
// UpdateImage replaces the mutable fields of a picture.
|
|
UpdateImage(ctx context.Context, img domain.Image) (domain.Image, error)
|
|
// ListImages returns pictures matching the filter, newest first.
|
|
ListImages(ctx context.Context, f ImageFilter) ([]domain.Image, error)
|
|
// ImagesWithin returns pictures whose capture point lies inside the radius.
|
|
ImagesWithin(ctx context.Context, q NearbyQuery) ([]domain.Image, error)
|
|
// ReplaceBadges swaps the badge set for a picture.
|
|
ReplaceBadges(ctx context.Context, imageID string, badges []domain.Badge) error
|
|
// CreateImageLink records that two pictures were taken at the same place.
|
|
// Re-recording the same pair updates the measured distance.
|
|
CreateImageLink(ctx context.Context, imageID, relatedImageID string, distanceMeters float64, reason string) error
|
|
// ListImageLinks returns the recorded links for a picture in both
|
|
// directions, so a link is visible from either end.
|
|
ListImageLinks(ctx context.Context, imageID string) ([]domain.Link, error)
|
|
// SetAnalysisState records the outcome of the analysis pipeline.
|
|
SetAnalysisState(ctx context.Context, imageID string, status domain.AnalysisStatus, failure string) error
|
|
// DeleteImage removes a picture and its badges.
|
|
DeleteImage(ctx context.Context, id string) error
|
|
|
|
// AddComment inserts a comment and returns it as persisted.
|
|
AddComment(ctx context.Context, c domain.Comment) (domain.Comment, error)
|
|
// ListComments returns the comments on a place, oldest first.
|
|
ListComments(ctx context.Context, placeID string) ([]domain.Comment, error)
|
|
|
|
// UpsertUser stores the identity asserted by the auth layer.
|
|
UpsertUser(ctx context.Context, u domain.User) error
|
|
// GetUser loads a user by identifier.
|
|
GetUser(ctx context.Context, id string) (domain.User, error)
|
|
|
|
// Close releases resources held by the store.
|
|
Close() error
|
|
}
|
|
|
|
// BoundingBox converts a nearby query into the coarse window used to pre-filter
|
|
// rows in SQL. Exposed here so implementations and tests agree on the filter.
|
|
func (q NearbyQuery) BoundingBox() geo.BoundingBox {
|
|
return geo.Around(q.Center, q.RadiusMeters)
|
|
}
|
|
|
|
// ErrInvalidInput is returned when a write would violate a domain rule, such as
|
|
// linking a picture to itself.
|
|
var ErrInvalidInput = errors.New("invalid input")
|
|
|
|
// ImageGetter is the optional extra surface the service uses for storage-key
|
|
// lookups. It is separate from Store so an implementation that cannot map a key
|
|
// back to a record simply does not satisfy it.
|
|
type ImageGetter interface {
|
|
GetImageByStorageKey(ctx context.Context, key string) (domain.Image, error)
|
|
}
|
|
|
|
// Pinger is the optional extra surface used by the readiness probe.
|
|
type Pinger interface {
|
|
Ping(ctx context.Context) error
|
|
}
|