Skip to content

Commit

Permalink
feat: add searching by tags
Browse files Browse the repository at this point in the history
  • Loading branch information
dav-wolff committed Jan 29, 2025
1 parent cc0cbd7 commit e723d6f
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 5 deletions.
11 changes: 10 additions & 1 deletion mobile/openapi/lib/model/metadata_search_dto.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion mobile/openapi/lib/model/random_search_dto.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion mobile/openapi/lib/model/smart_search_dto.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions open-api/immich-openapi-specs.json
Original file line number Diff line number Diff line change
Expand Up @@ -10032,6 +10032,13 @@
"nullable": true,
"type": "string"
},
"tagIds": {
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
},
"takenAfter": {
"format": "date-time",
"type": "string"
Expand Down Expand Up @@ -10645,6 +10652,13 @@
"nullable": true,
"type": "string"
},
"tagIds": {
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
},
"takenAfter": {
"format": "date-time",
"type": "string"
Expand Down Expand Up @@ -11560,6 +11574,13 @@
"nullable": true,
"type": "string"
},
"tagIds": {
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
},
"takenAfter": {
"format": "date-time",
"type": "string"
Expand Down
3 changes: 3 additions & 0 deletions open-api/typescript-sdk/src/fetch-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ export type MetadataSearchDto = {
previewPath?: string;
size?: number;
state?: string | null;
tagIds?: string[];
takenAfter?: string;
takenBefore?: string;
thumbnailPath?: string;
Expand Down Expand Up @@ -858,6 +859,7 @@ export type RandomSearchDto = {
personIds?: string[];
size?: number;
state?: string | null;
tagIds?: string[];
takenAfter?: string;
takenBefore?: string;
trashedAfter?: string;
Expand Down Expand Up @@ -893,6 +895,7 @@ export type SmartSearchDto = {
query: string;
size?: number;
state?: string | null;
tagIds?: string[];
takenAfter?: string;
takenBefore?: string;
trashedAfter?: string;
Expand Down
3 changes: 3 additions & 0 deletions server/src/dtos/search.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ class BaseSearchDto {

@ValidateUUID({ each: true, optional: true })
personIds?: string[];

@ValidateUUID({ each: true, optional: true })
tagIds?: string[];
}

export class RandomSearchDto extends BaseSearchDto {
Expand Down
16 changes: 16 additions & 0 deletions server/src/entities/asset.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,21 @@ export function hasPeopleCte(db: Kysely<DB>, personIds: string[]) {
);
}

export function hasTags<O>(qb: SelectQueryBuilder<DB, 'assets', O>, tagIds: string[]) {
return qb.innerJoin(
(eb) => eb
.selectFrom('tag_asset')
.select('assetsId')
.innerJoin('tags_closure', 'tag_asset.tagsId', 'tags_closure.id_descendant')
.where('tags_closure.id_ancestor', '=', anyUuid(tagIds))
.groupBy('assetsId')
.having((eb) => eb.fn.count('tags_closure.id_ancestor').distinct(), '>=', tagIds.length)
.as('has_tags'),
(join) => join
.onRef('has_tags.assetsId', '=', 'assets.id')
);
}

export function hasPeople(db: Kysely<DB>, personIds?: string[]) {
return personIds && personIds.length > 0
? hasPeopleCte(db, personIds).selectFrom('assets').innerJoin('has_people', 'has_people.assetId', 'assets.id')
Expand Down Expand Up @@ -328,6 +343,7 @@ export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuild
options.withDeleted ||= !!(options.trashedAfter || options.trashedBefore);
return hasPeople(kysely.withPlugin(joinDeduplicationPlugin), options.personIds)
.selectAll('assets')
.$if(!!options.tagIds && options.tagIds.length > 0, (qb) => hasTags(qb, options.tagIds!))
.$if(!!options.createdBefore, (qb) => qb.where('assets.createdAt', '<=', options.createdBefore!))
.$if(!!options.createdAfter, (qb) => qb.where('assets.createdAt', '>=', options.createdAfter!))
.$if(!!options.updatedBefore, (qb) => qb.where('assets.updatedAt', '<=', options.updatedBefore!))
Expand Down
10 changes: 8 additions & 2 deletions server/src/interfaces/search.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ export interface SearchPeopleOptions {
personIds?: string[];
}

export interface SearchTagOptions {
tagIds?: string[];
}

export interface SearchOrderOptions {
orderDirection?: 'asc' | 'desc';
}
Expand All @@ -128,7 +132,8 @@ type BaseAssetSearchOptions = SearchDateOptions &
SearchPathOptions &
SearchStatusOptions &
SearchUserIdOptions &
SearchPeopleOptions;
SearchPeopleOptions &
SearchTagOptions;

export type AssetSearchOptions = BaseAssetSearchOptions & SearchRelationOptions;

Expand All @@ -142,7 +147,8 @@ export type SmartSearchOptions = SearchDateOptions &
SearchOneToOneRelationOptions &
SearchStatusOptions &
SearchUserIdOptions &
SearchPeopleOptions;
SearchPeopleOptions &
SearchTagOptions;

export interface FaceEmbeddingSearch extends SearchEmbeddingOptions {
hasPerson?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
query: string;
queryType: 'smart' | 'metadata';
personIds: SvelteSet<string>;
tagIds: SvelteSet<string>;
location: SearchLocationFilter;
camera: SearchCameraFilter;
date: SearchDateFilter;
Expand All @@ -20,6 +21,7 @@
import { Button } from '@immich/ui';
import { AssetTypeEnum, type SmartSearchDto, type MetadataSearchDto } from '@immich/sdk';
import SearchPeopleSection from './search-people-section.svelte';
import SearchTagsSection from './search-tags-section.svelte';
import SearchLocationSection from './search-location-section.svelte';
import SearchCameraSection, { type SearchCameraFilter } from './search-camera-section.svelte';
import SearchDateSection from './search-date-section.svelte';
Expand Down Expand Up @@ -54,6 +56,7 @@
query: 'query' in searchQuery ? searchQuery.query : searchQuery.originalFileName || '',
queryType: 'query' in searchQuery ? 'smart' : 'metadata',
personIds: new SvelteSet('personIds' in searchQuery ? searchQuery.personIds : []),
tagIds: new SvelteSet('tagIds' in searchQuery ? searchQuery.tagIds : []),
location: {
country: withNullAsUndefined(searchQuery.country),
state: withNullAsUndefined(searchQuery.state),
Expand Down Expand Up @@ -85,6 +88,7 @@
query: '',
queryType: 'smart',
personIds: new SvelteSet(),
tagIds: new SvelteSet(),
location: {},
camera: {},
date: {},
Expand Down Expand Up @@ -117,6 +121,7 @@
isFavorite: filter.display.isFavorite || undefined,
isNotInAlbum: filter.display.isNotInAlbum || undefined,
personIds: filter.personIds.size > 0 ? [...filter.personIds] : undefined,
tagIds: filter.tagIds.size > 0 ? [...filter.tagIds] : undefined,
type,
};
Expand All @@ -143,6 +148,9 @@
<!-- TEXT -->
<SearchTextSection bind:query={filter.query} bind:queryType={filter.queryType} />

<!-- TAGS -->
<SearchTagsSection bind:selectedTags={filter.tagIds} />

<!-- LOCATION -->
<SearchLocationSection bind:filters={filter.location} />

Expand Down
Loading

0 comments on commit e723d6f

Please sign in to comment.