Add DEFAULT_{TEXT,VISION,MEAL_PLAN}_{PROVIDER,MODEL} site settings,
editable from Settings -> Admin (new AdminDefaultModelForm, mirroring
the per-user model-prefs UI). getDefaultProviderWithKey now accepts
the use case and checks the admin default (after the user's own BYOK
key, before the old "first configured site key" heuristic) so admins
can pin a specific provider/model per feature instead of it being
whichever key happens to exist.
Wired into scale/substitute/batch-cook/meal-plan generation routes,
which previously called getDefaultProviderWithKey() without a use
case and so never had visibility into per-feature routing at all.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
importFromPhoto() accepted a locale param but silently discarded it
(named _locale, never read) — now builds a language instruction the
same way generate-recipe.ts does for text generation.
The AI output schema had no way to signal "couldn't find a recipe in
this image" — generateObject would always fabricate a title/fields.
Added a `found` boolean the model sets to false in that case; the
route now returns 422 instead of creating a garbage recipe and
sending the user into the editor with it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Audited every documented request/response schema against the real
Zod validators and NextResponse.json shapes across all route
families, then fixed drift: missing batch-cook/tags/photos fields on
recipes, wrong comment/rating shapes, meal-plan entry shape (day vs
dayOfWeek, nullable recipeId), shopping-list/pantry response fields,
a phantom GET /webhooks/{id} with no backing route, and numerous
missing 400/401/429 response codes.
Also fixes a real bug found during the audit: the webhook-redelivery
route returned {error: object} instead of the app-wide {error:
string} convention on validation failure.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Went from 14 to ~100 documented endpoints in lib/openapi.ts — pantry,
meal-plans, shopping-lists, collections, users/social, ai/*, webhooks,
conversations, notifications, and admin routes now all appear in
/docs with accurate request/response schemas. No auth/route changes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New keys can be created as "Full access" (default, unchanged) or
"Read-only" — read-only keys can only make GET/HEAD/OPTIONS requests,
enforced once in requireSessionOrApiKey (lib/api-auth.ts) rather than in
every route, since a route has no way to know a request came from a
scoped key without that check. Existing keys default to full access —
no behavior change for anyone who doesn't opt in.
Also included in this migration: the chat_messages table for the
next commit (chat history persistence) — generated together since both
touched packages/db/src/schema/users.ts in the same pass.
Verified locally: created both a read-only and a full-access key,
confirmed GET succeeds and POST 403s on the read-only key, confirmed
POST still works on the full-access key, and checked the scope badges
render correctly in the real Settings → API Keys UI.
Full audit (bugs/UI-UX/backend/feature-gap) turned up a money-leak AI quota
bypass, webhook SSRF, and a long tail of missing pagination/auth/a11y work.
Fixes land together since HANDOFF.md tracked them as one backlog.
- AI routes charge tier quota before generating; nutrition POST is author-only
- Webhook dispatch re-validates URL per delivery (SSRF/DNS-rebinding), treats
redirects as failures; recipe.published now actually dispatches
- New indexes/unique constraints on recipes, meal-planning, comments FK cascade
- Recipe PUT/restore snapshot only inside the transaction, after validation
- Recipe DELETE cleans up S3 objects (recipe + review photos)
- Optimistic UI (favorite/star/follow/shopping-list) rolls back on failure
- Upload presign enforces file size cap + per-tier storage quota
- Route-level loading/error/not-found states across (app), admin, and root
- middleware.ts guards (app)/admin; requireAdmin checks DB role, not cached
session; rate limiting applied to both session and API-key branches,
bucketed per key; Stripe webhook dedupes by event id
- Pagination added to recipes, feed, profile, comments, pantry, admin tables
- Nav shows real avatar + profile link + dark-mode toggle; destructive actions
standardized on AlertDialog
- Unsaved-changes guard + real ingredient/step validation on recipe form;
canonical /recipes/[id] used in-app; next/image migration; aria-labels and
alt text across icon buttons, avatars, recipe photos
- packages/api-types removed (zero callers, too drifted to safely rewire);
openapi.ts and ai-keys error shape drift fixed; BYOK decrypt failures now
surface instead of silently falling back to the platform key
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes the 13-item codebase health scan backlog: wraps meal-plan
generation in a transaction, adds missing userId/GIN indexes, fixes
an IPv6-parsing gap in the webhook SSRF guard (and an identical
duplicated bug in the AI URL-import path, now consolidated onto one
implementation), paginates the collections list, dedupes the AI
recipe Zod schemas, wires up Stripe tier sync, rate-limits AI key
rotation, gets `pnpm typecheck` actually working, and adds test
coverage for the previously-untested admin/webhooks routes.
Two flagged issues (collection removeRecipeId IDOR, tier-limit race)
turned out to already be fixed/non-issues on inspection — noted in
TODO.md rather than silently dropped.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>