Files
photo-live/services/api/src/uploads/dto.ts
T
浅念 31ba03de11 feat(api): password change endpoint and source device id on uploads
P3 Task 3 契约增补批次(单一事实来源 openapi.yaml + cn 实现 + e2e):

- POST /v1/auth/password-change(operationId changePassword,需求 4.1 的
  P1 契约缺口补齐):Bearer + {oldPassword 1-128, newPassword 8-128} → 204;
  旧密码错误 401 AUTH_INVALID_CREDENTIALS;成功后事务内更新 argon2 哈希、
  撤销该用户除当前会话外的全部其他会话(当前会话保留,本人不下线)、写
  审计 auth.password_change。
- sourceDeviceId(A19 设备来源语义):UploadInitRequest 可选 8-64 位设备
  标识;upload_sessions 落列(VARCHAR(64) utf8mb4_0900_bin,可空,存量
  行 NULL 兼容);GET /uploads 条目回显;幂等重放不比对(first-writer,
  首次落库值不随重放漂移)。
- e2e:auth 增 4 例(未认证 401 / 新密码过短 400 / 旧密码错误 401 且会话
  无副作用 / 成功后他端会话撤销当前保留 + 新旧密码登录裁决 + 审计);
  uploads 增 1 例(携带落库回显、换设备重放保留首值、缺省 null、长度
  越界 400)。api e2e 179 → 184;contracts gen/redocly/单测、worker、
  sync 门全绿。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-19 00:59:39 +08:00

59 lines
1.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// services/api/src/uploads/dto.ts —— 上传端点入参 DTO(Task 18;sourceDeviceId
// 为 P3 Task 3 契约增补)。校验约束对齐契约 UploadInitRequest:
// · contentType 限白名单枚举(image/heic 暂定,G2 真机验证);
// · bytes ≥ 1(契约 minimum);上限 UPLOAD_MAX_BYTES 是环境相关量,逐请求
// 惰性读取,故在服务层校验(同 filename 扩展名白名单与 categoryId 属主校验);
// · clientTaskId/filename/albumId/categoryId 列宽对齐 db-cn schema
// (VarChar 128/255/64/64);sourceDeviceId 8-64(A19 设备来源语义)。
import {
IsIn,
IsInt,
IsNotEmpty,
IsOptional,
IsString,
MaxLength,
Min,
MinLength,
} from 'class-validator';
import { UPLOAD_CONTENT_TYPES, type UploadContentType } from './support';
export class UploadInitRequestDto {
@IsString()
@IsNotEmpty()
@MaxLength(64)
albumId!: string;
/** 缺省为相册默认分类(P1a 无默认分类概念,落 null)。 */
@IsOptional()
@IsString()
@IsNotEmpty()
@MaxLength(64)
categoryId?: string;
/** 客户端任务幂等键(A03:同任务重试复用会话与照片 ID)。 */
@IsString()
@IsNotEmpty()
@MaxLength(128)
clientTaskId!: string;
/** 原始文件名,仅元数据;扩展名白名单校验在服务层(对象键后缀派生)。 */
@IsString()
@IsNotEmpty()
@MaxLength(255)
filename!: string;
@IsInt()
@Min(1)
bytes!: number;
@IsIn(UPLOAD_CONTENT_TYPES)
contentType!: UploadContentType;
/** 客户端设备标识(A19,P3 Task 3 增补):可选,8-64 字符。 */
@IsOptional()
@IsString()
@MinLength(8)
@MaxLength(64)
sourceDeviceId?: string;
}