diff --git a/backend-spec.md b/backend-spec.md index 1a823d0..0053d10 100644 --- a/backend-spec.md +++ b/backend-spec.md @@ -1,4 +1,4 @@ -## 0.4 Validated behaviors and invariants +## 0.5 Validated v2 household behaviors (tests snapshot) - GET `/api/v1/households/{householdSlug}/shopping/{listId}` returning purchased list + lookups scoped to household; cross-household returns 404. - POST `/api/v1/households/{householdSlug}/meals/{mealId}/consumed` marks a meal consumed within the household; validates timezone on provided `consumedDate`; clears outstanding meal requests only within that household. - Tests: `tests/test_meals_consumed_v2.py` validates scoping (requests cleared in same household, unaffected in other household). PASS. @@ -10,7 +10,7 @@ This document has been validated against the current codebase (v1) to ensure the plan captures all required changes. It starts with a concise baseline of what exists today, then details the v2 multi-tenancy/auth refactor with concrete, file-scoped steps and acceptance criteria. -Date reviewed: 2025-11-01 (updated after completing meals write flows, scoped endpoints, and v1 removal) +Date reviewed: 2025-11-01 (updated after porting legacy coverage to v2; all checks green; OpenAPI exported) Repo modules checked: `main.py`, `api/*` (v2 routers only), `persons/*` (legacy, pending removal), `meals/*`, `ingredients/*`, `recipes/*`, `products/*`, `shopping/*`, `common.py`, `db.py`, `settings.py`, tests in `tests/*`. @@ -278,23 +278,16 @@ Impact on existing routes (exact files to refactor): ## 5. Gaps vs Current Codebase (summary) -What’s missing today (must be implemented in v2): -- JWT-based authentication (register/login/google) and `users` domain. Replace `persons` entirely. -- Household domain: `households`, `household_members`, `household_invitations` tables, repos, and APIs. -- Household scoping: path prefixes, membership checks, repository filtering by `household_id` across all data tables. -- OpenAPI security scheme update to JWT bearer; `403` responses for membership violations. +Status summary: +- Implemented: JWT auth v2 with refresh cookie; household domains and membership; invitations; full household scoping across recipes/meals/shopping; OpenAPI augmentation with bearerAuth and 403; RFC7807 preserved; tests green. +- Preserved from v1: camelCase response models, `Page` pagination and cursor semantics, `Location` headers on create, shopping storeName normalization ("home"). -Immediate priorities (feedback-incorporated): -- Upgrade password hashing from SHA-256 to a secure, adaptive scheme (PBKDF2/bcrypt/argon2). DONE with PBKDF2; consider migrating to argon2 in future. -- Finalize and commit `openapi.json` with household-scoped routes to unblock frontend (temporary X-Household-Slug header can be removed). -- Plan v1 cleanup: once parity is achieved for scoped routes, rename `*_v2.py` to canonical filenames and remove original v1 modules to avoid long-term duplication. - -What to preserve from v1: -- CamelCase response keys, non-null collection properties, `Page` envelope and cursor semantics, RFC7807 responses, `Location` header on create. -- Shopping outward storeName normalization ("home" instead of empty string). - -Nice-to-have carryovers (already partially implemented as per `tighten-api-spec.md`): -- Outward union models for shopping list items to reduce nullability (implemented in API without changing DB schema). +Remaining work (post-v2 cleanup): +- Remove `persons/` package and any residual references; consolidate entirely on `users`. +- Optional: migrate PBKDF2 to argon2/bcrypt and add password rehash-on-login. +- Invitations: integrate email delivery provider and track send status. +- DB: Add composite indices like `(household_id, id)` for common pagination; evaluate adding FK constraints from tenant tables to `households(id)` where safe. +- Rename `*_v2.py` modules back to canonical names once v1 has been fully retired (routers already removed) to reduce duplication noise. --- diff --git a/tests/test_meals_write_v2.py b/tests/test_meals_write_v2.py index 75e03ae..d026353 100644 --- a/tests/test_meals_write_v2.py +++ b/tests/test_meals_write_v2.py @@ -92,21 +92,27 @@ class TestMealsWriteV2(unittest.IsolatedAsyncioTestCase): # No chefs body = dict(base) body["chefs"] = [] - r = self.client.post(f"/api/v1/households/{self.slug}/meals", headers=self.headers, json=body) + r = self.client.post( + f"/api/v1/households/{self.slug}/meals", headers=self.headers, json=body + ) assert r.status_code == 400 assert "chef" in r.json()["title"].lower() # No cleanup body = dict(base) body["cleanup"] = [] - r = self.client.post(f"/api/v1/households/{self.slug}/meals", headers=self.headers, json=body) + r = self.client.post( + f"/api/v1/households/{self.slug}/meals", headers=self.headers, json=body + ) assert r.status_code == 400 assert "cleanup" in r.json()["title"].lower() # No consumers body = dict(base) body["consumers"] = [] - r = self.client.post(f"/api/v1/households/{self.slug}/meals", headers=self.headers, json=body) + r = self.client.post( + f"/api/v1/households/{self.slug}/meals", headers=self.headers, json=body + ) assert r.status_code == 400 assert "consumer" in r.json()["title"].lower() @@ -114,14 +120,18 @@ class TestMealsWriteV2(unittest.IsolatedAsyncioTestCase): body = dict(base) body["recipes"] = [] body["extraIngredients"] = [] - r = self.client.post(f"/api/v1/households/{self.slug}/meals", headers=self.headers, json=body) + r = self.client.post( + f"/api/v1/households/{self.slug}/meals", headers=self.headers, json=body + ) assert r.status_code == 400 assert "recipe" in r.json()["title"].lower() or "ingredient" in r.json()["title"].lower() # Zero servings in recipe body = dict(base) body["recipes"] = [{"mealId": -1, "recipeId": 1, "servings": 0}] - r = self.client.post(f"/api/v1/households/{self.slug}/meals", headers=self.headers, json=body) + r = self.client.post( + f"/api/v1/households/{self.slug}/meals", headers=self.headers, json=body + ) assert r.status_code == 400 assert "servings" in r.json()["title"].lower()