Add plantings backend: plop CRUD, derived counts, removed_at (#14)
- domain: Planting gains a computed (non-persisted) DerivedCount field. - store/plantings.go: Get/Create/Update (version-guarded)/Delete alongside the existing /full read helper. - service/plantings.go: place/move/resize/soft-remove a plop; editor role on the object's garden; object must be plantable; plant_id must be visible to the actor (built-in or own) else ErrInvalidInput; center must sit within the object's unrotated local bounds (radius may overhang); planted_at defaults to today. derivedCount = max(1, round(π·r²/spacing²)) — one unit-tested helper, reused by /full (via a spacing map, no N+1) and single responses. - api: POST /objects/:id/plantings, PATCH/DELETE /plantings/:id; nullable count/label/plantedAt/removedAt use RawMessage so null (clear) is distinct from absent (unchanged). removedAt is the soft-remove / "clear bed" seam. - /full now enriches each active plop with its derivedCount. Service tests: formula edge cases (tiny radius → 1), defaults + derived, count override, move/resize + clear override, non-plantable rejection, foreign/unknown plant rejection, bounds, soft-remove leaves /full, version conflict, cross-user masking, delete. Plus an API-level create/patch/full/delete flow. GOWORK=off go build/vet/test ./internal/... green. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
This commit is contained in:
@@ -176,6 +176,15 @@ func (s *Service) GardenFull(ctx context.Context, actorID, gardenID int64) (*Ful
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Fill each plop's DerivedCount from its plant's spacing (referenced plants
|
||||
// are already loaded, so this is a map lookup rather than N queries).
|
||||
spacing := make(map[int64]float64, len(plants))
|
||||
for _, pl := range plants {
|
||||
spacing[pl.ID] = pl.SpacingCM
|
||||
}
|
||||
for i := range plantings {
|
||||
plantings[i].DerivedCount = derivedCount(plantings[i].RadiusCM, spacing[plantings[i].PlantID])
|
||||
}
|
||||
return &FullGarden{Garden: g, Objects: objects, Plantings: plantings, Plants: plants}, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user