1
0
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:
Patrik J. Braun
2025-08-14 16:59:07 +02:00
parent b660b3c9f8
commit 602a3a08c0
24 changed files with 1007 additions and 312 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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]';
@@ -51,8 +55,8 @@ export class ObjectManagers {
Logger.silly(LOG_TAG, 'Object manager reset begin');
if (ObjectManagers.isReady()) {
if (
ObjectManagers.getInstance().IndexingManager &&
ObjectManagers.getInstance().IndexingManager.IsSavingInProgress
ObjectManagers.getInstance().IndexingManager &&
ObjectManagers.getInstance().IndexingManager.IsSavingInProgress
) {
await ObjectManagers.getInstance().IndexingManager.SavingReady;
}
@@ -111,7 +115,7 @@ export class ObjectManagers {
}
public async onDataChange(
changedDir: ParentDirectoryDTO = null
changedDir: ParentDirectoryDTO = null
): Promise<void> {
await this.VersionManager.onNewDataVersion();
@@ -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;
}
}

View File

@@ -0,0 +1,8 @@
import {Brackets} from 'typeorm';
import {UserDTO} from '../../common/entities/UserDTO';
export class SessionContext {
user: UserDTO;
projectionQuery: Brackets;
}

View File

@@ -6,14 +6,15 @@ 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';
import {ObjectManagers} from '../ObjectManagers';
import {DuplicatesDTO} from '../../../common/entities/DuplicatesDTO';
import {ReIndexingSensitivity} from '../../../common/config/private/PrivateConfig';
import { DiskManager } from '../fileaccess/DiskManager';
import {DiskManager} from '../fileaccess/DiskManager';
import {SessionContext} from '../SessionContext';
const LOG_TAG = '[GalleryManager]';
@@ -23,7 +24,7 @@ export class GalleryManager {
parent: string;
} {
relativeDirectoryName = DiskManager.normalizeDirPath(
relativeDirectoryName
relativeDirectoryName
);
return {
name: path.basename(relativeDirectoryName),
@@ -32,12 +33,13 @@ export class GalleryManager {
}
public async listDirectory(
relativeDirectoryName: string,
knownLastModified?: number,
knownLastScanned?: number
session: SessionContext,
relativeDirectoryName: string,
knownLastModified?: number,
knownLastScanned?: number
): Promise<ParentDirectoryDTO> {
const directoryPath = GalleryManager.parseRelativeDirePath(
relativeDirectoryName
relativeDirectoryName
);
const connection = await SQLConnection.getConnection();
@@ -48,35 +50,35 @@ export class GalleryManager {
// Return as soon as possible without touching the original data source (hdd)
// See https://github.com/bpatrik/pigallery2/issues/613
if (
Config.Indexing.reIndexingSensitivity ===
ReIndexingSensitivity.never
Config.Indexing.reIndexingSensitivity ===
ReIndexingSensitivity.never
) {
return null;
}
const stat = fs.statSync(
path.join(ProjectPath.ImageFolder, relativeDirectoryName)
path.join(ProjectPath.ImageFolder, relativeDirectoryName)
);
const lastModified = DiskManager.calcLastModified(stat);
// If it seems that the content did not change, do not work on it
if (
knownLastModified &&
knownLastScanned &&
lastModified === knownLastModified &&
dir.lastScanned === knownLastScanned
knownLastModified &&
knownLastScanned &&
lastModified === knownLastModified &&
dir.lastScanned === knownLastScanned
) {
if (
Config.Indexing.reIndexingSensitivity ===
ReIndexingSensitivity.low
Config.Indexing.reIndexingSensitivity ===
ReIndexingSensitivity.low
) {
return null;
}
if (
Date.now() - dir.lastScanned <=
Config.Indexing.cachedFolderTimeout &&
Config.Indexing.reIndexingSensitivity ===
ReIndexingSensitivity.medium
Date.now() - dir.lastScanned <=
Config.Indexing.cachedFolderTimeout &&
Config.Indexing.reIndexingSensitivity ===
ReIndexingSensitivity.medium
) {
return null;
}
@@ -84,16 +86,16 @@ export class GalleryManager {
if (dir.lastModified !== lastModified) {
Logger.silly(
LOG_TAG,
'Reindexing reason: lastModified mismatch: known: ' +
dir.lastModified +
', current:' +
lastModified
LOG_TAG,
'Reindexing reason: lastModified mismatch: known: ' +
dir.lastModified +
', current:' +
lastModified
);
const ret =
await ObjectManagers.getInstance().IndexingManager.indexDirectory(
relativeDirectoryName
);
await ObjectManagers.getInstance().IndexingManager.indexDirectory(
relativeDirectoryName
);
for (const subDir of ret.directories) {
if (!subDir.cover) {
// if subdirectories do not have photos, so cannot show a cover, try getting one from DB
@@ -103,70 +105,70 @@ 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 &&
Config.Indexing.reIndexingSensitivity >=
ReIndexingSensitivity.medium) ||
(Date.now() - dir.lastScanned >
Config.Indexing.cachedFolderTimeout &&
Config.Indexing.reIndexingSensitivity >=
ReIndexingSensitivity.high
ReIndexingSensitivity.medium) ||
Config.Indexing.reIndexingSensitivity >=
ReIndexingSensitivity.high
) {
// on the fly reindexing
Logger.silly(
LOG_TAG,
'lazy reindexing reason: cache timeout: lastScanned: ' +
(Date.now() - dir.lastScanned) +
'ms ago, cachedFolderTimeout:' +
Config.Indexing.cachedFolderTimeout
LOG_TAG,
'lazy reindexing reason: cache timeout: lastScanned: ' +
(Date.now() - dir.lastScanned) +
'ms ago, cachedFolderTimeout:' +
Config.Indexing.cachedFolderTimeout
);
ObjectManagers.getInstance()
.IndexingManager.indexDirectory(relativeDirectoryName)
.catch(console.error);
.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
Logger.silly(LOG_TAG, 'Reindexing reason: never scanned');
return ObjectManagers.getInstance().IndexingManager.indexDirectory(
relativeDirectoryName
relativeDirectoryName
);
}
async countDirectories(): Promise<number> {
const connection = await SQLConnection.getConnection();
return await connection
.getRepository(DirectoryEntity)
.createQueryBuilder('directory')
.getCount();
.getRepository(DirectoryEntity)
.createQueryBuilder('directory')
.getCount();
}
async countMediaSize(): Promise<number> {
const connection = await SQLConnection.getConnection();
const {sum} = await connection
.getRepository(MediaEntity)
.createQueryBuilder('media')
.select('SUM(media.metadata.fileSize)', 'sum')
.getRawOne();
.getRepository(MediaEntity)
.createQueryBuilder('media')
.select('SUM(media.metadata.fileSize)', 'sum')
.getRawOne();
return sum || 0;
}
async countPhotos(): Promise<number> {
const connection = await SQLConnection.getConnection();
return await connection
.getRepository(PhotoEntity)
.createQueryBuilder('directory')
.getCount();
.getRepository(PhotoEntity)
.createQueryBuilder('directory')
.getCount();
}
async countVideos(): Promise<number> {
const connection = await SQLConnection.getConnection();
return await connection
.getRepository(VideoEntity)
.createQueryBuilder('directory')
.getCount();
.getRepository(VideoEntity)
.createQueryBuilder('directory')
.getCount();
}
public async getPossibleDuplicates(): Promise<DuplicatesDTO[]> {
@@ -174,31 +176,31 @@ export class GalleryManager {
const mediaRepository = connection.getRepository(MediaEntity);
let duplicates = await mediaRepository
.createQueryBuilder('media')
.innerJoin(
(query) =>
query
.from(MediaEntity, 'innerMedia')
.select([
'innerMedia.name as name',
'innerMedia.metadata.fileSize as fileSize',
'count(*)',
])
.groupBy('innerMedia.name, innerMedia.metadata.fileSize')
.having('count(*)>1'),
'innerMedia',
'media.name=innerMedia.name AND media.metadata.fileSize = innerMedia.fileSize'
)
.innerJoinAndSelect('media.directory', 'directory')
.orderBy('media.name, media.metadata.fileSize')
.limit(Config.Duplicates.listingLimit)
.getMany();
.createQueryBuilder('media')
.innerJoin(
(query) =>
query
.from(MediaEntity, 'innerMedia')
.select([
'innerMedia.name as name',
'innerMedia.metadata.fileSize as fileSize',
'count(*)',
])
.groupBy('innerMedia.name, innerMedia.metadata.fileSize')
.having('count(*)>1'),
'innerMedia',
'media.name=innerMedia.name AND media.metadata.fileSize = innerMedia.fileSize'
)
.innerJoinAndSelect('media.directory', 'directory')
.orderBy('media.name, media.metadata.fileSize')
.limit(Config.Duplicates.listingLimit)
.getMany();
const duplicateParis: DuplicatesDTO[] = [];
const processDuplicates = (
duplicateList: MediaEntity[],
equalFn: (a: MediaEntity, b: MediaEntity) => boolean,
checkDuplicates = false
duplicateList: MediaEntity[],
equalFn: (a: MediaEntity, b: MediaEntity) => boolean,
checkDuplicates = false
): void => {
let i = duplicateList.length - 1;
while (i >= 0) {
@@ -216,15 +218,15 @@ export class GalleryManager {
if (checkDuplicates) {
// ad to group if one already existed
const foundDuplicates = duplicateParis.find(
(dp): boolean =>
!!dp.media.find(
(m): boolean => !!list.find((lm): boolean => lm.id === m.id)
)
(dp): boolean =>
!!dp.media.find(
(m): boolean => !!list.find((lm): boolean => lm.id === m.id)
)
);
if (foundDuplicates) {
list.forEach((lm): void => {
if (
foundDuplicates.media.find((m): boolean => m.id === lm.id)
foundDuplicates.media.find((m): boolean => m.id === lm.id)
) {
return;
}
@@ -239,40 +241,40 @@ export class GalleryManager {
};
processDuplicates(
duplicates,
(a, b): boolean =>
a.name === b.name && a.metadata.fileSize === b.metadata.fileSize
duplicates,
(a, b): boolean =>
a.name === b.name && a.metadata.fileSize === b.metadata.fileSize
);
duplicates = await mediaRepository
.createQueryBuilder('media')
.innerJoin(
(query) =>
query
.from(MediaEntity, 'innerMedia')
.select([
'innerMedia.metadata.creationDate as creationDate',
'innerMedia.metadata.fileSize as fileSize',
'count(*)',
])
.groupBy(
'innerMedia.metadata.creationDate, innerMedia.metadata.fileSize'
)
.having('count(*)>1'),
'innerMedia',
'media.metadata.creationDate=innerMedia.creationDate AND media.metadata.fileSize = innerMedia.fileSize'
)
.innerJoinAndSelect('media.directory', 'directory')
.orderBy('media.metadata.creationDate, media.metadata.fileSize')
.limit(Config.Duplicates.listingLimit)
.getMany();
.createQueryBuilder('media')
.innerJoin(
(query) =>
query
.from(MediaEntity, 'innerMedia')
.select([
'innerMedia.metadata.creationDate as creationDate',
'innerMedia.metadata.fileSize as fileSize',
'count(*)',
])
.groupBy(
'innerMedia.metadata.creationDate, innerMedia.metadata.fileSize'
)
.having('count(*)>1'),
'innerMedia',
'media.metadata.creationDate=innerMedia.creationDate AND media.metadata.fileSize = innerMedia.fileSize'
)
.innerJoinAndSelect('media.directory', 'directory')
.orderBy('media.metadata.creationDate, media.metadata.fileSize')
.limit(Config.Duplicates.listingLimit)
.getMany();
processDuplicates(
duplicates,
(a, b): boolean =>
a.metadata.creationDate === b.metadata.creationDate &&
a.metadata.fileSize === b.metadata.fileSize,
true
duplicates,
(a, b): boolean =>
a.metadata.creationDate === b.metadata.creationDate &&
a.metadata.fileSize === b.metadata.fileSize,
true
);
return duplicateParis;
@@ -282,20 +284,20 @@ export class GalleryManager {
* Returns with the directories only, does not include media or metafiles
*/
public async selectDirStructure(
relativeDirectoryName: string
relativeDirectoryName: string
): Promise<DirectoryEntity> {
const directoryPath = GalleryManager.parseRelativeDirePath(
relativeDirectoryName
relativeDirectoryName
);
const connection = await SQLConnection.getConnection();
const query = connection
.getRepository(DirectoryEntity)
.createQueryBuilder('directory')
.where('directory.name = :name AND directory.path = :path', {
name: directoryPath.name,
path: directoryPath.parent,
})
.leftJoinAndSelect('directory.directories', 'directories');
.getRepository(DirectoryEntity)
.createQueryBuilder('directory')
.where('directory.name = :name AND directory.path = :path', {
name: directoryPath.name,
path: directoryPath.parent,
})
.leftJoinAndSelect('directory.directories', 'directories');
return await query.getOne();
}
@@ -304,14 +306,14 @@ export class GalleryManager {
* Sets cover for the directory and caches it in the DB
*/
public async fillCoverForSubDir(
connection: Connection,
dir: SubDirectoryDTO
connection: Connection,
dir: SubDirectoryDTO
): Promise<void> {
if (!dir.validCover) {
dir.cover =
await ObjectManagers.getInstance().CoverManager.setAndGetCoverForDirectory(
dir
);
await ObjectManagers.getInstance().CoverManager.setAndGetCoverForDirectory(
dir
);
}
dir.media = [];
@@ -324,48 +326,76 @@ export class GalleryManager {
lastModified: number
}> {
return await connection
.getRepository(DirectoryEntity)
.createQueryBuilder('directory')
.where('directory.name = :name AND directory.path = :path', {
name: name,
path: path,
})
.select([
'directory.id',
'directory.lastScanned',
'directory.lastModified',
]).getOne();
.getRepository(DirectoryEntity)
.createQueryBuilder('directory')
.where('directory.name = :name AND directory.path = :path', {
name: name,
path: path,
})
.select([
'directory.id',
'directory.lastScanned',
'directory.lastModified',
]).getOne();
}
protected async getParentDirFromId(
connection: Connection,
partialDirId: number
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')
.where('directory.id = :id', {
id: partialDirId
})
.leftJoinAndSelect('directory.directories', 'directories')
.leftJoinAndSelect('directory.media', 'media')
.leftJoinAndSelect('directories.cover', 'cover')
.leftJoinAndSelect('cover.directory', 'coverDirectory')
.select([
'directory',
'directories',
'media',
'cover.name',
'coverDirectory.name',
'coverDirectory.path',
]);
.getRepository(DirectoryEntity)
.createQueryBuilder('directory')
.where('directory.id = :id', {
id: partialDirId
})
.leftJoinAndSelect('directory.directories', 'directories')
.leftJoinAndSelect(
'directory.media',
'media', sql, params
)
.leftJoinAndSelect('directories.cover', 'cover')
.leftJoinAndSelect('cover.directory', 'coverDirectory')
.select([
'directory',
'directories',
'media',
'cover.name',
'coverDirectory.name',
'coverDirectory.path',
]);
// TODO: do better filtering
// NOTE: it should not cause an issue as it also do not save to the DB
if (
Config.MetaFile.gpx === true ||
Config.MetaFile.pg2conf === true ||
Config.MetaFile.markdown === true
Config.MetaFile.gpx === true ||
Config.MetaFile.pg2conf === true ||
Config.MetaFile.markdown === true
) {
query.leftJoinAndSelect('directory.metaFile', 'metaFile');
}

View File

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

View File

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

View File

@@ -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 {

View File

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

View File

@@ -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 = {
@@ -35,8 +36,8 @@ export const UserDTOUtils = {
if (permission[permission.length - 1] === '*') {
permission = permission.slice(0, -1);
if (
path.startsWith(permission) &&
(!path[permission.length] || path[permission.length] === '/')
path.startsWith(permission) &&
(!path[permission.length] || path[permission.length] === '/')
) {
return true;
}
@@ -50,12 +51,12 @@ export const UserDTOUtils = {
},
isDirectoryAvailable: (
directory: DirectoryPathDTO,
permissions: string[]
directory: DirectoryPathDTO,
permissions: string[]
): boolean => {
return UserDTOUtils.isDirectoryPathAvailable(
Utils.concatUrls(directory.path, directory.name),
permissions
Utils.concatUrls(directory.path, directory.name),
permissions
);
},
};

View File

@@ -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);
}
}
@@ -120,20 +121,21 @@ export class DBTestHelper {
// await im.saveToDB(subDir2);
if (ObjectManagers.getInstance().IndexingManager &&
ObjectManagers.getInstance().IndexingManager.IsSavingInProgress) {
ObjectManagers.getInstance().IndexingManager.IsSavingInProgress) {
await ObjectManagers.getInstance().IndexingManager.SavingReady;
}
const gm = new GalleryManagerTest();
const session = new SessionContext();
const dir = await gm.getParentDirFromId(connection,
(await gm.getDirIdAndTime(connection, directory.name, path.join(directory.path, path.sep))).id);
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,
(await gm.getDirIdAndTime(connection, d.directories[i].name,
path.join(DiskManager.pathFromParent(d), path.sep))).id);
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]);
}
};

View File

@@ -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: {
user: 'A user'
context: {
user: 'A user'
}
},
sessionOptions: {},
query: {},
@@ -72,7 +75,9 @@ describe('Authentication middleware', () => {
const req = {
session: {
user: {permissions: null as string[]}
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: {
user: 'A user'
context: {
user: 'A user'
}
},
sessionOptions: {},
};
@@ -162,8 +169,10 @@ describe('Authentication middleware', () => {
it('should call next on authorised', (done: (err?: any) => void) => {
const req: any = {
session: {
user: {
role: UserRoles.LimitedGuest
context: {
user: {
role: UserRoles.LimitedGuest
}
}
},
sessionOptions: {}
@@ -183,8 +192,10 @@ describe('Authentication middleware', () => {
it('should call next with error on not authorised', (done: (err?: any) => void) => {
const req: any = {
session: {
user: {
role: UserRoles.LimitedGuest
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...', () => {
@@ -291,7 +302,7 @@ describe('Authentication middleware', () => {
});
it('should call next with user on the session on finding user', (done: (err?: any) => void) => {
it('should call next with user on the session on finding user', (done: (err?: any) => void) => {
const req: any = {
session: {},
body: {
@@ -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: {
user: {
role: UserRoles.LimitedGuest
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);

View 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);
});
});
});

View File

@@ -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);
}
}
@@ -179,7 +180,7 @@ describe('CoverManager', (sqlHelper: DBTestHelper) => {
const conn = await SQLConnection.getConnection();
await conn.createQueryBuilder()
.update(DirectoryEntity).set({validCover: false}).execute();
.update(DirectoryEntity).set({validCover: false}).execute();
expect(await pm.getPartialDirsWithoutCovers()).to.deep.equalInAnyOrder([dir, subDir, subDir2].map(d => partialDir(d)));
});
@@ -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);
@@ -280,13 +281,14 @@ describe('CoverManager', (sqlHelper: DBTestHelper) => {
expect(subdir.cover.id).to.equal(p2.id);
await conn.createQueryBuilder()
.update(DirectoryEntity)
.set({validCover: false, cover: null}).execute();
.update(DirectoryEntity)
.set({validCover: false, cover: null}).execute();
expect((await selectDir()).cover).to.equal(null);
const session = new SessionContext();
const res = await gm.getParentDirFromId(conn,
(await gm.getDirIdAndTime(conn, dir.name, dir.path)).id);
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);
expect(subdir.cover.id).to.equal(p2.id);

View File

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

View File

@@ -18,7 +18,8 @@ import * as path from 'path';
import {AlbumManager} from '../../../../../src/backend/model/database/AlbumManager';
import {SortByTypes} from '../../../../../src/common/entities/SortingMethods';
import {ClientSortingConfig} from '../../../../../src/common/config/public/ClientConfig';
import { DiskManager } from '../../../../../src/backend/model/fileaccess/DiskManager';
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);

View File

@@ -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,27 +94,27 @@ 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();
//we create a date 1 month and 1 day before now
d = Utils.addMonthToDate(d, -1); //subtract 1 month in the "human way"
d.setDate(d.getDate()-1); //subtract 1 day
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];