import { NextResponse } from "next/server"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { api } from "@/lib/convex/api"; import { HttpError } from "@/lib/auth/authContext"; import { getAuthedConvexClient } from "@/lib/convex/route"; export const dynamic = "force-dynamic"; const base64UrlEncodeUtf8 = (input: string) => { return Buffer.from(input, "utf8") .toString("base64") .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/g, ""); }; const getRequestOrigin = (request: Request) => { const xfProto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim(); const xfHost = request.headers.get("x-forwarded-host")?.split(",")[0]?.trim(); const host = xfHost || request.headers.get("host") || new URL(request.url).host; const protocol = (xfProto || new URL(request.url).protocol.replace(/:$/, "")) + ":"; return `${protocol}//${host}`; }; const isLocalHostname = (hostname: string) => hostname === "127.0.0.1" || hostname === "localhost" || hostname === "host.docker.internal"; const maybeProxyForBrowser = (request: Request, rawUrl: string) => { const input = String(rawUrl || "").trim(); if (!input) return input; if (input.startsWith("/api/onlyoffice/proxy")) return input; try { const u = new URL(input); if (!isLocalHostname(u.hostname)) return input; const origin = getRequestOrigin(request); const proxy = new URL("/api/onlyoffice/proxy", origin); proxy.searchParams.set("u", base64UrlEncodeUtf8(input)); return proxy.toString(); } catch { return input; } }; export async function GET(request: Request) { if (isConvexEnabled()) { 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 = searchParams.get("assetId"); if (!assetId) { return NextResponse.json({ error: "缺少 assetId" }, { status: 400 }); } const asset = await client.query(api.mediaAssets.getById, { userId: auth.userId, id: assetId }); if (!asset) { return NextResponse.json({ error: "资源不存在" }, { status: 404 }); } const refreshed = await client.mutation(api.mediaAssets.refreshUrl, { userId: auth.userId, id: assetId }); const signedUrl = (refreshed as { signedUrl?: string | null } | null)?.signedUrl ?? null; if (!signedUrl) { return NextResponse.json({ error: "生成签名链接失败" }, { status: 500 }); } return NextResponse.json({ signedUrl: maybeProxyForBrowser(request, signedUrl), asset: { id: asset.id, file_name: asset.file_name, mime_type: asset.mime_type, file_size: asset.file_size, }, }); } return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 }); }