Files
immich/server/src/dtos/sync.dto.ts
Timon 8db61d341f docs(openapi): add descriptions to OpenAPI specification (#25185)
* faces

* add openapi descriptions

* remove dto descriptions

* gen openapi

* dtos

* fix dtos

* fix more

* fix build

* more

* complete dtos

* descriptions on rebase

* gen rebase

* revert correct integer type conversion

* gen after revert

* revert correct nullables

* regen after revert

* actually incorrect adding default here

* revert correct number type conversion

* regen after revert

* revert nullable usage

* regen fully

* readd some comments

* one more

* one more

* use enum

* add missing

* add missing controllers

* add missing dtos

* complete it

* more

* describe global key and slug

* add remaining body and param descriptions

* lint and format

* cleanup

* response and schema descriptions

* test patch according to suggestion

* revert added api response objects

* revert added api body objects

* revert added api param object

* revert added api query objects

* revert reorganized http code objects

* revert reorganize ApiOkResponse objects

* revert added api response objects (2)

* revert added api tag object

* revert added api schema objects

* migrate missing asset.dto.ts

* regenerate openapi builds

* delete generated mustache files

* remove descriptions from properties that are schemas

* lint

* revert nullable type changes

* revert int/num type changes

* remove explicit default

* readd comment

* lint

* pr fixes

* last bits and pieces

* lint and format

* chore: remove rejected patches

* fix: deleting asset from asset-viewer on search results (#25596)

* fix: escape handling in search asset viewer (#25621)

* fix: correctly show owner in album options modal (#25618)

* fix: validation issues

* fix: validation issues

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
Co-authored-by: Min Idzelis <min123@gmail.com>
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
Co-authored-by: Paul Makles <me@insrt.uk>
2026-01-29 08:49:15 -05:00

540 lines
17 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-unsafe-function-type */
import { ApiProperty } from '@nestjs/swagger';
import { ArrayMaxSize, IsInt, IsPositive, IsString } from 'class-validator';
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
import {
AlbumUserRole,
AssetOrder,
AssetType,
AssetVisibility,
MemoryType,
SyncEntityType,
SyncRequestType,
UserAvatarColor,
UserMetadataKey,
} from 'src/enum';
import { UserMetadata } from 'src/types';
import { ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation';
export class AssetFullSyncDto {
@ValidateUUID({ optional: true, description: 'Last asset ID (pagination)' })
lastId?: string;
@ValidateDate({ description: 'Sync assets updated until this date' })
updatedUntil!: Date;
@ApiProperty({ type: 'integer', description: 'Maximum number of assets to return' })
@IsInt()
@IsPositive()
limit!: number;
@ValidateUUID({ optional: true, description: 'Filter by user ID' })
userId?: string;
}
export class AssetDeltaSyncDto {
@ValidateDate({ description: 'Sync assets updated after this date' })
updatedAfter!: Date;
@ValidateUUID({ each: true, description: 'User IDs to sync' })
userIds!: string[];
}
export class AssetDeltaSyncResponseDto {
@ApiProperty({ description: 'Whether full sync is needed' })
needsFullSync!: boolean;
@ApiProperty({ description: 'Upserted assets' })
upserted!: AssetResponseDto[];
@ApiProperty({ description: 'Deleted asset IDs' })
deleted!: string[];
}
export const extraSyncModels: Function[] = [];
export const ExtraModel = (): ClassDecorator => {
// eslint-disable-next-line unicorn/consistent-function-scoping
return (object: Function) => {
extraSyncModels.push(object);
};
};
@ExtraModel()
export class SyncUserV1 {
@ApiProperty({ description: 'User ID' })
id!: string;
@ApiProperty({ description: 'User name' })
name!: string;
@ApiProperty({ description: 'User email' })
email!: string;
@ValidateEnum({ enum: UserAvatarColor, name: 'UserAvatarColor', description: 'User avatar color' })
avatarColor!: UserAvatarColor | null;
@ApiProperty({ description: 'User deleted at' })
deletedAt!: Date | null;
@ApiProperty({ description: 'User has profile image' })
hasProfileImage!: boolean;
@ApiProperty({ description: 'User profile changed at' })
profileChangedAt!: Date;
}
@ExtraModel()
export class SyncAuthUserV1 extends SyncUserV1 {
@ApiProperty({ description: 'User is admin' })
isAdmin!: boolean;
@ApiProperty({ description: 'User pin code' })
pinCode!: string | null;
@ApiProperty({ description: 'User OAuth ID' })
oauthId!: string;
@ApiProperty({ description: 'User storage label' })
storageLabel!: string | null;
@ApiProperty({ type: 'integer' })
quotaSizeInBytes!: number | null;
@ApiProperty({ type: 'integer' })
quotaUsageInBytes!: number;
}
@ExtraModel()
export class SyncUserDeleteV1 {
@ApiProperty({ description: 'User ID' })
userId!: string;
}
@ExtraModel()
export class SyncPartnerV1 {
@ApiProperty({ description: 'Shared by ID' })
sharedById!: string;
@ApiProperty({ description: 'Shared with ID' })
sharedWithId!: string;
@ApiProperty({ description: 'In timeline' })
inTimeline!: boolean;
}
@ExtraModel()
export class SyncPartnerDeleteV1 {
@ApiProperty({ description: 'Shared by ID' })
sharedById!: string;
@ApiProperty({ description: 'Shared with ID' })
sharedWithId!: string;
}
@ExtraModel()
export class SyncAssetV1 {
@ApiProperty({ description: 'Asset ID' })
id!: string;
@ApiProperty({ description: 'Owner ID' })
ownerId!: string;
@ApiProperty({ description: 'Original file name' })
originalFileName!: string;
@ApiProperty({ description: 'Thumbhash' })
thumbhash!: string | null;
@ApiProperty({ description: 'Checksum' })
checksum!: string;
@ApiProperty({ description: 'File created at' })
fileCreatedAt!: Date | null;
@ApiProperty({ description: 'File modified at' })
fileModifiedAt!: Date | null;
@ApiProperty({ description: 'Local date time' })
localDateTime!: Date | null;
@ApiProperty({ description: 'Duration' })
duration!: string | null;
@ValidateEnum({ enum: AssetType, name: 'AssetTypeEnum', description: 'Asset type' })
type!: AssetType;
@ApiProperty({ description: 'Deleted at' })
deletedAt!: Date | null;
@ApiProperty({ description: 'Is favorite' })
isFavorite!: boolean;
@ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility', description: 'Asset visibility' })
visibility!: AssetVisibility;
@ApiProperty({ description: 'Live photo video ID' })
livePhotoVideoId!: string | null;
@ApiProperty({ description: 'Stack ID' })
stackId!: string | null;
@ApiProperty({ description: 'Library ID' })
libraryId!: string | null;
@ApiProperty({ type: 'integer', description: 'Asset width' })
width!: number | null;
@ApiProperty({ type: 'integer', description: 'Asset height' })
height!: number | null;
@ApiProperty({ description: 'Is edited' })
isEdited!: boolean;
}
@ExtraModel()
export class SyncAssetDeleteV1 {
@ApiProperty({ description: 'Asset ID' })
assetId!: string;
}
@ExtraModel()
export class SyncAssetExifV1 {
@ApiProperty({ description: 'Asset ID' })
assetId!: string;
@ApiProperty({ description: 'Description' })
description!: string | null;
@ApiProperty({ type: 'integer', description: 'Exif image width' })
exifImageWidth!: number | null;
@ApiProperty({ type: 'integer', description: 'Exif image height' })
exifImageHeight!: number | null;
@ApiProperty({ type: 'integer', description: 'File size in byte' })
fileSizeInByte!: number | null;
@ApiProperty({ description: 'Orientation' })
orientation!: string | null;
@ApiProperty({ description: 'Date time original' })
dateTimeOriginal!: Date | null;
@ApiProperty({ description: 'Modify date' })
modifyDate!: Date | null;
@ApiProperty({ description: 'Time zone' })
timeZone!: string | null;
@ApiProperty({ type: 'number', format: 'double', description: 'Latitude' })
latitude!: number | null;
@ApiProperty({ type: 'number', format: 'double', description: 'Longitude' })
longitude!: number | null;
@ApiProperty({ description: 'Projection type' })
projectionType!: string | null;
@ApiProperty({ description: 'City' })
city!: string | null;
@ApiProperty({ description: 'State' })
state!: string | null;
@ApiProperty({ description: 'Country' })
country!: string | null;
@ApiProperty({ description: 'Make' })
make!: string | null;
@ApiProperty({ description: 'Model' })
model!: string | null;
@ApiProperty({ description: 'Lens model' })
lensModel!: string | null;
@ApiProperty({ type: 'number', format: 'double', description: 'F number' })
fNumber!: number | null;
@ApiProperty({ type: 'number', format: 'double', description: 'Focal length' })
focalLength!: number | null;
@ApiProperty({ type: 'integer', description: 'ISO' })
iso!: number | null;
@ApiProperty({ description: 'Exposure time' })
exposureTime!: string | null;
@ApiProperty({ description: 'Profile description' })
profileDescription!: string | null;
@ApiProperty({ type: 'integer', description: 'Rating' })
rating!: number | null;
@ApiProperty({ type: 'number', format: 'double', description: 'FPS' })
fps!: number | null;
}
@ExtraModel()
export class SyncAssetMetadataV1 {
@ApiProperty({ description: 'Asset ID' })
assetId!: string;
@ApiProperty({ description: 'Key' })
key!: string;
@ApiProperty({ description: 'Value' })
value!: object;
}
@ExtraModel()
export class SyncAssetMetadataDeleteV1 {
@ApiProperty({ description: 'Asset ID' })
assetId!: string;
@ApiProperty({ description: 'Key' })
key!: string;
}
@ExtraModel()
export class SyncAlbumDeleteV1 {
@ApiProperty({ description: 'Album ID' })
albumId!: string;
}
@ExtraModel()
export class SyncAlbumUserDeleteV1 {
@ApiProperty({ description: 'Album ID' })
albumId!: string;
@ApiProperty({ description: 'User ID' })
userId!: string;
}
@ExtraModel()
export class SyncAlbumUserV1 {
@ApiProperty({ description: 'Album ID' })
albumId!: string;
@ApiProperty({ description: 'User ID' })
userId!: string;
@ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole', description: 'Album user role' })
role!: AlbumUserRole;
}
@ExtraModel()
export class SyncAlbumV1 {
@ApiProperty({ description: 'Album ID' })
id!: string;
@ApiProperty({ description: 'Owner ID' })
ownerId!: string;
@ApiProperty({ description: 'Album name' })
name!: string;
@ApiProperty({ description: 'Album description' })
description!: string;
@ApiProperty({ description: 'Created at' })
createdAt!: Date;
@ApiProperty({ description: 'Updated at' })
updatedAt!: Date;
@ApiProperty({ description: 'Thumbnail asset ID' })
thumbnailAssetId!: string | null;
@ApiProperty({ description: 'Is activity enabled' })
isActivityEnabled!: boolean;
@ValidateEnum({ enum: AssetOrder, name: 'AssetOrder' })
order!: AssetOrder;
}
@ExtraModel()
export class SyncAlbumToAssetV1 {
@ApiProperty({ description: 'Album ID' })
albumId!: string;
@ApiProperty({ description: 'Asset ID' })
assetId!: string;
}
@ExtraModel()
export class SyncAlbumToAssetDeleteV1 {
@ApiProperty({ description: 'Album ID' })
albumId!: string;
@ApiProperty({ description: 'Asset ID' })
assetId!: string;
}
@ExtraModel()
export class SyncMemoryV1 {
@ApiProperty({ description: 'Memory ID' })
id!: string;
@ApiProperty({ description: 'Created at' })
createdAt!: Date;
@ApiProperty({ description: 'Updated at' })
updatedAt!: Date;
@ApiProperty({ description: 'Deleted at' })
deletedAt!: Date | null;
@ApiProperty({ description: 'Owner ID' })
ownerId!: string;
@ValidateEnum({ enum: MemoryType, name: 'MemoryType', description: 'Memory type' })
type!: MemoryType;
@ApiProperty({ description: 'Data' })
data!: object;
@ApiProperty({ description: 'Is saved' })
isSaved!: boolean;
@ApiProperty({ description: 'Memory at' })
memoryAt!: Date;
@ApiProperty({ description: 'Seen at' })
seenAt!: Date | null;
@ApiProperty({ description: 'Show at' })
showAt!: Date | null;
@ApiProperty({ description: 'Hide at' })
hideAt!: Date | null;
}
@ExtraModel()
export class SyncMemoryDeleteV1 {
@ApiProperty({ description: 'Memory ID' })
memoryId!: string;
}
@ExtraModel()
export class SyncMemoryAssetV1 {
@ApiProperty({ description: 'Memory ID' })
memoryId!: string;
@ApiProperty({ description: 'Asset ID' })
assetId!: string;
}
@ExtraModel()
export class SyncMemoryAssetDeleteV1 {
@ApiProperty({ description: 'Memory ID' })
memoryId!: string;
@ApiProperty({ description: 'Asset ID' })
assetId!: string;
}
@ExtraModel()
export class SyncStackV1 {
@ApiProperty({ description: 'Stack ID' })
id!: string;
@ApiProperty({ description: 'Created at' })
createdAt!: Date;
@ApiProperty({ description: 'Updated at' })
updatedAt!: Date;
@ApiProperty({ description: 'Primary asset ID' })
primaryAssetId!: string;
@ApiProperty({ description: 'Owner ID' })
ownerId!: string;
}
@ExtraModel()
export class SyncStackDeleteV1 {
@ApiProperty({ description: 'Stack ID' })
stackId!: string;
}
@ExtraModel()
export class SyncPersonV1 {
@ApiProperty({ description: 'Person ID' })
id!: string;
@ApiProperty({ description: 'Created at' })
createdAt!: Date;
@ApiProperty({ description: 'Updated at' })
updatedAt!: Date;
@ApiProperty({ description: 'Owner ID' })
ownerId!: string;
@ApiProperty({ description: 'Person name' })
name!: string;
@ApiProperty({ description: 'Birth date' })
birthDate!: Date | null;
@ApiProperty({ description: 'Is hidden' })
isHidden!: boolean;
@ApiProperty({ description: 'Is favorite' })
isFavorite!: boolean;
@ApiProperty({ description: 'Color' })
color!: string | null;
@ApiProperty({ description: 'Face asset ID' })
faceAssetId!: string | null;
}
@ExtraModel()
export class SyncPersonDeleteV1 {
@ApiProperty({ description: 'Person ID' })
personId!: string;
}
@ExtraModel()
export class SyncAssetFaceV1 {
@ApiProperty({ description: 'Asset face ID' })
id!: string;
@ApiProperty({ description: 'Asset ID' })
assetId!: string;
@ApiProperty({ description: 'Person ID' })
personId!: string | null;
@ApiProperty({ type: 'integer' })
imageWidth!: number;
@ApiProperty({ type: 'integer' })
imageHeight!: number;
@ApiProperty({ type: 'integer' })
boundingBoxX1!: number;
@ApiProperty({ type: 'integer' })
boundingBoxY1!: number;
@ApiProperty({ type: 'integer' })
boundingBoxX2!: number;
@ApiProperty({ type: 'integer' })
boundingBoxY2!: number;
@ApiProperty({ description: 'Source type' })
sourceType!: string;
}
@ExtraModel()
export class SyncAssetFaceDeleteV1 {
@ApiProperty({ description: 'Asset face ID' })
assetFaceId!: string;
}
@ExtraModel()
export class SyncUserMetadataV1 {
@ApiProperty({ description: 'User ID' })
userId!: string;
@ValidateEnum({ enum: UserMetadataKey, name: 'UserMetadataKey', description: 'User metadata key' })
key!: UserMetadataKey;
@ApiProperty({ description: 'User metadata value' })
value!: UserMetadata[UserMetadataKey];
}
@ExtraModel()
export class SyncUserMetadataDeleteV1 {
@ApiProperty({ description: 'User ID' })
userId!: string;
@ValidateEnum({ enum: UserMetadataKey, name: 'UserMetadataKey', description: 'User metadata key' })
key!: UserMetadataKey;
}
@ExtraModel()
export class SyncAckV1 {}
@ExtraModel()
export class SyncResetV1 {}
@ExtraModel()
export class SyncCompleteV1 {}
export type SyncItem = {
[SyncEntityType.AuthUserV1]: SyncAuthUserV1;
[SyncEntityType.UserV1]: SyncUserV1;
[SyncEntityType.UserDeleteV1]: SyncUserDeleteV1;
[SyncEntityType.PartnerV1]: SyncPartnerV1;
[SyncEntityType.PartnerDeleteV1]: SyncPartnerDeleteV1;
[SyncEntityType.AssetV1]: SyncAssetV1;
[SyncEntityType.AssetDeleteV1]: SyncAssetDeleteV1;
[SyncEntityType.AssetMetadataV1]: SyncAssetMetadataV1;
[SyncEntityType.AssetMetadataDeleteV1]: SyncAssetMetadataDeleteV1;
[SyncEntityType.AssetExifV1]: SyncAssetExifV1;
[SyncEntityType.PartnerAssetV1]: SyncAssetV1;
[SyncEntityType.PartnerAssetBackfillV1]: SyncAssetV1;
[SyncEntityType.PartnerAssetDeleteV1]: SyncAssetDeleteV1;
[SyncEntityType.PartnerAssetExifV1]: SyncAssetExifV1;
[SyncEntityType.PartnerAssetExifBackfillV1]: SyncAssetExifV1;
[SyncEntityType.AlbumV1]: SyncAlbumV1;
[SyncEntityType.AlbumDeleteV1]: SyncAlbumDeleteV1;
[SyncEntityType.AlbumUserV1]: SyncAlbumUserV1;
[SyncEntityType.AlbumUserBackfillV1]: SyncAlbumUserV1;
[SyncEntityType.AlbumUserDeleteV1]: SyncAlbumUserDeleteV1;
[SyncEntityType.AlbumAssetCreateV1]: SyncAssetV1;
[SyncEntityType.AlbumAssetUpdateV1]: SyncAssetV1;
[SyncEntityType.AlbumAssetBackfillV1]: SyncAssetV1;
[SyncEntityType.AlbumAssetExifCreateV1]: SyncAssetExifV1;
[SyncEntityType.AlbumAssetExifUpdateV1]: SyncAssetExifV1;
[SyncEntityType.AlbumAssetExifBackfillV1]: SyncAssetExifV1;
[SyncEntityType.AlbumToAssetV1]: SyncAlbumToAssetV1;
[SyncEntityType.AlbumToAssetBackfillV1]: SyncAlbumToAssetV1;
[SyncEntityType.AlbumToAssetDeleteV1]: SyncAlbumToAssetDeleteV1;
[SyncEntityType.MemoryV1]: SyncMemoryV1;
[SyncEntityType.MemoryDeleteV1]: SyncMemoryDeleteV1;
[SyncEntityType.MemoryToAssetV1]: SyncMemoryAssetV1;
[SyncEntityType.MemoryToAssetDeleteV1]: SyncMemoryAssetDeleteV1;
[SyncEntityType.StackV1]: SyncStackV1;
[SyncEntityType.StackDeleteV1]: SyncStackDeleteV1;
[SyncEntityType.PartnerStackBackfillV1]: SyncStackV1;
[SyncEntityType.PartnerStackDeleteV1]: SyncStackDeleteV1;
[SyncEntityType.PartnerStackV1]: SyncStackV1;
[SyncEntityType.PersonV1]: SyncPersonV1;
[SyncEntityType.PersonDeleteV1]: SyncPersonDeleteV1;
[SyncEntityType.AssetFaceV1]: SyncAssetFaceV1;
[SyncEntityType.AssetFaceDeleteV1]: SyncAssetFaceDeleteV1;
[SyncEntityType.UserMetadataV1]: SyncUserMetadataV1;
[SyncEntityType.UserMetadataDeleteV1]: SyncUserMetadataDeleteV1;
[SyncEntityType.SyncAckV1]: SyncAckV1;
[SyncEntityType.SyncCompleteV1]: SyncCompleteV1;
[SyncEntityType.SyncResetV1]: SyncResetV1;
};
export class SyncStreamDto {
@ValidateEnum({ enum: SyncRequestType, name: 'SyncRequestType', each: true, description: 'Sync request types' })
types!: SyncRequestType[];
@ValidateBoolean({ optional: true, description: 'Reset sync state' })
reset?: boolean;
}
export class SyncAckDto {
@ValidateEnum({ enum: SyncEntityType, name: 'SyncEntityType', description: 'Sync entity type' })
type!: SyncEntityType;
@ApiProperty({ description: 'Acknowledgment ID' })
ack!: string;
}
export class SyncAckSetDto {
@ApiProperty({ description: 'Acknowledgment IDs (max 1000)' })
@ArrayMaxSize(1000)
@IsString({ each: true })
acks!: string[];
}
export class SyncAckDeleteDto {
@ValidateEnum({
enum: SyncEntityType,
name: 'SyncEntityType',
optional: true,
each: true,
description: 'Sync entity types to delete acks for',
})
types?: SyncEntityType[];
}