feat: clear a meal-plan day or the whole week in one action
New bulk-delete route for the owner's own weekStart-scoped plan (mirrors recipes/bulk's DELETE), plus ?ids= support added to the existing shared-plan DELETE route (kept ?entryId= working unchanged). UI: hover-reveal trash icon per day header, "Clear week" button in the toolbar, one shared confirm dialog for both. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { db, mealPlans, mealPlanEntries, eq, and, inArray } from "@epicure/db";
|
||||
import { requireSession } from "@/lib/api-auth";
|
||||
|
||||
type Params = { params: Promise<{ weekStart: string }> };
|
||||
|
||||
const BulkDeleteSchema = z.object({
|
||||
ids: z.array(z.string()).min(1).max(100),
|
||||
});
|
||||
|
||||
// Clear-a-day / clear-a-week — the meal planner has no per-entry select mode,
|
||||
// so this is only ever called with either one day's entry ids or the whole
|
||||
// week's, scoped to the caller's own plan.
|
||||
export async function DELETE(req: NextRequest, { params }: Params) {
|
||||
const { session, response } = await requireSession();
|
||||
if (response) return response;
|
||||
const { weekStart } = await params;
|
||||
|
||||
const plan = await db.query.mealPlans.findFirst({
|
||||
where: and(eq(mealPlans.userId, session!.user.id), eq(mealPlans.weekStart, weekStart)),
|
||||
});
|
||||
if (!plan) return NextResponse.json({ error: "Not found" }, { status: 404 });
|
||||
|
||||
const body = BulkDeleteSchema.safeParse(await req.json());
|
||||
if (!body.success) return NextResponse.json({ error: "Validation error" }, { status: 400 });
|
||||
|
||||
await db.delete(mealPlanEntries).where(
|
||||
and(inArray(mealPlanEntries.id, body.data.ids), eq(mealPlanEntries.mealPlanId, plan.id))
|
||||
);
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { db, mealPlanEntries, recipes, eq, and, or, ne } from "@epicure/db";
|
||||
import { db, mealPlanEntries, recipes, eq, and, or, ne, inArray } from "@epicure/db";
|
||||
import { requireSession } from "@/lib/api-auth";
|
||||
import { getMealPlanAccessById, canWriteMealPlan } from "@/lib/meal-plan-access";
|
||||
import { dispatchWebhook } from "@/lib/webhooks";
|
||||
@@ -70,11 +70,15 @@ export async function DELETE(req: NextRequest, { params }: Params) {
|
||||
if (!access) return NextResponse.json({ error: "Not found" }, { status: 404 });
|
||||
if (!canWriteMealPlan(access.role)) return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
|
||||
// ?entryId=x for a single delete (existing behavior), or ?ids=a,b,c for a
|
||||
// "clear this day"/"clear this week" bulk delete — same access check either way.
|
||||
const entryId = req.nextUrl.searchParams.get("entryId");
|
||||
if (!entryId) return NextResponse.json({ error: "entryId required" }, { status: 400 });
|
||||
const idsParam = req.nextUrl.searchParams.get("ids");
|
||||
const ids = idsParam ? idsParam.split(",").filter(Boolean) : entryId ? [entryId] : [];
|
||||
if (ids.length === 0) return NextResponse.json({ error: "entryId or ids required" }, { status: 400 });
|
||||
|
||||
await db.delete(mealPlanEntries).where(
|
||||
and(eq(mealPlanEntries.id, entryId), eq(mealPlanEntries.mealPlanId, mealPlanId))
|
||||
and(inArray(mealPlanEntries.id, ids), eq(mealPlanEntries.mealPlanId, mealPlanId))
|
||||
);
|
||||
|
||||
return new NextResponse(null, { status: 204 });
|
||||
|
||||
Reference in New Issue
Block a user