import { and, desc, eq, lte } from "drizzle-orm"; import { NextResponse } from "next/server"; import { z } from "zod"; import { auth } from "@/app/(auth)/auth"; import { db } from "@/lib/db"; import { tvstarSession } from "@/lib/db/schema"; import { ownsTaskProject } from "@/lib/video-canvas/task-store"; import { tvstarSessionSchema } from "@/lib/video-canvas/tvstar-session"; export const dynamic = "force-dynamic"; async function authorize(projectId: string) { const session = await auth(); if (!session?.user?.id) return { error: NextResponse.json({ error: "未登录" }, { status: 401 }) }; if (!z.string().uuid().safeParse(projectId).success || !await ownsTaskProject(session.user.id, projectId)) { return { error: NextResponse.json({ error: "画布不存在" }, { status: 404 }) }; } return { userId: session.user.id }; } export async function GET(_request: Request, { params }: { params: Promise<{ id: string }> }) { const { id } = await params; const access = await authorize(id); if (access.error) return access.error; const rows = await db.select({ snapshot: tvstarSession.snapshot, shareToken: tvstarSession.shareToken }) .from(tvstarSession) .where(and(eq(tvstarSession.userId, access.userId!), eq(tvstarSession.projectId, id))) .orderBy(desc(tvstarSession.clientUpdatedAt)).limit(20); return NextResponse.json({ sessions: rows.map(({ snapshot, shareToken }) => ({ ...(snapshot as Record), shareToken: shareToken || undefined, })) }, { headers: { "Cache-Control": "no-store" } }); } export async function PUT(request: Request, { params }: { params: Promise<{ id: string }> }) { const { id } = await params; const access = await authorize(id); if (access.error) return access.error; const raw = await request.text(); if (raw.length > 900_000) return NextResponse.json({ error: "对话记录过大" }, { status: 413 }); let body: unknown; try { body = JSON.parse(raw); } catch { return NextResponse.json({ error: "请求格式无效" }, { status: 400 }); } const parsed = z.object({ sessions: z.array(tvstarSessionSchema).max(20) }).safeParse(body); if (!parsed.success) return NextResponse.json({ error: "对话记录格式无效" }, { status: 400 }); for (const session of parsed.data.sessions) { const clientUpdatedAt = new Date(Math.min(session.updatedAt, Date.now() + 60_000)); await db.insert(tvstarSession).values({ id: session.id, projectId: id, userId: access.userId!, title: session.title, snapshot: session, clientUpdatedAt, }).onConflictDoNothing({ target: tvstarSession.id }); await db.update(tvstarSession).set({ title: session.title, snapshot: session, clientUpdatedAt, updatedAt: new Date() }) .where(and( eq(tvstarSession.id, session.id), eq(tvstarSession.projectId, id), eq(tvstarSession.userId, access.userId!), lte(tvstarSession.clientUpdatedAt, clientUpdatedAt), )); } return NextResponse.json({ ok: true }); }