d2faf98ac1
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>
4.9 KiB
4.9 KiB
Known issues / backlog
Findings from a codebase health scan (security, data integrity, tests, perf, cleanup). All items below are resolved as of this pass.
Resolved
IDOR —— investigated: thecollections/[id]/route.tsPUThandler's top-level ownership check (existing = findFirst(id + userId)) already gates the entire handler, includingremoveRecipeId/addRecipeId. Not actually vulnerable. No change made.Missing transaction — meal-plan generation— wrapped the per-entry insert sequence indb.transaction(...).Missing indexes— addeduserIdindexes tocollections,collectionMembers,cookingHistory,ratings,favorites.Zero test coverage on— added Vitest coverage for all 7 route files (role checks, SSRF validation path, redelivery logic).api/v1/admin/*andapi/v1/webhooks/*SSRF gap — malformed IPv6— replaced string-prefix heuristics with a proper IPv6 parser (handles::compression, IPv4-mapped addresses, fails closed on malformed input). Also found and fixed an identical duplicated bug inlib/ai/features/import-url.ts; consolidated both call sites onto the one fixed implementation.Race condition —— investigated: already atomic (singlecheckAndIncrementTierLimitINSERT ... ON CONFLICT DO UPDATE ... RETURNING). The old racycheckTierLimitwas dead code (zero callers) — deleted.N+1 / no pagination — collections list— addedlimit/offsetpagination, matching thesearchroute's pattern.Dietary-tag search — missing index— added a GIN index onrecipes.dietaryTags; also switched the search filter from->>text extraction to@>containment so the index is actually used.Duplicated Zod schemas across AI features— extracted shareddietaryTagsSchema/ingredientSchema/stepSchemaintolib/ai/features/recipe-schema.ts.Stripe webhook stubbed— implemented tier upgrade/downgrade; addedusers.stripeCustomerIdto mapcustomer.subscription.deletedevents back to a user.No rate limit on— addedai-keysapplyRateLimitto the POST handler.— added the script to all three workspace packages; also fixed the pre-existing type errors it exposed (pnpm typecheckdocumented but missingpackages/dbmissing@types/node, two stale test mocks).Hardcoded— now throws a 500 with a clear message iflocalhost:3001fallbackBETTER_AUTH_URLis unset, instead of silently generating a broken link.
Feature ideas
Brainstormed extensions building on existing infra (pantry match, meal planning, cooking mode w/ voice, tiers, AI generation, social/collections, print, version history).
Done
- Expiry-aware pantry — done. Pantry schema/UI already had
expiresAtfully wired (date input, sort, expiry badges); added the missing piece — the canCook page now surfaces a "Use it up" badge on recipes that use soon-expiring pantry items and sorts them to the top. - Shared meal plans/shopping lists — done. Added
shoppingListMembers/mealPlanMemberstables (viewer/editor roles, mirrorscollectionMembers), share dialogs, and membership-checked API routes. Meal plans keep their owner-side weekly routes; shared access goes through new/api/v1/meal-plans/shared/[mealPlanId]routes since plans are addressed by(userId, weekStart). - PDF cookbook export — done.
/print/collection/[id]renders every recipe in a collection withpage-break-afterbetween them, reusing the existing print-page CSS; browser print-to-PDF produces the file, matching the existing single-recipe/meal-plan print pattern (no new PDF-rendering dependency). - Recipe diff/compare view — done. Added the
diffpackage +VersionDiffView; version-history-button now has a "Compare with current" action next to Restore. - Grocery delivery handoff — done as a stub, per confirmed scope: shopping lists get a "Send to grocery delivery" button that always offers copy-as-text, plus a documented (not live) Instacart adapter gated behind
INSTACART_API_KEY/NEXT_PUBLIC_GROCERY_PROVIDER— real activation needs an actual partner agreement. - Personalized "for you" feed — done. New
/api/v1/feed/for-youranks public recipes by tag/dietary-tag overlap with the user's favorited/highly-rated history (falls back to recency when there's no history yet); third feed tab added. - PWA/offline mode — done. The service worker and offline fallback already existed; added
manifest.json+ icons and wired them into the root layout metadata so the app is installable. Cache-first on/cookalready makes previously-visited cooking-mode pages available offline.
Also fixed this pass
- Mobile responsiveness — Recipes/Collections/Pantry/Meal Plan/Shopping Lists page headers now stack and wrap instead of clipping buttons off-screen on narrow viewports (
flex-col sm:flex-row+flex-wrap).