diff --git a/backend/middlewares/GalleryMWs.ts b/backend/middlewares/GalleryMWs.ts index d088ed0c..6c309762 100644 --- a/backend/middlewares/GalleryMWs.ts +++ b/backend/middlewares/GalleryMWs.ts @@ -6,7 +6,7 @@ import {DirectoryDTO} from "../../common/entities/DirectoryDTO"; import {ObjectManagerRepository} from "../model/ObjectManagerRepository"; import {AutoCompleteItem, SearchTypes} from "../../common/entities/AutoCompleteItem"; import {ContentWrapper} from "../../common/entities/ConentWrapper"; -import {SearchResult} from "../../common/entities/SearchResult"; +import {SearchResultDTO} from "../../common/entities/SearchResult"; import {PhotoDTO} from "../../common/entities/PhotoDTO"; import {Config} from "../config/Config"; import {ProjectPath} from "../ProjectPath"; @@ -87,7 +87,7 @@ export class GalleryMWs { type = parseInt(req.query.type); } - ObjectManagerRepository.getInstance().getSearchManager().search(req.params.text, type, (err, result:SearchResult) => { + ObjectManagerRepository.getInstance().getSearchManager().search(req.params.text, type, (err, result: SearchResultDTO) => { if (err || !result) { return next(new Error(ErrorCodes.GENERAL_ERROR, err)); } @@ -107,7 +107,7 @@ export class GalleryMWs { } - ObjectManagerRepository.getInstance().getSearchManager().instantSearch(req.params.text, (err, result:SearchResult) => { + ObjectManagerRepository.getInstance().getSearchManager().instantSearch(req.params.text, (err, result: SearchResultDTO) => { if (err || !result) { return next(new Error(ErrorCodes.GENERAL_ERROR, err)); } diff --git a/backend/model/ObjectManagerRepository.ts b/backend/model/ObjectManagerRepository.ts index d23665be..179e8fbd 100644 --- a/backend/model/ObjectManagerRepository.ts +++ b/backend/model/ObjectManagerRepository.ts @@ -25,7 +25,7 @@ export class ObjectManagerRepository { MySQLConnection.init().then(() => { const GalleryManager = require("./mysql/GalleryManager").GalleryManager; const UserManager = require("./mysql/UserManager").UserManager; - const SearchManager = require("./memory/SearchManager").SearchManager; + const SearchManager = require("./mysql/SearchManager").SearchManager; ObjectManagerRepository.getInstance().setGalleryManager(new GalleryManager()); ObjectManagerRepository.getInstance().setUserManager(new UserManager()); ObjectManagerRepository.getInstance().setSearchManager(new SearchManager()); diff --git a/backend/model/interfaces/ISearchManager.ts b/backend/model/interfaces/ISearchManager.ts index 2c0443e2..5ccfc24a 100644 --- a/backend/model/interfaces/ISearchManager.ts +++ b/backend/model/interfaces/ISearchManager.ts @@ -1,7 +1,7 @@ import {AutoCompleteItem, SearchTypes} from "../../../common/entities/AutoCompleteItem"; -import {SearchResult} from "../../../common/entities/SearchResult"; +import {SearchResultDTO} from "../../../common/entities/SearchResult"; export interface ISearchManager { autocomplete(text: string, cb: (error: any, result: Array) => void): void; - search(text: string, searchType: SearchTypes, cb: (error: any, result: SearchResult) => void): void; - instantSearch(text: string, cb: (error: any, result: SearchResult) => void): void; + search(text: string, searchType: SearchTypes, cb: (error: any, result: SearchResultDTO) => void): void; + instantSearch(text: string, cb: (error: any, result: SearchResultDTO) => void): void; } \ No newline at end of file diff --git a/backend/model/memory/SearchManager.ts b/backend/model/memory/SearchManager.ts index 0b8864f1..18255b06 100644 --- a/backend/model/memory/SearchManager.ts +++ b/backend/model/memory/SearchManager.ts @@ -1,6 +1,6 @@ import {AutoCompleteItem, SearchTypes} from "../../../common/entities/AutoCompleteItem"; import {ISearchManager} from "../interfaces/ISearchManager"; -import {SearchResult} from "../../../common/entities/SearchResult"; +import {SearchResultDTO} from "../../../common/entities/SearchResult"; export class SearchManager implements ISearchManager { @@ -9,11 +9,11 @@ export class SearchManager implements ISearchManager { throw new Error("not implemented"); } - search(text: string, searchType: SearchTypes, cb: (error: any, result: SearchResult) => void) { + search(text: string, searchType: SearchTypes, cb: (error: any, result: SearchResultDTO) => void) { throw new Error("not implemented"); } - instantSearch(text: string, cb: (error: any, result: SearchResult) => void) { + instantSearch(text: string, cb: (error: any, result: SearchResultDTO) => void) { throw new Error("not implemented"); } diff --git a/backend/model/mysql/SearchManager.ts b/backend/model/mysql/SearchManager.ts new file mode 100644 index 00000000..899ca9c8 --- /dev/null +++ b/backend/model/mysql/SearchManager.ts @@ -0,0 +1,101 @@ +import {AutoCompleteItem, SearchTypes} from "../../../common/entities/AutoCompleteItem"; +import {ISearchManager} from "../interfaces/ISearchManager"; +import {SearchResultDTO} from "../../../common/entities/SearchResult"; +import {MySQLConnection} from "./MySQLConnection"; +import {PhotoEntity} from "./enitites/PhotoEntity"; +import {DirectoryEnitity} from "./enitites/DirectoryEntity"; +import {PositionMetaData} from "../../../common/entities/PhotoDTO"; + +export class SearchManager implements ISearchManager { + + + autocomplete(text: string, cb: (error: any, result: Array) => void) { + + MySQLConnection.getConnection().then(async connection => { + try { + let result: Array = []; + let photoRepository = connection.getRepository(PhotoEntity); + let directoryRepository = connection.getRepository(DirectoryEnitity); + + + (await photoRepository + .createQueryBuilder('photo') + .select('DISTINCT(photo.metadataKeywords)') + .where('photo.metadata.keywords LIKE :text COLLATE utf8_general_ci', {text: "%" + text + "%"}) + .setLimit(5) + .getRawMany<{metadataKeywords: string}>()) + .map(r => >JSON.parse(r.metadataKeywords)) + .forEach(keywords => { + result = result.concat(this.encapsulateAutoComplete(keywords.filter(k => k.toLowerCase().indexOf(text.toLowerCase()) != -1), SearchTypes.keyword)); + }); + + + (await photoRepository + .createQueryBuilder('photo') + .select('DISTINCT(photo.metadataPositionData)') + .where('photo.metadata.positionData LIKE :text COLLATE utf8_general_ci', {text: "%" + text + "%"}) + .setLimit(5) + .getRawMany<{metadataPositionData: string}>()) + .map(r => JSON.parse(r.metadataPositionData)) + .map(pm => >[pm.city || "", pm.country || "", pm.state || ""]) + .forEach(positions => { + result = result.concat(this.encapsulateAutoComplete(positions.filter(p => p.toLowerCase().indexOf(text.toLowerCase()) != -1), SearchTypes.position)); + }); + + + result = result.concat(this.encapsulateAutoComplete((await photoRepository + .createQueryBuilder('photo') + .select('DISTINCT(photo.name)') + .where('photo.name LIKE :text COLLATE utf8_general_ci', {text: "%" + text + "%"}) + .setLimit(5) + .getRawMany<{name: string}>()) + .map(r => r.name), SearchTypes.image)); + + result = result.concat(this.encapsulateAutoComplete((await directoryRepository + .createQueryBuilder('dir') + .select('DISTINCT(dir.name)') + .where('dir.name LIKE :text COLLATE utf8_general_ci', {text: "%" + text + "%"}) + .setLimit(5) + .getRawMany<{name: string}>()) + .map(r => r.name), SearchTypes.directory)); + + + return cb(null, this.autoCompleteItemsUnique(result)); + } catch (error) { + return cb(error, null); + } + + }).catch((error) => { + return cb(error, null); + }); + } + + search(text: string, searchType: SearchTypes, cb: (error: any, result: SearchResultDTO) => void) { + throw new Error("not implemented"); + } + + instantSearch(text: string, cb: (error: any, result: SearchResultDTO) => void) { + throw new Error("not implemented"); + } + + private encapsulateAutoComplete(values: Array, type: SearchTypes) { + let res = []; + values.forEach((value) => { + res.push(new AutoCompleteItem(value, type)); + }); + return res; + } + + + private autoCompleteItemsUnique(array: Array) { + let a = array.concat(); + for (let i = 0; i < a.length; ++i) { + for (let j = i + 1; j < a.length; ++j) { + if (a[i].equals(a[j])) + a.splice(j--, 1); + } + } + + return a; + } +} \ No newline at end of file diff --git a/common/config/Config.ts b/common/config/Config.ts index fbbcd2ab..f0f24278 100644 --- a/common/config/Config.ts +++ b/common/config/Config.ts @@ -42,9 +42,9 @@ export class ConfigClass { public Client:ClientConfig = { thumbnailSizes: [200, 400, 600], Search: { - searchEnabled: false, - instantSearchEnabled: false, - autocompleteEnabled: false + searchEnabled: true, + instantSearchEnabled: true, + autocompleteEnabled: true }, concurrentThumbnailGenerations: 1, enableCache: false, diff --git a/common/entities/ConentWrapper.ts b/common/entities/ConentWrapper.ts index 760937f3..09e82e57 100644 --- a/common/entities/ConentWrapper.ts +++ b/common/entities/ConentWrapper.ts @@ -1,11 +1,11 @@ import {DirectoryDTO} from "./DirectoryDTO"; -import {SearchResult} from "./SearchResult"; +import {SearchResultDTO} from "./SearchResult"; export class ContentWrapper { public directory: DirectoryDTO; - public searchResult:SearchResult; + public searchResult: SearchResultDTO; - constructor(directory: DirectoryDTO = null, searchResult: SearchResult = null) { + constructor(directory: DirectoryDTO = null, searchResult: SearchResultDTO = null) { this.directory = directory; this.searchResult = searchResult; } diff --git a/common/entities/SearchResult.ts b/common/entities/SearchResult.ts index e403e26f..0afe3770 100644 --- a/common/entities/SearchResult.ts +++ b/common/entities/SearchResult.ts @@ -1,9 +1,9 @@ import {DirectoryDTO} from "./DirectoryDTO"; import {PhotoDTO} from "./PhotoDTO"; import {SearchTypes} from "./AutoCompleteItem"; -export class SearchResult { - public searchText:string = ""; - public searchType:SearchTypes; - public directories: Array = []; - public photos: Array = []; +export interface SearchResultDTO { + searchText: string; + searchType: SearchTypes; + directories: Array; + photos: Array; } \ No newline at end of file diff --git a/package.json b/package.json index ccf64ce7..0de8e3cc 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "license": "MIT", "main": "./backend/server.js", "scripts": { - "postinstall": "tsc -p frontend && tsc -p backend && tsc -p test/backend && tsc -p common", - "pretest": "tsc -p frontend && tsc -p backend && tsc -p test/backend", + "postinstall": "tsc", + "pretest": "tsc", "test": "karma start karma.conf.js --single-run && mocha --recursive test/backend/unit", "start": "node ./backend/server" }, @@ -53,7 +53,6 @@ "zone.js": "^0.7.4" }, "devDependencies": { - "@types/core-js": "^0.9.35", "@types/express": "^4.0.34", "@types/express-session": "0.0.32", "@types/jasmine": "^2.5.38", diff --git a/test/backend/tsconfig.json b/test/backend/tsconfig.json deleted file mode 100644 index 9fcc3436..00000000 --- a/test/backend/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "sourceMap": true, - "module": "commonjs", - "emitDecoratorMetadata": true, - "experimentalDecorators": true - }, - "exclude": [ - "node_modules", - "typings" - ] -} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 1af4fe5a..a7996570 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,8 @@ "experimentalDecorators": true, "lib": [ "es2015", - "dom" + "dom", + "es2015.promise" ], "suppressImplicitAnyIndexErrors": false, "typeRoots": [ @@ -16,7 +17,6 @@ ] }, "exclude": [ - "node_modules", - "node_modules/@types" + "node_modules" ] }