diff --git a/src/backend/model/database/IndexingManager.ts b/src/backend/model/database/IndexingManager.ts
index df26b1d9..791dde97 100644
--- a/src/backend/model/database/IndexingManager.ts
+++ b/src/backend/model/database/IndexingManager.ts
@@ -367,6 +367,7 @@ export class IndexingManager {
),
];
}
+ (media[i].metadata as PhotoMetadataEntity).personsLength = (media[i].metadata as PhotoMetadataEntity)?.persons?.length || 0;
if (mediaItem == null) {
diff --git a/src/backend/model/database/SearchManager.ts b/src/backend/model/database/SearchManager.ts
index 5e60b33c..61249bf1 100644
--- a/src/backend/model/database/SearchManager.ts
+++ b/src/backend/model/database/SearchManager.ts
@@ -13,9 +13,9 @@ import {
DatePatternFrequency,
DatePatternSearch,
DistanceSearch,
- FromDateSearch,
+ FromDateSearch, MaxPersonCountSearch,
MaxRatingSearch,
- MaxResolutionSearch,
+ MaxResolutionSearch, MinPersonCountSearch,
MinRatingSearch,
MinResolutionSearch,
OrientationSearch,
@@ -379,6 +379,12 @@ export class SearchManager {
case SortingMethods.ascName:
query.addOrderBy('media.name', 'ASC');
break;
+ case SortingMethods.descPersonCount:
+ query.addOrderBy('media.metadata.personsLength', 'DESC');
+ break;
+ case SortingMethods.ascPersonCount:
+ query.addOrderBy('media.metadata.personsLength', 'ASC');
+ break;
case SortingMethods.random:
if (Config.Database.type === DatabaseType.mysql) {
query.groupBy('RAND(), media.id');
@@ -639,6 +645,52 @@ export class SearchManager {
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:
if (directoryOnly) {
throw new Error('not supported in directoryOnly mode');
diff --git a/src/backend/model/database/enitites/MediaEntity.ts b/src/backend/model/database/enitites/MediaEntity.ts
index 46d0e317..fb5495e7 100644
--- a/src/backend/model/database/enitites/MediaEntity.ts
+++ b/src/backend/model/database/enitites/MediaEntity.ts
@@ -1,26 +1,9 @@
-import {
- Column,
- Entity,
- Index,
- ManyToOne,
- OneToMany,
- 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';
+import {Column, Entity, Index, ManyToOne, OneToMany, 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 {
@Column('int')
@@ -31,7 +14,7 @@ export class MediaDimensionEntity implements MediaDimension {
}
export class CameraMetadataEntity implements CameraMetadata {
- @Column('int', { nullable: true, unsigned: true })
+ @Column('int', {nullable: true, unsigned: true})
ISO: number;
@Column({
@@ -50,23 +33,23 @@ export class CameraMetadataEntity implements CameraMetadata {
})
make: string;
- @Column('float', { nullable: true })
+ @Column('float', {nullable: true})
fStop: number;
- @Column('float', { nullable: true })
+ @Column('float', {nullable: true})
exposure: number;
- @Column('float', { nullable: true })
+ @Column('float', {nullable: true})
focalLength: number;
- @Column('text', { nullable: true })
+ @Column('text', {nullable: true})
lens: string;
}
export class GPSMetadataEntity implements GPSMetadata {
- @Column('float', { nullable: true })
+ @Column('float', {nullable: true})
latitude: number;
- @Column('float', { nullable: true })
+ @Column('float', {nullable: true})
longitude: number;
}
@@ -120,7 +103,7 @@ export class MediaMetadataEntity implements MediaMetadata {
@Index()
creationDate: number;
- @Column('int', { unsigned: true })
+ @Column('int', {unsigned: true})
fileSize: number;
@Column({
@@ -136,7 +119,7 @@ export class MediaMetadataEntity implements MediaMetadata {
@Column((type) => PositionMetaDataEntity)
positionData: PositionMetaDataEntity;
- @Column('tinyint', { unsigned: true })
+ @Column('tinyint', {unsigned: true})
@Index()
rating: 0 | 1 | 2 | 3 | 4 | 5;
@@ -144,10 +127,11 @@ export class MediaMetadataEntity implements MediaMetadata {
personJunction: PersonJunctionTable[];
@Column({
- type:'simple-json',
+ type: 'simple-json',
nullable: true,
charset: columnCharsetCS.charset,
- collation: columnCharsetCS.collation})
+ collation: columnCharsetCS.collation
+ })
faces: FaceRegion[];
/**
@@ -162,20 +146,32 @@ export class MediaMetadataEntity implements MediaMetadata {
})
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;
- @Column('int', { unsigned: true })
+ @Column('int', {unsigned: true})
duration: number;
}
// TODO: fix inheritance once its working in typeorm
@Entity()
@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 {
@Index()
- @PrimaryGeneratedColumn({ unsigned: true })
+ @PrimaryGeneratedColumn({unsigned: true})
id: number;
@Column(columnCharsetCS)
diff --git a/src/backend/model/jobs/jobs/TopPickSendJob.ts b/src/backend/model/jobs/jobs/TopPickSendJob.ts
index 4e1ff8da..bd47a567 100644
--- a/src/backend/model/jobs/jobs/TopPickSendJob.ts
+++ b/src/backend/model/jobs/jobs/TopPickSendJob.ts
@@ -35,7 +35,7 @@ export class TopPickSendJob extends Job<{
type: 'sort-array',
name: backendTexts.sortBy.name,
description: backendTexts.sortBy.description,
- defaultValue: [SortingMethods.descRating],
+ defaultValue: [SortingMethods.descRating, SortingMethods.descPersonCount],
}, {
id: 'pickAmount',
type: 'number',
@@ -48,7 +48,7 @@ export class TopPickSendJob extends Job<{
name: backendTexts.emailTo.name,
description: backendTexts.emailTo.description,
defaultValue: [],
- }, {
+ }, {
id: 'emailSubject',
type: 'string',
name: backendTexts.emailSubject.name,
diff --git a/src/common/DataStructureVersion.ts b/src/common/DataStructureVersion.ts
index 15f48ded..472faefa 100644
--- a/src/common/DataStructureVersion.ts
+++ b/src/common/DataStructureVersion.ts
@@ -1,4 +1,4 @@
/**
* 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;
diff --git a/src/common/PG2ConfMap.ts b/src/common/PG2ConfMap.ts
index 8a503516..54b63696 100644
--- a/src/common/PG2ConfMap.ts
+++ b/src/common/PG2ConfMap.ts
@@ -13,6 +13,8 @@ export const PG2ConfMap = {
'.order_descending_rating.pg2conf': SortingMethods.descRating,
'.order_ascending_rating.pg2conf': SortingMethods.ascRating,
'.order_random.pg2conf': SortingMethods.random,
+ '.order_descending_person_count.pg2conf': SortingMethods.descPersonCount,
+ '.order_ascending_person_count.pg2conf': SortingMethods.descPersonCount,
},
};
diff --git a/src/common/SearchQueryParser.ts b/src/common/SearchQueryParser.ts
index 225e32a8..ef76f9ae 100644
--- a/src/common/SearchQueryParser.ts
+++ b/src/common/SearchQueryParser.ts
@@ -4,10 +4,6 @@ import {
DatePatternSearch,
DistanceSearch,
FromDateSearch,
- MaxRatingSearch,
- MaxResolutionSearch,
- MinRatingSearch,
- MinResolutionSearch,
NegatableSearchQuery,
OrientationSearch,
ORSearchQuery,
@@ -41,6 +37,8 @@ export interface QueryKeywords {
minResolution: string;
maxRating: string;
minRating: string;
+ maxPersonCount: string;
+ minPersonCount: string;
NSomeOf: string;
someOf: string;
or: string;
@@ -65,8 +63,10 @@ export const defaultQueryKeywords: QueryKeywords = {
to: 'before',
maxRating: 'max-rating',
- maxResolution: 'max-resolution',
minRating: 'min-rating',
+ maxPersonCount: 'max-persons',
+ minPersonCount: 'min-persons',
+ maxResolution: 'max-resolution',
minResolution: 'min-resolution',
kmFrom: 'km-from',
@@ -303,38 +303,28 @@ export class SearchQueryParser {
} as ToDateSearch;
}
- if (kwStartsWith(str, this.keywords.minRating)) {
- return {
- type: SearchQueryTypes.min_rating,
- value: parseInt(str.substring(str.indexOf(':') + 1), 10),
- ...(str.startsWith(this.keywords.minRating + '!:') && {negate: true}), // only add if the value is true
- } as MinRatingSearch;
- }
- if (kwStartsWith(str, this.keywords.maxRating)) {
- return {
- type: SearchQueryTypes.max_rating,
- value: parseInt(str.substring(str.indexOf(':') + 1), 10),
- ...(str.startsWith(this.keywords.maxRating + '!:') && {negate: true}), // only add if the value is true
- } as MaxRatingSearch;
- }
- if (kwStartsWith(str, this.keywords.minResolution)) {
- return {
- type: SearchQueryTypes.min_resolution,
- value: parseInt(str.substring(str.indexOf(':') + 1), 10),
- ...(str.startsWith(this.keywords.minResolution + '!:') && {
- 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;
+ const addValueRangeParser = (matcher: string, type: SearchQueryTypes): RangeSearch | undefined => {
+ if (kwStartsWith(str, matcher)) {
+ return {
+ type: type,
+ value: parseInt(str.substring(str.indexOf(':') + 1), 10),
+ ...(str.startsWith(matcher + '!:') && {negate: true}), // only add if the value is true
+ } as RangeSearch;
+ }
+ };
+
+ const range = addValueRangeParser(this.keywords.minRating, SearchQueryTypes.min_rating) ||
+ addValueRangeParser(this.keywords.maxRating, SearchQueryTypes.max_rating) ||
+ addValueRangeParser(this.keywords.minResolution, SearchQueryTypes.min_resolution) ||
+ addValueRangeParser(this.keywords.maxResolution, SearchQueryTypes.max_resolution) ||
+ addValueRangeParser(this.keywords.minPersonCount, SearchQueryTypes.min_person_count) ||
+ addValueRangeParser(this.keywords.maxPersonCount, SearchQueryTypes.max_person_count);
+
+ if (range) {
+ return range;
}
+
+
if (new RegExp('^\\d*-' + this.keywords.kmFrom + '!?:').test(str)) {
let from = str.slice(
new RegExp('^\\d*-' + this.keywords.kmFrom + '!?:').exec(str)[0].length
@@ -528,6 +518,22 @@ export class SearchQueryParser {
? ''
: (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:
return (
this.keywords.minResolution +
diff --git a/src/common/config/private/PrivateConfig.ts b/src/common/config/private/PrivateConfig.ts
index e9b60a19..8c8aa6e5 100644
--- a/src/common/config/private/PrivateConfig.ts
+++ b/src/common/config/private/PrivateConfig.ts
@@ -898,6 +898,7 @@ export class ServerPreviewConfig {
Sorting: SortingMethods[] = [
SortingMethods.descRating,
SortingMethods.descDate,
+ SortingMethods.descPersonCount,
];
}
diff --git a/src/common/entities/SearchQueryDTO.ts b/src/common/entities/SearchQueryDTO.ts
index f08fb51b..18a61f15 100644
--- a/src/common/entities/SearchQueryDTO.ts
+++ b/src/common/entities/SearchQueryDTO.ts
@@ -14,10 +14,14 @@ export enum SearchQueryTypes {
max_rating,
min_resolution,
max_resolution,
+ min_person_count,
+ max_person_count,
- distance,
+ distance = 50,
orientation,
- date_pattern,
+
+
+ date_pattern = 60,
// TEXT search types
any_text = 100,
@@ -211,6 +215,17 @@ export interface MaxRatingSearch extends RangeSearch {
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 {
type: SearchQueryTypes.min_resolution;
value: number; // in megapixels
diff --git a/src/common/entities/SortingMethods.ts b/src/common/entities/SortingMethods.ts
index f5533b3d..5e4c49b1 100644
--- a/src/common/entities/SortingMethods.ts
+++ b/src/common/entities/SortingMethods.ts
@@ -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 {
- ascName = 1,
- descName,
- ascDate,
- descDate,
- ascRating,
- descRating,
- random,
+ ascName = 10,
+ descName = 11,
+ ascDate = 20,
+ descDate = 21,
+ ascRating = 30,
+ descRating = 31,
+ ascPersonCount = 40,
+ descPersonCount = 41,
+ random = 100 // let's keep random as the last in the UI
}
diff --git a/src/frontend/app/pipes/IconizeSortingMethod.ts b/src/frontend/app/pipes/IconizeSortingMethod.ts
index 5f5f3c8b..d9d5c4dc 100644
--- a/src/frontend/app/pipes/IconizeSortingMethod.ts
+++ b/src/frontend/app/pipes/IconizeSortingMethod.ts
@@ -9,6 +9,10 @@ export class IconizeSortingMethod implements PipeTransform {
return '';
case SortingMethods.descRating:
return '';
+ case SortingMethods.ascPersonCount:
+ return '';
+ case SortingMethods.descPersonCount:
+ return '';
case SortingMethods.ascName:
return 'A';
case SortingMethods.descName:
diff --git a/src/frontend/app/ui/EnumTranslations.ts b/src/frontend/app/ui/EnumTranslations.ts
index f4da98d1..17c6c89d 100644
--- a/src/frontend/app/ui/EnumTranslations.ts
+++ b/src/frontend/app/ui/EnumTranslations.ts
@@ -43,6 +43,8 @@ EnumTranslations[SortingMethods[SortingMethods.ascName]] = $localize`ascending n
EnumTranslations[SortingMethods[SortingMethods.descRating]] = $localize`descending rating`;
EnumTranslations[SortingMethods[SortingMethods.ascRating]] = $localize`ascending rating`;
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`;
diff --git a/src/frontend/app/ui/gallery/navigator/sorting.service.ts b/src/frontend/app/ui/gallery/navigator/sorting.service.ts
index 3c08b601..f5362805 100644
--- a/src/frontend/app/ui/gallery/navigator/sorting.service.ts
+++ b/src/frontend/app/ui/gallery/navigator/sorting.service.ts
@@ -179,6 +179,18 @@ export class GallerySortingService {
(b.metadata.rating || 0) - (a.metadata.rating || 0)
);
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:
this.rndService.setSeed(c.media.length);
c.media
diff --git a/src/frontend/app/ui/gallery/search/autocomplete.service.ts b/src/frontend/app/ui/gallery/search/autocomplete.service.ts
index 266469b6..8a9431cf 100644
--- a/src/frontend/app/ui/gallery/search/autocomplete.service.ts
+++ b/src/frontend/app/ui/gallery/search/autocomplete.service.ts
@@ -29,6 +29,8 @@ export class AutoCompleteService {
k !== this.searchQueryParserService.keywords.NSomeOf &&
k !== this.searchQueryParserService.keywords.minRating &&
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_month &&
k !== this.searchQueryParserService.keywords.every_year &&
@@ -91,6 +93,11 @@ export class AutoCompleteService {
this.noACKeywordsMap[this.searchQueryParserService.keywords.maxRating]
= 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]
= SearchQueryTypes.min_resolution;
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 mxrKey = this.searchQueryParserService.keywords.maxRating + ':';
- if (text.current.toLowerCase().startsWith(mrKey)) {
- for (let i = 1; i <= 5; ++i) {
- ret.push(generateMatch(mrKey + i));
+
+ const addRangeAutoComp = (minStr: string, maxStr: string, minRange: number, maxRange: number) => {
+ // only showing rating recommendations of the full query is typed
+ const mrKey = minStr + ':';
+ const mxrKey = maxStr + ':';
+ 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)) {
- for (let i = 1; i <= 5; ++i) {
- ret.push(generateMatch(mxrKey + i));
+ if (text.current.toLowerCase().startsWith(mxrKey)) {
+ for (let i = minRange; i <= maxRange; ++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
if (new RegExp('^' +
diff --git a/src/frontend/app/ui/gallery/search/search-query-parser.service.ts b/src/frontend/app/ui/gallery/search/search-query-parser.service.ts
index 320fd7d0..abfdc91c 100644
--- a/src/frontend/app/ui/gallery/search/search-query-parser.service.ts
+++ b/src/frontend/app/ui/gallery/search/search-query-parser.service.ts
@@ -16,8 +16,10 @@ export class SearchQueryParserService {
to: 'before',
landscape: 'landscape',
maxRating: 'max-rating',
- maxResolution: 'max-resolution',
minRating: 'min-rating',
+ minPersonCount: 'min-persons',
+ maxPersonCount: 'max-persons',
+ maxResolution: 'max-resolution',
minResolution: 'min-resolution',
orientation: 'orientation',
diff --git a/test/backend/unit/model/sql/SearchManager.spec.ts b/test/backend/unit/model/sql/SearchManager.spec.ts
index 3837aaee..d35d3770 100644
--- a/test/backend/unit/model/sql/SearchManager.spec.ts
+++ b/test/backend/unit/model/sql/SearchManager.spec.ts
@@ -9,8 +9,10 @@ import {
DatePatternSearch,
DistanceSearch,
FromDateSearch,
+ MaxPersonCountSearch,
MaxRatingSearch,
MaxResolutionSearch,
+ MinPersonCountSearch,
MinRatingSearch,
MinResolutionSearch,
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 () => {
const sm = new SearchManager();
diff --git a/test/common/unit/SearchQueryParser.ts b/test/common/unit/SearchQueryParser.ts
index f52c71cc..04497dd0 100644
--- a/test/common/unit/SearchQueryParser.ts
+++ b/test/common/unit/SearchQueryParser.ts
@@ -5,8 +5,9 @@ import {
DatePatternSearch,
DistanceSearch,
FromDateSearch,
+ MaxPersonCountSearch,
MaxRatingSearch,
- MaxResolutionSearch,
+ MaxResolutionSearch, MinPersonCountSearch,
MinRatingSearch,
MinResolutionSearch,
OrientationSearch,
@@ -98,6 +99,12 @@ describe('SearchQueryParser', () => {
check({type: SearchQueryTypes.min_rating, value: 10, negate: true} as MinRatingSearch);
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', () => {
check({type: SearchQueryTypes.min_resolution, value: 10} as MinResolutionSearch);
check({type: SearchQueryTypes.max_resolution, value: 5} as MaxResolutionSearch);