Skip to content

Commit

Permalink
Merge pull request bpatrik#832 from bpatrik/feature/extension-settings
Browse files Browse the repository at this point in the history
Feature/extension settings bpatrik#784
  • Loading branch information
bpatrik authored Mar 3, 2024
2 parents f5f34c5 + 2121471 commit 4352ff8
Show file tree
Hide file tree
Showing 19 changed files with 274 additions and 143 deletions.
11 changes: 7 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"reflect-metadata": "0.1.13",
"sharp": "0.31.3",
"ts-node-iptc": "1.0.11",
"typeconfig": "2.1.2",
"typeconfig": "2.2.11",
"typeorm": "0.3.12",
"xml2js": "0.6.2"
},
Expand Down
1 change: 0 additions & 1 deletion src/backend/middlewares/SharingMWs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ export class SharingMWs {
sharing,
forceUpdate
);
console.log(req.resultPipe);
return next();
} catch (err) {
return next(
Expand Down
6 changes: 3 additions & 3 deletions src/backend/middlewares/admin/SettingsMWs.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {NextFunction, Request, Response} from 'express';
import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error';
import {Logger} from '../../Logger';
import {Config} from '../../../common/config/private/Config';
import {ConfigDiagnostics} from '../../model/diagnostics/ConfigDiagnostics';
import {ConfigClassBuilder} from 'typeconfig/node';
import {TAGS} from '../../../common/config/public/ClientConfig';
import {ObjectManagers} from '../../model/ObjectManagers';
import {ExtensionConfigWrapper} from '../../model/extension/ExtensionConfigWrapper';
import {Logger} from '../../Logger';

const LOG_TAG = '[SettingsMWs]';

Expand All @@ -21,8 +21,8 @@ export class SettingsMWs {
*/
public static async updateSettings(req: Request, res: Response, next: NextFunction): Promise<void> {
if ((typeof req.body === 'undefined')
|| (typeof req.body.settings === 'undefined')
|| (typeof req.body.settingsPath !== 'string')) {
|| (typeof req.body.settings === 'undefined')
|| (typeof req.body.settingsPath !== 'string')) {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'settings is needed'));
}

Expand Down
26 changes: 19 additions & 7 deletions src/backend/model/extension/ExtensionConfigWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import {IConfigClass} from 'typeconfig/common';
import {Config, PrivateConfigClass} from '../../../common/config/private/Config';
import {ConfigClassBuilder} from 'typeconfig/node';
import {IExtensionConfig} from './IExtension';
import {Utils} from '../../../common/Utils';
import {ObjectManagers} from '../ObjectManagers';
import {ServerExtensionsEntryConfig} from '../../../common/config/private/subconfigs/ServerExtensionsConfig';

/**
* Wraps to original config and makes sure all extension related config is loaded
Expand All @@ -12,12 +12,13 @@ export class ExtensionConfigWrapper {
static async original(): Promise<PrivateConfigClass & IConfigClass> {
const pc = ConfigClassBuilder.attachPrivateInterface(new PrivateConfigClass());
try {
await pc.load();
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) {
console.error('Error during loading original config. Reverting to defaults.');
console.error(e);
Expand All @@ -29,11 +30,21 @@ export class ExtensionConfigWrapper {
export class ExtensionConfig<C> implements IExtensionConfig<C> {
public template: new() => C;

constructor(private readonly extensionId: string) {
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 Config.Extensions.configs[this.extensionId] as C;
return this.findConfig(Config).configs as C;
}

public setTemplate(template: new() => C): void {
Expand All @@ -45,8 +56,9 @@ export class ExtensionConfig<C> implements IExtensionConfig<C> {
if (!this.template) {
return;
}
const conf = ConfigClassBuilder.attachPrivateInterface(new this.template());
conf.__loadJSONObject(Utils.clone(config.Extensions.configs[this.extensionId] || {}));
config.Extensions.configs[this.extensionId] = conf;

const confTemplate = ConfigClassBuilder.attachPrivateInterface(new this.template());
const extConf = this.findConfig(config);
extConf.configs = confTemplate;
}
}
39 changes: 29 additions & 10 deletions src/backend/model/extension/ExtensionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {SQLConnection} from '../database/SQLConnection';
import {ExtensionObject} from './ExtensionObject';
import {ExtensionDecoratorObject} from './ExtensionDecorator';
import * as util from 'util';
import {ServerExtensionsEntryConfig} from '../../../common/config/private/subconfigs/ServerExtensionsConfig';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const exec = util.promisify(require('child_process').exec);

Expand Down Expand Up @@ -70,13 +71,23 @@ export class ExtensionManager implements IObjectManager {
return;
}

Config.Extensions.list = fs

const extList = fs
.readdirSync(ProjectPath.ExtensionFolder)
.filter((f): boolean =>
fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory()
);
Config.Extensions.list.sort();
Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.list));
extList.sort();

// delete not existing extensions
Config.Extensions.extensions = Config.Extensions.extensions.filter(ec => extList.indexOf(ec.path) !== -1);

// Add new extensions
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> {
Expand All @@ -95,9 +106,13 @@ export class ExtensionManager implements IObjectManager {

private async initExtensions() {

for (let i = 0; i < Config.Extensions.list.length; ++i) {
const extFolder = Config.Extensions.list[i];
for (let i = 0; i < Config.Extensions.extensions.length; ++i) {
const extFolder = Config.Extensions.extensions[i].path;
let extName = extFolder;

if(Config.Extensions.extensions[i].enabled === false){
Logger.silly(LOG_TAG, `Skipping ${extFolder} initiation. Extension is disabled.`);
}
const extPath = path.join(ProjectPath.ExtensionFolder, extFolder);
const serverExtPath = path.join(extPath, 'server.js');
const packageJsonPath = path.join(extPath, 'package.json');
Expand All @@ -107,10 +122,14 @@ export class ExtensionManager implements IObjectManager {
}

if (fs.existsSync(packageJsonPath)) {
Logger.silly(LOG_TAG, `Running: "npm install --prefer-offline --no-audit --progress=false --omit=dev" in ${extPath}`);
await exec('npm install --no-audit --progress=false --omit=dev', {
cwd: extPath
});
if (fs.existsSync(path.join(extPath, 'node_modules'))) {
Logger.debug(LOG_TAG, `node_modules folder exists. Skipping "npm install".`);
} else {
Logger.silly(LOG_TAG, `Running: "npm install --prefer-offline --no-audit --progress=false --omit=dev" in ${extPath}`);
await exec('npm install --no-audit --progress=false --omit=dev', {
cwd: extPath
});
}
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkg = require(packageJsonPath);
if (pkg.name) {
Expand All @@ -122,7 +141,7 @@ export class ExtensionManager implements IObjectManager {
const ext = require(serverExtPath);
if (typeof ext?.init === 'function') {
Logger.debug(LOG_TAG, 'Running init on extension: ' + extFolder);
await ext?.init(this.createUniqueExtensionObject(extName, extPath));
await ext?.init(this.createUniqueExtensionObject(extName, extFolder));
}
}
if (Config.Extensions.cleanUpUnusedTables) {
Expand Down
2 changes: 1 addition & 1 deletion src/backend/model/extension/ExtensionObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class ExtensionObject<C> implements IExtensionObject<C> {
events: IExtensionEvents) {
const logger = createLoggerWrapper(`[Extension][${extensionId}]`);
this._app = new ExtensionApp();
this.config = new ExtensionConfig<C>(extensionId);
this.config = new ExtensionConfig<C>(folder);
this.db = new ExtensionDB(logger);
this.paths = ProjectPath;
this.Logger = logger;
Expand Down
1 change: 0 additions & 1 deletion src/backend/model/jobs/jobs/TopPickSendJob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ export class TopPickSendJob extends Job<{
arr.findIndex(m => MediaDTOUtils.equals(m, value)) === index);

this.Progress.Processed++;
// console.log(this.mediaList);
return false;
}

Expand Down
33 changes: 2 additions & 31 deletions src/common/config/private/PrivateConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
} from '../../entities/job/JobScheduleDTO';
import {
ClientConfig,
ClientExtensionsConfig,
ClientGPXCompressingConfig,
ClientMediaConfig,
ClientMetaFileConfig,
Expand All @@ -30,7 +29,8 @@ import {SearchQueryDTO, SearchQueryTypes, TextSearch,} from '../../entities/Sear
import {SortByTypes} from '../../entities/SortingMethods';
import {UserRoles} from '../../entities/UserDTO';
import {MediaPickDTO} from '../../entities/MediaPickDTO';
import {MessagingConfig} from './MessagingConfig';
import {ServerExtensionsConfig} from './subconfigs/ServerExtensionsConfig';
import {MessagingConfig} from './subconfigs/MessagingConfig';

declare let $localize: (s: TemplateStringsArray) => string;

Expand Down Expand Up @@ -966,35 +966,6 @@ export class ServerServiceConfig extends ClientServiceConfig {
}


@SubConfigClass<TAGS>({softReadonly: true})
export class ServerExtensionsConfig extends ClientExtensionsConfig {

@ConfigProperty({
tags: {
name: $localize`Extension folder`,
priority: ConfigPriority.underTheHood,
dockerSensitive: true
},
description: $localize`Folder where the app stores the extensions. Extensions live in their sub-folders.`,
})
folder: string = 'extensions';

@ConfigProperty({volatile: true})
list: string[] = [];

@ConfigProperty({type: 'object'})
configs: Record<string, unknown> = {};

@ConfigProperty({
tags: {
name: $localize`Clean up unused tables`,
priority: ConfigPriority.underTheHood,
},
description: $localize`Automatically removes all tables from the DB that are not used anymore.`,
})
cleanUpUnusedTables: boolean = true;
}

@SubConfigClass({softReadonly: true})
export class ServerEnvironmentConfig {
@ConfigProperty({volatile: true})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-inferrable-types */
import {ConfigProperty, SubConfigClass} from 'typeconfig/common';
import {ConfigPriority, TAGS} from '../public/ClientConfig';
import {ConfigPriority, TAGS} from '../../public/ClientConfig';

declare let $localize: (s: TemplateStringsArray) => string;

Expand Down
73 changes: 73 additions & 0 deletions src/common/config/private/subconfigs/ServerExtensionsConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* eslint-disable @typescript-eslint/no-inferrable-types */
import {ConfigProperty, SubConfigClass} from 'typeconfig/common';
import {ClientExtensionsConfig, ConfigPriority, TAGS} from '../../public/ClientConfig';
import {GenericConfigType} from 'typeconfig/src/GenericConfigType';

@SubConfigClass<TAGS>({softReadonly: true})
export class ServerExtensionsEntryConfig {

constructor(path: string = '') {
this.path = path;
}

@ConfigProperty({
tags: {
name: $localize`Enabled`,
priority: ConfigPriority.advanced,
},
})
enabled: boolean = true;

@ConfigProperty({
readonly: true,
tags: {
name: $localize`Extension folder`,
priority: ConfigPriority.underTheHood,
},
description: $localize`Folder where the app stores all extensions. Individual extensions live in their own sub-folders.`,
})
path: string = '';

@ConfigProperty({
type: GenericConfigType,
tags: {
name: $localize`Config`,
priority: ConfigPriority.advanced
}
})
configs: GenericConfigType;
}

@SubConfigClass<TAGS>({softReadonly: true})
export class ServerExtensionsConfig extends ClientExtensionsConfig {

@ConfigProperty({
tags: {
name: $localize`Extension folder`,
priority: ConfigPriority.underTheHood,
dockerSensitive: true
},
description: $localize`Folder where the app stores all extensions. Individual extensions live in their own sub-folders.`,
})
folder: string = 'extensions';


@ConfigProperty({
arrayType: ServerExtensionsEntryConfig,
tags: {
name: $localize`Installed extensions`,
priority: ConfigPriority.advanced
}
})
extensions: ServerExtensionsEntryConfig[] = [];


@ConfigProperty({
tags: {
name: $localize`Clean up unused tables`,
priority: ConfigPriority.underTheHood,
},
description: $localize`Automatically removes all tables from the DB that are not used anymore.`,
})
cleanUpUnusedTables: boolean = true;
}
3 changes: 1 addition & 2 deletions src/common/config/public/ClientConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ declare let $localize: (s: TemplateStringsArray) => string;
if (typeof $localize === 'undefined') {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
global.$localize = (s) => s;
global.$localize = (s) => s[0];
}


Expand Down Expand Up @@ -1034,7 +1034,6 @@ export class ThemesConfig {
name: $localize`Selected theme css`, //this is a 'hack' to the UI settings. UI will only show the selected setting's css
uiDisabled: (sb: ThemesConfig) => !sb.enabled,
relevant: (c: ThemesConfig) => c.selectedTheme !== 'default',
uiType: 'SelectedThemeSettings'
} as TAGS,
description: $localize`Adds these css settings as it is to the end of the body tag of the page.`
})
Expand Down
Loading

0 comments on commit 4352ff8

Please sign in to comment.