0.5 缩减重构

This commit is contained in:
lix-2026
2026-04-13 19:21:42 +08:00
parent af92c4b149
commit 71fb1aee7e
2023 changed files with 21113 additions and 394493 deletions
@@ -54,39 +54,39 @@ export async function GET(request: Request) {
data: { user },
error: authError,
} = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ error: "未登录" }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const workspaceId = searchParams.get("workspaceId");
const limit = Number.parseInt(searchParams.get("limit") ?? "12", 10);
if (!workspaceId) {
return NextResponse.json({ error: "缺少 workspaceId" }, { status: 400 });
}
let query = supabase
.from("media_assets")
.select("*")
.eq("workspace_id", workspaceId)
.is("deleted_at", null)
.order("created_at", { ascending: false })
.limit(Number.isNaN(limit) ? 12 : limit);
const assetType = searchParams.get("assetType");
if (assetType) {
query = query.eq("asset_type", assetType);
}
const { data, error } = await query;
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
return NextResponse.json({ items: (data ?? []) as MediaAsset[] });
if (authError || !user) {
return NextResponse.json({ error: "未登录" }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const workspaceId = searchParams.get("workspaceId");
const limit = Number.parseInt(searchParams.get("limit") ?? "12", 10);
if (!workspaceId) {
return NextResponse.json({ error: "缺少 workspaceId" }, { status: 400 });
}
let query = supabase
.from("media_assets")
.select("*")
.eq("workspace_id", workspaceId)
.is("deleted_at", null)
.order("created_at", { ascending: false })
.limit(Number.isNaN(limit) ? 12 : limit);
const assetType = searchParams.get("assetType");
if (assetType) {
query = query.eq("asset_type", assetType);
}
const { data, error } = await query;
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
return NextResponse.json({ items: (data ?? []) as MediaAsset[] });
}
export async function POST(request: Request) {
@@ -171,46 +171,46 @@ export async function POST(request: Request) {
const {
data: { user },
error: authError,
} = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ error: "未登录" }, { status: 401 });
}
const payload = (await request.json()) as {
workspaceId: string;
documentId: string;
fileUrl: string;
thumbnailUrl?: string;
assetType?: string;
fileName?: string;
fileSize?: number;
mimeType?: string;
};
if (!payload.workspaceId || !payload.documentId || !payload.fileUrl) {
return NextResponse.json({ error: "参数不完整" }, { status: 400 });
}
const { data, error } = await supabase
.from("media_assets")
.insert({
workspace_id: payload.workspaceId,
document_id: payload.documentId,
file_url: payload.fileUrl,
thumbnail_url: payload.thumbnailUrl ?? payload.fileUrl,
asset_type: payload.assetType ?? "image",
file_name: payload.fileName,
file_size: payload.fileSize ?? null,
mime_type: payload.mimeType ?? null,
created_by: user.id,
})
.select("*")
.single();
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
return NextResponse.json({ asset: data as MediaAsset });
}
} = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ error: "未登录" }, { status: 401 });
}
const payload = (await request.json()) as {
workspaceId: string;
documentId: string;
fileUrl: string;
thumbnailUrl?: string;
assetType?: string;
fileName?: string;
fileSize?: number;
mimeType?: string;
};
if (!payload.workspaceId || !payload.documentId || !payload.fileUrl) {
return NextResponse.json({ error: "参数不完整" }, { status: 400 });
}
const { data, error } = await supabase
.from("media_assets")
.insert({
workspace_id: payload.workspaceId,
document_id: payload.documentId,
file_url: payload.fileUrl,
thumbnail_url: payload.thumbnailUrl ?? payload.fileUrl,
asset_type: payload.assetType ?? "image",
file_name: payload.fileName,
file_size: payload.fileSize ?? null,
mime_type: payload.mimeType ?? null,
created_by: user.id,
})
.select("*")
.single();
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
return NextResponse.json({ asset: data as MediaAsset });
}
+43 -43
View File
@@ -9,9 +9,9 @@ import { getConvexAuthedHttpClient } from "@/lib/convex/server";
import { maybeProxyForBrowserUrl } from "@/lib/url/proxyForBrowser";
export const dynamic = "force-dynamic";
type Action = "copy" | "move" | "delete" | "rename" | "restore";
interface BatchPayload {
action: Action;
assetIds: string[];
@@ -19,7 +19,7 @@ interface BatchPayload {
targetSubPath?: string;
newName?: string;
}
const BUCKET = process.env.NEXT_PUBLIC_SUPABASE_MEDIA_BUCKET ?? "workspace";
const DEFAULT_DOC_BUCKET = process.env.NEXT_PUBLIC_SUPABASE_DOC_BUCKET ?? "documents";
@@ -225,31 +225,31 @@ export async function POST(request: Request) {
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
return NextResponse.json({ error: "未登录" }, { status: 401 });
}
const payload = (await request.json()) as BatchPayload;
if (!payload?.action || !payload.assetIds?.length) {
return NextResponse.json({ error: "缺少参数" }, { status: 400 });
}
const { data: assets, error: fetchError } = await supabase
.from("media_assets")
.select("*")
.in("id", payload.assetIds);
if (fetchError) {
return NextResponse.json({ error: fetchError.message }, { status: 500 });
}
if (!assets?.length) {
return NextResponse.json({ error: "未找到附件" }, { status: 404 });
}
try {
switch (payload.action) {
if (!session) {
return NextResponse.json({ error: "未登录" }, { status: 401 });
}
const payload = (await request.json()) as BatchPayload;
if (!payload?.action || !payload.assetIds?.length) {
return NextResponse.json({ error: "缺少参数" }, { status: 400 });
}
const { data: assets, error: fetchError } = await supabase
.from("media_assets")
.select("*")
.in("id", payload.assetIds);
if (fetchError) {
return NextResponse.json({ error: fetchError.message }, { status: 500 });
}
if (!assets?.length) {
return NextResponse.json({ error: "未找到附件" }, { status: 404 });
}
try {
switch (payload.action) {
case "delete": {
// 可撤销删除:仅标记 deleted_at,真正清理(OCR/Storage/LightRAG)由后台宽限期任务处理
const { error } = await supabase
@@ -327,14 +327,14 @@ export async function POST(request: Request) {
}
case "copy":
case "move": {
if (!payload.targetDocumentId) {
return NextResponse.json({ error: "缺少目标页面" }, { status: 400 });
}
const { data: targetDoc, error: docErr } = await supabase
.from("documents")
.select("workspace_id")
.eq("id", payload.targetDocumentId)
.single();
if (!payload.targetDocumentId) {
return NextResponse.json({ error: "缺少目标页面" }, { status: 400 });
}
const { data: targetDoc, error: docErr } = await supabase
.from("documents")
.select("workspace_id")
.eq("id", payload.targetDocumentId)
.single();
if (docErr || !targetDoc) {
return NextResponse.json({ error: "目标页面不存在" }, { status: 404 });
}
@@ -411,12 +411,12 @@ export async function POST(request: Request) {
}
return NextResponse.json({ items: results });
}
default:
return NextResponse.json({ error: "不支持的操作" }, { status: 400 });
}
} catch (err) {
const message = err instanceof Error ? err.message : "操作失败";
return NextResponse.json({ error: message }, { status: 500 });
}
default:
return NextResponse.json({ error: "不支持的操作" }, { status: 400 });
}
} catch (err) {
const message = err instanceof Error ? err.message : "操作失败";
return NextResponse.json({ error: message }, { status: 500 });
}
*/
}
+13 -13
View File
@@ -15,23 +15,23 @@ export async function POST(request: Request) {
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
return NextResponse.json({ error: "未登录" }, { status: 401 });
}
const { assetId } = (await request.json()) as { assetId?: string };
if (!assetId) {
return NextResponse.json({ error: "缺少 assetId" }, { status: 400 });
}
if (!session) {
return NextResponse.json({ error: "未登录" }, { status: 401 });
}
const { assetId } = (await request.json()) as { assetId?: string };
if (!assetId) {
return NextResponse.json({ error: "缺少 assetId" }, { status: 400 });
}
const { error } = await supabase
.from("media_assets")
.update({ ocr_status: "processing" })
.eq("id", assetId)
.is("deleted_at", null)
.limit(1);
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
@@ -51,7 +51,7 @@ export async function POST(request: Request) {
console.warn("触发后端 OCR 失败", err);
});
}
return NextResponse.json({ ok: true });
return NextResponse.json({ ok: true });
*/
}
@@ -113,44 +113,44 @@ export async function POST(request: Request) {
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
return NextResponse.json({ error: "未登录" }, { status: 401 });
}
const formData = await request.formData();
if (!session) {
return NextResponse.json({ error: "未登录" }, { status: 401 });
}
const formData = await request.formData();
const file = formData.get("file");
const workspaceId = String(formData.get("workspaceId") ?? "");
const documentId = String(formData.get("documentId") ?? "");
const mindmapIdRaw = String(formData.get("mindmapId") ?? "").trim();
if (!(file instanceof File) || !workspaceId || !documentId) {
return NextResponse.json({ error: "缺少必要参数" }, { status: 400 });
}
try {
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const extension = extname(file.name || "").replace(/\s+/g, "");
const uniqueId = typeof crypto.randomUUID === "function" ? crypto.randomUUID() : Math.random().toString(36).slice(2);
if (!(file instanceof File) || !workspaceId || !documentId) {
return NextResponse.json({ error: "缺少必要参数" }, { status: 400 });
}
try {
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const extension = extname(file.name || "").replace(/\s+/g, "");
const uniqueId = typeof crypto.randomUUID === "function" ? crypto.randomUUID() : Math.random().toString(36).slice(2);
// 与 /api/media/batch 的 move/rename 规则对齐:放到 workspaceId/documentId 下
const mindmapId =
mindmapIdRaw && /^[a-zA-Z0-9_-]{1,128}$/.test(mindmapIdRaw) ? mindmapIdRaw : "";
const subdir = mindmapId ? `mindmaps/${mindmapId}` : "";
const path = `${workspaceId}/${documentId}${subdir ? `/${subdir}` : ""}/${Date.now()}-${uniqueId}${extension}`;
const assetType = resolveAssetType(file.type || "");
const { error: uploadError } = await supabase.storage.from(DOC_BUCKET).upload(path, buffer, {
contentType: file.type,
upsert: false,
});
if (uploadError) {
return NextResponse.json({ error: uploadError.message }, { status: 500 });
}
// 为私有桶生成临时访问链接(7 天);前端可在需要时通过 /api/media/signed-url 刷新
// 对于图片,使用高质量参数以获得更好的显示效果
const assetType = resolveAssetType(file.type || "");
const { error: uploadError } = await supabase.storage.from(DOC_BUCKET).upload(path, buffer, {
contentType: file.type,
upsert: false,
});
if (uploadError) {
return NextResponse.json({ error: uploadError.message }, { status: 500 });
}
// 为私有桶生成临时访问链接(7 天);前端可在需要时通过 /api/media/signed-url 刷新
// 对于图片,使用高质量参数以获得更好的显示效果
let signedUrl = "";
if (assetType === "image") {
const { data: signed } = await supabase.storage.from(DOC_BUCKET).createSignedUrl(path, 60 * 60 * 24 * 7, {
@@ -166,37 +166,37 @@ export async function POST(request: Request) {
}
signedUrl = rewriteToPublicOrigin(signedUrl, getMnoteRuntimeConfig().supabaseUrl);
const { data: asset, error } = await supabase
.from("media_assets")
.insert({
workspace_id: workspaceId,
document_id: documentId,
file_url: signedUrl,
thumbnail_url: signedUrl,
bucket: DOC_BUCKET,
storage_path: path,
asset_type: assetType,
file_name: file.name,
file_size: file.size,
mime_type: file.type,
created_by: session.user.id,
})
.select("*")
.single();
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
// 返回 asset 和一个特殊的 asset:id 格式用于存储在思维导图中
return NextResponse.json({
asset: asset as MediaAsset,
mindmapUrl: `asset:${asset.id}`,
});
} catch (error) {
console.error(error);
return NextResponse.json({ error: "上传失败" }, { status: 500 });
}
const { data: asset, error } = await supabase
.from("media_assets")
.insert({
workspace_id: workspaceId,
document_id: documentId,
file_url: signedUrl,
thumbnail_url: signedUrl,
bucket: DOC_BUCKET,
storage_path: path,
asset_type: assetType,
file_name: file.name,
file_size: file.size,
mime_type: file.type,
created_by: session.user.id,
})
.select("*")
.single();
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
// 返回 asset 和一个特殊的 asset:id 格式用于存储在思维导图中
return NextResponse.json({
asset: asset as MediaAsset,
mindmapUrl: `asset:${asset.id}`,
});
} catch (error) {
console.error(error);
return NextResponse.json({ error: "上传失败" }, { status: 500 });
}
*/
}