feat(api-water): 支持上传水印图片
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Headers,
|
||||
Inject,
|
||||
Param,
|
||||
Post,
|
||||
Req,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiCreatedResponse,
|
||||
ApiOkResponse,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import {
|
||||
completeMediaUploadIntentRequestSchema,
|
||||
createMediaAccessUrlRequestSchema,
|
||||
createMediaUploadIntentRequestSchema,
|
||||
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 { MediaService } from './media.service.js';
|
||||
|
||||
@ApiTags('Media')
|
||||
@ApiBearerAuth('access-token')
|
||||
@Controller()
|
||||
export class MediaController {
|
||||
public constructor(
|
||||
@Inject(MediaService) private readonly media: MediaService,
|
||||
) {}
|
||||
|
||||
@Post('media/upload-intents')
|
||||
@ApiCreatedResponse({ description: '已创建一次性预签名上传意图' })
|
||||
public async createIntent(
|
||||
@Body() body: unknown,
|
||||
@Headers('idempotency-key') key: string | undefined,
|
||||
@Req() request: AuthenticatedRequest,
|
||||
@CurrentWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return (
|
||||
await this.media.createIntent(
|
||||
workspace.id,
|
||||
request.auth.userId,
|
||||
idempotencyKey(key),
|
||||
parse(createMediaUploadIntentRequestSchema, body),
|
||||
)
|
||||
).body;
|
||||
}
|
||||
|
||||
@Post('media/upload-intents/:uploadId/complete')
|
||||
@ApiCreatedResponse({ description: '已完成对象安全校验并创建素材' })
|
||||
public async complete(
|
||||
@Param('uploadId') uploadId: string,
|
||||
@Body() body: unknown,
|
||||
@Headers('idempotency-key') key: string | undefined,
|
||||
@Req() request: AuthenticatedRequest,
|
||||
@CurrentWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return (
|
||||
await this.media.completeIntent(
|
||||
workspace.id,
|
||||
request.auth.userId,
|
||||
uuid('upload_id', uploadId),
|
||||
idempotencyKey(key),
|
||||
parse(completeMediaUploadIntentRequestSchema, body),
|
||||
)
|
||||
).body;
|
||||
}
|
||||
|
||||
@Post('media-assets/:assetId/access-urls')
|
||||
@ApiOkResponse({ description: '已签发短时预览 URL' })
|
||||
public async accessUrl(
|
||||
@Param('assetId') assetId: string,
|
||||
@Body() body: unknown,
|
||||
@CurrentWorkspace() workspace: Workspace,
|
||||
) {
|
||||
parse(createMediaAccessUrlRequestSchema, body);
|
||||
return {
|
||||
data: await this.media.accessUrl(
|
||||
workspace.id,
|
||||
uuid('asset_id', assetId),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user