Public Access
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>
3.5 KiB
3.5 KiB
R1-B+D — Auth + path canonicalization + DATABASE_URL fail-fast
Files added
backend/app/security.py—require_admin(bearer token vssettings.ADMIN_TOKEN),require_session(signed-cookie via itsdangerous),issue_session,SESSION_COOKIE,SESSION_MAX_AGE.backend/app/api/auth.py—POST /api/auth/login(sharedSESSION_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.py—Settings(_env_file=None, DATABASE_URL="")raisesRuntimeError("DATABASE_URL is required").
Files modified
backend/app/config.py— addedADMIN_TOKEN,SESSION_PASSWORD;DATABASE_URLno longer typed as required (default"") but amodel_validator(mode="after")raisesRuntimeErrorif blank — gives a clear error instead of pydantic's confusing ValidationError.backend/app/main.py— wiredauth.routerat/api/auth.backend/app/api/admin.py—APIRouter(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/ingredientsGET/POST come before/{recipe_id}. Session-gated:POST /,DELETE /{id},POST /ingredients. GETs open.GET /ingredients/listrenamed →GET /ingredients. Trailing slashes dropped.backend/app/api/meals.py—GET /plannedrenamed →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— addeditsdangerous==2.1.2.frontend/src/api/index.ts—withCredentials: true, addedauth.login/logout,recipes.listIngredients→/recipes/ingredients,meals.getPlanned→/meals..env.example— addedADMIN_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.
Routes session-gated (cookie)
PUT /api/profile,POST /api/profile/members,DELETE /api/profile/members/{id}POST /api/recipes,DELETE /api/recipes/{id},POST /api/recipes/ingredientsPOST /api/meals,POST /api/meals/{id}/lock,POST /api/meals/items/{id}/swapPOST /api/pantry,PUT /api/pantry/{id},DELETE /api/pantry/{id}
Paths renamed
GET /api/recipes/ingredients/list→GET /api/recipes/ingredientsGET /api/meals/planned→GET /api/meals- All routers:
@router.get("/")→@router.get("")(no trailing slash on resource roots)
Blockers
- Could not execute
pytestorpython -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=Truecookie won't auto-roundtrip.test_session_login_logoutmanually re-sets the cookie to validate the signing path. Production (https via nginx) is unaffected.