0.4.0 convex及界面修改
This commit is contained in:
@@ -4,6 +4,7 @@ import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { HttpError, requireAuthContext } from "@/lib/auth/authContext";
|
||||
import { api } from "@/lib/convex/api";
|
||||
import { getConvexAuthedHttpClient } from "@/lib/convex/server";
|
||||
import { maybeProxyForBrowserUrl } from "@/lib/url/proxyForBrowser";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -37,7 +38,13 @@ export async function GET(request: Request) {
|
||||
limit: Number.isNaN(limit) ? 12 : limit,
|
||||
});
|
||||
|
||||
return NextResponse.json({ items: (items ?? []) as MediaAsset[] });
|
||||
const safeItems = (items ?? []).map((a: any) => ({
|
||||
...a,
|
||||
file_url: maybeProxyForBrowserUrl(request, String(a?.file_url ?? "")),
|
||||
thumbnail_url: maybeProxyForBrowserUrl(request, String(a?.thumbnail_url ?? a?.file_url ?? "")),
|
||||
})) as MediaAsset[];
|
||||
|
||||
return NextResponse.json({ items: safeItems });
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
@@ -149,7 +156,13 @@ export async function POST(request: Request) {
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({ asset });
|
||||
const safeAsset = {
|
||||
...asset,
|
||||
file_url: maybeProxyForBrowserUrl(request, String(asset?.file_url ?? "")),
|
||||
thumbnail_url: maybeProxyForBrowserUrl(request, String(asset?.thumbnail_url ?? asset?.file_url ?? "")),
|
||||
} as MediaAsset;
|
||||
|
||||
return NextResponse.json({ asset: safeAsset });
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
|
||||
@@ -6,6 +6,7 @@ import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { HttpError, requireAuthContext } from "@/lib/auth/authContext";
|
||||
import { api } from "@/lib/convex/api";
|
||||
import { getConvexAuthedHttpClient } from "@/lib/convex/server";
|
||||
import { maybeProxyForBrowserUrl } from "@/lib/url/proxyForBrowser";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -200,8 +201,14 @@ export async function POST(request: Request) {
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ items: results });
|
||||
}
|
||||
const safeItems = (results ?? []).map((a: any) => ({
|
||||
...a,
|
||||
file_url: maybeProxyForBrowserUrl(request, String(a?.file_url ?? "")),
|
||||
thumbnail_url: maybeProxyForBrowserUrl(request, String(a?.thumbnail_url ?? a?.file_url ?? "")),
|
||||
}));
|
||||
|
||||
return NextResponse.json({ items: safeItems });
|
||||
}
|
||||
default:
|
||||
return NextResponse.json({ error: "不支持的操作" }, { status: 400 });
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { HttpError, requireAuthContext } from "@/lib/auth/authContext";
|
||||
import { api } from "@/lib/convex/api";
|
||||
import { getConvexAuthedHttpClient } from "@/lib/convex/server";
|
||||
import { maybeProxyForBrowserUrl } from "@/lib/url/proxyForBrowser";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -34,9 +35,14 @@ export async function GET(request: Request) {
|
||||
limit: Number.isNaN(limit) ? 200 : limit,
|
||||
});
|
||||
|
||||
return NextResponse.json({ items: (items ?? []) as MediaAsset[] });
|
||||
const safeItems = (items ?? []).map((a: any) => ({
|
||||
...a,
|
||||
file_url: maybeProxyForBrowserUrl(request, String(a?.file_url ?? "")),
|
||||
thumbnail_url: maybeProxyForBrowserUrl(request, String(a?.thumbnail_url ?? a?.file_url ?? "")),
|
||||
})) as MediaAsset[];
|
||||
|
||||
return NextResponse.json({ items: safeItems });
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
}
|
||||
|
||||
|
||||
@@ -10,21 +10,10 @@ interface EmptyTrashPayload {
|
||||
workspaceId?: 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();
|
||||
function makeExpiredDeletedAtForForceEmpty(): string {
|
||||
// “清空附件垃圾桶”是一个显式的强制操作:直接清空所有垃圾桶内的附件,不受 10 分钟宽限期限制。
|
||||
// Convex 侧按 `deleted_at <= expiredDeletedAt` 做过滤,因此这里给一个很靠后的时间上界即可。
|
||||
return new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 100).toISOString();
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
@@ -48,7 +37,7 @@ export async function POST(request: Request) {
|
||||
const res = await client.mutation(api.mediaAssets.emptyTrashByWorkspace, {
|
||||
userId: auth.userId,
|
||||
workspaceId,
|
||||
expiredDeletedAt: makeExpiredDeletedAt(),
|
||||
expiredDeletedAt: makeExpiredDeletedAtForForceEmpty(),
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true, ...(res as any) });
|
||||
|
||||
@@ -3,44 +3,10 @@ import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { api } from "@/lib/convex/api";
|
||||
import { HttpError } from "@/lib/auth/authContext";
|
||||
import { getAuthedConvexClient } from "@/lib/convex/route";
|
||||
import { maybeProxyForBrowserUrl } from "@/lib/url/proxyForBrowser";
|
||||
|
||||
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;
|
||||
@@ -75,7 +41,7 @@ export async function GET(request: Request) {
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
signedUrl: maybeProxyForBrowser(request, signedUrl),
|
||||
signedUrl: maybeProxyForBrowserUrl(request, signedUrl),
|
||||
asset: {
|
||||
id: asset.id,
|
||||
file_name: asset.file_name,
|
||||
|
||||
@@ -1,44 +1,10 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { HttpError, requireAuthContext } from "@/lib/auth/authContext";
|
||||
import { maybeProxyForBrowserUrl } from "@/lib/url/proxyForBrowser";
|
||||
|
||||
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()) {
|
||||
try {
|
||||
@@ -58,7 +24,7 @@ export async function GET(request: Request) {
|
||||
|
||||
// 说明:Convex + MinIO 模式下,这个接口目前主要用于“外链文件”走 ONLYOFFICE 的场景。
|
||||
// 由于外链本身已经是可访问的 URL,这里只做最小透传。
|
||||
return NextResponse.json({ signedUrl: maybeProxyForBrowser(request, fileUrl) });
|
||||
return NextResponse.json({ signedUrl: maybeProxyForBrowserUrl(request, fileUrl) });
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: "当前仅支持 Convex 模式" }, { status: 501 });
|
||||
|
||||
@@ -7,6 +7,7 @@ import { isConvexEnabled } from "@/lib/convex/enabled";
|
||||
import { HttpError, requireAuthContext } from "@/lib/auth/authContext";
|
||||
import { api } from "@/lib/convex/api";
|
||||
import { getConvexAuthedHttpClient } from "@/lib/convex/server";
|
||||
import { maybeProxyForBrowserUrl } from "@/lib/url/proxyForBrowser";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -89,9 +90,14 @@ export async function POST(request: Request) {
|
||||
});
|
||||
|
||||
const asset = created as unknown as MediaAsset;
|
||||
const safeAsset = {
|
||||
...(asset as any),
|
||||
file_url: maybeProxyForBrowserUrl(request, String((asset as any)?.file_url ?? "")),
|
||||
thumbnail_url: maybeProxyForBrowserUrl(request, String((asset as any)?.thumbnail_url ?? (asset as any)?.file_url ?? "")),
|
||||
} as MediaAsset;
|
||||
|
||||
return NextResponse.json({
|
||||
asset,
|
||||
asset: safeAsset,
|
||||
mindmapUrl: `asset:${asset.id}`,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user