feat(product-edit):商品编辑页初版
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { Body, Controller, Get, Headers, Inject, Param, Post, Put, Req, Res } from '@nestjs/common';
|
||||
import { z } from 'zod';
|
||||
import { saveProductEditorDraftRequestSchema, type Workspace } from '@atlas/contracts';
|
||||
import { ApiError } from '../common/api-error.js';
|
||||
import type { AuthenticatedRequest } from '../auth/auth.guard.js';
|
||||
import { CurrentWorkspace } from '../workspace/current-workspace.decorator.js';
|
||||
import { ProductEditorService } from './product-editor.service.js';
|
||||
|
||||
type Response = { status: (value: number) => Response };
|
||||
|
||||
@Controller()
|
||||
export class ProductEditorController {
|
||||
public constructor(
|
||||
@Inject(ProductEditorService)
|
||||
private readonly editor: ProductEditorService,
|
||||
) {}
|
||||
|
||||
@Post('collection-items/:collectionItemId/editor-contexts')
|
||||
async enter(
|
||||
@Param('collectionItemId') collectionItemId: string,
|
||||
@Headers('x-atlas-platform-id') platformId: string | undefined,
|
||||
@Headers('idempotency-key') key: string | undefined,
|
||||
@Req() request: AuthenticatedRequest,
|
||||
@CurrentWorkspace() workspace: Workspace,
|
||||
@Res({ passthrough: true }) response: Response,
|
||||
) {
|
||||
const result = await this.editor.enter(
|
||||
workspace.id,
|
||||
request.auth.userId,
|
||||
requiredUuid('collection_item_id', collectionItemId),
|
||||
requiredUuid('X-Atlas-Platform-Id', platformId),
|
||||
requiredUuid('Idempotency-Key', key),
|
||||
);
|
||||
response.status(result.status);
|
||||
return result.body;
|
||||
}
|
||||
|
||||
@Get('products/:productId/editor')
|
||||
async read(
|
||||
@Param('productId') productId: string,
|
||||
@Headers('x-atlas-platform-id') platformId: string | undefined,
|
||||
@CurrentWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return {
|
||||
data: await this.editor.read(
|
||||
workspace.id,
|
||||
requiredUuid('product_id', productId),
|
||||
requiredUuid('X-Atlas-Platform-Id', platformId),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@Put('products/:productId/editor-draft')
|
||||
async save(
|
||||
@Param('productId') productId: string,
|
||||
@Body() body: unknown,
|
||||
@Headers('x-atlas-platform-id') platformId: string | undefined,
|
||||
@Headers('idempotency-key') key: string | undefined,
|
||||
@Headers('if-match') ifMatch: string | undefined,
|
||||
@Req() request: AuthenticatedRequest,
|
||||
@CurrentWorkspace() workspace: Workspace,
|
||||
) {
|
||||
const parsed = saveProductEditorDraftRequestSchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
throw new ApiError(422, 'VALIDATION_FAILED', '请求参数不符合要求', parsed.error.issues.map((issue) => ({
|
||||
field: issue.path.join('.'), code: 'INVALID_FIELD', message: issue.message,
|
||||
})));
|
||||
}
|
||||
return (await this.editor.save(
|
||||
workspace.id,
|
||||
request.auth.userId,
|
||||
requiredUuid('product_id', productId),
|
||||
requiredUuid('X-Atlas-Platform-Id', platformId),
|
||||
requiredUuid('Idempotency-Key', key),
|
||||
parseEditorRevision(ifMatch),
|
||||
parsed.data,
|
||||
)).body;
|
||||
}
|
||||
}
|
||||
|
||||
function requiredUuid(field: string, value: string | undefined) {
|
||||
if (value && z.uuid().safeParse(value).success) return value;
|
||||
throw new ApiError(400, 'VALIDATION_FAILED', '请求参数不符合要求', [
|
||||
{ field, code: 'INVALID_FIELD', message: `${field} 必须是 UUID` },
|
||||
]);
|
||||
}
|
||||
|
||||
function parseEditorRevision(value: string | undefined) {
|
||||
const match = value?.match(/^"([1-9]\d*):([1-9]\d*)"$/);
|
||||
if (!match) throw new ApiError(400, 'IF_MATCH_REQUIRED', '请求缺少或格式错误的 If-Match');
|
||||
return { product: Number(match[1]), profile: Number(match[2]) };
|
||||
}
|
||||
Reference in New Issue
Block a user