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:
Jason Rasmussen
2023-03-05 15:44:31 -05:00
committed by GitHub
parent 1f631eafce
commit 2ca560ebf8
35 changed files with 1079 additions and 63 deletions

View File

@@ -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;
}

View File

@@ -1,2 +1,3 @@
export * from './search-config-response.dto';
export * from './search-explore.response.dto';
export * from './search-response.dto';

View File

@@ -0,0 +1,11 @@
import { AssetResponseDto } from '../../asset';
class SearchExploreItem {
value!: string;
data!: AssetResponseDto;
}
export class SearchExploreResponseDto {
fieldName!: string;
items!: SearchExploreItem[];
}

View File

@@ -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>[]>;
}

View File

@@ -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');
}
}
}