1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2024-11-03 21:04:03 +08:00

Implementing AlbumBase and SavedSearch Entities and Manager #45

This commit is contained in:
Patrik J. Braun 2021-05-28 19:21:18 +02:00
parent e63a7cae98
commit 512f5c18d6
11 changed files with 321 additions and 4 deletions

View File

@ -0,0 +1,39 @@
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';
export class AlbumManager {
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;
}
}

View File

@ -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>;
}

View File

@ -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 {
@ -43,6 +45,8 @@ export class SQLConnection {
VideoEntity,
DirectoryEntity,
SharingEntity,
AlbumBaseEntity,
SavedSearchEntity,
VersionEntity
];
options.synchronize = false;
@ -73,6 +77,8 @@ export class SQLConnection {
VideoEntity,
DirectoryEntity,
SharingEntity,
AlbumBaseEntity,
SavedSearchEntity,
VersionEntity
];
options.synchronize = false;

View File

@ -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';
@ -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();
@ -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
*/
@ -632,4 +648,5 @@ export class SearchManager implements ISearchManager {
return res;
}
}

View File

@ -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()

View File

@ -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: 16}})
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;
}

View File

@ -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;
}

View File

@ -158,7 +158,7 @@ export interface TextSearch extends NegatableSearchQuery {
SearchQueryTypes.caption |
SearchQueryTypes.file_name |
SearchQueryTypes.directory;
matchType: TextSearchQueryMatchTypes;
matchType?: TextSearchQueryMatchTypes;
text: string;
}

View File

@ -0,0 +1,7 @@
import {PreviewPhotoDTO} from '../PhotoDTO';
export interface AlbumBaseDTO {
id: number;
name: string;
preview: PreviewPhotoDTO;
}

View File

@ -0,0 +1,11 @@
import {AlbumBaseDTO} from './AlbumBaseDTO';
import {PreviewPhotoDTO} from '../PhotoDTO';
import {SearchQueryDTO} from '../SearchQueryDTO';
export interface SavedSearchDTO extends AlbumBaseDTO {
id: number;
name: string;
preview: PreviewPhotoDTO;
searchQuery: SearchQueryDTO;
}

View File

@ -0,0 +1,176 @@
import {DBTestHelper} from '../../../DBTestHelper';
import {DirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO';
import {TestHelper} from './TestHelper';
import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers';
import {PhotoDTO, PhotoMetadata} from '../../../../../src/common/entities/PhotoDTO';
import {VideoDTO} from '../../../../../src/common/entities/VideoDTO';
import {AlbumManager} from '../../../../../src/backend/model/database/sql/AlbumManager';
import {SearchQueryTypes, TextSearch} from '../../../../../src/common/entities/SearchQueryDTO';
import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLConnection';
import {AlbumBaseEntity} from '../../../../../src/backend/model/database/sql/enitites/album/AlbumBaseEntity';
import {Utils} from '../../../../../src/common/Utils';
import {MediaDTO} from '../../../../../src/common/entities/MediaDTO';
const deepEqualInAnyOrder = require('deep-equal-in-any-order');
const chai = require('chai');
chai.use(deepEqualInAnyOrder);
const {expect} = chai;
// to help WebStorm to handle the test cases
declare let describe: any;
declare const after: any;
declare const before: any;
const tmpDescribe = describe;
describe = DBTestHelper.describe(); // fake it os IDE plays nicely (recognize the test)
describe('AlbumManager', (sqlHelper: DBTestHelper) => {
describe = tmpDescribe;
/**
* dir
* |- v
* |- p
* |- p2
* |-> subDir
* |- p3
* |-> subDir2
* |- p4
*/
let dir: DirectoryDTO;
let subDir: DirectoryDTO;
let subDir2: DirectoryDTO;
let v: VideoDTO;
let p: PhotoDTO;
let p2: PhotoDTO;
let p3: PhotoDTO;
let p4: PhotoDTO;
const setUpTestGallery = async (): Promise<void> => {
const directory: DirectoryDTO = TestHelper.getDirectoryEntry();
subDir = TestHelper.getDirectoryEntry(directory, 'The Phantom Menace');
subDir2 = TestHelper.getDirectoryEntry(directory, 'Return of the Jedi');
p = TestHelper.getRandomizedPhotoEntry(directory, 'Photo1');
p2 = TestHelper.getRandomizedPhotoEntry(directory, 'Photo2');
p3 = TestHelper.getRandomizedPhotoEntry(subDir, 'Photo3');
p4 = TestHelper.getRandomizedPhotoEntry(subDir2, 'Photo4');
v = TestHelper.getVideoEntry1(directory);
dir = await DBTestHelper.persistTestDir(directory);
subDir = dir.directories[0];
subDir2 = dir.directories[1];
p = (dir.media.filter(m => m.name === p.name)[0] as any);
p2 = (dir.media.filter(m => m.name === p2.name)[0] as any);
v = (dir.media.filter(m => m.name === v.name)[0] as any);
p3 = (dir.directories[0].media[0] as any);
p4 = (dir.directories[1].media[0] as any);
};
const setUpSqlDB = async () => {
await sqlHelper.initDB();
await setUpTestGallery();
await ObjectManagers.InitSQLManagers();
};
const toAlbumPreview = (m: MediaDTO): MediaDTO => {
const tmpM = m.directory.media;
const tmpD = m.directory.directories;
const tmpP = m.directory.preview;
const tmpMT = m.directory.metaFile;
delete m.directory.directories;
delete m.directory.media;
delete m.directory.preview;
delete m.directory.metaFile;
const ret = Utils.clone(m);
delete (ret.metadata as PhotoMetadata).faces;
m.directory.directories = tmpD;
m.directory.media = tmpM;
m.directory.preview = tmpP;
m.directory.metaFile = tmpMT;
return ret;
};
before(setUpSqlDB);
after(sqlHelper.clearDB);
describe('Saved search', () => {
beforeEach(setUpSqlDB);
afterEach(sqlHelper.clearDB);
it('should add album', async () => {
const am = new AlbumManager();
const connection = await SQLConnection.getConnection();
const query: TextSearch = {text: 'test', type: SearchQueryTypes.any_text};
expect(await connection.getRepository(AlbumBaseEntity).find()).to.deep.equalInAnyOrder([]);
await am.addSavedSearch('Test Album', Utils.clone(query));
expect(await connection.getRepository(AlbumBaseEntity).find()).to.deep.equalInAnyOrder([{
id: 1,
name: 'Test Album',
searchQuery: query
}]);
});
it('should delete album', async () => {
const am = new AlbumManager();
const connection = await SQLConnection.getConnection();
const query: TextSearch = {text: 'test', type: SearchQueryTypes.any_text};
await am.addSavedSearch('Test Album', Utils.clone(query));
await am.addSavedSearch('Test Album2', Utils.clone(query));
expect(await connection.getRepository(AlbumBaseEntity).find()).to.deep.equalInAnyOrder([
{
id: 1,
name: 'Test Album',
searchQuery: query
},
{
id: 2,
name: 'Test Album2',
searchQuery: query
}]);
await am.deleteAlbum(1);
expect(await connection.getRepository(AlbumBaseEntity).find()).to.deep.equalInAnyOrder([{
id: 2,
name: 'Test Album2',
searchQuery: query
}]);
await am.deleteAlbum(2);
expect(await connection.getRepository(AlbumBaseEntity).find()).to.deep.equalInAnyOrder([]);
});
});
it('should list album', async () => {
const am = new AlbumManager();
const query: TextSearch = {text: 'photo1', type: SearchQueryTypes.any_text};
await am.addSavedSearch('Test Album', Utils.clone(query));
expect(await am.getAlbums()).to.deep.equalInAnyOrder(([{
id: 1,
name: 'Test Album',
searchQuery: query,
preview: toAlbumPreview(p)
}]));
});
});