feat: connect resource library workflows

This commit is contained in:
afei A
2026-03-28 13:42:22 +08:00
parent c604e6ace1
commit 162d3e12d2
42 changed files with 4709 additions and 305 deletions

View File

@@ -0,0 +1,94 @@
import { adaptLibraryItem, type BackendLibraryResource } from "@/lib/adapters/libraries";
import { backendRequest } from "@/lib/http/backend-client";
import {
RouteError,
jsonSuccess,
parseJsonBody,
withErrorHandling,
} from "@/lib/http/response";
import type { LibraryType } from "@/lib/types/view-models";
import { parseUpdateLibraryResourcePayload } from "@/lib/validation/library-resource";
type RouteContext = {
params: Promise<{
libraryType: string;
resourceId: string;
}>;
};
const BACKEND_LIBRARY_TYPE_MAP: Record<LibraryType, "model" | "scene" | "garment"> = {
models: "model",
scenes: "scene",
garments: "garment",
};
function isLibraryType(value: string): value is LibraryType {
return Object.hasOwn(BACKEND_LIBRARY_TYPE_MAP, value);
}
function parseResourceId(value: string) {
const resourceId = Number(value);
if (!Number.isInteger(resourceId) || resourceId <= 0) {
throw new RouteError(400, "VALIDATION_ERROR", "资源 ID 不合法。");
}
return resourceId;
}
export async function PATCH(request: Request, context: RouteContext) {
return withErrorHandling(async () => {
const { libraryType, resourceId: rawResourceId } = await context.params;
if (!isLibraryType(libraryType)) {
throw new RouteError(404, "NOT_FOUND", "不支持的资源库类型。");
}
const resourceId = parseResourceId(rawResourceId);
const rawPayload = await parseJsonBody(request);
const payload = parseUpdateLibraryResourcePayload(rawPayload);
const response = await backendRequest<BackendLibraryResource>(
`/library/resources/${resourceId}`,
{
method: "PATCH",
body: JSON.stringify(payload),
},
);
return jsonSuccess(
{
item: adaptLibraryItem(response.data),
},
{
status: response.status,
message: "资源更新成功。",
},
);
});
}
export async function DELETE(_request: Request, context: RouteContext) {
return withErrorHandling(async () => {
const { libraryType, resourceId: rawResourceId } = await context.params;
if (!isLibraryType(libraryType)) {
throw new RouteError(404, "NOT_FOUND", "不支持的资源库类型。");
}
const resourceId = parseResourceId(rawResourceId);
const response = await backendRequest<{ id: number }>(
`/library/resources/${resourceId}`,
{
method: "DELETE",
},
);
return jsonSuccess(
{
id: String(response.data.id),
},
{
status: response.status,
message: "资源已移入归档。",
},
);
});
}

View File

@@ -1,10 +1,17 @@
import {
GARMENT_LIBRARY_FIXTURES,
MODEL_LIBRARY_FIXTURES,
SCENE_LIBRARY_FIXTURES,
} from "@/lib/mock/libraries";
import { RouteError, jsonSuccess, withErrorHandling } from "@/lib/http/response";
import type { LibraryItemVM, LibraryType } from "@/lib/types/view-models";
adaptLibraryItem,
type BackendLibraryListResponse,
type BackendLibraryResource,
} from "@/lib/adapters/libraries";
import { backendRequest } from "@/lib/http/backend-client";
import {
RouteError,
jsonSuccess,
parseJsonBody,
withErrorHandling,
} from "@/lib/http/response";
import type { LibraryType } from "@/lib/types/view-models";
import { parseCreateLibraryResourcePayload } from "@/lib/validation/library-resource";
type RouteContext = {
params: Promise<{
@@ -12,16 +19,16 @@ type RouteContext = {
}>;
};
const LIBRARY_FIXTURE_MAP: Record<LibraryType, LibraryItemVM[]> = {
models: MODEL_LIBRARY_FIXTURES,
scenes: SCENE_LIBRARY_FIXTURES,
garments: GARMENT_LIBRARY_FIXTURES,
const BACKEND_LIBRARY_TYPE_MAP: Record<LibraryType, "model" | "scene" | "garment"> = {
models: "model",
scenes: "scene",
garments: "garment",
};
const MESSAGE = "资源库当前使用占位数据,真实后端接口尚未提供。";
const MESSAGE = "资源库当前显示真实后端数据。";
function isLibraryType(value: string): value is LibraryType {
return Object.hasOwn(LIBRARY_FIXTURE_MAP, value);
return Object.hasOwn(BACKEND_LIBRARY_TYPE_MAP, value);
}
export async function GET(_request: Request, context: RouteContext) {
@@ -32,14 +39,48 @@ export async function GET(_request: Request, context: RouteContext) {
throw new RouteError(404, "NOT_FOUND", "不支持的资源库类型。");
}
const backendLibraryType = BACKEND_LIBRARY_TYPE_MAP[libraryType];
const response = await backendRequest<BackendLibraryListResponse>(
`/library/resources?resource_type=${backendLibraryType}&limit=100`,
);
return jsonSuccess(
{
items: LIBRARY_FIXTURE_MAP[libraryType],
items: response.data.items.map(adaptLibraryItem),
},
{
mode: "placeholder",
message: MESSAGE,
},
);
});
}
export async function POST(request: Request, context: RouteContext) {
return withErrorHandling(async () => {
const { libraryType } = await context.params;
if (!isLibraryType(libraryType)) {
throw new RouteError(404, "NOT_FOUND", "不支持的资源库类型。");
}
const rawPayload = await parseJsonBody(request);
const payload = parseCreateLibraryResourcePayload(
rawPayload,
BACKEND_LIBRARY_TYPE_MAP[libraryType],
);
const response = await backendRequest<BackendLibraryResource>("/library/resources", {
method: "POST",
body: JSON.stringify(payload),
});
return jsonSuccess(
{
item: adaptLibraryItem(response.data),
},
{
status: response.status,
message: "资源创建成功。",
},
);
});
}

View File

@@ -0,0 +1,37 @@
import { backendRequest } from "@/lib/http/backend-client";
import {
jsonSuccess,
parseJsonBody,
withErrorHandling,
} from "@/lib/http/response";
import { parseLibraryUploadPresignPayload } from "@/lib/validation/library-resource";
type PresignUploadResponseDto = {
method: string;
upload_url: string;
headers: Record<string, string>;
storage_key: string;
public_url: string;
};
export async function POST(request: Request) {
return withErrorHandling(async () => {
const rawPayload = await parseJsonBody(request);
const payload = parseLibraryUploadPresignPayload(rawPayload);
const response = await backendRequest<PresignUploadResponseDto>(
"/library/uploads/presign",
{
method: "POST",
body: JSON.stringify(payload),
},
);
return jsonSuccess({
method: response.data.method,
uploadUrl: response.data.upload_url,
headers: response.data.headers,
storageKey: response.data.storage_key,
publicUrl: response.data.public_url,
});
});
}