Files
Epicure/TODO.md
T
Arnaud d2faf98ac1 fix: resolve TODO.md security/perf/test-coverage backlog
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>
2026-07-02 12:12:42 +02:00

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

  1. IDORcollections/[id]/route.ts — investigated: the PUT handler's top-level ownership check (existing = findFirst(id + userId)) already gates the entire handler, including removeRecipeId/addRecipeId. Not actually vulnerable. No change made.
  2. Missing transaction — meal-plan generation — wrapped the per-entry insert sequence in db.transaction(...).
  3. Missing indexes — added userId indexes to collections, collectionMembers, cookingHistory, ratings, favorites.
  4. Zero test coverage on api/v1/admin/* and api/v1/webhooks/* — added Vitest coverage for all 7 route files (role checks, SSRF validation path, redelivery logic).
  5. 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 in lib/ai/features/import-url.ts; consolidated both call sites onto the one fixed implementation.
  6. Race condition — checkAndIncrementTierLimit — investigated: already atomic (single INSERT ... ON CONFLICT DO UPDATE ... RETURNING). The old racy checkTierLimit was dead code (zero callers) — deleted.
  7. N+1 / no pagination — collections list — added limit/offset pagination, matching the search route's pattern.
  8. Dietary-tag search — missing index — added a GIN index on recipes.dietaryTags; also switched the search filter from ->> text extraction to @> containment so the index is actually used.
  9. Duplicated Zod schemas across AI features — extracted shared dietaryTagsSchema/ingredientSchema/stepSchema into lib/ai/features/recipe-schema.ts.
  10. Stripe webhook stubbed — implemented tier upgrade/downgrade; added users.stripeCustomerId to map customer.subscription.deleted events back to a user.
  11. No rate limit on ai-keys — added applyRateLimit to the POST handler.
  12. pnpm typecheck documented but missing — added the script to all three workspace packages; also fixed the pre-existing type errors it exposed (packages/db missing @types/node, two stale test mocks).
  13. Hardcoded localhost:3001 fallback — now throws a 500 with a clear message if BETTER_AUTH_URL is 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

  1. Expiry-aware pantry — done. Pantry schema/UI already had expiresAt fully 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.
  2. Shared meal plans/shopping lists — done. Added shoppingListMembers/mealPlanMembers tables (viewer/editor roles, mirrors collectionMembers), 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).
  3. PDF cookbook export — done. /print/collection/[id] renders every recipe in a collection with page-break-after between 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).
  4. Recipe diff/compare view — done. Added the diff package + VersionDiffView; version-history-button now has a "Compare with current" action next to Restore.
  5. 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.
  6. Personalized "for you" feed — done. New /api/v1/feed/for-you ranks 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.
  7. 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 /cook already 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).