mirror of
https://github.com/KevinMidboe/immich.git
synced 2026-03-02 12:09:53 +00:00
feat(web,server): explore (#1926)
* feat: explore * chore: generate open api * styling explore page * styling no result page * style overlay * style: bluring text on thumbnail card for readability * explore page tweaks * fix(web): search urls * feat(web): use objects for things * feat(server): filter by motion, sort by createdAt * More styling * better navigation --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com> Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
This commit is contained in:
@@ -54,4 +54,14 @@ export class SearchDto {
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value.split(','))
|
||||
'smartInfo.tags'?: string[];
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Transform(toBoolean)
|
||||
recent?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Transform(toBoolean)
|
||||
motion?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './search-config-response.dto';
|
||||
export * from './search-explore.response.dto';
|
||||
export * from './search-response.dto';
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { AssetResponseDto } from '../../asset';
|
||||
|
||||
class SearchExploreItem {
|
||||
value!: string;
|
||||
data!: AssetResponseDto;
|
||||
}
|
||||
|
||||
export class SearchExploreResponseDto {
|
||||
fieldName!: string;
|
||||
items!: SearchExploreItem[];
|
||||
}
|
||||
@@ -17,6 +17,8 @@ export interface SearchFilter {
|
||||
model?: string;
|
||||
objects?: string[];
|
||||
tags?: string[];
|
||||
recent?: boolean;
|
||||
motion?: boolean;
|
||||
}
|
||||
|
||||
export interface SearchResult<T> {
|
||||
@@ -39,6 +41,14 @@ export interface SearchFacet {
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface SearchExploreItem<T> {
|
||||
fieldName: string;
|
||||
items: Array<{
|
||||
value: string;
|
||||
data: T;
|
||||
}>;
|
||||
}
|
||||
|
||||
export type SearchCollectionIndexStatus = Record<SearchCollection, boolean>;
|
||||
|
||||
export const ISearchRepository = 'ISearchRepository';
|
||||
@@ -57,4 +67,6 @@ export interface ISearchRepository {
|
||||
|
||||
search(collection: SearchCollection.ASSETS, query: string, filters: SearchFilter): Promise<SearchResult<AssetEntity>>;
|
||||
search(collection: SearchCollection.ALBUMS, query: string, filters: SearchFilter): Promise<SearchResult<AlbumEntity>>;
|
||||
|
||||
explore(userId: string): Promise<SearchExploreItem<AssetEntity>[]>;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AssetEntity } from '@app/infra/db/entities';
|
||||
import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { IAlbumRepository } from '../album/album.repository';
|
||||
@@ -6,7 +7,7 @@ import { AuthUserDto } from '../auth';
|
||||
import { IAlbumJob, IAssetJob, IDeleteJob, IJobRepository, JobName } from '../job';
|
||||
import { SearchDto } from './dto';
|
||||
import { SearchConfigResponseDto, SearchResponseDto } from './response-dto';
|
||||
import { ISearchRepository, SearchCollection } from './search.repository';
|
||||
import { ISearchRepository, SearchCollection, SearchExploreItem } from './search.repository';
|
||||
|
||||
@Injectable()
|
||||
export class SearchService {
|
||||
@@ -52,10 +53,13 @@ export class SearchService {
|
||||
}
|
||||
}
|
||||
|
||||
async getExploreData(authUser: AuthUserDto): Promise<SearchExploreItem<AssetEntity>[]> {
|
||||
this.assertEnabled();
|
||||
return this.searchRepository.explore(authUser.id);
|
||||
}
|
||||
|
||||
async search(authUser: AuthUserDto, dto: SearchDto): Promise<SearchResponseDto> {
|
||||
if (!this.enabled) {
|
||||
throw new BadRequestException('Search is disabled');
|
||||
}
|
||||
this.assertEnabled();
|
||||
|
||||
const query = dto.query || '*';
|
||||
|
||||
@@ -83,6 +87,7 @@ export class SearchService {
|
||||
|
||||
this.logger.log(`Indexing ${assets.length} assets`);
|
||||
await this.searchRepository.import(SearchCollection.ASSETS, assets, true);
|
||||
this.logger.debug('Finished re-indexing all assets');
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Unable to index all assets`, error?.stack);
|
||||
}
|
||||
@@ -94,6 +99,9 @@ export class SearchService {
|
||||
}
|
||||
|
||||
const { asset } = data;
|
||||
if (!asset.isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.searchRepository.index(SearchCollection.ASSETS, asset);
|
||||
@@ -111,6 +119,7 @@ export class SearchService {
|
||||
const albums = await this.albumRepository.getAll();
|
||||
this.logger.log(`Indexing ${albums.length} albums`);
|
||||
await this.searchRepository.import(SearchCollection.ALBUMS, albums, true);
|
||||
this.logger.debug('Finished re-indexing all albums');
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Unable to index all albums`, error?.stack);
|
||||
}
|
||||
@@ -151,4 +160,10 @@ export class SearchService {
|
||||
this.logger.error(`Unable to remove ${collection}: ${id}`, error?.stack);
|
||||
}
|
||||
}
|
||||
|
||||
private assertEnabled() {
|
||||
if (!this.enabled) {
|
||||
throw new BadRequestException('Search is disabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user