mirror-immich/server/src/dtos/search.dto.ts

349 lines
7.1 KiB
TypeScript

import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator';
import { Place } from 'src/database';
import { PropertyLifecycle } from 'src/decorators';
import { AlbumResponseDto } from 'src/dtos/album.dto';
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
import { AssetOrder, AssetType, AssetVisibility } from 'src/enum';
import { Optional, ValidateBoolean, ValidateDate, ValidateEnum, ValidateString, ValidateUUID } from 'src/validation';
class BaseSearchDto {
@ValidateUUID({ optional: true, nullable: true })
libraryId?: string | null;
@IsString()
@IsNotEmpty()
@Optional()
deviceId?: string;
@ValidateEnum({ enum: AssetType, name: 'AssetTypeEnum', optional: true })
type?: AssetType;
@ValidateBoolean({ optional: true })
isEncoded?: boolean;
@ValidateBoolean({ optional: true })
isFavorite?: boolean;
@ValidateBoolean({ optional: true })
isMotion?: boolean;
@ValidateBoolean({ optional: true })
isOffline?: boolean;
@ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility', optional: true })
visibility?: AssetVisibility;
@ValidateDate({ optional: true })
createdBefore?: Date;
@ValidateDate({ optional: true })
createdAfter?: Date;
@ValidateDate({ optional: true })
updatedBefore?: Date;
@ValidateDate({ optional: true })
updatedAfter?: Date;
@ValidateDate({ optional: true })
trashedBefore?: Date;
@ValidateDate({ optional: true })
trashedAfter?: Date;
@ValidateDate({ optional: true })
takenBefore?: Date;
@ValidateDate({ optional: true })
takenAfter?: Date;
@IsString()
@Optional({ nullable: true, emptyToNull: true })
city?: string | null;
@IsString()
@Optional({ nullable: true, emptyToNull: true })
state?: string | null;
@IsString()
@IsNotEmpty()
@Optional({ nullable: true, emptyToNull: true })
country?: string | null;
@IsString()
@Optional({ nullable: true, emptyToNull: true })
make?: string;
@IsString()
@Optional({ nullable: true, emptyToNull: true })
model?: string | null;
@IsString()
@Optional({ nullable: true, emptyToNull: true })
lensModel?: string | null;
@ValidateBoolean({ optional: true })
isNotInAlbum?: boolean;
@ValidateUUID({ each: true, optional: true })
personIds?: string[];
@ValidateUUID({ each: true, optional: true, nullable: true })
tagIds?: string[] | null;
@ValidateUUID({ each: true, optional: true })
albumIds?: string[];
@Optional()
@IsInt()
@Max(5)
@Min(-1)
rating?: number;
}
class BaseSearchWithResultsDto extends BaseSearchDto {
@ValidateBoolean({ optional: true })
withDeleted?: boolean;
@ValidateBoolean({ optional: true })
withExif?: boolean;
@IsInt()
@Min(1)
@Max(1000)
@Type(() => Number)
@Optional()
size?: number;
}
export class RandomSearchDto extends BaseSearchWithResultsDto {
@ValidateBoolean({ optional: true })
withStacked?: boolean;
@ValidateBoolean({ optional: true })
withPeople?: boolean;
}
export class LargeAssetSearchDto extends BaseSearchWithResultsDto {
@Optional()
@IsInt()
@Min(0)
@Type(() => Number)
@ApiProperty({ type: 'integer' })
minFileSize?: number;
}
export class MetadataSearchDto extends RandomSearchDto {
@ValidateUUID({ optional: true })
id?: string;
@IsString()
@IsNotEmpty()
@Optional()
deviceAssetId?: string;
@ValidateString({ optional: true, trim: true })
description?: string;
@IsString()
@IsNotEmpty()
@Optional()
checksum?: string;
@ValidateString({ optional: true, trim: true })
originalFileName?: string;
@IsString()
@IsNotEmpty()
@Optional()
originalPath?: string;
@IsString()
@IsNotEmpty()
@Optional()
previewPath?: string;
@IsString()
@IsNotEmpty()
@Optional()
thumbnailPath?: string;
@IsString()
@IsNotEmpty()
@Optional()
encodedVideoPath?: string;
@ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', optional: true, default: AssetOrder.Desc })
order?: AssetOrder;
@IsInt()
@Min(1)
@Type(() => Number)
@Optional()
page?: number;
}
export class StatisticsSearchDto extends BaseSearchDto {
@ValidateString({ optional: true, trim: true })
description?: string;
}
export class SmartSearchDto extends BaseSearchWithResultsDto {
@ValidateString({ optional: true, trim: true })
query?: string;
@ValidateUUID({ optional: true })
@Optional()
queryAssetId?: string;
@IsString()
@IsNotEmpty()
@Optional()
language?: string;
@IsInt()
@Min(1)
@Type(() => Number)
@Optional()
page?: number;
}
export class SearchPlacesDto {
@IsString()
@IsNotEmpty()
name!: string;
}
export class SearchPeopleDto {
@IsString()
@IsNotEmpty()
name!: string;
@ValidateBoolean({ optional: true })
withHidden?: boolean;
}
export class PlacesResponseDto {
name!: string;
latitude!: number;
longitude!: number;
admin1name?: string;
admin2name?: string;
}
export function mapPlaces(place: Place): PlacesResponseDto {
return {
name: place.name,
latitude: place.latitude,
longitude: place.longitude,
admin1name: place.admin1Name ?? undefined,
admin2name: place.admin2Name ?? undefined,
};
}
export enum SearchSuggestionType {
COUNTRY = 'country',
STATE = 'state',
CITY = 'city',
CAMERA_MAKE = 'camera-make',
CAMERA_MODEL = 'camera-model',
CAMERA_LENS_MODEL = 'camera-lens-model',
}
export class SearchSuggestionRequestDto {
@ValidateEnum({ enum: SearchSuggestionType, name: 'SearchSuggestionType' })
type!: SearchSuggestionType;
@IsString()
@Optional()
country?: string;
@IsString()
@Optional()
state?: string;
@IsString()
@Optional()
make?: string;
@IsString()
@Optional()
model?: string;
@IsString()
@Optional()
lensModel?: string;
@ValidateBoolean({ optional: true })
@PropertyLifecycle({ addedAt: 'v111.0.0' })
includeNull?: boolean;
}
class SearchFacetCountResponseDto {
@ApiProperty({ type: 'integer' })
count!: number;
value!: string;
}
class SearchFacetResponseDto {
fieldName!: string;
counts!: SearchFacetCountResponseDto[];
}
class SearchAlbumResponseDto {
@ApiProperty({ type: 'integer' })
total!: number;
@ApiProperty({ type: 'integer' })
count!: number;
items!: AlbumResponseDto[];
facets!: SearchFacetResponseDto[];
}
class SearchAssetResponseDto {
@ApiProperty({ type: 'integer' })
total!: number;
@ApiProperty({ type: 'integer' })
count!: number;
items!: AssetResponseDto[];
facets!: SearchFacetResponseDto[];
nextPage!: string | null;
}
export class SearchResponseDto {
albums!: SearchAlbumResponseDto;
assets!: SearchAssetResponseDto;
}
export class SearchStatisticsResponseDto {
@ApiProperty({ type: 'integer' })
total!: number;
}
class SearchExploreItem {
value!: string;
data!: AssetResponseDto;
}
export class SearchExploreResponseDto {
fieldName!: string;
items!: SearchExploreItem[];
}
export class MemoryLaneDto {
@IsInt()
@Type(() => Number)
@Max(31)
@Min(1)
@ApiProperty({ type: 'integer' })
day!: number;
@IsInt()
@Type(() => Number)
@Max(12)
@Min(1)
@ApiProperty({ type: 'integer' })
month!: number;
}