import { NextRequest, NextResponse } from "next/server"; import { eq, inArray } from "drizzle-orm"; import { z } from "zod"; import { db } from "@/db"; import { clients, comments, tasks, phases, deliverables, projects } from "@/db/schema"; import { rateLimit } from "@/lib/rate-limit"; const commentSchema = z.object({ token: z.string().min(1), entity_type: z.enum(["task", "deliverable", "general", "phase"]), entity_id: z.string().min(1), body: z.string().min(1, "Il commento non può essere vuoto").max(2000), }); export async function POST(request: NextRequest) { const ip = request.headers.get("x-forwarded-for") ?? "unknown"; if (!rateLimit(`comment:${ip}`, 10, 60_000)) { return NextResponse.json({ error: "Troppe richieste" }, { status: 429 }); } try { const body = await request.json(); const parsed = commentSchema.safeParse(body); if (!parsed.success) { return NextResponse.json( { error: parsed.error.issues[0].message }, { status: 400 } ); } const { token, entity_type, entity_id, body: commentBody } = parsed.data; // Validate token 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: "Accesso non consentito" }, { status: 403 }); } const clientId = clientRows[0].id; if (entity_type === "general") { // General messages: entity_id must be the client's own id if (entity_id !== clientId) { return NextResponse.json({ error: "Accesso non consentito" }, { status: 403 }); } } else { // Scope phases through projects → client const clientProjects = await db .select({ id: projects.id }) .from(projects) .where(eq(projects.client_id, clientId)); const projectIds = clientProjects.map((p) => p.id); if (projectIds.length === 0) { return NextResponse.json({ error: "Accesso non consentito" }, { status: 403 }); } const phasesForClient = await db .select({ id: phases.id }) .from(phases) .where(inArray(phases.project_id, projectIds)); const phaseIds = phasesForClient.map((p) => p.id); if (phaseIds.length === 0) { return NextResponse.json({ error: "Accesso non consentito" }, { status: 403 }); } const taskRows = await db .select({ id: tasks.id }) .from(tasks) .where(inArray(tasks.phase_id, phaseIds)); if (entity_type === "phase") { // Phase: entity_id must be one of the client's phases if (!phasesForClient.find((p) => p.id === entity_id)) { return NextResponse.json({ error: "Accesso non consentito" }, { status: 403 }); } } else if (entity_type === "task") { if (!taskRows.find((r) => r.id === entity_id)) { return NextResponse.json({ error: "Accesso non consentito" }, { status: 403 }); } } else { // deliverable const taskIds = taskRows.map((r) => r.id); if (taskIds.length === 0) { return NextResponse.json({ error: "Accesso non consentito" }, { status: 403 }); } const delivRows = await db .select({ id: deliverables.id }) .from(deliverables) .where(inArray(deliverables.task_id, taskIds)); if (!delivRows.find((r) => r.id === entity_id)) { return NextResponse.json({ error: "Accesso non consentito" }, { status: 403 }); } } } await db.insert(comments).values({ entity_type, entity_id, author: "client", body: commentBody, }); return NextResponse.json({ success: true }, { status: 201 }); } catch (err) { console.error("/api/client/comment error:", err); return NextResponse.json({ error: "Errore interno" }, { status: 500 }); } }