MVP-1: helpdesk core with dashboard, Telegram channel, customer portal
Next.js 16 + Drizzle/SQLite (matching ~/project-claude conventions), SSE-based realtime updates, admin dashboard with status columns, ticket thread view, Telegram long-polling integration (inbound + outbound), token-based customer portal, Docker Compose packaging. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
This commit is contained in:
1 parent
888b7b82ba
commit
cf0b6cf58e
59 files changed
+12556
No files matched your search
@@ -0,0 +1,39 @@
|
||||
export const runtime = "nodejs";
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { getCustomerByPortalToken, appendCustomerReply } from "@/lib/tickets/service";
|
||||
|
||||
const schema = z.object({
|
||||
token: z.string().min(1),
|
||||
body: z.string().min(1).max(10_000),
|
||||
});
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ ticketId: string }> },
|
||||
) {
|
||||
const { ticketId } = await params;
|
||||
const body = await request.json().catch(() => null);
|
||||
const parsed = schema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: "Invalid input" }, { status: 400 });
|
||||
}
|
||||
|
||||
const customer = await getCustomerByPortalToken(parsed.data.token);
|
||||
if (!customer) {
|
||||
return NextResponse.json({ error: "Invalid token" }, { status: 404 });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await appendCustomerReply({
|
||||
ticketId,
|
||||
customerId: customer.id,
|
||||
authorName: customer.displayName,
|
||||
body: parsed.data.body,
|
||||
});
|
||||
return NextResponse.json(result, { status: 201 });
|
||||
} catch {
|
||||
return NextResponse.json({ error: "Ticket not found" }, { status: 404 });
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user