Commit Graph

4 Commits

Author SHA1 Message Date
Arnaud 7626f1b496 fix: close TOCTOU race + missing checks in tier limits, add AI rate limits, patch CVEs (v0.60.0)
Security audit fixes (see SECURITY_AUDIT.md):
- lib/tiers.ts: checkAndIncrementTierLimit's recipe/storage branches did a
  live count/sum check with no lock tying it to the later write, so two
  concurrent requests near the cap could both pass and both write past the
  limit. Added checkTierLimitInTransaction — holds a per-user Postgres
  advisory lock for the duration of the caller's transaction, so the check
  and the write are atomic together. Applied to every recipe/storage write
  path (recipes create/update, fork, rate, avatar, and 7 AI recipe-creation
  routes).
- Adversarial re-verification of that fix caught a bigger gap in the same
  area: four AI routes (photo import, idea generation, batch-cook,
  translate-to-new-draft) had no recipe-limit check at all. Fixed.
- Added missing per-user rate limits to 5 AI endpoints (adapt, drinks,
  pairings, translate, variations) that had none, unlike their siblings.
- search page now checks for a session server-side, matching every other
  page under (app)/ — defense in depth; the underlying API was already
  public by design and proxy.ts already blocked unauthenticated requests.
- admin/settings route now uses the shared requireAdmin instead of a local
  duplicate.
- Documented two admin support-ticket endpoints missing from the OpenAPI
  spec; verified full route/OpenAPI parity otherwise.
- Bumped drizzle-orm to fix a SQL-identifier-escaping CVE, and overrode two
  transitive deps (esbuild, postcss) with known CVEs. pnpm audit is clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 22:21:51 +02:00
Arnaud f6975e98a9 fix: recipe/storage tier limits are lifetime totals, not monthly (v0.58.0)
Recipe count and storage usage shared the monthly user_usage bucket with
AI calls, so both incorrectly reset every month even though nothing was
deleted. Only AI calls should be monthly.

Recipe count and storage are now derived live from real data (recipes,
recipe/review photos, avatar) instead of a counter — deleting a photo or
recipe is itself the "decrement", no extra wiring needed. Storage size is
tracked per-row (recipePhotos.sizeMb, ratings.photoSizeMb, users.avatarSizeMb)
and threaded through presign -> upload -> save.

Also fixes avatar removal silently no-oping (client sent a field the PATCH
schema didn't recognize).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 20:32:31 +02:00
Arnaud b1f745da66 fix: batch of collections/i18n/translate-button fixes (v0.53.1)
- Drag-reorder used verticalListSortingStrategy on a multi-column grid,
  which computes wrong transforms for grid reflow — swapped to
  rectSortingStrategy so cards actually animate live while dragging.
- Grip handle was rendered as a sibling of (not a descendant of) the
  `group` element its `group-hover:opacity-100` depended on, so it was
  permanently invisible. Fixed the DOM nesting and made it always
  partially visible instead of hover-only.
- common.edit was missing from both locales (not just French) —
  edit-collection-dialog.tsx was the first caller to hit it.
- Root cause of "generated in my language but Translate still shows":
  generate-meal, meal-plan/generate, and adapt never set recipes.language
  on the row they inserted, so the button's `!recipe.language || ...`
  check always fell back to "show it". Fixed at all three insert sites.
- Translate dialog was entirely hardcoded English (title, description,
  language names, buttons) despite i18n keys already existing for most of
  it — now uses them, plus new translated language-name keys.
- Recipe tags now render on the recipe detail page (previously grid-card
  only).
- Collection header actions converted to icon-only + tooltip, matching
  the recipe page's pattern instead of icon+label buttons.
- Collections list search now also matches recipe titles inside each
  collection, not just the collection's own name/description.
- Explore page links to /collections/explore next to its tabs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 19:16:03 +02:00
Arnaud 8f83a1eaae feat: generate a complete themed meal from a collection (v0.52.0)
New "Generate meal" button on any owned collection — pick a theme (free
text) and 2-6 courses (starter/main/side/dessert/drink), one generateObject
call produces a coherent recipe per course (shared cuisine/style, matching
flavors across courses) and all of them get saved as real recipes and
added to that collection in one action.

Follows the meal-plan generation route's established pattern: single
withAiQuota charge for the whole generation, then a pre-flight loop
charging the tier's recipe limit once per generated recipe (rolled back
on a partial breach) before the insert transaction runs. Recipes are
tagged with their course name for later filtering; visibility defaults to
private like every other AI-generated recipe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 12:16:28 +02:00