chore: save snapshot before tag 0.3
This commit is contained in:
@@ -28,13 +28,14 @@ export default async function AppLayout({ children }: { children: ReactNode }) {
|
||||
let sidebarInitialData: SidebarInitialData | null = null;
|
||||
|
||||
if (activeWorkspaceId) {
|
||||
const dataset = await fetchSidebarDataset(supabase, activeWorkspaceId);
|
||||
const dataset = await fetchSidebarDataset(supabase, activeWorkspaceId);
|
||||
documents = dataset.documents;
|
||||
sidebarInitialData = {
|
||||
activeWorkspaceId,
|
||||
workspaces,
|
||||
documents: dataset.documents,
|
||||
trashedDocuments: dataset.trashedDocuments,
|
||||
mediaAssets: dataset.mediaAssets,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createSupabaseRouteClient } from "@/lib/supabase/server";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type Action = "copy" | "move" | "delete" | "rename";
|
||||
|
||||
interface BatchPayload {
|
||||
action: Action;
|
||||
assetIds: string[];
|
||||
targetDocumentId?: string;
|
||||
newName?: string;
|
||||
}
|
||||
|
||||
const BUCKET = process.env.NEXT_PUBLIC_SUPABASE_MEDIA_BUCKET ?? "workspace";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const supabase = await createSupabaseRouteClient();
|
||||
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) {
|
||||
case "delete": {
|
||||
await Promise.all(
|
||||
assets.map(async (asset) => {
|
||||
if (asset.storage_path) {
|
||||
await supabase.storage.from(asset.bucket || BUCKET).remove([asset.storage_path]);
|
||||
}
|
||||
}),
|
||||
);
|
||||
const { error } = await supabase.from("media_assets").delete().in("id", payload.assetIds);
|
||||
if (error) throw error;
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
case "rename": {
|
||||
if (payload.assetIds.length !== 1 || !payload.newName) {
|
||||
return NextResponse.json({ error: "重命名需要单个文件与新名称" }, { status: 400 });
|
||||
}
|
||||
const asset = assets[0];
|
||||
const ext = asset.file_name?.includes(".") ? `.${asset.file_name.split(".").pop()}` : "";
|
||||
const newFileName = payload.newName.includes(".") || !ext ? payload.newName : `${payload.newName}${ext}`;
|
||||
const targetPath = `${session.user.id}/${asset.workspace_id}/${asset.document_id}/assets/${newFileName}`;
|
||||
if (asset.storage_path) {
|
||||
const moveResult = await supabase.storage
|
||||
.from(asset.bucket || BUCKET)
|
||||
.move(asset.storage_path, targetPath);
|
||||
if (moveResult.error) throw moveResult.error;
|
||||
}
|
||||
const { error } = await supabase
|
||||
.from("media_assets")
|
||||
.update({
|
||||
file_name: newFileName,
|
||||
storage_path: targetPath,
|
||||
file_url: null,
|
||||
thumbnail_url: null,
|
||||
})
|
||||
.eq("id", asset.id);
|
||||
if (error) throw error;
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
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 (docErr || !targetDoc) {
|
||||
return NextResponse.json({ error: "目标页面不存在" }, { status: 404 });
|
||||
}
|
||||
const results = [];
|
||||
for (const asset of assets) {
|
||||
const fileName = asset.file_name ?? "附件";
|
||||
const targetPath = `${session.user.id}/${targetDoc.workspace_id}/${payload.targetDocumentId}/assets/${fileName}`;
|
||||
const sourcePath = asset.storage_path;
|
||||
const bucket = asset.bucket || BUCKET;
|
||||
if (!sourcePath) continue;
|
||||
if (payload.action === "copy") {
|
||||
const copyRes = await supabase.storage.from(bucket).copy(sourcePath, targetPath);
|
||||
if (copyRes.error) throw copyRes.error;
|
||||
const { data: inserted, error } = await supabase
|
||||
.from("media_assets")
|
||||
.insert({
|
||||
workspace_id: targetDoc.workspace_id,
|
||||
document_id: payload.targetDocumentId,
|
||||
asset_type: asset.asset_type,
|
||||
file_name: fileName,
|
||||
file_size: asset.file_size,
|
||||
mime_type: asset.mime_type,
|
||||
bucket,
|
||||
storage_path: targetPath,
|
||||
created_by: session.user.id,
|
||||
})
|
||||
.select("*")
|
||||
.single();
|
||||
if (error) throw error;
|
||||
results.push(inserted);
|
||||
} else {
|
||||
const moveRes = await supabase.storage.from(bucket).move(sourcePath, targetPath);
|
||||
if (moveRes.error) throw moveRes.error;
|
||||
const { data: updated, error } = await supabase
|
||||
.from("media_assets")
|
||||
.update({
|
||||
workspace_id: targetDoc.workspace_id,
|
||||
document_id: payload.targetDocumentId,
|
||||
storage_path: targetPath,
|
||||
file_name: fileName,
|
||||
file_url: null,
|
||||
thumbnail_url: null,
|
||||
})
|
||||
.eq("id", asset.id)
|
||||
.select("*")
|
||||
.single();
|
||||
if (error) throw error;
|
||||
results.push(updated);
|
||||
}
|
||||
}
|
||||
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 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user