feat(api-watermark): 添加水印管理相关接口
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
Body, Controller, Delete, Get, Headers, HttpCode, Inject, Param, Patch, Post, Put, Query, Req,
|
||||
} from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiCreatedResponse, ApiNoContentResponse, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
bindStoreWatermarkRequestSchema, createWatermarkTemplateRequestSchema,
|
||||
listWatermarkTemplatesQuerySchema, updateWatermarkTemplateRequestSchema, type Workspace,
|
||||
} from '@atlas/contracts';
|
||||
import { z } from 'zod';
|
||||
import { ApiError } from '../common/api-error.js';
|
||||
import type { AuthenticatedRequest } from '../auth/auth.guard.js';
|
||||
import { CurrentWorkspace } from '../workspace/current-workspace.decorator.js';
|
||||
import { WatermarksService } from './watermarks.service.js';
|
||||
|
||||
@ApiTags('Watermarks')
|
||||
@ApiBearerAuth('access-token')
|
||||
@Controller()
|
||||
export class WatermarksController {
|
||||
public constructor(@Inject(WatermarksService) private readonly watermarks: WatermarksService) {}
|
||||
|
||||
@Get('watermark-fonts')
|
||||
@ApiOperation({ summary: '读取当前渲染服务可用字体' })
|
||||
public async fonts() { return { data: await this.watermarks.fonts() }; }
|
||||
|
||||
@Get('watermark-templates')
|
||||
@ApiOperation({ summary: '分页读取当前工作空间活动水印模板' })
|
||||
public async list(@Query() query: unknown, @CurrentWorkspace() workspace: Workspace) {
|
||||
return this.watermarks.list(workspace.id, parse(listWatermarkTemplatesQuerySchema, query));
|
||||
}
|
||||
|
||||
@Get('watermark-templates/:templateId')
|
||||
@ApiOperation({ summary: '读取水印模板当前版本' })
|
||||
public async get(@Param('templateId') templateId: string, @CurrentWorkspace() workspace: Workspace) {
|
||||
return { data: await this.watermarks.get(workspace.id, uuid('template_id', templateId)) };
|
||||
}
|
||||
|
||||
@Post('watermark-templates')
|
||||
@ApiCreatedResponse({ description: '已创建水印模板及首个渲染版本' })
|
||||
public async create(@Body() body: unknown, @Headers('idempotency-key') key: string | undefined,
|
||||
@Req() request: AuthenticatedRequest, @CurrentWorkspace() workspace: Workspace) {
|
||||
return (await this.watermarks.create(workspace.id, request.auth.userId, idempotencyKey(key),
|
||||
parse(createWatermarkTemplateRequestSchema, body))).body;
|
||||
}
|
||||
|
||||
@Patch('watermark-templates/:templateId')
|
||||
@ApiOkResponse({ description: '已更新水印模板' })
|
||||
public async update(@Param('templateId') templateId: string, @Body() body: unknown,
|
||||
@Headers('idempotency-key') key: string | undefined, @Headers('if-match') ifMatch: string | undefined,
|
||||
@Req() request: AuthenticatedRequest, @CurrentWorkspace() workspace: Workspace) {
|
||||
return (await this.watermarks.update(workspace.id, request.auth.userId, uuid('template_id', templateId),
|
||||
idempotencyKey(key), revision(ifMatch), parse(updateWatermarkTemplateRequestSchema, body))).body;
|
||||
}
|
||||
|
||||
@Delete('watermark-templates/:templateId')
|
||||
@HttpCode(204)
|
||||
@ApiNoContentResponse({ description: '已软删除模板并解除当前店铺绑定' })
|
||||
public async remove(@Param('templateId') templateId: string, @Headers('idempotency-key') key: string | undefined,
|
||||
@Req() request: AuthenticatedRequest, @CurrentWorkspace() workspace: Workspace) {
|
||||
await this.watermarks.remove(workspace.id, request.auth.userId, uuid('template_id', templateId), idempotencyKey(key));
|
||||
}
|
||||
|
||||
@Put('stores/:storeId/watermark-binding')
|
||||
@ApiOkResponse({ description: '已更新店铺推荐水印' })
|
||||
public async bindStore(@Param('storeId') storeId: string, @Body() body: unknown,
|
||||
@Headers('idempotency-key') key: string | undefined, @Req() request: AuthenticatedRequest,
|
||||
@CurrentWorkspace() workspace: Workspace) {
|
||||
const input = parse(bindStoreWatermarkRequestSchema, body) as { template_id: string | null };
|
||||
return (await this.watermarks.bindStore(workspace.id, request.auth.userId, uuid('store_id', storeId),
|
||||
idempotencyKey(key), input.template_id)).body;
|
||||
}
|
||||
}
|
||||
|
||||
function parse<T extends { safeParse: (input: unknown) => { success: boolean; data?: unknown; error?: { issues: Array<{ path: PropertyKey[]; message: string }> } } }>(schema: T, input: unknown) {
|
||||
const result = schema.safeParse(input);
|
||||
if (result.success) return result.data as ReturnType<T['safeParse']> extends { data: infer Data } ? Data : never;
|
||||
throw new ApiError(422, 'VALIDATION_FAILED', '请求参数不符合要求',
|
||||
result.error?.issues.map((issue) => ({ field: issue.path.join('.'), code: 'INVALID_FIELD', message: issue.message })));
|
||||
}
|
||||
function uuid(field: string, value: string) {
|
||||
if (z.uuid().safeParse(value).success) return value;
|
||||
throw new ApiError(422, 'VALIDATION_FAILED', '请求参数不符合要求', [{ field, code: 'INVALID_FIELD', message: `${field} 必须是 UUID` }]);
|
||||
}
|
||||
function idempotencyKey(value: string | undefined) {
|
||||
if (!value) throw new ApiError(400, 'IDEMPOTENCY_KEY_REQUIRED', '请求缺少 Idempotency-Key');
|
||||
return uuid('Idempotency-Key', value);
|
||||
}
|
||||
function revision(value: string | undefined) {
|
||||
const match = value?.match(/^"([1-9]\d*)"$/);
|
||||
if (!match) throw new ApiError(400, 'IF_MATCH_REQUIRED', '请求缺少或格式错误的 If-Match');
|
||||
return Number(match[1]);
|
||||
}
|
||||
Reference in New Issue
Block a user