From 34b88bb47a4849423bc6935bb3a52a43ba9a1398 Mon Sep 17 00:00:00 2001 From: Jason Antwi-Appah Date: Thu, 20 Feb 2025 10:17:06 -0600 Subject: [PATCH] feat(web): support searching by EXIF rating (#16208) * Add rating to search DTO * Add search by EXIF rating in search query builder * Generate OpenAPI spec * Add rating filter on web * Add rating filter to search docs * Format / lint * Hide rating filter if ratings are disabled * chore: component order in form --------- Co-authored-by: Alex Tran --- docs/docs/features/searching.md | 1 + i18n/en.json | 1 + .../lib/model/metadata_search_dto.dart | Bin 29274 -> 29963 bytes .../openapi/lib/model/random_search_dto.dart | Bin 21824 -> 22513 bytes .../openapi/lib/model/smart_search_dto.dart | Bin 21229 -> 21918 bytes open-api/immich-openapi-specs.json | 15 +++++++++ open-api/typescript-sdk/src/fetch-client.ts | 3 ++ server/src/dtos/search.dto.ts | 6 ++++ server/src/entities/asset.entity.ts | 5 +++ server/src/repositories/search.repository.ts | 1 + .../search-bar/search-filter-modal.svelte | 11 +++++++ .../search-bar/search-ratings-section.svelte | 31 ++++++++++++++++++ 12 files changed, 74 insertions(+) create mode 100644 web/src/lib/components/shared-components/search-bar/search-ratings-section.svelte diff --git a/docs/docs/features/searching.md b/docs/docs/features/searching.md index 13547f6ba..eed5faa6f 100644 --- a/docs/docs/features/searching.md +++ b/docs/docs/features/searching.md @@ -31,6 +31,7 @@ The filters smart search allows you to search by include: - Not in any album - Archived - Favorited + - Rating diff --git a/i18n/en.json b/i18n/en.json index 72559d450..b6f75ce4f 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1134,6 +1134,7 @@ "search_timezone": "Search timezone...", "search_type": "Search type", "search_your_photos": "Search your photos", + "search_rating": "Search by rating...", "searching_locales": "Searching locales...", "second": "Second", "see_all_people": "See all people", diff --git a/mobile/openapi/lib/model/metadata_search_dto.dart b/mobile/openapi/lib/model/metadata_search_dto.dart index 3a3c14144209cf4a059eb32556d38e9d2089cca0..3fb003d16434e45154462ffdd16df51d0f9c7386 100644 GIT binary patch delta 240 zcmcchgt7Y-nr#sR%GUu<|>pW=9H#dDVT1) zD4)s*Q+i5ihqwSln}V&a0@Q#KD_#z1glJxAPL6`Tf`Nh+RKetrj>4N8omR8*K@{1l zKs9Q4nDax8p8U{PQVb%j9;;xhP?C{ZtOwywb_|w-sk#^-w>dHVs(>m)%1R-xG*_=6 bv8XszLtUjht2jR|x=0;nOzmc^3H*s10kFpdv)BV0AG41npck`EJpTu?dPJ)Qvo%UJ53}J})ey7iY9tEB CdlPa1 diff --git a/mobile/openapi/lib/model/random_search_dto.dart b/mobile/openapi/lib/model/random_search_dto.dart index c63d7e82f611df5be5ef919d26d7a03e313305dc..10727ec10d015b19ed64621a062e1a4cc3e441e7 100644 GIT binary patch delta 247 zcmX@Git*!m#trwG*oqQMGV{_mUtu~YD5-16rJ$g%udm>nSdp1qnyXNjm{Xc+rC_@G zqfi1PcV20(y#iEym&kDu0SH^c))pq}ta*=B8X=ljnv_1Ic<&CR*qmpK8r(pZ=P delta 44 zcmV+{0Mq~RuK~cS0kGc!v+x4L5wlMguN1Q!E6@eA8Zm(cv#T{J3bRWH%wQpy5{ETo?)B--FirC delta 44 zcmV+{0Mq}Ts{!q$0k8xEvlRm?6SLeG{}HqKDZ>S`>Mtw=vnDi&3bQ6Z`wX*EOiT%s C^AS=2 diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 5b5c3a150..14245e11b 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -9956,6 +9956,11 @@ "previewPath": { "type": "string" }, + "rating": { + "maximum": 5, + "minimum": -1, + "type": "number" + }, "size": { "maximum": 1000, "minimum": 1, @@ -10613,6 +10618,11 @@ }, "type": "array" }, + "rating": { + "maximum": 5, + "minimum": -1, + "type": "number" + }, "size": { "maximum": 1000, "minimum": 1, @@ -11563,6 +11573,11 @@ "query": { "type": "string" }, + "rating": { + "maximum": 5, + "minimum": -1, + "type": "number" + }, "size": { "maximum": 1000, "minimum": 1, diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index d4b36a04f..9ff35331f 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -811,6 +811,7 @@ export type MetadataSearchDto = { page?: number; personIds?: string[]; previewPath?: string; + rating?: number; size?: number; state?: string | null; tagIds?: string[]; @@ -878,6 +879,7 @@ export type RandomSearchDto = { make?: string; model?: string | null; personIds?: string[]; + rating?: number; size?: number; state?: string | null; tagIds?: string[]; @@ -914,6 +916,7 @@ export type SmartSearchDto = { page?: number; personIds?: string[]; query: string; + rating?: number; size?: number; state?: string | null; tagIds?: string[]; diff --git a/server/src/dtos/search.dto.ts b/server/src/dtos/search.dto.ts index 6cf34debe..3589331c7 100644 --- a/server/src/dtos/search.dto.ts +++ b/server/src/dtos/search.dto.ts @@ -114,6 +114,12 @@ class BaseSearchDto { @ValidateUUID({ each: true, optional: true }) tagIds?: string[]; + + @Optional() + @IsInt() + @Max(5) + @Min(-1) + rating?: number; } export class RandomSearchDto extends BaseSearchDto { diff --git a/server/src/entities/asset.entity.ts b/server/src/entities/asset.entity.ts index 8ff4130ed..fd69673eb 100644 --- a/server/src/entities/asset.entity.ts +++ b/server/src/entities/asset.entity.ts @@ -387,6 +387,11 @@ export function searchAssetBuilder(kysely: Kysely, options: AssetSearchBuild .innerJoin('exif', 'assets.id', 'exif.assetId') .where('exif.lensModel', options.lensModel === null ? 'is' : '=', options.lensModel!), ) + .$if(options.rating !== undefined, (qb) => + qb + .innerJoin('exif', 'assets.id', 'exif.assetId') + .where('exif.rating', options.rating === null ? 'is' : '=', options.rating!), + ) .$if(!!options.checksum, (qb) => qb.where('assets.checksum', '=', options.checksum!)) .$if(!!options.deviceAssetId, (qb) => qb.where('assets.deviceAssetId', '=', options.deviceAssetId!)) .$if(!!options.deviceId, (qb) => qb.where('assets.deviceId', '=', options.deviceId!)) diff --git a/server/src/repositories/search.repository.ts b/server/src/repositories/search.repository.ts index a6eb5c7a8..2f313aa08 100644 --- a/server/src/repositories/search.repository.ts +++ b/server/src/repositories/search.repository.ts @@ -109,6 +109,7 @@ export interface SearchExifOptions { model?: string | null; state?: string | null; description?: string | null; + rating?: number | null; } export interface SearchEmbeddingOptions { diff --git a/web/src/lib/components/shared-components/search-bar/search-filter-modal.svelte b/web/src/lib/components/shared-components/search-bar/search-filter-modal.svelte index 817001033..4fc646b20 100644 --- a/web/src/lib/components/shared-components/search-bar/search-filter-modal.svelte +++ b/web/src/lib/components/shared-components/search-bar/search-filter-modal.svelte @@ -14,6 +14,7 @@ date: SearchDateFilter; display: SearchDisplayFilters; mediaType: MediaType; + rating?: number; }; @@ -26,6 +27,7 @@ import SearchCameraSection, { type SearchCameraFilter } from './search-camera-section.svelte'; import SearchDateSection from './search-date-section.svelte'; import SearchMediaSection from './search-media-section.svelte'; + import SearchRatingsSection from './search-ratings-section.svelte'; import { parseUtcDate } from '$lib/utils/date-time'; import SearchDisplaySection from './search-display-section.svelte'; import SearchTextSection from './search-text-section.svelte'; @@ -34,6 +36,7 @@ import { mdiTune } from '@mdi/js'; import { generateId } from '$lib/utils/generate-id'; import { SvelteSet } from 'svelte/reactivity'; + import { preferences } from '$lib/stores/user.store'; interface Props { searchQuery: MetadataSearchDto | SmartSearchDto; @@ -81,6 +84,7 @@ : searchQuery.type === AssetTypeEnum.Video ? MediaType.Video : MediaType.All, + rating: searchQuery.rating, }); const resetForm = () => { @@ -94,6 +98,7 @@ date: {}, display: {}, mediaType: MediaType.All, + rating: undefined, }; }; @@ -124,6 +129,7 @@ personIds: filter.personIds.size > 0 ? [...filter.personIds] : undefined, tagIds: filter.tagIds.size > 0 ? [...filter.tagIds] : undefined, type, + rating: filter.rating, }; onSearch(payload); @@ -161,6 +167,11 @@ + + {#if $preferences?.ratings.enabled} + + {/if} +
diff --git a/web/src/lib/components/shared-components/search-bar/search-ratings-section.svelte b/web/src/lib/components/shared-components/search-bar/search-ratings-section.svelte new file mode 100644 index 000000000..00e622380 --- /dev/null +++ b/web/src/lib/components/shared-components/search-bar/search-ratings-section.svelte @@ -0,0 +1,31 @@ + + +
+ +