Skip to content

Commit

Permalink
Merge pull request #316 from bpatrik/feature/saved-search
Browse files Browse the repository at this point in the history
Creating saved search as Albums #45
  • Loading branch information
bpatrik authored May 28, 2021
2 parents 2e15def + a5630a2 commit 77b2d7e
Show file tree
Hide file tree
Showing 44 changed files with 1,153 additions and 119 deletions.
63 changes: 63 additions & 0 deletions src/backend/middlewares/AlbumMWs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {NextFunction, Request, Response} from 'express';
import {ErrorCodes, ErrorDTO} from '../../common/entities/Error';
import {ObjectManagers} from '../model/ObjectManagers';
import {Utils} from '../../common/Utils';
import {Config} from '../../common/config/private/Config';


export class AlbumMWs {


public static async listAlbums(req: Request, res: Response, next: NextFunction): Promise<void> {
if (Config.Client.Album.enabled === false) {
return next();
}
try {
req.resultPipe = await ObjectManagers.getInstance()
.AlbumManager.getAlbums();
return next();

} catch (err) {
return next(new ErrorDTO(ErrorCodes.ALBUM_ERROR, 'Error during listing albums', err));
}
}


public static async deleteAlbum(req: Request, res: Response, next: NextFunction): Promise<void> {
if (Config.Client.Album.enabled === false) {
return next();
}
if (!req.params.id || !Utils.isUInt32(parseInt(req.params.id, 10))) {
return next();
}
try {
await ObjectManagers.getInstance().AlbumManager.deleteAlbum(parseInt(req.params.id, 10));
req.resultPipe = 'ok';
return next();

} catch (err) {
return next(new ErrorDTO(ErrorCodes.ALBUM_ERROR, 'Error during deleting albums', err));
}
}

public static async createSavedSearch(req: Request, res: Response, next: NextFunction): Promise<void> {
if (Config.Client.Album.enabled === false) {
return next();
}
if ((typeof req.body === 'undefined') || (typeof req.body.name !== 'string') || (typeof req.body.searchQuery !== 'object')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'updateSharing filed is missing'));
}
try {
await ObjectManagers.getInstance().AlbumManager.addSavedSearch(req.body.name, req.body.searchQuery);
req.resultPipe = 'ok';
return next();

} catch (err) {
return next(new ErrorDTO(ErrorCodes.ALBUM_ERROR, 'Error during creating saved search albums', err));
}
}


}


1 change: 1 addition & 0 deletions src/backend/middlewares/SharingMWs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export class SharingMWs {

try {
req.resultPipe = await ObjectManagers.getInstance().SharingManager.deleteSharing(sharingKey);
req.resultPipe = 'ok';
return next();
} catch (err) {
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during deleting sharing', err));
Expand Down
64 changes: 27 additions & 37 deletions src/backend/model/ObjectManagers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {IPersonManager} from './database/interfaces/IPersonManager';
import {IVersionManager} from './database/interfaces/IVersionManager';
import {IJobManager} from './database/interfaces/IJobManager';
import {LocationManager} from './database/LocationManager';
import {IAlbumManager} from './database/interfaces/IAlbumManager';
import {JobManager} from './jobs/JobManager';

const LOG_TAG = '[ObjectManagers]';

Expand All @@ -25,6 +27,7 @@ export class ObjectManagers {
private versionManager: IVersionManager;
private jobManager: IJobManager;
private locationManager: LocationManager;
private albumManager: IAlbumManager;


get VersionManager(): IVersionManager {
Expand All @@ -43,6 +46,14 @@ export class ObjectManagers {
this.locationManager = value;
}

get AlbumManager(): IAlbumManager {
return this.albumManager;
}

set AlbumManager(value: IAlbumManager) {
this.albumManager = value;
}

get PersonManager(): IPersonManager {
return this.personManager;
}
Expand Down Expand Up @@ -121,51 +132,30 @@ export class ObjectManagers {
}


public static async InitCommonManagers(): Promise<void> {
const JobManager = require('./jobs/JobManager').JobManager;
ObjectManagers.getInstance().JobManager = new JobManager();
}

public static async InitMemoryManagers(): Promise<void> {
await ObjectManagers.reset();
const GalleryManager = require('./database/memory/GalleryManager').GalleryManager;
const UserManager = require('./database/memory/UserManager').UserManager;
const SearchManager = require('./database/memory/SearchManager').SearchManager;
const SharingManager = require('./database/memory/SharingManager').SharingManager;
const IndexingManager = require('./database/memory/IndexingManager').IndexingManager;
const PersonManager = require('./database/memory/PersonManager').PersonManager;
const VersionManager = require('./database/memory/VersionManager').VersionManager;
ObjectManagers.getInstance().GalleryManager = new GalleryManager();
ObjectManagers.getInstance().UserManager = new UserManager();
ObjectManagers.getInstance().SearchManager = new SearchManager();
ObjectManagers.getInstance().SharingManager = new SharingManager();
ObjectManagers.getInstance().IndexingManager = new IndexingManager();
ObjectManagers.getInstance().PersonManager = new PersonManager();
ObjectManagers.getInstance().VersionManager = new VersionManager();
ObjectManagers.getInstance().LocationManager = new LocationManager();
this.InitCommonManagers();
this.initManagers('memory');
Logger.debug(LOG_TAG, 'Memory DB inited');
}

public static async InitSQLManagers(): Promise<void> {
await ObjectManagers.reset();
await SQLConnection.init();
const GalleryManager = require('./database/sql/GalleryManager').GalleryManager;
const UserManager = require('./database/sql/UserManager').UserManager;
const SearchManager = require('./database/sql/SearchManager').SearchManager;
const SharingManager = require('./database/sql/SharingManager').SharingManager;
const IndexingManager = require('./database/sql/IndexingManager').IndexingManager;
const PersonManager = require('./database/sql/PersonManager').PersonManager;
const VersionManager = require('./database/sql/VersionManager').VersionManager;
ObjectManagers.getInstance().GalleryManager = new GalleryManager();
ObjectManagers.getInstance().UserManager = new UserManager();
ObjectManagers.getInstance().SearchManager = new SearchManager();
ObjectManagers.getInstance().SharingManager = new SharingManager();
ObjectManagers.getInstance().IndexingManager = new IndexingManager();
ObjectManagers.getInstance().PersonManager = new PersonManager();
ObjectManagers.getInstance().VersionManager = new VersionManager();
ObjectManagers.getInstance().LocationManager = new LocationManager();
this.InitCommonManagers();
this.initManagers('sql');
Logger.debug(LOG_TAG, 'SQL DB inited');
}

private static initManagers(type: 'memory' | 'sql'): void {
ObjectManagers.getInstance().AlbumManager = new (require(`./database/${type}/AlbumManager`).AlbumManager)();
ObjectManagers.getInstance().GalleryManager = new (require(`./database/${type}/GalleryManager`).GalleryManager)();
ObjectManagers.getInstance().IndexingManager = new (require(`./database/${type}/IndexingManager`).IndexingManager)();
ObjectManagers.getInstance().PersonManager = new (require(`./database/${type}/PersonManager`).PersonManager)();
ObjectManagers.getInstance().SearchManager = new (require(`./database/${type}/SearchManager`).SearchManager)();
ObjectManagers.getInstance().SharingManager = new (require(`./database/${type}/SharingManager`).SharingManager)();
ObjectManagers.getInstance().UserManager = new (require(`./database/${type}/UserManager`).UserManager)();
ObjectManagers.getInstance().VersionManager = new (require(`./database/${type}/VersionManager`).VersionManager)();
ObjectManagers.getInstance().JobManager = new JobManager();
ObjectManagers.getInstance().LocationManager = new LocationManager();
}

}
19 changes: 19 additions & 0 deletions src/backend/model/database/interfaces/IAlbumManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO';
import {AlbumBaseDTO} from '../../../../common/entities/album/AlbumBaseDTO';

export interface IAlbumManager {
/**
* Creates a saved search type of album
*/
addSavedSearch(name: string, searchQuery: SearchQueryDTO): Promise<void>;

/**
* Deletes an album
*/
deleteAlbum(id: number): Promise<void>;

/**
* Returns with all albums
*/
getAlbums(): Promise<AlbumBaseDTO[]>;
}
19 changes: 19 additions & 0 deletions src/backend/model/database/memory/AlbumManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {AlbumBaseDTO} from '../../../../common/entities/album/AlbumBaseDTO';
import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO';
import {IAlbumManager} from '../interfaces/IAlbumManager';

export class AlbumManager implements IAlbumManager {

public async addSavedSearch(name: string, searchQuery: SearchQueryDTO): Promise<void> {
throw new Error('not supported by memory DB');

}

public async deleteAlbum(id: number): Promise<void> {
throw new Error('not supported by memory DB');
}

public async getAlbums(): Promise<AlbumBaseDTO[]> {
throw new Error('not supported by memory DB');
}
}
40 changes: 40 additions & 0 deletions src/backend/model/database/sql/AlbumManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {SQLConnection} from './SQLConnection';
import {AlbumBaseEntity} from './enitites/album/AlbumBaseEntity';
import {AlbumBaseDTO} from '../../../../common/entities/album/AlbumBaseDTO';
import {SavedSearchDTO} from '../../../../common/entities/album/SavedSearchDTO';
import {ObjectManagers} from '../../ObjectManagers';
import {ISQLSearchManager} from './ISearchManager';
import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO';
import {SavedSearchEntity} from './enitites/album/SavedSearchEntity';
import { IAlbumManager } from '../interfaces/IAlbumManager';

export class AlbumManager implements IAlbumManager{
private static async fillPreviewToAlbum(album: AlbumBaseDTO): Promise<void> {
if (!(album as SavedSearchDTO).searchQuery) {
throw new Error('no search query present');
}
album.preview = await (ObjectManagers.getInstance().SearchManager as ISQLSearchManager)
.getPreview((album as SavedSearchDTO).searchQuery);
}

public async addSavedSearch(name: string, searchQuery: SearchQueryDTO): Promise<void> {
const connection = await SQLConnection.getConnection();
await connection.getRepository(SavedSearchEntity).insert({name, searchQuery});

}

public async deleteAlbum(id: number): Promise<void> {
const connection = await SQLConnection.getConnection();
await connection.getRepository(AlbumBaseEntity).delete({id});
}

public async getAlbums(): Promise<AlbumBaseDTO[]> {
const connection = await SQLConnection.getConnection();
const albums = await connection.getRepository(AlbumBaseEntity).find();
for (const a of albums) {
await AlbumManager.fillPreviewToAlbum(a);
}

return albums;
}
}
17 changes: 17 additions & 0 deletions src/backend/model/database/sql/ISearchManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {SearchQueryDTO, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO';
import {MediaDTO} from '../../../../common/entities/MediaDTO';
import {ISearchManager} from '../interfaces/ISearchManager';
import {AutoCompleteItem} from '../../../../common/entities/AutoCompleteItem';
import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO';
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';

export interface ISQLSearchManager extends ISearchManager {
autocomplete(text: string, type: SearchQueryTypes): Promise<AutoCompleteItem[]>;

search(query: SearchQueryDTO): Promise<SearchResultDTO>;

getRandomPhoto(queryFilter: SearchQueryDTO): Promise<PhotoDTO>;

// "Protected" functions. only called from other Managers, not from middlewares
getPreview(query: SearchQueryDTO): Promise<MediaDTO>;
}
6 changes: 6 additions & 0 deletions src/backend/model/database/sql/SQLConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {PersonEntry} from './enitites/PersonEntry';
import {Utils} from '../../../../common/Utils';
import * as path from 'path';
import {DatabaseType, ServerDataBaseConfig, SQLLogLevel} from '../../../../common/config/private/PrivateConfig';
import {AlbumBaseEntity} from './enitites/album/AlbumBaseEntity';
import {SavedSearchEntity} from './enitites/album/SavedSearchEntity';


export class SQLConnection {
Expand All @@ -43,6 +45,8 @@ export class SQLConnection {
VideoEntity,
DirectoryEntity,
SharingEntity,
AlbumBaseEntity,
SavedSearchEntity,
VersionEntity
];
options.synchronize = false;
Expand Down Expand Up @@ -73,6 +77,8 @@ export class SQLConnection {
VideoEntity,
DirectoryEntity,
SharingEntity,
AlbumBaseEntity,
SavedSearchEntity,
VersionEntity
];
options.synchronize = false;
Expand Down
21 changes: 19 additions & 2 deletions src/backend/model/database/sql/SearchManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {AutoCompleteItem} from '../../../../common/entities/AutoCompleteItem';
import {ISearchManager} from '../interfaces/ISearchManager';
import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO';
import {SQLConnection} from './SQLConnection';
import {PhotoEntity} from './enitites/PhotoEntity';
Expand Down Expand Up @@ -32,8 +31,10 @@ import {Utils} from '../../../../common/Utils';
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
import {DatabaseType} from '../../../../common/config/private/PrivateConfig';
import {ISQLGalleryManager} from './IGalleryManager';
import {ISQLSearchManager} from './ISearchManager';
import {MediaDTO} from '../../../../common/entities/MediaDTO';

export class SearchManager implements ISearchManager {
export class SearchManager implements ISQLSearchManager {

private static autoCompleteItemsUnique(array: Array<AutoCompleteItem>): Array<AutoCompleteItem> {
const a = array.concat();
Expand Down Expand Up @@ -223,6 +224,21 @@ export class SearchManager implements ISearchManager {

}

public async getPreview(queryIN: SearchQueryDTO): Promise<MediaDTO> {
let query = this.flattenSameOfQueries(queryIN);
query = await this.getGPSData(query);
const connection = await SQLConnection.getConnection();

return await connection
.getRepository(MediaEntity)
.createQueryBuilder('media')
.innerJoinAndSelect('media.directory', 'directory')
.where(this.buildWhereQuery(query))
.orderBy('media.metadata.creationDate', 'DESC')
.limit(1)
.getOne();
}

/**
* Returns only those part of a query tree that only contains directory related search queries
*/
Expand Down Expand Up @@ -632,4 +648,5 @@ export class SearchManager implements ISearchManager {
return res;
}


}
2 changes: 1 addition & 1 deletion src/backend/model/database/sql/enitites/MediaEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class MediaMetadataEntity implements MediaMetadata {
// TODO: fix inheritance once its working in typeorm
@Entity()
@Unique(['name', 'directory'])
@TableInheritance({column: {type: 'varchar', name: 'type', length: 32}})
@TableInheritance({column: {type: 'varchar', name: 'type', length: 16}})
export abstract class MediaEntity implements MediaDTO {

@Index()
Expand Down
21 changes: 21 additions & 0 deletions src/backend/model/database/sql/enitites/album/AlbumBaseEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {Column, Entity, Index, PrimaryGeneratedColumn, TableInheritance} from 'typeorm';
import {MediaEntity} from '../MediaEntity';
import {columnCharsetCS} from '../EntityUtils';
import {AlbumBaseDTO} from '../../../../../../common/entities/album/AlbumBaseDTO';

@Entity()
@TableInheritance({column: {type: 'varchar', name: 'type', length: 24}})
export class AlbumBaseEntity implements AlbumBaseDTO {

@Index()
@PrimaryGeneratedColumn({unsigned: true})
id: number;

@Index()
@Column(columnCharsetCS)
name: string;

// not saving to database, it is only assigned when querying the DB
public preview: MediaEntity;

}
23 changes: 23 additions & 0 deletions src/backend/model/database/sql/enitites/album/SavedSearchEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {ChildEntity, Column} from 'typeorm';
import {AlbumBaseEntity} from './AlbumBaseEntity';
import {SavedSearchDTO} from '../../../../../../common/entities/album/SavedSearchDTO';
import {SearchQueryDTO} from '../../../../../../common/entities/SearchQueryDTO';

@ChildEntity()
export class SavedSearchEntity extends AlbumBaseEntity implements SavedSearchDTO {
@Column({
type: 'text',
nullable: false,
transformer: {
// used to deserialize your data from db field value
from: (val: string) => {
return JSON.parse(val);
},
// used to serialize your data to db field
to: (val: object) => {
return JSON.stringify(val);
}
}
})
searchQuery: SearchQueryDTO;
}
Loading

0 comments on commit 77b2d7e

Please sign in to comment.