diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index 5691965bc..93b758a59 100644 Binary files a/mobile/openapi/doc/AssetApi.md and b/mobile/openapi/doc/AssetApi.md differ diff --git a/mobile/openapi/doc/MetadataSearchDto.md b/mobile/openapi/doc/MetadataSearchDto.md index bfbf81749..d1d098fb0 100644 Binary files a/mobile/openapi/doc/MetadataSearchDto.md and b/mobile/openapi/doc/MetadataSearchDto.md differ diff --git a/mobile/openapi/doc/SmartSearchDto.md b/mobile/openapi/doc/SmartSearchDto.md index fd9bc3549..d4ec1a70f 100644 Binary files a/mobile/openapi/doc/SmartSearchDto.md and b/mobile/openapi/doc/SmartSearchDto.md differ diff --git a/mobile/openapi/lib/model/metadata_search_dto.dart b/mobile/openapi/lib/model/metadata_search_dto.dart index 47756cd52..86a2856e6 100644 Binary files a/mobile/openapi/lib/model/metadata_search_dto.dart and b/mobile/openapi/lib/model/metadata_search_dto.dart differ diff --git a/mobile/openapi/lib/model/smart_search_dto.dart b/mobile/openapi/lib/model/smart_search_dto.dart index 269a07102..664850db8 100644 Binary files a/mobile/openapi/lib/model/smart_search_dto.dart and b/mobile/openapi/lib/model/smart_search_dto.dart differ diff --git a/mobile/openapi/test/metadata_search_dto_test.dart b/mobile/openapi/test/metadata_search_dto_test.dart index f1635de4e..f817b7da7 100644 Binary files a/mobile/openapi/test/metadata_search_dto_test.dart and b/mobile/openapi/test/metadata_search_dto_test.dart differ diff --git a/mobile/openapi/test/smart_search_dto_test.dart b/mobile/openapi/test/smart_search_dto_test.dart index 84a85cf20..4db3ac080 100644 Binary files a/mobile/openapi/test/smart_search_dto_test.dart and b/mobile/openapi/test/smart_search_dto_test.dart differ diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 790fe8e8e..d32d5b820 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -2463,6 +2463,7 @@ "required": false, "in": "query", "schema": { + "default": false, "type": "boolean" } }, @@ -8429,6 +8430,7 @@ "type": "string" }, "withArchived": { + "default": false, "type": "boolean" }, "withDeleted": { @@ -9500,6 +9502,7 @@ "type": "string" }, "withArchived": { + "default": false, "type": "boolean" }, "withDeleted": { diff --git a/server/e2e/api/specs/asset.e2e-spec.ts b/server/e2e/api/specs/asset.e2e-spec.ts index 5993a7040..778988120 100644 --- a/server/e2e/api/specs/asset.e2e-spec.ts +++ b/server/e2e/api/specs/asset.e2e-spec.ts @@ -50,6 +50,7 @@ describe(`${AssetController.name} (e2e)`, () => { let asset3: AssetResponseDto; let asset4: AssetResponseDto; let asset5: AssetResponseDto; + let asset6: AssetResponseDto; const createAsset = async ( loginResponse: LoginResponseDto, @@ -96,12 +97,11 @@ describe(`${AssetController.name} (e2e)`, () => { beforeEach(async () => { await testApp.reset({ entities: [AssetEntity, AssetStackEntity] }); - [asset1, asset2, asset3, asset4, asset5] = await Promise.all([ + [asset1, asset2, asset3, asset4, asset5, asset6] = await Promise.all([ createAsset(user1, new Date('1970-01-01')), createAsset(user1, new Date('1970-02-10')), createAsset(user1, new Date('1970-02-11'), { isFavorite: true, - isArchived: true, isExternal: true, isReadOnly: true, type: AssetType.VIDEO, @@ -118,6 +118,9 @@ describe(`${AssetController.name} (e2e)`, () => { createAsset(user1, new Date('1970-01-01'), { deletedAt: yesterday.toJSDate(), }), + createAsset(user1, new Date('1970-02-11'), { + isArchived: true, + }), ]); await assetRepository.upsertExif({ @@ -275,14 +278,14 @@ describe(`${AssetController.name} (e2e)`, () => { should: 'should search by isArchived (true)', deferred: () => ({ query: { isArchived: true }, - assets: [asset3], + assets: [asset6], }), }, { should: 'should search by isArchived (false)', deferred: () => ({ query: { isArchived: false }, - assets: [asset2, asset1], + assets: [asset3, asset2, asset1], }), }, { @@ -313,6 +316,20 @@ describe(`${AssetController.name} (e2e)`, () => { assets: [asset3], }), }, + { + should: 'should search by withArchived (true)', + deferred: () => ({ + query: { withArchived: true }, + assets: [asset3, asset6, asset2, asset1], + }), + }, + { + should: 'should search by withArchived (false)', + deferred: () => ({ + query: { withArchived: false }, + assets: [asset3, asset2, asset1], + }), + }, { should: 'should search by createdBefore', deferred: () => ({ @@ -902,7 +919,7 @@ describe(`${AssetController.name} (e2e)`, () => { .get('/asset/statistics') .set('Authorization', `Bearer ${user1.accessToken}`); - expect(body).toEqual({ images: 5, videos: 1, total: 6 }); + expect(body).toEqual({ images: 6, videos: 1, total: 7 }); expect(status).toBe(200); }); @@ -923,7 +940,7 @@ describe(`${AssetController.name} (e2e)`, () => { .query({ isArchived: true }); expect(status).toBe(200); - expect(body).toEqual({ images: 2, videos: 1, total: 3 }); + expect(body).toEqual({ images: 3, videos: 0, total: 3 }); }); it('should return stats of all favored and archived assets', async () => { @@ -933,7 +950,7 @@ describe(`${AssetController.name} (e2e)`, () => { .query({ isFavorite: true, isArchived: true }); expect(status).toBe(200); - expect(body).toEqual({ images: 1, videos: 1, total: 2 }); + expect(body).toEqual({ images: 1, videos: 0, total: 1 }); }); it('should return stats of all assets neither favored nor archived', async () => { @@ -1041,7 +1058,7 @@ describe(`${AssetController.name} (e2e)`, () => { expect.arrayContaining([ { count: 1, timeBucket: '2023-11-01T00:00:00.000Z' }, { count: 1, timeBucket: '1970-01-01T00:00:00.000Z' }, - { count: 1, timeBucket: '1970-02-01T00:00:00.000Z' }, + { count: 2, timeBucket: '1970-02-01T00:00:00.000Z' }, ]), ); }); @@ -1198,8 +1215,13 @@ describe(`${AssetController.name} (e2e)`, () => { .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); - expect(body).toHaveLength(1); - expect(body).toEqual(expect.arrayContaining([expect.objectContaining({ id: asset2.id })])); + expect(body).toHaveLength(2); + expect(body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: asset2.id }), + expect.objectContaining({ id: asset3.id }), + ]), + ); }); it('should get all map markers', async () => { @@ -1209,8 +1231,13 @@ describe(`${AssetController.name} (e2e)`, () => { .query({ isArchived: false }); expect(status).toBe(200); - expect(body).toHaveLength(1); - expect(body).toEqual([expect.objectContaining({ id: asset2.id })]); + expect(body).toHaveLength(2); + expect(body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: asset2.id }), + expect.objectContaining({ id: asset3.id }), + ]), + ); }); }); diff --git a/server/src/domain/search/dto/search.dto.ts b/server/src/domain/search/dto/search.dto.ts index 519e39fd2..4f2aa1819 100644 --- a/server/src/domain/search/dto/search.dto.ts +++ b/server/src/domain/search/dto/search.dto.ts @@ -23,6 +23,7 @@ class BaseSearchDto { isArchived?: boolean; @QueryBoolean({ optional: true }) + @ApiProperty({ default: false }) withArchived?: boolean; @QueryBoolean({ optional: true }) diff --git a/server/src/infra/infra.utils.ts b/server/src/infra/infra.utils.ts index ab7d74431..1538958e0 100644 --- a/server/src/infra/infra.utils.ts +++ b/server/src/infra/infra.utils.ts @@ -183,7 +183,7 @@ export function searchAssetBuilder( _.omitBy( { ...status, - isArchived: isArchived ?? withArchived, + isArchived: isArchived ?? (withArchived ? undefined : false), encodedVideoPath: isEncoded ? Not(IsNull()) : undefined, livePhotoVideoId: isMotion ? Not(IsNull()) : undefined, }, diff --git a/server/src/infra/sql/asset.repository.sql b/server/src/infra/sql/asset.repository.sql index d971129e7..e5cf6771f 100644 --- a/server/src/infra/sql/asset.repository.sql +++ b/server/src/infra/sql/asset.repository.sql @@ -434,7 +434,7 @@ WHERE AND 1 = 1 AND "asset"."ownerId" IN ($2) AND 1 = 1 - AND 1 = 1 + AND "asset"."isArchived" = $3 ) AND ("asset"."deletedAt" IS NULL) ORDER BY diff --git a/server/src/infra/sql/search.repository.sql b/server/src/infra/sql/search.repository.sql index ebae46f65..a21697c26 100644 --- a/server/src/infra/sql/search.repository.sql +++ b/server/src/infra/sql/search.repository.sql @@ -79,7 +79,10 @@ FROM AND "exifInfo"."lensModel" = $2 AND 1 = 1 AND 1 = 1 - AND "asset"."isFavorite" = $3 + AND ( + "asset"."isFavorite" = $3 + AND "asset"."isArchived" = $4 + ) AND ( "stack"."primaryAssetId" = "asset"."id" OR "asset"."stackId" IS NULL @@ -177,16 +180,19 @@ WHERE AND "exifInfo"."lensModel" = $2 AND 1 = 1 AND 1 = 1 - AND "asset"."isFavorite" = $3 + AND ( + "asset"."isFavorite" = $3 + AND "asset"."isArchived" = $4 + ) AND ( "stack"."primaryAssetId" = "asset"."id" OR "asset"."stackId" IS NULL ) - AND "asset"."ownerId" IN ($4) + AND "asset"."ownerId" IN ($5) ) AND ("asset"."deletedAt" IS NULL) ORDER BY - "search"."embedding" <= > $5 ASC + "search"."embedding" <= > $6 ASC LIMIT 101 COMMIT