import { NextRequest, NextResponse } from "next/server"; import { eq, and } from "drizzle-orm"; import { db } from "@/db"; import { clients, deliverables, tasks, phases } from "@/db/schema"; export async function POST(request: NextRequest) { try { const body = await request.json(); const { token, deliverableId } = body as { token?: string; deliverableId?: string }; if (!token || !deliverableId) { return NextResponse.json({ error: "token e deliverableId richiesti" }, { status: 400 }); } // Validate token — find the client const clientRows = await db .select({ id: clients.id }) .from(clients) .where(eq(clients.token, token)) .limit(1); if (clientRows.length === 0) { return NextResponse.json({ error: "Token non valido" }, { status: 404 }); } const clientId = clientRows[0].id; // Verify deliverable belongs to this client (prevents cross-client approval) // deliverable → task → phase → client const ownershipCheck = await db .select({ deliverable_id: deliverables.id, approved_at: deliverables.approved_at }) .from(deliverables) .innerJoin(tasks, eq(deliverables.task_id, tasks.id)) .innerJoin(phases, and(eq(tasks.phase_id, phases.id), eq(phases.client_id, clientId))) .where(eq(deliverables.id, deliverableId)) .limit(1); if (ownershipCheck.length === 0) { return NextResponse.json({ error: "Deliverable non trovato" }, { status: 404 }); } // IMMUTABILITY RULE (CLAUDE.md): if approved_at is already set, this is a no-op if (ownershipCheck[0].approved_at !== null) { return NextResponse.json({ approved: true, message: "Già approvato" }, { status: 200 }); } // Set approved — approved_at is immutable once set, client cannot unset it await db .update(deliverables) .set({ status: "approved", approved_at: new Date() }) .where(eq(deliverables.id, deliverableId)); return NextResponse.json({ approved: true }, { status: 200 }); } catch (err) { console.error("/api/client/approve error:", err); return NextResponse.json({ error: "Errore interno" }, { status: 500 }); } }