Files
mnote/wolai-frontend/src/app/api/onlyoffice/forcesave/route.ts
T

88 lines
3.3 KiB
TypeScript

import { NextResponse } from "next/server";
import { api } from "@/lib/convex/api";
import { HttpError } from "@/lib/auth/authContext";
import { getAuthedConvexClient } from "@/lib/convex/route";
import { resolveOnlyOfficeInternalUrl } from "@/lib/onlyoffice/internal-url";
import { documentBridgeErrorResponse } from "@/lib/documents/bridge";
import { prepareOnlyOfficeForcesave } from "@/lib/onlyoffice/rust-adapter";
export const dynamic = "force-dynamic";
export async function POST(request: Request) {
const onlyofficeInternalUrl = await resolveOnlyOfficeInternalUrl();
let auth;
let client;
try {
const authed = await getAuthedConvexClient();
auth = authed.auth;
client = authed.client;
} catch (err) {
if (err instanceof HttpError) {
return NextResponse.json({ error: "未登录" }, { status: err.status });
}
return NextResponse.json({ error: "未登录" }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const assetId = String(searchParams.get("assetId") || "").trim();
const key = String(searchParams.get("key") || "").trim();
if (!assetId) return NextResponse.json({ error: "缺少 assetId" }, { status: 400 });
if (!key) return NextResponse.json({ error: "缺少 key" }, { status: 400 });
const asset = await client.query(api.mediaAssets.getById, { userId: auth.userId, id: assetId });
if (!asset) {
return NextResponse.json({ error: "资源不存在" }, { status: 404 });
}
// 只读权限:不允许触发 forcesave(避免只读用户间接写回存储)
const docId = String((asset as any).document_id || "").trim();
if (!docId) {
return NextResponse.json({ error: "缺少 documentId" }, { status: 400 });
}
const doc = await client.query(api.documents.getMeta, { id: docId });
if (!doc || (doc as any).can_edit === false) {
return NextResponse.json({ error: "无权修改(只读共享的文件)" }, { status: 403 });
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const tryJson = async (res: Response) => (await res.json().catch(() => null)) as any;
try {
const prepared = await prepareOnlyOfficeForcesave({
request,
assetId,
key,
onlyofficeInternalUrl,
secret: process.env.ONLYOFFICE_JWT_SECRET || "",
actorId: auth.userId,
documentId: docId,
workspaceId: String((asset as any).workspace_id || "").trim() || null,
});
const errors: Record<string, unknown> = {};
for (const outbound of prepared.requests) {
const response = await fetch(outbound.url, {
method: outbound.method,
headers: Object.fromEntries(outbound.headers.map((item) => [item.name, item.value])),
body: outbound.bodyJson,
});
const payload = await tryJson(response);
if (response.ok && Number(payload?.error ?? 0) === 0) {
return NextResponse.json({ ok: true, via: outbound.via, result: payload });
}
errors[outbound.via] = payload;
}
return NextResponse.json(
{ error: "触发 forcesave 失败", detail: errors },
{ status: 502 },
);
} catch (error) {
if (error instanceof Error && error.name === "DocumentBridgeError") {
return documentBridgeErrorResponse(error);
}
console.error("[onlyoffice/forcesave] failed:", error);
return NextResponse.json({ error: "触发 forcesave 失败" }, { status: 502 });
}
}