diff --git a/e2e/src/api/specs/search.e2e-spec.ts b/e2e/src/api/specs/search.e2e-spec.ts index f46c07e0b878c..b49dac642e700 100644 --- a/e2e/src/api/specs/search.e2e-spec.ts +++ b/e2e/src/api/specs/search.e2e-spec.ts @@ -339,6 +339,13 @@ describe('/search', () => { should: 'should search by model', deferred: () => ({ dto: { model: 'Canon EOS 7D' }, assets: [assetDenali] }), }, + { + should: 'should allow searching the upload library (libraryId: null)', + deferred: () => ({ + dto: { libraryId: null, size: 1 }, + assets: [assetLast], + }), + }, ]; for (const { should, deferred } of searchTests) { diff --git a/mobile/openapi/lib/model/metadata_search_dto.dart b/mobile/openapi/lib/model/metadata_search_dto.dart index 322373ee5815d..d77f2e7736c69 100644 --- a/mobile/openapi/lib/model/metadata_search_dto.dart +++ b/mobile/openapi/lib/model/metadata_search_dto.dart @@ -192,12 +192,6 @@ class MetadataSearchDto { /// String? lensModel; - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// String? libraryId; /// diff --git a/mobile/openapi/lib/model/smart_search_dto.dart b/mobile/openapi/lib/model/smart_search_dto.dart index 0ff8cf6115b5d..25927f42445c0 100644 --- a/mobile/openapi/lib/model/smart_search_dto.dart +++ b/mobile/openapi/lib/model/smart_search_dto.dart @@ -150,12 +150,6 @@ class SmartSearchDto { /// String? lensModel; - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// String? libraryId; /// diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index e884b4fc29812..17f74d33b0f69 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -9026,6 +9026,7 @@ }, "libraryId": { "format": "uuid", + "nullable": true, "type": "string" }, "make": { @@ -10140,6 +10141,7 @@ }, "libraryId": { "format": "uuid", + "nullable": true, "type": "string" }, "make": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 43e24e939beae..143ec74e65eb7 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -697,7 +697,7 @@ export type MetadataSearchDto = { isOffline?: boolean; isVisible?: boolean; lensModel?: string; - libraryId?: string; + libraryId?: string | null; make?: string; model?: string; order?: AssetOrder; @@ -768,7 +768,7 @@ export type SmartSearchDto = { isOffline?: boolean; isVisible?: boolean; lensModel?: string; - libraryId?: string; + libraryId?: string | null; make?: string; model?: string; page?: number; diff --git a/server/src/dtos/search.dto.ts b/server/src/dtos/search.dto.ts index 5927aa86fcdaf..59bb95b47595e 100644 --- a/server/src/dtos/search.dto.ts +++ b/server/src/dtos/search.dto.ts @@ -9,8 +9,8 @@ import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity'; import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; class BaseSearchDto { - @ValidateUUID({ optional: true }) - libraryId?: string; + @ValidateUUID({ optional: true, nullable: true }) + libraryId?: string | null; @IsString() @IsNotEmpty() diff --git a/server/src/interfaces/search.interface.ts b/server/src/interfaces/search.interface.ts index d5382a04fa148..c84b56c62eb53 100644 --- a/server/src/interfaces/search.interface.ts +++ b/server/src/interfaces/search.interface.ts @@ -45,7 +45,7 @@ export interface SearchAssetIDOptions { export interface SearchUserIdOptions { deviceId?: string; - libraryId?: string; + libraryId?: string | null; userIds?: string[]; } diff --git a/server/src/utils/database.ts b/server/src/utils/database.ts index 65456e8192043..944978bddde81 100644 --- a/server/src/utils/database.ts +++ b/server/src/utils/database.ts @@ -52,6 +52,11 @@ export function searchAssetBuilder( } const id = _.pick(options, ['checksum', 'deviceAssetId', 'deviceId', 'id', 'libraryId']); + + if (id.libraryId === null) { + id.libraryId = IsNull() as unknown as string; + } + builder.andWhere(_.omitBy(id, _.isUndefined)); if (options.userIds) { diff --git a/server/src/validation.ts b/server/src/validation.ts index 6fb1684c06c5a..4a8d8db8667e4 100644 --- a/server/src/validation.ts +++ b/server/src/validation.ts @@ -80,13 +80,13 @@ export function Optional({ nullable, ...validationOptions }: OptionalOptions = { return ValidateIf((object: any, v: any) => v !== undefined, validationOptions); } -type UUIDOptions = { optional?: boolean; each?: boolean }; +type UUIDOptions = { optional?: boolean; each?: boolean; nullable?: boolean }; export const ValidateUUID = (options?: UUIDOptions) => { - const { optional, each } = { optional: false, each: false, ...options }; + const { optional, each, nullable } = { optional: false, each: false, nullable: false, ...options }; return applyDecorators( IsUUID('4', { each }), ApiProperty({ format: 'uuid' }), - optional ? Optional() : IsNotEmpty(), + optional ? Optional({ nullable }) : IsNotEmpty(), each ? IsArray() : IsString(), ); };