velody/backend/src/modules/uploads/uploads.dto.ts
2026-06-14 09:43:41 +02:00

157 lines
3.4 KiB
TypeScript

import { ApiProperty } from '@nestjs/swagger';
import { UploadSessionStatus } from '@prisma/client';
import { Type } from 'class-transformer';
import {
IsInt,
IsOptional,
IsString,
IsUUID,
Matches,
Max,
Min,
MinLength,
ValidateNested,
} from 'class-validator';
export class UploadPrepareRequestDto {
@ApiProperty({
format: 'uuid',
required: false,
description:
'Optional client metadata. Authorization: Bearer <deviceAccessToken> is required and determines access.',
})
@IsOptional()
@IsUUID()
deviceId?: string;
@ApiProperty({
example:
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
})
@Matches(/^[a-f0-9]{64}$/)
sha256!: string;
@ApiProperty({ example: 'track.mp3' })
@IsString()
@Matches(/\.mp3$/i)
originalFilename!: string;
@ApiProperty({ example: 10485760 })
@IsInt()
@Min(1)
@Max(Number.MAX_SAFE_INTEGER)
sizeBytes!: number;
}
export class UploadPrepareResponseDto {
@ApiProperty({ enum: ['exists', 'upload_required'] })
status!: 'exists' | 'upload_required';
@ApiProperty({ required: false, format: 'uuid' })
uploadId?: string;
@ApiProperty({ required: false, example: 0 })
nextOffset?: number;
@ApiProperty({ required: false, format: 'uuid' })
trackId?: string;
@ApiProperty({ required: false, format: 'uuid' })
assetId?: string;
}
export class UploadSessionStatusResponseDto {
@ApiProperty({ format: 'uuid' })
uploadId!: string;
@ApiProperty({ enum: UploadSessionStatus })
status!: UploadSessionStatus;
@ApiProperty({ example: 0 })
receivedBytes!: string;
@ApiProperty({ example: 10485760 })
expectedSizeBytes!: string;
@ApiProperty({ example: 0 })
nextOffset!: string;
@ApiProperty({ required: false, example: '2026-05-28T12:00:00.000Z' })
finalizedAt?: string;
}
export class UploadFinalizeArtworkDto {
@ApiProperty({ example: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJ...' })
@IsString()
@MinLength(1)
dataBase64!: string;
@ApiProperty({
example:
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
})
@Matches(/^[a-f0-9]{64}$/)
sha256!: string;
@ApiProperty({ enum: ['image/jpeg', 'image/png'] })
@Matches(/^image\/(jpeg|png)$/)
mimeType!: string;
@ApiProperty({ required: false, example: 512 })
@IsOptional()
@IsInt()
@Min(1)
@Max(Number.MAX_SAFE_INTEGER)
width?: number;
@ApiProperty({ required: false, example: 512 })
@IsOptional()
@IsInt()
@Min(1)
@Max(Number.MAX_SAFE_INTEGER)
height?: number;
}
export class UploadFinalizeRequestDto {
static readonly artworkMimeTypes = ['image/jpeg', 'image/png'] as const;
@ApiProperty({ example: 'Track Title' })
@IsString()
@MinLength(1)
title!: string;
@ApiProperty({ example: 'Track Artist' })
@IsString()
@MinLength(1)
artist!: string;
@ApiProperty({ required: false, example: 'Album Title' })
@IsOptional()
@IsString()
album?: string;
@ApiProperty({ required: false, example: 245000 })
@IsOptional()
@IsInt()
@Min(1)
@Max(Number.MAX_SAFE_INTEGER)
durationMs?: number;
@ApiProperty({
required: false,
type: () => UploadFinalizeArtworkDto,
})
@IsOptional()
@ValidateNested()
@Type(() => UploadFinalizeArtworkDto)
artwork?: UploadFinalizeArtworkDto;
}
export class UploadFinalizeResponseDto {
@ApiProperty({ format: 'uuid' })
trackId!: string;
@ApiProperty({ format: 'uuid' })
assetId!: string;
}