Extends the plants store (which had only the /full read side) with full
catalog CRUD, adds a service layer with visibility/immutability rules, REST
endpoints, and a seed migration of ~32 built-ins.
- 0003_seed_plants.sql: 32 built-in plants (owner_id NULL, read-only), seeded
once by the version-tracked migration runner.
- store: ListPlantsForActor (built-ins + own), Get/Create/Update/Delete +
CountPlantingsForPlant; isForeignKeyViolation backstop for the RESTRICT FK.
- service: built-ins are read-only (ErrForbidden); another user's plants are
invisible (ErrNotFound); delete of a referenced plant → ErrPlantInUse (409);
validation mirrors the schema (category enum, spacing>0, hex color, icon).
- api: GET,POST /plants and PATCH,DELETE /plants/:id with the version guard.
- Made the migration-count store tests derive their expectation from the files
so future migrations don't break them.
Service + API tests cover visibility, built-in immutability, validation,
version conflict, and delete-in-use.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
Fixes from the PR #26 adversarial review (graded 18 real / 0 false positive).
Correctness / security
- maxGardenCM fixed to 10_000 (100 m), matching its comment — it was
100_000 cm (1 km), 10x too lax (5 models flagged this).
- Dimension validation now rejects NaN/Inf (which slip past naive
comparisons) and subnormal-tiny positives, via a finite [1cm, 100m]
check. Name (200) and notes (10_000) are length-capped so untrusted
input can't balloon storage.
- Update version binding is `required,min=1`, so a negative/zero version
is a 400, not a 409.
Maintainability / performance
- One unified writeServiceError (new errors.go) maps every auth + resource
sentinel; writeResourceError removed. writeVersionConflict and
parseIDParam moved to errors.go (shared, not in the gardens feature file).
- Request structs share an embedded gardenFields (one toInput).
- CreateGarden uses INSERT ... RETURNING (one round-trip).
- ListGardensForOwner has a defensive LIMIT (pagination is post-v1).
Tests: name/notes length, NaN/Inf/subnormal dims, dimension-at-cap valid,
negative/zero version -> 400.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi