import { ensureSchema, getRawDb, getUploadBucket, uid, } from "../../../lib/mvp-db"; import { partnerOptions, withPartnerCors, } from "../../../lib/partner-cors"; export const runtime = "edge"; async function readUpload(request: Request) { const contentType = request.headers.get("content-type") ?? ""; if (contentType.startsWith("multipart/form-data")) { const form = await request.formData(); const file = form.get("file"); if (!(file instanceof File)) return null; return { taskToken: String(form.get("taskToken") ?? "").trim(), claimToken: String(form.get("claimToken") ?? "").trim(), delegationToken: String(form.get("delegationToken") ?? "").trim(), distributionId: String(form.get("distributionId") ?? "").trim(), uploadKind: String(form.get("uploadKind") ?? "publish").trim(), fileName: file.name, fileType: file.type, fileBytes: await file.arrayBuffer(), }; } const encodedName = request.headers.get("x-koc-file-name") ?? "screenshot.jpg"; let fileName = "screenshot.jpg"; try { fileName = decodeURIComponent(encodedName); } catch { fileName = "screenshot.jpg"; } return { taskToken: String(request.headers.get("x-koc-task") ?? "").trim(), claimToken: String(request.headers.get("x-koc-claim") ?? "").trim(), delegationToken: String( request.headers.get("x-koc-delegation") ?? "", ).trim(), distributionId: String( request.headers.get("x-koc-distribution") ?? "", ).trim(), uploadKind: String( request.headers.get("x-koc-upload-kind") ?? "publish", ).trim(), fileName, fileType: contentType.split(";")[0].trim(), fileBytes: await request.arrayBuffer(), }; } async function handlePost(request: Request) { try { await ensureSchema(); const upload = await readUpload(request); if ( !upload || (!upload.claimToken && !upload.delegationToken) || (!upload.delegationToken && !upload.taskToken) || !upload.distributionId || upload.fileBytes.byteLength === 0 ) { return Response.json({ error: "请选择发布截图" }, { status: 400 }); } if ( !upload.fileType.startsWith("image/") || upload.fileBytes.byteLength > 8_000_000 ) { return Response.json( { error: "仅支持8MB以内的图片" }, { status: 400 }, ); } const isCreatorCenter = upload.uploadKind === "creator-center"; const assignment = upload.delegationToken ? await getRawDb() .prepare( `SELECT d.id, d.publish_url FROM distributions d JOIN delegation_bundles b ON b.id = d.delegation_bundle_id WHERE d.id = ? AND b.share_token = ? AND b.status = 'active'`, ) .bind(upload.distributionId, upload.delegationToken) .first<{ id: string; publish_url: string | null }>() : await getRawDb() .prepare( `SELECT d.id, d.publish_url FROM distributions d JOIN claims c ON c.id = d.claim_id JOIN tasks t ON t.id = d.task_id WHERE d.id = ? AND c.claim_token = ? AND t.share_token = ? AND c.task_id = t.id`, ) .bind(upload.distributionId, upload.claimToken, upload.taskToken) .first<{ id: string; publish_url: string | null }>(); if (!assignment) { return Response.json({ error: "笔记与领取凭证不匹配" }, { status: 403 }); } if (isCreatorCenter && !assignment.publish_url) { return Response.json( { error: "请先回填这篇笔记的发布信息" }, { status: 409 }, ); } const extension = upload.fileName.split(".").at(-1)?.replace(/[^a-zA-Z0-9]/g, "") || "jpg"; const key = isCreatorCenter ? `creator-center/${upload.distributionId}/${uid("shot")}.${extension}` : `publish-evidence/${upload.distributionId}/${uid("shot")}.${extension}`; await getUploadBucket().put(key, upload.fileBytes, { httpMetadata: { contentType: upload.fileType }, }); if (isCreatorCenter) { await getRawDb() .prepare( `UPDATE distributions SET screenshot_key = ?, ocr_status = CASE WHEN exposure IS NOT NULL AND views IS NOT NULL THEN ocr_status ELSE 'uploaded' END, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, ) .bind(key, upload.distributionId) .run(); } else { await getRawDb() .prepare( `UPDATE distributions SET publish_screenshot_key = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, ) .bind(key, upload.distributionId) .run(); } return Response.json({ uploaded: true, kind: isCreatorCenter ? "creator-center" : "publish", }); } catch (error) { return Response.json( { error: error instanceof Error ? error.message : "上传失败" }, { status: 500 }, ); } } export async function POST(request: Request) { return withPartnerCors(request, await handlePost(request)); } export async function OPTIONS(request: Request) { return partnerOptions(request); }