feat(api-watermark): 添加水印管理相关接口
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
export * from './collection';
|
||||
export * from './auth';
|
||||
export * from './stores';
|
||||
export * from './watermarks';
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const uuid = z.uuid();
|
||||
const opacity = z
|
||||
.string()
|
||||
.regex(/^(0|1)(\.\d{1,4})?$|^0\.\d{1,4}$/, '不透明度必须是 0 到 1 的小数');
|
||||
const scaleRatio = z
|
||||
.string()
|
||||
.regex(/^(0\.([1-9]|[1-9]\d{1,3})|1(\.\d{1,4})?|2(\.0{1,4})?)$/, '缩放比例必须在 0.1 到 2 之间');
|
||||
export const watermarkPositionSchema = z.enum([
|
||||
'top_left', 'top_center', 'top_right', 'middle_left', 'center',
|
||||
'middle_right', 'bottom_left', 'bottom_center', 'bottom_right',
|
||||
]);
|
||||
|
||||
const textConfig = z.object({
|
||||
text_content: z.string().trim().min(1).max(200),
|
||||
font_code: z.string().trim().min(1).max(120),
|
||||
font_size_px: z.number().int().min(12).max(60),
|
||||
opacity,
|
||||
position: watermarkPositionSchema,
|
||||
}).strict();
|
||||
const imageConfig = z.object({
|
||||
image_asset_id: uuid,
|
||||
scale_ratio: scaleRatio,
|
||||
opacity,
|
||||
position: watermarkPositionSchema,
|
||||
}).strict();
|
||||
|
||||
export const createWatermarkTemplateRequestSchema = z.object({
|
||||
name: z.string().trim().min(1).max(120),
|
||||
kind: z.enum(['text', 'image']),
|
||||
config: z.union([textConfig, imageConfig]),
|
||||
}).strict().superRefine((value, context) => {
|
||||
if (value.kind === 'text' && !('text_content' in value.config)) {
|
||||
context.addIssue({ code: 'custom', path: ['config'], message: '文字水印需要完整文字配置' });
|
||||
}
|
||||
if (value.kind === 'image' && !('image_asset_id' in value.config)) {
|
||||
context.addIssue({ code: 'custom', path: ['config'], message: '图片水印需要完整图片配置' });
|
||||
}
|
||||
});
|
||||
|
||||
const partialConfig = z.object({
|
||||
text_content: z.string().trim().min(1).max(200).optional(),
|
||||
image_asset_id: uuid.optional(),
|
||||
font_code: z.string().trim().min(1).max(120).optional(),
|
||||
font_size_px: z.number().int().min(12).max(60).optional(),
|
||||
opacity: opacity.optional(),
|
||||
scale_ratio: scaleRatio.optional(),
|
||||
position: watermarkPositionSchema.optional(),
|
||||
}).strict();
|
||||
|
||||
export const updateWatermarkTemplateRequestSchema = z.object({
|
||||
name: z.string().trim().min(1).max(120).optional(),
|
||||
kind: z.enum(['text', 'image']).optional(),
|
||||
config: partialConfig.optional(),
|
||||
}).strict()
|
||||
.refine((value) => Object.keys(value).length > 0, { message: '至少需要提交一个可修改字段' });
|
||||
|
||||
export const listWatermarkTemplatesQuerySchema = z.object({
|
||||
cursor: z.string().min(1).optional(),
|
||||
limit: z.coerce.number().int().min(1).max(100).default(20),
|
||||
}).strict();
|
||||
|
||||
export const bindStoreWatermarkRequestSchema = z.object({
|
||||
template_id: uuid.nullable(),
|
||||
}).strict();
|
||||
|
||||
export type CreateWatermarkTemplateRequest = z.infer<typeof createWatermarkTemplateRequestSchema>;
|
||||
export type UpdateWatermarkTemplateRequest = z.infer<typeof updateWatermarkTemplateRequestSchema>;
|
||||
export type ListWatermarkTemplatesQuery = z.infer<typeof listWatermarkTemplatesQuerySchema>;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- 首期文字水印使用随渲染运行时部署的 Noto Sans 字体。
|
||||
SET search_path TO atlas, public;
|
||||
|
||||
INSERT INTO watermark_fonts (code, display_name, status)
|
||||
VALUES ('NotoSans', 'Noto Sans', 'active')
|
||||
ON CONFLICT (code) DO UPDATE
|
||||
SET display_name = EXCLUDED.display_name,
|
||||
status = 'active';
|
||||
Reference in New Issue
Block a user