2023-03-05 21:44:31 +01:00
|
|
|
import { AssetEntity } from '@app/infra/db/entities';
|
2023-03-03 03:47:08 +01:00
|
|
|
import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common';
|
|
|
|
import { ConfigService } from '@nestjs/config';
|
|
|
|
import { IAlbumRepository } from '../album/album.repository';
|
|
|
|
import { IAssetRepository } from '../asset/asset.repository';
|
|
|
|
import { AuthUserDto } from '../auth';
|
|
|
|
import { IAlbumJob, IAssetJob, IDeleteJob, IJobRepository, JobName } from '../job';
|
|
|
|
import { SearchDto } from './dto';
|
|
|
|
import { SearchConfigResponseDto, SearchResponseDto } from './response-dto';
|
2023-03-05 21:44:31 +01:00
|
|
|
import { ISearchRepository, SearchCollection, SearchExploreItem } from './search.repository';
|
2023-03-03 03:47:08 +01:00
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
export class SearchService {
|
|
|
|
private logger = new Logger(SearchService.name);
|
|
|
|
private enabled: boolean;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
|
|
|
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
|
|
|
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
|
|
|
@Inject(ISearchRepository) private searchRepository: ISearchRepository,
|
|
|
|
configService: ConfigService,
|
|
|
|
) {
|
|
|
|
this.enabled = configService.get('TYPESENSE_ENABLED') !== 'false';
|
|
|
|
}
|
|
|
|
|
|
|
|
isEnabled() {
|
|
|
|
return this.enabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
getConfig(): SearchConfigResponseDto {
|
|
|
|
return {
|
|
|
|
enabled: this.enabled,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
async bootstrap() {
|
|
|
|
if (!this.enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.logger.log('Running bootstrap');
|
|
|
|
await this.searchRepository.setup();
|
|
|
|
|
|
|
|
const migrationStatus = await this.searchRepository.checkMigrationStatus();
|
|
|
|
if (migrationStatus[SearchCollection.ASSETS]) {
|
|
|
|
this.logger.debug('Queueing job to re-index all assets');
|
|
|
|
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSETS });
|
|
|
|
}
|
|
|
|
if (migrationStatus[SearchCollection.ALBUMS]) {
|
|
|
|
this.logger.debug('Queueing job to re-index all albums');
|
|
|
|
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ALBUMS });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-05 21:44:31 +01:00
|
|
|
async getExploreData(authUser: AuthUserDto): Promise<SearchExploreItem<AssetEntity>[]> {
|
|
|
|
this.assertEnabled();
|
|
|
|
return this.searchRepository.explore(authUser.id);
|
|
|
|
}
|
|
|
|
|
2023-03-03 03:47:08 +01:00
|
|
|
async search(authUser: AuthUserDto, dto: SearchDto): Promise<SearchResponseDto> {
|
2023-03-05 21:44:31 +01:00
|
|
|
this.assertEnabled();
|
2023-03-03 03:47:08 +01:00
|
|
|
|
|
|
|
const query = dto.query || '*';
|
|
|
|
|
|
|
|
return {
|
|
|
|
assets: (await this.searchRepository.search(SearchCollection.ASSETS, query, {
|
|
|
|
userId: authUser.id,
|
|
|
|
...dto,
|
|
|
|
})) as any,
|
|
|
|
albums: (await this.searchRepository.search(SearchCollection.ALBUMS, query, {
|
|
|
|
userId: authUser.id,
|
|
|
|
...dto,
|
|
|
|
})) as any,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
async handleIndexAssets() {
|
|
|
|
if (!this.enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
this.logger.debug(`Running indexAssets`);
|
|
|
|
// TODO: do this in batches based on searchIndexVersion
|
|
|
|
const assets = await this.assetRepository.getAll({ isVisible: true });
|
|
|
|
|
|
|
|
this.logger.log(`Indexing ${assets.length} assets`);
|
|
|
|
await this.searchRepository.import(SearchCollection.ASSETS, assets, true);
|
2023-03-05 21:44:31 +01:00
|
|
|
this.logger.debug('Finished re-indexing all assets');
|
2023-03-03 03:47:08 +01:00
|
|
|
} catch (error: any) {
|
|
|
|
this.logger.error(`Unable to index all assets`, error?.stack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async handleIndexAsset(data: IAssetJob) {
|
|
|
|
if (!this.enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { asset } = data;
|
2023-03-05 21:44:31 +01:00
|
|
|
if (!asset.isVisible) {
|
|
|
|
return;
|
|
|
|
}
|
2023-03-03 03:47:08 +01:00
|
|
|
|
|
|
|
try {
|
|
|
|
await this.searchRepository.index(SearchCollection.ASSETS, asset);
|
|
|
|
} catch (error: any) {
|
|
|
|
this.logger.error(`Unable to index asset: ${asset.id}`, error?.stack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async handleIndexAlbums() {
|
|
|
|
if (!this.enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const albums = await this.albumRepository.getAll();
|
|
|
|
this.logger.log(`Indexing ${albums.length} albums`);
|
|
|
|
await this.searchRepository.import(SearchCollection.ALBUMS, albums, true);
|
2023-03-05 21:44:31 +01:00
|
|
|
this.logger.debug('Finished re-indexing all albums');
|
2023-03-03 03:47:08 +01:00
|
|
|
} catch (error: any) {
|
|
|
|
this.logger.error(`Unable to index all albums`, error?.stack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async handleIndexAlbum(data: IAlbumJob) {
|
|
|
|
if (!this.enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { album } = data;
|
|
|
|
|
|
|
|
try {
|
|
|
|
await this.searchRepository.index(SearchCollection.ALBUMS, album);
|
|
|
|
} catch (error: any) {
|
|
|
|
this.logger.error(`Unable to index album: ${album.id}`, error?.stack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async handleRemoveAlbum(data: IDeleteJob) {
|
|
|
|
await this.handleRemove(SearchCollection.ALBUMS, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
async handleRemoveAsset(data: IDeleteJob) {
|
|
|
|
await this.handleRemove(SearchCollection.ASSETS, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
private async handleRemove(collection: SearchCollection, data: IDeleteJob) {
|
|
|
|
if (!this.enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { id } = data;
|
|
|
|
|
|
|
|
try {
|
|
|
|
await this.searchRepository.delete(collection, id);
|
|
|
|
} catch (error: any) {
|
|
|
|
this.logger.error(`Unable to remove ${collection}: ${id}`, error?.stack);
|
|
|
|
}
|
|
|
|
}
|
2023-03-05 21:44:31 +01:00
|
|
|
|
|
|
|
private assertEnabled() {
|
|
|
|
if (!this.enabled) {
|
|
|
|
throw new BadRequestException('Search is disabled');
|
|
|
|
}
|
|
|
|
}
|
2023-03-03 03:47:08 +01:00
|
|
|
}
|