diff --git a/package-lock.json b/package-lock.json index 0e2f15eb..5de62607 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "pigallery2", - "version": "1.8.2", + "version": "1.8.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3344,6 +3344,15 @@ "integrity": "sha512-4nhBPStMK04rruRVtVc6cDqhu7S9GZai0fpXgPXrFpcPX6Xul8xnrjSdGB4KPBVYG/R5+fXWdCM8qBoiULWGPQ==", "dev": true }, + "@types/node-geocoder": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/@types/node-geocoder/-/node-geocoder-3.24.1.tgz", + "integrity": "sha512-NpkCX6ooS3Fc6fjVVCWUDcq7/0siDU6zVu+Q29neHLXOGkRc9VmLvLTl3yeaDRatdpAjkD9O7hOY67zrf7WP9w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/q": { "version": "0.0.32", "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", @@ -4702,8 +4711,7 @@ "bluebird": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", - "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==", - "dev": true + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" }, "bmp-js": { "version": "0.1.0", @@ -13607,6 +13615,11 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz", "integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg==" }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "node-fetch-npm": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", @@ -13624,6 +13637,17 @@ "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", "dev": true }, + "node-geocoder": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/node-geocoder/-/node-geocoder-3.27.0.tgz", + "integrity": "sha512-fNMi9smx56wFhG+2sd0qVsq5RgNlkUuQQ7UWqPwynoMb0GjxSP9joAn8wah4YDv6UzZu3b41cNmd0BglEPkC+Q==", + "requires": { + "bluebird": "^3.5.2", + "node-fetch": "^2.6.0", + "request": "^2.88.0", + "request-promise": "^4.2.2" + } + }, "node-gyp": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", @@ -16502,6 +16526,32 @@ } } }, + "request-promise": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz", + "integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==", + "requires": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "requires": { + "lodash": "^4.17.19" + }, + "dependencies": { + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + } + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -17840,6 +17890,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", diff --git a/package.json b/package.json index 23a838e7..1e7480c4 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "image-size": "0.9.3", "jimp": "0.16.1", "locale": "0.1.0", + "node-geocoder": "^3.27.0", "reflect-metadata": "0.1.13", "sharp": "0.23.4", "sqlite3": "5.0.0", @@ -84,6 +85,7 @@ "@types/jasmine": "3.6.2", "@types/jsonwebtoken": "8.5.0", "@types/node": "14.14.19", + "@types/node-geocoder": "^3.24.1", "@types/sharp": "0.26.1", "@types/winston": "2.4.4", "@types/xml2js": "0.4.7", diff --git a/src/backend/exceptions/LocationLookupException.ts b/src/backend/exceptions/LocationLookupException.ts new file mode 100644 index 00000000..140d9815 --- /dev/null +++ b/src/backend/exceptions/LocationLookupException.ts @@ -0,0 +1,6 @@ +export class LocationLookupException extends Error { + + constructor(message: string, public location: string) { + super(message); + } +} diff --git a/src/backend/middlewares/GalleryMWs.ts b/src/backend/middlewares/GalleryMWs.ts index 47ea8dc7..51cf2566 100644 --- a/src/backend/middlewares/GalleryMWs.ts +++ b/src/backend/middlewares/GalleryMWs.ts @@ -15,6 +15,7 @@ import {Utils} from '../../common/Utils'; import {QueryParams} from '../../common/QueryParams'; import {VideoProcessing} from '../model/fileprocessing/VideoProcessing'; import {SearchQueryDTO, SearchQueryTypes} from '../../common/entities/SearchQueryDTO'; +import {LocationLookupException} from '../exceptions/LocationLookupException'; export class GalleryMWs { @@ -167,6 +168,9 @@ export class GalleryMWs { req.resultPipe = new ContentWrapper(null, result); return next(); } catch (err) { + if (err instanceof LocationLookupException) { + return next(new ErrorDTO(ErrorCodes.LocationLookUp_ERROR, 'Cannot find location: ' + err.location, err)); + } return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during searching', err)); } } diff --git a/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts b/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts index e4df1afc..c482a4c6 100644 --- a/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts +++ b/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts @@ -138,7 +138,6 @@ export class ThumbnailGeneratorMWs { private static addThInfoTODir(directory: DirectoryDTO) { - console.log(directory.path, directory.name); if (typeof directory.media !== 'undefined') { ThumbnailGeneratorMWs.addThInfoToPhotos(directory.media); } diff --git a/src/backend/model/database/LocationManager.ts b/src/backend/model/database/LocationManager.ts index 122fb9b8..093e0d66 100644 --- a/src/backend/model/database/LocationManager.ts +++ b/src/backend/model/database/LocationManager.ts @@ -1,10 +1,30 @@ import {GPSMetadata} from '../../../common/entities/PhotoDTO'; - +import * as NodeGeocoder from 'node-geocoder'; +import {LocationLookupException} from '../../exceptions/LocationLookupException'; +import {Utils} from '../../../common/Utils'; export class LocationManager { + readonly geocoder: NodeGeocoder.Geocoder; + cache = new Utils.LRU(100); + + constructor() { + this.geocoder = NodeGeocoder({provider: 'openstreetmap'}); + } async getGPSData(text: string): Promise { - throw new Error('TODO implement'); + if (!this.cache.get(text)) { + + const ret = await this.geocoder.geocode(text); + if (ret.length < 1) { + throw new LocationLookupException('Cannot find location:' + text, text); + } + this.cache.set(text, { + latitude: ret[0].latitude, + longitude: ret[0].longitude + }); + } + + return this.cache.get(text); } } diff --git a/src/backend/model/database/sql/GalleryManager.ts b/src/backend/model/database/sql/GalleryManager.ts index 7ca711dd..d410d901 100644 --- a/src/backend/model/database/sql/GalleryManager.ts +++ b/src/backend/model/database/sql/GalleryManager.ts @@ -236,7 +236,6 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { if (dir.preview) { dir.preview.readyThumbnails = []; dir.preview.readyIcon = false; - console.log(dir.preview.directory); } } diff --git a/src/backend/model/database/sql/SearchManager.ts b/src/backend/model/database/sql/SearchManager.ts index 0d2f35b3..847d3d36 100644 --- a/src/backend/model/database/sql/SearchManager.ts +++ b/src/backend/model/database/sql/SearchManager.ts @@ -84,7 +84,7 @@ export class SearchManager implements ISearchManager { .map(r => r.name), SearchQueryTypes.person)); } - if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.position) { + if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.position || type === SearchQueryTypes.distance) { (await photoRepository .createQueryBuilder('photo') .select('photo.metadata.positionData.country as country, ' + @@ -99,7 +99,8 @@ export class SearchManager implements ISearchManager { .map(pm => >[pm.city || '', pm.country || '', pm.state || '']) .forEach(positions => { result = result.concat(this.encapsulateAutoComplete(positions - .filter(p => p.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchQueryTypes.position)); + .filter(p => p.toLowerCase().indexOf(text.toLowerCase()) !== -1), + type === SearchQueryTypes.distance ? type : SearchQueryTypes.position)); }); } @@ -241,12 +242,18 @@ export class SearchManager implements ISearchManager { latDelta = (1 / ((2 * Math.PI / 360) * earth)), // 1 km in degree lonDelta = (1 / ((2 * Math.PI / 360) * earth)); // 1 km in degree - const minLat = (query).from.GPSData.latitude - ((query).distance * latDelta), - maxLat = (query).from.GPSData.latitude + ((query).distance * latDelta), - minLon = (query).from.GPSData.latitude - - ((query).distance * lonDelta) / Math.cos(minLat * (Math.PI / 180)), - maxLon = (query).from.GPSData.latitude + - ((query).distance * lonDelta) / Math.cos(maxLat * (Math.PI / 180)); + // TODO: properly handle latitude / longitude boundaries + const trimRange = (value: number, min: number, max: number) => { + return Math.min(Math.max(value, min), max); + }; + + const minLat = trimRange((query).from.GPSData.latitude - ((query).distance * latDelta), -90, 90), + maxLat = trimRange((query).from.GPSData.latitude + ((query).distance * latDelta), -90, 90), + minLon = trimRange((query).from.GPSData.longitude - + ((query).distance * lonDelta) / Math.cos(minLat * (Math.PI / 180)), -180, 180), + maxLon = trimRange((query).from.GPSData.longitude + + ((query).distance * lonDelta) / Math.cos(maxLat * (Math.PI / 180)), -180, 180); + return new Brackets(q => { const textParam: any = {}; diff --git a/src/common/SearchQueryParser.ts b/src/common/SearchQueryParser.ts index 9cc801bd..ff3f484c 100644 --- a/src/common/SearchQueryParser.ts +++ b/src/common/SearchQueryParser.ts @@ -146,7 +146,7 @@ export class SearchQueryParser { this.keywords.someOf + ':' : new RegExp('^\\d*-' + this.keywords.NSomeOf + ':').exec(str)[0]; let tmpList: any = this.parse(str.slice(prefix.length + 1, -1), false); // trim brackets - // console.log(JSON.stringify(tmpList, null, 4)); + const unfoldList = (q: SearchListQuery): SearchQueryDTO[] => { if (q.list) { if (q.type === SearchQueryTypes.UNKNOWN_RELATION) { @@ -207,7 +207,8 @@ export class SearchQueryParser { } if (new RegExp('^\\d*-' + this.keywords.kmFrom + ':').test(str)) { let from = str.slice(new RegExp('^\\d*-' + this.keywords.kmFrom + ':').exec(str)[0].length); - if (from.charAt(0) === '(' && from.charAt(from.length - 1) === ')') { + if ((from.charAt(0) === '(' && from.charAt(from.length - 1) === ')') || + (from.charAt(0) === '"' && from.charAt(from.length - 1) === '"')) { from = from.slice(1, from.length - 1); } return { diff --git a/src/common/Utils.ts b/src/common/Utils.ts index b2bb0f7e..1329f288 100644 --- a/src/common/Utils.ts +++ b/src/common/Utils.ts @@ -268,3 +268,35 @@ export class Utils { } } + +export namespace Utils { + export class LRU { + data: { [key: string]: { value: V, usage: number } } = {}; + + + constructor(public readonly size: number) { + } + + set(key: string, value: V): void { + this.data[key] = {usage: Date.now(), value: value}; + if (Object.keys(this.data).length > this.size) { + let oldestK = key; + let oldest = this.data[oldestK].usage; + for (const k in this.data) { + if (this.data[k].usage < oldest) { + oldestK = k; + oldest = this.data[oldestK].usage; + } + } + delete this.data[oldestK]; + } + } + + get(key: string): V { + if (!this.data[key]) { + return; + } + return this.data[key].value; + } + } +} diff --git a/src/common/entities/Error.ts b/src/common/entities/Error.ts index 38c8dc43..0fc322b3 100644 --- a/src/common/entities/Error.ts +++ b/src/common/entities/Error.ts @@ -22,7 +22,8 @@ export enum ErrorCodes { SETTINGS_ERROR = 13, TASK_ERROR = 14, - JOB_ERROR = 15 + JOB_ERROR = 15, + LocationLookUp_ERROR = 16, } export class ErrorDTO { diff --git a/src/frontend/app/ui/gallery/gallery.component.html b/src/frontend/app/ui/gallery/gallery.component.html index 17357c02..c9e93e77 100644 --- a/src/frontend/app/ui/gallery/gallery.component.html +++ b/src/frontend/app/ui/gallery/gallery.component.html @@ -27,40 +27,55 @@ -
- +
+ + + + + - + - - -
- -
- - + - + + - - + + + + + + + + + + + + + + +
+ && !_galleryService.content.value.searchResult + && !_galleryService.content.value.error">
diff --git a/src/frontend/app/ui/gallery/gallery.service.ts b/src/frontend/app/ui/gallery/gallery.service.ts index 2d5bf509..5b551d04 100644 --- a/src/frontend/app/ui/gallery/gallery.service.ts +++ b/src/frontend/app/ui/gallery/gallery.service.ts @@ -11,12 +11,13 @@ import {SortingMethods} from '../../../../common/entities/SortingMethods'; import {QueryParams} from '../../../../common/QueryParams'; import {PG2ConfMap} from '../../../../common/PG2ConfMap'; import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO'; +import {ErrorCodes} from '../../../../common/entities/Error'; @Injectable() export class GalleryService { - public content: BehaviorSubject; + public content: BehaviorSubject; public sorting: BehaviorSubject; lastRequest: { directory: string } = { directory: null @@ -29,7 +30,7 @@ export class GalleryService { private galleryCacheService: GalleryCacheService, private _shareService: ShareService, private navigationService: NavigationService) { - this.content = new BehaviorSubject(new ContentWrapper()); + this.content = new BehaviorSubject(new ContentWrapperWithError()); this.sorting = new BehaviorSubject(Config.Client.Other.defaultPhotoSortingMethod); } @@ -56,7 +57,7 @@ export class GalleryService { } - setContent(content: ContentWrapper): void { + setContent(content: ContentWrapperWithError): void { this.content.next(content); if (content.directory) { const sort = this.galleryCacheService.getSorting(content.directory); @@ -70,7 +71,7 @@ export class GalleryService { public async loadDirectory(directoryName: string): Promise { - const content = new ContentWrapper(); + const content = new ContentWrapperWithError(); content.directory = this.galleryCacheService.getDirectory(directoryName); content.searchResult = null; @@ -93,7 +94,7 @@ export class GalleryService { } try { - const cw = await this.networkService.getJson('/gallery/content/' + directoryName, params); + const cw = await this.networkService.getJson('/gallery/content/' + directoryName, params); if (!cw || cw.notModified === true) { @@ -124,12 +125,20 @@ export class GalleryService { this.ongoingSearch = query; - this.setContent(new ContentWrapper()); - const cw = new ContentWrapper(); + this.setContent(new ContentWrapperWithError()); + const cw = new ContentWrapperWithError(); cw.searchResult = this.galleryCacheService.getSearch(query); if (cw.searchResult == null) { - cw.searchResult = (await this.networkService.getJson('/search/' + query)).searchResult; - this.galleryCacheService.setSearch(query, cw.searchResult); + try { + cw.searchResult = (await this.networkService.getJson('/search/' + query)).searchResult; + this.galleryCacheService.setSearch(query, cw.searchResult); + } catch (e) { + if (e.code === ErrorCodes.LocationLookUp_ERROR) { + cw.error = 'Cannot find location: ' + e.message; + } else { + throw e; + } + } } if (this.ongoingSearch !== query) { @@ -146,3 +155,8 @@ export class GalleryService { } + + +export class ContentWrapperWithError extends ContentWrapper { + public error: string; +} diff --git a/src/frontend/app/ui/gallery/search/autocomplete.service.ts b/src/frontend/app/ui/gallery/search/autocomplete.service.ts index 474942dc..a1c3d571 100644 --- a/src/frontend/app/ui/gallery/search/autocomplete.service.ts +++ b/src/frontend/app/ui/gallery/search/autocomplete.service.ts @@ -12,7 +12,6 @@ import {SearchQueryParser} from '../../../../../common/SearchQueryParser'; export class AutoCompleteService { private keywords: string[] = []; - private relationKeywords: string[] = []; private textSearchKeywordsMap: { [key: string]: SearchQueryTypes } = {}; constructor(private _networkService: NetworkService, @@ -29,7 +28,11 @@ export class AutoCompleteService { this.keywords.push(this._searchQueryParserService.keywords.and); this.keywords.push(this._searchQueryParserService.keywords.or); for (let i = 0; i < 10; i++) { - this.keywords.push(i + this._searchQueryParserService.keywords.NSomeOf); + this.keywords.push(i + '-' + this._searchQueryParserService.keywords.NSomeOf + ':( )'); + } + + for (let i = 0; i < 10; i++) { + this.keywords.push(i + '-' + this._searchQueryParserService.keywords.kmFrom + ':'); } this.keywords.push(this._searchQueryParserService.keywords.to + ':' + @@ -55,11 +58,11 @@ export class AutoCompleteService { if (searchText === '') { return items; } - this.typedAutoComplete(searchText, type, items); + this.typedAutoComplete(searchText, text.current, type, items); return items; } - public typedAutoComplete(text: string, type: SearchQueryTypes, + public typedAutoComplete(text: string, fullText: string, type: SearchQueryTypes, items?: BehaviorSubject): BehaviorSubject { items = items || new BehaviorSubject([]); @@ -71,10 +74,10 @@ export class AutoCompleteService { } this._networkService.getJson('/autocomplete/' + text, acParams).then(ret => { this._galleryCacheService.setAutoComplete(text, type, ret); - items.next(this.sortResults(text, ret.map(i => this.ACItemToRenderable(i)).concat(items.value))); + items.next(this.sortResults(text, ret.map(i => this.ACItemToRenderable(i, fullText)).concat(items.value))); }); } else { - items.next(this.sortResults(text, cached.map(i => this.ACItemToRenderable(i)).concat(items.value))); + items.next(this.sortResults(text, cached.map(i => this.ACItemToRenderable(i, fullText)).concat(items.value))); } return items; } @@ -91,23 +94,37 @@ export class AutoCompleteService { return tokens[1]; } + private getTypeFromPrefix(text: string): SearchQueryTypes { const tokens = text.split(':'); if (tokens.length !== 2) { return null; } + if (new RegExp('^\\d*-' + this._searchQueryParserService.keywords.kmFrom).test(tokens[0])) { + return SearchQueryTypes.distance; + } return this.textSearchKeywordsMap[tokens[0]] || null; } - private ACItemToRenderable(item: IAutoCompleteItem): RenderableAutoCompleteItem { + private ACItemToRenderable(item: IAutoCompleteItem, searchToken: string): RenderableAutoCompleteItem { if (!item.type) { return {text: item.text, queryHint: item.text}; } - if (TextSearchQueryTypes.includes(item.type) && item.type !== SearchQueryTypes.any_text) { + if ((TextSearchQueryTypes.includes(item.type) || + item.type === SearchQueryTypes.distance) && + item.type !== SearchQueryTypes.any_text) { + let queryHint = (this._searchQueryParserService.keywords)[SearchQueryTypes[item.type]] + ':"' + item.text + '"'; + + // if its a distance search, change hint text + const tokens = searchToken.split(':'); + if (tokens.length === 2 && + new RegExp('^\\d*-' + this._searchQueryParserService.keywords.kmFrom).test(tokens[0])) { + queryHint = tokens[0] + ':"' + item.text + '"'; + } + return { text: item.text, type: item.type, - queryHint: - (this._searchQueryParserService.keywords)[SearchQueryTypes[item.type]] + ':"' + item.text + '"' + queryHint: queryHint }; } return { diff --git a/src/frontend/app/ui/gallery/search/search-field/search-field.gallery.component.html b/src/frontend/app/ui/gallery/search/search-field/search-field.gallery.component.html index 7cb11687..a915c89f 100644 --- a/src/frontend/app/ui/gallery/search/search-field/search-field.gallery.component.html +++ b/src/frontend/app/ui/gallery/search/search-field/search-field.gallery.component.html @@ -34,7 +34,7 @@
@@ -46,6 +46,7 @@ + {{item.preText}}{{item.highLightText}}{{item.postText}}
diff --git a/test/backend/integration/routers/GalleryRouter.ts b/test/backend/integration/routers/GalleryRouter.ts index 6073c7ff..e4fe37df 100644 --- a/test/backend/integration/routers/GalleryRouter.ts +++ b/test/backend/integration/routers/GalleryRouter.ts @@ -62,7 +62,6 @@ describe('GalleryRouter', (sqlHelper: DBTestHelper) => { expect(result.body.error).to.be.equal(null); expect(result.body.result).to.not.be.equal(null); expect(result.body.result.directory).to.not.be.equal(null); - console.log(result.body.result.directory); }); it('should load gallery twice (to force loading form db)', async () => { diff --git a/test/backend/unit/model/sql/SearchManager.ts b/test/backend/unit/model/sql/SearchManager.ts index fb77b899..f6212e7e 100644 --- a/test/backend/unit/model/sql/SearchManager.ts +++ b/test/backend/unit/model/sql/SearchManager.ts @@ -999,7 +999,7 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => { }); - it('should search distance', async () => { + it('should search distance', async () => { ObjectManagers.getInstance().LocationManager = new LocationManager(); const sm = new SearchManager();