optimize settings helper
This commit is contained in:
parent
730c25a9a1
commit
a8edc19f34
11 changed files with 343 additions and 119 deletions
|
@ -23,8 +23,8 @@ export default abstract class RefreshCommandHandler {
|
||||||
token: refreshToken,
|
token: refreshToken,
|
||||||
userId: createRefresh.userId,
|
userId: createRefresh.userId,
|
||||||
expiry: createRefresh.isFromPwa
|
expiry: createRefresh.isFromPwa
|
||||||
? new Date(Date.now() + ms(SettingHelper.getSetting("session.pwa_refresh_expiration") as ms.StringValue))
|
? new Date(Date.now() + ms(SettingHelper.getSetting("session.pwa_refresh_expiration")))
|
||||||
: new Date(Date.now() + ms(SettingHelper.getSetting("session.refresh_expiration") as ms.StringValue)),
|
: new Date(Date.now() + ms(SettingHelper.getSetting("session.refresh_expiration"))),
|
||||||
})
|
})
|
||||||
.execute()
|
.execute()
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
|
|
@ -78,7 +78,7 @@ export async function createWebapi(req: Request, res: Response): Promise<any> {
|
||||||
|
|
||||||
let token = await JWTHelper.create(
|
let token = await JWTHelper.create(
|
||||||
{
|
{
|
||||||
iss: SettingHelper.getSetting("club.name") as string,
|
iss: SettingHelper.getSetting("club.name"),
|
||||||
sub: "api_token_retrieve",
|
sub: "api_token_retrieve",
|
||||||
aud: StringHelper.random(32),
|
aud: StringHelper.random(32),
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,6 +7,7 @@ import moment from "moment";
|
||||||
import InternalException from "../exceptions/internalException";
|
import InternalException from "../exceptions/internalException";
|
||||||
import CalendarFactory from "../factory/admin/club/calendar";
|
import CalendarFactory from "../factory/admin/club/calendar";
|
||||||
import { CalendarHelper } from "../helpers/calendarHelper";
|
import { CalendarHelper } from "../helpers/calendarHelper";
|
||||||
|
import SettingHelper from "../helpers/settingsHelper";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description get all calendar items by types or nscdr
|
* @description get all calendar items by types or nscdr
|
||||||
|
@ -51,3 +52,22 @@ export async function getCalendarItemsByTypes(req: Request, res: Response): Prom
|
||||||
res.type("ics").send(value);
|
res.type("ics").send(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description get configuration of UI
|
||||||
|
* @param req {Request} Express req object
|
||||||
|
* @param res {Response} Express res object
|
||||||
|
* @returns {Promise<*>}
|
||||||
|
*/
|
||||||
|
export async function getApplicationConfig(req: Request, res: Response): Promise<any> {
|
||||||
|
let config = {
|
||||||
|
"club.name": SettingHelper.getSetting("club.name"),
|
||||||
|
"club.imprint": SettingHelper.getSetting("club.imprint"),
|
||||||
|
"club.privacy": SettingHelper.getSetting("club.privacy"),
|
||||||
|
"club.website": SettingHelper.getSetting("club.website"),
|
||||||
|
"app.custom_login_message": SettingHelper.getSetting("app.custom_login_message"),
|
||||||
|
"app.show_link_to_calendar": SettingHelper.getSetting("app.show_link_to_calendar"),
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json(config);
|
||||||
|
}
|
||||||
|
|
|
@ -103,7 +103,7 @@ export default abstract class BackupHelper {
|
||||||
let files = FileSystemHelper.getFilesInDirectory("backup", ".json");
|
let files = FileSystemHelper.getFilesInDirectory("backup", ".json");
|
||||||
let sorted = files.sort((a, b) => new Date(b.split(".")[0]).getTime() - new Date(a.split(".")[0]).getTime());
|
let sorted = files.sort((a, b) => new Date(b.split(".")[0]).getTime() - new Date(a.split(".")[0]).getTime());
|
||||||
|
|
||||||
const filesToDelete = sorted.slice(SettingHelper.getSetting("backup.copies") as number);
|
const filesToDelete = sorted.slice(SettingHelper.getSetting("backup.copies"));
|
||||||
for (const file of filesToDelete) {
|
for (const file of filesToDelete) {
|
||||||
FileSystemHelper.deleteFile("backup", file);
|
FileSystemHelper.deleteFile("backup", file);
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ export default abstract class BackupHelper {
|
||||||
let diffInMs = new Date().getTime() - lastBackup.getTime();
|
let diffInMs = new Date().getTime() - lastBackup.getTime();
|
||||||
let diffInDays = diffInMs / (1000 * 60 * 60 * 24);
|
let diffInDays = diffInMs / (1000 * 60 * 60 * 24);
|
||||||
|
|
||||||
if (diffInDays >= (SettingHelper.getSetting("backup.interval") as number)) {
|
if (diffInDays >= SettingHelper.getSetting("backup.interval")) {
|
||||||
await this.createBackup({});
|
await this.createBackup({});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,8 +36,8 @@ export abstract class CalendarHelper {
|
||||||
location: i.location,
|
location: i.location,
|
||||||
categories: [i.type.type],
|
categories: [i.type.type],
|
||||||
organizer: {
|
organizer: {
|
||||||
name: SettingHelper.getSetting("club.name") as string,
|
name: SettingHelper.getSetting("club.name"),
|
||||||
email: SettingHelper.getSetting("mail.username") as string,
|
email: SettingHelper.getSetting("mail.username"),
|
||||||
},
|
},
|
||||||
created: moment(i.createdAt)
|
created: moment(i.createdAt)
|
||||||
.format("YYYY-M-D-H-m")
|
.format("YYYY-M-D-H-m")
|
||||||
|
@ -49,9 +49,7 @@ export abstract class CalendarHelper {
|
||||||
.map((a) => parseInt(a)) as [number, number, number, number, number],
|
.map((a) => parseInt(a)) as [number, number, number, number, number],
|
||||||
transp: "OPAQUE" as "OPAQUE",
|
transp: "OPAQUE" as "OPAQUE",
|
||||||
status: "CONFIRMED",
|
status: "CONFIRMED",
|
||||||
...(SettingHelper.getSetting("club.website") != ""
|
...(SettingHelper.getSetting("club.website") != "" ? { url: SettingHelper.getSetting("club.website") } : {}),
|
||||||
? { url: SettingHelper.getSetting("club.website") as string }
|
|
||||||
: {}),
|
|
||||||
alarms: [
|
alarms: [
|
||||||
{
|
{
|
||||||
action: "display",
|
action: "display",
|
||||||
|
|
69
src/helpers/convertHelper.ts
Normal file
69
src/helpers/convertHelper.ts
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import ms from "ms";
|
||||||
|
|
||||||
|
export abstract class TypeConverter<T> {
|
||||||
|
abstract fromString(value: string): T;
|
||||||
|
abstract toString(value: T): string;
|
||||||
|
abstract validate(value: string): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class StringTypeConverter extends TypeConverter<string> {
|
||||||
|
fromString(value: string): string {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
toString(value: string): string {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
validate(value: string): boolean {
|
||||||
|
return typeof value === "string";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class NumberTypeConverter extends TypeConverter<number> {
|
||||||
|
fromString(value: string): number {
|
||||||
|
return Number(value);
|
||||||
|
}
|
||||||
|
toString(value: number): string {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
validate(value: string): boolean {
|
||||||
|
const num = Number(value);
|
||||||
|
return !isNaN(num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class BooleanTypeConverter extends TypeConverter<boolean> {
|
||||||
|
fromString(value: string): boolean {
|
||||||
|
return value === "true";
|
||||||
|
}
|
||||||
|
toString(value: boolean): string {
|
||||||
|
return value ? "true" : "false";
|
||||||
|
}
|
||||||
|
validate(value: string): boolean {
|
||||||
|
return value === "true" || value === "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class MsTypeConverter extends TypeConverter<ms.StringValue> {
|
||||||
|
fromString(value: string): ms.StringValue {
|
||||||
|
return value as ms.StringValue;
|
||||||
|
}
|
||||||
|
toString(value: ms.StringValue): string {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
validate(value: string): boolean {
|
||||||
|
try {
|
||||||
|
const result = ms(value as ms.StringValue);
|
||||||
|
return result !== undefined;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Konkrete Implementierungen der Converter
|
||||||
|
export class StringConverter extends StringTypeConverter {}
|
||||||
|
export class LongStringConverter extends StringTypeConverter {}
|
||||||
|
export class UrlConverter extends StringTypeConverter {}
|
||||||
|
export class NumberConverter extends NumberTypeConverter {}
|
||||||
|
export class BooleanConverter extends BooleanTypeConverter {}
|
||||||
|
export class MsConverter extends MsTypeConverter {}
|
|
@ -6,10 +6,12 @@ export default abstract class MailHelper {
|
||||||
private static transporter: Transporter;
|
private static transporter: Transporter;
|
||||||
|
|
||||||
static createTransport() {
|
static createTransport() {
|
||||||
|
this.transporter?.close();
|
||||||
|
|
||||||
this.transporter = createTransport({
|
this.transporter = createTransport({
|
||||||
host: SettingHelper.getSetting("mail.host"),
|
host: SettingHelper.getSetting("mail.host"),
|
||||||
port: SettingHelper.getSetting("mail.port"),
|
port: SettingHelper.getSetting("mail.port"),
|
||||||
secure: SettingHelper.getSetting("mail.secure") as boolean,
|
secure: SettingHelper.getSetting("mail.secure"),
|
||||||
auth: {
|
auth: {
|
||||||
user: SettingHelper.getSetting("mail.username"),
|
user: SettingHelper.getSetting("mail.username"),
|
||||||
pass: SettingHelper.getSetting("mail.password"),
|
pass: SettingHelper.getSetting("mail.password"),
|
||||||
|
@ -17,6 +19,13 @@ export default abstract class MailHelper {
|
||||||
} as TransportOptions);
|
} as TransportOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static initialize() {
|
||||||
|
SettingHelper.onSettingTopicChanged("mail", () => {
|
||||||
|
this.createTransport();
|
||||||
|
});
|
||||||
|
this.createTransport();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description send mail
|
* @description send mail
|
||||||
* @param {string} target
|
* @param {string} target
|
||||||
|
|
|
@ -1,135 +1,242 @@
|
||||||
import { SettingString, settingsType } from "../type/settingTypes";
|
import { SettingString, settingsType, SettingTopic, SettingTypeAtom, SettingValueMapping } from "../type/settingTypes";
|
||||||
import ms from "ms";
|
|
||||||
import { CodingHelper } from "./codingHelper";
|
import { CodingHelper } from "./codingHelper";
|
||||||
import SettingCommandHandler from "../command/settingCommandHandler";
|
import SettingCommandHandler from "../command/settingCommandHandler";
|
||||||
import SettingService from "../service/settingService";
|
import SettingService from "../service/settingService";
|
||||||
import { APPLICATION_SECRET } from "../env.defaults";
|
import { APPLICATION_SECRET } from "../env.defaults";
|
||||||
|
import {
|
||||||
|
BooleanConverter,
|
||||||
|
LongStringConverter,
|
||||||
|
MsConverter,
|
||||||
|
NumberConverter,
|
||||||
|
StringConverter,
|
||||||
|
TypeConverter,
|
||||||
|
UrlConverter,
|
||||||
|
} from "./convertHelper";
|
||||||
|
|
||||||
export default abstract class SettingHelper {
|
export default abstract class SettingHelper {
|
||||||
private static settings: { [key in SettingString]?: string } = {};
|
private static settings: { [key in SettingString]?: string } = {};
|
||||||
|
|
||||||
public static getSetting(key: SettingString): string | number | boolean | ms.StringValue {
|
private static listeners: Map<SettingString, Array<(newValue: any, oldValue: any) => void>> = new Map();
|
||||||
let settingType = settingsType[key];
|
private static topicListeners: Map<SettingTopic, Array<() => void>> = new Map();
|
||||||
let setting = this.settings[key] ?? String(settingType.default ?? "");
|
|
||||||
|
private static readonly converters: Record<SettingTypeAtom, TypeConverter<any>> = {
|
||||||
|
longstring: new LongStringConverter(),
|
||||||
|
string: new StringConverter(),
|
||||||
|
url: new UrlConverter(),
|
||||||
|
number: new NumberConverter(),
|
||||||
|
boolean: new BooleanConverter(),
|
||||||
|
ms: new MsConverter(),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of a setting with the correct type based on the key
|
||||||
|
* @param key The key of the setting
|
||||||
|
* @returns The typed value of the setting
|
||||||
|
*/
|
||||||
|
public static getSetting<K extends SettingString>(key: K): SettingValueMapping[K] {
|
||||||
|
const settingType = settingsType[key];
|
||||||
|
const rawValue = this.settings[key] ?? String(settingType.default ?? "");
|
||||||
|
|
||||||
if (Array.isArray(settingType.type)) {
|
if (Array.isArray(settingType.type)) {
|
||||||
return setting;
|
return rawValue as unknown as SettingValueMapping[K];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingType.type.includes("/crypt")) {
|
let processedValue = rawValue;
|
||||||
setting = CodingHelper.decrypt(APPLICATION_SECRET, String(setting));
|
if (typeof settingType.type === "string" && settingType.type.includes("/crypt")) {
|
||||||
|
processedValue = CodingHelper.decrypt(APPLICATION_SECRET, processedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingType.type.startsWith("string")) {
|
const baseType =
|
||||||
return setting;
|
typeof settingType.type === "string"
|
||||||
}
|
? (settingType.type.split("/")[0] as SettingTypeAtom)
|
||||||
if (settingType.type.startsWith("ms")) {
|
: (settingType.type as SettingTypeAtom);
|
||||||
return setting as ms.StringValue;
|
|
||||||
}
|
return this.converters[baseType].fromString(processedValue) as unknown as SettingValueMapping[K];
|
||||||
if (settingType.type.startsWith("number")) {
|
|
||||||
return Number(setting);
|
|
||||||
}
|
|
||||||
if (settingType.type.startsWith("boolean")) {
|
|
||||||
return setting == "true";
|
|
||||||
}
|
|
||||||
return setting;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async setSetting(key: SettingString, value: string) {
|
/**
|
||||||
if (value == undefined || value == null) return;
|
* Sets a setting
|
||||||
let settingType = settingsType[key];
|
* @param key The key of the setting
|
||||||
|
* @param value The value to set
|
||||||
|
*/
|
||||||
|
public static async setSetting(key: SettingString, value: string): Promise<void> {
|
||||||
|
if (value === undefined || value === null) return;
|
||||||
|
|
||||||
let result = value;
|
const settingType = settingsType[key];
|
||||||
|
this.validateSetting(key, value);
|
||||||
|
|
||||||
this.checkSettings(key, result);
|
const oldValue = this.getSetting(key);
|
||||||
|
let finalValue = value;
|
||||||
|
|
||||||
if (!Array.isArray(settingType.type) && settingType.type.includes("/crypt")) {
|
if (typeof settingType.type === "string" && settingType.type.includes("/crypt")) {
|
||||||
result = CodingHelper.encrypt(APPLICATION_SECRET, value);
|
finalValue = CodingHelper.encrypt(APPLICATION_SECRET, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.settings[key] = finalValue;
|
||||||
|
const [topic, settingKey] = key.split(".") as [SettingTopic, string];
|
||||||
|
|
||||||
await SettingCommandHandler.create({
|
await SettingCommandHandler.create({
|
||||||
topic: key.split(".")[0],
|
topic,
|
||||||
key: key.split(".")[1],
|
key: settingKey,
|
||||||
value: result,
|
value: finalValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const newValue = this.getSetting(key);
|
||||||
|
this.notifyListeners(key, newValue, oldValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async resetSetting(key: SettingString) {
|
/**
|
||||||
let settingType = settingsType[key];
|
* Resets a setting to its default value
|
||||||
|
* @param key The key of the setting
|
||||||
|
*/
|
||||||
|
public static async resetSetting(key: SettingString): Promise<void> {
|
||||||
|
const oldValue = this.getSetting(key);
|
||||||
|
|
||||||
|
const settingType = settingsType[key];
|
||||||
this.settings[key] = String(settingType.default ?? "");
|
this.settings[key] = String(settingType.default ?? "");
|
||||||
|
|
||||||
|
const [topic, settingKey] = key.split(".") as [SettingTopic, string];
|
||||||
await SettingCommandHandler.delete({
|
await SettingCommandHandler.delete({
|
||||||
topic: key.split(".")[0],
|
topic,
|
||||||
key: key.split(".")[1],
|
key: settingKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const newValue = this.getSetting(key);
|
||||||
|
this.notifyListeners(key, newValue, oldValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async configure() {
|
public static async configure(): Promise<void> {
|
||||||
console.log("Configured Settings");
|
console.log("Configuring Settings");
|
||||||
let settings = await SettingService.getSettings();
|
const settings = await SettingService.getSettings();
|
||||||
|
|
||||||
for (const element of settings) {
|
for (const element of settings) {
|
||||||
let ref = `${element.topic}.${element.key}` as SettingString;
|
const ref = `${element.topic}.${element.key}` as SettingString;
|
||||||
this.settings[ref] = element.value;
|
this.settings[ref] = element.value;
|
||||||
this.checkSettings(ref);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static checkSettings(key: SettingString, value?: string) {
|
try {
|
||||||
let settingType = settingsType[key];
|
this.validateSetting(ref);
|
||||||
|
} catch (error) {
|
||||||
if (!value) {
|
console.warn(`Invalid setting ${ref}: ${error.message}`);
|
||||||
value = this.getSetting(key).toString();
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (
|
|
||||||
!Array.isArray(settingType.type) &&
|
/**
|
||||||
settingType.type.startsWith("string") &&
|
* Validates a setting
|
||||||
this.checkIfEmptyOrNotString(value)
|
* @param key The key of the setting
|
||||||
) {
|
* @param value Optional value to validate
|
||||||
throw new Error(`set valid value to ${key}`);
|
*/
|
||||||
}
|
private static validateSetting(key: SettingString, value?: string): void {
|
||||||
if (
|
const settingType = settingsType[key];
|
||||||
!Array.isArray(settingType.type) &&
|
const valueToCheck = value ?? this.settings[key] ?? String(settingType.default ?? "");
|
||||||
settingType.type.startsWith("ms") &&
|
|
||||||
this.checkIfNotMS(value as ms.StringValue)
|
if (Array.isArray(settingType.type)) {
|
||||||
) {
|
return;
|
||||||
throw new Error(`set valid ms value to ${key} -> [0-9]*(y|d|h|m|s)`);
|
}
|
||||||
}
|
|
||||||
if (!Array.isArray(settingType.type) && settingType.type.startsWith("number") && isNaN(Number(value))) {
|
let processedValue = valueToCheck;
|
||||||
throw new Error(`set valid numeric value to ${key}`);
|
if (typeof settingType.type === "string" && settingType.type.includes("/crypt")) {
|
||||||
}
|
try {
|
||||||
if (
|
processedValue = CodingHelper.decrypt(APPLICATION_SECRET, processedValue);
|
||||||
!Array.isArray(settingType.type) &&
|
} catch (error) {
|
||||||
settingType.type.startsWith("number") &&
|
throw new Error(`Unable to decrypt value for ${key}: ${error.message}`);
|
||||||
settingType.min &&
|
}
|
||||||
Number(value) < settingType.min
|
}
|
||||||
) {
|
|
||||||
throw new Error(`${key} has to be at least ${settingType.min}`);
|
const baseType =
|
||||||
}
|
typeof settingType.type === "string"
|
||||||
if (
|
? (settingType.type.split("/")[0] as SettingTypeAtom)
|
||||||
!Array.isArray(settingType.type) &&
|
: (settingType.type as SettingTypeAtom);
|
||||||
settingType.type.startsWith("boolean") &&
|
|
||||||
value != "true" &&
|
if (!this.converters[baseType].validate(processedValue)) {
|
||||||
value != "false"
|
throw new Error(`Invalid value for ${key} of type ${baseType}`);
|
||||||
) {
|
}
|
||||||
throw new Error(`"set 'true' or 'false' to ${key}`);
|
|
||||||
}
|
if (baseType === "number" && settingType.min !== undefined) {
|
||||||
}
|
const numValue = Number(processedValue);
|
||||||
|
if (numValue < settingType.min) {
|
||||||
private static checkIfEmptyOrNotString(val: any) {
|
throw new Error(`${key} must be at least ${settingType.min}`);
|
||||||
return typeof val != "string" || val == "";
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private static checkIfNotMS(input: ms.StringValue): boolean {
|
|
||||||
try {
|
/**
|
||||||
const result = ms(input);
|
* Registers a listener for changes to a specific setting
|
||||||
if (result === undefined) {
|
* @param key The setting to monitor
|
||||||
return true;
|
* @param callback Function to be called when changes occur
|
||||||
|
*/
|
||||||
|
public static onSettingChanged<K extends SettingString>(
|
||||||
|
key: K,
|
||||||
|
callback: (newValue: SettingValueMapping[K], oldValue: SettingValueMapping[K]) => void
|
||||||
|
): void {
|
||||||
|
if (!this.listeners.has(key)) {
|
||||||
|
this.listeners.set(key, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listeners.get(key)!.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a listener for changes to a specific setting
|
||||||
|
* @param key The setting to monitor
|
||||||
|
* @param callback Function to be called when changes occur
|
||||||
|
*/
|
||||||
|
public static onSettingTopicChanged<K extends SettingTopic>(key: K, callback: () => void): void {
|
||||||
|
if (!this.topicListeners.has(key)) {
|
||||||
|
this.topicListeners.set(key, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.topicListeners.get(key)!.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a registered listener
|
||||||
|
* @param key The setting
|
||||||
|
* @param callback The callback to remove
|
||||||
|
*/
|
||||||
|
public static removeSettingListener<K extends SettingString>(
|
||||||
|
key: K,
|
||||||
|
callback: (newValue: SettingValueMapping[K], oldValue: SettingValueMapping[K]) => void
|
||||||
|
): void {
|
||||||
|
if (!this.listeners.has(key)) return;
|
||||||
|
|
||||||
|
const callbacks = this.listeners.get(key)!;
|
||||||
|
const index = callbacks.indexOf(callback);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
callbacks.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callbacks.length === 0) {
|
||||||
|
this.listeners.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies all registered listeners about changes
|
||||||
|
* @param key The changed setting
|
||||||
|
* @param newValue The new value
|
||||||
|
* @param oldValue The old value
|
||||||
|
*/
|
||||||
|
private static notifyListeners(key: SettingString, newValue: any, oldValue: any): void {
|
||||||
|
if (!this.listeners.has(key)) return;
|
||||||
|
|
||||||
|
const callbacks = this.listeners.get(key)!;
|
||||||
|
for (const callback of callbacks) {
|
||||||
|
try {
|
||||||
|
callback(newValue, oldValue);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error in setting listener for ${key}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const topicCallbacks = this.topicListeners.get(key.split(".")[0] as SettingTopic)!;
|
||||||
|
for (const callback of topicCallbacks) {
|
||||||
|
try {
|
||||||
|
callback();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error in setting listener for ${key.split(".")[0]}:`, error);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ dataSource.initialize().then(async () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await SettingHelper.configure();
|
await SettingHelper.configure();
|
||||||
MailHelper.createTransport();
|
MailHelper.initialize();
|
||||||
});
|
});
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
import { setting_table } from "./baseSchemaTables/admin";
|
import { setting_table } from "./baseSchemaTables/admin";
|
||||||
import { envSettingsType } from "../type/settingTypes";
|
|
||||||
import SettingHelper from "../helpers/settingsHelper";
|
import SettingHelper from "../helpers/settingsHelper";
|
||||||
|
|
||||||
export class SettingsFromEnv1745059495808 implements MigrationInterface {
|
export class SettingsFromEnv1745059495808 implements MigrationInterface {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { StringHelper } from "../helpers/stringHelper";
|
|
||||||
|
|
||||||
export type SettingTopic = "club" | "app" | "session" | "mail" | "backup" | "security";
|
export type SettingTopic = "club" | "app" | "session" | "mail" | "backup" | "security";
|
||||||
|
|
||||||
export type SettingString =
|
export type SettingString =
|
||||||
| "club.name"
|
| "club.name"
|
||||||
| "club.imprint"
|
| "club.imprint"
|
||||||
|
@ -24,14 +22,38 @@ export type SettingString =
|
||||||
export type SettingTypeAtom = "longstring" | "string" | "ms" | "number" | "boolean" | "url";
|
export type SettingTypeAtom = "longstring" | "string" | "ms" | "number" | "boolean" | "url";
|
||||||
export type SettingType = SettingTypeAtom | `${SettingTypeAtom}/crypt` | `${SettingTypeAtom}/rand`;
|
export type SettingType = SettingTypeAtom | `${SettingTypeAtom}/crypt` | `${SettingTypeAtom}/rand`;
|
||||||
|
|
||||||
export const settingsType: {
|
export type SettingValueMapping = {
|
||||||
[key in SettingString]: {
|
"club.name": string;
|
||||||
type: SettingType | SettingTypeAtom[];
|
"club.imprint": string;
|
||||||
default?: string | number | boolean | ms.StringValue;
|
"club.privacy": string;
|
||||||
optional?: boolean;
|
"club.website": string;
|
||||||
min?: number;
|
"app.custom_login_message": string;
|
||||||
};
|
"app.show_link_to_calendar": boolean;
|
||||||
} = {
|
"session.jwt_expiration": ms.StringValue;
|
||||||
|
"session.refresh_expiration": ms.StringValue;
|
||||||
|
"session.pwa_refresh_expiration": ms.StringValue;
|
||||||
|
"mail.username": string;
|
||||||
|
"mail.password": string;
|
||||||
|
"mail.host": string;
|
||||||
|
"mail.port": number;
|
||||||
|
"mail.secure": boolean;
|
||||||
|
"backup.interval": number;
|
||||||
|
"backup.copies": number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Typsicherer Zugriff auf Settings
|
||||||
|
export type SettingDefinition<T extends SettingType | SettingTypeAtom[]> = {
|
||||||
|
type: T;
|
||||||
|
default?: string | number | boolean;
|
||||||
|
optional?: boolean;
|
||||||
|
min?: T extends "number" | `number/crypt` | `number/rand` ? number : never;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SettingsSchema = {
|
||||||
|
[key in SettingString]: SettingDefinition<SettingType | SettingTypeAtom[]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const settingsType: SettingsSchema = {
|
||||||
"club.name": { type: "string", default: "FF Admin" },
|
"club.name": { type: "string", default: "FF Admin" },
|
||||||
"club.imprint": { type: "url", optional: true },
|
"club.imprint": { type: "url", optional: true },
|
||||||
"club.privacy": { type: "url", optional: true },
|
"club.privacy": { type: "url", optional: true },
|
||||||
|
|
Loading…
Add table
Reference in a new issue