From 730c25a9a1ec1b016f7f327edd51eec0d71045a9 Mon Sep 17 00:00:00 2001
From: Julian Krauser <jkrauser209@gmail.com>
Date: Sat, 19 Apr 2025 16:51:37 +0200
Subject: [PATCH] split in env required and dynamic values

---
 .env.example                                  |  19 +--
 package-lock.json                             |   8 +
 package.json                                  |   2 +
 src/command/refreshCommandHandler.ts          |   8 +-
 src/command/settingCommand.ts                 |  10 ++
 src/command/settingCommandHandler.ts          |  53 +++++++
 .../admin/management/userController.ts        |   4 +-
 .../admin/management/webapiController.ts      |   4 +-
 src/controller/inviteController.ts            |  11 +-
 src/controller/resetController.ts             |  12 +-
 src/controller/userController.ts              |   4 +-
 src/data-source.ts                            |  18 ++-
 .../{management/settings.ts => setting.ts}    |   4 +-
 src/env.defaults.ts                           |  50 +++---
 .../admin/club/member/dateMappingHelper.ts    |   4 +-
 src/helpers/backupHelper.ts                   |   6 +-
 src/helpers/calendarHelper.ts                 |  11 +-
 src/helpers/codingHelper.ts                   |  86 ++++++++++
 src/helpers/jwtHelper.ts                      |  14 +-
 src/helpers/mailHelper.ts                     |  26 +--
 src/helpers/newsletterHelper.ts               |  17 +-
 src/helpers/settingsHelper.ts                 | 148 +++++++++++++-----
 src/index.ts                                  |   7 +-
 .../1738166124200-BackupAndResetDatabase.ts   |   7 +-
 src/migrations/1738166167472-CreateSchema.ts  |   3 +-
 .../1745059495808-settingsFromEnv.ts          |  30 ++++
 src/migrations/baseSchemaTables/admin.ts      |   9 ++
 src/migrations/ormHelper.ts                   |   6 +-
 src/service/club/member/memberService.ts      |   8 +-
 src/service/settingService.ts                 |  43 +++++
 src/type/settingTypes.ts                      |  45 +++---
 src/views/memberExecutivePositionView.ts      |   3 +-
 src/views/memberQualificationsView.ts         |   3 +-
 src/views/memberView.ts                       |   3 +-
 src/views/membershipsView.ts                  |   3 +-
 35 files changed, 491 insertions(+), 198 deletions(-)
 create mode 100644 src/command/settingCommand.ts
 create mode 100644 src/command/settingCommandHandler.ts
 rename src/entity/{management/settings.ts => setting.ts} (77%)
 create mode 100644 src/helpers/codingHelper.ts
 create mode 100644 src/migrations/1745059495808-settingsFromEnv.ts
 create mode 100644 src/service/settingService.ts

diff --git a/.env.example b/.env.example
index 0d9bf9d..c1fe1dc 100644
--- a/.env.example
+++ b/.env.example
@@ -17,25 +17,10 @@ DB_PASSWORD = database_password
 ## BSP für sqlite
 DB_HOST = filename.db
 
+## Dev only
 SERVER_PORT = portnumber
 
-JWT_SECRET = ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 # besitzt default
-JWT_EXPIRATION = [0-9]*(y|d|h|m|s) # default ist 15m
-REFRESH_EXPIRATION = [0-9]*(y|d|h|m|s) # default ist 1d
-PWA_REFRESH_EXPIRATION = [0-9]*(y|d|h|m|s) # default ist 5d
-
-MAIL_USERNAME = mail_username
-MAIL_PASSWORD = mail_password
-MAIL_HOST = mail_hoststring
-MAIL_PORT = mail_portnumber # default ist 587
-MAIL_SECURE = (true|false) # true für port 465, false für anders gewählten port
-
-CLUB_NAME = clubname #default FF Admin
-CLUB_WEBSITE = https://my-club-website-url #optional, muss aber mit http:// oder https:// beginnen
-
-BACKUP_INTERVAL = number of days (min 1) # default 1
-BACKUP_COPIES = number of parallel copies # default 7
-BACKUP_AUTO_RESTORE = (true|false) # default ist true
+APPLICATION_SECRET = mysecret
 
 USE_SECURITY_STRICT_LIMIT = (true|false) # default ist true
 SECURITY_STRICT_LIMIT_WINDOW = [0-9]*(y|d|h|m|s) # default ist 15m
diff --git a/package-lock.json b/package-lock.json
index 901f487..a7c3021 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
       "license": "AGPL-3.0-only",
       "dependencies": {
         "cors": "^2.8.5",
+        "crypto": "^1.0.1",
         "dotenv": "^16.4.5",
         "express": "^5.1.0",
         "express-rate-limit": "^7.5.0",
@@ -1522,6 +1523,13 @@
         "node": ">= 8"
       }
     },
+    "node_modules/crypto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
+      "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
+      "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.",
+      "license": "ISC"
+    },
     "node_modules/data-uri-to-buffer": {
       "version": "6.0.2",
       "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
diff --git a/package.json b/package.json
index 357a7de..9f7d24c 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
     "start_ts": "ts-node src/index.ts",
     "typeorm": "typeorm-ts-node-commonjs",
     "migrate": "set DBMODE=migration && npx typeorm-ts-node-commonjs migration:generate ./src/migrations/%npm_config_name% -d ./src/data-source.ts",
+    "migrate-empty": "set DBMODE=migration && npx typeorm-ts-node-commonjs migration:create ./src/migrations/%npm_config_name%",
     "synchronize-database": "set DBMODE=update-database && npx typeorm-ts-node-commonjs schema:sync -d ./src/data-source.ts",
     "update-database": "set DBMODE=update-database && npx typeorm-ts-node-commonjs migration:run -d ./src/data-source.ts",
     "revert-database": "set DBMODE=update-database && npx typeorm-ts-node-commonjs migration:revert -d ./src/data-source.ts",
@@ -25,6 +26,7 @@
   "license": "AGPL-3.0-only",
   "dependencies": {
     "cors": "^2.8.5",
+    "crypto": "^1.0.1",
     "dotenv": "^16.4.5",
     "express": "^5.1.0",
     "express-rate-limit": "^7.5.0",
diff --git a/src/command/refreshCommandHandler.ts b/src/command/refreshCommandHandler.ts
index df6a8ea..959996a 100644
--- a/src/command/refreshCommandHandler.ts
+++ b/src/command/refreshCommandHandler.ts
@@ -1,10 +1,8 @@
 import { dataSource } from "../data-source";
 import { refresh } from "../entity/refresh";
-import { PWA_REFRESH_EXPIRATION, REFRESH_EXPIRATION } from "../env.defaults";
 import DatabaseActionException from "../exceptions/databaseActionException";
-import InternalException from "../exceptions/internalException";
+import SettingHelper from "../helpers/settingsHelper";
 import { StringHelper } from "../helpers/stringHelper";
-import UserService from "../service/management/userService";
 import { CreateRefreshCommand, DeleteRefreshCommand } from "./refreshCommand";
 import ms from "ms";
 
@@ -25,8 +23,8 @@ export default abstract class RefreshCommandHandler {
         token: refreshToken,
         userId: createRefresh.userId,
         expiry: createRefresh.isFromPwa
-          ? new Date(Date.now() + ms(PWA_REFRESH_EXPIRATION))
-          : new Date(Date.now() + ms(REFRESH_EXPIRATION)),
+          ? new Date(Date.now() + ms(SettingHelper.getSetting("session.pwa_refresh_expiration") as ms.StringValue))
+          : new Date(Date.now() + ms(SettingHelper.getSetting("session.refresh_expiration") as ms.StringValue)),
       })
       .execute()
       .then((result) => {
diff --git a/src/command/settingCommand.ts b/src/command/settingCommand.ts
new file mode 100644
index 0000000..e0c9d84
--- /dev/null
+++ b/src/command/settingCommand.ts
@@ -0,0 +1,10 @@
+export interface CreateOrUpdateSettingCommand {
+  topic: string;
+  key: string;
+  value: string;
+}
+
+export interface DeleteSettingCommand {
+  topic: string;
+  key: string;
+}
diff --git a/src/command/settingCommandHandler.ts b/src/command/settingCommandHandler.ts
new file mode 100644
index 0000000..148bdef
--- /dev/null
+++ b/src/command/settingCommandHandler.ts
@@ -0,0 +1,53 @@
+import { dataSource } from "../data-source";
+import { setting } from "../entity/setting";
+import DatabaseActionException from "../exceptions/databaseActionException";
+import { StringHelper } from "../helpers/stringHelper";
+import { CreateOrUpdateSettingCommand, DeleteSettingCommand } from "./settingCommand";
+
+export default abstract class SettingCommandHandler {
+  /**
+   * @description create setting
+   * @param {CreateOrUpdateSettingCommand} createSetting
+   * @returns {Promise<string>}
+   */
+  static async create(createSetting: CreateOrUpdateSettingCommand): Promise<string> {
+    const token = StringHelper.random(32);
+
+    return await dataSource
+      .createQueryBuilder()
+      .insert()
+      .into(setting)
+      .values({
+        topic: createSetting.topic,
+        key: createSetting.key,
+        value: createSetting.value,
+      })
+      .orUpdate(["value"], ["topic", "key"])
+      .execute()
+      .then((result) => {
+        return token;
+      })
+      .catch((err) => {
+        throw new DatabaseActionException("CREATE OR UPDATE", "setting", err);
+      });
+  }
+
+  /**
+   * @description delete setting by topic and key
+   * @param {DeleteRefreshCommand} deleteSetting
+   * @returns {Promise<any>}
+   */
+  static async delete(deleteSetting: DeleteSettingCommand): Promise<any> {
+    return await dataSource
+      .createQueryBuilder()
+      .delete()
+      .from(setting)
+      .where("setting.topic = :topic", { topic: deleteSetting.topic })
+      .andWhere("setting.key = :key", { key: deleteSetting.key })
+      .execute()
+      .then((res) => {})
+      .catch((err) => {
+        throw new DatabaseActionException("DELETE", "setting", err);
+      });
+  }
+}
diff --git a/src/controller/admin/management/userController.ts b/src/controller/admin/management/userController.ts
index ba2a134..7da2460 100644
--- a/src/controller/admin/management/userController.ts
+++ b/src/controller/admin/management/userController.ts
@@ -11,10 +11,10 @@ import {
 } from "../../../command/management/user/userCommand";
 import UserCommandHandler from "../../../command/management/user/userCommandHandler";
 import MailHelper from "../../../helpers/mailHelper";
-import { CLUB_NAME } from "../../../env.defaults";
 import { UpdateUserPermissionsCommand } from "../../../command/management/user/userPermissionCommand";
 import UserPermissionCommandHandler from "../../../command/management/user/userPermissionCommandHandler";
 import BadRequestException from "../../../exceptions/badRequestException";
+import SettingHelper from "../../../helpers/settingsHelper";
 
 /**
  * @description get All users
@@ -157,7 +157,7 @@ export async function deleteUser(req: Request, res: Response): Promise<any> {
     // sendmail
     await MailHelper.sendMail(
       mail,
-      `Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`,
+      `Email Bestätigung für Mitglieder Admin-Portal von ${SettingHelper.getSetting("club.name")}`,
       `Ihr Nutzerkonto des Adminportals wurde erfolgreich gelöscht.`
     );
   } catch (error) {}
diff --git a/src/controller/admin/management/webapiController.ts b/src/controller/admin/management/webapiController.ts
index 80df60a..38d40cd 100644
--- a/src/controller/admin/management/webapiController.ts
+++ b/src/controller/admin/management/webapiController.ts
@@ -12,8 +12,8 @@ import WebapiCommandHandler from "../../../command/management/webapi/webapiComma
 import { UpdateWebapiPermissionsCommand } from "../../../command/management/webapi/webapiPermissionCommand";
 import WebapiPermissionCommandHandler from "../../../command/management/webapi/webapiPermissionCommandHandler";
 import { JWTHelper } from "../../../helpers/jwtHelper";
-import { CLUB_NAME } from "../../../env.defaults";
 import { StringHelper } from "../../../helpers/stringHelper";
+import SettingHelper from "../../../helpers/settingsHelper";
 
 /**
  * @description get All apis
@@ -78,7 +78,7 @@ export async function createWebapi(req: Request, res: Response): Promise<any> {
 
   let token = await JWTHelper.create(
     {
-      iss: CLUB_NAME,
+      iss: SettingHelper.getSetting("club.name") as string,
       sub: "api_token_retrieve",
       aud: StringHelper.random(32),
     },
diff --git a/src/controller/inviteController.ts b/src/controller/inviteController.ts
index 14e346c..769260f 100644
--- a/src/controller/inviteController.ts
+++ b/src/controller/inviteController.ts
@@ -1,6 +1,5 @@
 import { Request, Response } from "express";
 import { JWTHelper } from "../helpers/jwtHelper";
-import { JWTToken } from "../type/jwtTypes";
 import InternalException from "../exceptions/internalException";
 import RefreshCommandHandler from "../command/refreshCommandHandler";
 import { CreateRefreshCommand } from "../command/refreshCommand";
@@ -15,10 +14,8 @@ import MailHelper from "../helpers/mailHelper";
 import InviteService from "../service/management/inviteService";
 import UserService from "../service/management/userService";
 import CustomRequestException from "../exceptions/customRequestException";
-import { CLUB_NAME } from "../env.defaults";
-import { CreateUserPermissionCommand } from "../command/management/user/userPermissionCommand";
-import UserPermissionCommandHandler from "../command/management/user/userPermissionCommandHandler";
 import InviteFactory from "../factory/admin/management/invite";
+import SettingHelper from "../helpers/settingsHelper";
 
 /**
  * @description get all invites
@@ -59,7 +56,7 @@ export async function inviteUser(req: Request, res: Response, isInvite: boolean
     throw new CustomRequestException(409, "Username and Mail are already in use");
   }
 
-  var secret = speakeasy.generateSecret({ length: 20, name: `FF Admin ${CLUB_NAME}` });
+  var secret = speakeasy.generateSecret({ length: 20, name: `FF Admin ${SettingHelper.getSetting("club.name")}` });
 
   let createInvite: CreateInviteCommand = {
     username: username,
@@ -73,7 +70,7 @@ export async function inviteUser(req: Request, res: Response, isInvite: boolean
   // sendmail
   await MailHelper.sendMail(
     mail,
-    `Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`,
+    `Email Bestätigung für Mitglieder Admin-Portal von ${SettingHelper.getSetting("club.name")}`,
     `Öffne folgenden Link: ${origin}/${isInvite ? "invite" : "setup"}/verify?mail=${mail}&token=${token}`
   );
 
@@ -92,7 +89,7 @@ export async function verifyInvite(req: Request, res: Response): Promise<any> {
 
   let { secret, username } = await InviteService.getByMailAndToken(mail, token);
 
-  const url = `otpauth://totp/FF Admin ${CLUB_NAME}?secret=${secret}`;
+  const url = `otpauth://totp/FF Admin ${SettingHelper.getSetting("club.name")}?secret=${secret}`;
 
   QRCode.toDataURL(url)
     .then((result) => {
diff --git a/src/controller/resetController.ts b/src/controller/resetController.ts
index fc0dc3e..3592639 100644
--- a/src/controller/resetController.ts
+++ b/src/controller/resetController.ts
@@ -1,6 +1,5 @@
 import { Request, Response } from "express";
 import { JWTHelper } from "../helpers/jwtHelper";
-import { JWTToken } from "../type/jwtTypes";
 import InternalException from "../exceptions/internalException";
 import RefreshCommandHandler from "../command/refreshCommandHandler";
 import { CreateRefreshCommand } from "../command/refreshCommand";
@@ -12,12 +11,9 @@ import ResetCommandHandler from "../command/resetCommandHandler";
 import MailHelper from "../helpers/mailHelper";
 import ResetService from "../service/resetService";
 import UserService from "../service/management/userService";
-import { CLUB_NAME } from "../env.defaults";
-import PermissionHelper from "../helpers/permissionHelper";
-import RolePermissionService from "../service/management/rolePermissionService";
-import UserPermissionService from "../service/management/userPermissionService";
 import { UpdateUserSecretCommand } from "../command/management/user/userCommand";
 import UserCommandHandler from "../command/management/user/userCommandHandler";
+import SettingHelper from "../helpers/settingsHelper";
 
 /**
  * @description request totp reset
@@ -31,7 +27,7 @@ export async function startReset(req: Request, res: Response): Promise<any> {
 
   let { mail } = await UserService.getByUsername(username);
 
-  var secret = speakeasy.generateSecret({ length: 20, name: `FF Admin ${CLUB_NAME}` });
+  var secret = speakeasy.generateSecret({ length: 20, name: `FF Admin ${SettingHelper.getSetting("club.name")}` });
 
   let createReset: CreateResetCommand = {
     username: username,
@@ -43,7 +39,7 @@ export async function startReset(req: Request, res: Response): Promise<any> {
   // sendmail
   await MailHelper.sendMail(
     mail,
-    `Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`,
+    `Email Bestätigung für Mitglieder Admin-Portal von ${SettingHelper.getSetting("club.name")}`,
     `Öffne folgenden Link: ${origin}/reset/reset?mail=${mail}&token=${token}`
   );
 
@@ -62,7 +58,7 @@ export async function verifyReset(req: Request, res: Response): Promise<any> {
 
   let { secret } = await ResetService.getByMailAndToken(mail, token);
 
-  const url = `otpauth://totp/FF Admin ${CLUB_NAME}?secret=${secret}`;
+  const url = `otpauth://totp/FF Admin ${SettingHelper.getSetting("club.name")}?secret=${secret}`;
 
   QRCode.toDataURL(url)
     .then((result) => {
diff --git a/src/controller/userController.ts b/src/controller/userController.ts
index 1e764f1..8827dc9 100644
--- a/src/controller/userController.ts
+++ b/src/controller/userController.ts
@@ -2,12 +2,12 @@ import { Request, Response } from "express";
 import speakeasy from "speakeasy";
 import QRCode from "qrcode";
 import InternalException from "../exceptions/internalException";
-import { CLUB_NAME } from "../env.defaults";
 import UserService from "../service/management/userService";
 import UserFactory from "../factory/admin/management/user";
 import { TransferUserOwnerCommand, UpdateUserCommand } from "../command/management/user/userCommand";
 import UserCommandHandler from "../command/management/user/userCommandHandler";
 import ForbiddenRequestException from "../exceptions/forbiddenRequestException";
+import SettingHelper from "../helpers/settingsHelper";
 
 /**
  * @description get my by id
@@ -33,7 +33,7 @@ export async function getMyTotp(req: Request, res: Response): Promise<any> {
 
   let { secret } = await UserService.getById(userId);
 
-  const url = `otpauth://totp/FF Admin ${CLUB_NAME}?secret=${secret}`;
+  const url = `otpauth://totp/FF Admin ${SettingHelper.getSetting("club.name")}?secret=${secret}`;
 
   QRCode.toDataURL(url)
     .then((result) => {
diff --git a/src/data-source.ts b/src/data-source.ts
index d7be30d..d94d77f 100644
--- a/src/data-source.ts
+++ b/src/data-source.ts
@@ -1,7 +1,6 @@
 import "dotenv/config";
 import "reflect-metadata";
 import { DataSource } from "typeorm";
-import { SettingHelper } from "./helpers/settingsHelper";
 
 import { user } from "./entity/management/user";
 import { refresh } from "./entity/refresh";
@@ -51,14 +50,17 @@ import { TemplatesAndProtocolSort1742549956787 } from "./migrations/174254995678
 import { QueryToUUID1742922178643 } from "./migrations/1742922178643-queryToUUID";
 import { NewsletterColumnType1744351418751 } from "./migrations/1744351418751-newsletterColumnType";
 import { QueryUpdatedAt1744795756230 } from "./migrations/1744795756230-QueryUpdatedAt";
+import { setting } from "./entity/setting";
+import { SettingsFromEnv1745059495808 } from "./migrations/1745059495808-settingsFromEnv";
+import { DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_TYPE, DB_USERNAME } from "./env.defaults";
 
 const dataSource = new DataSource({
-  type: SettingHelper.getEnvSetting("database.type") as any,
-  host: SettingHelper.getEnvSetting("database.host"),
-  port: Number(SettingHelper.getEnvSetting("database.port")),
-  username: SettingHelper.getEnvSetting("database.username"),
-  password: SettingHelper.getEnvSetting("database.password"),
-  database: SettingHelper.getEnvSetting("database.name"),
+  type: DB_TYPE as any,
+  host: DB_HOST,
+  port: DB_PORT,
+  username: DB_USERNAME,
+  password: DB_PASSWORD,
+  database: DB_NAME,
   synchronize: false,
   logging: process.env.NODE_ENV ? true : ["schema", "error", "warn", "log", "migration"],
   bigNumberStrings: false,
@@ -103,6 +105,7 @@ const dataSource = new DataSource({
     membershipView,
     webapi,
     webapiPermission,
+    setting,
   ],
   migrations: [
     BackupAndResetDatabase1738166124200,
@@ -111,6 +114,7 @@ const dataSource = new DataSource({
     QueryToUUID1742922178643,
     NewsletterColumnType1744351418751,
     QueryUpdatedAt1744795756230,
+    SettingsFromEnv1745059495808,
   ],
   migrationsRun: true,
   migrationsTransactionMode: "each",
diff --git a/src/entity/management/settings.ts b/src/entity/setting.ts
similarity index 77%
rename from src/entity/management/settings.ts
rename to src/entity/setting.ts
index 0057cbc..99d3cda 100644
--- a/src/entity/management/settings.ts
+++ b/src/entity/setting.ts
@@ -1,13 +1,13 @@
 import { Column, Entity, PrimaryColumn } from "typeorm";
 
 @Entity()
-export class user {
+export class setting {
   @PrimaryColumn({ type: "varchar", length: 255 })
   topic: string;
 
   @PrimaryColumn({ type: "varchar", length: 255 })
   key: string;
 
-  @Column({ type: "varchar", length: 255 })
+  @Column({ type: "text" })
   value: string;
 }
diff --git a/src/env.defaults.ts b/src/env.defaults.ts
index 97a3928..f7bc48d 100644
--- a/src/env.defaults.ts
+++ b/src/env.defaults.ts
@@ -2,22 +2,16 @@ import "dotenv/config";
 import ms from "ms";
 import ip from "ip";
 
-export const JWT_SECRET = process.env.JWT_SECRET ?? "my_jwt_secret_string_ilughfnadiuhgq§$IUZGFVRweiouarbt1oub3h5q4a";
-export const JWT_EXPIRATION = (process.env.JWT_EXPIRATION ?? "15m") as ms.StringValue;
-export const REFRESH_EXPIRATION = (process.env.REFRESH_EXPIRATION ?? "1d") as ms.StringValue;
-export const PWA_REFRESH_EXPIRATION = (process.env.PWA_REFRESH_EXPIRATION ?? "5d") as ms.StringValue;
+export const DB_TYPE = process.env.DB_TYPE ?? "mysql";
+export const DB_HOST = process.env.DB_HOST ?? "";
+export const DB_PORT = Number(process.env.DB_PORT ?? 3306);
+export const DB_NAME = process.env.DB_NAME ?? "";
+export const DB_USERNAME = process.env.DB_USERNAME ?? "";
+export const DB_PASSWORD = process.env.DB_PASSWORD ?? "";
 
-export const MAIL_USERNAME = process.env.MAIL_USERNAME ?? "";
-export const MAIL_PASSWORD = process.env.MAIL_PASSWORD ?? "";
-export const MAIL_HOST = process.env.MAIL_HOST ?? "";
-export const MAIL_PORT = Number(process.env.MAIL_PORT ?? "587");
-export const MAIL_SECURE = process.env.MAIL_SECURE ?? "false";
+export const SERVER_PORT = Number(process.env.SERVER_PORT ?? 5000);
 
-export const CLUB_NAME = process.env.CLUB_NAME ?? "FF Admin";
-export const CLUB_WEBSITE = process.env.CLUB_WEBSITE ?? "";
-
-export const BACKUP_INTERVAL = Number(process.env.BACKUP_INTERVAL ?? "1");
-export const BACKUP_COPIES = Number(process.env.BACKUP_COPIES ?? "7");
+export const APPLICATION_SECRET = process.env.APPLICATION_SECRET;
 
 export const USE_SECURITY_STRICT_LIMIT = process.env.USE_SECURITY_STRICT_LIMIT ?? "true";
 export const SECURITY_STRICT_LIMIT_WINDOW = (process.env.SECURITY_STRICT_LIMIT_WINDOW ?? "15m") as ms.StringValue;
@@ -45,25 +39,17 @@ export const TRUST_PROXY = ((): Array<string> | string | boolean | number | null
 })();
 
 export function configCheck() {
-  if (JWT_SECRET == "" || typeof JWT_SECRET != "string") throw new Error("set valid value to JWT_SECRET");
-  checkMS(JWT_EXPIRATION, "JWT_EXPIRATION");
-  checkMS(REFRESH_EXPIRATION, "REFRESH_EXPIRATION");
-  checkMS(PWA_REFRESH_EXPIRATION, "PWA_REFRESH_EXPIRATION");
+  if (DB_TYPE != "mysql" && DB_TYPE != "sqlite" && DB_TYPE != "postgres")
+    throw new Error("set valid value to DB_TYPE (mysql|sqlite|postgres)");
+  if ((DB_HOST == "" || typeof DB_HOST != "string") && DB_TYPE != "sqlite")
+    throw new Error("set valid value to DB_HOST");
+  if (DB_NAME == "" || typeof DB_NAME != "string") throw new Error("set valid value to DB_NAME");
+  if ((DB_USERNAME == "" || typeof DB_USERNAME != "string") && DB_TYPE != "sqlite")
+    throw new Error("set valid value to DB_USERNAME");
+  if ((DB_PASSWORD == "" || typeof DB_PASSWORD != "string") && DB_TYPE != "sqlite")
+    throw new Error("set valid value to DB_PASSWORD");
 
-  if (MAIL_USERNAME == "" || typeof MAIL_USERNAME != "string") throw new Error("set valid value to MAIL_USERNAME");
-  if (MAIL_PASSWORD == "" || typeof MAIL_PASSWORD != "string") throw new Error("set valid value to MAIL_PASSWORD");
-  if (MAIL_HOST == "" || typeof MAIL_HOST != "string") throw new Error("set valid value to MAIL_HOST");
-  if (isNaN(MAIL_PORT)) throw new Error("set valid numeric value to MAIL_PORT");
-  if (MAIL_SECURE != "true" && MAIL_SECURE != "false") throw new Error("set 'true' or 'false' to MAIL_SECURE");
-
-  if (
-    CLUB_WEBSITE != "" &&
-    !/^(http(s):\/\/.)[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/.test(CLUB_WEBSITE)
-  )
-    throw new Error("CLUB_WEBSITE is not valid url");
-
-  if (BACKUP_INTERVAL < 1) throw new Error("BACKUP_INTERVAL has to be at least 1");
-  if (BACKUP_COPIES < 1) throw new Error("BACKUP_COPIES has to be at least 1");
+  if (isNaN(SERVER_PORT)) throw new Error("set valid numeric value to SERVER_PORT");
 
   if (USE_SECURITY_STRICT_LIMIT != "true" && USE_SECURITY_STRICT_LIMIT != "false")
     throw new Error("set 'true' or 'false' to USE_SECURITY_STRICT_LIMIT");
diff --git a/src/factory/admin/club/member/dateMappingHelper.ts b/src/factory/admin/club/member/dateMappingHelper.ts
index e4b6a2c..8fdd072 100644
--- a/src/factory/admin/club/member/dateMappingHelper.ts
+++ b/src/factory/admin/club/member/dateMappingHelper.ts
@@ -1,8 +1,8 @@
-import { SettingHelper } from "../../../../helpers/settingsHelper";
+import { DB_TYPE } from "../../../../env.defaults";
 
 export default abstract class DateMappingHelper {
   static mapDate(entry: any) {
-    switch (SettingHelper.getEnvSetting("database.type")) {
+    switch (DB_TYPE) {
       case "postgres":
         return `${entry?.years ?? 0} years ${entry?.months ?? 0} months ${entry?.days ?? 0} days`;
       case "mysql":
diff --git a/src/helpers/backupHelper.ts b/src/helpers/backupHelper.ts
index 319a298..76f7693 100644
--- a/src/helpers/backupHelper.ts
+++ b/src/helpers/backupHelper.ts
@@ -4,9 +4,9 @@ import { EntityManager } from "typeorm";
 import uniqBy from "lodash.uniqby";
 import InternalException from "../exceptions/internalException";
 import UserService from "../service/management/userService";
-import { BACKUP_COPIES, BACKUP_INTERVAL } from "../env.defaults";
 import DatabaseActionException from "../exceptions/databaseActionException";
 import { availableTemplates } from "../type/templateTypes";
+import SettingHelper from "./settingsHelper";
 
 export type BackupSection =
   | "member"
@@ -103,7 +103,7 @@ export default abstract class BackupHelper {
     let files = FileSystemHelper.getFilesInDirectory("backup", ".json");
     let sorted = files.sort((a, b) => new Date(b.split(".")[0]).getTime() - new Date(a.split(".")[0]).getTime());
 
-    const filesToDelete = sorted.slice(BACKUP_COPIES);
+    const filesToDelete = sorted.slice(SettingHelper.getSetting("backup.copies") as number);
     for (const file of filesToDelete) {
       FileSystemHelper.deleteFile("backup", file);
     }
@@ -117,7 +117,7 @@ export default abstract class BackupHelper {
     let diffInMs = new Date().getTime() - lastBackup.getTime();
     let diffInDays = diffInMs / (1000 * 60 * 60 * 24);
 
-    if (diffInDays >= BACKUP_INTERVAL) {
+    if (diffInDays >= (SettingHelper.getSetting("backup.interval") as number)) {
       await this.createBackup({});
     }
   }
diff --git a/src/helpers/calendarHelper.ts b/src/helpers/calendarHelper.ts
index 7418089..fb60496 100644
--- a/src/helpers/calendarHelper.ts
+++ b/src/helpers/calendarHelper.ts
@@ -1,7 +1,7 @@
 import { createEvents } from "ics";
 import { calendar } from "../entity/club/calendar";
 import moment from "moment";
-import { CLUB_NAME, CLUB_WEBSITE, MAIL_USERNAME } from "../env.defaults";
+import SettingHelper from "./settingsHelper";
 
 export abstract class CalendarHelper {
   public static buildICS(entries: Array<calendar>): { error?: Error; value?: string } {
@@ -35,7 +35,10 @@ export abstract class CalendarHelper {
         description: i.content,
         location: i.location,
         categories: [i.type.type],
-        organizer: { name: CLUB_NAME, email: MAIL_USERNAME },
+        organizer: {
+          name: SettingHelper.getSetting("club.name") as string,
+          email: SettingHelper.getSetting("mail.username") as string,
+        },
         created: moment(i.createdAt)
           .format("YYYY-M-D-H-m")
           .split("-")
@@ -46,7 +49,9 @@ export abstract class CalendarHelper {
           .map((a) => parseInt(a)) as [number, number, number, number, number],
         transp: "OPAQUE" as "OPAQUE",
         status: "CONFIRMED",
-        ...(CLUB_WEBSITE != "" ? { url: CLUB_WEBSITE } : {}),
+        ...(SettingHelper.getSetting("club.website") != ""
+          ? { url: SettingHelper.getSetting("club.website") as string }
+          : {}),
         alarms: [
           {
             action: "display",
diff --git a/src/helpers/codingHelper.ts b/src/helpers/codingHelper.ts
new file mode 100644
index 0000000..e6a79f9
--- /dev/null
+++ b/src/helpers/codingHelper.ts
@@ -0,0 +1,86 @@
+import { createCipheriv, createDecipheriv, scryptSync, randomBytes } from "crypto";
+import { ValueTransformer } from "typeorm";
+
+export abstract class CodingHelper {
+  private static readonly algorithm = "aes-256-gcm";
+  private static readonly ivLength = 16;
+  private static readonly authTagLength = 16;
+
+  static entityBaseCoding(key: string = "", fallback: string = ""): ValueTransformer {
+    return {
+      from(val: string | null | undefined): string {
+        if (!val) return fallback;
+        try {
+          return CodingHelper.decrypt(key, val) || fallback;
+        } catch (error) {
+          console.error("Decryption error:", error);
+          return fallback;
+        }
+      },
+      to(val: string | null | undefined): string {
+        const valueToEncrypt = val || fallback;
+        if (valueToEncrypt === "") return "";
+
+        try {
+          return CodingHelper.encrypt(key, valueToEncrypt);
+        } catch (error) {
+          console.error("Encryption error:", error);
+          return "";
+        }
+      },
+    };
+  }
+
+  public static encrypt(phrase: string, content: string): string {
+    if (!content) return "";
+
+    // Generiere zufälligen IV für jede Verschlüsselung (sicherer als statischer IV)
+    const iv = randomBytes(this.ivLength);
+    const key = scryptSync(phrase, "salt", 32);
+
+    const cipher = createCipheriv(this.algorithm, Uint8Array.from(key), Uint8Array.from(iv));
+
+    // Verschlüssele den Inhalt
+    let encrypted = cipher.update(content, "utf8", "hex");
+    encrypted += cipher.final("hex");
+
+    // Speichere das Auth-Tag für GCM (wichtig für die Entschlüsselung)
+    const authTag = cipher.getAuthTag();
+
+    // Gib das Format: iv:verschlüsselter_text:authTag zurück
+    return Buffer.concat([
+      Uint8Array.from(iv),
+      Uint8Array.from(Buffer.from(encrypted, "hex")),
+      Uint8Array.from(authTag),
+    ]).toString("base64");
+  }
+
+  public static decrypt(phrase: string, content: string): string {
+    if (!content) return "";
+
+    try {
+      // Dekodiere den Base64-String
+      const buffer = Buffer.from(content, "base64");
+
+      // Extrahiere IV, verschlüsselten Text und Auth-Tag
+      const iv = buffer.subarray(0, this.ivLength);
+      const authTag = buffer.subarray(buffer.length - this.authTagLength);
+      const encryptedText = buffer.subarray(this.ivLength, buffer.length - this.authTagLength).toString("hex");
+
+      const key = scryptSync(phrase, "salt", 32);
+
+      // Erstelle Decipher und setze Auth-Tag
+      const decipher = createDecipheriv(this.algorithm, Uint8Array.from(key), Uint8Array.from(iv));
+      decipher.setAuthTag(Uint8Array.from(authTag));
+
+      // Entschlüssele den Text
+      let decrypted = decipher.update(encryptedText, "hex", "utf8");
+      decrypted += decipher.final("utf8");
+
+      return decrypted;
+    } catch (error) {
+      console.error("Decryption failed:", error);
+      return "";
+    }
+  }
+}
diff --git a/src/helpers/jwtHelper.ts b/src/helpers/jwtHelper.ts
index 5708ab8..066bb8b 100644
--- a/src/helpers/jwtHelper.ts
+++ b/src/helpers/jwtHelper.ts
@@ -1,6 +1,5 @@
 import jwt from "jsonwebtoken";
 import { JWTData, JWTToken } from "../type/jwtTypes";
-import { JWT_SECRET, JWT_EXPIRATION } from "../env.defaults";
 import InternalException from "../exceptions/internalException";
 import RolePermissionService from "../service/management/rolePermissionService";
 import UserPermissionService from "../service/management/userPermissionService";
@@ -9,11 +8,13 @@ import PermissionHelper from "./permissionHelper";
 import WebapiService from "../service/management/webapiService";
 import WebapiPermissionService from "../service/management/webapiPermissionService";
 import ms from "ms";
+import SettingHelper from "./settingsHelper";
+import { APPLICATION_SECRET } from "../env.defaults";
 
 export abstract class JWTHelper {
   static validate(token: string): Promise<string | jwt.JwtPayload> {
     return new Promise<string | jwt.JwtPayload>((resolve, reject) => {
-      jwt.verify(token, JWT_SECRET, (err, decoded) => {
+      jwt.verify(token, APPLICATION_SECRET, (err, decoded) => {
         if (err) reject(err.message);
         else resolve(decoded);
       });
@@ -27,9 +28,11 @@ export abstract class JWTHelper {
     return new Promise<string>((resolve, reject) => {
       jwt.sign(
         data,
-        JWT_SECRET,
+        APPLICATION_SECRET,
         {
-          ...(useExpiration ?? true ? { expiresIn: expOverwrite ?? JWT_EXPIRATION } : {}),
+          ...(useExpiration ?? true
+            ? { expiresIn: expOverwrite ?? (SettingHelper.getSetting("session.jwt_expiration") as ms.StringValue) }
+            : {}),
         },
         (err, token) => {
           if (err) reject(err.message);
@@ -100,7 +103,8 @@ export abstract class JWTHelper {
     };
 
     let overwriteExpiration =
-      ms(JWT_EXPIRATION) < new Date().getTime() - new Date(expiration).getTime()
+      ms(SettingHelper.getSetting("session.jwt_expiration") as ms.StringValue) <
+      new Date().getTime() - new Date(expiration).getTime()
         ? null
         : Date.now() - new Date(expiration).getTime();
 
diff --git a/src/helpers/mailHelper.ts b/src/helpers/mailHelper.ts
index ab44a26..1bf8634 100644
--- a/src/helpers/mailHelper.ts
+++ b/src/helpers/mailHelper.ts
@@ -1,17 +1,21 @@
 import { Transporter, createTransport, TransportOptions } from "nodemailer";
-import { CLUB_NAME, MAIL_HOST, MAIL_PASSWORD, MAIL_PORT, MAIL_SECURE, MAIL_USERNAME } from "../env.defaults";
 import { Attachment } from "nodemailer/lib/mailer";
+import SettingHelper from "./settingsHelper";
 
 export default abstract class MailHelper {
-  private static readonly transporter: Transporter = createTransport({
-    host: MAIL_HOST,
-    port: MAIL_PORT,
-    secure: (MAIL_SECURE as "true" | "false") == "true",
-    auth: {
-      user: MAIL_USERNAME,
-      pass: MAIL_PASSWORD,
-    },
-  } as TransportOptions);
+  private static transporter: Transporter;
+
+  static createTransport() {
+    this.transporter = createTransport({
+      host: SettingHelper.getSetting("mail.host"),
+      port: SettingHelper.getSetting("mail.port"),
+      secure: SettingHelper.getSetting("mail.secure") as boolean,
+      auth: {
+        user: SettingHelper.getSetting("mail.username"),
+        pass: SettingHelper.getSetting("mail.password"),
+      },
+    } as TransportOptions);
+  }
 
   /**
    * @description send mail
@@ -29,7 +33,7 @@ export default abstract class MailHelper {
     return new Promise((resolve, reject) => {
       this.transporter
         .sendMail({
-          from: `"${CLUB_NAME}" <${MAIL_USERNAME}>`,
+          from: `"${SettingHelper.getSetting("club.name")}" <${SettingHelper.getSetting("mail.username")}>`,
           to: target,
           subject,
           text: content,
diff --git a/src/helpers/newsletterHelper.ts b/src/helpers/newsletterHelper.ts
index 32cdd16..c09da94 100644
--- a/src/helpers/newsletterHelper.ts
+++ b/src/helpers/newsletterHelper.ts
@@ -10,13 +10,13 @@ import { CalendarHelper } from "./calendarHelper";
 import DynamicQueryBuilder from "./dynamicQueryBuilder";
 import { FileSystemHelper } from "./fileSystemHelper";
 import MailHelper from "./mailHelper";
-import { CLUB_NAME } from "../env.defaults";
 import { TemplateHelper } from "./templateHelper";
 import { PdfExport } from "./pdfExport";
 import NewsletterConfigService from "../service/configuration/newsletterConfigService";
 import { NewsletterConfigEnum } from "../enums/newsletterConfigEnum";
 import InternalException from "../exceptions/internalException";
 import EventEmitter from "events";
+import SettingHelper from "./settingsHelper";
 
 export interface NewsletterEventType {
   kind: "pdf" | "mail";
@@ -179,7 +179,7 @@ export abstract class NewsletterHelper {
     pdfRecipients.unshift({
       id: "0",
       firstname: "Alle Mitglieder",
-      lastname: CLUB_NAME,
+      lastname: SettingHelper.getSetting("club.name"),
       nameaffix: "",
       salutation: { salutation: "" },
     } as member);
@@ -221,11 +221,14 @@ export abstract class NewsletterHelper {
       const { body } = await TemplateHelper.renderFileForModule({
         module: "newsletter",
         bodyData: data,
-        title: `Newsletter von ${CLUB_NAME}`,
+        title: `Newsletter von ${SettingHelper.getSetting("club.name")}`,
       });
-      await MailHelper.sendMail(rec.sendNewsletter.email, `Newsletter von ${CLUB_NAME}`, body, [
-        { filename: "events.ics", path: this.getICSFilePath(newsletter) },
-      ])
+      await MailHelper.sendMail(
+        rec.sendNewsletter.email,
+        `Newsletter von ${SettingHelper.getSetting("club.name")}`,
+        body,
+        [{ filename: "events.ics", path: this.getICSFilePath(newsletter) }]
+      )
         .then(() => {
           this.formatJobEmit(
             "progress",
@@ -286,7 +289,7 @@ export abstract class NewsletterHelper {
 
       await PdfExport.renderFile({
         template: "newsletter",
-        title: `Newsletter von ${CLUB_NAME}`,
+        title: `Newsletter von ${SettingHelper.getSetting("club.name")}`,
         filename: `${rec.lastname}_${rec.firstname}_${rec.id}`.replaceAll(" ", "-"),
         folder: `newsletter/${newsletter.id}_${newsletter.title.replaceAll(" ", "")}`,
         data: data,
diff --git a/src/helpers/settingsHelper.ts b/src/helpers/settingsHelper.ts
index d176cc2..3652bce 100644
--- a/src/helpers/settingsHelper.ts
+++ b/src/helpers/settingsHelper.ts
@@ -1,57 +1,135 @@
+import { SettingString, settingsType } from "../type/settingTypes";
 import ms from "ms";
-import { EnvSettingString, envSettingsType, SettingString, settingsType } from "../type/settingTypes";
+import { CodingHelper } from "./codingHelper";
+import SettingCommandHandler from "../command/settingCommandHandler";
+import SettingService from "../service/settingService";
+import { APPLICATION_SECRET } from "../env.defaults";
 
-export abstract class SettingHelper {
+export default abstract class SettingHelper {
   private static settings: { [key in SettingString]?: string } = {};
-  private static envSettings: { [key in EnvSettingString]?: string } = {};
 
   public static getSetting(key: SettingString): string | number | boolean | ms.StringValue {
     let settingType = settingsType[key];
-    return this.settings[key] ?? settingType.default ?? "";
+    let setting = this.settings[key] ?? String(settingType.default ?? "");
+
+    if (Array.isArray(settingType.type)) {
+      return setting;
+    }
+
+    if (settingType.type.includes("/crypt")) {
+      setting = CodingHelper.decrypt(APPLICATION_SECRET, String(setting));
+    }
+
+    if (settingType.type.startsWith("string")) {
+      return setting;
+    }
+    if (settingType.type.startsWith("ms")) {
+      return setting as ms.StringValue;
+    }
+    if (settingType.type.startsWith("number")) {
+      return Number(setting);
+    }
+    if (settingType.type.startsWith("boolean")) {
+      return setting == "true";
+    }
+    return setting;
   }
 
-  public static getEnvSetting(key: EnvSettingString): string {
-    let settingType = envSettingsType[key];
-    return this.envSettings[key] ?? settingType.default ?? "";
+  public static async setSetting(key: SettingString, value: string) {
+    if (value == undefined || value == null) return;
+    let settingType = settingsType[key];
+
+    let result = value;
+
+    this.checkSettings(key, result);
+
+    if (!Array.isArray(settingType.type) && settingType.type.includes("/crypt")) {
+      result = CodingHelper.encrypt(APPLICATION_SECRET, value);
+    }
+
+    await SettingCommandHandler.create({
+      topic: key.split(".")[0],
+      key: key.split(".")[1],
+      value: result,
+    });
   }
 
-  public static async configure() {}
+  public static async resetSetting(key: SettingString) {
+    let settingType = settingsType[key];
+    this.settings[key] = String(settingType.default ?? "");
 
-  public static async configurEnv() {
-    this.envSettings = {
-      "database.type": process.env.DB_TYPE,
-      "database.host": process.env.DB_HOST,
-      "database.port": process.env.DB_PORT,
-      "database.name": process.env.DB_NAME,
-      "database.username": process.env.DB_USERNAME,
-      "database.password": process.env.DB_PASSWORD,
-    };
-    this.checkEnvSettings();
+    await SettingCommandHandler.delete({
+      topic: key.split(".")[0],
+      key: key.split(".")[1],
+    });
   }
 
-  private static checkEnvSettings() {
-    if (!["mysql", "sqlite", "postgres"].includes(this.envSettings["database.type"]))
-      throw new Error("set valid value to DB_TYPE (mysql|sqlite|postgres)");
-    if (this.checkIfEmptyOrNotString(this.envSettings["database.name"]))
-      throw new Error("set valid value to DB_NAME (name of database or filepath for sqlite)");
+  public static async configure() {
+    console.log("Configured Settings");
+    let settings = await SettingService.getSettings();
+
+    for (const element of settings) {
+      let ref = `${element.topic}.${element.key}` as SettingString;
+      this.settings[ref] = element.value;
+      this.checkSettings(ref);
+    }
+  }
+
+  private static checkSettings(key: SettingString, value?: string) {
+    let settingType = settingsType[key];
+
+    if (!value) {
+      value = this.getSetting(key).toString();
+    }
+
     if (
-      this.checkIfEmptyOrNotString(this.envSettings["database.host"]) &&
-      this.envSettings["database.type"] != "sqlite"
-    )
-      throw new Error("set valid value to DB_HOST");
+      !Array.isArray(settingType.type) &&
+      settingType.type.startsWith("string") &&
+      this.checkIfEmptyOrNotString(value)
+    ) {
+      throw new Error(`set valid value to ${key}`);
+    }
     if (
-      this.checkIfEmptyOrNotString(this.envSettings["database.username"]) &&
-      this.envSettings["database.type"] != "sqlite"
-    )
-      throw new Error("set valid value to DB_USERNAME");
+      !Array.isArray(settingType.type) &&
+      settingType.type.startsWith("ms") &&
+      this.checkIfNotMS(value as ms.StringValue)
+    ) {
+      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))) {
+      throw new Error(`set valid numeric value to ${key}`);
+    }
     if (
-      this.checkIfEmptyOrNotString(this.envSettings["database.password"]) &&
-      this.envSettings["database.type"] != "sqlite"
-    )
-      throw new Error("set valid value to DB_PASSWORD");
+      !Array.isArray(settingType.type) &&
+      settingType.type.startsWith("number") &&
+      settingType.min &&
+      Number(value) < settingType.min
+    ) {
+      throw new Error(`${key} has to be at least ${settingType.min}`);
+    }
+    if (
+      !Array.isArray(settingType.type) &&
+      settingType.type.startsWith("boolean") &&
+      value != "true" &&
+      value != "false"
+    ) {
+      throw new Error(`"set 'true' or 'false' to ${key}`);
+    }
   }
 
   private static checkIfEmptyOrNotString(val: any) {
     return typeof val != "string" || val == "";
   }
+
+  private static checkIfNotMS(input: ms.StringValue): boolean {
+    try {
+      const result = ms(input);
+      if (result === undefined) {
+        return true;
+      }
+    } catch (e) {
+      return true;
+    }
+    return false;
+  }
 }
diff --git a/src/index.ts b/src/index.ts
index 973f7bf..4aa928c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,9 +1,7 @@
 import "dotenv/config";
 import "./handlebars.config";
 import express from "express";
-import { SettingHelper } from "./helpers/settingsHelper";
 
-SettingHelper.configurEnv();
 import { configCheck } from "./env.defaults";
 configCheck();
 
@@ -23,13 +21,15 @@ declare global {
 
 import { dataSource } from "./data-source";
 import BackupHelper from "./helpers/backupHelper";
+import SettingHelper from "./helpers/settingsHelper";
 dataSource.initialize().then(async () => {
   if (await dataSource.createQueryRunner().hasTable("user")) {
     await BackupHelper.autoRestoreBackup().catch((err) => {
       console.log(`${new Date().toISOString()}: failed auto-restoring database`, err);
     });
   }
-  SettingHelper.configure();
+  await SettingHelper.configure();
+  MailHelper.createTransport();
 });
 
 const app = express();
@@ -43,6 +43,7 @@ app.listen(process.env.NODE_ENV ? process.env.SERVER_PORT ?? 5000 : 5000, () =>
 
 import schedule from "node-schedule";
 import RefreshCommandHandler from "./command/refreshCommandHandler";
+import MailHelper from "./helpers/mailHelper";
 const job = schedule.scheduleJob("0 0 * * *", async () => {
   console.log(`${new Date().toISOString()}: running Cron`);
   await RefreshCommandHandler.deleteExpired();
diff --git a/src/migrations/1738166124200-BackupAndResetDatabase.ts b/src/migrations/1738166124200-BackupAndResetDatabase.ts
index 30595da..f0656ba 100644
--- a/src/migrations/1738166124200-BackupAndResetDatabase.ts
+++ b/src/migrations/1738166124200-BackupAndResetDatabase.ts
@@ -2,16 +2,13 @@ import { MigrationInterface, QueryRunner, Table } from "typeorm";
 import BackupHelper from "../helpers/backupHelper";
 import { getDefaultByORM, getTypeByORM, isIncrementPrimary } from "./ormHelper";
 import InternalException from "../exceptions/internalException";
-import { SettingHelper } from "../helpers/settingsHelper";
+import { DB_TYPE } from "../env.defaults";
 
 export class BackupAndResetDatabase1738166124200 implements MigrationInterface {
   name = "BackupAndResetDatabase1738166124200";
 
   public async up(queryRunner: QueryRunner): Promise<void> {
-    let query =
-      SettingHelper.getEnvSetting("database.type") == "postgres"
-        ? "SELECT name FROM migrations"
-        : "SELECT `name` FROM `migrations`";
+    let query = DB_TYPE == "postgres" ? "SELECT name FROM migrations" : "SELECT `name` FROM `migrations`";
     let migrations = await queryRunner.query(query);
     if (
       (await queryRunner.hasTable("user")) &&
diff --git a/src/migrations/1738166167472-CreateSchema.ts b/src/migrations/1738166167472-CreateSchema.ts
index c9a4c8d..df9c6fd 100644
--- a/src/migrations/1738166167472-CreateSchema.ts
+++ b/src/migrations/1738166167472-CreateSchema.ts
@@ -54,7 +54,7 @@ import {
   newsletter_recipients_table,
   newsletter_table,
 } from "./baseSchemaTables/newsletter";
-import { SettingHelper } from "../helpers/settingsHelper";
+import { DB_TYPE } from "../env.defaults";
 
 export class CreateSchema1738166167472 implements MigrationInterface {
   name = "CreateSchema1738166167472";
@@ -84,7 +84,6 @@ export class CreateSchema1738166167472 implements MigrationInterface {
     await queryRunner.createTable(member_executive_positions_table, true, true, true);
     await queryRunner.createTable(member_qualifications_table, true, true, true);
 
-    const DB_TYPE = SettingHelper.getEnvSetting("database.type");
     if (DB_TYPE == "postgres") await queryRunner.createView(member_view_postgres, true);
     else if (DB_TYPE == "mysql") await queryRunner.createView(member_view_mysql, true);
     else if (DB_TYPE == "sqlite") await queryRunner.createView(member_view_sqlite, true);
diff --git a/src/migrations/1745059495808-settingsFromEnv.ts b/src/migrations/1745059495808-settingsFromEnv.ts
new file mode 100644
index 0000000..6cc8f56
--- /dev/null
+++ b/src/migrations/1745059495808-settingsFromEnv.ts
@@ -0,0 +1,30 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+import { setting_table } from "./baseSchemaTables/admin";
+import { envSettingsType } from "../type/settingTypes";
+import SettingHelper from "../helpers/settingsHelper";
+
+export class SettingsFromEnv1745059495808 implements MigrationInterface {
+  name = "SettingsFromEnv1745059495808";
+
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.createTable(setting_table, true, true, true);
+
+    //transfer settings of env to database
+    await SettingHelper.setSetting("club.name", process.env.CLUB_NAME);
+    await SettingHelper.setSetting("club.website", process.env.CLUB_WEBSITE);
+    await SettingHelper.setSetting("session.jwt_expiration", process.env.JWT_EXPIRATION);
+    await SettingHelper.setSetting("session.refresh_expiration", process.env.REFRESH_EXPIRATION);
+    await SettingHelper.setSetting("session.pwa_refresh_expiration", process.env.PWA_REFRESH_EXPIRATION);
+    await SettingHelper.setSetting("mail.username", process.env.MAIL_USERNAME);
+    await SettingHelper.setSetting("mail.password", process.env.MAIL_PASSWORD);
+    await SettingHelper.setSetting("mail.host", process.env.MAIL_HOST);
+    await SettingHelper.setSetting("mail.port", process.env.MAIL_PORT);
+    await SettingHelper.setSetting("mail.secure", process.env.MAIL_SECURE);
+    await SettingHelper.setSetting("backup.interval", process.env.BACKUP_INTERVAL);
+    await SettingHelper.setSetting("backup.copies", process.env.BACKUP_COPIES);
+  }
+
+  public async down(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.dropTable(setting_table.name, true, true, true);
+  }
+}
diff --git a/src/migrations/baseSchemaTables/admin.ts b/src/migrations/baseSchemaTables/admin.ts
index c3eb94b..6ece9ad 100644
--- a/src/migrations/baseSchemaTables/admin.ts
+++ b/src/migrations/baseSchemaTables/admin.ts
@@ -148,3 +148,12 @@ export const reset_table = new Table({
     { name: "secret", ...getTypeByORM("varchar") },
   ],
 });
+
+export const setting_table = new Table({
+  name: "setting",
+  columns: [
+    { name: "topic", ...getTypeByORM("varchar"), isPrimary: true },
+    { name: "key", ...getTypeByORM("varchar"), isPrimary: true },
+    { name: "value", ...getTypeByORM("text") },
+  ],
+});
diff --git a/src/migrations/ormHelper.ts b/src/migrations/ormHelper.ts
index 099e006..8ebe5c9 100644
--- a/src/migrations/ormHelper.ts
+++ b/src/migrations/ormHelper.ts
@@ -1,4 +1,4 @@
-import { SettingHelper } from "../helpers/settingsHelper";
+import { DB_TYPE } from "../env.defaults";
 
 export type ORMType = "int" | "bigint" | "boolean" | "date" | "datetime" | "time" | "text" | "varchar" | "uuid";
 export type ORMDefault = "currentTimestamp" | "string" | "boolean" | "number" | "null";
@@ -15,7 +15,7 @@ export type Primary = {
 };
 
 export function getTypeByORM(type: ORMType, nullable: boolean = false, length: number = 255): ColumnConfig {
-  const dbType = SettingHelper.getEnvSetting("database.type");
+  const dbType = DB_TYPE;
 
   const typeMap: Record<string, Record<ORMType, string>> = {
     mysql: {
@@ -65,7 +65,7 @@ export function getTypeByORM(type: ORMType, nullable: boolean = false, length: n
 }
 
 export function getDefaultByORM<T = string | null>(type: ORMDefault, data?: string | number | boolean): T {
-  const dbType = SettingHelper.getEnvSetting("database.type");
+  const dbType = DB_TYPE;
 
   const typeMap: Record<string, Record<ORMDefault, string | null>> = {
     mysql: {
diff --git a/src/service/club/member/memberService.ts b/src/service/club/member/memberService.ts
index eb791b3..cba1e47 100644
--- a/src/service/club/member/memberService.ts
+++ b/src/service/club/member/memberService.ts
@@ -1,11 +1,9 @@
 import { Brackets, Like, SelectQueryBuilder } from "typeorm";
 import { dataSource } from "../../../data-source";
 import { member } from "../../../entity/club/member/member";
-import { membership } from "../../../entity/club/member/membership";
 import DatabaseActionException from "../../../exceptions/databaseActionException";
-import InternalException from "../../../exceptions/internalException";
 import { memberView } from "../../../views/memberView";
-import { SettingHelper } from "../../../helpers/settingsHelper";
+import { DB_TYPE } from "../../../env.defaults";
 
 export default abstract class MemberService {
   /**
@@ -169,7 +167,7 @@ export default abstract class MemberService {
         "member.firstMembershipEntry",
         "member.memberships",
         "membership_first",
-        SettingHelper.getEnvSetting("database.type") == "postgres"
+        DB_TYPE == "postgres"
           ? 'membership_first.memberId = member.id AND membership_first.start = (SELECT MIN("m_first"."start") FROM "membership" "m_first" WHERE "m_first"."memberId" = "member"."id")'
           : "membership_first.memberId = member.id AND membership_first.start = (SELECT MIN(m_first.start) FROM membership m_first WHERE m_first.memberId = member.id)"
       )
@@ -177,7 +175,7 @@ export default abstract class MemberService {
         "member.lastMembershipEntry",
         "member.memberships",
         "membership_last",
-        SettingHelper.getEnvSetting("database.type") == "postgres"
+        DB_TYPE == "postgres"
           ? 'membership_last.memberId = member.id AND membership_last.start = (SELECT MAX("m_last"."start") FROM "membership" "m_last" WHERE "m_last"."memberId" = "member"."id")'
           : "membership_last.memberId = member.id AND membership_last.start = (SELECT MAX(m_last.start) FROM membership m_last WHERE m_last.memberId = member.id)"
       )
diff --git a/src/service/settingService.ts b/src/service/settingService.ts
new file mode 100644
index 0000000..d5f7701
--- /dev/null
+++ b/src/service/settingService.ts
@@ -0,0 +1,43 @@
+import { dataSource } from "../data-source";
+import { setting } from "../entity/setting";
+import InternalException from "../exceptions/internalException";
+import { SettingString } from "../type/settingTypes";
+
+export default abstract class SettingService {
+  /**
+   * @description get settings
+   * @returns {Promise<setting[]>}
+   */
+  static async getSettings(): Promise<setting[]> {
+    return await dataSource
+      .getRepository(setting)
+      .createQueryBuilder("setting")
+      .getMany()
+      .then((res) => {
+        return res;
+      })
+      .catch((err) => {
+        throw new InternalException("setting not found", err);
+      });
+  }
+
+  /**
+   * @description get setting
+   * @param token SettingString
+   * @returns {Promise<setting>}
+   */
+  static async getBySettingString(key: SettingString): Promise<setting> {
+    return await dataSource
+      .getRepository(setting)
+      .createQueryBuilder("setting")
+      .where("setting.topic = :topic", { topic: key.split(".")[0] })
+      .andWhere("setting.key >= :key", { key: key.split(".")[1] })
+      .getOneOrFail()
+      .then((res) => {
+        return res;
+      })
+      .catch((err) => {
+        throw new InternalException("setting not found", err);
+      });
+  }
+}
diff --git a/src/type/settingTypes.ts b/src/type/settingTypes.ts
index 6c7935c..f1ada64 100644
--- a/src/type/settingTypes.ts
+++ b/src/type/settingTypes.ts
@@ -10,7 +10,6 @@ export type SettingString =
   | "club.website"
   | "app.custom_login_message"
   | "app.show_link_to_calendar"
-  | "session.jwt_secret"
   | "session.jwt_expiration"
   | "session.refresh_expiration"
   | "session.pwa_refresh_expiration"
@@ -20,23 +19,17 @@ export type SettingString =
   | "mail.port"
   | "mail.secure"
   | "backup.interval"
-  | "backup.copies"
-  | "security.strict_limit"
-  | "security.strict_limit_window"
-  | "security.strict_limit_request_count"
-  | "security.limit"
-  | "security.limit_window"
-  | "security.limit_request_count"
-  | "security.trust_proxy";
+  | "backup.copies";
 
 export type SettingTypeAtom = "longstring" | "string" | "ms" | "number" | "boolean" | "url";
 export type SettingType = SettingTypeAtom | `${SettingTypeAtom}/crypt` | `${SettingTypeAtom}/rand`;
 
 export const settingsType: {
   [key in SettingString]: {
-    type: SettingType | SettingType[];
+    type: SettingType | SettingTypeAtom[];
     default?: string | number | boolean | ms.StringValue;
     optional?: boolean;
+    min?: number;
   };
 } = {
   "club.name": { type: "string", default: "FF Admin" },
@@ -45,7 +38,6 @@ export const settingsType: {
   "club.website": { type: "url", optional: true },
   "app.custom_login_message": { type: "string", optional: true },
   "app.show_link_to_calendar": { type: "boolean", default: true },
-  "session.jwt_secret": { type: "longstring/rand", default: StringHelper.random(64) },
   "session.jwt_expiration": { type: "ms", default: "15m" },
   "session.refresh_expiration": { type: "ms", default: "1d" },
   "session.pwa_refresh_expiration": { type: "ms", default: "5d" },
@@ -54,15 +46,8 @@ export const settingsType: {
   "mail.host": { type: "url", optional: false },
   "mail.port": { type: "number", default: 587 },
   "mail.secure": { type: "boolean", default: false },
-  "backup.interval": { type: "number", default: 1 },
-  "backup.copies": { type: "number", default: 7 },
-  "security.strict_limit": { type: "boolean", default: true },
-  "security.strict_limit_window": { type: "ms", default: "15m" },
-  "security.strict_limit_request_count": { type: "number", default: 15 },
-  "security.limit": { type: "boolean", default: true },
-  "security.limit_window": { type: "ms", default: "1m" },
-  "security.limit_request_count": { type: "number", default: 500 },
-  "security.trust_proxy": { type: ["boolean", "number"], optional: true },
+  "backup.interval": { type: "number", default: 1, min: 1 },
+  "backup.copies": { type: "number", default: 7, min: 1 },
 };
 
 /** ENV Settings */
@@ -72,12 +57,20 @@ export type EnvSettingString =
   | "database.port"
   | "database.name"
   | "database.username"
-  | "database.password";
+  | "database.password"
+  | "application.secret"
+  | "security.strict_limit"
+  | "security.strict_limit_window"
+  | "security.strict_limit_request_count"
+  | "security.limit"
+  | "security.limit_window"
+  | "security.limit_request_count"
+  | "security.trust_proxy";
 
 export const envSettingsType: {
   [key in EnvSettingString]: {
     type: SettingType | SettingType[];
-    default?: string;
+    default?: string | number | boolean;
   };
 } = {
   "database.type": { type: "string", default: "postgres" },
@@ -86,4 +79,12 @@ export const envSettingsType: {
   "database.name": { type: "string" },
   "database.username": { type: "string" },
   "database.password": { type: "string" },
+  "application.secret": { type: "string" },
+  "security.strict_limit": { type: "boolean", default: true },
+  "security.strict_limit_window": { type: "ms", default: "15m" },
+  "security.strict_limit_request_count": { type: "number", default: 15 },
+  "security.limit": { type: "boolean", default: true },
+  "security.limit_window": { type: "ms", default: "1m" },
+  "security.limit_request_count": { type: "number", default: 500 },
+  "security.trust_proxy": { type: ["boolean", "number", "string"] },
 };
diff --git a/src/views/memberExecutivePositionView.ts b/src/views/memberExecutivePositionView.ts
index fb85148..8b8f328 100644
--- a/src/views/memberExecutivePositionView.ts
+++ b/src/views/memberExecutivePositionView.ts
@@ -1,11 +1,10 @@
 import { DataSource, ViewColumn, ViewEntity } from "typeorm";
 import { memberExecutivePositions } from "../entity/club/member/memberExecutivePositions";
-import { SettingHelper } from "../helpers/settingsHelper";
+import { DB_TYPE } from "../env.defaults";
 
 let durationInDays: string;
 let durationInYears: string;
 let exactDuration: string;
-const DB_TYPE = SettingHelper.getEnvSetting("database.type");
 if (DB_TYPE == "postgres") {
   durationInDays = `SUM(COALESCE("memberExecutivePositions"."end", CURRENT_DATE) - "memberExecutivePositions"."start")`;
   durationInYears = `SUM(EXTRACT(YEAR FROM AGE(COALESCE("memberExecutivePositions"."end", CURRENT_DATE), "memberExecutivePositions"."start")))`;
diff --git a/src/views/memberQualificationsView.ts b/src/views/memberQualificationsView.ts
index 0d7c05b..6f88f39 100644
--- a/src/views/memberQualificationsView.ts
+++ b/src/views/memberQualificationsView.ts
@@ -1,11 +1,10 @@
 import { DataSource, ViewColumn, ViewEntity } from "typeorm";
 import { memberQualifications } from "../entity/club/member/memberQualifications";
-import { SettingHelper } from "../helpers/settingsHelper";
+import { DB_TYPE } from "../env.defaults";
 
 let durationInDays: string;
 let durationInYears: string;
 let exactDuration: string;
-const DB_TYPE = SettingHelper.getEnvSetting("database.type");
 if (DB_TYPE == "postgres") {
   durationInDays = `SUM(COALESCE("memberQualifications"."end", CURRENT_DATE) - "memberQualifications"."start") `;
   durationInYears = `SUM(EXTRACT(YEAR FROM AGE(COALESCE("memberQualifications"."end", CURRENT_DATE), "memberQualifications"."start")))`;
diff --git a/src/views/memberView.ts b/src/views/memberView.ts
index 748d07e..194bade 100644
--- a/src/views/memberView.ts
+++ b/src/views/memberView.ts
@@ -1,11 +1,10 @@
 import { DataSource, ViewColumn, ViewEntity } from "typeorm";
 import { member } from "../entity/club/member/member";
-import { SettingHelper } from "../helpers/settingsHelper";
+import { DB_TYPE } from "../env.defaults";
 
 let todayAge: string;
 let ageThisYear: string;
 let exactAge: string;
-const DB_TYPE = SettingHelper.getEnvSetting("database.type");
 if (DB_TYPE == "postgres") {
   todayAge = `DATE_PART('year', AGE(CURRENT_DATE, member.birthdate))`;
   ageThisYear = `EXTRACT(YEAR FROM CURRENT_DATE) - EXTRACT(YEAR FROM member.birthdate)`;
diff --git a/src/views/membershipsView.ts b/src/views/membershipsView.ts
index 4e21191..fc38c23 100644
--- a/src/views/membershipsView.ts
+++ b/src/views/membershipsView.ts
@@ -1,11 +1,10 @@
 import { DataSource, ViewColumn, ViewEntity } from "typeorm";
 import { membership } from "../entity/club/member/membership";
-import { SettingHelper } from "../helpers/settingsHelper";
+import { DB_TYPE } from "../env.defaults";
 
 let durationInDays: string;
 let durationInYears: string;
 let exactDuration: string;
-const DB_TYPE = SettingHelper.getEnvSetting("database.type");
 if (DB_TYPE == "postgres") {
   durationInDays = `SUM(COALESCE("membership"."end", CURRENT_DATE) - "membership"."start") `;
   durationInYears = `SUM(EXTRACT(YEAR FROM AGE(COALESCE("membership"."end", CURRENT_DATE), "membership"."start")))`;