Season view: filter the editor to a year (#54) (#66)
Build image / build-and-push (push) Successful in 17s
Build image / build-and-push (push) Successful in 17s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #66.
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gitea.stevedudenhoeffer.com/steve/pansy/internal/domain"
|
||||
@@ -197,28 +198,80 @@ func (s *Service) DeleteObject(ctx context.Context, actorID, objectID int64) err
|
||||
}
|
||||
|
||||
// GardenFull returns the whole editor payload for a garden the actor can view.
|
||||
func (s *Service) GardenFull(ctx context.Context, actorID, gardenID int64) (*FullGarden, error) {
|
||||
// year nil means "what's planted now"; a year means the season view (#54).
|
||||
func (s *Service) GardenFull(ctx context.Context, actorID, gardenID int64, year *int) (*FullGarden, error) {
|
||||
g, err := s.requireGardenRole(ctx, actorID, gardenID, roleViewer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.assembleFull(ctx, g)
|
||||
if year != nil && (*year < minSeasonYear || *year > maxSeasonYear) {
|
||||
return nil, domain.ErrInvalidInput
|
||||
}
|
||||
return s.assembleFullFor(ctx, g, year)
|
||||
}
|
||||
|
||||
// Seasons are bounded to keep a typo'd year from producing a confidently empty
|
||||
// garden; the range is wide enough to cover any real record.
|
||||
const (
|
||||
minSeasonYear = 1900
|
||||
maxSeasonYear = 2200
|
||||
)
|
||||
|
||||
// GardenYears lists the calendar years a garden has planting data for, newest
|
||||
// first, always including the current one.
|
||||
//
|
||||
// This exists so the year selector can offer only years that actually hold
|
||||
// something. A free numeric field invites a typo and an empty view that looks
|
||||
// like data loss rather than a mistake.
|
||||
func (s *Service) GardenYears(ctx context.Context, actorID, gardenID int64) ([]int, error) {
|
||||
if _, err := s.requireGardenRole(ctx, actorID, gardenID, roleViewer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
years, err := s.store.GardenPlantingYears(ctx, gardenID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
current := s.now().UTC().Year()
|
||||
for _, y := range years {
|
||||
if y == current {
|
||||
return years, nil
|
||||
}
|
||||
}
|
||||
// Newest first, and this year is newest unless the data runs into the future.
|
||||
out := append([]int{current}, years...)
|
||||
sort.Sort(sort.Reverse(sort.IntSlice(out)))
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// assembleFull builds the read-only /full payload for an already-authorized
|
||||
// garden: its objects, active plops (with DerivedCount filled in), and the
|
||||
// plants those plops reference. Shared by the authenticated GardenFull and the
|
||||
// unauthenticated PublicGarden read, so both return the identical shape.
|
||||
// garden as it stands NOW. Shared by the unauthenticated PublicGarden read,
|
||||
// which never offers a season view.
|
||||
func (s *Service) assembleFull(ctx context.Context, g *domain.Garden) (*FullGarden, error) {
|
||||
return s.assembleFullFor(ctx, g, nil)
|
||||
}
|
||||
|
||||
// assembleFullFor builds the /full payload: objects, plops (with DerivedCount
|
||||
// filled in), and the plants those plops reference.
|
||||
//
|
||||
// year nil is today's behaviour — active plops only. A year widens it to every
|
||||
// plop whose time in the ground overlapped that year, which necessarily includes
|
||||
// plops since removed, so the referenced-plant lookup has to widen with it or
|
||||
// those plops would render with no icon and no colour.
|
||||
func (s *Service) assembleFullFor(ctx context.Context, g *domain.Garden, year *int) (*FullGarden, error) {
|
||||
objects, err := s.store.ListObjectsForGarden(ctx, g.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plantings, err := s.store.ListActivePlantingsForGarden(ctx, g.ID)
|
||||
var plantings []domain.Planting
|
||||
if year != nil {
|
||||
plantings, err = s.store.ListPlantingsForGardenYear(ctx, g.ID, *year)
|
||||
} else {
|
||||
plantings, err = s.store.ListActivePlantingsForGarden(ctx, g.ID)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plants, err := s.store.ListReferencedPlants(ctx, g.ID)
|
||||
plants, err := s.store.ListReferencedPlants(ctx, g.ID, year)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user