import { NextResponse } from "next/server"; import { createSupabaseRouteClient } from "@/lib/supabase/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { HttpError, requireAuthContext } from "@/lib/auth/authContext"; import { api } from "@/lib/convex/api"; import { getConvexHttpClient } from "@/lib/convex/server"; export const dynamic = "force-dynamic"; interface PurgePayload { assetId?: string; } function resolveGraceSeconds(): number { const raw = process.env.DELETE_GRACE_SECONDS ?? process.env.NEXT_PUBLIC_DELETE_GRACE_SECONDS ?? "600"; const parsed = Number(raw); if (!Number.isFinite(parsed) || parsed <= 0) { return 600; } return Math.floor(parsed); } function makeExpiredDeletedAt(): string { const graceSeconds = resolveGraceSeconds(); return new Date(Date.now() - (graceSeconds + 5) * 1000).toISOString(); } export async function POST(request: Request) { if (isConvexEnabled()) { let auth; try { auth = await requireAuthContext(); } catch (err) { if (err instanceof HttpError) { return NextResponse.json({ error: "未登录" }, { status: err.status }); } return NextResponse.json({ error: "未登录" }, { status: 401 }); } const { assetId }: PurgePayload = await request.json().catch(() => ({})); if (!assetId) { return NextResponse.json({ error: "缺少 assetId" }, { status: 400 }); } const client = getConvexHttpClient(); const res = await client.mutation(api.mediaAssets.purgeById, { userId: auth.userId, id: assetId, expiredDeletedAt: makeExpiredDeletedAt(), }); return NextResponse.json({ success: true, ...(res as any) }); } const supabase = await createSupabaseRouteClient(); const { data: { session }, } = await supabase.auth.getSession(); if (!session) { return NextResponse.json({ error: "未登录" }, { status: 401 }); } const { assetId }: PurgePayload = await request.json().catch(() => ({})); if (!assetId) { return NextResponse.json({ error: "缺少 assetId" }, { status: 400 }); } const { data: asset, error: fetchError } = await supabase .from("media_assets") .select("id,workspace_id,deleted_at,purged_at") .eq("id", assetId) .single(); if (fetchError) { return NextResponse.json({ error: fetchError.message }, { status: 500 }); } if (!asset) { return NextResponse.json({ error: "未找到附件" }, { status: 404 }); } const { data: membership, error: membershipError } = await supabase .from("workspace_members") .select("workspace_id") .eq("workspace_id", asset.workspace_id) .eq("user_id", session.user.id) .limit(1); if (membershipError) { return NextResponse.json({ error: membershipError.message }, { status: 500 }); } if (!membership || membership.length === 0) { return NextResponse.json({ error: "无权操作该附件" }, { status: 403 }); } if (asset.purged_at) { return NextResponse.json({ success: true, alreadyPurged: true }); } const { error: updateError } = await supabase .from("media_assets") .update({ deleted_at: makeExpiredDeletedAt(), deleted_by: session.user.id, }) .eq("id", assetId); if (updateError) { return NextResponse.json({ error: updateError.message }, { status: 500 }); } return NextResponse.json({ success: true }); }