1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

Add person count search and sorting support.

Note it will trigger a DB drop as the scheme changed. #683
This commit is contained in:
Patrik J. Braun 2023-08-01 22:57:36 +02:00
parent 1974323b61
commit 0e460d07af
17 changed files with 305 additions and 105 deletions

View File

@ -367,6 +367,7 @@ export class IndexingManager {
), ),
]; ];
} }
(media[i].metadata as PhotoMetadataEntity).personsLength = (media[i].metadata as PhotoMetadataEntity)?.persons?.length || 0;
if (mediaItem == null) { if (mediaItem == null) {

View File

@ -13,9 +13,9 @@ import {
DatePatternFrequency, DatePatternFrequency,
DatePatternSearch, DatePatternSearch,
DistanceSearch, DistanceSearch,
FromDateSearch, FromDateSearch, MaxPersonCountSearch,
MaxRatingSearch, MaxRatingSearch,
MaxResolutionSearch, MaxResolutionSearch, MinPersonCountSearch,
MinRatingSearch, MinRatingSearch,
MinResolutionSearch, MinResolutionSearch,
OrientationSearch, OrientationSearch,
@ -379,6 +379,12 @@ export class SearchManager {
case SortingMethods.ascName: case SortingMethods.ascName:
query.addOrderBy('media.name', 'ASC'); query.addOrderBy('media.name', 'ASC');
break; break;
case SortingMethods.descPersonCount:
query.addOrderBy('media.metadata.personsLength', 'DESC');
break;
case SortingMethods.ascPersonCount:
query.addOrderBy('media.metadata.personsLength', 'ASC');
break;
case SortingMethods.random: case SortingMethods.random:
if (Config.Database.type === DatabaseType.mysql) { if (Config.Database.type === DatabaseType.mysql) {
query.groupBy('RAND(), media.id'); query.groupBy('RAND(), media.id');
@ -639,6 +645,52 @@ export class SearchManager {
return q; return q;
}); });
case SearchQueryTypes.min_person_count:
if (directoryOnly) {
throw new Error('not supported in directoryOnly mode');
}
return new Brackets((q): unknown => {
if (typeof (query as MinPersonCountSearch).value === 'undefined') {
throw new Error(
'Invalid search query: Person count Query should contain minvalue'
);
}
const relation = (query as TextSearch).negate ? '<' : '>=';
const textParam: { [key: string]: unknown } = {};
textParam['min' + queryId] = (query as MinPersonCountSearch).value;
q.where(
`media.metadata.personsLength ${relation} :min${queryId}`,
textParam
);
return q;
});
case SearchQueryTypes.max_person_count:
if (directoryOnly) {
throw new Error('not supported in directoryOnly mode');
}
return new Brackets((q): unknown => {
if (typeof (query as MaxPersonCountSearch).value === 'undefined') {
throw new Error(
'Invalid search query: Person count Query should contain max value'
);
}
const relation = (query as TextSearch).negate ? '>' : '<=';
if (typeof (query as MaxRatingSearch).value !== 'undefined') {
const textParam: { [key: string]: unknown } = {};
textParam['max' + queryId] = (query as MaxPersonCountSearch).value;
q.where(
`media.metadata.personsLength ${relation} :max${queryId}`,
textParam
);
}
return q;
});
case SearchQueryTypes.min_resolution: case SearchQueryTypes.min_resolution:
if (directoryOnly) { if (directoryOnly) {
throw new Error('not supported in directoryOnly mode'); throw new Error('not supported in directoryOnly mode');

View File

@ -1,26 +1,9 @@
import { import {Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance, Unique,} from 'typeorm';
Column, import {DirectoryEntity} from './DirectoryEntity';
Entity, import {MediaDimension, MediaDTO, MediaMetadata,} from '../../../../common/entities/MediaDTO';
Index, import {PersonJunctionTable} from './PersonJunctionTable';
ManyToOne, import {columnCharsetCS} from './EntityUtils';
OneToMany, import {CameraMetadata, FaceRegion, GPSMetadata, PositionMetaData,} from '../../../../common/entities/PhotoDTO';
PrimaryGeneratedColumn,
TableInheritance,
Unique,
} from 'typeorm';
import { DirectoryEntity } from './DirectoryEntity';
import {
MediaDimension,
MediaDTO,
MediaMetadata,
} from '../../../../common/entities/MediaDTO';
import { PersonJunctionTable} from './PersonJunctionTable';
import { columnCharsetCS } from './EntityUtils';
import {
CameraMetadata, FaceRegion,
GPSMetadata,
PositionMetaData,
} from '../../../../common/entities/PhotoDTO';
export class MediaDimensionEntity implements MediaDimension { export class MediaDimensionEntity implements MediaDimension {
@Column('int') @Column('int')
@ -31,7 +14,7 @@ export class MediaDimensionEntity implements MediaDimension {
} }
export class CameraMetadataEntity implements CameraMetadata { export class CameraMetadataEntity implements CameraMetadata {
@Column('int', { nullable: true, unsigned: true }) @Column('int', {nullable: true, unsigned: true})
ISO: number; ISO: number;
@Column({ @Column({
@ -50,23 +33,23 @@ export class CameraMetadataEntity implements CameraMetadata {
}) })
make: string; make: string;
@Column('float', { nullable: true }) @Column('float', {nullable: true})
fStop: number; fStop: number;
@Column('float', { nullable: true }) @Column('float', {nullable: true})
exposure: number; exposure: number;
@Column('float', { nullable: true }) @Column('float', {nullable: true})
focalLength: number; focalLength: number;
@Column('text', { nullable: true }) @Column('text', {nullable: true})
lens: string; lens: string;
} }
export class GPSMetadataEntity implements GPSMetadata { export class GPSMetadataEntity implements GPSMetadata {
@Column('float', { nullable: true }) @Column('float', {nullable: true})
latitude: number; latitude: number;
@Column('float', { nullable: true }) @Column('float', {nullable: true})
longitude: number; longitude: number;
} }
@ -120,7 +103,7 @@ export class MediaMetadataEntity implements MediaMetadata {
@Index() @Index()
creationDate: number; creationDate: number;
@Column('int', { unsigned: true }) @Column('int', {unsigned: true})
fileSize: number; fileSize: number;
@Column({ @Column({
@ -136,7 +119,7 @@ export class MediaMetadataEntity implements MediaMetadata {
@Column((type) => PositionMetaDataEntity) @Column((type) => PositionMetaDataEntity)
positionData: PositionMetaDataEntity; positionData: PositionMetaDataEntity;
@Column('tinyint', { unsigned: true }) @Column('tinyint', {unsigned: true})
@Index() @Index()
rating: 0 | 1 | 2 | 3 | 4 | 5; rating: 0 | 1 | 2 | 3 | 4 | 5;
@ -144,10 +127,11 @@ export class MediaMetadataEntity implements MediaMetadata {
personJunction: PersonJunctionTable[]; personJunction: PersonJunctionTable[];
@Column({ @Column({
type:'simple-json', type: 'simple-json',
nullable: true, nullable: true,
charset: columnCharsetCS.charset, charset: columnCharsetCS.charset,
collation: columnCharsetCS.collation}) collation: columnCharsetCS.collation
})
faces: FaceRegion[]; faces: FaceRegion[];
/** /**
@ -162,20 +146,32 @@ export class MediaMetadataEntity implements MediaMetadata {
}) })
persons: string[]; persons: string[];
@Column('int', { unsigned: true }) /**
* Caches the list of persons' length. Only used for searching
*/
@Column({
type: 'tinyint',
select: false,
nullable: false,
default: 0
})
personsLength: number;
@Column('int', {unsigned: true})
bitRate: number; bitRate: number;
@Column('int', { unsigned: true }) @Column('int', {unsigned: true})
duration: number; duration: number;
} }
// TODO: fix inheritance once its working in typeorm // TODO: fix inheritance once its working in typeorm
@Entity() @Entity()
@Unique(['name', 'directory']) @Unique(['name', 'directory'])
@TableInheritance({ column: { type: 'varchar', name: 'type', length: 16 } }) @TableInheritance({column: {type: 'varchar', name: 'type', length: 16}})
export abstract class MediaEntity implements MediaDTO { export abstract class MediaEntity implements MediaDTO {
@Index() @Index()
@PrimaryGeneratedColumn({ unsigned: true }) @PrimaryGeneratedColumn({unsigned: true})
id: number; id: number;
@Column(columnCharsetCS) @Column(columnCharsetCS)

View File

@ -35,7 +35,7 @@ export class TopPickSendJob extends Job<{
type: 'sort-array', type: 'sort-array',
name: backendTexts.sortBy.name, name: backendTexts.sortBy.name,
description: backendTexts.sortBy.description, description: backendTexts.sortBy.description,
defaultValue: [SortingMethods.descRating], defaultValue: [SortingMethods.descRating, SortingMethods.descPersonCount],
}, { }, {
id: 'pickAmount', id: 'pickAmount',
type: 'number', type: 'number',
@ -48,7 +48,7 @@ export class TopPickSendJob extends Job<{
name: backendTexts.emailTo.name, name: backendTexts.emailTo.name,
description: backendTexts.emailTo.description, description: backendTexts.emailTo.description,
defaultValue: [], defaultValue: [],
}, { }, {
id: 'emailSubject', id: 'emailSubject',
type: 'string', type: 'string',
name: backendTexts.emailSubject.name, name: backendTexts.emailSubject.name,

View File

@ -1,4 +1,4 @@
/** /**
* This version indicates that the sql/entities/*Entity.ts files got changed and the db needs to be recreated * This version indicates that the sql/entities/*Entity.ts files got changed and the db needs to be recreated
*/ */
export const DataStructureVersion = 30; export const DataStructureVersion = 31;

View File

@ -13,6 +13,8 @@ export const PG2ConfMap = {
'.order_descending_rating.pg2conf': SortingMethods.descRating, '.order_descending_rating.pg2conf': SortingMethods.descRating,
'.order_ascending_rating.pg2conf': SortingMethods.ascRating, '.order_ascending_rating.pg2conf': SortingMethods.ascRating,
'.order_random.pg2conf': SortingMethods.random, '.order_random.pg2conf': SortingMethods.random,
'.order_descending_person_count.pg2conf': SortingMethods.descPersonCount,
'.order_ascending_person_count.pg2conf': SortingMethods.descPersonCount,
}, },
}; };

View File

@ -4,10 +4,6 @@ import {
DatePatternSearch, DatePatternSearch,
DistanceSearch, DistanceSearch,
FromDateSearch, FromDateSearch,
MaxRatingSearch,
MaxResolutionSearch,
MinRatingSearch,
MinResolutionSearch,
NegatableSearchQuery, NegatableSearchQuery,
OrientationSearch, OrientationSearch,
ORSearchQuery, ORSearchQuery,
@ -41,6 +37,8 @@ export interface QueryKeywords {
minResolution: string; minResolution: string;
maxRating: string; maxRating: string;
minRating: string; minRating: string;
maxPersonCount: string;
minPersonCount: string;
NSomeOf: string; NSomeOf: string;
someOf: string; someOf: string;
or: string; or: string;
@ -65,8 +63,10 @@ export const defaultQueryKeywords: QueryKeywords = {
to: 'before', to: 'before',
maxRating: 'max-rating', maxRating: 'max-rating',
maxResolution: 'max-resolution',
minRating: 'min-rating', minRating: 'min-rating',
maxPersonCount: 'max-persons',
minPersonCount: 'min-persons',
maxResolution: 'max-resolution',
minResolution: 'min-resolution', minResolution: 'min-resolution',
kmFrom: 'km-from', kmFrom: 'km-from',
@ -303,38 +303,28 @@ export class SearchQueryParser {
} as ToDateSearch; } as ToDateSearch;
} }
if (kwStartsWith(str, this.keywords.minRating)) { const addValueRangeParser = (matcher: string, type: SearchQueryTypes): RangeSearch | undefined => {
return { if (kwStartsWith(str, matcher)) {
type: SearchQueryTypes.min_rating, return {
value: parseInt(str.substring(str.indexOf(':') + 1), 10), type: type,
...(str.startsWith(this.keywords.minRating + '!:') && {negate: true}), // only add if the value is true value: parseInt(str.substring(str.indexOf(':') + 1), 10),
} as MinRatingSearch; ...(str.startsWith(matcher + '!:') && {negate: true}), // only add if the value is true
} } as RangeSearch;
if (kwStartsWith(str, this.keywords.maxRating)) { }
return { };
type: SearchQueryTypes.max_rating,
value: parseInt(str.substring(str.indexOf(':') + 1), 10), const range = addValueRangeParser(this.keywords.minRating, SearchQueryTypes.min_rating) ||
...(str.startsWith(this.keywords.maxRating + '!:') && {negate: true}), // only add if the value is true addValueRangeParser(this.keywords.maxRating, SearchQueryTypes.max_rating) ||
} as MaxRatingSearch; addValueRangeParser(this.keywords.minResolution, SearchQueryTypes.min_resolution) ||
} addValueRangeParser(this.keywords.maxResolution, SearchQueryTypes.max_resolution) ||
if (kwStartsWith(str, this.keywords.minResolution)) { addValueRangeParser(this.keywords.minPersonCount, SearchQueryTypes.min_person_count) ||
return { addValueRangeParser(this.keywords.maxPersonCount, SearchQueryTypes.max_person_count);
type: SearchQueryTypes.min_resolution,
value: parseInt(str.substring(str.indexOf(':') + 1), 10), if (range) {
...(str.startsWith(this.keywords.minResolution + '!:') && { return range;
negate: true,
}), // only add if the value is true
} as MinResolutionSearch;
}
if (kwStartsWith(str, this.keywords.maxResolution)) {
return {
type: SearchQueryTypes.max_resolution,
value: parseInt(str.substring(str.indexOf(':') + 1), 10),
...(str.startsWith(this.keywords.maxResolution + '!:') && {
negate: true,
}), // only add if the value is true
} as MaxResolutionSearch;
} }
if (new RegExp('^\\d*-' + this.keywords.kmFrom + '!?:').test(str)) { if (new RegExp('^\\d*-' + this.keywords.kmFrom + '!?:').test(str)) {
let from = str.slice( let from = str.slice(
new RegExp('^\\d*-' + this.keywords.kmFrom + '!?:').exec(str)[0].length new RegExp('^\\d*-' + this.keywords.kmFrom + '!?:').exec(str)[0].length
@ -528,6 +518,22 @@ export class SearchQueryParser {
? '' ? ''
: (query as RangeSearch).value) : (query as RangeSearch).value)
); );
case SearchQueryTypes.min_person_count:
return (
this.keywords.minPersonCount +
colon +
(isNaN((query as RangeSearch).value)
? ''
: (query as RangeSearch).value)
);
case SearchQueryTypes.max_person_count:
return (
this.keywords.maxPersonCount +
colon +
(isNaN((query as RangeSearch).value)
? ''
: (query as RangeSearch).value)
);
case SearchQueryTypes.min_resolution: case SearchQueryTypes.min_resolution:
return ( return (
this.keywords.minResolution + this.keywords.minResolution +

View File

@ -898,6 +898,7 @@ export class ServerPreviewConfig {
Sorting: SortingMethods[] = [ Sorting: SortingMethods[] = [
SortingMethods.descRating, SortingMethods.descRating,
SortingMethods.descDate, SortingMethods.descDate,
SortingMethods.descPersonCount,
]; ];
} }

View File

@ -14,10 +14,14 @@ export enum SearchQueryTypes {
max_rating, max_rating,
min_resolution, min_resolution,
max_resolution, max_resolution,
min_person_count,
max_person_count,
distance, distance = 50,
orientation, orientation,
date_pattern,
date_pattern = 60,
// TEXT search types // TEXT search types
any_text = 100, any_text = 100,
@ -211,6 +215,17 @@ export interface MaxRatingSearch extends RangeSearch {
value: number; value: number;
} }
export interface MinPersonCountSearch extends RangeSearch {
type: SearchQueryTypes.min_person_count;
value: number;
}
export interface MaxPersonCountSearch extends RangeSearch {
type: SearchQueryTypes.max_person_count;
value: number;
}
export interface MinResolutionSearch extends RangeSearch { export interface MinResolutionSearch extends RangeSearch {
type: SearchQueryTypes.min_resolution; type: SearchQueryTypes.min_resolution;
value: number; // in megapixels value: number; // in megapixels

View File

@ -1,9 +1,15 @@
/**
* Order of these enums determines the order in the UI.
* Keep spaces between the values, so new value can be added in between without changing the existing ones
*/
export enum SortingMethods { export enum SortingMethods {
ascName = 1, ascName = 10,
descName, descName = 11,
ascDate, ascDate = 20,
descDate, descDate = 21,
ascRating, ascRating = 30,
descRating, descRating = 31,
random, ascPersonCount = 40,
descPersonCount = 41,
random = 100 // let's keep random as the last in the UI
} }

View File

@ -9,6 +9,10 @@ export class IconizeSortingMethod implements PipeTransform {
return '<span class="oi oi-sort-ascending"></span><span class="oi oi-star text-bold"></span>'; return '<span class="oi oi-sort-ascending"></span><span class="oi oi-star text-bold"></span>';
case SortingMethods.descRating: case SortingMethods.descRating:
return '<span class="oi oi-sort-descending"></span><span class="oi oi-star text-bold"></span>'; return '<span class="oi oi-sort-descending"></span><span class="oi oi-star text-bold"></span>';
case SortingMethods.ascPersonCount:
return '<span class="oi oi-sort-ascending"></span><span class="oi oi-person text-bold"></span>';
case SortingMethods.descPersonCount:
return '<span class="oi oi-sort-descending"></span><span class="oi oi-person text-bold"></span>';
case SortingMethods.ascName: case SortingMethods.ascName:
return '<span class="oi oi-sort-ascending"></span><strong>A</strong>'; return '<span class="oi oi-sort-ascending"></span><strong>A</strong>';
case SortingMethods.descName: case SortingMethods.descName:

View File

@ -43,6 +43,8 @@ EnumTranslations[SortingMethods[SortingMethods.ascName]] = $localize`ascending n
EnumTranslations[SortingMethods[SortingMethods.descRating]] = $localize`descending rating`; EnumTranslations[SortingMethods[SortingMethods.descRating]] = $localize`descending rating`;
EnumTranslations[SortingMethods[SortingMethods.ascRating]] = $localize`ascending rating`; EnumTranslations[SortingMethods[SortingMethods.ascRating]] = $localize`ascending rating`;
EnumTranslations[SortingMethods[SortingMethods.random]] = $localize`random`; EnumTranslations[SortingMethods[SortingMethods.random]] = $localize`random`;
EnumTranslations[SortingMethods[SortingMethods.ascPersonCount]] = $localize`ascending persons`;
EnumTranslations[SortingMethods[SortingMethods.descPersonCount]] = $localize`descending persons`;
EnumTranslations[NavigationLinkTypes[NavigationLinkTypes.url]] = $localize`Url`; EnumTranslations[NavigationLinkTypes[NavigationLinkTypes.url]] = $localize`Url`;

View File

@ -179,6 +179,18 @@ export class GallerySortingService {
(b.metadata.rating || 0) - (a.metadata.rating || 0) (b.metadata.rating || 0) - (a.metadata.rating || 0)
); );
break; break;
case SortingMethods.ascPersonCount:
c.media.sort(
(a: PhotoDTO, b: PhotoDTO) =>
(a.metadata?.faces?.length || 0) - (b.metadata?.faces?.length || 0)
);
break;
case SortingMethods.descPersonCount:
c.media.sort(
(a: PhotoDTO, b: PhotoDTO) =>
(b.metadata?.faces?.length || 0) - (a.metadata?.faces?.length || 0)
);
break;
case SortingMethods.random: case SortingMethods.random:
this.rndService.setSeed(c.media.length); this.rndService.setSeed(c.media.length);
c.media c.media

View File

@ -29,6 +29,8 @@ export class AutoCompleteService {
k !== this.searchQueryParserService.keywords.NSomeOf && k !== this.searchQueryParserService.keywords.NSomeOf &&
k !== this.searchQueryParserService.keywords.minRating && k !== this.searchQueryParserService.keywords.minRating &&
k !== this.searchQueryParserService.keywords.maxRating && k !== this.searchQueryParserService.keywords.maxRating &&
k !== this.searchQueryParserService.keywords.minPersonCount &&
k !== this.searchQueryParserService.keywords.maxPersonCount &&
k !== this.searchQueryParserService.keywords.every_week && k !== this.searchQueryParserService.keywords.every_week &&
k !== this.searchQueryParserService.keywords.every_month && k !== this.searchQueryParserService.keywords.every_month &&
k !== this.searchQueryParserService.keywords.every_year && k !== this.searchQueryParserService.keywords.every_year &&
@ -91,6 +93,11 @@ export class AutoCompleteService {
this.noACKeywordsMap[this.searchQueryParserService.keywords.maxRating] this.noACKeywordsMap[this.searchQueryParserService.keywords.maxRating]
= SearchQueryTypes.max_rating; = SearchQueryTypes.max_rating;
this.noACKeywordsMap[this.searchQueryParserService.keywords.minPersonCount]
= SearchQueryTypes.min_person_count;
this.noACKeywordsMap[this.searchQueryParserService.keywords.maxPersonCount]
= SearchQueryTypes.max_person_count;
this.noACKeywordsMap[this.searchQueryParserService.keywords.minResolution] this.noACKeywordsMap[this.searchQueryParserService.keywords.minResolution]
= SearchQueryTypes.min_resolution; = SearchQueryTypes.min_resolution;
this.noACKeywordsMap[this.searchQueryParserService.keywords.maxResolution] this.noACKeywordsMap[this.searchQueryParserService.keywords.maxResolution]
@ -321,25 +328,33 @@ export class AutoCompleteService {
} }
} }
// only showing rating recommendations of the full query is typed
const mrKey = this.searchQueryParserService.keywords.minRating + ':'; const addRangeAutoComp = (minStr: string, maxStr: string, minRange: number, maxRange: number) => {
const mxrKey = this.searchQueryParserService.keywords.maxRating + ':'; // only showing rating recommendations of the full query is typed
if (text.current.toLowerCase().startsWith(mrKey)) { const mrKey = minStr + ':';
for (let i = 1; i <= 5; ++i) { const mxrKey = maxStr + ':';
ret.push(generateMatch(mrKey + i)); if (text.current.toLowerCase().startsWith(mrKey)) {
for (let i = minRange; i <= maxRange; ++i) {
ret.push(generateMatch(mrKey + i));
}
} else if (mrKey.startsWith(text.current.toLowerCase())) {
ret.push(generateMatch(mrKey));
} }
} else if (mrKey.startsWith(text.current.toLowerCase())) {
ret.push(generateMatch(mrKey));
}
if (text.current.toLowerCase().startsWith(mxrKey)) { if (text.current.toLowerCase().startsWith(mxrKey)) {
for (let i = 1; i <= 5; ++i) { for (let i = minRange; i <= maxRange; ++i) {
ret.push(generateMatch(mxrKey + i)); ret.push(generateMatch(mxrKey + i));
}
} else if (mxrKey.startsWith(text.current.toLowerCase())) {
ret.push(generateMatch(mxrKey));
} }
} else if (mxrKey.startsWith(text.current.toLowerCase())) { };
ret.push(generateMatch(mxrKey)); addRangeAutoComp(this.searchQueryParserService.keywords.minRating,
} this.searchQueryParserService.keywords.maxRating, 1, 5);
addRangeAutoComp(this.searchQueryParserService.keywords.minPersonCount,
this.searchQueryParserService.keywords.maxPersonCount, 0, 9);
// Date patterns // Date patterns
if (new RegExp('^' + if (new RegExp('^' +

View File

@ -16,8 +16,10 @@ export class SearchQueryParserService {
to: 'before', to: 'before',
landscape: 'landscape', landscape: 'landscape',
maxRating: 'max-rating', maxRating: 'max-rating',
maxResolution: 'max-resolution',
minRating: 'min-rating', minRating: 'min-rating',
minPersonCount: 'min-persons',
maxPersonCount: 'max-persons',
maxResolution: 'max-resolution',
minResolution: 'min-resolution', minResolution: 'min-resolution',
orientation: 'orientation', orientation: 'orientation',

View File

@ -9,8 +9,10 @@ import {
DatePatternSearch, DatePatternSearch,
DistanceSearch, DistanceSearch,
FromDateSearch, FromDateSearch,
MaxPersonCountSearch,
MaxRatingSearch, MaxRatingSearch,
MaxResolutionSearch, MaxResolutionSearch,
MinPersonCountSearch,
MinRatingSearch, MinRatingSearch,
MinResolutionSearch, MinResolutionSearch,
OrientationSearch, OrientationSearch,
@ -1094,6 +1096,83 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
}); });
it('should search person count', async () => {
const sm = new SearchManager();
let query: MinPersonCountSearch | MaxPersonCountSearch = {value: 0, type: SearchQueryTypes.max_person_count} as MaxPersonCountSearch;
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [pFaceLess, v],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
query = ({value: 20, type: SearchQueryTypes.max_person_count} as MaxPersonCountSearch);
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p, p2, pFaceLess, p4, v],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
query = ({value: 20, negate: true, type: SearchQueryTypes.max_person_count} as MaxPersonCountSearch);
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
query = ({value: 4, type: SearchQueryTypes.max_person_count} as MaxPersonCountSearch);
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p2, p4, pFaceLess, v],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
query = ({value: 2, type: SearchQueryTypes.min_person_count} as MinPersonCountSearch);
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p, p2, p4],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
query = ({value: 6, type: SearchQueryTypes.min_person_count} as MinPersonCountSearch);
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [p],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
query = ({value: 2, negate: true, type: SearchQueryTypes.min_person_count} as MinPersonCountSearch);
expect(Utils.clone(await sm.search(query)))
.to.deep.equalInAnyOrder(removeDir({
searchQuery: query,
directories: [],
media: [v, pFaceLess],
metaFile: [],
resultOverflow: false
} as SearchResultDTO));
});
it('should search resolution', async () => { it('should search resolution', async () => {
const sm = new SearchManager(); const sm = new SearchManager();

View File

@ -5,8 +5,9 @@ import {
DatePatternSearch, DatePatternSearch,
DistanceSearch, DistanceSearch,
FromDateSearch, FromDateSearch,
MaxPersonCountSearch,
MaxRatingSearch, MaxRatingSearch,
MaxResolutionSearch, MaxResolutionSearch, MinPersonCountSearch,
MinRatingSearch, MinRatingSearch,
MinResolutionSearch, MinResolutionSearch,
OrientationSearch, OrientationSearch,
@ -98,6 +99,12 @@ describe('SearchQueryParser', () => {
check({type: SearchQueryTypes.min_rating, value: 10, negate: true} as MinRatingSearch); check({type: SearchQueryTypes.min_rating, value: 10, negate: true} as MinRatingSearch);
check({type: SearchQueryTypes.max_rating, value: 1, negate: true} as MaxRatingSearch); check({type: SearchQueryTypes.max_rating, value: 1, negate: true} as MaxRatingSearch);
}); });
it('Person count search', () => {
check({type: SearchQueryTypes.min_person_count, value: 10} as MinPersonCountSearch);
check({type: SearchQueryTypes.max_person_count, value: 1} as MaxPersonCountSearch);
check({type: SearchQueryTypes.min_person_count, value: 10, negate: true} as MinPersonCountSearch);
check({type: SearchQueryTypes.max_person_count, value: 1, negate: true} as MaxPersonCountSearch);
});
it('Resolution search', () => { it('Resolution search', () => {
check({type: SearchQueryTypes.min_resolution, value: 10} as MinResolutionSearch); check({type: SearchQueryTypes.min_resolution, value: 10} as MinResolutionSearch);
check({type: SearchQueryTypes.max_resolution, value: 5} as MaxResolutionSearch); check({type: SearchQueryTypes.max_resolution, value: 5} as MaxResolutionSearch);