You've already forked pigallery2
mirror of
https://github.com/bpatrik/pigallery2.git
synced 2025-11-23 22:24:44 +02:00
Implement user allow and blocklist and apply it to general directory result #1015
This commit is contained in:
@@ -30,6 +30,7 @@ import {
|
||||
} from '../src/common/entities/SearchQueryDTO';
|
||||
import {defaultQueryKeywords, QueryKeywords, SearchQueryParser} from '../src/common/SearchQueryParser';
|
||||
import {ParentDirectoryDTO} from '../src/common/entities/DirectoryDTO';
|
||||
import {SessionContext} from '../src/backend/model/SessionContext';
|
||||
|
||||
|
||||
export interface BenchmarkResult {
|
||||
@@ -268,7 +269,8 @@ export class BenchmarkRunner {
|
||||
const queue = ['/'];
|
||||
while (queue.length > 0) {
|
||||
const dirPath = queue.shift();
|
||||
const dir = await gm.listDirectory(dirPath);
|
||||
const session = new SessionContext();
|
||||
const dir = await gm.listDirectory(session,dirPath);
|
||||
dir.directories.forEach((d): number => queue.push(path.join(d.path + d.name)));
|
||||
if (biggest < dir.media.length) {
|
||||
biggestPath = path.join(dir.path + dir.name);
|
||||
|
||||
@@ -41,6 +41,7 @@ export class GalleryMWs {
|
||||
try {
|
||||
const directory =
|
||||
await ObjectManagers.getInstance().GalleryManager.listDirectory(
|
||||
req.session.context,
|
||||
directoryName,
|
||||
parseInt(
|
||||
req.query[QueryParams.gallery.knownLastModified] as string,
|
||||
@@ -57,12 +58,12 @@ export class GalleryMWs {
|
||||
return next();
|
||||
}
|
||||
if (
|
||||
req.session['user'].permissions &&
|
||||
req.session['user'].permissions.length > 0 &&
|
||||
req.session['user'].permissions[0] !== '/*'
|
||||
req.session.context?.user.permissions &&
|
||||
req.session.context?.user.permissions.length > 0 &&
|
||||
req.session.context?.user.permissions[0] !== '/*'
|
||||
) {
|
||||
directory.directories = directory.directories.filter((d): boolean =>
|
||||
UserDTOUtils.isDirectoryAvailable(d, req.session['user'].permissions)
|
||||
UserDTOUtils.isDirectoryAvailable(d, req.session.context.user.permissions)
|
||||
);
|
||||
}
|
||||
req.resultPipe = new ContentWrapper(directory, null);
|
||||
|
||||
@@ -4,7 +4,7 @@ import {NotificationManager} from '../model/NotifocationManager';
|
||||
|
||||
export class NotificationMWs {
|
||||
public static list(req: Request, res: Response, next: NextFunction): void {
|
||||
if (req.session['user'].role >= UserRoles.Admin) {
|
||||
if (req.session.context?.user.role >= UserRoles.Admin) {
|
||||
req.resultPipe = NotificationManager.notifications;
|
||||
} else if (NotificationManager.notifications.length > 0) {
|
||||
req.resultPipe = NotificationManager.HasNotification;
|
||||
|
||||
@@ -31,16 +31,17 @@ export class RenderingMWs {
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (!req.session['user']) {
|
||||
if (!req.session.context?.user) {
|
||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'User not exists'));
|
||||
}
|
||||
|
||||
const user = {
|
||||
id: req.session['user'].id,
|
||||
name: req.session['user'].name,
|
||||
role: req.session['user'].role,
|
||||
usedSharingKey: req.session['user'].usedSharingKey,
|
||||
permissions: req.session['user'].permissions,
|
||||
id: req.session.context.user.id,
|
||||
name: req.session.context.user.name,
|
||||
role: req.session.context.user.role,
|
||||
usedSharingKey: req.session.context.user.usedSharingKey,
|
||||
permissions: req.session.context.user.permissions,
|
||||
projectionKey: req.session.context.user.projectionKey,
|
||||
} as UserDTO;
|
||||
|
||||
|
||||
@@ -142,8 +143,8 @@ export class RenderingMWs {
|
||||
!(
|
||||
forcedDebug ||
|
||||
(req.session &&
|
||||
req.session['user'] &&
|
||||
req.session['user'].role >= UserRoles.Developer)
|
||||
req.session.context?.user &&
|
||||
req.session.context?.user.role >= UserRoles.Developer)
|
||||
)
|
||||
) {
|
||||
delete err.detailsStr;
|
||||
|
||||
@@ -102,9 +102,9 @@ export class SharingMWs {
|
||||
sharingKey,
|
||||
path: directoryName,
|
||||
password: createSharing.password,
|
||||
creator: req.session['user'],
|
||||
creator: req.session.context?.user,
|
||||
expires:
|
||||
createSharing.valid >= 0 // if === -1 its forever
|
||||
createSharing.valid >= 0 // if === -1 it's forever
|
||||
? Date.now() + createSharing.valid
|
||||
: new Date(9999, 0, 1).getTime(), // never expire
|
||||
includeSubfolders: createSharing.includeSubfolders,
|
||||
@@ -155,7 +155,7 @@ export class SharingMWs {
|
||||
updateSharing.password && updateSharing.password !== ''
|
||||
? updateSharing.password
|
||||
: null,
|
||||
creator: req.session['user'],
|
||||
creator: req.session.context?.user,
|
||||
expires:
|
||||
updateSharing.valid >= 0 // if === -1 its forever
|
||||
? Date.now() + updateSharing.valid
|
||||
@@ -165,7 +165,7 @@ export class SharingMWs {
|
||||
};
|
||||
|
||||
try {
|
||||
const forceUpdate = req.session['user'].role >= UserRoles.Admin;
|
||||
const forceUpdate = req.session.context.user.role >= UserRoles.Admin;
|
||||
req.resultPipe =
|
||||
await ObjectManagers.getInstance().SharingManager.updateSharing(
|
||||
sharing,
|
||||
@@ -203,9 +203,9 @@ export class SharingMWs {
|
||||
|
||||
try {
|
||||
// Check if user has the right to delete sharing.
|
||||
if (req.session['user'].role < UserRoles.Admin) {
|
||||
if (req.session.context?.user.role < UserRoles.Admin) {
|
||||
const s = await ObjectManagers.getInstance().SharingManager.findOne(sharingKey);
|
||||
if (s.creator.id !== req.session['user'].id) {
|
||||
if (s.creator.id !== req.session.context?.user.id) {
|
||||
return next(new ErrorDTO(ErrorCodes.NOT_AUTHORISED, 'Can\'t delete sharing.'));
|
||||
}
|
||||
}
|
||||
@@ -260,12 +260,12 @@ export class SharingMWs {
|
||||
|
||||
const dir = path.normalize(req.params['directory'] || '/');
|
||||
try {
|
||||
if (req.session['user'].role >= UserRoles.Admin) {
|
||||
if (req.session.context?.user.role >= UserRoles.Admin) {
|
||||
req.resultPipe =
|
||||
await ObjectManagers.getInstance().SharingManager.listAllForDir(dir);
|
||||
} else {
|
||||
req.resultPipe =
|
||||
await ObjectManagers.getInstance().SharingManager.listAllForDir(dir, req.session['user']);
|
||||
await ObjectManagers.getInstance().SharingManager.listAllForDir(dir, req.session.context?.user);
|
||||
}
|
||||
return next();
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {LoginCredential} from '../../../common/entities/LoginCredential';
|
||||
import {UserDTO} from '../../../common/entities/UserDTO';
|
||||
import {SessionContext} from '../../model/SessionContext';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
@@ -10,16 +11,16 @@ declare global {
|
||||
loginCredential?: LoginCredential;
|
||||
};
|
||||
locale?: string;
|
||||
session: {
|
||||
context?: SessionContext;
|
||||
rememberMe?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface Response {
|
||||
tpl?: Record<string, any>;
|
||||
}
|
||||
|
||||
interface Session {
|
||||
user?: UserDTO;
|
||||
rememberMe?: boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,16 +18,17 @@ export class AuthenticationMWs {
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Users.authenticationRequired === false) {
|
||||
req.session['user'] = {
|
||||
const user = {
|
||||
name: UserRoles[Config.Users.unAuthenticatedUserRole],
|
||||
role: Config.Users.unAuthenticatedUserRole,
|
||||
} as UserDTO;
|
||||
req.session.context = await ObjectManagers.getInstance().buildContext(user);
|
||||
return next();
|
||||
}
|
||||
try {
|
||||
const user = await AuthenticationMWs.getSharingUser(req);
|
||||
if (user) {
|
||||
req.session['user'] = user;
|
||||
req.session.context = await ObjectManagers.getInstance().buildContext(user);
|
||||
return next();
|
||||
}
|
||||
// eslint-disable-next-line no-empty
|
||||
@@ -43,28 +44,29 @@ export class AuthenticationMWs {
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (Config.Users.authenticationRequired === false) {
|
||||
req.session['user'] = {
|
||||
const user = {
|
||||
name: UserRoles[Config.Users.unAuthenticatedUserRole],
|
||||
role: Config.Users.unAuthenticatedUserRole,
|
||||
} as UserDTO;
|
||||
req.session.context = await ObjectManagers.getInstance().buildContext(user);
|
||||
return next();
|
||||
}
|
||||
|
||||
// if already authenticated, do not try to use sharing authentication
|
||||
if (typeof req.session['user'] !== 'undefined') {
|
||||
if (typeof req.session.context !== 'undefined') {
|
||||
return next();
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await AuthenticationMWs.getSharingUser(req);
|
||||
if (user) {
|
||||
req.session['user'] = user;
|
||||
req.session.context = await ObjectManagers.getInstance().buildContext(user);
|
||||
return next();
|
||||
}
|
||||
} catch (err) {
|
||||
return next(new ErrorDTO(ErrorCodes.CREDENTIAL_NOT_FOUND, null, err));
|
||||
}
|
||||
if (typeof req.session['user'] === 'undefined') {
|
||||
if (typeof req.session.context === 'undefined') {
|
||||
res.status(401);
|
||||
return next(
|
||||
new ErrorDTO(ErrorCodes.NOT_AUTHENTICATED, 'Not authenticated')
|
||||
@@ -104,7 +106,7 @@ export class AuthenticationMWs {
|
||||
}
|
||||
|
||||
if (
|
||||
!UserDTOUtils.isDirectoryPathAvailable(p, req.session['user'].permissions)
|
||||
!UserDTOUtils.isDirectoryPathAvailable(p, req.session.context.user.permissions)
|
||||
) {
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
@@ -121,7 +123,7 @@ export class AuthenticationMWs {
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (req.session['user'].role < role) {
|
||||
if (req.session.context?.user.role < role) {
|
||||
return next(new ErrorDTO(ErrorCodes.NOT_AUTHORISED));
|
||||
}
|
||||
return next();
|
||||
@@ -170,12 +172,13 @@ export class AuthenticationMWs {
|
||||
sharingPath += '*';
|
||||
}
|
||||
|
||||
req.session['user'] = {
|
||||
const user = {
|
||||
name: 'Guest',
|
||||
role: UserRoles.LimitedGuest,
|
||||
permissions: [sharingPath],
|
||||
usedSharingKey: sharing.sharingKey,
|
||||
} as UserDTO;
|
||||
req.session.context = await ObjectManagers.getInstance().buildContext(user);
|
||||
return next();
|
||||
} catch (err) {
|
||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, null, err));
|
||||
@@ -187,7 +190,7 @@ export class AuthenticationMWs {
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
if (typeof req.session['user'] !== 'undefined') {
|
||||
if (typeof req.session.context?.user !== 'undefined') {
|
||||
return next(new ErrorDTO(ErrorCodes.ALREADY_AUTHENTICATED));
|
||||
}
|
||||
return next();
|
||||
@@ -202,7 +205,7 @@ export class AuthenticationMWs {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
|
||||
// not enough parameter
|
||||
// not enough parameters
|
||||
if (
|
||||
typeof req.body === 'undefined' ||
|
||||
typeof req.body.loginCredential === 'undefined' ||
|
||||
@@ -226,7 +229,7 @@ export class AuthenticationMWs {
|
||||
})
|
||||
);
|
||||
delete user.password;
|
||||
req.session['user'] = user;
|
||||
req.session.context = await ObjectManagers.getInstance().buildContext(user);
|
||||
if (req.body.loginCredential.rememberMe) {
|
||||
req.sessionOptions.expires = new Date(
|
||||
Date.now() + Config.Server.sessionTimeout
|
||||
@@ -247,7 +250,7 @@ export class AuthenticationMWs {
|
||||
}
|
||||
|
||||
public static logout(req: Request, res: Response, next: NextFunction): void {
|
||||
delete req.session['user'];
|
||||
delete req.session.context;
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export class UserRequestConstrainsMWs {
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
if (req.session['user'].id !== parseInt(req.params.id, 10)) {
|
||||
if (req.session.context?.user.id !== parseInt(req.params.id, 10)) {
|
||||
return next(new ErrorDTO(ErrorCodes.NOT_AUTHORISED));
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export class UserRequestConstrainsMWs {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (req.session['user'].id === parseInt(req.params.id, 10)) {
|
||||
if (req.session.context?.user.id === parseInt(req.params.id, 10)) {
|
||||
return next(new ErrorDTO(ErrorCodes.NOT_AUTHORISED));
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export class UserRequestConstrainsMWs {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (req.session['user'].id !== parseInt(req.params.id, 10)) {
|
||||
if (req.session.context?.user.id !== parseInt(req.params.id, 10)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import * as crypto from 'crypto';
|
||||
import {SQLConnection} from './database/SQLConnection';
|
||||
import {Logger} from '../Logger';
|
||||
import {LocationManager} from './database/LocationManager';
|
||||
@@ -15,6 +16,9 @@ import {PersonManager} from './database/PersonManager';
|
||||
import {SharingManager} from './database/SharingManager';
|
||||
import {IObjectManager} from './database/IObjectManager';
|
||||
import {ExtensionManager} from './extension/ExtensionManager';
|
||||
import {SessionContext} from './SessionContext';
|
||||
import {UserEntity} from './database/enitites/UserEntity';
|
||||
import {ANDSearchQuery, SearchQueryDTOUtils, SearchQueryTypes} from '../../common/entities/SearchQueryDTO';
|
||||
|
||||
const LOG_TAG = '[ObjectManagers]';
|
||||
|
||||
@@ -269,4 +273,27 @@ export class ObjectManagers {
|
||||
this.extensionManager = value;
|
||||
this.managers.push(this.extensionManager as IObjectManager);
|
||||
}
|
||||
|
||||
async buildContext(user: UserEntity): Promise<SessionContext> {
|
||||
const context = new SessionContext();
|
||||
context.user = user;
|
||||
if (user.blockQuery) {
|
||||
user.blockQuery = SearchQueryDTOUtils.negate(user.blockQuery);
|
||||
}
|
||||
if (user.allowQuery || user.blockQuery) {
|
||||
let query = user.allowQuery || user.blockQuery;
|
||||
if (user.allowQuery && user.blockQuery) {
|
||||
query = {
|
||||
type: SearchQueryTypes.AND,
|
||||
list: [
|
||||
user.allowQuery,
|
||||
user.blockQuery
|
||||
]
|
||||
} as ANDSearchQuery;
|
||||
}
|
||||
context.projectionQuery = await ObjectManagers.getInstance().SearchManager.prepareAndBuildWhereQuery(query);
|
||||
context.user.projectionKey = crypto.createHash('md5').update(JSON.stringify(query)).digest('hex');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
8
src/backend/model/SessionContext.ts
Normal file
8
src/backend/model/SessionContext.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import {Brackets} from 'typeorm';
|
||||
import {UserDTO} from '../../common/entities/UserDTO';
|
||||
|
||||
export class SessionContext {
|
||||
user: UserDTO;
|
||||
projectionQuery: Brackets;
|
||||
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import {SQLConnection} from './SQLConnection';
|
||||
import {PhotoEntity} from './enitites/PhotoEntity';
|
||||
import {ProjectPath} from '../../ProjectPath';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {Connection} from 'typeorm';
|
||||
import {Brackets, Connection} from 'typeorm';
|
||||
import {MediaEntity} from './enitites/MediaEntity';
|
||||
import {VideoEntity} from './enitites/VideoEntity';
|
||||
import {Logger} from '../../Logger';
|
||||
@@ -14,6 +14,7 @@ import {ObjectManagers} from '../ObjectManagers';
|
||||
import {DuplicatesDTO} from '../../../common/entities/DuplicatesDTO';
|
||||
import {ReIndexingSensitivity} from '../../../common/config/private/PrivateConfig';
|
||||
import {DiskManager} from '../fileaccess/DiskManager';
|
||||
import {SessionContext} from '../SessionContext';
|
||||
|
||||
const LOG_TAG = '[GalleryManager]';
|
||||
|
||||
@@ -32,6 +33,7 @@ export class GalleryManager {
|
||||
}
|
||||
|
||||
public async listDirectory(
|
||||
session: SessionContext,
|
||||
relativeDirectoryName: string,
|
||||
knownLastModified?: number,
|
||||
knownLastScanned?: number
|
||||
@@ -103,7 +105,7 @@ export class GalleryManager {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// not indexed since a while, index it in a lazy manner
|
||||
// not indexed since a while, index it lazily
|
||||
if (
|
||||
(Date.now() - dir.lastScanned >
|
||||
Config.Indexing.cachedFolderTimeout &&
|
||||
@@ -125,7 +127,7 @@ export class GalleryManager {
|
||||
.IndexingManager.indexDirectory(relativeDirectoryName)
|
||||
.catch(console.error);
|
||||
}
|
||||
return await this.getParentDirFromId(connection, dir.id);
|
||||
return await this.getParentDirFromId(connection, session, dir.id);
|
||||
}
|
||||
|
||||
// never scanned (deep indexed), do it and return with it
|
||||
@@ -339,8 +341,33 @@ export class GalleryManager {
|
||||
|
||||
protected async getParentDirFromId(
|
||||
connection: Connection,
|
||||
session: SessionContext,
|
||||
partialDirId: number
|
||||
): Promise<ParentDirectoryDTO> {
|
||||
|
||||
const mediaJoinCondition = () => {
|
||||
// Temporary QB to let Brackets build conditions and params
|
||||
const dummyQb = connection.createQueryBuilder();
|
||||
session.projectionQuery.whereFactory(dummyQb);
|
||||
|
||||
// Build condition string
|
||||
const sql = dummyQb.expressionMap.wheres
|
||||
.map(w => w.condition)
|
||||
.join(' ');
|
||||
|
||||
// Extract params that Brackets added
|
||||
const params = dummyQb.getParameters();
|
||||
|
||||
return {sql, params};
|
||||
};
|
||||
let sql = 'media.directoryId = directory.id';
|
||||
let params = {};
|
||||
if (session.projectionQuery) {
|
||||
const mj = mediaJoinCondition();
|
||||
sql += ' AND ' + mj.sql;
|
||||
params = mj.params;
|
||||
}
|
||||
|
||||
const query = connection
|
||||
.getRepository(DirectoryEntity)
|
||||
.createQueryBuilder('directory')
|
||||
@@ -348,7 +375,10 @@ export class GalleryManager {
|
||||
id: partialDirId
|
||||
})
|
||||
.leftJoinAndSelect('directory.directories', 'directories')
|
||||
.leftJoinAndSelect('directory.media', 'media')
|
||||
.leftJoinAndSelect(
|
||||
'directory.media',
|
||||
'media', sql, params
|
||||
)
|
||||
.leftJoinAndSelect('directories.cover', 'cover')
|
||||
.leftJoinAndSelect('cover.directory', 'coverDirectory')
|
||||
.select([
|
||||
|
||||
@@ -292,15 +292,16 @@ export class ConfigDiagnostics {
|
||||
settings.SearchQuery
|
||||
)
|
||||
) {
|
||||
throw new Error('SearchQuery is not valid. Got: ' + JSON.stringify(sp.parse(sp.stringify(settings.SearchQuery))));
|
||||
throw new Error('SearchQuery is not valid. Expected: ' + JSON.stringify(sp.parse(sp.stringify(settings.SearchQuery))) +
|
||||
' Got: ' + JSON.stringify(settings.SearchQuery));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes unsupported image formats.
|
||||
* It is possible that some OS support one or the other image formats (like Mac os does with HEIC)
|
||||
* , but others not.
|
||||
* Those formats are added to the config, but dynamically removed.
|
||||
* It is possible that some OS support one or the other image formats (like macOS does with HEIC),
|
||||
* but others do not.
|
||||
* Those formats are added to the config but dynamically removed.
|
||||
* @param config
|
||||
*/
|
||||
static async removeUnsupportedPhotoExtensions(config: ClientPhotoConfig): Promise<void> {
|
||||
@@ -328,7 +329,7 @@ export class ConfigDiagnostics {
|
||||
} as MediaRendererInput, true
|
||||
);
|
||||
} catch (e) {
|
||||
Logger.verbose(e)
|
||||
Logger.verbose(e);
|
||||
Logger.verbose(LOG_TAG, 'The current OS does not support the following photo format:' + ext + ', removing it form config.');
|
||||
config.supportedFormats.splice(i, 1);
|
||||
removedSome = true;
|
||||
|
||||
@@ -57,7 +57,7 @@ export class ExpressRouteWrapper implements IExtensionRESTRoute {
|
||||
this.router[this.func](fullPaths,
|
||||
...(this.getAuthMWs(minRole).concat([
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
req.resultPipe = await cb(req.params, req.body, req.session['user']);
|
||||
req.resultPipe = await cb(req.params, req.body, req.session.context.user);
|
||||
next();
|
||||
},
|
||||
RenderingMWs.renderResult
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as fs from 'fs';
|
||||
const LOG_TAG = '[ExtensionConfigWrapper]';
|
||||
|
||||
/**
|
||||
* Wraps to original config and makes sure all extension related config is loaded
|
||||
* Wraps to the original config and makes sure all extension-related configs are loaded
|
||||
*/
|
||||
export class ExtensionConfigWrapper {
|
||||
|
||||
|
||||
@@ -74,13 +74,14 @@ export class PublicRouter {
|
||||
res.tpl = {};
|
||||
|
||||
res.tpl.user = null;
|
||||
if (req.session['user']) {
|
||||
if (req.session.context?.user) {
|
||||
res.tpl.user = {
|
||||
id: req.session['user'].id,
|
||||
name: req.session['user'].name,
|
||||
role: req.session['user'].role,
|
||||
usedSharingKey: req.session['user'].usedSharingKey,
|
||||
permissions: req.session['user'].permissions,
|
||||
id: req.session.context.user.id,
|
||||
name: req.session.context.user.name,
|
||||
role: req.session.context.user.role,
|
||||
usedSharingKey: req.session.context.user.usedSharingKey,
|
||||
permissions:req.session.context.user.permissions,
|
||||
projectionKey:req.session.context.user.projectionKey,
|
||||
} as UserDTO;
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface UserDTO {
|
||||
role: UserRoles;
|
||||
usedSharingKey?: string;
|
||||
permissions: string[]; // user can only see these permissions. if ends with *, its recursive
|
||||
projectionKey?: string; // allow- and blocklist projection hash. if null, no projection
|
||||
}
|
||||
|
||||
export const UserDTOUtils = {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {TestHelper} from '../TestHelper';
|
||||
import {VideoDTO} from '../../src/common/entities/VideoDTO';
|
||||
import {PhotoDTO} from '../../src/common/entities/PhotoDTO';
|
||||
import {Logger} from '../../src/backend/Logger';
|
||||
import {SessionContext} from '../../src/backend/model/SessionContext';
|
||||
|
||||
declare let describe: any;
|
||||
const savedDescribe = describe;
|
||||
@@ -32,8 +33,8 @@ class GalleryManagerTest extends GalleryManager {
|
||||
return super.getDirIdAndTime(connection, directoryName, directoryParent);
|
||||
}
|
||||
|
||||
public async getParentDirFromId(connection: Connection, dir: number): Promise<ParentDirectoryDTO> {
|
||||
return super.getParentDirFromId(connection, dir);
|
||||
public async getParentDirFromId(connection: Connection, session: SessionContext, dir: number): Promise<ParentDirectoryDTO> {
|
||||
return super.getParentDirFromId(connection, session, dir);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -125,13 +126,14 @@ export class DBTestHelper {
|
||||
}
|
||||
|
||||
const gm = new GalleryManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
const dir = await gm.getParentDirFromId(connection,
|
||||
const dir = await gm.getParentDirFromId(connection, session,
|
||||
(await gm.getDirIdAndTime(connection, directory.name, path.join(directory.path, path.sep))).id);
|
||||
|
||||
const populateDir = async (d: DirectoryBaseDTO) => {
|
||||
for (let i = 0; i < d.directories.length; i++) {
|
||||
d.directories[i] = await gm.getParentDirFromId(connection,
|
||||
d.directories[i] = await gm.getParentDirFromId(connection, session,
|
||||
(await gm.getDirIdAndTime(connection, d.directories[i].name,
|
||||
path.join(DiskManager.pathFromParent(d), path.sep))).id);
|
||||
await populateDir(d.directories[i]);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers';
|
||||
import {Config} from '../../../../../src/common/config/private/Config';
|
||||
import * as path from 'path';
|
||||
import {UserManager} from '../../../../../src/backend/model/database/UserManager';
|
||||
import {SearchQueryTypes, TextSearchQueryMatchTypes} from '../../../../../src/common/entities/SearchQueryDTO';
|
||||
|
||||
|
||||
declare const describe: any;
|
||||
@@ -23,7 +24,9 @@ describe('Authentication middleware', () => {
|
||||
it('should call next on authenticated', (done: (err?: any) => void) => {
|
||||
const req: any = {
|
||||
session: {
|
||||
context: {
|
||||
user: 'A user'
|
||||
}
|
||||
},
|
||||
sessionOptions: {},
|
||||
query: {},
|
||||
@@ -72,7 +75,9 @@ describe('Authentication middleware', () => {
|
||||
|
||||
const req = {
|
||||
session: {
|
||||
context: {
|
||||
user: {permissions: null as string[]}
|
||||
}
|
||||
},
|
||||
sessionOptions: {},
|
||||
query: {},
|
||||
@@ -93,14 +98,14 @@ describe('Authentication middleware', () => {
|
||||
};
|
||||
|
||||
it('should catch unauthorized path usage', async () => {
|
||||
req.session['user'].permissions = [path.normalize('/sub/subsub')];
|
||||
req.session.context.user.permissions = [path.normalize('/sub/subsub')];
|
||||
expect(await test('/sub/subsub')).to.be.eql('ok');
|
||||
expect(await test('/test')).to.be.eql(403);
|
||||
expect(await test('/')).to.be.eql(403);
|
||||
expect(await test('/sub/test')).to.be.eql(403);
|
||||
expect(await test('/sub/subsub/test')).to.be.eql(403);
|
||||
expect(await test('/sub/subsub/test/test2')).to.be.eql(403);
|
||||
req.session['user'].permissions = [path.normalize('/sub/subsub'), path.normalize('/sub/subsub2')];
|
||||
req.session.context.user.permissions = [path.normalize('/sub/subsub'), path.normalize('/sub/subsub2')];
|
||||
expect(await test('/sub/subsub2')).to.be.eql('ok');
|
||||
expect(await test('/sub/subsub')).to.be.eql('ok');
|
||||
expect(await test('/test')).to.be.eql(403);
|
||||
@@ -108,7 +113,7 @@ describe('Authentication middleware', () => {
|
||||
expect(await test('/sub/test')).to.be.eql(403);
|
||||
expect(await test('/sub/subsub/test')).to.be.eql(403);
|
||||
expect(await test('/sub/subsub2/test')).to.be.eql(403);
|
||||
req.session['user'].permissions = [path.normalize('/sub/subsub*')];
|
||||
req.session.context.user.permissions = [path.normalize('/sub/subsub*')];
|
||||
expect(await test('/b')).to.be.eql(403);
|
||||
expect(await test('/sub')).to.be.eql(403);
|
||||
expect(await test('/sub/subsub2')).to.be.eql(403);
|
||||
@@ -144,7 +149,9 @@ describe('Authentication middleware', () => {
|
||||
it('should call next error on authenticated', (done: (err?: any) => void) => {
|
||||
const req: any = {
|
||||
session: {
|
||||
context: {
|
||||
user: 'A user'
|
||||
}
|
||||
},
|
||||
sessionOptions: {},
|
||||
};
|
||||
@@ -162,9 +169,11 @@ describe('Authentication middleware', () => {
|
||||
it('should call next on authorised', (done: (err?: any) => void) => {
|
||||
const req: any = {
|
||||
session: {
|
||||
context: {
|
||||
user: {
|
||||
role: UserRoles.LimitedGuest
|
||||
}
|
||||
}
|
||||
},
|
||||
sessionOptions: {}
|
||||
};
|
||||
@@ -183,9 +192,11 @@ describe('Authentication middleware', () => {
|
||||
it('should call next with error on not authorised', (done: (err?: any) => void) => {
|
||||
const req: any = {
|
||||
session: {
|
||||
context: {
|
||||
user: {
|
||||
role: UserRoles.LimitedGuest
|
||||
}
|
||||
}
|
||||
},
|
||||
sessionOptions: {}
|
||||
};
|
||||
@@ -200,8 +211,8 @@ describe('Authentication middleware', () => {
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
beforeEach(() => {
|
||||
ObjectManagers.reset();
|
||||
beforeEach(async () => {
|
||||
await ObjectManagers.reset();
|
||||
});
|
||||
|
||||
describe('should call input ErrorDTO next on missing...', () => {
|
||||
@@ -303,19 +314,226 @@ describe('Authentication middleware', () => {
|
||||
query: {},
|
||||
params: {}
|
||||
};
|
||||
const testUser = 'test user' as any;
|
||||
const testContext = {user: testUser};
|
||||
|
||||
const next: any = (err: ErrorDTO) => {
|
||||
expect(err).to.be.undefined;
|
||||
expect(req.session['user']).to.be.eql('test user');
|
||||
expect(req.session.context).to.be.eql(testContext);
|
||||
done();
|
||||
};
|
||||
|
||||
ObjectManagers.getInstance().UserManager = {
|
||||
findOne: (filter: never) => {
|
||||
return Promise.resolve('test user' as any);
|
||||
return Promise.resolve(testUser);
|
||||
}
|
||||
} as UserManager;
|
||||
|
||||
// Add SearchManager mock to avoid errors in buildContext
|
||||
ObjectManagers.getInstance().SearchManager = {
|
||||
prepareAndBuildWhereQuery: (query: any) => {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
} as any;
|
||||
|
||||
|
||||
AuthenticationMWs.login(req, null, next);
|
||||
});
|
||||
|
||||
it('should create context with allowQuery', (done: (err?: any) => void) => {
|
||||
const req: any = {
|
||||
session: {},
|
||||
body: {
|
||||
loginCredential: {
|
||||
username: 'aa',
|
||||
password: 'bb'
|
||||
}
|
||||
},
|
||||
query: {},
|
||||
params: {}
|
||||
};
|
||||
|
||||
const testUser = {
|
||||
name: 'testuser',
|
||||
role: UserRoles.Admin,
|
||||
allowQuery: {
|
||||
type: SearchQueryTypes.directory,
|
||||
text: '/allowed/path',
|
||||
matchType: TextSearchQueryMatchTypes.exact_match
|
||||
}
|
||||
};
|
||||
|
||||
const projectionQuery = {someQueryObject: true};
|
||||
const testContext = {
|
||||
user: testUser,
|
||||
projectionQuery: projectionQuery
|
||||
};
|
||||
|
||||
const next: any = (err: ErrorDTO) => {
|
||||
try {
|
||||
expect(err).to.be.undefined;
|
||||
expect(req.session?.context).to.be.eql(testContext);
|
||||
done();
|
||||
}catch (e){
|
||||
done(e);
|
||||
}
|
||||
};
|
||||
|
||||
ObjectManagers.getInstance().UserManager = {
|
||||
findOne: (filter: never) => {
|
||||
return Promise.resolve(testUser);
|
||||
}
|
||||
} as any;
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
ObjectManagers.getInstance().buildContext = async (user: any) => {
|
||||
expect(user).to.be.eql(testUser);
|
||||
return testContext;
|
||||
};
|
||||
|
||||
|
||||
AuthenticationMWs.login(req, null, next);
|
||||
});
|
||||
|
||||
it('should create context with blockQuery', (done: (err?: any) => void) => {
|
||||
const req: any = {
|
||||
session: {},
|
||||
body: {
|
||||
loginCredential: {
|
||||
username: 'aa',
|
||||
password: 'bb'
|
||||
}
|
||||
},
|
||||
query: {},
|
||||
params: {}
|
||||
};
|
||||
|
||||
const testUser = {
|
||||
name: 'testuser',
|
||||
role: UserRoles.Admin,
|
||||
blockQuery: {
|
||||
type: SearchQueryTypes.directory,
|
||||
text: '/blocked/path',
|
||||
matchType: TextSearchQueryMatchTypes.exact_match
|
||||
}
|
||||
};
|
||||
|
||||
const projectionQuery = {someQueryObject: true};
|
||||
const testContext = {
|
||||
user: testUser,
|
||||
projectionQuery: projectionQuery
|
||||
};
|
||||
|
||||
|
||||
const next: any = (err: ErrorDTO) => {
|
||||
try {
|
||||
expect(err).to.be.undefined;
|
||||
expect(req.session?.context).to.be.eql(testContext);
|
||||
done();
|
||||
}catch (e){
|
||||
done(e);
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
ObjectManagers.getInstance().UserManager = {
|
||||
findOne: (filter: never) => {
|
||||
return Promise.resolve(testUser);
|
||||
}
|
||||
} as UserManager;
|
||||
|
||||
ObjectManagers.getInstance().SearchManager = {
|
||||
prepareAndBuildWhereQuery: (query: any) => {
|
||||
// In real code, the blockQuery would be negated first
|
||||
expect(query).not.to.be.undefined;
|
||||
return Promise.resolve(projectionQuery);
|
||||
}
|
||||
} as any;
|
||||
|
||||
// @ts-ignore
|
||||
ObjectManagers.getInstance().buildContext = async (user: any) => {
|
||||
expect(user).to.be.eql(testUser);
|
||||
return testContext;
|
||||
};
|
||||
|
||||
AuthenticationMWs.login(req, null, next);
|
||||
});
|
||||
|
||||
it('should create context with both allowQuery and blockQuery', (done: (err?: any) => void) => {
|
||||
const req: any = {
|
||||
session: {},
|
||||
body: {
|
||||
loginCredential: {
|
||||
username: 'aa',
|
||||
password: 'bb'
|
||||
}
|
||||
},
|
||||
query: {},
|
||||
params: {}
|
||||
};
|
||||
|
||||
const testUser = {
|
||||
name: 'testuser',
|
||||
role: UserRoles.Admin,
|
||||
allowQuery: {
|
||||
type: SearchQueryTypes.directory,
|
||||
text: '/allowed/path',
|
||||
matchType: TextSearchQueryMatchTypes.exact_match
|
||||
},
|
||||
blockQuery: {
|
||||
type: SearchQueryTypes.directory,
|
||||
text: '/blocked/path',
|
||||
matchType: TextSearchQueryMatchTypes.exact_match
|
||||
}
|
||||
};
|
||||
|
||||
const projectionQuery = {someQueryObject: true};
|
||||
const testContext = {
|
||||
user: {
|
||||
...testUser,
|
||||
projectionKey: 'some-hash-value'
|
||||
},
|
||||
projectionQuery: projectionQuery
|
||||
};
|
||||
|
||||
const next: any = (err: ErrorDTO) => {
|
||||
try {
|
||||
expect(err).to.be.undefined;
|
||||
expect(req.session.context).to.be.eql(testContext);
|
||||
expect(req.session.context.user.projectionKey).not.to.be.undefined;
|
||||
done();
|
||||
}catch (e){
|
||||
done(e);
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
ObjectManagers.getInstance().UserManager = {
|
||||
findOne: (filter: never) => {
|
||||
return Promise.resolve({...testUser});
|
||||
}
|
||||
} as UserManager;
|
||||
|
||||
ObjectManagers.getInstance().SearchManager = {
|
||||
prepareAndBuildWhereQuery: (query: any) => {
|
||||
// In the real code, this would be an AND query combining allowQuery and negated blockQuery
|
||||
expect(query.type).to.be.eql(SearchQueryTypes.AND);
|
||||
expect(query.list).to.have.lengthOf(2);
|
||||
return Promise.resolve(projectionQuery);
|
||||
}
|
||||
} as any;
|
||||
|
||||
// @ts-ignore
|
||||
ObjectManagers.getInstance().buildContext = async (user: any) => {
|
||||
expect(user).to.deep.include({
|
||||
name: testUser.name,
|
||||
role: testUser.role
|
||||
});
|
||||
return testContext;
|
||||
};
|
||||
|
||||
AuthenticationMWs.login(req, null, next);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -324,15 +542,17 @@ describe('Authentication middleware', () => {
|
||||
it('should call next on logout', (done: (err?: any) => void) => {
|
||||
const req: any = {
|
||||
session: {
|
||||
context: {
|
||||
user: {
|
||||
role: UserRoles.LimitedGuest
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const next: any = (err: ErrorDTO) => {
|
||||
try {
|
||||
expect(err).to.be.undefined;
|
||||
expect(req.user).to.be.undefined;
|
||||
expect(req.session.context).to.be.undefined;
|
||||
done();
|
||||
} catch (err) {
|
||||
done(err);
|
||||
201
test/backend/unit/model/ObjectManagers.spec.ts
Normal file
201
test/backend/unit/model/ObjectManagers.spec.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
/* eslint-disable no-unused-expressions,@typescript-eslint/no-unused-expressions */
|
||||
import {expect} from 'chai';
|
||||
import {ObjectManagers} from '../../../../src/backend/model/ObjectManagers';
|
||||
import {UserEntity} from '../../../../src/backend/model/database/enitites/UserEntity';
|
||||
import {SearchQueryTypes, TextSearch, TextSearchQueryMatchTypes} from '../../../../src/common/entities/SearchQueryDTO';
|
||||
import {UserRoles} from '../../../../src/common/entities/UserDTO';
|
||||
import {Brackets} from 'typeorm';
|
||||
|
||||
declare const describe: any;
|
||||
declare const it: any;
|
||||
declare const beforeEach: any;
|
||||
declare const afterEach: any;
|
||||
|
||||
describe('ObjectManagers', () => {
|
||||
describe('buildContext', () => {
|
||||
|
||||
// Reset ObjectManagers before each test
|
||||
beforeEach(async () => {
|
||||
await ObjectManagers.reset();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await ObjectManagers.reset();
|
||||
});
|
||||
|
||||
it('should create a basic context with user and no queries', async () => {
|
||||
// Create a basic user with no queries
|
||||
const user = new UserEntity();
|
||||
user.id = 1;
|
||||
user.name = 'testuser';
|
||||
user.role = UserRoles.Admin;
|
||||
user.permissions = ['/test'];
|
||||
|
||||
// Create the context
|
||||
const context = await ObjectManagers.getInstance().buildContext(user);
|
||||
|
||||
// Verify the context
|
||||
expect(context).to.not.be.null;
|
||||
expect(context.user).to.be.eql(user);
|
||||
expect(context.projectionQuery).to.be.undefined;
|
||||
});
|
||||
|
||||
it('should create a context with allowQuery and set projectionQuery', async () => {
|
||||
// Create a user with allowQuery
|
||||
const user = new UserEntity();
|
||||
user.id = 2;
|
||||
user.name = 'allowuser';
|
||||
user.role = UserRoles.Admin;
|
||||
user.allowQuery = {
|
||||
type: SearchQueryTypes.directory,
|
||||
text: '/allowed/path',
|
||||
matchType: TextSearchQueryMatchTypes.exact_match
|
||||
} as TextSearch;
|
||||
|
||||
// Mock the SearchManager.prepareAndBuildWhereQuery method
|
||||
const mockProjectionQuery = new Brackets(qb => qb.where('directory.path = :path', {path: '/allowed/path'}));
|
||||
ObjectManagers.getInstance().SearchManager = {
|
||||
prepareAndBuildWhereQuery: (query: any) => {
|
||||
// Verify the query is passed correctly
|
||||
expect(query).to.be.eql(user.allowQuery);
|
||||
return Promise.resolve(mockProjectionQuery);
|
||||
}
|
||||
} as any;
|
||||
|
||||
// Create the context
|
||||
const context = await ObjectManagers.getInstance().buildContext(user);
|
||||
|
||||
// Verify the context
|
||||
expect(context).to.not.be.null;
|
||||
expect(context.user).to.be.eql(user);
|
||||
expect(context.projectionQuery).to.be.eql(mockProjectionQuery);
|
||||
expect(context.user.projectionKey).to.be.a('string').and.not.empty;
|
||||
});
|
||||
|
||||
it('should create a context with blockQuery, negate it, and set projectionQuery', async () => {
|
||||
// Create a user with blockQuery
|
||||
const user = new UserEntity();
|
||||
user.id = 3;
|
||||
user.name = 'blockuser';
|
||||
user.role = UserRoles.Admin;
|
||||
user.blockQuery = {
|
||||
type: SearchQueryTypes.directory,
|
||||
text: '/blocked/path',
|
||||
matchType: TextSearchQueryMatchTypes.exact_match,
|
||||
negate: false
|
||||
} as TextSearch;
|
||||
|
||||
// Clone the original blockQuery for later comparison
|
||||
const originalBlockQuery = JSON.parse(JSON.stringify(user.blockQuery));
|
||||
|
||||
// Mock the SearchManager.prepareAndBuildWhereQuery method
|
||||
const mockProjectionQuery = new Brackets(qb => qb.where('directory.path != :path', {path: '/blocked/path'}));
|
||||
ObjectManagers.getInstance().SearchManager = {
|
||||
prepareAndBuildWhereQuery: (query: any) => {
|
||||
// Verify the query has been negated
|
||||
expect(query).to.not.be.eql(originalBlockQuery);
|
||||
expect(query.negate).to.be.true;
|
||||
return Promise.resolve(mockProjectionQuery);
|
||||
}
|
||||
} as any;
|
||||
|
||||
// Create the context
|
||||
const context = await ObjectManagers.getInstance().buildContext(user);
|
||||
|
||||
// Verify the context
|
||||
expect(context).to.not.be.null;
|
||||
expect(context.user).to.be.eql(user);
|
||||
expect(context.projectionQuery).to.be.eql(mockProjectionQuery);
|
||||
expect(context.user.projectionKey).to.be.a('string').and.not.empty;
|
||||
// Verify the blockQuery was negated
|
||||
expect((user.blockQuery as TextSearch).negate).to.be.true;
|
||||
});
|
||||
|
||||
it('should create a context with both allowQuery and blockQuery, combine them with AND', async () => {
|
||||
// Create a user with both allowQuery and blockQuery
|
||||
const user = new UserEntity();
|
||||
user.id = 4;
|
||||
user.name = 'bothuser';
|
||||
user.role = UserRoles.Admin;
|
||||
user.allowQuery = {
|
||||
type: SearchQueryTypes.directory,
|
||||
text: '/allowed/path',
|
||||
matchType: TextSearchQueryMatchTypes.exact_match
|
||||
} as TextSearch;
|
||||
user.blockQuery = {
|
||||
type: SearchQueryTypes.directory,
|
||||
text: '/blocked/path',
|
||||
matchType: TextSearchQueryMatchTypes.exact_match,
|
||||
negate: false
|
||||
} as TextSearch;
|
||||
|
||||
// Clone the original queries for later comparison
|
||||
const originalAllowQuery = JSON.parse(JSON.stringify(user.allowQuery));
|
||||
const originalBlockQuery = JSON.parse(JSON.stringify(user.blockQuery));
|
||||
|
||||
// Mock the SearchManager.prepareAndBuildWhereQuery method
|
||||
const mockProjectionQuery = new Brackets(qb => qb.where('complex combined query'));
|
||||
ObjectManagers.getInstance().SearchManager = {
|
||||
prepareAndBuildWhereQuery: (query: any) => {
|
||||
// Verify the query is an AND combination of allowQuery and negated blockQuery
|
||||
expect(query.type).to.be.eql(SearchQueryTypes.AND);
|
||||
expect(query.list).to.have.lengthOf(2);
|
||||
expect(query.list[0]).to.be.eql(originalAllowQuery);
|
||||
expect(query.list[1].negate).to.be.true;
|
||||
return Promise.resolve(mockProjectionQuery);
|
||||
}
|
||||
} as any;
|
||||
|
||||
// Create the context
|
||||
const context = await ObjectManagers.getInstance().buildContext(user);
|
||||
|
||||
// Verify the context
|
||||
expect(context).to.not.be.null;
|
||||
expect(context.user).to.be.eql(user);
|
||||
expect(context.projectionQuery).to.be.eql(mockProjectionQuery);
|
||||
expect(context.user.projectionKey).to.be.a('string').and.not.empty;
|
||||
// Verify the blockQuery was negated
|
||||
expect((user.blockQuery as TextSearch).negate).to.be.true;
|
||||
});
|
||||
|
||||
it('should generate consistent projectionKey for the same query', async () => {
|
||||
// Create two identical users with the same query
|
||||
const user1 = new UserEntity();
|
||||
user1.id = 5;
|
||||
user1.name = 'user1';
|
||||
user1.role = UserRoles.Admin;
|
||||
user1.allowQuery = {
|
||||
type: SearchQueryTypes.directory,
|
||||
text: '/allowed/path',
|
||||
matchType: TextSearchQueryMatchTypes.exact_match
|
||||
} as TextSearch;
|
||||
|
||||
const user2 = new UserEntity();
|
||||
user2.id = 6;
|
||||
user2.name = 'user2';
|
||||
user2.role = UserRoles.Admin;
|
||||
user2.allowQuery = {
|
||||
type: SearchQueryTypes.directory,
|
||||
text: '/allowed/path',
|
||||
matchType: TextSearchQueryMatchTypes.exact_match
|
||||
} as TextSearch;
|
||||
|
||||
// Mock the SearchManager.prepareAndBuildWhereQuery method
|
||||
const mockProjectionQuery = new Brackets(qb => qb.where('directory.path = :path', {path: '/allowed/path'}));
|
||||
ObjectManagers.getInstance().SearchManager = {
|
||||
prepareAndBuildWhereQuery: (query: any) => {
|
||||
return Promise.resolve(mockProjectionQuery);
|
||||
}
|
||||
} as any;
|
||||
|
||||
// Create the contexts
|
||||
const context1 = await ObjectManagers.getInstance().buildContext(user1);
|
||||
const context2 = await ObjectManagers.getInstance().buildContext(user2);
|
||||
|
||||
// Verify both users have the same projectionKey despite being different users
|
||||
expect(context1.user.projectionKey).to.be.a('string').and.not.empty;
|
||||
expect(context2.user.projectionKey).to.be.a('string').and.not.empty;
|
||||
expect(context1.user.projectionKey).to.be.eql(context2.user.projectionKey);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -17,6 +17,7 @@ import {Utils} from '../../../../../src/common/Utils';
|
||||
import {SQLConnection} from '../../../../../src/backend/model/database/SQLConnection';
|
||||
import {DirectoryEntity} from '../../../../../src/backend/model/database/enitites/DirectoryEntity';
|
||||
import {ClientSortingConfig} from '../../../../../src/common/config/public/ClientConfig';
|
||||
import {SessionContext} from '../../../../../src/backend/model/SessionContext';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const deepEqualInAnyOrder = require('deep-equal-in-any-order');
|
||||
@@ -55,8 +56,8 @@ class GalleryManagerTest extends GalleryManager {
|
||||
return super.getDirIdAndTime(connection, directoryName, directoryParent);
|
||||
}
|
||||
|
||||
public async getParentDirFromId(connection: Connection, dir: number): Promise<ParentDirectoryDTO> {
|
||||
return super.getParentDirFromId(connection, dir);
|
||||
public async getParentDirFromId(connection: Connection, session: SessionContext, dir: number): Promise<ParentDirectoryDTO> {
|
||||
return super.getParentDirFromId(connection, session, dir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +273,7 @@ describe('CoverManager', (sqlHelper: DBTestHelper) => {
|
||||
expect(subdir.validCover).to.equal(true);
|
||||
expect(subdir.cover.id).to.equal(p2.id);
|
||||
|
||||
// new version should invalidate
|
||||
// The new version should invalidate
|
||||
await pm.onNewDataVersion(subDir as ParentDirectoryDTO);
|
||||
subdir = await selectDir();
|
||||
expect(subdir.validCover).to.equal(false);
|
||||
@@ -284,8 +285,9 @@ describe('CoverManager', (sqlHelper: DBTestHelper) => {
|
||||
.set({validCover: false, cover: null}).execute();
|
||||
expect((await selectDir()).cover).to.equal(null);
|
||||
|
||||
const session = new SessionContext();
|
||||
|
||||
const res = await gm.getParentDirFromId(conn,
|
||||
const res = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, dir.name, dir.path)).id);
|
||||
subdir = await selectDir();
|
||||
expect(subdir.validCover).to.equal(true);
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
import {DBTestHelper} from '../../../DBTestHelper';
|
||||
import {GalleryManager} from '../../../../../src/backend/model/database/GalleryManager';
|
||||
import {ParentDirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO';
|
||||
import {Connection} from 'typeorm';
|
||||
import {ParentDirectoryDTO, SubDirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO';
|
||||
import {Connection, Brackets} from 'typeorm';
|
||||
import {SessionContext} from '../../../../../src/backend/model/SessionContext';
|
||||
import {SearchQueryTypes, TextSearchQueryMatchTypes} from '../../../../../src/common/entities/SearchQueryDTO';
|
||||
import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers';
|
||||
import * as path from 'path';
|
||||
import {SQLConnection} from '../../../../../src/backend/model/database/SQLConnection';
|
||||
import {TestHelper} from '../../../../TestHelper';
|
||||
import {PhotoDTO} from '../../../../../src/common/entities/PhotoDTO';
|
||||
import {VideoDTO} from '../../../../../src/common/entities/VideoDTO';
|
||||
import {FileDTO} from '../../../../../src/common/entities/FileDTO';
|
||||
import {Utils} from '../../../../../src/common/Utils';
|
||||
import {UserEntity} from '../../../../../src/backend/model/database/enitites/UserEntity';
|
||||
import {Config} from '../../../../../src/common/config/private/Config';
|
||||
import {SQLLogLevel} from '../../../../../src/common/config/private/PrivateConfig';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const deepEqualInAnyOrder = require('deep-equal-in-any-order');
|
||||
@@ -15,6 +28,9 @@ const {expect} = chai;
|
||||
declare let describe: any;
|
||||
declare const before: any;
|
||||
declare const after: any;
|
||||
declare const beforeEach: any;
|
||||
declare const afterEach: any;
|
||||
declare const it: any;
|
||||
const tmpDescribe = describe;
|
||||
describe = DBTestHelper.describe();
|
||||
|
||||
@@ -25,12 +41,191 @@ class GalleryManagerTest extends GalleryManager {
|
||||
return super.getDirIdAndTime(connection, directoryName, directoryParent);
|
||||
}
|
||||
|
||||
public async getParentDirFromId(connection: Connection, dir: number): Promise<ParentDirectoryDTO> {
|
||||
return super.getParentDirFromId(connection, dir);
|
||||
public async getParentDirFromId(connection: Connection, session: SessionContext, dir: number): Promise<ParentDirectoryDTO> {
|
||||
return super.getParentDirFromId(connection, session, dir);
|
||||
}
|
||||
}
|
||||
|
||||
describe('GalleryManager', (sqlHelper: DBTestHelper) => {
|
||||
describe = tmpDescribe;
|
||||
|
||||
let galleryManager: GalleryManagerTest;
|
||||
let connection: Connection;
|
||||
let dir: ParentDirectoryDTO;
|
||||
let subDir: SubDirectoryDTO;
|
||||
let subDir2: SubDirectoryDTO;
|
||||
let p: PhotoDTO;
|
||||
let p2: PhotoDTO;
|
||||
let p3: PhotoDTO;
|
||||
let p4: PhotoDTO;
|
||||
let v: VideoDTO;
|
||||
let gpx: FileDTO;
|
||||
|
||||
const setUpGalleryTest = async (): Promise<void> => {
|
||||
// Create base directory structure
|
||||
const directory: ParentDirectoryDTO = TestHelper.getDirectoryEntry();
|
||||
subDir = TestHelper.getDirectoryEntry(directory, 'The Phantom Menace');
|
||||
subDir2 = TestHelper.getDirectoryEntry(directory, 'Return of the Jedi');
|
||||
|
||||
// Create photo entries with specific names to match the test cases
|
||||
p = TestHelper.getPhotoEntry1(directory);
|
||||
p.name = 'photo1.jpg'; // Ensure we have a photo1 for the test case
|
||||
p.metadata.creationDate = Date.now();
|
||||
p.metadata.creationDateOffset = '+02:00';
|
||||
|
||||
p2 = TestHelper.getPhotoEntry2(directory);
|
||||
p2.name = 'photo2.jpg';
|
||||
p2.metadata.creationDate = Date.now() - 60 * 60 * 24 * 1000; // 1 day ago
|
||||
p2.metadata.creationDateOffset = '+02:00';
|
||||
|
||||
// Create video entry
|
||||
v = TestHelper.getVideoEntry1(directory);
|
||||
v.metadata.creationDate = Date.now() - 60 * 60 * 24 * 7 * 1000; // 1 week ago
|
||||
v.metadata.creationDateOffset = '+02:00';
|
||||
|
||||
// Create a GPX file
|
||||
gpx = TestHelper.getRandomizedGPXEntry(directory);
|
||||
|
||||
// Create photos in subdirectories
|
||||
p3 = TestHelper.getPhotoEntry3(subDir);
|
||||
p3.name = 'photo3.jpg';
|
||||
let d = new Date();
|
||||
d = Utils.addMonthToDate(d, -1); // 1 month ago
|
||||
d.setDate(d.getDate() - 1); // minus 1 day
|
||||
p3.metadata.creationDate = d.getTime();
|
||||
p3.metadata.creationDateOffset = '+02:00';
|
||||
|
||||
p4 = TestHelper.getPhotoEntry4(subDir2);
|
||||
p4.name = 'photo4.jpg';
|
||||
d = new Date();
|
||||
// Set creation date to one year and one day earlier
|
||||
p4.metadata.creationDate = d.getTime() - 60 * 60 * 24 * (Utils.isDateFromLeapYear(d) ? 367 : 366) * 1000;
|
||||
p4.metadata.creationDateOffset = '+02:00';
|
||||
|
||||
// Persist the directory structure to the database
|
||||
dir = await DBTestHelper.persistTestDir(directory);
|
||||
subDir = dir.directories[0];
|
||||
subDir2 = dir.directories[1];
|
||||
|
||||
// Get the media items from the persisted directory
|
||||
p = (dir.media.filter(m => m.name === p.name)[0] as any);
|
||||
p.directory = dir;
|
||||
|
||||
p2 = (dir.media.filter(m => m.name === p2.name)[0] as any);
|
||||
p2.directory = dir;
|
||||
|
||||
v = (dir.media.filter(m => m.name === v.name)[0] as any);
|
||||
v.directory = dir;
|
||||
|
||||
if (dir.metaFile && dir.metaFile.length > 0) {
|
||||
gpx = (dir.metaFile[0] as any);
|
||||
gpx.directory = dir;
|
||||
}
|
||||
|
||||
p3 = (dir.directories[0].media[0] as any);
|
||||
p3.directory = dir.directories[0];
|
||||
|
||||
p4 = (dir.directories[1].media[0] as any);
|
||||
p4.directory = dir.directories[1];
|
||||
};
|
||||
|
||||
const setUpSqlDB = async () => {
|
||||
await sqlHelper.initDB();
|
||||
await setUpGalleryTest();
|
||||
await ObjectManagers.getInstance().init();
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
await setUpSqlDB();
|
||||
galleryManager = new GalleryManagerTest();
|
||||
connection = await SQLConnection.getConnection();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await sqlHelper.clearDB();
|
||||
});
|
||||
|
||||
describe('Projection Query Tests', () => {
|
||||
|
||||
it('should return all media when no projection query is provided', async () => {
|
||||
// Setup a session with no projection query
|
||||
const session = new SessionContext();
|
||||
|
||||
// Use the already persisted directory from our setup
|
||||
expect(dir).to.not.be.null;
|
||||
expect(dir.media).to.not.be.empty;
|
||||
|
||||
// Get the directory contents without any filtering using our directory's name and path
|
||||
const dirInfo = await galleryManager.getDirIdAndTime(connection, dir.name, dir.path);
|
||||
expect(dirInfo).to.not.be.null;
|
||||
|
||||
const directory = await galleryManager.getParentDirFromId(connection, session, dirInfo.id);
|
||||
|
||||
// Verify that all media is returned
|
||||
expect(directory.media).to.not.be.empty;
|
||||
const originalMediaCount = directory.media.length;
|
||||
expect(originalMediaCount).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('should filter media based on the projection query', async () => {
|
||||
|
||||
// Create a projection query that filters based on media name
|
||||
const searchQuery = {
|
||||
type: SearchQueryTypes.file_name,
|
||||
text: 'photo1', // We've named our test photo specifically with this pattern
|
||||
matchType: TextSearchQueryMatchTypes.like
|
||||
};
|
||||
const session = await ObjectManagers.getInstance().buildContext({allowQuery: searchQuery} as any);
|
||||
|
||||
|
||||
// Use the already persisted directory from our setup
|
||||
expect(dir).to.not.be.null;
|
||||
|
||||
// Get the directory contents with filtering using our directory's name and path
|
||||
const dirInfo = await galleryManager.getDirIdAndTime(connection, dir.name, dir.path);
|
||||
expect(dirInfo).to.not.be.null;
|
||||
|
||||
// Get the directory contents with filtering
|
||||
const directory = await galleryManager.getParentDirFromId(connection, session, dirInfo.id);
|
||||
|
||||
// Verify that only media matching the query is returned
|
||||
expect(directory.media).to.not.be.undefined;
|
||||
|
||||
// All returned media should match the query (name contains 'photo1')
|
||||
if (directory.media.length > 0) {
|
||||
directory.media.forEach(media => {
|
||||
expect(media.name.toLowerCase()).to.include('photo1');
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
it('should return directory with empty media when projection filters out all media', async () => {
|
||||
|
||||
const searchQuery = {
|
||||
type: SearchQueryTypes.file_name,
|
||||
text: 'this_file_name_does_not_exist_anywhere',
|
||||
matchType: TextSearchQueryMatchTypes.like
|
||||
};
|
||||
|
||||
|
||||
const session = await ObjectManagers.getInstance().buildContext({allowQuery: searchQuery} as any);
|
||||
|
||||
|
||||
const dirInfo = await galleryManager.getDirIdAndTime(connection, dir.name, dir.path);
|
||||
expect(dirInfo).to.not.be.null;
|
||||
|
||||
const directory = await galleryManager.getParentDirFromId(connection, session, dirInfo.id);
|
||||
|
||||
// The directory itself should still be returned
|
||||
expect(directory).to.not.be.undefined;
|
||||
expect(directory).to.not.be.null;
|
||||
|
||||
// And media should be an empty array when nothing matches the projection
|
||||
expect(directory.media).to.be.an('array');
|
||||
expect(directory.media.length).to.equal(0);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ import {AlbumManager} from '../../../../../src/backend/model/database/AlbumManag
|
||||
import {SortByTypes} from '../../../../../src/common/entities/SortingMethods';
|
||||
import {ClientSortingConfig} from '../../../../../src/common/config/public/ClientConfig';
|
||||
import {DiskManager} from '../../../../../src/backend/model/fileaccess/DiskManager';
|
||||
import {SessionContext} from '../../../../../src/backend/model/SessionContext';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const deepEqualInAnyOrder = require('deep-equal-in-any-order');
|
||||
@@ -34,8 +35,8 @@ class GalleryManagerTest extends GalleryManager {
|
||||
return super.getDirIdAndTime(connection, directoryName, directoryParent);
|
||||
}
|
||||
|
||||
public async getParentDirFromId(connection: Connection, dir: number): Promise<ParentDirectoryDTO> {
|
||||
return super.getParentDirFromId(connection, dir);
|
||||
public async getParentDirFromId(connection: Connection, session: SessionContext, dir: number): Promise<ParentDirectoryDTO> {
|
||||
return super.getParentDirFromId(connection, session, dir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +136,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
it('should support case sensitive file names', async () => {
|
||||
const gm = new GalleryManagerTest();
|
||||
const im = new IndexingManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
const parent = TestHelper.getRandomizedDirectoryEntry();
|
||||
const p1 = TestHelper.getRandomizedPhotoEntry(parent, 'Photo1');
|
||||
@@ -146,7 +148,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
await im.saveToDB(Utils.clone(parent) as ParentDirectoryDTO);
|
||||
|
||||
const conn = await SQLConnection.getConnection();
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
|
||||
|
||||
DirectoryDTOUtils.removeReferences(selected);
|
||||
@@ -158,6 +160,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
|
||||
it('should stop indexing on empty folder', async () => {
|
||||
const gm = new GalleryManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
ProjectPath.reset();
|
||||
ProjectPath.ImageFolder = path.join(__dirname, '/../../../assets');
|
||||
@@ -171,7 +174,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
'.'
|
||||
);
|
||||
const conn = await SQLConnection.getConnection();
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, directoryPath.name, directoryPath.parent)).id);
|
||||
|
||||
|
||||
@@ -200,6 +203,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
it('should support case sensitive directory', async () => {
|
||||
const gm = new GalleryManagerTest();
|
||||
const im = new IndexingManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
const parent = TestHelper.getRandomizedDirectoryEntry(null, 'parent');
|
||||
const subDir1 = TestHelper.getRandomizedDirectoryEntry(parent, 'subDir');
|
||||
@@ -213,7 +217,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
|
||||
const conn = await SQLConnection.getConnection();
|
||||
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
|
||||
|
||||
DirectoryDTOUtils.removeReferences(selected);
|
||||
@@ -227,6 +231,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
it('should support case sensitive directory path', async () => {
|
||||
const gm = new GalleryManagerTest();
|
||||
const im = new IndexingManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
const parent1 = TestHelper.getRandomizedDirectoryEntry(null, 'parent');
|
||||
const parent2 = TestHelper.getRandomizedDirectoryEntry(null, 'PARENT');
|
||||
@@ -243,7 +248,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
|
||||
const conn = await SQLConnection.getConnection();
|
||||
{
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, parent1.name, parent1.path)).id);
|
||||
|
||||
DirectoryDTOUtils.removeReferences(selected);
|
||||
@@ -253,7 +258,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
.to.deep.equalInAnyOrder(Utils.removeNullOrEmptyObj(indexifyReturn(parent1)));
|
||||
}
|
||||
{
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, parent2.name, parent2.path)).id);
|
||||
|
||||
DirectoryDTOUtils.removeReferences(selected);
|
||||
@@ -267,6 +272,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
it('should support emoji in names', async () => {
|
||||
const gm = new GalleryManagerTest();
|
||||
const im = new IndexingManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
const parent = TestHelper.getRandomizedDirectoryEntry(null, 'parent dir 😀');
|
||||
const p1 = TestHelper.getRandomizedPhotoEntry(parent, 'Photo1');
|
||||
@@ -277,7 +283,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
|
||||
const conn = await SQLConnection.getConnection();
|
||||
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
|
||||
|
||||
DirectoryDTOUtils.removeReferences(selected);
|
||||
@@ -290,7 +296,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
it('should select cover', async () => {
|
||||
const selectDirectory = async (gmTest: GalleryManagerTest, dir: DirectoryBaseDTO): Promise<ParentDirectoryDTO> => {
|
||||
const conn = await SQLConnection.getConnection();
|
||||
const selected = await gmTest.getParentDirFromId(conn,
|
||||
const session = new SessionContext();
|
||||
const selected = await gmTest.getParentDirFromId(conn, session,
|
||||
(await gmTest.getDirIdAndTime(conn, dir.name, dir.path)).id);
|
||||
|
||||
DirectoryDTOUtils.removeReferences(selected);
|
||||
@@ -346,6 +353,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
it('should save parent after child', async () => {
|
||||
const gm = new GalleryManagerTest();
|
||||
const im = new IndexingManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
const parent = TestHelper.getRandomizedDirectoryEntry(null, 'parentDir');
|
||||
const p1 = TestHelper.getRandomizedPhotoEntry(parent, 'Photo1');
|
||||
@@ -370,7 +378,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
|
||||
const conn = await SQLConnection.getConnection();
|
||||
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
|
||||
|
||||
DirectoryDTOUtils.removeReferences(selected);
|
||||
@@ -384,6 +392,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
it('should save root parent after child', async () => {
|
||||
const gm = new GalleryManagerTest();
|
||||
const im = new IndexingManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
const parent = TestHelper.getRandomizedDirectoryEntry(null, '.');
|
||||
const p1 = TestHelper.getRandomizedPhotoEntry(parent, 'Photo1');
|
||||
@@ -408,7 +417,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
await im.saveToDB(Utils.clone(parent) as ParentDirectoryDTO);
|
||||
|
||||
const conn = await SQLConnection.getConnection();
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
|
||||
|
||||
DirectoryDTOUtils.removeReferences(selected);
|
||||
@@ -421,6 +430,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
it('should save parent directory', async () => {
|
||||
const gm = new GalleryManagerTest();
|
||||
const im = new IndexingManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
const parent = TestHelper.getRandomizedDirectoryEntry();
|
||||
const p1 = TestHelper.getRandomizedPhotoEntry(parent, 'Photo1');
|
||||
@@ -438,7 +448,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
await im.saveToDB(Utils.clone(parent) as ParentDirectoryDTO);
|
||||
|
||||
const conn = await SQLConnection.getConnection();
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
|
||||
|
||||
DirectoryDTOUtils.removeReferences(selected);
|
||||
@@ -452,6 +462,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
it('should save photos with extreme parameters', async () => {
|
||||
const gm = new GalleryManagerTest();
|
||||
const im = new IndexingManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
const parent = TestHelper.getRandomizedDirectoryEntry();
|
||||
const p1 = TestHelper.getRandomizedPhotoEntry(parent, 'Photo1');
|
||||
@@ -474,7 +485,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
await im.saveToDB(Utils.clone(parent) as ParentDirectoryDTO);
|
||||
|
||||
const conn = await SQLConnection.getConnection();
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
|
||||
|
||||
DirectoryDTOUtils.removeReferences(selected);
|
||||
@@ -486,6 +497,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
it('should skip meta files', async () => {
|
||||
const gm = new GalleryManagerTest();
|
||||
const im = new IndexingManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
const parent = TestHelper.getRandomizedDirectoryEntry();
|
||||
const p1 = TestHelper.getRandomizedPhotoEntry(parent, 'Photo1');
|
||||
const p2 = TestHelper.getRandomizedPhotoEntry(parent, 'Photo2');
|
||||
@@ -500,7 +513,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
Config.MetaFile.markdown = false;
|
||||
Config.MetaFile.pg2conf = false;
|
||||
const conn = await SQLConnection.getConnection();
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
|
||||
|
||||
delete parent.metaFile;
|
||||
@@ -513,6 +526,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
it('should update sub directory', async () => {
|
||||
const gm = new GalleryManagerTest();
|
||||
const im = new IndexingManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
const parent = TestHelper.getRandomizedDirectoryEntry();
|
||||
parent.name = 'parent';
|
||||
@@ -531,7 +545,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
await im.saveToDB(Utils.clone(subDir) as ParentDirectoryDTO);
|
||||
|
||||
const conn = await SQLConnection.getConnection();
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, subDir.name, subDir.path)).id);
|
||||
|
||||
// subDir.isPartial = true;
|
||||
@@ -549,6 +563,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
const conn = await SQLConnection.getConnection();
|
||||
const gm = new GalleryManagerTest();
|
||||
const im = new IndexingManagerTest();
|
||||
const session = new SessionContext();
|
||||
Config.MetaFile.gpx = true;
|
||||
Config.MetaFile.markdown = true;
|
||||
Config.MetaFile.pg2conf = true;
|
||||
@@ -571,7 +586,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
|
||||
await Promise.all([s1, s2, s3]);
|
||||
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
|
||||
|
||||
DirectoryDTOUtils.removeReferences(selected);
|
||||
@@ -587,6 +602,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
it('should reset DB', async () => {
|
||||
const gm = new GalleryManagerTest();
|
||||
const im = new IndexingManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
const parent = TestHelper.getRandomizedDirectoryEntry();
|
||||
const p1 = TestHelper.getRandomizedPhotoEntry(parent, 'Photo1');
|
||||
@@ -596,7 +612,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
await im.saveToDB(Utils.clone(parent) as ParentDirectoryDTO);
|
||||
|
||||
const conn = await SQLConnection.getConnection();
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, parent.name, parent.path)).id);
|
||||
|
||||
DirectoryDTOUtils.removeReferences(selected);
|
||||
@@ -614,6 +630,8 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
const conn = await SQLConnection.getConnection();
|
||||
const gm = new GalleryManagerTest();
|
||||
const im = new IndexingManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
Config.MetaFile.gpx = true;
|
||||
Config.MetaFile.markdown = true;
|
||||
Config.MetaFile.pg2conf = true;
|
||||
@@ -628,7 +646,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
DirectoryDTOUtils.removeReferences(parent);
|
||||
await im.saveToDB(subDir as ParentDirectoryDTO);
|
||||
|
||||
const selected = await gm.getParentDirFromId(conn,
|
||||
const selected = await gm.getParentDirFromId(conn, session,
|
||||
(await gm.getDirIdAndTime(conn, subDir.name, subDir.path)).id);
|
||||
expect(selected.media.length).to.equal(subDir.media.length);
|
||||
}) as any).timeout(40000);
|
||||
@@ -641,12 +659,13 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
ProjectPath.ImageFolder = path.join(__dirname, '/../../../assets');
|
||||
const im = new IndexingManagerTest();
|
||||
const gm = new GalleryManagerTest();
|
||||
const session = new SessionContext();
|
||||
|
||||
const d = await DiskManager.scanDirectory('/');
|
||||
|
||||
await im.saveToDB(d);
|
||||
|
||||
const dir = await gm.listDirectory('/');
|
||||
const dir = await gm.listDirectory(session, '/');
|
||||
expect(dir.metaFile).to.be.an('array');
|
||||
|
||||
expect(dir.metaFile).to.be.deep.equal([
|
||||
@@ -687,6 +706,7 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
// @ts-ignore
|
||||
fs.statSync = () => ({ctime: new Date(dirTime), mtime: new Date(dirTime)});
|
||||
const gm = new GalleryManagerTest();
|
||||
const session = new SessionContext();
|
||||
gm.getDirIdAndTime = () => {
|
||||
return Promise.resolve(indexedTime as any);
|
||||
};
|
||||
@@ -699,15 +719,15 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
||||
};
|
||||
|
||||
indexedTime.lastScanned = null;
|
||||
expect(await gm.listDirectory('./')).to.be.equal('indexing');
|
||||
expect(await gm.listDirectory(session, './')).to.be.equal('indexing');
|
||||
indexedTime.lastModified = 0;
|
||||
dirTime = 1;
|
||||
expect(await gm.listDirectory('./')).to.be.equal('indexing');
|
||||
expect(await gm.listDirectory(session, './')).to.be.equal('indexing');
|
||||
indexedTime.lastScanned = 10;
|
||||
indexedTime.lastModified = 1;
|
||||
dirTime = 1;
|
||||
expect(await gm.listDirectory('./')).to.be.equal(indexedTime);
|
||||
expect(await gm.listDirectory('./', 1, 10))
|
||||
expect(await gm.listDirectory(session, './')).to.be.equal(indexedTime);
|
||||
expect(await gm.listDirectory(session, './', 1, 10))
|
||||
.to.be.equal(null);
|
||||
|
||||
|
||||
|
||||
@@ -25,12 +25,9 @@ import {
|
||||
TextSearchQueryMatchTypes,
|
||||
ToDateSearch
|
||||
} from '../../../../../src/common/entities/SearchQueryDTO';
|
||||
import {IndexingManager} from '../../../../../src/backend/model/database/IndexingManager';
|
||||
import {DirectoryBaseDTO, ParentDirectoryDTO, SubDirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO';
|
||||
import {TestHelper} from '../../../../TestHelper';
|
||||
import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers';
|
||||
import {GalleryManager} from '../../../../../src/backend/model/database/GalleryManager';
|
||||
import {Connection} from 'typeorm';
|
||||
import {GPSMetadata, PhotoDTO, PhotoMetadata} from '../../../../../src/common/entities/PhotoDTO';
|
||||
import {VideoDTO} from '../../../../../src/common/entities/VideoDTO';
|
||||
import {AutoCompleteItem} from '../../../../../src/common/entities/AutoCompleteItem';
|
||||
@@ -38,7 +35,6 @@ import {Config} from '../../../../../src/common/config/private/Config';
|
||||
import {SearchQueryParser} from '../../../../../src/common/SearchQueryParser';
|
||||
import {FileDTO} from '../../../../../src/common/entities/FileDTO';
|
||||
import {SortByTypes} from '../../../../../src/common/entities/SortingMethods';
|
||||
import {LogLevel} from '../../../../../src/common/config/private/PrivateConfig';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const deepEqualInAnyOrder = require('deep-equal-in-any-order');
|
||||
@@ -56,13 +52,6 @@ const tmpDescribe = describe;
|
||||
describe = DBTestHelper.describe(); // fake it os IDE plays nicely (recognize the test)
|
||||
|
||||
|
||||
class IndexingManagerTest extends IndexingManager {
|
||||
|
||||
public async saveToDB(scannedDirectory: ParentDirectoryDTO): Promise<void> {
|
||||
return super.saveToDB(scannedDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
class SearchManagerTest extends SearchManager {
|
||||
|
||||
public flattenSameOfQueries(query: SearchQueryDTO): SearchQueryDTO {
|
||||
@@ -71,16 +60,6 @@ class SearchManagerTest extends SearchManager {
|
||||
|
||||
}
|
||||
|
||||
class GalleryManagerTest extends GalleryManager {
|
||||
|
||||
public async getDirIdAndTime(connection: Connection, directoryName: string, directoryParent: string) {
|
||||
return super.getDirIdAndTime(connection, directoryName, directoryParent);
|
||||
}
|
||||
|
||||
public async getParentDirFromId(connection: Connection, dir: number): Promise<ParentDirectoryDTO> {
|
||||
return super.getParentDirFromId(connection, dir);
|
||||
}
|
||||
}
|
||||
|
||||
describe('SearchManager', (sqlHelper: DBTestHelper) => {
|
||||
describe = tmpDescribe;
|
||||
@@ -115,19 +94,19 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
|
||||
subDir2 = TestHelper.getDirectoryEntry(directory, 'Return of the Jedi');
|
||||
p = TestHelper.getPhotoEntry1(directory);
|
||||
p.metadata.creationDate = Date.now();
|
||||
p.metadata.creationDateOffset = "+02:00";
|
||||
p.metadata.creationDateOffset = '+02:00';
|
||||
p2 = TestHelper.getPhotoEntry2(directory);
|
||||
p2.metadata.creationDate = Date.now() - 60 * 60 * 24 * 1000;
|
||||
p2.metadata.creationDateOffset = "+02:00";
|
||||
p2.metadata.creationDateOffset = '+02:00';
|
||||
v = TestHelper.getVideoEntry1(directory);
|
||||
v.metadata.creationDate = Date.now() - 60 * 60 * 24 * 7 * 1000;
|
||||
v.metadata.creationDateOffset = "+02:00";
|
||||
v.metadata.creationDateOffset = '+02:00';
|
||||
gpx = TestHelper.getRandomizedGPXEntry(directory);
|
||||
p4 = TestHelper.getPhotoEntry4(subDir2);
|
||||
let d = new Date();
|
||||
//set creation date to one year and one day earlier
|
||||
p4.metadata.creationDate = d.getTime() - 60 * 60 * 24 * (Utils.isDateFromLeapYear(d) ? 367 : 366) * 1000;
|
||||
p4.metadata.creationDateOffset = "+02:00";
|
||||
p4.metadata.creationDateOffset = '+02:00';
|
||||
const pFaceLessTmp = TestHelper.getPhotoEntry3(subDir);
|
||||
delete pFaceLessTmp.metadata.faces;
|
||||
d = new Date();
|
||||
@@ -135,7 +114,7 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => {
|
||||
d = Utils.addMonthToDate(d, -1); //subtract 1 month in the "human way"
|
||||
d.setDate(d.getDate() - 1); //subtract 1 day
|
||||
pFaceLessTmp.metadata.creationDate = d.getTime();
|
||||
pFaceLessTmp.metadata.creationDateOffset = "+02:00";
|
||||
pFaceLessTmp.metadata.creationDateOffset = '+02:00';
|
||||
|
||||
dir = await DBTestHelper.persistTestDir(directory);
|
||||
subDir = dir.directories[0];
|
||||
|
||||
Reference in New Issue
Block a user