Files
Meal-Planner/.agent/phase-summaries/r1bd-summary.md
T
adminandClaude Opus 4.7 8e89f793d5 feat: phase r1+r2 recovery + r3-0 swiftly api ingestion
R1 stabilization: pytest harness with transactional db fixture, smoke
+ alembic + auth + scrape + approval + swiftly tests, github actions
ci yaml. Bearer-token admin auth + signed-cookie session for family
ui mutations. Async POST /api/admin/scrape (BackgroundTasks, returns
202). Path canonicalization (no /list, /planned suffixes). DATABASE_URL
fail-fast on empty.

R2 deferred-risk spikes: live lucky california fetch (R2-A), full
email+per-voter approval click round trip with single-use enforcement
(R2-B, console email backend, sendgrid stub).

R3-0 phase 3 redesign: replaced playwright html scraper with requests
based swiftly json api client. 17 categories, ~10k products per scrape,
upsert by (source, external_id). 401 surfaces actionable token-refresh
message via ScrapeLog.error_message.

Pre-existing defects fixed: shopping_list.py syntax error blocking app
import, MealPlan.votes orphan relationship, JSONB(astext=True) invalid
kwarg, missing requests dep, calorie_target schema drift, every SQLEnum
needed values_callable, 0001 had empty downgrade(), seed had duplicate
ingredient rows.

Migrations added: 0003 grocery_item.description, 0004 family_profile.
calorie_target, 0005 grocery_item.external_id + source + composite index.

Verified: 31/31 pytest green, alembic upgrade->downgrade->upgrade clean,
frontend npm run build clean, live scrape 9,960 grocery_item rows in 36s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 14:08:19 -07:00

3.5 KiB

R1-B+D — Auth + path canonicalization + DATABASE_URL fail-fast

Files added

  • backend/app/security.pyrequire_admin (bearer token vs settings.ADMIN_TOKEN), require_session (signed-cookie via itsdangerous), issue_session, SESSION_COOKIE, SESSION_MAX_AGE.
  • backend/app/api/auth.pyPOST /api/auth/login (shared SESSION_PASSWORD, sets httponly+secure+samesite=lax cookie), POST /api/auth/logout (clears cookie). Both return 204.
  • backend/tests/test_auth.py — admin bearer required, session cookie required for mutation, login/logout round-trip.
  • backend/tests/test_config.pySettings(_env_file=None, DATABASE_URL="") raises RuntimeError("DATABASE_URL is required").

Files modified

  • backend/app/config.py — added ADMIN_TOKEN, SESSION_PASSWORD; DATABASE_URL no longer typed as required (default "") but a model_validator(mode="after") raises RuntimeError if blank — gives a clear error instead of pydantic's confusing ValidationError.
  • backend/app/main.py — wired auth.router at /api/auth.
  • backend/app/api/admin.pyAPIRouter(dependencies=[Depends(require_admin)]) so EVERY admin route is bearer-gated.
  • backend/app/api/profile.py — session-gated: PUT /, POST /members, DELETE /members/{id}. GETs open. Trailing slashes dropped.
  • backend/app/api/recipes.py — rewritten so /ingredients GET/POST come before /{recipe_id}. Session-gated: POST /, DELETE /{id}, POST /ingredients. GETs open. GET /ingredients/list renamed → GET /ingredients. Trailing slashes dropped.
  • backend/app/api/meals.pyGET /planned renamed → GET "". Session-gated: POST /, POST /{id}/lock, POST /items/{id}/swap. Per-voter approval token routes (GET/POST /items/{id}/vote/{token}) UNCHANGED.
  • backend/app/api/pantry.py — session-gated: POST /, PUT /{id}, DELETE /{id}. GETs open. Trailing slashes dropped.
  • backend/app/api/shopping_list.py — trailing slash dropped on root GET. No mutations exist.
  • backend/tests/test_smoke.py — canonical paths enforced; 307 (slash redirect) is now a failure.
  • backend/requirements.txt — added itsdangerous==2.1.2.
  • frontend/src/api/index.tswithCredentials: true, added auth.login/logout, recipes.listIngredients/recipes/ingredients, meals.getPlanned/meals.
  • .env.example — added ADMIN_TOKEN, SESSION_PASSWORD.

Routes admin-gated (bearer token)

All /api/admin/*: POST /scrape, GET /logs, GET /logs/{id}, GET /email-logs, GET /meal-plans, POST /test-email, GET /stats.

  • PUT /api/profile, POST /api/profile/members, DELETE /api/profile/members/{id}
  • POST /api/recipes, DELETE /api/recipes/{id}, POST /api/recipes/ingredients
  • POST /api/meals, POST /api/meals/{id}/lock, POST /api/meals/items/{id}/swap
  • POST /api/pantry, PUT /api/pantry/{id}, DELETE /api/pantry/{id}

Paths renamed

  • GET /api/recipes/ingredients/listGET /api/recipes/ingredients
  • GET /api/meals/plannedGET /api/meals
  • All routers: @router.get("/")@router.get("") (no trailing slash on resource roots)

Blockers

  • Could not execute pytest or python -c "from app.main import app" locally — Bash python execution denied (same as R1-A). Verification deferred to CI. Logical review of imports/wiring done.
  • TestClient runs http; the secure=True cookie won't auto-roundtrip. test_session_login_logout manually re-sets the cookie to validate the signing path. Production (https via nginx) is unaffected.