mirror of
https://github.com/bpatrik/pigallery2.git
synced 2025-01-02 03:37:54 +02:00
parent
f551509fee
commit
d4d8dcfcdb
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pigallery2-extension-kit",
|
"name": "pigallery2-extension-kit",
|
||||||
"version": "2.0.3-edge4",
|
"version": "2.0.3-edge6",
|
||||||
"description": "Interfaces for developing extensions for pigallery2",
|
"description": "Interfaces for developing extensions for pigallery2",
|
||||||
"author": "Patrik J. Braun",
|
"author": "Patrik J. Braun",
|
||||||
"homepage": "https://github.com/bpatrik/pigallery2",
|
"homepage": "https://github.com/bpatrik/pigallery2",
|
||||||
|
@ -12,7 +12,7 @@ import * as child_process from 'child_process';
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as jeditor from 'gulp-json-editor';
|
import * as jeditor from 'gulp-json-editor';
|
||||||
import {XLIFF} from 'xlf-google-translate';
|
import {XLIFF} from 'xlf-google-translate';
|
||||||
import {PrivateConfigClass} from './src/common/config/private/Config';
|
import {PrivateConfigClass} from './src/common/config/private/PrivateConfigClass';
|
||||||
import {ConfigClassBuilder} from 'typeconfig/src/decorators/builders/ConfigClassBuilder';
|
import {ConfigClassBuilder} from 'typeconfig/src/decorators/builders/ConfigClassBuilder';
|
||||||
|
|
||||||
const execPr = util.promisify(child_process.exec);
|
const execPr = util.promisify(child_process.exec);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {NextFunction, Request, Response} from 'express';
|
import {NextFunction, Request, Response} from 'express';
|
||||||
import {ErrorCodes, ErrorDTO} from '../../common/entities/Error';
|
import {ErrorCodes, ErrorDTO} from '../../common/entities/Error';
|
||||||
import {Message} from '../../common/entities/Message';
|
import {Message} from '../../common/entities/Message';
|
||||||
import {Config, PrivateConfigClass} from '../../common/config/private/Config';
|
import {PrivateConfigClass} from '../../common/config/private/PrivateConfigClass';
|
||||||
import {UserDTO, UserRoles} from '../../common/entities/UserDTO';
|
import {UserDTO, UserRoles} from '../../common/entities/UserDTO';
|
||||||
import {NotificationManager} from '../model/NotifocationManager';
|
import {NotificationManager} from '../model/NotifocationManager';
|
||||||
import {Logger} from '../Logger';
|
import {Logger} from '../Logger';
|
||||||
|
@ -51,8 +51,8 @@ export class SettingsMWs {
|
|||||||
await ConfigDiagnostics.runDiagnostics();
|
await ConfigDiagnostics.runDiagnostics();
|
||||||
// restart all schedule timers. In case they have changed
|
// restart all schedule timers. In case they have changed
|
||||||
ObjectManagers.getInstance().JobManager.runSchedules();
|
ObjectManagers.getInstance().JobManager.runSchedules();
|
||||||
Logger.info(LOG_TAG, 'new config:');
|
// Logger.info(LOG_TAG, 'new config:');
|
||||||
Logger.info(LOG_TAG, JSON.stringify(Config.toJSON({attachDescription: false}), null, '\t'));
|
// Logger.info(LOG_TAG, JSON.stringify(Config.toJSON({attachDescription: false}), null, '\t'));
|
||||||
return next();
|
return next();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {Config, PrivateConfigClass} from '../../../common/config/private/Config';
|
import {PrivateConfigClass} from '../../../common/config/private/PrivateConfigClass';
|
||||||
import {Logger} from '../../Logger';
|
import {Logger} from '../../Logger';
|
||||||
import {NotificationManager} from '../NotifocationManager';
|
import {NotificationManager} from '../NotifocationManager';
|
||||||
import {SQLConnection} from '../database/SQLConnection';
|
import {SQLConnection} from '../database/SQLConnection';
|
||||||
@ -27,8 +27,8 @@ import {SearchQueryParser} from '../../../common/SearchQueryParser';
|
|||||||
import {SearchQueryTypes, TextSearch,} from '../../../common/entities/SearchQueryDTO';
|
import {SearchQueryTypes, TextSearch,} from '../../../common/entities/SearchQueryDTO';
|
||||||
import {Utils} from '../../../common/Utils';
|
import {Utils} from '../../../common/Utils';
|
||||||
import {JobRepository} from '../jobs/JobRepository';
|
import {JobRepository} from '../jobs/JobRepository';
|
||||||
import {ExtensionConfig} from '../extension/ExtensionConfigWrapper';
|
|
||||||
import {ConfigClassBuilder} from '../../../../node_modules/typeconfig/node';
|
import {ConfigClassBuilder} from '../../../../node_modules/typeconfig/node';
|
||||||
|
import { Config } from '../../../common/config/private/Config';
|
||||||
|
|
||||||
const LOG_TAG = '[ConfigDiagnostics]';
|
const LOG_TAG = '[ConfigDiagnostics]';
|
||||||
|
|
||||||
|
18
src/backend/model/extension/ExtensionConfig.ts
Normal file
18
src/backend/model/extension/ExtensionConfig.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import {IExtensionConfig} from './IExtension';
|
||||||
|
import {Config} from '../../../common/config/private/Config';
|
||||||
|
|
||||||
|
export class ExtensionConfig<C> implements IExtensionConfig<C> {
|
||||||
|
|
||||||
|
constructor(private readonly extensionFolder: string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public getConfig(): C {
|
||||||
|
const c = (Config.Extensions.extensions || [])
|
||||||
|
.find(e => e.path === this.extensionFolder);
|
||||||
|
|
||||||
|
return c?.configs as C;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
110
src/backend/model/extension/ExtensionConfigTemplateLoader.ts
Normal file
110
src/backend/model/extension/ExtensionConfigTemplateLoader.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import {PrivateConfigClass} from '../../../common/config/private/PrivateConfigClass';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import {ServerExtensionsEntryConfig} from '../../../common/config/private/subconfigs/ServerExtensionsConfig';
|
||||||
|
|
||||||
|
|
||||||
|
const LOG_TAG = '[ExtensionConfigTemplateLoader]';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class decouples the extension management and the config.
|
||||||
|
* It helps to solve the "chicken and the egg" which should load first:
|
||||||
|
* Config or the extension as they have a circular dependency
|
||||||
|
*/
|
||||||
|
export class ExtensionConfigTemplateLoader {
|
||||||
|
|
||||||
|
private static instance: ExtensionConfigTemplateLoader;
|
||||||
|
private extensionsFolder: string;
|
||||||
|
|
||||||
|
private loaded = false;
|
||||||
|
private extensionList: string[] = [];
|
||||||
|
private extensionTemplates: { folder: string, template?: { new(): unknown } }[] = [];
|
||||||
|
|
||||||
|
public static get Instance() {
|
||||||
|
if (!this.instance) {
|
||||||
|
this.instance = new ExtensionConfigTemplateLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init(extensionsFolder: string) {
|
||||||
|
this.extensionsFolder = extensionsFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public loadExtensionTemplates(config: PrivateConfigClass) {
|
||||||
|
if (!this.extensionsFolder) {
|
||||||
|
throw new Error('Unknown extensions folder.');
|
||||||
|
}
|
||||||
|
// already loaded
|
||||||
|
if (!this.loaded) {
|
||||||
|
|
||||||
|
this.extensionList = (fs
|
||||||
|
.readdirSync(this.extensionsFolder))
|
||||||
|
.filter((f): boolean =>
|
||||||
|
fs.statSync(path.join(this.extensionsFolder, f)).isDirectory()
|
||||||
|
);
|
||||||
|
this.extensionList.sort();
|
||||||
|
|
||||||
|
this.extensionTemplates = [];
|
||||||
|
for (let i = 0; i < this.extensionList.length; ++i) {
|
||||||
|
const extFolder = this.extensionList[i];
|
||||||
|
const extPath = path.join(this.extensionsFolder, extFolder);
|
||||||
|
const serverExtPath = path.join(extPath, 'server.js');
|
||||||
|
if (!fs.existsSync(serverExtPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const ext = require(serverExtPath);
|
||||||
|
if (typeof ext?.initConfig === 'function') {
|
||||||
|
ext?.initConfig({
|
||||||
|
setConfigTemplate: (template: { new(): unknown }): void => {
|
||||||
|
this.extensionTemplates.push({folder: extFolder, template: template});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
//also create basic config extensions that do not have any
|
||||||
|
this.extensionTemplates.push({folder: extFolder});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setTemplatesToConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private setTemplatesToConfig(config: PrivateConfigClass) {
|
||||||
|
if (!this.extensionTemplates) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ePaths = this.extensionTemplates.map(et => et.folder);
|
||||||
|
// delete not existing extensions
|
||||||
|
config.Extensions.extensions = config.Extensions.extensions
|
||||||
|
.filter(ec => ePaths.indexOf(ec.path) !== -1);
|
||||||
|
|
||||||
|
|
||||||
|
for (let i = 0; i < this.extensionTemplates.length; ++i) {
|
||||||
|
const ext = this.extensionTemplates[i];
|
||||||
|
|
||||||
|
let c = (config.Extensions.extensions || [])
|
||||||
|
.find(e => e.path === ext.folder);
|
||||||
|
|
||||||
|
// set the new structure with the new def values
|
||||||
|
if (!c) {
|
||||||
|
c = new ServerExtensionsEntryConfig(ext.folder);
|
||||||
|
if (ext.template) {
|
||||||
|
c.configs= new ext.template()
|
||||||
|
}
|
||||||
|
config.Extensions.extensions.push(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,64 +1,49 @@
|
|||||||
import {IConfigClass} from 'typeconfig/common';
|
import {IConfigClass} from 'typeconfig/common';
|
||||||
import {Config, PrivateConfigClass} from '../../../common/config/private/Config';
|
import {PrivateConfigClass} from '../../../common/config/private/PrivateConfigClass';
|
||||||
import {ConfigClassBuilder} from 'typeconfig/node';
|
import {ConfigClassBuilder} from 'typeconfig/node';
|
||||||
import {IExtensionConfig} from './IExtension';
|
import {ExtensionConfigTemplateLoader} from './ExtensionConfigTemplateLoader';
|
||||||
import {ObjectManagers} from '../ObjectManagers';
|
import {NotificationManager} from '../NotifocationManager';
|
||||||
import {ServerExtensionsEntryConfig} from '../../../common/config/private/subconfigs/ServerExtensionsConfig';
|
|
||||||
|
|
||||||
|
const LOG_TAG = '[ExtensionConfigWrapper]';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps to original config and makes sure all extension related config is loaded
|
* Wraps to original config and makes sure all extension related config is loaded
|
||||||
*/
|
*/
|
||||||
export class ExtensionConfigWrapper {
|
export class ExtensionConfigWrapper {
|
||||||
static async original(): Promise<PrivateConfigClass & IConfigClass> {
|
|
||||||
|
static async original(showError = false): Promise<PrivateConfigClass & IConfigClass> {
|
||||||
const pc = ConfigClassBuilder.attachPrivateInterface(new PrivateConfigClass());
|
const pc = ConfigClassBuilder.attachPrivateInterface(new PrivateConfigClass());
|
||||||
|
ExtensionConfigTemplateLoader.Instance.loadExtensionTemplates(pc);
|
||||||
try {
|
try {
|
||||||
await pc.load(); // loading the basic configs but we do not know the extension config hierarchy yet
|
await pc.load(); // loading the basic configs, but we do not know the extension config hierarchy yet
|
||||||
if (ObjectManagers.isReady()) {
|
|
||||||
for (const ext of Object.values(ObjectManagers.getInstance().ExtensionManager.extObjects)) {
|
|
||||||
ext.config.loadToConfig(ConfigClassBuilder.attachPrivateInterface(pc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await pc.load(); // loading the extension related configs
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error during loading original config. Reverting to defaults.');
|
if(showError){
|
||||||
console.error(e);
|
console.error(LOG_TAG,'Error during loading config. Reverting to defaults.');
|
||||||
|
console.error(LOG_TAG,'This is most likely due to: 1) you added a bad configuration in the server.json OR 2) The configuration changed in the latest release.');
|
||||||
|
console.error(e);
|
||||||
|
NotificationManager.error('Can\'t load config. Reverting to default. This is most likely due to: 1) you added a bad configuration in the server.json OR 2) The configuration changed in the latest release.', (e.toString ? e.toString() : JSON.stringify(e)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static originalSync(showError = false): PrivateConfigClass & IConfigClass {
|
||||||
|
const pc = ConfigClassBuilder.attachPrivateInterface(new PrivateConfigClass());
|
||||||
|
ExtensionConfigTemplateLoader.Instance.loadExtensionTemplates(pc);
|
||||||
|
try {
|
||||||
|
pc.loadSync(); // loading the basic configs, but we do not know the extension config hierarchy yet
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
if(showError){
|
||||||
|
console.error(LOG_TAG,'Error during loading config. Reverting to defaults.');
|
||||||
|
console.error(LOG_TAG,'This is most likely due to: 1) you added a bad configuration in the server.json OR 2) The configuration changed in the latest release.');
|
||||||
|
console.error(e);
|
||||||
|
NotificationManager.error('Ca\'nt load config. Reverting to default. This is most likely due to: 1) you added a bad configuration in the server.json OR 2) The configuration changed in the latest release.', (e.toString ? e.toString() : JSON.stringify(e)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return pc;
|
return pc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExtensionConfig<C> implements IExtensionConfig<C> {
|
|
||||||
public template: new() => C;
|
|
||||||
|
|
||||||
constructor(private readonly extensionFolder: string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private findConfig(config: PrivateConfigClass): ServerExtensionsEntryConfig {
|
|
||||||
let c = (config.Extensions.extensions || []).find(e => e.path === this.extensionFolder);
|
|
||||||
if (!c) {
|
|
||||||
c = new ServerExtensionsEntryConfig(this.extensionFolder);
|
|
||||||
config.Extensions.extensions.push(c);
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public getConfig(): C {
|
|
||||||
return this.findConfig(Config).configs as C;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setTemplate(template: new() => C): void {
|
|
||||||
this.template = template;
|
|
||||||
this.loadToConfig(Config);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadToConfig(config: PrivateConfigClass) {
|
|
||||||
if (!this.template) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const confTemplate = ConfigClassBuilder.attachPrivateInterface(new this.template());
|
|
||||||
const extConf = this.findConfig(config);
|
|
||||||
extConf.configs = confTemplate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -12,7 +12,6 @@ import {SQLConnection} from '../database/SQLConnection';
|
|||||||
import {ExtensionObject} from './ExtensionObject';
|
import {ExtensionObject} from './ExtensionObject';
|
||||||
import {ExtensionDecoratorObject} from './ExtensionDecorator';
|
import {ExtensionDecoratorObject} from './ExtensionDecorator';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
import {ServerExtensionsEntryConfig} from '../../../common/config/private/subconfigs/ServerExtensionsConfig';
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const exec = util.promisify(require('child_process').exec);
|
const exec = util.promisify(require('child_process').exec);
|
||||||
|
|
||||||
@ -80,15 +79,8 @@ export class ExtensionManager implements IObjectManager {
|
|||||||
);
|
);
|
||||||
extList.sort();
|
extList.sort();
|
||||||
|
|
||||||
// delete not existing extensions
|
|
||||||
Config.Extensions.extensions = Config.Extensions.extensions.filter(ec => extList.indexOf(ec.path) !== -1);
|
|
||||||
|
|
||||||
// Add new extensions
|
Logger.debug(LOG_TAG, 'Extensions found: ', JSON.stringify(Config.Extensions.extensions.map(ec => ec.path)));
|
||||||
const ePaths = Config.Extensions.extensions.map(ec => ec.path);
|
|
||||||
extList.filter(ep => ePaths.indexOf(ep) === -1).forEach(ep =>
|
|
||||||
Config.Extensions.extensions.push(new ServerExtensionsEntryConfig(ep)));
|
|
||||||
|
|
||||||
Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.extensions.map(ec => ec.path)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createUniqueExtensionObject(name: string, folder: string): IExtensionObject<unknown> {
|
private createUniqueExtensionObject(name: string, folder: string): IExtensionObject<unknown> {
|
||||||
@ -111,7 +103,7 @@ export class ExtensionManager implements IObjectManager {
|
|||||||
const extFolder = Config.Extensions.extensions[i].path;
|
const extFolder = Config.Extensions.extensions[i].path;
|
||||||
let extName = extFolder;
|
let extName = extFolder;
|
||||||
|
|
||||||
if(Config.Extensions.extensions[i].enabled === false){
|
if (Config.Extensions.extensions[i].enabled === false) {
|
||||||
Logger.silly(LOG_TAG, `Skipping ${extFolder} initiation. Extension is disabled.`);
|
Logger.silly(LOG_TAG, `Skipping ${extFolder} initiation. Extension is disabled.`);
|
||||||
}
|
}
|
||||||
const extPath = path.join(ProjectPath.ExtensionFolder, extFolder);
|
const extPath = path.join(ProjectPath.ExtensionFolder, extFolder);
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import {IExtensionEvents, IExtensionObject} from './IExtension';
|
import {IExtensionEvents, IExtensionObject} from './IExtension';
|
||||||
import {ExtensionApp} from './ExtensionApp';
|
import {ExtensionApp} from './ExtensionApp';
|
||||||
import {ExtensionConfig} from './ExtensionConfigWrapper';
|
|
||||||
import {ExtensionDB} from './ExtensionDB';
|
import {ExtensionDB} from './ExtensionDB';
|
||||||
import {ProjectPath} from '../../ProjectPath';
|
import {ProjectPath} from '../../ProjectPath';
|
||||||
import {ExpressRouterWrapper} from './ExpressRouterWrapper';
|
import {ExpressRouterWrapper} from './ExpressRouterWrapper';
|
||||||
import {createLoggerWrapper} from '../../Logger';
|
import {createLoggerWrapper} from '../../Logger';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import {ExtensionMessengerHandler} from './ExtensionMessengerHandler';
|
import {ExtensionMessengerHandler} from './ExtensionMessengerHandler';
|
||||||
|
import {ExtensionConfig} from './ExtensionConfig';
|
||||||
|
|
||||||
export class ExtensionObject<C> implements IExtensionObject<C> {
|
export class ExtensionObject<C> implements IExtensionObject<C> {
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import {NextFunction, Request, Response} from 'express';
|
import {NextFunction, Request, Response} from 'express';
|
||||||
import {PrivateConfigClass} from '../../../common/config/private/Config';
|
import {PrivateConfigClass} from '../../../common/config/private/PrivateConfigClass';
|
||||||
import {ObjectManagers} from '../ObjectManagers';
|
import {ObjectManagers} from '../ObjectManagers';
|
||||||
import {ProjectPathClass} from '../../ProjectPath';
|
import {ProjectPathClass} from '../../ProjectPath';
|
||||||
import {ILogger} from '../../Logger';
|
import {ILogger} from '../../Logger';
|
||||||
@ -141,8 +141,6 @@ export interface IExtensionDB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IExtensionConfig<C> {
|
export interface IExtensionConfig<C> {
|
||||||
setTemplate(template: new() => C): void;
|
|
||||||
|
|
||||||
getConfig(): C;
|
getConfig(): C;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,11 +208,25 @@ export interface IExtensionObject<C = void> {
|
|||||||
messengers: IExtensionMessengers;
|
messengers: IExtensionMessengers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IExtensionConfigInit<C> {
|
||||||
|
/**
|
||||||
|
* Sets the config tempalte class
|
||||||
|
* @param template
|
||||||
|
*/
|
||||||
|
setConfigTemplate(template: new() => C): void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extension interface. All extension is expected to implement and export these methods
|
* Extension interface. All extension is expected to implement and export these methods
|
||||||
*/
|
*/
|
||||||
export interface IServerExtension<C> {
|
export interface IServerExtension<C> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function can be called any time. It should only set the config template class
|
||||||
|
* @param extension
|
||||||
|
*/
|
||||||
|
initConfig(extension: IExtensionConfigInit<C>): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extension init function. Extension should at minimum expose this function.
|
* Extension init function. Extension should at minimum expose this function.
|
||||||
* @param extension
|
* @param extension
|
||||||
|
@ -1,101 +1,13 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
import {ExtensionConfigWrapper} from '../../../backend/model/extension/ExtensionConfigWrapper';
|
||||||
import {ServerConfig} from './PrivateConfig';
|
import {PrivateConfigClass} from './PrivateConfigClass';
|
||||||
import * as crypto from 'crypto';
|
import {ConfigClassBuilder} from 'typeconfig/node';
|
||||||
|
import {ExtensionConfigTemplateLoader} from '../../../backend/model/extension/ExtensionConfigTemplateLoader';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import {ConfigClass, ConfigClassBuilder} from 'typeconfig/node';
|
|
||||||
import {IConfigClass} from 'typeconfig/common';
|
|
||||||
import {PasswordHelper} from '../../../backend/model/PasswordHelper';
|
|
||||||
import {TAGS} from '../public/ClientConfig';
|
|
||||||
import {NotificationManager} from '../../../backend/model/NotifocationManager';
|
|
||||||
|
|
||||||
declare const process: any;
|
const pre = ConfigClassBuilder.attachPrivateInterface(new PrivateConfigClass());
|
||||||
const upTime = new Date().toISOString();
|
|
||||||
// TODO: Refactor Config to be injectable globally.
|
|
||||||
// This is a bad habit to let the Config know if its in a testing env.
|
|
||||||
const isTesting = process.env['NODE_ENV'] == true || ['afterEach', 'after', 'beforeEach', 'before', 'describe', 'it']
|
|
||||||
.every((fn) => (global as any)[fn] instanceof Function);
|
|
||||||
|
|
||||||
@ConfigClass<IConfigClass<TAGS> & ServerConfig>({
|
|
||||||
configPath: path.join(__dirname, !isTesting ? './../../../../config.json' : './../../../../test/backend/tmp/config.json'),
|
|
||||||
crateConfigPathIfNotExists: isTesting,
|
|
||||||
saveIfNotExist: true,
|
|
||||||
attachDescription: true,
|
|
||||||
enumsAsString: true,
|
|
||||||
softReadonly: true,
|
|
||||||
cli: {
|
|
||||||
enable: {
|
|
||||||
configPath: true,
|
|
||||||
attachState: true,
|
|
||||||
attachDescription: true,
|
|
||||||
rewriteCLIConfig: true,
|
|
||||||
rewriteENVConfig: true,
|
|
||||||
enumsAsString: true,
|
|
||||||
saveIfNotExist: true,
|
|
||||||
exitOnConfig: true,
|
|
||||||
},
|
|
||||||
defaults: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
onLoadedSync: async (config) => {
|
|
||||||
let changed = false;
|
|
||||||
for (let i = 0; i < config.Users.enforcedUsers.length; ++i) {
|
|
||||||
const uc = config.Users.enforcedUsers[i];
|
|
||||||
|
|
||||||
// encrypt password and save back to the config
|
|
||||||
if (uc.password) {
|
|
||||||
if (!uc.encryptedPassword) {
|
|
||||||
uc.encryptedPassword = PasswordHelper.cryptPassword(uc.password);
|
|
||||||
}
|
|
||||||
uc.password = '';
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
if (!uc.encrypted) {
|
|
||||||
uc.encrypted = !!uc.encryptedPassword;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
if (!uc.encrypted && !uc.password) {
|
|
||||||
throw new Error('Password error for enforced user: ' + uc.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (changed) {
|
|
||||||
config.saveSync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
export class PrivateConfigClass extends ServerConfig {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
if (!this.Server.sessionSecret || this.Server.sessionSecret.length === 0) {
|
|
||||||
this.Server.sessionSecret = [
|
|
||||||
crypto.randomBytes(256).toString('hex'),
|
|
||||||
crypto.randomBytes(256).toString('hex'),
|
|
||||||
crypto.randomBytes(256).toString('hex'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Environment.appVersion =
|
|
||||||
require('../../../../package.json').version;
|
|
||||||
this.Environment.buildTime =
|
|
||||||
require('../../../../package.json').buildTime;
|
|
||||||
this.Environment.buildCommitHash =
|
|
||||||
require('../../../../package.json').buildCommitHash;
|
|
||||||
this.Environment.upTime = upTime;
|
|
||||||
this.Environment.isDocker = !!process.env.PI_DOCKER;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Config = ConfigClassBuilder.attachInterface(
|
|
||||||
new PrivateConfigClass()
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
Config.loadSync();
|
pre.loadSync();
|
||||||
} catch (e) {
|
} catch (e) { /* empty */ }
|
||||||
console.error('Error during loading config. Reverting to defaults.');
|
ExtensionConfigTemplateLoader.Instance.init(path.join(__dirname, '/../../../../', pre.Extensions.folder));
|
||||||
console.error('This is most likely due to: 1) you added a bad configuration in the server.json OR 2) The configuration changed in the latest release.');
|
|
||||||
console.error(e);
|
export const Config = ExtensionConfigWrapper.originalSync(true);
|
||||||
NotificationManager.error('Cant load config. Reverting to default. This is most likely due to: 1) you added a bad configuration in the server.json OR 2) The configuration changed in the latest release.', (e.toString ? e.toString() : JSON.stringify(e)));
|
|
||||||
}
|
|
||||||
|
89
src/common/config/private/PrivateConfigClass.ts
Normal file
89
src/common/config/private/PrivateConfigClass.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
import {ServerConfig} from './PrivateConfig';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
|
import * as path from 'path';
|
||||||
|
import {ConfigClass, ConfigClassBuilder} from 'typeconfig/node';
|
||||||
|
import {IConfigClass} from 'typeconfig/common';
|
||||||
|
import {PasswordHelper} from '../../../backend/model/PasswordHelper';
|
||||||
|
import {TAGS} from '../public/ClientConfig';
|
||||||
|
import {NotificationManager} from '../../../backend/model/NotifocationManager';
|
||||||
|
|
||||||
|
declare const process: any;
|
||||||
|
const upTime = new Date().toISOString();
|
||||||
|
// TODO: Refactor Config to be injectable globally.
|
||||||
|
// This is a bad habit to let the Config know if its in a testing env.
|
||||||
|
const isTesting = process.env['NODE_ENV'] == true || ['afterEach', 'after', 'beforeEach', 'before', 'describe', 'it']
|
||||||
|
.every((fn) => (global as any)[fn] instanceof Function);
|
||||||
|
|
||||||
|
@ConfigClass<IConfigClass<TAGS> & ServerConfig>({
|
||||||
|
configPath: path.join(__dirname, !isTesting ? './../../../../config.json' : './../../../../test/backend/tmp/config.json'),
|
||||||
|
crateConfigPathIfNotExists: isTesting,
|
||||||
|
saveIfNotExist: true,
|
||||||
|
attachDescription: true,
|
||||||
|
enumsAsString: true,
|
||||||
|
softReadonly: true,
|
||||||
|
cli: {
|
||||||
|
enable: {
|
||||||
|
configPath: true,
|
||||||
|
attachState: true,
|
||||||
|
attachDescription: true,
|
||||||
|
rewriteCLIConfig: true,
|
||||||
|
rewriteENVConfig: true,
|
||||||
|
enumsAsString: true,
|
||||||
|
saveIfNotExist: true,
|
||||||
|
exitOnConfig: true,
|
||||||
|
},
|
||||||
|
defaults: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onLoadedSync: async (config) => {
|
||||||
|
let changed = false;
|
||||||
|
for (let i = 0; i < config.Users.enforcedUsers.length; ++i) {
|
||||||
|
const uc = config.Users.enforcedUsers[i];
|
||||||
|
|
||||||
|
// encrypt password and save back to the config
|
||||||
|
if (uc.password) {
|
||||||
|
if (!uc.encryptedPassword) {
|
||||||
|
uc.encryptedPassword = PasswordHelper.cryptPassword(uc.password);
|
||||||
|
}
|
||||||
|
uc.password = '';
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (!uc.encrypted) {
|
||||||
|
uc.encrypted = !!uc.encryptedPassword;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (!uc.encrypted && !uc.password) {
|
||||||
|
throw new Error('Password error for enforced user: ' + uc.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
config.saveSync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export class PrivateConfigClass extends ServerConfig {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
if (!this.Server.sessionSecret || this.Server.sessionSecret.length === 0) {
|
||||||
|
this.Server.sessionSecret = [
|
||||||
|
crypto.randomBytes(256).toString('hex'),
|
||||||
|
crypto.randomBytes(256).toString('hex'),
|
||||||
|
crypto.randomBytes(256).toString('hex'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Environment.appVersion =
|
||||||
|
require('../../../../package.json').version;
|
||||||
|
this.Environment.buildTime =
|
||||||
|
require('../../../../package.json').buildTime;
|
||||||
|
this.Environment.buildCommitHash =
|
||||||
|
require('../../../../package.json').buildCommitHash;
|
||||||
|
this.Environment.upTime = upTime;
|
||||||
|
this.Environment.isDocker = !!process.env.PI_DOCKER;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
import {ConfigProperty, SubConfigClass} from 'typeconfig/common';
|
import {ConfigProperty, SubConfigClass} from 'typeconfig/common';
|
||||||
import {ClientExtensionsConfig, ConfigPriority, TAGS} from '../../public/ClientConfig';
|
import {ClientExtensionsConfig, ConfigPriority, TAGS} from '../../public/ClientConfig';
|
||||||
import {GenericConfigType} from 'typeconfig/src/GenericConfigType';
|
import {GenericConfigType} from 'typeconfig/src/GenericConfigType';
|
||||||
|
|
||||||
declare let $localize: (s: TemplateStringsArray) => string;
|
declare let $localize: (s: TemplateStringsArray) => string;
|
||||||
|
|
||||||
if (typeof $localize === 'undefined') {
|
if (typeof $localize === 'undefined') {
|
||||||
|
@ -107,7 +107,7 @@
|
|||||||
[ngModel]="rStates?.value.__state[ck]">
|
[ngModel]="rStates?.value.__state[ck]">
|
||||||
</app-settings-entry>
|
</app-settings-entry>
|
||||||
<!-- Config entries --->
|
<!-- Config entries --->
|
||||||
<ng-container *ngIf="isExpandableConfig(rStates.value.__state[ck])">
|
<ng-container *ngIf="isExpandableConfig(rStates.value.__state[ck]) && rStates.value.__state[ck].value">
|
||||||
<!-- Sub category with header and menu item -->
|
<!-- Sub category with header and menu item -->
|
||||||
<div class="card mt-2 mb-2" *ngIf="topLevel && rStates?.value.__state[ck].tags?.uiIcon"
|
<div class="card mt-2 mb-2" *ngIf="topLevel && rStates?.value.__state[ck].tags?.uiIcon"
|
||||||
[id]="ConfigPath+'.'+ck">
|
[id]="ConfigPath+'.'+ck">
|
||||||
|
@ -220,12 +220,12 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting
|
|||||||
st.original = Utils.clone(st.value);
|
st.original = Utils.clone(st.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st.isConfigType) {
|
if (st.isConfigType && st.value) {
|
||||||
for (const k of Object.keys(st.value.__state)) {
|
for (const k of Object.keys(st.value.__state)) {
|
||||||
instrument(st.value.__state[k], st);
|
instrument(st.value.__state[k], st);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (st.isConfigArrayType) {
|
if (st.isConfigArrayType && st.value) {
|
||||||
for (let i = 0; i < st.value?.length; ++i) {
|
for (let i = 0; i < st.value?.length; ++i) {
|
||||||
for (const k of Object.keys(st.value[i].__state)) {
|
for (const k of Object.keys(st.value[i].__state)) {
|
||||||
instrument(st.value[i].__state[k], st);
|
instrument(st.value[i].__state[k], st);
|
||||||
@ -317,6 +317,9 @@ export class TemplateComponent implements OnInit, OnChanges, OnDestroy, ISetting
|
|||||||
if (states.keys) {
|
if (states.keys) {
|
||||||
return states.keys;
|
return states.keys;
|
||||||
}
|
}
|
||||||
|
if (!states.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
const s = states.value.__state;
|
const s = states.value.__state;
|
||||||
const keys = Object.keys(s).sort((a, b) => {
|
const keys = Object.keys(s).sort((a, b) => {
|
||||||
if ((this.isExpandableConfig(s[a]) || s[a].isConfigArrayType) !== (this.isExpandableConfig(s[b]) || s[b].isConfigArrayType)) {
|
if ((this.isExpandableConfig(s[a]) || s[a].isConfigArrayType) !== (this.isExpandableConfig(s[b]) || s[b].isConfigArrayType)) {
|
||||||
|
@ -20,11 +20,14 @@ import {
|
|||||||
import {DirectoryBaseDTO, DirectoryPathDTO} from '../src/common/entities/DirectoryDTO';
|
import {DirectoryBaseDTO, DirectoryPathDTO} from '../src/common/entities/DirectoryDTO';
|
||||||
import {FileDTO} from '../src/common/entities/FileDTO';
|
import {FileDTO} from '../src/common/entities/FileDTO';
|
||||||
import {DiskManager} from '../src/backend/model/fileaccess/DiskManager';
|
import {DiskManager} from '../src/backend/model/fileaccess/DiskManager';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
export class TestHelper {
|
export class TestHelper {
|
||||||
|
|
||||||
static creationCounter = 0;
|
static creationCounter = 0;
|
||||||
|
|
||||||
|
public static readonly TMP_DIR= path.join(__dirname, './tmp');
|
||||||
|
|
||||||
public static getDirectoryEntry(parent: DirectoryBaseDTO = null, name = 'wars dir'): DirectoryEntity {
|
public static getDirectoryEntry(parent: DirectoryBaseDTO = null, name = 'wars dir'): DirectoryEntity {
|
||||||
|
|
||||||
const dir = new DirectoryEntity();
|
const dir = new DirectoryEntity();
|
||||||
|
@ -47,7 +47,6 @@ export class DBTestHelper {
|
|||||||
mysql: process.env.TEST_MYSQL !== 'false'
|
mysql: process.env.TEST_MYSQL !== 'false'
|
||||||
};
|
};
|
||||||
public static readonly savedDescribe = savedDescribe;
|
public static readonly savedDescribe = savedDescribe;
|
||||||
public tempDir: string;
|
|
||||||
public readonly testGalleyEntities: {
|
public readonly testGalleyEntities: {
|
||||||
dir: ParentDirectoryDTO,
|
dir: ParentDirectoryDTO,
|
||||||
subDir: SubDirectoryDTO,
|
subDir: SubDirectoryDTO,
|
||||||
@ -80,7 +79,6 @@ export class DBTestHelper {
|
|||||||
};
|
};
|
||||||
|
|
||||||
constructor(public dbType: DatabaseType) {
|
constructor(public dbType: DatabaseType) {
|
||||||
this.tempDir = path.join(__dirname, './tmp');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static describe(settingsOverride: {
|
static describe(settingsOverride: {
|
||||||
@ -206,7 +204,7 @@ export class DBTestHelper {
|
|||||||
await ObjectManagers.reset();
|
await ObjectManagers.reset();
|
||||||
Config.Database.type = DatabaseType.mysql;
|
Config.Database.type = DatabaseType.mysql;
|
||||||
Config.Database.mysql.database = 'pigallery2_test';
|
Config.Database.mysql.database = 'pigallery2_test';
|
||||||
await fs.promises.rm(this.tempDir, {recursive: true, force: true});
|
await fs.promises.rm(TestHelper.TMP_DIR, {recursive: true, force: true});
|
||||||
const conn = await SQLConnection.getConnection();
|
const conn = await SQLConnection.getConnection();
|
||||||
await conn.query('DROP DATABASE IF EXISTS ' + conn.options.database);
|
await conn.query('DROP DATABASE IF EXISTS ' + conn.options.database);
|
||||||
await SQLConnection.close();
|
await SQLConnection.close();
|
||||||
@ -225,10 +223,10 @@ export class DBTestHelper {
|
|||||||
private async clearUpSQLite(): Promise<void> {
|
private async clearUpSQLite(): Promise<void> {
|
||||||
Logger.debug(LOG_TAG, 'clearing up sqlite');
|
Logger.debug(LOG_TAG, 'clearing up sqlite');
|
||||||
Config.Database.type = DatabaseType.sqlite;
|
Config.Database.type = DatabaseType.sqlite;
|
||||||
Config.Database.dbFolder = this.tempDir;
|
Config.Database.dbFolder = TestHelper.TMP_DIR;
|
||||||
ProjectPath.reset();
|
ProjectPath.reset();
|
||||||
await ObjectManagers.reset();
|
await ObjectManagers.reset();
|
||||||
await fs.promises.rm(this.tempDir, {recursive: true, force: true});
|
await fs.promises.rm(TestHelper.TMP_DIR, {recursive: true, force: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import {SuperAgentStatic} from 'superagent';
|
|||||||
import {ProjectPath} from '../../../../src/backend/ProjectPath';
|
import {ProjectPath} from '../../../../src/backend/ProjectPath';
|
||||||
import {DBTestHelper} from '../../DBTestHelper';
|
import {DBTestHelper} from '../../DBTestHelper';
|
||||||
import {ReIndexingSensitivity} from '../../../../src/common/config/private/PrivateConfig';
|
import {ReIndexingSensitivity} from '../../../../src/common/config/private/PrivateConfig';
|
||||||
|
import {TestHelper} from '../../../TestHelper';
|
||||||
|
|
||||||
|
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
@ -25,14 +26,13 @@ describe = DBTestHelper.describe({sqlite: true});
|
|||||||
describe('GalleryRouter', (sqlHelper: DBTestHelper) => {
|
describe('GalleryRouter', (sqlHelper: DBTestHelper) => {
|
||||||
describe = tmpDescribe;
|
describe = tmpDescribe;
|
||||||
|
|
||||||
const tempDir = sqlHelper.tempDir;
|
|
||||||
let server: Server;
|
let server: Server;
|
||||||
const setUp = async () => {
|
const setUp = async () => {
|
||||||
await sqlHelper.initDB();
|
await sqlHelper.initDB();
|
||||||
Config.Users.authenticationRequired = false;
|
Config.Users.authenticationRequired = false;
|
||||||
Config.Media.Video.enabled = true;
|
Config.Media.Video.enabled = true;
|
||||||
Config.Media.folder = path.join(__dirname, '../../assets');
|
Config.Media.folder = path.join(__dirname, '../../assets');
|
||||||
Config.Media.tempFolder = tempDir;
|
Config.Media.tempFolder = TestHelper.TMP_DIR;
|
||||||
ProjectPath.reset();
|
ProjectPath.reset();
|
||||||
server = new Server();
|
server = new Server();
|
||||||
await server.onStarted.wait();
|
await server.onStarted.wait();
|
||||||
|
@ -19,6 +19,7 @@ describe('SettingsRouter', () => {
|
|||||||
|
|
||||||
const tempDir = path.join(__dirname, '../../tmp');
|
const tempDir = path.join(__dirname, '../../tmp');
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
await ObjectManagers.reset();
|
||||||
await fs.promises.rm(tempDir, {recursive: true, force: true});
|
await fs.promises.rm(tempDir, {recursive: true, force: true});
|
||||||
Config.Database.type = DatabaseType.sqlite;
|
Config.Database.type = DatabaseType.sqlite;
|
||||||
Config.Database.dbFolder = tempDir;
|
Config.Database.dbFolder = tempDir;
|
||||||
|
@ -177,10 +177,10 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => {
|
|||||||
|
|
||||||
expect(selected?.media?.length)
|
expect(selected?.media?.length)
|
||||||
.to.be.greaterThan(0);
|
.to.be.greaterThan(0);
|
||||||
if (!fs.existsSync(sqlHelper.tempDir)) {
|
if (!fs.existsSync(TestHelper.TMP_DIR)) {
|
||||||
fs.mkdirSync(sqlHelper.tempDir);
|
fs.mkdirSync(TestHelper.TMP_DIR);
|
||||||
}
|
}
|
||||||
const tmpDir = path.join(sqlHelper.tempDir, '/rnd5sdf_emptyDir');
|
const tmpDir = path.join(TestHelper.TMP_DIR, '/rnd5sdf_emptyDir');
|
||||||
fs.mkdirSync(tmpDir);
|
fs.mkdirSync(tmpDir);
|
||||||
ProjectPath.ImageFolder = tmpDir;
|
ProjectPath.ImageFolder = tmpDir;
|
||||||
let notFailed = false;
|
let notFailed = false;
|
||||||
|
Loading…
Reference in New Issue
Block a user