import { and, eq } from 'drizzle-orm'; import { NextResponse } from 'next/server'; import type { AuthContext } from '@/lib/api/helpers'; import { parseBody } from '@/lib/api/route-helpers'; import { db } from '@/lib/db'; import { savedViews } from '@/lib/db/schema'; import { errorResponse } from '@/lib/errors'; import { savedViewsService } from '@/lib/services/saved-views.service'; import { updateSavedViewSchema } from '@/lib/validators/saved-views'; /** * Resolves the view and enforces ownership before mutating. * * Returns a 404 when the view does not exist (or lives in a different port) * and a 403 when it belongs to a different user. The 404-before-403 split * matches the rest of the API and avoids leaking the existence of another * user's saved view via timing or status code. */ async function assertViewOwner( id: string, portId: string, userId: string, ): Promise { const view = await db.query.savedViews.findFirst({ where: and(eq(savedViews.id, id), eq(savedViews.portId, portId)), }); if (!view) { return NextResponse.json({ error: 'Not found' }, { status: 404 }); } if (view.userId !== userId) { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } return null; } export async function patchHandler( req: Request, ctx: AuthContext, params: { id?: string }, ): Promise { try { const id = params.id ?? ''; const denied = await assertViewOwner(id, ctx.portId, ctx.userId); if (denied) return denied; const body = await parseBody(req as never, updateSavedViewSchema); const view = await savedViewsService.update(ctx.portId, ctx.userId, id, body); return NextResponse.json({ data: view }); } catch (error) { return errorResponse(error); } } export async function deleteHandler( _req: Request, ctx: AuthContext, params: { id?: string }, ): Promise { try { const id = params.id ?? ''; const denied = await assertViewOwner(id, ctx.portId, ctx.userId); if (denied) return denied; await savedViewsService.delete(ctx.portId, ctx.userId, id); return NextResponse.json({ data: null }, { status: 200 }); } catch (error) { return errorResponse(error); } }