From fa2cceddc334f7d1e5243a73ec42fa4dbf78d249 Mon Sep 17 00:00:00 2001 From: Paul Huliganga Date: Wed, 25 Mar 2026 10:25:45 -0400 Subject: [PATCH] [migration] Normalize schema to MVP target: split recipes into ingredients and steps tables, drop JSON columns, adjust tags, update migration docs. WARNING: Tests fail until API and tests are refactored for new structure. --- docs/recipe-manager-mvp-plan.md | 198 ++++++++++++++++++++++ docs/schema-migration-delta-log.md | 25 +++ migrations/2026-03-25-schema-migration.md | 61 +++++++ src/backend/db/schema.sql | 49 ++++-- 4 files changed, 317 insertions(+), 16 deletions(-) create mode 100644 docs/recipe-manager-mvp-plan.md create mode 100644 docs/schema-migration-delta-log.md create mode 100644 migrations/2026-03-25-schema-migration.md diff --git a/docs/recipe-manager-mvp-plan.md b/docs/recipe-manager-mvp-plan.md new file mode 100644 index 0000000..d6dd6b6 --- /dev/null +++ b/docs/recipe-manager-mvp-plan.md @@ -0,0 +1,198 @@ +# Recipe Manager — MVP Execution Plan + +**Date:** 2026-03-25 +**Owner:** Paul + Cleo +**Goal:** Ship a tight, usable MVP with clear scope and a fast implementation path. + +--- + +## 1) MVP Scope (Step 1 — locked) + +### In Scope (MVP) +1. **Recipe CRUD** + - Create recipe + - Read/list recipes + - View single recipe detail + - Edit recipe + - Delete recipe +2. **Tags** + - Create/delete tags + - Attach/remove tags on recipes + - Filter list by tag +3. **Ingredients + Steps** + - Ingredient list per recipe (qty/unit/item) + - Ordered instruction steps +4. **Search** + - Search by title + - Search by ingredient text + - Search by tag +5. **Backup portability** + - JSON export + - JSON import (merge strategy: skip duplicates by title+sourceUrl) + +### Out of Scope (Post-MVP) +- AI substitutions +- Meal planner UX (calendar) +- Shopping list automation +- Nutrition calculations +- OCR/image import +- Multi-user auth/roles + +### Definition of Done (MVP) +- User can create/edit/delete recipes with ingredients + steps +- User can tag and filter recipes +- User can search quickly by title/tag/ingredient +- User can export and import data JSON without data loss for core fields +- API + UI docs updated +- Docker compose run path works end-to-end + +--- + +## 2) Architecture (existing stack + MVP alignment) + +- **Backend:** Node/TypeScript REST API (SQLite) +- **Frontend:** React/TypeScript (Vite) +- **Storage:** SQLite file (`data/recipes.db`) +- **Deployment:** Docker + docker-compose + +MVP will reuse existing architecture and avoid stack churn. + +--- + +## 3) Data Model (Step 2 target) + +### Tables +- `recipes` + - `id` (PK) + - `title` (TEXT, required) + - `description` (TEXT) + - `servings` (INTEGER) + - `prep_time_minutes` (INTEGER) + - `cook_time_minutes` (INTEGER) + - `source_url` (TEXT) + - `created_at`, `updated_at` + +- `ingredients` + - `id` (PK) + - `recipe_id` (FK -> recipes.id) + - `position` (INTEGER) + - `quantity` (TEXT) + - `unit` (TEXT) + - `item` (TEXT, required) + - `notes` (TEXT) + +- `steps` + - `id` (PK) + - `recipe_id` (FK -> recipes.id) + - `position` (INTEGER) + - `instruction` (TEXT, required) + +- `tags` + - `id` (PK) + - `name` (TEXT UNIQUE, required) + +- `recipe_tags` + - `recipe_id` (FK -> recipes.id) + - `tag_id` (FK -> tags.id) + - composite PK (`recipe_id`, `tag_id`) + +### Suggested indexes +- `idx_recipes_title` on `recipes(title)` +- `idx_ingredients_item` on `ingredients(item)` +- `idx_recipe_tags_tag_id` on `recipe_tags(tag_id)` + +--- + +## 4) API Surface (MVP) + +### Recipes +- `GET /api/recipes?query=&tag=` +- `GET /api/recipes/:id` +- `POST /api/recipes` +- `PUT /api/recipes/:id` +- `DELETE /api/recipes/:id` + +### Tags +- `GET /api/tags` +- `POST /api/tags` +- `DELETE /api/tags/:id` + +### Backup +- `GET /api/export/json` +- `POST /api/import/json` + +--- + +## 5) Frontend Surface (MVP) + +- `RecipeListPage` + - search box + - tag filter + - recipe cards/list +- `RecipeDetailPage` + - metadata + ingredients + steps +- `RecipeFormPage` + - create/edit form with dynamic ingredient + step rows +- `TagManager` + - add/remove tags +- `ImportExportPanel` + - export button + - import file upload + summary + +--- + +## 6) File-by-File Build Board + +### Backend +1. DB schema/migrations (recipes, ingredients, steps, tags, recipe_tags) +2. Recipe repository/services with nested ingredient/step handling +3. Search query enhancements (title/ingredient/tag) +4. Tag endpoints + recipe-tag mapping logic +5. JSON export endpoint +6. JSON import endpoint + dedupe policy +7. API tests for CRUD + search + import/export + +### Frontend +1. Recipe form supports ordered ingredients/steps +2. List page unified search + tag filter +3. Detail page renders normalized sections +4. Tag management wiring +5. Import/export UI panel +6. UI tests for key flows + +### Docs +1. `docs/api.md` update +2. `docs/user-guide.md` update +3. README MVP usage + backup section + +--- + +## 7) 6-Day Execution Plan + +- **Day 1:** Scope freeze + schema lock + migration prep +- **Day 2:** CRUD hardening (nested ingredients/steps correctness) +- **Day 3:** Search + tag filter backend/frontend alignment +- **Day 4:** Import/export JSON endpoints + UI integration +- **Day 5:** Test pass, seed data, bug fixes +- **Day 6:** Docs + Docker validation + release tag + +--- + +## 8) Immediate Next Actions (Starting now) + +1. ✅ **Step 1 complete:** MVP scope locked in this document. +2. Next: verify existing TODO/roadmap lines up with locked scope. +3. Then start Step 2 (data model lock) by checking current schema vs target model and creating the migration delta list. + +--- + +## 9) Risks / Decisions to keep an eye on + +- Duplicate logic for import merge rules (title collisions) +- Tag naming normalization (case-insensitive unique) +- Ingredient parsing consistency for search quality +- Backward compatibility with existing recipe records + +--- + +If scope changes are requested, update this file first and treat it as the source of truth for implementation sequencing. diff --git a/docs/schema-migration-delta-log.md b/docs/schema-migration-delta-log.md new file mode 100644 index 0000000..63b69ca --- /dev/null +++ b/docs/schema-migration-delta-log.md @@ -0,0 +1,25 @@ +# Schema Migration Delta Log — 2026-03-25 + +Date: 2026-03-25 + +## Delta Summary + +- Normalized `recipes` table: removed JSON columns for ingredients and instructions +- Added `ingredients` table (with position/order, FK to recipe) +- Added `steps` table (with position/order, FK to recipe) +- Removed `notes` and `last_cooked_at` columns from recipes +- Changed timestamp columns to DATETIME for better SQLite compatibility +- Removed color from tags table +- Added index on `ingredients(item)` +- Confirmed/completed indexes per spec + +See [../migrations/2026-03-25-schema-migration.md](../migrations/2026-03-25-schema-migration.md) for the detailed migration plan and SQL changes. + +--- + +## Action Items +- [ ] Implement migration logic (split JSON columns) +- [ ] Update `src/backend/db/schema.sql` with normalized tables +- [ ] Write data migration script for live DBs (extract and insert ingredient/step records) +- [ ] Run DB migration +- [ ] Update test cases and data fixtures \ No newline at end of file diff --git a/migrations/2026-03-25-schema-migration.md b/migrations/2026-03-25-schema-migration.md new file mode 100644 index 0000000..f97e841 --- /dev/null +++ b/migrations/2026-03-25-schema-migration.md @@ -0,0 +1,61 @@ +# Recipe Manager DB Schema Migration — 2026-03-25 + +## Schema Diff (current → MVP target) + +The current schema (src/backend/db/schema.sql) differs from the Step 2 MVP spec in docs/recipe-manager-mvp-plan.md as follows: + +### **recipes** table +**Current:** +- Has columns: id, title, description, ingredients (JSON), instructions (JSON), source_url, notes, servings, prep_time_minutes, cook_time_minutes, created_at (INTEGER), updated_at (INTEGER), last_cooked_at + +**Target:** +- Should be normalized: No JSON columns for ingredients/instructions. Should instead link to separate ingredients and steps tables via recipe_id FK. +- Remove: ingredients, instructions, notes, last_cooked_at columns. +- created_at and updated_at should be DATETIME. + +### **ingredients** table +- Does NOT exist in current schema. **Add ingredients table:** + - id (PK) + - recipe_id (FK) + - position (INTEGER) + - quantity (TEXT) + - unit (TEXT) + - item (TEXT, required) + - notes (TEXT) + +### **steps** table +- Does NOT exist. **Add steps table:** + - id (PK) + - recipe_id (FK) + - position (INTEGER) + - instruction (TEXT, required) + +### **tags/recipe_tags** +- Already present (mostly matches spec) +- Remove color column from tags (not in MVP spec) +- "name" should be unique and required (already declared) + +### **Indexes** +- Add idx_ingredients_item on ingredients(item) +- Add idx_recipe_tags_tag_id on recipe_tags(tag_id) (already present) + +--- + +## Migration Steps +1. Create new **ingredients** and **steps** tables +2. Populate ingredients & steps tables from JSON columns of recipes +3. Migrate tags: drop color column from tags (if exists) +4. Drop ingredients, instructions, notes, last_cooked_at columns from recipes +5. Change created_at, updated_at columns to DATETIME +6. Add new indexes (ingredients(item)) +7. Update test data and migration documentation + +--- + +## Notes +- This migration normalizes the schema: recipes → ingredients & steps as separate tables +- Data in existing recipes.ingredients and instructions must be extracted and split into new tables + +--- + +## Status: _DRAFT — pending implementation_ \ No newline at end of file diff --git a/src/backend/db/schema.sql b/src/backend/db/schema.sql index b07b007..5de2269 100644 --- a/src/backend/db/schema.sql +++ b/src/backend/db/schema.sql @@ -1,28 +1,46 @@ --- Create recipes table +-- SCHEMA VERSION: 2026-03-25 — MVP-NORMALIZED + +-- Recipes table (normalized) CREATE TABLE recipes ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, - ingredients TEXT NOT NULL, -- JSON array - instructions TEXT NOT NULL, -- JSON array of steps - source_url TEXT, - notes TEXT, servings INTEGER, prep_time_minutes INTEGER, cook_time_minutes INTEGER, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - last_cooked_at INTEGER + source_url TEXT, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL ); --- Create tags table +-- Ingredients table +CREATE TABLE ingredients ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + recipe_id INTEGER NOT NULL, + position INTEGER, + quantity TEXT, + unit TEXT, + item TEXT NOT NULL, + notes TEXT, + FOREIGN KEY (recipe_id) REFERENCES recipes(id) ON DELETE CASCADE +); + +-- Steps table +CREATE TABLE steps ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + recipe_id INTEGER NOT NULL, + position INTEGER, + instruction TEXT NOT NULL, + FOREIGN KEY (recipe_id) REFERENCES recipes(id) ON DELETE CASCADE +); + +-- Tags table (no color) CREATE TABLE tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT UNIQUE NOT NULL, - color TEXT -- Hex color for UI + name TEXT UNIQUE NOT NULL ); --- Create recipe_tags join table +-- Recipe_tags join table CREATE TABLE recipe_tags ( recipe_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, @@ -31,8 +49,7 @@ CREATE TABLE recipe_tags ( FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE ); --- Indexes for efficiency +-- Indexes CREATE INDEX idx_recipes_title ON recipes(title); -CREATE INDEX idx_recipes_created_at ON recipes(created_at DESC); -CREATE INDEX idx_recipe_tags_recipe ON recipe_tags(recipe_id); -CREATE INDEX idx_recipe_tags_tag ON recipe_tags(tag_id); +CREATE INDEX idx_ingredients_item ON ingredients(item); +CREATE INDEX idx_recipe_tags_tag_id ON recipe_tags(tag_id); \ No newline at end of file