From 2f53f6a62cedeb05a1da983b292db18b1ec95618 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 5 Mar 2024 17:08:35 -0600 Subject: [PATCH] feat(web): search by filename (#7624) * Toggle to search by filename * wild card search and pr feedback * Pr feedback * naming * placeholder * Create index * pr feedback * pr feedback * Update web/src/lib/components/shared-components/search-bar/search-text-section.svelte Co-authored-by: Jason Rasmussen * pr feedback * pr feedback * pr feedback * pr feedback --------- Co-authored-by: Jason Rasmussen --- server/src/infra/entities/asset.entity.ts | 1 + server/src/infra/infra.utils.ts | 8 ++- ...140355-AddAssetOriginalPathTrigramIndex.ts | 14 +++++ .../search-bar/search-filter-box.svelte | 20 ++----- .../search-bar/search-text-section.svelte | 57 +++++++++++++++++++ web/src/routes/(user)/search/+page.svelte | 1 + 6 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 server/src/infra/migrations/1709608140355-AddAssetOriginalPathTrigramIndex.ts create mode 100644 web/src/lib/components/shared-components/search-bar/search-text-section.svelte diff --git a/server/src/infra/entities/asset.entity.ts b/server/src/infra/entities/asset.entity.ts index 3732711581..7335c3ddf7 100644 --- a/server/src/infra/entities/asset.entity.ts +++ b/server/src/infra/entities/asset.entity.ts @@ -35,6 +35,7 @@ export const ASSET_CHECKSUM_CONSTRAINT = 'UQ_assets_owner_library_checksum'; @Index('IDX_day_of_month', { synchronize: false }) @Index('IDX_month', { synchronize: false }) @Index('IDX_originalPath_libraryId', ['originalPath', 'libraryId']) +@Index('idx_originalpath_trigram', { synchronize: false }) // For all assets, each originalpath must be unique per user and library export class AssetEntity { @PrimaryGeneratedColumn('uuid') diff --git a/server/src/infra/infra.utils.ts b/server/src/infra/infra.utils.ts index 745f5a38ff..2c6e4b7470 100644 --- a/server/src/infra/infra.utils.ts +++ b/server/src/infra/infra.utils.ts @@ -160,9 +160,15 @@ export function searchAssetBuilder( builder.andWhere(`${builder.alias}.ownerId IN (:...userIds)`, { userIds: options.userIds }); } - const path = _.pick(options, ['encodedVideoPath', 'originalFileName', 'originalPath', 'resizePath', 'webpPath']); + const path = _.pick(options, ['encodedVideoPath', 'originalFileName', 'resizePath', 'webpPath']); builder.andWhere(_.omitBy(path, _.isUndefined)); + if (options.originalPath) { + builder.andWhere(`f_unaccent(${builder.alias}.originalPath) ILIKE f_unaccent(:originalPath)`, { + originalPath: `%${options.originalPath}%`, + }); + } + const status = _.pick(options, ['isExternal', 'isFavorite', 'isOffline', 'isReadOnly', 'isVisible', 'type']); const { isArchived, diff --git a/server/src/infra/migrations/1709608140355-AddAssetOriginalPathTrigramIndex.ts b/server/src/infra/migrations/1709608140355-AddAssetOriginalPathTrigramIndex.ts new file mode 100644 index 0000000000..fdca15cbff --- /dev/null +++ b/server/src/infra/migrations/1709608140355-AddAssetOriginalPathTrigramIndex.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAssetOriginalPathTrigramIndex1709608140355 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE INDEX idx_originalpath_trigram + ON assets + USING gin (f_unaccent("originalPath") gin_trgm_ops)`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "idx_originalpath_trigram"`); + } +} diff --git a/web/src/lib/components/shared-components/search-bar/search-filter-box.svelte b/web/src/lib/components/shared-components/search-bar/search-filter-box.svelte index b05e2d5a3b..d81ed566ca 100644 --- a/web/src/lib/components/shared-components/search-bar/search-filter-box.svelte +++ b/web/src/lib/components/shared-components/search-bar/search-filter-box.svelte @@ -11,6 +11,7 @@ export type SearchFilter = { context?: string; + filename?: string; personIds: Set; location: SearchLocationFilter; camera: SearchCameraFilter; @@ -32,6 +33,7 @@ import SearchMediaSection from './search-media-section.svelte'; import { parseUtcDate } from '$lib/utils/date-time'; import SearchDisplaySection from './search-display-section.svelte'; + import SearchTextSection from './search-text-section.svelte'; export let searchQuery: MetadataSearchDto | SmartSearchDto; @@ -41,6 +43,7 @@ let filter: SearchFilter = { context: 'query' in searchQuery ? searchQuery.query : '', + filename: 'originalPath' in searchQuery ? searchQuery.originalPath : undefined, personIds: new Set('personIds' in searchQuery ? searchQuery.personIds : []), location: { country: searchQuery.country, @@ -91,6 +94,7 @@ let payload: SmartSearchDto | MetadataSearchDto = { query: filter.context || undefined, + originalPath: filter.filename, country: filter.location.country, state: filter.location.state, city: filter.location.city, @@ -124,20 +128,8 @@ - -
- -
+ + diff --git a/web/src/lib/components/shared-components/search-bar/search-text-section.svelte b/web/src/lib/components/shared-components/search-bar/search-text-section.svelte new file mode 100644 index 0000000000..fd3751eecc --- /dev/null +++ b/web/src/lib/components/shared-components/search-bar/search-text-section.svelte @@ -0,0 +1,57 @@ + + +
+ + + +
+ +{#if selectedOption === TextSearchOptions.Context} + +{:else} + +{/if} diff --git a/web/src/routes/(user)/search/+page.svelte b/web/src/routes/(user)/search/+page.svelte index b6c28f4b35..3b7d6b8e6e 100644 --- a/web/src/routes/(user)/search/+page.svelte +++ b/web/src/routes/(user)/search/+page.svelte @@ -173,6 +173,7 @@ make: 'Camera brand', model: 'Camera model', personIds: 'People', + originalPath: 'File name', }; return keyMap[key] || key; }