Files
Epicure/apps/web/proxy.ts
T
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

40 lines
1.3 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { getSessionCookie } from "better-auth/cookies";
const PUBLIC_PATHS = ["/login", "/signup", "/verify-email", "/forgot-password", "/reset-password", "/api/auth", "/r/", "/u/", "/docs", "/api/v1/openapi.json", "/api/webhooks", "/api/v1/invites/", "/api/internal/"];
const ADMIN_PATHS = ["/admin"];
export async function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
const isPublic = PUBLIC_PATHS.some((p) => pathname.startsWith(p));
const isAdmin = ADMIN_PATHS.some((p) => pathname.startsWith(p));
const isApi = pathname.startsWith("/api/v1");
if (isPublic) return NextResponse.next();
const sessionCookie = getSessionCookie(request);
if (!sessionCookie) {
if (isApi) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
return NextResponse.redirect(new URL("/login", request.url));
}
if (isAdmin) {
// Role check happens inside admin pages — middleware only checks auth
// Full role verification requires DB lookup, done server-side in admin layout
}
return NextResponse.next();
}
export default proxy;
export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|icon.svg|icon-192.svg|icon-512.svg|manifest.webmanifest|sw.js|public/).*)",
],
};