// Public REST API — post a reply to a help-center ticket. // // Bearer-authed. POST inserts a reply ({ content }) authored by the current user // into website_help_center_ticket_replies. The target ticket must exist and // belong to the authed user. Fail-soft: never a 500. import { apiError, apiJson, positiveBigInt } from "@/lib/api"; import { bearerUserId } from "@/lib/api-auth"; import { prisma } from "@/lib/prisma"; import { createOwnedTicketReply } from "@/lib/services/ticket-replies"; export const dynamic = "force-dynamic"; // POST /api/tickets/:id/reply body: { content } export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) { const uid = await bearerUserId(req); if (!uid) return apiError("Unauthorized", 401); const { id } = await params; const ticketId = positiveBigInt(id); if (!ticketId) return apiError("Invalid ticket id", 422); const body = (await req.json().catch(() => ({}))) as { content?: unknown }; const content = String(body.content ?? "") .trim() .slice(0, 5000); if (!content) return apiError("Content is required"); try { // Ownership check — only the ticket owner may reply. const reply = await prisma.$transaction((tx) => createOwnedTicketReply( { findTicket: (ticketId) => tx.websiteHelpCenterTickets.findUnique({ where: { id: ticketId }, select: { id: true, userId: true }, }), createReply: (data) => tx.websiteHelpCenterTicketReplies.create({ data, select: { id: true, userId: true, content: true, createdAt: true }, }), touchTicket: (ticketId, updatedAt) => tx.websiteHelpCenterTickets.update({ where: { id: ticketId }, data: { updatedAt }, select: { id: true }, }), }, { ticketId, userId: uid, content }, )); if (!reply) return apiError("Ticket not found", 404); return apiJson( { reply: { id: reply.id, userId: reply.userId, content: reply.content, createdAt: reply.createdAt, }, }, { status: 201 }, ); } catch { return apiError("Failed to post reply", 503); } }