Commit Graph

9 Commits

Author SHA1 Message Date
Arnaud 8df292dfee fix: photo thumbnails always used localhost:9000 in production
getPublicUrl() runs in the browser (called from client components rendering
recipe thumbnails), but read the plain STORAGE_PUBLIC_URL env var — never
inlined into the client bundle, so every browser fell back to the hardcoded
localhost:9000 default regardless of the real deployed storage domain,
tripping CSP img-src and mixed-content blocks in production. Added a
NEXT_PUBLIC_STORAGE_PUBLIC_URL build arg (Dockerfile, compose.prod.yml) wired
from the same STORAGE_PUBLIC_URL value, and getPublicUrl() now reads that.

Verified locally: building with a fake public storage domain set shows it
correctly inlined into the client JS chunk (previously only the localhost
fallback ever appeared there).
2026-07-12 13:53:15 +02:00
Arnaud 2682eba2be fix: presigned upload URLs, card badge alignment, filter menu UX, floating save
- Presigned upload URLs were signed against STORAGE_ENDPOINT (the
  internal docker hostname, e.g. http://minio:9000) instead of a
  browser-reachable URL — uploads/edits with photos were broken in
  prod. Now signed with a separate client bound to STORAGE_PUBLIC_URL,
  which also needed threading through as a Docker build arg (CSP
  headers are computed at build time) and into compose.prod.yml/
  .env.production (gitignored, not committed).
- recipe-form.tsx used the wrong i18n namespace for the visibility
  label (t("recipeForm") instead of t_recipe("recipe")), causing
  MISSING_MESSAGE in French.
- Compact recipe-list view: batch-cook rows showed no dish count, and
  the date/difficulty columns shifted per-row depending on whether a
  difficulty badge was present — reserved a fixed-width slot for it.
- All three card icon badges (batch-cook, favorite, visibility) now
  share the same muted color instead of the batch badge standing out.
- Recipes filter dropdown: clicking a filter option closed the whole
  menu, and the tag text input couldn't be typed into (the menu's
  roving-focus/type-ahead handling was intercepting keystrokes) — menu
  items now stay open on click, and the tag input stops event
  propagation so it behaves like a normal text field.
- Recipe form: added a floating save/cancel bar so long recipes don't
  require scrolling back down to save.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 10:53:12 +02:00
Arnaud 002f14ced0 feat: batch-cook shopping list (already worked) + leftover expiry reminders
Shopping list add already worked generically for batch-cook recipes —
no code needed there.

New: mark a specific batch-cook dish as "cooked today", track its
fridge expiry (cookingHistory.batchDishId), surface a "Leftovers
expiring soon" widget on the pantry page, and send a daily push+email
reminder via a new /api/internal/cron/leftover-reminders endpoint
(mirrors the weekly-digest cron pattern; doesn't use the social
notifications table, which requires a non-null actor and isn't built
for self-reminders).

Also fixes, from user-reported bugs:
- Recipe cards showed no batch-cook badge/dish-count/prep-time in some
  views — added dishCount + prepMins/cookMins (now generated by the AI
  and persisted) to the card component and /recipes query.
- Batch-cook descriptions occasionally contained raw markdown
  (**bold**) — added explicit "plain prose only" prompt instructions
  and a stripMarkdown() defensive fallback at render time.
- Truncated/cut-off descriptions — the generateObject call had no
  maxOutputTokens set, so long structured responses could get cut off
  mid-field; now capped explicitly at 8000.
- Generate dialogs (batch-cook + the main AI dialog) could show
  buttons unreachable once the progress bar appeared mid-generation —
  restructured so the action row is pinned outside the scrollable
  content area, not affected by content height changes.
- /api/internal/* routes were being redirected to /login by middleware
  before their own CRON_SECRET check ever ran (pre-existing bug,
  affected the weekly-digest cron too) — added to PUBLIC_PATHS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 10:03:52 +02:00
Arnaud 68f0f490b9 chore: move docker/ compose files and infra to repo root
Moved compose.yml, compose.prod.yml, DEPLOY.md, traefik/, and cron/ out
of docker/ into the repo root (docker/ removed). Updated every reference:

- Dockerfile: COPY paths for the cron stage
- .dockerignore: dropped the now-unneeded docker/!docker-cron
  exclude+exception pair (cron/ is just a normal top-level dir now, no
  special-casing needed)
- compose.prod.yml: build context ".." -> "." (Dockerfile and compose
  files are now siblings, not one level apart)
- CLAUDE.md, DEPLOY.md, compose.yml, traefik/epicure.yml,
  cron/run-digest.sh, weekly-digest route.ts: path references in comments/
  docs

Verified: both compose files validate, and all three Dockerfile targets
(runner/cron/migrator) build clean via a local --no-cache docker build
from the new layout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-11 20:25:38 +02:00
Arnaud b0849c3989 feat: cooking history/gallery, unit conversion, nutrition diary, pantry scan, digest cron, nutrition-targeted meal plans
Six M-sized items from HANDOFF.md's new-features backlog:

- Profile tabs: cooking-history stats (total cooked, last-cooked, streak)
  and a "cooked it" photo gallery, both owner-only
- Display-time unit conversion (metric<->imperial) for recipe ingredients,
  respecting each user's unitPref; original value always shown alongside
  the conversion
- Nutrition daily diary: per-day macro totals computed from cooking history
  x recipe nutritionData, compared against user goals
- Pantry scan: real barcode lookup (zxing + Open Food Facts, no API key)
  with an AI-vision fallback for unbarcoded items, always confirm-before-
  insert, both paths tier/rate-limited like other AI features
- Weekly digest email: new followers/comments/ratings + trending recipes,
  sent via a new `cron` Docker stage (alpine+crond+curl) and `digest-cron`
  compose service hitting a bearer-token-protected internal route
- Meal-plan generation can now target a user's nutrition goals as a
  prompt-level nudge (recipes are AI-invented, not DB-sourced, so this
  can't be a hard macro constraint)

Caught a real deploy-breaking issue while adding the cron stage: appending
it after `runner` silently changed the Dockerfile's default build target,
and `web`'s compose config didn't pin one — fixed by pinning `target:
runner` explicitly. Verified with typecheck, lint, and three separate
`docker build --target` runs (runner/cron/migrator) plus `docker compose
config` validation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 08:06:28 +02:00
Arnaud d035378520 fix: remove dead api-types Dockerfile refs, drop duplicate middleware.ts
Dockerfile still COPYed packages/api-types (deleted in the prior audit-fixes
commit), breaking Portainer builds. Also drop apps/web/middleware.ts, added
in the same commit for auth guarding — it duplicated and conflicted with the
pre-existing apps/web/proxy.ts (Next 16's middleware entry point), which
already redirects unauthenticated requests to /login, 401s API routes, and
covers /recipes/new, /explore, /search, /settings/webhooks/docs via its
catch-all matcher. Having both middleware.ts and proxy.ts breaks `next build`
outright. Verified with a local `docker build` end to end.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 07:22:24 +02:00
Arnaud 5ff8ef5547 fix(deploy): bake NEXT_PUBLIC_* vars in at build time, not runtime
NEXT_PUBLIC_VAPID_PUBLIC_KEY (and the OAuth *_ENABLED flags) are inlined
into the client bundle by next build — setting them as container env vars
at runtime does nothing, since the client JS was already built without
them. Client bundle shipped with applicationServerKey: undefined, so
pushManager.subscribe() threw and the button always failed with "Failed
to enable notifications."

Wire them as Docker build args (Dockerfile ARG/ENV before `next build`,
compose.prod.yml build.args), sourced from the same stack env vars used
at runtime so they stay in sync.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 07:24:18 +02:00
Arnaud a4fb62c86c feat(deploy): auto-run db migrations + seed on stack deploy
Add a migrator Dockerfile target (drizzle-kit migrate + tier-definitions
seed, both idempotent) and wire it as a one-shot compose service that web
waits on via service_completed_successfully. No more manual exec step
after first deploy — runs safely on every redeploy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 16:23:54 +02:00
Arnaud 4a90ad910c feat(deploy): dockerize web app for portainer git-stack deploy behind external traefik
Add root Dockerfile (standalone Next output, multi-stage pnpm build), drop
in-stack caddy in favor of publishing web's port for an external traefik
LXC (file-provider dynamic config included), and document the portainer
deploy flow.

Also fixes issues that blocked any production build: a bad auth-client
type cast, the ai SDK's mimeType->mediaType rename, an implicit-any
callback param, and push.ts eagerly calling webpush.setVapidDetails at
module import time (which crashed page-data collection whenever VAPID
env vars weren't present at build) — now lazily configured on first send.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 16:20:34 +02:00