import { NextResponse } from "next/server"; import { requireCurrentUser, UnauthorizedError } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; import { normalizePositiveInteger, normalizeTitle } from "@/lib/validation"; type RouteContext = { params: Promise<{ id: string; }>; }; function normalizeOpacity(value: unknown, fallback: number): number { return normalizePositiveInteger(value, fallback, 20, 100); } function normalizeWidgetFontSize(value: unknown, fallback: number): number { const numberValue = typeof value === "number" ? value : Number(value); if (!Number.isFinite(numberValue)) { return fallback; } return Math.max(70, Math.min(140, Math.round(numberValue))); } function normalizeViewMode(value: unknown, fallback: string): string { if (value === "grid" || value === "list") { return value; } return fallback === "grid" ? "grid" : "list"; } async function normalizeWidgetPositions(userId: string, tabId: string | null) { const widgets = await prisma.widget.findMany({ where: { userId, tabId }, orderBy: [ { y: "asc" }, { x: "asc" }, { createdAt: "asc" } ] }); await Promise.all( widgets.map((widget, index) => prisma.widget.update({ where: { id: widget.id }, data: { position: index } }) ) ); } export async function PATCH(request: Request, context: RouteContext) { try { const user = await requireCurrentUser(); const params = await context.params; const body = (await request.json().catch(() => null)) as { title?: unknown; position?: unknown; x?: unknown; y?: unknown; w?: unknown; h?: unknown; viewMode?: unknown; opacity?: unknown; fontSize?: unknown; calendarNextEventsCount?: unknown; } | null; const existingWidget = await prisma.widget.findFirst({ where: { id: params.id, userId: user.id } }); if (!existingWidget) { return NextResponse.json({ error: "Widget nicht gefunden." }, { status: 404 }); } const hasTitle = body && Object.prototype.hasOwnProperty.call(body, "title"); const title = hasTitle ? normalizeTitle(body?.title) : existingWidget.title; if (!title) { return NextResponse.json({ error: "Widget-Titel ist ungültig." }, { status: 400 }); } const widget = await prisma.widget.update({ where: { id: existingWidget.id }, data: { title, position: normalizePositiveInteger(body?.position, existingWidget.position, 0, 10000), x: normalizePositiveInteger(body?.x, existingWidget.x, 0, 47), y: normalizePositiveInteger(body?.y, existingWidget.y, 0, 40000), w: normalizePositiveInteger(body?.w, existingWidget.w, 1, 48), h: normalizePositiveInteger(body?.h, existingWidget.h, 1, 180), opacity: normalizeOpacity(body?.opacity, existingWidget.opacity ?? 100), fontSize: normalizeWidgetFontSize(body?.fontSize, existingWidget.fontSize ?? 100), calendarNextEventsCount: normalizePositiveInteger( body?.calendarNextEventsCount, existingWidget.calendarNextEventsCount ?? 3, 0, 10 ), viewMode: normalizeViewMode(body?.viewMode, existingWidget.viewMode ?? "list") } }); if (widget.type === "note") { await prisma.noteBoardItem.updateMany({ where: { id: widget.id, userId: user.id }, data: { title } }); } return NextResponse.json({ widget }); } catch (error) { if (error instanceof UnauthorizedError) { return NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 }); } return NextResponse.json({ error: "Widget konnte nicht gespeichert werden." }, { status: 500 }); } } export async function DELETE(_request: Request, context: RouteContext) { try { const user = await requireCurrentUser(); const params = await context.params; const existingWidget = await prisma.widget.findFirst({ where: { id: params.id, userId: user.id }, select: { id: true, tabId: true } }); if (!existingWidget) { return NextResponse.json({ error: "Widget nicht gefunden." }, { status: 404 }); } await prisma.$transaction([ prisma.noteBoardItem.deleteMany({ where: { id: params.id, userId: user.id } }), prisma.widget.deleteMany({ where: { id: params.id, userId: user.id } }) ]); await normalizeWidgetPositions(user.id, existingWidget.tabId); return NextResponse.json({ ok: true }); } catch (error) { if (error instanceof UnauthorizedError) { return NextResponse.json({ error: "Nicht angemeldet." }, { status: 401 }); } return NextResponse.json({ error: "Widget konnte nicht gelöscht werden." }, { status: 500 }); } }