feat: connect resource library workflows
This commit is contained in:
94
app/api/libraries/[libraryType]/[resourceId]/route.ts
Normal file
94
app/api/libraries/[libraryType]/[resourceId]/route.ts
Normal 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: "资源已移入归档。",
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -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: "资源创建成功。",
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
37
app/api/libraries/uploads/presign/route.ts
Normal file
37
app/api/libraries/uploads/presign/route.ts
Normal 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,
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user