Add single-image zk and hotlink upload API with service tests.

Replace batch DTOs with per-mode endpoints, persist ciphertext with file-type
checks, and return a hashed delete token for future revocation.
This commit is contained in:
2026-10-12 20:18:40 +02:00
parent 7f99256863
commit d4529376c6
15 changed files with 412 additions and 92 deletions
@@ -1,40 +1,24 @@
import { plainToInstance, Transform } from 'class-transformer';
import {
ArrayMaxSize,
ArrayMinSize,
IsArray,
IsBoolean,
IsNotEmpty,
ValidateNested
} from 'class-validator';
import { getAppConfig } from '../../../config';
import { CreateUploadCiphertextItemDto } from './CreateUploadCiphertextItemDto';
import { CreateZkPasswordUploadItemDto } from './CreateZkPasswordUploadItemDto';
import { UploadBatchOptionsDto } from './UploadBatchOptionsDto';
import { IsBoolean, IsNotEmpty, ValidateIf } from 'class-validator';
import { IsBase64 } from '../../../validation/decorators/isBase64';
import { UploadCiphertextOptionsDto } from './UploadCiphertextOptionsDto';
const {
validation: { maxImagesPerUpload }
} = getAppConfig();
export class CreateZkUploadDto extends UploadBatchOptionsDto {
export class CreateZkUploadDto extends UploadCiphertextOptionsDto {
@IsNotEmpty()
@IsBoolean()
passwordProtected: boolean;
@ValidateIf((dto: CreateZkUploadDto) => dto.passwordProtected === true)
@IsNotEmpty()
@IsArray()
@ArrayMinSize(1)
@ArrayMaxSize(maxImagesPerUpload)
@ValidateNested({ each: true })
@Transform(({ value, obj }: { value: unknown; obj: CreateZkUploadDto }) => {
if (!Array.isArray(value)) {
return value;
}
@IsBase64({ maxByteLength: 128 })
wrappedDek?: string;
const ItemClass =
obj.passwordProtected === true ? CreateZkPasswordUploadItemDto : CreateUploadCiphertextItemDto;
@ValidateIf((dto: CreateZkUploadDto) => dto.passwordProtected === true)
@IsNotEmpty()
@IsBase64({ byteLength: 12 })
wrapIv?: string;
return value.map(item => plainToInstance(ItemClass, item));
})
items: Array<CreateUploadCiphertextItemDto | CreateZkPasswordUploadItemDto>;
@ValidateIf((dto: CreateZkUploadDto) => dto.passwordProtected === true)
@IsNotEmpty()
@IsBase64({ maxByteLength: 64 })
salt?: string;
}