mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02:00
fix(server): search and explore part 2 (#2031)
* explore logging * chore: regenerate open api * fix: explore page
This commit is contained in:
parent
6e1d09fc32
commit
25a10784eb
BIN
mobile/openapi/README.md
generated
BIN
mobile/openapi/README.md
generated
Binary file not shown.
@ -8,7 +8,7 @@ import {
|
||||
} from '@app/domain';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import _, { Dictionary } from 'lodash';
|
||||
import { filter, firstValueFrom, from, map, mergeMap, toArray } from 'rxjs';
|
||||
import { catchError, filter, firstValueFrom, from, map, mergeMap, of, toArray } from 'rxjs';
|
||||
import { Client } from 'typesense';
|
||||
import { CollectionCreateSchema } from 'typesense/lib/Typesense/Collections';
|
||||
import { DocumentSchema, SearchResponse } from 'typesense/lib/Typesense/Documents';
|
||||
@ -148,7 +148,7 @@ export class TypesenseRepository implements ISearchRepository {
|
||||
|
||||
const common = {
|
||||
q: '*',
|
||||
filter_by: `ownerId:${userId}`,
|
||||
filter_by: this.buildFilterBy('ownerId', userId, true),
|
||||
per_page: 100,
|
||||
};
|
||||
|
||||
@ -157,8 +157,8 @@ export class TypesenseRepository implements ISearchRepository {
|
||||
const { facet_counts: facets } = await asset$.search({
|
||||
...common,
|
||||
query_by: 'exifInfo.imageName',
|
||||
facet_by: this.getFacetFieldNames(SearchCollection.ASSETS),
|
||||
max_facet_values: 50,
|
||||
facet_by: 'exifInfo.city,smartInfo.objects',
|
||||
max_facet_values: 12,
|
||||
});
|
||||
|
||||
return firstValueFrom(
|
||||
@ -166,23 +166,31 @@ export class TypesenseRepository implements ISearchRepository {
|
||||
mergeMap(
|
||||
(facet) =>
|
||||
from(facet.counts).pipe(
|
||||
mergeMap(
|
||||
(count) =>
|
||||
from(
|
||||
asset$.search({
|
||||
...common,
|
||||
query_by: 'exifInfo.imageName',
|
||||
filter_by: `${facet.field_name}:${count.value}`,
|
||||
}),
|
||||
).pipe(
|
||||
map((result) => ({
|
||||
value: count.value,
|
||||
data: result.hits?.[0]?.document as AssetEntity,
|
||||
})),
|
||||
filter((item) => !!item.data),
|
||||
),
|
||||
5,
|
||||
),
|
||||
mergeMap((count) => {
|
||||
const config = {
|
||||
...common,
|
||||
query_by: 'exifInfo.imageName',
|
||||
filter_by: [
|
||||
this.buildFilterBy('ownerId', userId, true),
|
||||
this.buildFilterBy(facet.field_name, count.value, true),
|
||||
].join(' && '),
|
||||
per_page: 1,
|
||||
};
|
||||
|
||||
this.logger.verbose(`Explore subquery: "filter_by:${config.filter_by}" (count:${count.count})`);
|
||||
|
||||
return from(asset$.search(config)).pipe(
|
||||
catchError((error: any) => {
|
||||
this.logger.warn(`Explore subquery error: ${error}`, error?.stack);
|
||||
return of({ hits: [] });
|
||||
}),
|
||||
map((result) => ({
|
||||
value: count.value,
|
||||
data: result.hits?.[0]?.document as AssetEntity,
|
||||
})),
|
||||
filter((item) => !!item.data),
|
||||
);
|
||||
}, 5),
|
||||
toArray(),
|
||||
map((items) => ({
|
||||
fieldName: facet.field_name as string,
|
||||
@ -208,7 +216,7 @@ export class TypesenseRepository implements ISearchRepository {
|
||||
await this.client
|
||||
.collections(schemaMap[collection].name)
|
||||
.documents()
|
||||
.delete({ filter_by: `id: [${ids.join(',')}]` });
|
||||
.delete({ filter_by: this.buildFilterBy('id', ids, true) });
|
||||
}
|
||||
|
||||
async searchAlbums(query: string, filters: SearchFilter): Promise<SearchResult<AlbumEntity>> {
|
||||
@ -350,18 +358,17 @@ export class TypesenseRepository implements ISearchRepository {
|
||||
|
||||
private getAlbumFilters(filters: SearchFilter) {
|
||||
const { userId } = filters;
|
||||
const _filters = [`ownerId:${userId}`];
|
||||
|
||||
const _filters = [this.buildFilterBy('ownerId', userId, true)];
|
||||
|
||||
if (filters.id) {
|
||||
_filters.push(`id:=${filters.id}`);
|
||||
_filters.push(this.buildFilterBy('id', filters.id, true));
|
||||
}
|
||||
|
||||
for (const item of albumSchema.fields || []) {
|
||||
let value = filters[item.name as keyof SearchFilter];
|
||||
if (Array.isArray(value)) {
|
||||
value = `[${value.join(',')}]`;
|
||||
}
|
||||
const value = filters[item.name as keyof SearchFilter];
|
||||
if (item.facet && value !== undefined) {
|
||||
_filters.push(`${item.name}:${value}`);
|
||||
_filters.push(this.buildFilterBy(item.name, value));
|
||||
}
|
||||
}
|
||||
|
||||
@ -373,17 +380,17 @@ export class TypesenseRepository implements ISearchRepository {
|
||||
}
|
||||
|
||||
private getAssetFilters(filters: SearchFilter) {
|
||||
const _filters = [`ownerId:${filters.userId}`];
|
||||
const { userId } = filters;
|
||||
const _filters = [this.buildFilterBy('ownerId', userId, true)];
|
||||
|
||||
if (filters.id) {
|
||||
_filters.push(`id:=${filters.id}`);
|
||||
_filters.push(this.buildFilterBy('id', filters.id, true));
|
||||
}
|
||||
|
||||
for (const item of assetSchema.fields || []) {
|
||||
let value = filters[item.name as keyof SearchFilter];
|
||||
if (Array.isArray(value)) {
|
||||
value = `[${value.join(',')}]`;
|
||||
}
|
||||
const value = filters[item.name as keyof SearchFilter];
|
||||
if (item.facet && value !== undefined) {
|
||||
_filters.push(`${item.name}:${value}`);
|
||||
_filters.push(this.buildFilterBy(item.name, value));
|
||||
}
|
||||
}
|
||||
|
||||
@ -393,4 +400,19 @@ export class TypesenseRepository implements ISearchRepository {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private buildFilterBy(key: string, values: boolean | string | string[], exact?: boolean) {
|
||||
const token = exact ? ':=' : ':';
|
||||
|
||||
const _values = (Array.isArray(values) ? values : [values]).map((value) => {
|
||||
if (typeof value === 'boolean' || value === 'true' || value === 'false') {
|
||||
return value;
|
||||
}
|
||||
return '`' + value + '`';
|
||||
});
|
||||
|
||||
const value = _values.length > 1 ? `[${_values.join(',')}]` : _values[0];
|
||||
|
||||
return `${key}${token}${value}`;
|
||||
}
|
||||
}
|
||||
|
2
web/src/api/open-api/api.ts
generated
2
web/src/api/open-api/api.ts
generated
@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.51.0
|
||||
* The version of the OpenAPI document: 1.51.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
2
web/src/api/open-api/base.ts
generated
2
web/src/api/open-api/base.ts
generated
@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.51.0
|
||||
* The version of the OpenAPI document: 1.51.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
2
web/src/api/open-api/common.ts
generated
2
web/src/api/open-api/common.ts
generated
@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.51.0
|
||||
* The version of the OpenAPI document: 1.51.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
2
web/src/api/open-api/configuration.ts
generated
2
web/src/api/open-api/configuration.ts
generated
@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.51.0
|
||||
* The version of the OpenAPI document: 1.51.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
2
web/src/api/open-api/index.ts
generated
2
web/src/api/open-api/index.ts
generated
@ -4,7 +4,7 @@
|
||||
* Immich
|
||||
* Immich API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.51.0
|
||||
* The version of the OpenAPI document: 1.51.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
|
Loading…
Reference in New Issue
Block a user