Files
mnote/wolai-frontend/src/app/api/media/sign/route.ts
T
2026-01-21 18:21:10 +08:00

90 lines
3.0 KiB
TypeScript

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 });
}