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
47 lines
2.7 KiB
SQL
47 lines
2.7 KiB
SQL
-- 0003_seed_plants.sql — built-in plant catalog.
|
|
--
|
|
-- owner_id NULL marks a read-only built-in (see 0001_init.sql § plants). These
|
|
-- seed once: the migration runner records this version in schema_migrations, so
|
|
-- re-running Migrate is a no-op and never duplicates them. Users clone a built-in
|
|
-- (POST a copy owned by themselves) to customize; the originals stay immutable.
|
|
--
|
|
-- spacing_cm is mature in-row spacing and drives derived plop counts (#14).
|
|
-- color is a display hex; icon is an emoji (v1 ships zero image assets).
|
|
|
|
INSERT INTO plants (owner_id, name, category, spacing_cm, color, icon) VALUES
|
|
-- Alliums & vegetables
|
|
(NULL, 'Garlic', 'vegetable', 15, '#d9d2c5', '🧄'),
|
|
(NULL, 'Onion', 'vegetable', 10, '#b07fc7', '🧅'),
|
|
(NULL, 'Bush bean', 'vegetable', 10, '#6b8e23', '🫘'),
|
|
(NULL, 'Pole bean', 'vegetable', 15, '#4e7a27', '🫘'),
|
|
(NULL, 'Pea', 'vegetable', 8, '#8bc34a', '🫛'),
|
|
(NULL, 'Tomato', 'vegetable', 60, '#e53935', '🍅'),
|
|
(NULL, 'Pepper', 'vegetable', 45, '#d32f2f', '🌶️'),
|
|
(NULL, 'Cucumber', 'vegetable', 30, '#43a047', '🥒'),
|
|
(NULL, 'Zucchini', 'vegetable', 60, '#2e7d32', '🥒'),
|
|
(NULL, 'Winter squash', 'vegetable', 90, '#ef6c00', '🎃'),
|
|
(NULL, 'Carrot', 'vegetable', 8, '#f57c00', '🥕'),
|
|
(NULL, 'Radish', 'vegetable', 5, '#e91e63', '🌱'),
|
|
(NULL, 'Beet', 'vegetable', 10, '#8e24aa', '🌱'),
|
|
(NULL, 'Lettuce', 'vegetable', 20, '#7cb342', '🥬'),
|
|
(NULL, 'Spinach', 'vegetable', 10, '#33691e', '🥬'),
|
|
(NULL, 'Kale', 'vegetable', 45, '#2e5d34', '🥬'),
|
|
(NULL, 'Broccoli', 'vegetable', 45, '#3f7d3f', '🥦'),
|
|
(NULL, 'Cabbage', 'vegetable', 45, '#8bc98b', '🥬'),
|
|
(NULL, 'Potato', 'vegetable', 30, '#a1887f', '🥔'),
|
|
(NULL, 'Corn', 'vegetable', 25, '#fbc02d', '🌽'),
|
|
-- Herbs
|
|
(NULL, 'Basil', 'herb', 25, '#4a7c3f', '🌿'),
|
|
(NULL, 'Cilantro', 'herb', 10, '#6aa84f', '🌿'),
|
|
(NULL, 'Dill', 'herb', 15, '#7cb342', '🌿'),
|
|
(NULL, 'Parsley', 'herb', 15, '#388e3c', '🌿'),
|
|
(NULL, 'Oregano', 'herb', 25, '#558b2f', '🌿'),
|
|
(NULL, 'Thyme', 'herb', 20, '#7a9b6e', '🌿'),
|
|
(NULL, 'Rosemary', 'herb', 45, '#4b6b47', '🌿'),
|
|
(NULL, 'Sage', 'herb', 45, '#9caf88', '🌿'),
|
|
(NULL, 'Mint', 'herb', 30, '#66bb6a', '🌿'),
|
|
-- Fruit & flowers
|
|
(NULL, 'Strawberry', 'fruit', 30, '#e91e63', '🍓'),
|
|
(NULL, 'Sunflower', 'flower', 45, '#fdd835', '🌻'),
|
|
(NULL, 'Marigold', 'flower', 25, '#fb8c00', '🌼');
|