Spec updates
This commit is contained in:
parent
52b47769a6
commit
fc1f414bbb
1 changed files with 42 additions and 44 deletions
|
|
@ -1,18 +1,16 @@
|
||||||
## 0.5 Validated v2 household behaviors (tests snapshot)
|
## 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.
|
- 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.
|
- 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.
|
|
||||||
- POST `/api/v1/households/{householdSlug}/shopping/current/meals/me` requests a meal under the household scope; visible only within that household in GET current.
|
- POST `/api/v1/households/{householdSlug}/shopping/current/meals/me` requests a meal under the household scope; visible only within that household in GET current.
|
||||||
- DELETE `/api/v1/households/{householdSlug}/shopping/current/meals/{mealId}` unrequests the meal (scoped) and returns `{ ok: true }`.
|
- DELETE `/api/v1/households/{householdSlug}/shopping/current/meals/{mealId}` unrequests the meal (scoped) and returns `{ ok: true }`.
|
||||||
- Tests: `tests/test_shopping_request_meal_v2.py` validates request/unrequest isolation across households. PASS.
|
- Comprehensive v2 coverage exists for scoping, purchases, request/unrequest, meals CRUD/consumed, and OpenAPI security. PASS.
|
||||||
- Tests: `tests/test_shopping_household_v2.py` verifies isolation of outstanding items; `tests/test_shopping_list_by_id_v2.py` verifies list-by-id scoping; `tests/test_shopping_purchase_v2.py` covers scoped purchase (PASS).
|
|
||||||
# Backend Specification: Household Multi-Tenancy (v2)
|
# Backend Specification: Household Multi-Tenancy (v2)
|
||||||
|
|
||||||
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.
|
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 v2 cutover; Argon2 enabled; members endpoint added; all checks green; OpenAPI exported)
|
Date reviewed: 2025-11-01 (post-cutover: JWT + households live, Argon2 enabled, members endpoint added, v2 routers inlined; 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/*`.
|
Repo modules checked: `main.py`, `api/*` (v2-only; *_v2 modules removed), `users/*`, `households/*`, `meals/*`, `ingredients/*`, `recipes/*`, `products/*`, `shopping/*`, `common.py`, `db.py`, `settings.py`, tests in `tests/*`. `persons/*` remains for migration compatibility but has no routes.
|
||||||
|
|
||||||
Key conventions in v1:
|
Key conventions in v1:
|
||||||
- Response shape uses camelCase aliases (via `ApiModel` in `common.py`).
|
- Response shape uses camelCase aliases (via `ApiModel` in `common.py`).
|
||||||
|
|
@ -142,36 +140,31 @@ Refactor the backend from a single-tenant architecture to a robust, multi-tenant
|
||||||
|
|
||||||
## 3. API & Logic Changes
|
## 3. API & Logic Changes
|
||||||
|
|
||||||
### 3.1. Authentication API
|
### 3.1. Authentication API (current)
|
||||||
|
|
||||||
- **Refactor `api/auth.py`**:
|
- `api/auth.py` implements JWT-based authentication with Argon2 hashing for new passwords (PBKDF2 verification fallback for legacy hashes).
|
||||||
- Replace existing login/refresh logic with JWT-based authentication.
|
- `POST /api/v1/auth/register`: `{ email, password, displayName }` → creates User + LocalCredentials; returns access token; sets HttpOnly refresh cookie.
|
||||||
- **`POST /api/v1/auth/register`**: Body `{ email, password, displayName }`. Creates `User` and `LocalCredentials`. Returns JWT.
|
- `POST /api/v1/auth/login`: `{ email, password }` → validates; returns access token; sets HttpOnly refresh cookie.
|
||||||
- **`POST /api/v1/auth/login`**: Body `{ email, password }`. Validates credentials. Returns JWT.
|
- `POST /api/v1/auth/refresh`: returns a fresh access token (reads refresh cookie).
|
||||||
- **`POST /api/v1/auth/google`**: Body `{ token }`. Validates Google ID token, finds/creates `User` and `OAuthCredentials`. Returns JWT.
|
- `POST /api/v1/auth/logout`: clears refresh cookie.
|
||||||
- **JWT Structure**: Payload must contain `user_id`. Use short-lived access tokens with a long-lived refresh token stored in an `HttpOnly` cookie.
|
|
||||||
|
|
||||||
Notes from v1 baseline to carry forward:
|
Notes:
|
||||||
- Replace the `user_id` cookie dependency (`cookie_person`) with `get_current_user` (JWT) and update all protected endpoints in: `api/recipes.py`, `api/meals.py`, `api/shopping.py`.
|
- `get_current_user` (JWT) is used by protected endpoints (households, shopping purchase, etc.). Legacy `cookie_person` remains only for historical v1 module references and will be removed with full v2 completion.
|
||||||
- Preserve RFC7807 error semantics and the existing OpenAPI augmentation in `api/openapi.py`. The special-case 401 mapping for POST `/api/v1/shopping` will become unnecessary—JWT middleware should standardize 401/403.
|
- RFC7807 error semantics and the existing OpenAPI augmentation are preserved. The special-case 401 mapping for POST shopping is obsolete under JWT.
|
||||||
|
|
||||||
### 3.2. Household & Tenancy API
|
### 3.2. Household & Tenancy API (current)
|
||||||
|
|
||||||
- **Create `api/households.py`**:
|
- `api/households.py`:
|
||||||
- **`GET /api/v1/users/me/households`**: (Auth: JWT) Returns a list of the user's households.
|
- `GET /api/v1/users/me/households`: (JWT) lists the user's households.
|
||||||
- **`POST /api/v1/households`**: (Auth: JWT) Body `{ name }`. Creates a `Household` and adds the user as the first 'admin' member.
|
- `POST /api/v1/households`: (JWT) creates a household and adds the creator as admin.
|
||||||
- **Household-Scoped Routes**:
|
- Scoped router `/api/v1/households/{householdSlug}` with `GET /whoami` and `GET /members` (returns `{ id, displayName, role }`).
|
||||||
- All data-related routes must be prefixed: `/api/v1/households/{householdSlug}/...`.
|
|
||||||
- **Create a new dependency in `api/deps.py`**: `get_household_from_slug` will:
|
|
||||||
1. Take `householdSlug` from the path.
|
|
||||||
2. Verify the authenticated user (from JWT) is a member of that household.
|
|
||||||
3. Raise `HTTPException(403, "Forbidden")` if not a member.
|
|
||||||
4. Return the `household` object (or just `household_id`) for use in endpoints.
|
|
||||||
|
|
||||||
Impact on existing routes (exact files to refactor):
|
Household-scoped routes (implemented):
|
||||||
- `api/recipes.py`, `api/meals.py`, `api/shopping.py`, `api/products.py` → move under `APIRouter(prefix="/api/v1/households/{householdSlug}")` or mount via a `household_router` in `main.py`.
|
- `api/recipes_v2.py`: `/api/v1/households/{householdSlug}/recipes` list/get/create/delete.
|
||||||
- Repository functions must accept `household_id` and filter by it in all SELECT/INSERT/UPDATE. See `meals.repository`, `recipes.repository`, `ingredients.repository`, `shopping.repository`, `products.repository`.
|
- `api/meals.py`: `/api/v1/households/{householdSlug}/meals` upcoming/get/create/update/consumed/delete (inlined from v2).
|
||||||
- `persons` is replaced by `users + household_members`. Endpoints that currently return `Person` should instead resolve household-scoped `User` references.
|
- `api/shopping.py`: `/api/v1/households/{householdSlug}/shopping` current/list-by-id/purchase/request/unrequest (inlined from v2, with shared DTOs in `api/shopping_models.py`).
|
||||||
|
|
||||||
|
Repositories accept `household_id` and filter by it across `meals`, `recipes`, and `shopping` domains.
|
||||||
|
|
||||||
### 3.3. Invitation API
|
### 3.3. Invitation API
|
||||||
|
|
||||||
|
|
@ -263,15 +256,15 @@ Impact on existing routes (exact files to refactor):
|
||||||
|
|
||||||
5. **[✅] Update OpenAPI Specification**:
|
5. **[✅] Update OpenAPI Specification**:
|
||||||
- ✅ Augmentation updated in `api/openapi.py`:
|
- ✅ Augmentation updated in `api/openapi.py`:
|
||||||
- Adds `bearerAuth` security scheme while keeping `cookieAuth` for v1.
|
- Adds `bearerAuth` security scheme (cookieAuth removed).
|
||||||
- Marks household routes and `/users/me/*` with bearer security and adds `403` Problem response.
|
- Marks household routes and `/users/me/*` with bearer security and adds `403` Problem response.
|
||||||
- Preserves RFC7807 Problem responses and shopping storeName outward enum normalization.
|
- Preserves RFC7807 Problem responses and shopping storeName outward enum normalization.
|
||||||
- ✅ New tests `tests/test_openapi_security.py` verify presence of bearerAuth and 403s.
|
- ✅ New tests `tests/test_openapi_security.py` verify presence of bearerAuth and 403s.
|
||||||
- ✅ Export script writes updated `openapi.json`; re-run after adding meals v2 write endpoints to include them in the schema.
|
- ✅ Export script writes updated `openapi.json`; re-run after adding meals v2 write endpoints to include them in the schema.
|
||||||
|
|
||||||
6. **[✅] Refactor and Test**:
|
6. **[✅] Refactor and Test**:
|
||||||
- Tests updated to v2 household-scoped endpoints with JWT setup. Legacy v1 API tests are marked skipped (kept only as historical reference).
|
- Tests updated to v2 household-scoped endpoints with JWT setup. Legacy v1 API tests are currently skipped (`tests/test_main.py`, `tests/test_v1.py`).
|
||||||
- Full suite green under `make all-checks`. OpenAPI export updated successfully.
|
- Full suite green under `make all-checks`. OpenAPI export successful.
|
||||||
- Next cleanup: remove `persons/` package and remaining references in domain internals once users fully replace persons in models.
|
- Next cleanup: remove `persons/` package and remaining references in domain internals once users fully replace persons in models.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -285,12 +278,12 @@ Status summary:
|
||||||
- New: Household members listing `GET /api/v1/households/{householdSlug}/members` returning [{ id, displayName, role }].
|
- New: Household members listing `GET /api/v1/households/{householdSlug}/members` returning [{ id, displayName, role }].
|
||||||
- Preserved: camelCase responses, `Page<T>` semantics, `Location` headers on create, shopping storeName normalization ("home").
|
- Preserved: camelCase responses, `Page<T>` semantics, `Location` headers on create, shopping storeName normalization ("home").
|
||||||
|
|
||||||
Remaining work (prioritized cleanup):
|
Remaining work (prioritized cleanup to final state):
|
||||||
1. Remove `persons/` package and any residual references; consolidate entirely on `users` / `household_members` across repositories/services.
|
1. Remove the `persons/` package and all code references. Replace Person usages in domain models/services with `users`/household members. This includes any remaining dependencies in `api/recipes.py` and services still typed with `persons.Person`.
|
||||||
2. Recipes outward fields: migrate `createdBy`/`hiddenBy` to a user/member DTO (no Person) similar to meals MemberRef.
|
2. Recipes outward schema: replace `createdBy`/`hiddenBy` Person references with a `MemberRef`-style DTO (user/member) to fully eliminate Person from API responses.
|
||||||
3. Inline v2 content and delete `*_v2.py` files to reduce indirection; imports currently delegate cleanly and are mounted canonically.
|
3. Delete or port legacy v1 test modules that are currently skipped (`tests/test_main.py`, `tests/test_v1.py`). Either migrate assertions to v2 routes or remove them so the full test run has no skips.
|
||||||
4. Invitations: integrate email delivery provider and track send status.
|
4. Invitations: integrate an email delivery provider and track send status/expiry enforcement; add tests.
|
||||||
5. DB: Add composite indices like `(household_id, id)` for pagination; evaluate additional FKs to `Household(id)`.
|
5. Database polish: add composite indices like `(household_id, id)` for pagination; consider adding explicit FK constraints from tenant tables to `Household(id)` where safe.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -302,10 +295,10 @@ Remaining work (prioritized cleanup):
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Rollout & Migration Plan
|
## 7. Rollout & Migration Plan (updated)
|
||||||
- Phase A: Introduce `users` alongside `persons`; dual-write in migration; add JWT while keeping cookie for limited time (behind feature flag) if needed.
|
- Phase A: Introduced `users` alongside `persons`; added JWT while cookie auth remained briefly for transition.
|
||||||
- Phase B: Add households and backfill default household; gate routes to household prefix with a migration shim for old clients if necessary.
|
- Phase B: Added households and backfilled default household; migrated routes under `/households/{householdSlug}`.
|
||||||
- Phase C: Remove cookie auth and `persons` package; require JWT everywhere; finalize OpenAPI to v2.
|
- Phase C (now): Removed cookie auth from app surface and inlined v2 routers; final step is to remove `persons` entirely and ensure no tests are skipped.
|
||||||
|
|
||||||
Data migration acceptance:
|
Data migration acceptance:
|
||||||
- All existing data appears under a default household and is accessible to the migrated user accounts.
|
- All existing data appears under a default household and is accessible to the migrated user accounts.
|
||||||
|
|
@ -313,8 +306,13 @@ Data migration acceptance:
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Acceptance Criteria (v2)
|
## 8. Acceptance Criteria (v2)
|
||||||
- Authentication: JWT bearer with register/login/google flows; refresh via HttpOnly cookie; `get_current_user` dependency replaced everywhere.
|
- Authentication: JWT bearer with register/login; refresh via HttpOnly cookie; `get_current_user` used in protected routes.
|
||||||
- Tenancy: All read/write queries filter by `household_id`; cross-household access returns 403; invitation flow works.
|
- Tenancy: All read/write queries filter by `household_id`; cross-household access returns 403; invitation flow works.
|
||||||
- API parity: v1 behavior preserved aside from auth/paths; ProblemDetails and pagination semantics intact; Location headers set on creates.
|
- API parity: v1 behavior preserved aside from auth/paths; ProblemDetails and pagination semantics intact; Location headers set on creates.
|
||||||
- OpenAPI: Security scheme is JWT; household path param present; arrays required and enums normalized; 400/404/422 standardized; 403 added on household routes.
|
- OpenAPI: Security scheme is JWT; household path param present; arrays required and enums normalized; 400/404/422 standardized; 403 added on household routes.
|
||||||
|
|
||||||
|
Final-state definition (what “done” looks like):
|
||||||
|
- No legacy v1 endpoints mounted; no `*_v2.py` files in repo (done).
|
||||||
|
- No `persons` package in codebase or responses; all tests ported from v1 and no tests are skipped.
|
||||||
|
- OpenAPI reflects only JWT-secured, household-scoped endpoints.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue