Compare commits
No commits in common. "main" and "v1.3.9" have entirely different histories.
89 changed files with 2245 additions and 4678 deletions
24
.env.example
24
.env.example
|
@ -1,4 +1,4 @@
|
||||||
DB_TYPE = (mysql|postgres) # default ist mysql
|
DB_TYPE = (mysql|sqlite|postgres) # default ist mysql
|
||||||
|
|
||||||
## BSP für mysql
|
## BSP für mysql
|
||||||
DB_PORT = 3306
|
DB_PORT = 3306
|
||||||
|
@ -14,10 +14,28 @@ DB_NAME = database_name
|
||||||
DB_USERNAME = database_username
|
DB_USERNAME = database_username
|
||||||
DB_PASSWORD = database_password
|
DB_PASSWORD = database_password
|
||||||
|
|
||||||
## Dev only
|
## BSP für sqlite
|
||||||
|
DB_HOST = filename.db
|
||||||
|
|
||||||
SERVER_PORT = portnumber
|
SERVER_PORT = portnumber
|
||||||
|
|
||||||
APPLICATION_SECRET = mysecret
|
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
|
||||||
|
|
||||||
USE_SECURITY_STRICT_LIMIT = (true|false) # default ist true
|
USE_SECURITY_STRICT_LIMIT = (true|false) # default ist true
|
||||||
SECURITY_STRICT_LIMIT_WINDOW = [0-9]*(y|d|h|m|s) # default ist 15m
|
SECURITY_STRICT_LIMIT_WINDOW = [0-9]*(y|d|h|m|s) # default ist 15m
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:22-alpine AS build
|
FROM node:18-alpine AS build
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
chromium \
|
chromium \
|
||||||
|
@ -20,7 +20,7 @@ COPY . /app
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM node:22-alpine AS prod
|
FROM node:18-alpine AS prod
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
chromium \
|
chromium \
|
||||||
|
@ -37,7 +37,6 @@ RUN mkdir -p /app/files
|
||||||
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
||||||
|
|
||||||
COPY --from=build /app/src/templates /app/src/templates
|
COPY --from=build /app/src/templates /app/src/templates
|
||||||
COPY --from=build /app/src/assets /app/src/assets
|
|
||||||
COPY --from=build /app/dist /app/dist
|
COPY --from=build /app/dist /app/dist
|
||||||
COPY --from=build /app/node_modules /app/node_modules
|
COPY --from=build /app/node_modules /app/node_modules
|
||||||
COPY --from=build /app/package.json /app/package.json
|
COPY --from=build /app/package.json /app/package.json
|
||||||
|
|
21
README.md
21
README.md
|
@ -8,8 +8,6 @@ Dieses Projekt, `ff-admin-server`, ist das Backend zur Verwaltung von Mitglieder
|
||||||
|
|
||||||
Eine Demo zusammen mit der `ff-admin` finden Sie unter [https://admin-demo.ff-admin.de](https://admin-demo.ff-admin.de).
|
Eine Demo zusammen mit der `ff-admin` finden Sie unter [https://admin-demo.ff-admin.de](https://admin-demo.ff-admin.de).
|
||||||
|
|
||||||
Das Handbuch zur Anwendung finden sie unter [https://ff-admin.de/ff-admin-handbook](https://ff-admin.de/ff-admin-handbook).
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Das Image exposed nur den Port 5000. Die Env-Variable SERVER_PORT kann nur im lokal ausführenden dev-Kontext verwendet werden.
|
Das Image exposed nur den Port 5000. Die Env-Variable SERVER_PORT kann nur im lokal ausführenden dev-Kontext verwendet werden.
|
||||||
|
@ -27,13 +25,26 @@ services:
|
||||||
container_name: ff_member_administration_server
|
container_name: ff_member_administration_server
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- DB_TYPE=<mysql|postgres> # default ist auf mysql gesetzt
|
- DB_TYPE=<mysql|sqlite|postgres> # default ist auf mysql gesetzt
|
||||||
- DB_HOST=ff-db
|
- DB_HOST=ff-db
|
||||||
- DB_PORT=<number> # default ist auf 3306 gesetzt
|
- DB_PORT=<number> # default ist auf 3306 gesetzt
|
||||||
- DB_NAME=ffadmin
|
- DB_NAME=ffadmin
|
||||||
- DB_USERNAME=administration_backend
|
- DB_USERNAME=administration_backend
|
||||||
- DB_PASSWORD=<dbuserpasswd>
|
- DB_PASSWORD=<dbuserpasswd>
|
||||||
- APPLICATION_SECRET=<tobemodified>
|
- JWT_SECRET=<tobemodified>
|
||||||
|
- JWT_EXPIRATION=<number[m|d] - bsp.:15m> # default ist auf 15m gesetzt
|
||||||
|
- REFRESH_EXPIRATION=<number[m|d] - bsp.:1d> # default ist auf 1d gesetzt
|
||||||
|
- PWA_REFRESH_EXPIRATION=<number[m|d] - bsp.:5d> # default ist auf 5d gesetzt
|
||||||
|
- MAIL_USERNAME=<mailadress|username>
|
||||||
|
- MAIL_PASSWORD=<password>
|
||||||
|
- MAIL_HOST=<url>
|
||||||
|
- MAIL_PORT=<port> # default ist auf 587 gesetzt
|
||||||
|
- MAIL_SECURE=<boolean> # default ist auf false gesetzt
|
||||||
|
- CLUB_NAME=<tobemodified> # default ist auf FF Admin gesetzt
|
||||||
|
- CLUB_WEBSITE=<tobemodified>
|
||||||
|
- BACKUP_INTERVAL=<number of days (min. 1)> # alle x Tage, sonst keine
|
||||||
|
- BACKUP_COPIES=<number of parallel copies> # Anzahl parallel bestehender Backups
|
||||||
|
- BACKUP_AUTO_RESTORE=<boolean> # default ist auf true gesetzt
|
||||||
- USE_SECURITY_STRICT_LIMIT = (true|false) # default ist true
|
- USE_SECURITY_STRICT_LIMIT = (true|false) # default ist true
|
||||||
- SECURITY_STRICT_LIMIT_WINDOW = [0-9]*(y|d|h|m|s) # default ist 15
|
- SECURITY_STRICT_LIMIT_WINDOW = [0-9]*(y|d|h|m|s) # default ist 15
|
||||||
- SECURITY_STRICT_LIMIT_REQUEST_COUNT = strict_request_count # default ist 15
|
- SECURITY_STRICT_LIMIT_REQUEST_COUNT = strict_request_count # default ist 15
|
||||||
|
@ -80,6 +91,8 @@ networks:
|
||||||
|
|
||||||
Die Verwendung von postgres wird aufgrund des Verhaltens bei Datenbank-Update-Fehlern empfohlen.
|
Die Verwendung von postgres wird aufgrund des Verhaltens bei Datenbank-Update-Fehlern empfohlen.
|
||||||
|
|
||||||
|
Die Verwendung von SQLite wird nur für die Entwicklung oder lokale Tests empfohlen.
|
||||||
|
|
||||||
Führen Sie dann den folgenden Befehl im Verzeichnis der compose-Datei aus, um den Container zu starten:
|
Führen Sie dann den folgenden Befehl im Verzeichnis der compose-Datei aus, um den Container zu starten:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|
3915
package-lock.json
generated
3915
package-lock.json
generated
File diff suppressed because it is too large
Load diff
53
package.json
53
package.json
|
@ -1,13 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "ff-admin-server",
|
"name": "ff-admin-server",
|
||||||
"version": "1.5.1",
|
"version": "1.3.9",
|
||||||
"description": "Feuerwehr/Verein Mitgliederverwaltung Server",
|
"description": "Feuerwehr/Verein Mitgliederverwaltung Server",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start_ts": "ts-node src/index.ts",
|
"start_ts": "ts-node src/index.ts",
|
||||||
"typeorm": "typeorm-ts-node-commonjs",
|
"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": "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",
|
"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",
|
"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",
|
"revert-database": "set DBMODE=update-database && npx typeorm-ts-node-commonjs migration:revert -d ./src/data-source.ts",
|
||||||
|
@ -26,60 +25,52 @@
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"crypto": "^1.0.1",
|
"dotenv": "^16.4.5",
|
||||||
"dotenv": "^16.5.0",
|
"express": "^5.0.0-beta.3",
|
||||||
"express": "^5.1.0",
|
|
||||||
"express-rate-limit": "^7.5.0",
|
"express-rate-limit": "^7.5.0",
|
||||||
"express-validator": "^7.2.1",
|
"express-validator": "^7.2.1",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"helmet": "^8.1.0",
|
"helmet": "^8.0.0",
|
||||||
"ics": "^3.8.1",
|
"ics": "^3.8.1",
|
||||||
"ip": "^2.0.1",
|
"ip": "^2.0.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
|
||||||
"lodash.uniqby": "^4.7.0",
|
"lodash.uniqby": "^4.7.0",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
"multer": "^1.4.5-lts.2",
|
"multer": "^1.4.5-lts.1",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"node-schedule": "^2.1.1",
|
"node-schedule": "^2.1.1",
|
||||||
"nodemailer": "^7.0.2",
|
"nodemailer": "^6.9.14",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pg": "^8.15.6",
|
"pg": "^8.13.1",
|
||||||
"puppeteer": "^24.8.0",
|
"puppeteer": "^23.11.1",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rss-parser": "^3.13.0",
|
"rss-parser": "^3.13.0",
|
||||||
"sharp": "^0.34.1",
|
"socket.io": "^4.7.5",
|
||||||
"sharp-ico": "^0.1.5",
|
|
||||||
"socket.io": "^4.8.1",
|
|
||||||
"speakeasy": "^2.0.0",
|
"speakeasy": "^2.0.0",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"typeorm": "^0.3.22",
|
"typeorm": "^0.3.20",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^10.0.0"
|
||||||
"validator": "^13.15.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.14",
|
||||||
"@types/express": "^5.0.1",
|
"@types/express": "^4.17.17",
|
||||||
"@types/ip": "^1.1.3",
|
"@types/ip": "^1.1.3",
|
||||||
"@types/jsonwebtoken": "^9.0.9",
|
"@types/jsonwebtoken": "^9.0.6",
|
||||||
"@types/lodash.clonedeep": "^4.5.9",
|
|
||||||
"@types/lodash.uniqby": "^4.7.9",
|
"@types/lodash.uniqby": "^4.7.9",
|
||||||
"@types/morgan": "^1.9.9",
|
"@types/morgan": "^1.9.9",
|
||||||
"@types/ms": "^2.1.0",
|
"@types/ms": "^0.7.34",
|
||||||
"@types/multer": "^1.4.12",
|
"@types/multer": "^1.4.12",
|
||||||
"@types/mysql": "^2.15.27",
|
"@types/mysql": "^2.15.21",
|
||||||
"@types/node": "^22.15.12",
|
"@types/node": "^16.18.41",
|
||||||
"@types/node-schedule": "^2.1.7",
|
"@types/node-schedule": "^2.1.6",
|
||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.14",
|
||||||
"@types/pg": "~8.12.0",
|
|
||||||
"@types/qrcode": "~1.5.5",
|
"@types/qrcode": "~1.5.5",
|
||||||
"@types/speakeasy": "^2.0.10",
|
"@types/speakeasy": "^2.0.10",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^9.0.2",
|
||||||
"@types/validator": "^13.15.0",
|
"ts-node": "10.7.0",
|
||||||
"ts-node": "10.9.2",
|
"typescript": "^4.5.2"
|
||||||
"typescript": "^5.8.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 34 KiB |
Binary file not shown.
Before Width: | Height: | Size: 29 KiB |
|
@ -18,17 +18,6 @@ export default abstract class CommunicationCommandHandler {
|
||||||
let insertId = -1;
|
let insertId = -1;
|
||||||
return await dataSource
|
return await dataSource
|
||||||
.transaction(async (manager) => {
|
.transaction(async (manager) => {
|
||||||
if (createCommunication.isSendNewsletter) {
|
|
||||||
await manager
|
|
||||||
.createQueryBuilder()
|
|
||||||
.update(communication)
|
|
||||||
.set({
|
|
||||||
isSendNewsletter: false,
|
|
||||||
})
|
|
||||||
.where("memberId = :memberId", { memberId: createCommunication.memberId })
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
await manager
|
await manager
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.insert()
|
.insert()
|
||||||
|
@ -51,6 +40,16 @@ export default abstract class CommunicationCommandHandler {
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
insertId = result.identifiers[0].id;
|
insertId = result.identifiers[0].id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await manager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.update(communication)
|
||||||
|
.set({
|
||||||
|
isSendNewsletter: false,
|
||||||
|
})
|
||||||
|
.where("memberId = :memberId", { memberId: createCommunication.memberId })
|
||||||
|
.andWhere("id <> :id", { id: insertId })
|
||||||
|
.execute();
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return insertId;
|
return insertId;
|
||||||
|
@ -68,17 +67,6 @@ export default abstract class CommunicationCommandHandler {
|
||||||
static async update(updateCommunication: UpdateCommunicationCommand): Promise<void> {
|
static async update(updateCommunication: UpdateCommunicationCommand): Promise<void> {
|
||||||
return await dataSource
|
return await dataSource
|
||||||
.transaction(async (manager) => {
|
.transaction(async (manager) => {
|
||||||
if (updateCommunication.isSendNewsletter) {
|
|
||||||
await manager
|
|
||||||
.createQueryBuilder()
|
|
||||||
.update(communication)
|
|
||||||
.set({
|
|
||||||
isSendNewsletter: false,
|
|
||||||
})
|
|
||||||
.where("memberId = :memberId", { memberId: updateCommunication.memberId })
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
await manager
|
await manager
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.update(communication)
|
.update(communication)
|
||||||
|
@ -97,6 +85,16 @@ export default abstract class CommunicationCommandHandler {
|
||||||
.where("id = :id", { id: updateCommunication.id })
|
.where("id = :id", { id: updateCommunication.id })
|
||||||
.andWhere("memberId = :memberId", { memberId: updateCommunication.memberId })
|
.andWhere("memberId = :memberId", { memberId: updateCommunication.memberId })
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
|
await manager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.update(communication)
|
||||||
|
.set({
|
||||||
|
isSendNewsletter: false,
|
||||||
|
})
|
||||||
|
.where("memberId = :memberId", { memberId: updateCommunication.memberId })
|
||||||
|
.andWhere("id <> :id", { id: updateCommunication.id })
|
||||||
|
.execute();
|
||||||
})
|
})
|
||||||
.then(() => {})
|
.then(() => {})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { NewsletterConfigEnum } from "../../../enums/newsletterConfigEnum";
|
import { NewsletterConfigType } from "../../../enums/newsletterConfigType";
|
||||||
|
|
||||||
export interface SetNewsletterConfigCommand {
|
export interface SetNewsletterConfigCommand {
|
||||||
comTypeId: number;
|
comTypeId: number;
|
||||||
config: NewsletterConfigEnum;
|
config: NewsletterConfigType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeleteNewsletterConfigCommand {
|
export interface DeleteNewsletterConfigCommand {
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
export interface CreateOrUpdateSettingCommand {
|
|
||||||
topic: string;
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DeleteSettingCommand {
|
|
||||||
topic: string;
|
|
||||||
key: string;
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
import { dataSource } from "../../../data-source";
|
|
||||||
import { setting } from "../../../entity/management/setting";
|
|
||||||
import DatabaseActionException from "../../../exceptions/databaseActionException";
|
|
||||||
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> {
|
|
||||||
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 createSetting.value;
|
|
||||||
})
|
|
||||||
.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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { LoginRoutineEnum } from "../../../enums/loginRoutineEnum";
|
|
||||||
|
|
||||||
export interface CreateUserCommand {
|
export interface CreateUserCommand {
|
||||||
mail: string;
|
mail: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
@ -7,7 +5,6 @@ export interface CreateUserCommand {
|
||||||
lastname: string;
|
lastname: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
isOwner: boolean;
|
isOwner: boolean;
|
||||||
routine: LoginRoutineEnum;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateUserCommand {
|
export interface UpdateUserCommand {
|
||||||
|
@ -21,7 +18,6 @@ export interface UpdateUserCommand {
|
||||||
export interface UpdateUserSecretCommand {
|
export interface UpdateUserSecretCommand {
|
||||||
id: string;
|
id: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
routine: LoginRoutineEnum;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransferUserOwnerCommand {
|
export interface TransferUserOwnerCommand {
|
||||||
|
|
|
@ -31,7 +31,6 @@ export default abstract class UserCommandHandler {
|
||||||
lastname: createUser.lastname,
|
lastname: createUser.lastname,
|
||||||
secret: createUser.secret,
|
secret: createUser.secret,
|
||||||
isOwner: createUser.isOwner,
|
isOwner: createUser.isOwner,
|
||||||
routine: createUser.routine,
|
|
||||||
})
|
})
|
||||||
.execute()
|
.execute()
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
@ -76,7 +75,6 @@ export default abstract class UserCommandHandler {
|
||||||
.update(user)
|
.update(user)
|
||||||
.set({
|
.set({
|
||||||
secret: updateUser.secret,
|
secret: updateUser.secret,
|
||||||
routine: updateUser.routine,
|
|
||||||
})
|
})
|
||||||
.where("id = :id", { id: updateUser.id })
|
.where("id = :id", { id: updateUser.id })
|
||||||
.execute()
|
.execute()
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { dataSource } from "../data-source";
|
import { dataSource } from "../data-source";
|
||||||
import { refresh } from "../entity/refresh";
|
import { refresh } from "../entity/refresh";
|
||||||
|
import { PWA_REFRESH_EXPIRATION, REFRESH_EXPIRATION } from "../env.defaults";
|
||||||
import DatabaseActionException from "../exceptions/databaseActionException";
|
import DatabaseActionException from "../exceptions/databaseActionException";
|
||||||
import SettingHelper from "../helpers/settingsHelper";
|
import InternalException from "../exceptions/internalException";
|
||||||
import { StringHelper } from "../helpers/stringHelper";
|
import { StringHelper } from "../helpers/stringHelper";
|
||||||
|
import UserService from "../service/management/userService";
|
||||||
import { CreateRefreshCommand, DeleteRefreshCommand } from "./refreshCommand";
|
import { CreateRefreshCommand, DeleteRefreshCommand } from "./refreshCommand";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
|
@ -23,8 +25,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")))
|
? new Date(Date.now() + ms(PWA_REFRESH_EXPIRATION))
|
||||||
: new Date(Date.now() + ms(SettingHelper.getSetting("session.refresh_expiration"))),
|
: new Date(Date.now() + ms(REFRESH_EXPIRATION)),
|
||||||
})
|
})
|
||||||
.execute()
|
.execute()
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
|
|
@ -92,18 +92,6 @@ export async function getMembersByIds(req: Request, res: Response): Promise<any>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description get member latest inserted InternalId
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function getMemberLastInternalId(req: Request, res: Response): Promise<any> {
|
|
||||||
let latest = await MemberService.getLatestInternalId();
|
|
||||||
|
|
||||||
res.send(latest);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description get member by id
|
* @description get member by id
|
||||||
* @param req {Request} Express req object
|
* @param req {Request} Express req object
|
||||||
|
|
|
@ -94,7 +94,7 @@ export async function getNewsletterPrintoutsById(req: Request, res: Response): P
|
||||||
let newsletter = await NewsletterService.getById(newsletterId);
|
let newsletter = await NewsletterService.getById(newsletterId);
|
||||||
|
|
||||||
let filesInFolder = FileSystemHelper.getFilesInDirectory(
|
let filesInFolder = FileSystemHelper.getFilesInDirectory(
|
||||||
`newsletter/${newsletter.id}_${newsletter.title.replaceAll(" ", "")}`
|
`newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`
|
||||||
);
|
);
|
||||||
|
|
||||||
res.json(filesInFolder);
|
res.json(filesInFolder);
|
||||||
|
@ -114,7 +114,7 @@ export async function getNewsletterPrintoutByIdAndPrint(req: Request, res: Respo
|
||||||
|
|
||||||
let filepath = FileSystemHelper.formatPath(
|
let filepath = FileSystemHelper.formatPath(
|
||||||
"newsletter",
|
"newsletter",
|
||||||
`${newsletter.id}_${newsletter.title.replaceAll(" ", "")}`,
|
`${newsletter.id}_${newsletter.title.replace(" ", "")}`,
|
||||||
filename
|
filename
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -164,34 +164,6 @@ export async function createNewsletterPrintoutPreviewById(req: Request, res: Res
|
||||||
res.send(pdfbuffer);
|
res.send(pdfbuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description get all members receiving a newsletter printout by id
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function getNewsletterPrintReceiversById(req: Request, res: Response): Promise<any> {
|
|
||||||
let newsletterId = parseInt(req.params.newsletterId);
|
|
||||||
|
|
||||||
let recipients = await NewsletterHelper.getPrintRecipients(newsletterId);
|
|
||||||
|
|
||||||
res.json(recipients);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description get all members receiving a newsletter mail by id
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function getNewsletterMailReceiversById(req: Request, res: Response): Promise<any> {
|
|
||||||
let newsletterId = parseInt(req.params.newsletterId);
|
|
||||||
|
|
||||||
let recipients = await NewsletterHelper.getMailRecipients(newsletterId);
|
|
||||||
|
|
||||||
res.json(recipients);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description create newsletter
|
* @description create newsletter
|
||||||
* @param req {Request} Express req object
|
* @param req {Request} Express req object
|
||||||
|
|
|
@ -235,7 +235,7 @@ export async function createProtocolPrintoutById(req: Request, res: Response): P
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
})}`;
|
})}`;
|
||||||
|
|
||||||
let filename = `${new Date().toISOString().split("T")[0]}_${iteration + 1}_Protokoll_${protocol.title.replaceAll(
|
let filename = `${new Date().toISOString().split("T")[0]}_${iteration + 1}_Protokoll_${protocol.title.replace(
|
||||||
/[^a-zA-Z0-9]/g,
|
/[^a-zA-Z0-9]/g,
|
||||||
""
|
""
|
||||||
)}`;
|
)}`;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import DynamicQueryBuilder from "../../../helpers/dynamicQueryBuilder";
|
import DynamicQueryBuilder from "../../../helpers/dynamicQueryBuilder";
|
||||||
import { dataSource } from "../../../data-source";
|
import { dataSource } from "../../../data-source";
|
||||||
import QueryStoreService from "../../../service/configuration/queryStoreService";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description get all table metas
|
* @description get all table metas
|
||||||
|
@ -44,23 +43,3 @@ export async function executeQuery(req: Request, res: Response): Promise<any> {
|
||||||
|
|
||||||
res.json(result);
|
res.json(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description execute Query by StoreId
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function executeQueryByStoreId(req: Request, res: Response): Promise<any> {
|
|
||||||
let offset = parseInt((req.query.offset as string) ?? "0");
|
|
||||||
let count = parseInt((req.query.count as string) ?? "25");
|
|
||||||
let noLimit = req.query.noLimit === "true";
|
|
||||||
const storeId = req.params.storeId;
|
|
||||||
|
|
||||||
let queryStore = await QueryStoreService.getById(storeId);
|
|
||||||
let query = queryStore.query.startsWith("{") ? JSON.parse(queryStore.query) : queryStore.query;
|
|
||||||
|
|
||||||
let result = await DynamicQueryBuilder.executeQuery({ query, offset, count, noLimit });
|
|
||||||
|
|
||||||
res.json(result);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
import { Request, Response } from "express";
|
|
||||||
import SettingHelper from "../../../helpers/settingsHelper";
|
|
||||||
import { SettingString, SettingValueMapping } from "../../../type/settingTypes";
|
|
||||||
import MailHelper from "../../../helpers/mailHelper";
|
|
||||||
import InternalException from "../../../exceptions/internalException";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description get All settings
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function getSettings(req: Request, res: Response): Promise<any> {
|
|
||||||
res.json({ ...SettingHelper.getAllSettings(), ["mail.password"]: undefined });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description get setting
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function getSetting(req: Request, res: Response): Promise<any> {
|
|
||||||
let setting = req.params.setting as SettingString;
|
|
||||||
|
|
||||||
let value = SettingHelper.getSetting(setting);
|
|
||||||
|
|
||||||
if (setting == "mail.password") {
|
|
||||||
value = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.send(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description set setting
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function setSetting(req: Request, res: Response): Promise<any> {
|
|
||||||
let setting = req.body.setting as SettingString;
|
|
||||||
let value = req.body.value as string;
|
|
||||||
|
|
||||||
await SettingHelper.checkMail([{ key: setting, value }]).catch((err) => {
|
|
||||||
if (err == "mail") {
|
|
||||||
throw new InternalException("Mail is not valid");
|
|
||||||
} else {
|
|
||||||
throw new InternalException("Config is not valid");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await SettingHelper.setSetting(setting, value);
|
|
||||||
|
|
||||||
res.sendStatus(204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description set settings
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function setSettings<K extends SettingString>(req: Request, res: Response): Promise<any> {
|
|
||||||
let setting = req.body as Array<{ key: K; value: SettingValueMapping[K] }>;
|
|
||||||
|
|
||||||
await SettingHelper.checkMail(setting).catch((err) => {
|
|
||||||
if (err == "mail") {
|
|
||||||
throw new InternalException("Mail is not valid");
|
|
||||||
} else {
|
|
||||||
throw new InternalException("Config is not valid");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let entry of setting) {
|
|
||||||
await SettingHelper.setSetting(entry.key, entry.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.sendStatus(204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description set setting
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function setImages(req: Request, res: Response): Promise<any> {
|
|
||||||
if (req.files && !Array.isArray(req.files) && req.files.icon) {
|
|
||||||
await SettingHelper.setSetting("club.icon", "configured");
|
|
||||||
} else if (req.body["club.icon"] != "keep") {
|
|
||||||
await SettingHelper.resetSetting("club.icon");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.files && !Array.isArray(req.files) && req.files.logo) {
|
|
||||||
await SettingHelper.setSetting("club.logo", "configured");
|
|
||||||
} else if (req.body["club.logo"] != "keep") {
|
|
||||||
await SettingHelper.resetSetting("club.logo");
|
|
||||||
}
|
|
||||||
|
|
||||||
res.sendStatus(204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description reset setting
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function resetSetting(req: Request, res: Response): Promise<any> {
|
|
||||||
let setting = req.params.setting as SettingString;
|
|
||||||
|
|
||||||
await SettingHelper.resetSetting(setting);
|
|
||||||
|
|
||||||
res.sendStatus(204);
|
|
||||||
}
|
|
|
@ -11,10 +11,10 @@ import {
|
||||||
} from "../../../command/management/user/userCommand";
|
} from "../../../command/management/user/userCommand";
|
||||||
import UserCommandHandler from "../../../command/management/user/userCommandHandler";
|
import UserCommandHandler from "../../../command/management/user/userCommandHandler";
|
||||||
import MailHelper from "../../../helpers/mailHelper";
|
import MailHelper from "../../../helpers/mailHelper";
|
||||||
|
import { CLUB_NAME } from "../../../env.defaults";
|
||||||
import { UpdateUserPermissionsCommand } from "../../../command/management/user/userPermissionCommand";
|
import { UpdateUserPermissionsCommand } from "../../../command/management/user/userPermissionCommand";
|
||||||
import UserPermissionCommandHandler from "../../../command/management/user/userPermissionCommandHandler";
|
import UserPermissionCommandHandler from "../../../command/management/user/userPermissionCommandHandler";
|
||||||
import BadRequestException from "../../../exceptions/badRequestException";
|
import BadRequestException from "../../../exceptions/badRequestException";
|
||||||
import SettingHelper from "../../../helpers/settingsHelper";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description get All users
|
* @description get All users
|
||||||
|
@ -157,7 +157,7 @@ export async function deleteUser(req: Request, res: Response): Promise<any> {
|
||||||
// sendmail
|
// sendmail
|
||||||
await MailHelper.sendMail(
|
await MailHelper.sendMail(
|
||||||
mail,
|
mail,
|
||||||
`Email Bestätigung für Mitglieder Admin-Portal von ${SettingHelper.getSetting("club.name")}`,
|
`Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`,
|
||||||
`Ihr Nutzerkonto des Adminportals wurde erfolgreich gelöscht.`
|
`Ihr Nutzerkonto des Adminportals wurde erfolgreich gelöscht.`
|
||||||
);
|
);
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
|
|
|
@ -12,8 +12,8 @@ import WebapiCommandHandler from "../../../command/management/webapi/webapiComma
|
||||||
import { UpdateWebapiPermissionsCommand } from "../../../command/management/webapi/webapiPermissionCommand";
|
import { UpdateWebapiPermissionsCommand } from "../../../command/management/webapi/webapiPermissionCommand";
|
||||||
import WebapiPermissionCommandHandler from "../../../command/management/webapi/webapiPermissionCommandHandler";
|
import WebapiPermissionCommandHandler from "../../../command/management/webapi/webapiPermissionCommandHandler";
|
||||||
import { JWTHelper } from "../../../helpers/jwtHelper";
|
import { JWTHelper } from "../../../helpers/jwtHelper";
|
||||||
|
import { CLUB_NAME } from "../../../env.defaults";
|
||||||
import { StringHelper } from "../../../helpers/stringHelper";
|
import { StringHelper } from "../../../helpers/stringHelper";
|
||||||
import SettingHelper from "../../../helpers/settingsHelper";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description get All apis
|
* @description get All apis
|
||||||
|
@ -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"),
|
iss: CLUB_NAME,
|
||||||
sub: "api_token_retrieve",
|
sub: "api_token_retrieve",
|
||||||
aud: StringHelper.random(32),
|
aud: StringHelper.random(32),
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,25 +8,6 @@ import UserService from "../service/management/userService";
|
||||||
import speakeasy from "speakeasy";
|
import speakeasy from "speakeasy";
|
||||||
import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException";
|
import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException";
|
||||||
import RefreshService from "../service/refreshService";
|
import RefreshService from "../service/refreshService";
|
||||||
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Check authentication status by token
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function kickof(req: Request, res: Response): Promise<any> {
|
|
||||||
let username = req.body.username;
|
|
||||||
|
|
||||||
let { routine } = await UserService.getByUsername(username).catch(() => {
|
|
||||||
throw new UnauthorizedRequestException("Username not found");
|
|
||||||
});
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
routine,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Check authentication status by token
|
* @description Check authentication status by token
|
||||||
|
@ -36,25 +17,19 @@ export async function kickof(req: Request, res: Response): Promise<any> {
|
||||||
*/
|
*/
|
||||||
export async function login(req: Request, res: Response): Promise<any> {
|
export async function login(req: Request, res: Response): Promise<any> {
|
||||||
let username = req.body.username;
|
let username = req.body.username;
|
||||||
let passedSecret = req.body.secret;
|
let totp = req.body.totp;
|
||||||
|
|
||||||
let { id } = await UserService.getByUsername(username);
|
let { id, secret } = await UserService.getByUsername(username);
|
||||||
let { secret, routine } = await UserService.getUserSecretAndRoutine(id);
|
|
||||||
|
|
||||||
let valid = false;
|
let valid = speakeasy.totp.verify({
|
||||||
if (routine == LoginRoutineEnum.totp) {
|
secret: secret,
|
||||||
valid = speakeasy.totp.verify({
|
encoding: "base32",
|
||||||
secret: secret,
|
token: totp,
|
||||||
encoding: "base32",
|
window: 2,
|
||||||
token: passedSecret,
|
});
|
||||||
window: 2,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
valid = passedSecret == secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
throw new UnauthorizedRequestException("Credentials not valid or expired");
|
throw new UnauthorizedRequestException("Token not valid or expired");
|
||||||
}
|
}
|
||||||
|
|
||||||
let accessToken = await JWTHelper.buildToken(id);
|
let accessToken = await JWTHelper.buildToken(id);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { JWTHelper } from "../helpers/jwtHelper";
|
import { JWTHelper } from "../helpers/jwtHelper";
|
||||||
|
import { JWTToken } from "../type/jwtTypes";
|
||||||
import InternalException from "../exceptions/internalException";
|
import InternalException from "../exceptions/internalException";
|
||||||
import RefreshCommandHandler from "../command/refreshCommandHandler";
|
import RefreshCommandHandler from "../command/refreshCommandHandler";
|
||||||
import { CreateRefreshCommand } from "../command/refreshCommand";
|
import { CreateRefreshCommand } from "../command/refreshCommand";
|
||||||
|
@ -14,9 +15,10 @@ import MailHelper from "../helpers/mailHelper";
|
||||||
import InviteService from "../service/management/inviteService";
|
import InviteService from "../service/management/inviteService";
|
||||||
import UserService from "../service/management/userService";
|
import UserService from "../service/management/userService";
|
||||||
import CustomRequestException from "../exceptions/customRequestException";
|
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 InviteFactory from "../factory/admin/management/invite";
|
||||||
import SettingHelper from "../helpers/settingsHelper";
|
|
||||||
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description get all invites
|
* @description get all invites
|
||||||
|
@ -36,7 +38,7 @@ export async function getInvites(req: Request, res: Response): Promise<any> {
|
||||||
* @param res {Response} Express res object
|
* @param res {Response} Express res object
|
||||||
* @returns {Promise<*>}
|
* @returns {Promise<*>}
|
||||||
*/
|
*/
|
||||||
export async function inviteUser(req: Request, res: Response, isSetup: boolean = false): Promise<any> {
|
export async function inviteUser(req: Request, res: Response, isInvite: boolean = true): Promise<any> {
|
||||||
let origin = req.headers.origin;
|
let origin = req.headers.origin;
|
||||||
let username = req.body.username;
|
let username = req.body.username;
|
||||||
let mail = req.body.mail;
|
let mail = req.body.mail;
|
||||||
|
@ -57,7 +59,7 @@ export async function inviteUser(req: Request, res: Response, isSetup: boolean =
|
||||||
throw new CustomRequestException(409, "Username and Mail are already in use");
|
throw new CustomRequestException(409, "Username and Mail are already in use");
|
||||||
}
|
}
|
||||||
|
|
||||||
var secret = speakeasy.generateSecret({ length: 20, name: `FF Admin ${SettingHelper.getSetting("club.name")}` });
|
var secret = speakeasy.generateSecret({ length: 20, name: `FF Admin ${CLUB_NAME}` });
|
||||||
|
|
||||||
let createInvite: CreateInviteCommand = {
|
let createInvite: CreateInviteCommand = {
|
||||||
username: username,
|
username: username,
|
||||||
|
@ -71,8 +73,8 @@ export async function inviteUser(req: Request, res: Response, isSetup: boolean =
|
||||||
// sendmail
|
// sendmail
|
||||||
await MailHelper.sendMail(
|
await MailHelper.sendMail(
|
||||||
mail,
|
mail,
|
||||||
`Email Bestätigung für Mitglieder Admin-Portal von ${SettingHelper.getSetting("club.name")}`,
|
`Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`,
|
||||||
`Öffne folgenden Link: ${origin}/${isSetup ? "setup" : "invite"}/verify?mail=${mail}&token=${token}`
|
`Öffne folgenden Link: ${origin}/${isInvite ? "invite" : "setup"}/verify?mail=${mail}&token=${token}`
|
||||||
);
|
);
|
||||||
|
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
|
@ -90,7 +92,7 @@ export async function verifyInvite(req: Request, res: Response): Promise<any> {
|
||||||
|
|
||||||
let { secret, username } = await InviteService.getByMailAndToken(mail, token);
|
let { secret, username } = await InviteService.getByMailAndToken(mail, token);
|
||||||
|
|
||||||
const url = `otpauth://totp/FF Admin ${SettingHelper.getSetting("club.name")}?secret=${secret}`;
|
const url = `otpauth://totp/FF Admin ${CLUB_NAME}?secret=${secret}`;
|
||||||
|
|
||||||
QRCode.toDataURL(url)
|
QRCode.toDataURL(url)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
@ -113,26 +115,20 @@ export async function verifyInvite(req: Request, res: Response): Promise<any> {
|
||||||
*/
|
*/
|
||||||
export async function finishInvite(req: Request, res: Response, grantAdmin: boolean = false): Promise<any> {
|
export async function finishInvite(req: Request, res: Response, grantAdmin: boolean = false): Promise<any> {
|
||||||
let mail = req.body.mail;
|
let mail = req.body.mail;
|
||||||
let routine = req.body.routine;
|
|
||||||
let token = req.body.token;
|
let token = req.body.token;
|
||||||
let passedSecret = req.body.secret;
|
let totp = req.body.totp;
|
||||||
|
|
||||||
let { secret, username, firstname, lastname } = await InviteService.getByMailAndToken(mail, token);
|
let { secret, username, firstname, lastname } = await InviteService.getByMailAndToken(mail, token);
|
||||||
|
|
||||||
let valid = false;
|
let valid = speakeasy.totp.verify({
|
||||||
if (routine == LoginRoutineEnum.totp) {
|
secret: secret,
|
||||||
valid = speakeasy.totp.verify({
|
encoding: "base32",
|
||||||
secret: secret,
|
token: totp,
|
||||||
encoding: "base32",
|
window: 2,
|
||||||
token: passedSecret,
|
});
|
||||||
window: 2,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
valid = passedSecret != "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
throw new UnauthorizedRequestException("Credentials not valid or expired");
|
throw new UnauthorizedRequestException("Token not valid or expired");
|
||||||
}
|
}
|
||||||
|
|
||||||
let createUser: CreateUserCommand = {
|
let createUser: CreateUserCommand = {
|
||||||
|
@ -140,9 +136,8 @@ export async function finishInvite(req: Request, res: Response, grantAdmin: bool
|
||||||
firstname: firstname,
|
firstname: firstname,
|
||||||
lastname: lastname,
|
lastname: lastname,
|
||||||
mail: mail,
|
mail: mail,
|
||||||
secret: routine == LoginRoutineEnum.totp ? secret : passedSecret,
|
secret: secret,
|
||||||
isOwner: grantAdmin,
|
isOwner: grantAdmin,
|
||||||
routine,
|
|
||||||
};
|
};
|
||||||
let id = await UserCommandHandler.create(createUser);
|
let id = await UserCommandHandler.create(createUser);
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,11 @@ import { Request, Response } from "express";
|
||||||
import CalendarService from "../service/club/calendarService";
|
import CalendarService from "../service/club/calendarService";
|
||||||
import CalendarTypeService from "../service/configuration/calendarTypeService";
|
import CalendarTypeService from "../service/configuration/calendarTypeService";
|
||||||
import { calendar } from "../entity/club/calendar";
|
import { calendar } from "../entity/club/calendar";
|
||||||
|
import { createEvents } from "ics";
|
||||||
|
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";
|
|
||||||
import sharp from "sharp";
|
|
||||||
import ico from "sharp-ico";
|
|
||||||
import { FileSystemHelper } from "../helpers/fileSystemHelper";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description get all calendar items by types or nscdr
|
* @description get all calendar items by types or nscdr
|
||||||
|
@ -53,155 +51,3 @@ 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description get application Manifest
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function getApplicationManifest(req: Request, res: Response): Promise<any> {
|
|
||||||
const backendUrl = `${req.protocol}://${req.get("host")}`;
|
|
||||||
const frontenUrl = `${req.get("referer")}`;
|
|
||||||
|
|
||||||
const manifest = {
|
|
||||||
id: "ff_admin_webapp",
|
|
||||||
lang: "de",
|
|
||||||
name: SettingHelper.getSetting("club.name"),
|
|
||||||
short_name: SettingHelper.getSetting("club.name"),
|
|
||||||
theme_color: "#990b00",
|
|
||||||
display: "standalone",
|
|
||||||
orientation: "portrait-primary",
|
|
||||||
start_url: frontenUrl,
|
|
||||||
icons: [
|
|
||||||
{
|
|
||||||
src: `${backendUrl}/api/public/favicon.ico`,
|
|
||||||
sizes: "48x48",
|
|
||||||
type: "image/ico",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: `${backendUrl}/api/public/icon.png?width=512&height=512`,
|
|
||||||
sizes: "512x512",
|
|
||||||
type: "image/png",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
res.set({
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Content-Type": "application/manifest+json",
|
|
||||||
});
|
|
||||||
|
|
||||||
res.json(manifest);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description get application Logo
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function getApplicationLogo(req: Request, res: Response): Promise<any> {
|
|
||||||
let setLogo = SettingHelper.getSetting("club.logo");
|
|
||||||
|
|
||||||
res.set({
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Cross-Origin-Resource-Policy": "cross-origin",
|
|
||||||
"Cross-Origin-Embedder-Policy": "credentialless",
|
|
||||||
"Timing-Allow-Origin": "*",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (setLogo != "" && FileSystemHelper.getFilesInDirectory("/app", ".png").includes("admin-icon.png")) {
|
|
||||||
res.sendFile(FileSystemHelper.formatPath("/app/admin-logo.png"));
|
|
||||||
} else {
|
|
||||||
res.sendFile(FileSystemHelper.readAssetFile("admin-logo.png", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description get application Favicon
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function getApplicationFavicon(req: Request, res: Response): Promise<any> {
|
|
||||||
let icon = FileSystemHelper.readAssetFile("icon.png", true);
|
|
||||||
let setLogo = SettingHelper.getSetting("club.icon");
|
|
||||||
|
|
||||||
if (setLogo != "" && FileSystemHelper.getFilesInDirectory("/app", ".png").includes("admin-icon.png")) {
|
|
||||||
icon = FileSystemHelper.formatPath("/app/admin-icon.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
let image = await sharp(icon)
|
|
||||||
.resize(48, 48, {
|
|
||||||
fit: "inside",
|
|
||||||
})
|
|
||||||
.png()
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
let buffer = ico.encode([image]);
|
|
||||||
|
|
||||||
res.set({
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Cross-Origin-Resource-Policy": "cross-origin",
|
|
||||||
"Cross-Origin-Embedder-Policy": "credentialless",
|
|
||||||
"Timing-Allow-Origin": "*",
|
|
||||||
"Content-Type": "image/x-icon",
|
|
||||||
});
|
|
||||||
|
|
||||||
res.send(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description get application Icon
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function getApplicationIcon(req: Request, res: Response): Promise<any> {
|
|
||||||
const width = parseInt((req.query.width as string) ?? "512");
|
|
||||||
const height = parseInt((req.query.height as string) ?? "512");
|
|
||||||
|
|
||||||
let icon = FileSystemHelper.readAssetFile("icon.png", true);
|
|
||||||
let setLogo = SettingHelper.getSetting("club.icon");
|
|
||||||
|
|
||||||
if (setLogo != "" && FileSystemHelper.getFilesInDirectory("/app", ".png").includes("admin-icon.png")) {
|
|
||||||
icon = FileSystemHelper.formatPath("/app/admin-icon.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
let image = await sharp(icon)
|
|
||||||
.resize(width, height, {
|
|
||||||
fit: "inside",
|
|
||||||
})
|
|
||||||
.png()
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
res.set({
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Cross-Origin-Resource-Policy": "cross-origin",
|
|
||||||
"Cross-Origin-Embedder-Policy": "credentialless",
|
|
||||||
"Timing-Allow-Origin": "*",
|
|
||||||
"Content-Type": "image/png",
|
|
||||||
});
|
|
||||||
|
|
||||||
res.send(image);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { JWTHelper } from "../helpers/jwtHelper";
|
import { JWTHelper } from "../helpers/jwtHelper";
|
||||||
|
import { JWTToken } from "../type/jwtTypes";
|
||||||
import InternalException from "../exceptions/internalException";
|
import InternalException from "../exceptions/internalException";
|
||||||
import RefreshCommandHandler from "../command/refreshCommandHandler";
|
import RefreshCommandHandler from "../command/refreshCommandHandler";
|
||||||
import { CreateRefreshCommand } from "../command/refreshCommand";
|
import { CreateRefreshCommand } from "../command/refreshCommand";
|
||||||
|
@ -11,10 +12,12 @@ import ResetCommandHandler from "../command/resetCommandHandler";
|
||||||
import MailHelper from "../helpers/mailHelper";
|
import MailHelper from "../helpers/mailHelper";
|
||||||
import ResetService from "../service/resetService";
|
import ResetService from "../service/resetService";
|
||||||
import UserService from "../service/management/userService";
|
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 { UpdateUserSecretCommand } from "../command/management/user/userCommand";
|
||||||
import UserCommandHandler from "../command/management/user/userCommandHandler";
|
import UserCommandHandler from "../command/management/user/userCommandHandler";
|
||||||
import SettingHelper from "../helpers/settingsHelper";
|
|
||||||
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description request totp reset
|
* @description request totp reset
|
||||||
|
@ -28,7 +31,7 @@ export async function startReset(req: Request, res: Response): Promise<any> {
|
||||||
|
|
||||||
let { mail } = await UserService.getByUsername(username);
|
let { mail } = await UserService.getByUsername(username);
|
||||||
|
|
||||||
var secret = speakeasy.generateSecret({ length: 20, name: `FF Admin ${SettingHelper.getSetting("club.name")}` });
|
var secret = speakeasy.generateSecret({ length: 20, name: `FF Admin ${CLUB_NAME}` });
|
||||||
|
|
||||||
let createReset: CreateResetCommand = {
|
let createReset: CreateResetCommand = {
|
||||||
username: username,
|
username: username,
|
||||||
|
@ -40,7 +43,7 @@ export async function startReset(req: Request, res: Response): Promise<any> {
|
||||||
// sendmail
|
// sendmail
|
||||||
await MailHelper.sendMail(
|
await MailHelper.sendMail(
|
||||||
mail,
|
mail,
|
||||||
`Email Bestätigung für Mitglieder Admin-Portal von ${SettingHelper.getSetting("club.name")}`,
|
`Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`,
|
||||||
`Öffne folgenden Link: ${origin}/reset/reset?mail=${mail}&token=${token}`
|
`Öffne folgenden Link: ${origin}/reset/reset?mail=${mail}&token=${token}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -59,7 +62,7 @@ export async function verifyReset(req: Request, res: Response): Promise<any> {
|
||||||
|
|
||||||
let { secret } = await ResetService.getByMailAndToken(mail, token);
|
let { secret } = await ResetService.getByMailAndToken(mail, token);
|
||||||
|
|
||||||
const url = `otpauth://totp/FF Admin ${SettingHelper.getSetting("club.name")}?secret=${secret}`;
|
const url = `otpauth://totp/FF Admin ${CLUB_NAME}?secret=${secret}`;
|
||||||
|
|
||||||
QRCode.toDataURL(url)
|
QRCode.toDataURL(url)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
@ -81,34 +84,27 @@ export async function verifyReset(req: Request, res: Response): Promise<any> {
|
||||||
*/
|
*/
|
||||||
export async function finishReset(req: Request, res: Response): Promise<any> {
|
export async function finishReset(req: Request, res: Response): Promise<any> {
|
||||||
let mail = req.body.mail;
|
let mail = req.body.mail;
|
||||||
let routine = req.body.routine;
|
|
||||||
let token = req.body.token;
|
let token = req.body.token;
|
||||||
let passedSecret = req.body.secret;
|
let totp = req.body.totp;
|
||||||
|
|
||||||
let { secret, username } = await ResetService.getByMailAndToken(mail, token);
|
let { secret, username } = await ResetService.getByMailAndToken(mail, token);
|
||||||
|
|
||||||
let valid = false;
|
let valid = speakeasy.totp.verify({
|
||||||
if (routine == LoginRoutineEnum.totp) {
|
secret: secret,
|
||||||
valid = speakeasy.totp.verify({
|
encoding: "base32",
|
||||||
secret: secret,
|
token: totp,
|
||||||
encoding: "base32",
|
window: 2,
|
||||||
token: passedSecret,
|
});
|
||||||
window: 2,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
valid = passedSecret != "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
throw new UnauthorizedRequestException("Credentials not valid or expired");
|
throw new UnauthorizedRequestException("Token not valid or expired");
|
||||||
}
|
}
|
||||||
|
|
||||||
let { id } = await UserService.getByUsername(username);
|
let { id } = await UserService.getByUsername(username);
|
||||||
|
|
||||||
let updateUserSecret: UpdateUserSecretCommand = {
|
let updateUserSecret: UpdateUserSecretCommand = {
|
||||||
id,
|
id,
|
||||||
secret: routine == LoginRoutineEnum.totp ? secret : passedSecret,
|
secret,
|
||||||
routine,
|
|
||||||
};
|
};
|
||||||
await UserCommandHandler.updateSecret(updateUserSecret);
|
await UserCommandHandler.updateSecret(updateUserSecret);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import SettingHelper from "../helpers/settingsHelper";
|
|
||||||
import MailHelper from "../helpers/mailHelper";
|
|
||||||
import InternalException from "../exceptions/internalException";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Service is currently not configured
|
* @description Service is currently not configured
|
||||||
|
@ -12,131 +9,3 @@ import InternalException from "../exceptions/internalException";
|
||||||
export async function isSetup(req: Request, res: Response): Promise<any> {
|
export async function isSetup(req: Request, res: Response): Promise<any> {
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description set club identity
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function setClubIdentity(req: Request, res: Response): Promise<any> {
|
|
||||||
const name = req.body.name;
|
|
||||||
const imprint = req.body.imprint;
|
|
||||||
const privacy = req.body.privacy;
|
|
||||||
const website = req.body.website;
|
|
||||||
|
|
||||||
if (name) {
|
|
||||||
await SettingHelper.setSetting("club.name", name);
|
|
||||||
} else {
|
|
||||||
await SettingHelper.resetSetting("club.name");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imprint) {
|
|
||||||
await SettingHelper.setSetting("club.imprint", imprint);
|
|
||||||
} else {
|
|
||||||
await SettingHelper.resetSetting("club.imprint");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (privacy) {
|
|
||||||
await SettingHelper.setSetting("club.privacy", privacy);
|
|
||||||
} else {
|
|
||||||
await SettingHelper.resetSetting("club.privacy");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (website) {
|
|
||||||
await SettingHelper.setSetting("club.website", website);
|
|
||||||
} else {
|
|
||||||
await SettingHelper.resetSetting("club.website");
|
|
||||||
}
|
|
||||||
|
|
||||||
res.sendStatus(204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description set applucation icon and logo
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function uploadClubImages(req: Request, res: Response): Promise<any> {
|
|
||||||
if (req.files && !Array.isArray(req.files) && req.files.icon) {
|
|
||||||
await SettingHelper.setSetting("club.icon", "configured");
|
|
||||||
} else {
|
|
||||||
await SettingHelper.resetSetting("club.icon");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.files && !Array.isArray(req.files) && req.files.logo) {
|
|
||||||
await SettingHelper.setSetting("club.logo", "configured");
|
|
||||||
} else {
|
|
||||||
await SettingHelper.resetSetting("club.logo");
|
|
||||||
}
|
|
||||||
|
|
||||||
res.sendStatus(204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description set app identity
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function setAppIdentity(req: Request, res: Response): Promise<any> {
|
|
||||||
const custom_login_message = req.body.custom_login_message;
|
|
||||||
const show_link_to_calendar = req.body.show_link_to_calendar;
|
|
||||||
|
|
||||||
if (custom_login_message) {
|
|
||||||
await SettingHelper.setSetting("app.custom_login_message", custom_login_message);
|
|
||||||
} else {
|
|
||||||
await SettingHelper.resetSetting("app.custom_login_message");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (show_link_to_calendar == false || show_link_to_calendar == true) {
|
|
||||||
await SettingHelper.setSetting("app.show_link_to_calendar", show_link_to_calendar);
|
|
||||||
} else {
|
|
||||||
await SettingHelper.resetSetting("app.show_link_to_calendar");
|
|
||||||
}
|
|
||||||
|
|
||||||
res.sendStatus(204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description set app identity
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function setMailConfig(req: Request, res: Response): Promise<any> {
|
|
||||||
const mail = req.body.mail;
|
|
||||||
const username = req.body.username;
|
|
||||||
const password = req.body.password;
|
|
||||||
const host = req.body.host;
|
|
||||||
const port = req.body.port;
|
|
||||||
const secure = req.body.secure;
|
|
||||||
|
|
||||||
let checkMail = await MailHelper.checkMail(mail);
|
|
||||||
|
|
||||||
if (!checkMail) {
|
|
||||||
throw new InternalException("Mail is not valid");
|
|
||||||
}
|
|
||||||
|
|
||||||
let checkConfig = await MailHelper.verifyTransport({
|
|
||||||
user: username,
|
|
||||||
password,
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
secure,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!checkConfig) {
|
|
||||||
throw new InternalException("Config is not valid");
|
|
||||||
}
|
|
||||||
|
|
||||||
await SettingHelper.setSetting("mail.email", mail);
|
|
||||||
await SettingHelper.setSetting("mail.username", username);
|
|
||||||
await SettingHelper.setSetting("mail.password", password);
|
|
||||||
await SettingHelper.setSetting("mail.host", host);
|
|
||||||
await SettingHelper.setSetting("mail.port", port);
|
|
||||||
await SettingHelper.setSetting("mail.secure", secure);
|
|
||||||
|
|
||||||
res.sendStatus(204);
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,17 +2,12 @@ import { Request, Response } from "express";
|
||||||
import speakeasy from "speakeasy";
|
import speakeasy from "speakeasy";
|
||||||
import QRCode from "qrcode";
|
import QRCode from "qrcode";
|
||||||
import InternalException from "../exceptions/internalException";
|
import InternalException from "../exceptions/internalException";
|
||||||
|
import { CLUB_NAME } from "../env.defaults";
|
||||||
import UserService from "../service/management/userService";
|
import UserService from "../service/management/userService";
|
||||||
import UserFactory from "../factory/admin/management/user";
|
import UserFactory from "../factory/admin/management/user";
|
||||||
import {
|
import { TransferUserOwnerCommand, UpdateUserCommand } from "../command/management/user/userCommand";
|
||||||
TransferUserOwnerCommand,
|
|
||||||
UpdateUserCommand,
|
|
||||||
UpdateUserSecretCommand,
|
|
||||||
} from "../command/management/user/userCommand";
|
|
||||||
import UserCommandHandler from "../command/management/user/userCommandHandler";
|
import UserCommandHandler from "../command/management/user/userCommandHandler";
|
||||||
import ForbiddenRequestException from "../exceptions/forbiddenRequestException";
|
import ForbiddenRequestException from "../exceptions/forbiddenRequestException";
|
||||||
import SettingHelper from "../helpers/settingsHelper";
|
|
||||||
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description get my by id
|
* @description get my by id
|
||||||
|
@ -27,21 +22,6 @@ export async function getMeById(req: Request, res: Response): Promise<any> {
|
||||||
res.json(UserFactory.mapToSingle(user));
|
res.json(UserFactory.mapToSingle(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description get my routine by id
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function getMyRoutine(req: Request, res: Response): Promise<any> {
|
|
||||||
const id = req.userId;
|
|
||||||
let user = await UserService.getById(id);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
routine: user.routine,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description get my totp
|
* @description get my totp
|
||||||
* @param req {Request} Express req object
|
* @param req {Request} Express req object
|
||||||
|
@ -51,9 +31,9 @@ export async function getMyRoutine(req: Request, res: Response): Promise<any> {
|
||||||
export async function getMyTotp(req: Request, res: Response): Promise<any> {
|
export async function getMyTotp(req: Request, res: Response): Promise<any> {
|
||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
|
|
||||||
let { secret, routine } = await UserService.getUserSecretAndRoutine(userId);
|
let { secret } = await UserService.getById(userId);
|
||||||
|
|
||||||
const url = `otpauth://totp/FF Admin ${SettingHelper.getSetting("club.name")}?secret=${secret}`;
|
const url = `otpauth://totp/FF Admin ${CLUB_NAME}?secret=${secret}`;
|
||||||
|
|
||||||
QRCode.toDataURL(url)
|
QRCode.toDataURL(url)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
@ -77,12 +57,7 @@ export async function verifyMyTotp(req: Request, res: Response): Promise<any> {
|
||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
let totp = req.body.totp;
|
let totp = req.body.totp;
|
||||||
|
|
||||||
let { secret, routine } = await UserService.getUserSecretAndRoutine(userId);
|
let { secret } = await UserService.getById(userId);
|
||||||
|
|
||||||
if (routine != LoginRoutineEnum.totp) {
|
|
||||||
throw new ForbiddenRequestException("only allowed for totp login");
|
|
||||||
}
|
|
||||||
|
|
||||||
let valid = speakeasy.totp.verify({
|
let valid = speakeasy.totp.verify({
|
||||||
secret: secret,
|
secret: secret,
|
||||||
encoding: "base32",
|
encoding: "base32",
|
||||||
|
@ -96,106 +71,6 @@ export async function verifyMyTotp(req: Request, res: Response): Promise<any> {
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description change my password
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function changeMyPassword(req: Request, res: Response): Promise<any> {
|
|
||||||
const userId = req.userId;
|
|
||||||
let current = req.body.current;
|
|
||||||
let newpassword = req.body.newpassword;
|
|
||||||
|
|
||||||
let { secret, routine } = await UserService.getUserSecretAndRoutine(userId);
|
|
||||||
|
|
||||||
if (routine == LoginRoutineEnum.password && current != secret) {
|
|
||||||
throw new ForbiddenRequestException("passwords do not match");
|
|
||||||
}
|
|
||||||
|
|
||||||
let updateUser: UpdateUserSecretCommand = {
|
|
||||||
id: userId,
|
|
||||||
secret: newpassword,
|
|
||||||
routine: LoginRoutineEnum.password,
|
|
||||||
};
|
|
||||||
await UserCommandHandler.updateSecret(updateUser);
|
|
||||||
|
|
||||||
res.sendStatus(204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description get change to totp
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function getChangeToTOTP(req: Request, res: Response): Promise<any> {
|
|
||||||
var secret = speakeasy.generateSecret({ length: 20, name: `FF Admin ${SettingHelper.getSetting("club.name")}` });
|
|
||||||
|
|
||||||
QRCode.toDataURL(secret.otpauth_url)
|
|
||||||
.then((result) => {
|
|
||||||
res.json({
|
|
||||||
dataUrl: result,
|
|
||||||
otp: secret.base32,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
throw new InternalException("QRCode not created", err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description change to totp
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function changeToTOTP(req: Request, res: Response): Promise<any> {
|
|
||||||
const userId = req.userId;
|
|
||||||
let otp = req.body.otp;
|
|
||||||
let totp = req.body.totp;
|
|
||||||
|
|
||||||
let valid = speakeasy.totp.verify({
|
|
||||||
secret: otp,
|
|
||||||
encoding: "base32",
|
|
||||||
token: totp,
|
|
||||||
window: 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!valid) {
|
|
||||||
throw new InternalException("Token not valid or expired");
|
|
||||||
}
|
|
||||||
|
|
||||||
let updateUser: UpdateUserSecretCommand = {
|
|
||||||
id: userId,
|
|
||||||
secret: otp,
|
|
||||||
routine: LoginRoutineEnum.totp,
|
|
||||||
};
|
|
||||||
await UserCommandHandler.updateSecret(updateUser);
|
|
||||||
|
|
||||||
res.sendStatus(204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description change to password
|
|
||||||
* @param req {Request} Express req object
|
|
||||||
* @param res {Response} Express res object
|
|
||||||
* @returns {Promise<*>}
|
|
||||||
*/
|
|
||||||
export async function changeToPW(req: Request, res: Response): Promise<any> {
|
|
||||||
const userId = req.userId;
|
|
||||||
let newpassword = req.body.newpassword;
|
|
||||||
|
|
||||||
let updateUser: UpdateUserSecretCommand = {
|
|
||||||
id: userId,
|
|
||||||
secret: newpassword,
|
|
||||||
routine: LoginRoutineEnum.password,
|
|
||||||
};
|
|
||||||
await UserCommandHandler.updateSecret(updateUser);
|
|
||||||
|
|
||||||
res.sendStatus(204);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description transferOwnership
|
* @description transferOwnership
|
||||||
* @param req {Request} Express req object
|
* @param req {Request} Express req object
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { JWTHelper } from "../helpers/jwtHelper";
|
import { JWTHelper } from "../helpers/jwtHelper";
|
||||||
|
import { JWTToken } from "../type/jwtTypes";
|
||||||
|
import InternalException from "../exceptions/internalException";
|
||||||
|
import RefreshCommandHandler from "../command/refreshCommandHandler";
|
||||||
|
import { CreateRefreshCommand, DeleteRefreshCommand } from "../command/refreshCommand";
|
||||||
|
import UserService from "../service/management/userService";
|
||||||
|
import speakeasy from "speakeasy";
|
||||||
|
import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException";
|
||||||
|
import RefreshService from "../service/refreshService";
|
||||||
import WebapiService from "../service/management/webapiService";
|
import WebapiService from "../service/management/webapiService";
|
||||||
import ForbiddenRequestException from "../exceptions/forbiddenRequestException";
|
import ForbiddenRequestException from "../exceptions/forbiddenRequestException";
|
||||||
import WebapiCommandHandler from "../command/management/webapi/webapiCommandHandler";
|
import WebapiCommandHandler from "../command/management/webapi/webapiCommandHandler";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import "reflect-metadata";
|
import "reflect-metadata";
|
||||||
import { DataSource } from "typeorm";
|
import { DataSource } from "typeorm";
|
||||||
import { configCheck, DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_TYPE, DB_USERNAME } from "./env.defaults";
|
import { DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME, DB_TYPE, DB_PORT } from "./env.defaults";
|
||||||
|
|
||||||
import { user } from "./entity/management/user";
|
import { user } from "./entity/management/user";
|
||||||
import { refresh } from "./entity/refresh";
|
import { refresh } from "./entity/refresh";
|
||||||
|
@ -44,20 +44,11 @@ import { newsletterConfig } from "./entity/configuration/newsletterConfig";
|
||||||
import { webapi } from "./entity/management/webapi";
|
import { webapi } from "./entity/management/webapi";
|
||||||
import { webapiPermission } from "./entity/management/webapi_permission";
|
import { webapiPermission } from "./entity/management/webapi_permission";
|
||||||
import { salutation } from "./entity/configuration/salutation";
|
import { salutation } from "./entity/configuration/salutation";
|
||||||
import { setting } from "./entity/management/setting";
|
|
||||||
|
|
||||||
import { BackupAndResetDatabase1738166124200 } from "./migrations/1738166124200-BackupAndResetDatabase";
|
import { BackupAndResetDatabase1738166124200 } from "./migrations/1738166124200-BackupAndResetDatabase";
|
||||||
import { CreateSchema1738166167472 } from "./migrations/1738166167472-CreateSchema";
|
import { CreateSchema1738166167472 } from "./migrations/1738166167472-CreateSchema";
|
||||||
import { TemplatesAndProtocolSort1742549956787 } from "./migrations/1742549956787-templatesAndProtocolSort";
|
import { TemplatesAndProtocolSort1742549956787 } from "./migrations/1742549956787-templatesAndProtocolSort";
|
||||||
import { QueryToUUID1742922178643 } from "./migrations/1742922178643-queryToUUID";
|
import { QueryToUUID1742922178643 } from "./migrations/1742922178643-queryToUUID";
|
||||||
import { NewsletterColumnType1744351418751 } from "./migrations/1744351418751-newsletterColumnType";
|
|
||||||
import { QueryUpdatedAt1744795756230 } from "./migrations/1744795756230-QueryUpdatedAt";
|
|
||||||
import { SettingsFromEnv1745059495807 } from "./migrations/1745059495807-settingsFromEnv";
|
|
||||||
import { MemberCreatedAt1746006549262 } from "./migrations/1746006549262-memberCreatedAt";
|
|
||||||
import { UserLoginRoutine1746252454922 } from "./migrations/1746252454922-UserLoginRoutine";
|
|
||||||
import { SettingsFromEnv_SET1745059495808 } from "./migrations/1745059495808-settingsFromEnv_set";
|
|
||||||
|
|
||||||
configCheck();
|
|
||||||
|
|
||||||
const dataSource = new DataSource({
|
const dataSource = new DataSource({
|
||||||
type: DB_TYPE as any,
|
type: DB_TYPE as any,
|
||||||
|
@ -110,19 +101,12 @@ const dataSource = new DataSource({
|
||||||
membershipView,
|
membershipView,
|
||||||
webapi,
|
webapi,
|
||||||
webapiPermission,
|
webapiPermission,
|
||||||
setting,
|
|
||||||
],
|
],
|
||||||
migrations: [
|
migrations: [
|
||||||
BackupAndResetDatabase1738166124200,
|
BackupAndResetDatabase1738166124200,
|
||||||
CreateSchema1738166167472,
|
CreateSchema1738166167472,
|
||||||
TemplatesAndProtocolSort1742549956787,
|
TemplatesAndProtocolSort1742549956787,
|
||||||
QueryToUUID1742922178643,
|
QueryToUUID1742922178643,
|
||||||
NewsletterColumnType1744351418751,
|
|
||||||
QueryUpdatedAt1744795756230,
|
|
||||||
SettingsFromEnv1745059495807,
|
|
||||||
SettingsFromEnv_SET1745059495808,
|
|
||||||
MemberCreatedAt1746006549262,
|
|
||||||
UserLoginRoutine1746252454922,
|
|
||||||
],
|
],
|
||||||
migrationsRun: true,
|
migrationsRun: true,
|
||||||
migrationsTransactionMode: "each",
|
migrationsTransactionMode: "each",
|
||||||
|
|
|
@ -1,14 +1,4 @@
|
||||||
import {
|
import { Column, ColumnType, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, PrimaryColumn } from "typeorm";
|
||||||
Column,
|
|
||||||
ColumnType,
|
|
||||||
CreateDateColumn,
|
|
||||||
Entity,
|
|
||||||
JoinColumn,
|
|
||||||
ManyToOne,
|
|
||||||
OneToMany,
|
|
||||||
OneToOne,
|
|
||||||
PrimaryColumn,
|
|
||||||
} from "typeorm";
|
|
||||||
import { membership } from "./membership";
|
import { membership } from "./membership";
|
||||||
import { memberAwards } from "./memberAwards";
|
import { memberAwards } from "./memberAwards";
|
||||||
import { memberQualifications } from "./memberQualifications";
|
import { memberQualifications } from "./memberQualifications";
|
||||||
|
@ -40,9 +30,6 @@ export class member {
|
||||||
@Column()
|
@Column()
|
||||||
salutationId: number;
|
salutationId: number;
|
||||||
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@ManyToOne(() => salutation, (salutation) => salutation.members, {
|
@ManyToOne(() => salutation, (salutation) => salutation.members, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: "RESTRICT",
|
onDelete: "RESTRICT",
|
||||||
|
|
|
@ -14,13 +14,13 @@ export class newsletter {
|
||||||
@Column({ type: "varchar", length: 255, default: "" })
|
@Column({ type: "varchar", length: 255, default: "" })
|
||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
@Column({ type: "text", default: "" })
|
@Column({ type: "varchar", length: 255, default: "" })
|
||||||
newsletterTitle: string;
|
newsletterTitle: string;
|
||||||
|
|
||||||
@Column({ type: "text", default: "" })
|
@Column({ type: "text", default: "" })
|
||||||
newsletterText: string;
|
newsletterText: string;
|
||||||
|
|
||||||
@Column({ type: "text", default: "" })
|
@Column({ type: "varchar", length: 255, default: "" })
|
||||||
newsletterSignatur: string;
|
newsletterSignatur: string;
|
||||||
|
|
||||||
@Column({ type: "boolean", default: false })
|
@Column({ type: "boolean", default: false })
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm";
|
import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm";
|
||||||
import { NewsletterConfigEnum } from "../../enums/newsletterConfigEnum";
|
import { NewsletterConfigType } from "../../enums/newsletterConfigType";
|
||||||
import { communicationType } from "./communicationType";
|
import { communicationType } from "./communicationType";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
|
@ -11,15 +11,15 @@ export class newsletterConfig {
|
||||||
type: "varchar",
|
type: "varchar",
|
||||||
length: "255",
|
length: "255",
|
||||||
transformer: {
|
transformer: {
|
||||||
to(value: NewsletterConfigEnum) {
|
to(value: NewsletterConfigType) {
|
||||||
return value.toString();
|
return value.toString();
|
||||||
},
|
},
|
||||||
from(value: string) {
|
from(value: string) {
|
||||||
return NewsletterConfigEnum[value as keyof typeof NewsletterConfigEnum];
|
return NewsletterConfigType[value as keyof typeof NewsletterConfigType];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
config: NewsletterConfigEnum;
|
config: NewsletterConfigType;
|
||||||
|
|
||||||
@ManyToOne(() => communicationType, {
|
@ManyToOne(() => communicationType, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Column, Entity, PrimaryColumn, UpdateDateColumn } from "typeorm";
|
import { Column, Entity, PrimaryColumn } from "typeorm";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class query {
|
export class query {
|
||||||
|
@ -10,7 +10,4 @@ export class query {
|
||||||
|
|
||||||
@Column({ type: "text", default: "" })
|
@Column({ type: "text", default: "" })
|
||||||
query: string;
|
query: string;
|
||||||
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { Column, Entity, PrimaryColumn } from "typeorm";
|
|
||||||
|
|
||||||
@Entity()
|
|
||||||
export class setting {
|
|
||||||
@PrimaryColumn({ type: "varchar", length: 255 })
|
|
||||||
topic: string;
|
|
||||||
|
|
||||||
@PrimaryColumn({ type: "varchar", length: 255 })
|
|
||||||
key: string;
|
|
||||||
|
|
||||||
@Column({ type: "text" })
|
|
||||||
value: string;
|
|
||||||
}
|
|
|
@ -1,9 +1,6 @@
|
||||||
import { Column, Entity, JoinTable, ManyToMany, OneToMany, PrimaryColumn } from "typeorm";
|
import { Column, Entity, JoinTable, ManyToMany, OneToMany, PrimaryColumn } from "typeorm";
|
||||||
import { role } from "./role";
|
import { role } from "./role";
|
||||||
import { userPermission } from "./user_permission";
|
import { userPermission } from "./user_permission";
|
||||||
import { LoginRoutineEnum } from "../../enums/loginRoutineEnum";
|
|
||||||
import { CodingHelper } from "../../helpers/codingHelper";
|
|
||||||
import { APPLICATION_SECRET } from "../../env.defaults";
|
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class user {
|
export class user {
|
||||||
|
@ -22,27 +19,11 @@ export class user {
|
||||||
@Column({ type: "varchar", length: 255 })
|
@Column({ type: "varchar", length: 255 })
|
||||||
lastname: string;
|
lastname: string;
|
||||||
|
|
||||||
@Column({
|
@Column({ type: "varchar", length: 255 })
|
||||||
type: "text",
|
|
||||||
select: false,
|
|
||||||
transformer: CodingHelper.entityBaseCoding(APPLICATION_SECRET, "<self>"),
|
|
||||||
})
|
|
||||||
secret: string;
|
secret: string;
|
||||||
|
|
||||||
@Column({
|
@Column({ type: "boolean", default: false })
|
||||||
type: "varchar",
|
static: boolean;
|
||||||
length: "255",
|
|
||||||
default: LoginRoutineEnum.totp,
|
|
||||||
transformer: {
|
|
||||||
to(value: LoginRoutineEnum) {
|
|
||||||
return value.toString();
|
|
||||||
},
|
|
||||||
from(value: string) {
|
|
||||||
return LoginRoutineEnum[value as keyof typeof LoginRoutineEnum];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
routine: LoginRoutineEnum;
|
|
||||||
|
|
||||||
@Column({ type: "boolean", default: false })
|
@Column({ type: "boolean", default: false })
|
||||||
isOwner: boolean;
|
isOwner: boolean;
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
export enum LoginRoutineEnum {
|
|
||||||
password = "password", // login with self defined password
|
|
||||||
totp = "totp", // login with totp by auth apps
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
export enum NewsletterConfigEnum {
|
|
||||||
pdf = "pdf",
|
|
||||||
mail = "mail",
|
|
||||||
none = "none",
|
|
||||||
}
|
|
4
src/enums/newsletterConfigType.ts
Normal file
4
src/enums/newsletterConfigType.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export enum NewsletterConfigType {
|
||||||
|
pdf = "pdf",
|
||||||
|
mail = "mail",
|
||||||
|
}
|
|
@ -11,13 +11,29 @@ export const DB_PASSWORD = process.env.DB_PASSWORD ?? "";
|
||||||
|
|
||||||
export const SERVER_PORT = Number(process.env.SERVER_PORT ?? 5000);
|
export const SERVER_PORT = Number(process.env.SERVER_PORT ?? 5000);
|
||||||
|
|
||||||
export const APPLICATION_SECRET = process.env.APPLICATION_SECRET ?? "";
|
export const JWT_SECRET = process.env.JWT_SECRET ?? "my_jwt_secret_string_ilughfnadiuhgq§$IUZGFVRweiouarbt1oub3h5q4a";
|
||||||
|
export const JWT_EXPIRATION = process.env.JWT_EXPIRATION ?? "15m";
|
||||||
|
export const REFRESH_EXPIRATION = process.env.REFRESH_EXPIRATION ?? "1d";
|
||||||
|
export const PWA_REFRESH_EXPIRATION = process.env.PWA_REFRESH_EXPIRATION ?? "5d";
|
||||||
|
|
||||||
|
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 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 BACKUP_AUTO_RESTORE = process.env.BACKUP_AUTO_RESTORE ?? "true";
|
||||||
|
|
||||||
export const USE_SECURITY_STRICT_LIMIT = process.env.USE_SECURITY_STRICT_LIMIT ?? "true";
|
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;
|
export const SECURITY_STRICT_LIMIT_WINDOW = process.env.SECURITY_STRICT_LIMIT_WINDOW ?? "15m";
|
||||||
export const SECURITY_STRICT_LIMIT_REQUEST_COUNT = Number(process.env.SECURITY_STRICT_LIMIT_REQUEST_COUNT ?? "15");
|
export const SECURITY_STRICT_LIMIT_REQUEST_COUNT = Number(process.env.SECURITY_STRICT_LIMIT_REQUEST_COUNT ?? "15");
|
||||||
export const USE_SECURITY_LIMIT = process.env.USE_SECURITY_LIMIT ?? "true";
|
export const USE_SECURITY_LIMIT = process.env.USE_SECURITY_LIMIT ?? "true";
|
||||||
export const SECURITY_LIMIT_WINDOW = (process.env.SECURITY_LIMIT_WINDOW ?? "1m") as ms.StringValue;
|
export const SECURITY_LIMIT_WINDOW = process.env.SECURITY_LIMIT_WINDOW ?? "1m";
|
||||||
export const SECURITY_LIMIT_REQUEST_COUNT = Number(process.env.SECURITY_LIMIT_REQUEST_COUNT ?? "500");
|
export const SECURITY_LIMIT_REQUEST_COUNT = Number(process.env.SECURITY_LIMIT_REQUEST_COUNT ?? "500");
|
||||||
|
|
||||||
export const TRUST_PROXY = ((): Array<string> | string | boolean | number | null => {
|
export const TRUST_PROXY = ((): Array<string> | string | boolean | number | null => {
|
||||||
|
@ -39,16 +55,40 @@ export const TRUST_PROXY = ((): Array<string> | string | boolean | number | null
|
||||||
})();
|
})();
|
||||||
|
|
||||||
export function configCheck() {
|
export function configCheck() {
|
||||||
if (DB_TYPE != "mysql" && DB_TYPE != "postgres") throw new Error("set valid value to DB_TYPE (mysql|postgres)");
|
if (DB_TYPE != "mysql" && DB_TYPE != "sqlite" && DB_TYPE != "postgres")
|
||||||
if (DB_HOST == "" || typeof DB_HOST != "string") throw new Error("set valid value to DB_HOST");
|
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_NAME == "" || typeof DB_NAME != "string") throw new Error("set valid value to DB_NAME");
|
||||||
if (DB_USERNAME == "" || typeof DB_USERNAME != "string") throw new Error("set valid value to DB_USERNAME");
|
if ((DB_USERNAME == "" || typeof DB_USERNAME != "string") && DB_TYPE != "sqlite")
|
||||||
if (DB_PASSWORD == "" || typeof DB_PASSWORD != "string") throw new Error("set valid value to DB_PASSWORD");
|
throw new Error("set valid value to DB_USERNAME");
|
||||||
|
if ((DB_PASSWORD == "" || typeof DB_PASSWORD != "string") && DB_TYPE != "sqlite")
|
||||||
if (APPLICATION_SECRET == "") throw new Error("set valid APPLICATION_SECRET");
|
throw new Error("set valid value to DB_PASSWORD");
|
||||||
|
|
||||||
if (isNaN(SERVER_PORT)) throw new Error("set valid numeric value to SERVER_PORT");
|
if (isNaN(SERVER_PORT)) throw new Error("set valid numeric value to SERVER_PORT");
|
||||||
|
|
||||||
|
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 (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_AUTO_RESTORE != "true" && BACKUP_AUTO_RESTORE != "false")
|
||||||
|
throw new Error("set 'true' or 'false' to BACKUP_AUTO_RESTORE");
|
||||||
|
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 (USE_SECURITY_STRICT_LIMIT != "true" && USE_SECURITY_STRICT_LIMIT != "false")
|
if (USE_SECURITY_STRICT_LIMIT != "true" && USE_SECURITY_STRICT_LIMIT != "false")
|
||||||
throw new Error("set 'true' or 'false' to USE_SECURITY_STRICT_LIMIT");
|
throw new Error("set 'true' or 'false' to USE_SECURITY_STRICT_LIMIT");
|
||||||
checkMS(SECURITY_STRICT_LIMIT_WINDOW, "SECURITY_STRICT_LIMIT_WINDOW");
|
checkMS(SECURITY_STRICT_LIMIT_WINDOW, "SECURITY_STRICT_LIMIT_WINDOW");
|
||||||
|
@ -64,7 +104,7 @@ export function configCheck() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkMS(input: ms.StringValue, origin: string) {
|
function checkMS(input: string, origin: string) {
|
||||||
try {
|
try {
|
||||||
const result = ms(input);
|
const result = ms(input);
|
||||||
if (result === undefined) {
|
if (result === undefined) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import CustomRequestException from "./customRequestException";
|
||||||
|
|
||||||
export default class DatabaseActionException extends CustomRequestException {
|
export default class DatabaseActionException extends CustomRequestException {
|
||||||
constructor(action: string, table: string, err: any) {
|
constructor(action: string, table: string, err: any) {
|
||||||
let errstring = `${action} on ${table} with ${err?.code ?? "XX"} at ${err?.sqlMessage ?? err?.message ?? "XX"}`;
|
let errstring = `${action} on ${table} with ${err?.code ?? "XX"} at ${err?.sqlMessage ?? "XX"}`;
|
||||||
super(500, errstring, err);
|
super(500, errstring, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ export default abstract class NewsletterFactory {
|
||||||
newsletterSignatur: record.newsletterSignatur,
|
newsletterSignatur: record.newsletterSignatur,
|
||||||
isSent: record.isSent,
|
isSent: record.isSent,
|
||||||
recipientsByQueryId: record?.recipientsByQuery ? record.recipientsByQuery.id : null,
|
recipientsByQueryId: record?.recipientsByQuery ? record.recipientsByQuery.id : null,
|
||||||
|
recipientsByQuery: record?.recipientsByQuery ? QueryStoreFactory.mapToSingle(record.recipientsByQuery) : null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ export default abstract class QueryStoreFactory {
|
||||||
id: record.id,
|
id: record.id,
|
||||||
title: record.title,
|
title: record.title,
|
||||||
query: record.query.startsWith("{") ? JSON.parse(record.query) : record.query,
|
query: record.query.startsWith("{") ? JSON.parse(record.query) : record.query,
|
||||||
updatedAt: record.updatedAt,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,9 @@ import { EntityManager } from "typeorm";
|
||||||
import uniqBy from "lodash.uniqby";
|
import uniqBy from "lodash.uniqby";
|
||||||
import InternalException from "../exceptions/internalException";
|
import InternalException from "../exceptions/internalException";
|
||||||
import UserService from "../service/management/userService";
|
import UserService from "../service/management/userService";
|
||||||
|
import { BACKUP_COPIES, BACKUP_INTERVAL } from "../env.defaults";
|
||||||
import DatabaseActionException from "../exceptions/databaseActionException";
|
import DatabaseActionException from "../exceptions/databaseActionException";
|
||||||
import { availableTemplates } from "../type/templateTypes";
|
import { availableTemplates } from "../type/templateTypes";
|
||||||
import SettingHelper from "./settingsHelper";
|
|
||||||
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
|
|
||||||
|
|
||||||
export type BackupSection =
|
export type BackupSection =
|
||||||
| "member"
|
| "member"
|
||||||
|
@ -19,8 +18,7 @@ export type BackupSection =
|
||||||
| "query"
|
| "query"
|
||||||
| "template"
|
| "template"
|
||||||
| "user"
|
| "user"
|
||||||
| "webapi"
|
| "webapi";
|
||||||
| "settings";
|
|
||||||
|
|
||||||
export type BackupSectionRefered = {
|
export type BackupSectionRefered = {
|
||||||
[key in BackupSection]?: Array<string>;
|
[key in BackupSection]?: Array<string>;
|
||||||
|
@ -44,7 +42,6 @@ export default abstract class BackupHelper {
|
||||||
{ type: "template", orderOnInsert: 2, orderOnClear: 1 }, // INSERT depends on member com
|
{ type: "template", orderOnInsert: 2, orderOnClear: 1 }, // INSERT depends on member com
|
||||||
{ type: "user", orderOnInsert: 1, orderOnClear: 1 },
|
{ type: "user", orderOnInsert: 1, orderOnClear: 1 },
|
||||||
{ type: "webapi", orderOnInsert: 1, orderOnClear: 1 },
|
{ type: "webapi", orderOnInsert: 1, orderOnClear: 1 },
|
||||||
{ type: "settings", orderOnInsert: 1, orderOnClear: 1 },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
private static readonly backupSectionRefered: BackupSectionRefered = {
|
private static readonly backupSectionRefered: BackupSectionRefered = {
|
||||||
|
@ -79,7 +76,6 @@ export default abstract class BackupHelper {
|
||||||
template: ["template", "template_usage"],
|
template: ["template", "template_usage"],
|
||||||
user: ["user", "user_permission", "role", "role_permission", "invite"],
|
user: ["user", "user_permission", "role", "role_permission", "invite"],
|
||||||
webapi: ["webapi", "webapi_permission"],
|
webapi: ["webapi", "webapi_permission"],
|
||||||
settings: ["setting"],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private static transactionManager: EntityManager;
|
private static transactionManager: EntityManager;
|
||||||
|
@ -107,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"));
|
const filesToDelete = sorted.slice(BACKUP_COPIES);
|
||||||
for (const file of filesToDelete) {
|
for (const file of filesToDelete) {
|
||||||
FileSystemHelper.deleteFile("backup", file);
|
FileSystemHelper.deleteFile("backup", file);
|
||||||
}
|
}
|
||||||
|
@ -121,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")) {
|
if (diffInDays >= BACKUP_INTERVAL) {
|
||||||
await this.createBackup({});
|
await this.createBackup({});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,8 +220,6 @@ export default abstract class BackupHelper {
|
||||||
return await this.getUser(collectIds);
|
return await this.getUser(collectIds);
|
||||||
case "webapi":
|
case "webapi":
|
||||||
return await this.getWebapi();
|
return await this.getWebapi();
|
||||||
case "settings":
|
|
||||||
return await this.getSettings();
|
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -355,12 +349,7 @@ export default abstract class BackupHelper {
|
||||||
"member.birthdate",
|
"member.birthdate",
|
||||||
"member.internalId",
|
"member.internalId",
|
||||||
])
|
])
|
||||||
.addSelect([
|
.addSelect([...(collectIds ? ["query.id"] : []), "recipientsByQuery.title", "recipientsByQuery.query"])
|
||||||
...(collectIds ? ["recipientsByQuery.id"] : []),
|
|
||||||
"recipientsByQuery.title",
|
|
||||||
"recipientsByQuery.query",
|
|
||||||
"recipientsByQuery.updatedAt",
|
|
||||||
])
|
|
||||||
.getMany()
|
.getMany()
|
||||||
.then((res: any) =>
|
.then((res: any) =>
|
||||||
res.map((n: any) => ({
|
res.map((n: any) => ({
|
||||||
|
@ -441,7 +430,6 @@ export default abstract class BackupHelper {
|
||||||
"user.firstname",
|
"user.firstname",
|
||||||
"user.lastname",
|
"user.lastname",
|
||||||
"user.secret",
|
"user.secret",
|
||||||
"user.routine",
|
|
||||||
"user.isOwner",
|
"user.isOwner",
|
||||||
])
|
])
|
||||||
.addSelect(["permissions.permission"])
|
.addSelect(["permissions.permission"])
|
||||||
|
@ -467,13 +455,6 @@ export default abstract class BackupHelper {
|
||||||
.addSelect(["permissions.permission"])
|
.addSelect(["permissions.permission"])
|
||||||
.getMany();
|
.getMany();
|
||||||
}
|
}
|
||||||
private static async getSettings(): Promise<Array<any>> {
|
|
||||||
return await dataSource
|
|
||||||
.getRepository("setting")
|
|
||||||
.createQueryBuilder("setting")
|
|
||||||
.select(["setting.topic", "setting.key", "setting.value"])
|
|
||||||
.getMany();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async setSectionData(
|
private static async setSectionData(
|
||||||
section: BackupSection,
|
section: BackupSection,
|
||||||
|
@ -490,7 +471,6 @@ export default abstract class BackupHelper {
|
||||||
if (section == "template" && !Array.isArray(data)) await this.setTemplate(data);
|
if (section == "template" && !Array.isArray(data)) await this.setTemplate(data);
|
||||||
if (section == "user" && !Array.isArray(data)) await this.setUser(data);
|
if (section == "user" && !Array.isArray(data)) await this.setUser(data);
|
||||||
if (section == "webapi" && Array.isArray(data)) await this.setWebapi(data);
|
if (section == "webapi" && Array.isArray(data)) await this.setWebapi(data);
|
||||||
if (section == "settings" && Array.isArray(data)) await this.setSettings(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async setMemberData(data: Array<any>): Promise<void> {
|
private static async setMemberData(data: Array<any>): Promise<void> {
|
||||||
|
@ -770,11 +750,11 @@ export default abstract class BackupHelper {
|
||||||
.filter((d) => availableTemplates.includes(d.scope))
|
.filter((d) => availableTemplates.includes(d.scope))
|
||||||
.map((d) => ({
|
.map((d) => ({
|
||||||
...d,
|
...d,
|
||||||
headerHeightId: templates.find((template) => template.template == d.headerHeight)?.id ?? null,
|
headerHeightId: templates.find((template) => template.template == d.headerHeight.template)?.id ?? null,
|
||||||
footerHeightId: templates.find((template) => template.template == d.footerHeight)?.id ?? null,
|
footerHeightId: templates.find((template) => template.template == d.footerHeight.template)?.id ?? null,
|
||||||
headerId: templates.find((template) => template.template == d.header?.template)?.id ?? null,
|
headerId: templates.find((template) => template.template == d.header.template)?.id ?? null,
|
||||||
bodyId: templates.find((template) => template.template == d.body?.template)?.id ?? null,
|
bodyId: templates.find((template) => template.template == d.body.template)?.id ?? null,
|
||||||
footerId: templates.find((template) => template.template == d.footer?.template)?.id ?? null,
|
footerId: templates.find((template) => template.template == d.footer.template)?.id ?? null,
|
||||||
}));
|
}));
|
||||||
availableTemplates.forEach((at) => {
|
availableTemplates.forEach((at) => {
|
||||||
if (!dataWithMappedId.some((d) => d.scope == at)) {
|
if (!dataWithMappedId.some((d) => d.scope == at)) {
|
||||||
|
@ -808,7 +788,6 @@ export default abstract class BackupHelper {
|
||||||
let roles = await this.transactionManager.getRepository("role").find();
|
let roles = await this.transactionManager.getRepository("role").find();
|
||||||
let dataWithMappedIds = (data?.["user"] ?? []).map((u) => ({
|
let dataWithMappedIds = (data?.["user"] ?? []).map((u) => ({
|
||||||
...u,
|
...u,
|
||||||
routine: u.routine ?? LoginRoutineEnum.totp,
|
|
||||||
roles: u.roles.map((r: any) => ({
|
roles: u.roles.map((r: any) => ({
|
||||||
...r,
|
...r,
|
||||||
id: roles.find((role) => role.role == r.role)?.id ?? undefined,
|
id: roles.find((role) => role.role == r.role)?.id ?? undefined,
|
||||||
|
@ -826,7 +805,4 @@ export default abstract class BackupHelper {
|
||||||
private static async setWebapi(data: Array<any>): Promise<void> {
|
private static async setWebapi(data: Array<any>): Promise<void> {
|
||||||
await this.transactionManager.getRepository("webapi").save(data);
|
await this.transactionManager.getRepository("webapi").save(data);
|
||||||
}
|
}
|
||||||
private static async setSettings(data: Array<any>): Promise<void> {
|
|
||||||
await this.transactionManager.getRepository("setting").save(data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { createEvents } from "ics";
|
import { createEvents } from "ics";
|
||||||
import { calendar } from "../entity/club/calendar";
|
import { calendar } from "../entity/club/calendar";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import SettingHelper from "./settingsHelper";
|
import { CLUB_NAME, CLUB_WEBSITE, MAIL_USERNAME } from "../env.defaults";
|
||||||
|
|
||||||
export abstract class CalendarHelper {
|
export abstract class CalendarHelper {
|
||||||
public static buildICS(entries: Array<calendar>): { error?: Error; value?: string } {
|
public static buildICS(entries: Array<calendar>): { error?: Error; value?: string } {
|
||||||
|
@ -35,10 +35,7 @@ export abstract class CalendarHelper {
|
||||||
description: i.content,
|
description: i.content,
|
||||||
location: i.location,
|
location: i.location,
|
||||||
categories: [i.type.type],
|
categories: [i.type.type],
|
||||||
organizer: {
|
organizer: { name: CLUB_NAME, email: MAIL_USERNAME },
|
||||||
name: SettingHelper.getSetting("club.name"),
|
|
||||||
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")
|
||||||
.split("-")
|
.split("-")
|
||||||
|
@ -49,7 +46,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") != "" ? { url: SettingHelper.getSetting("club.website") } : {}),
|
...(CLUB_WEBSITE != "" ? { url: CLUB_WEBSITE } : {}),
|
||||||
alarms: [
|
alarms: [
|
||||||
{
|
{
|
||||||
action: "display",
|
action: "display",
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
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 || val == "") return fallback;
|
|
||||||
try {
|
|
||||||
return CodingHelper.decrypt(key, val, true);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Decryption error in database-read - can be ignored");
|
|
||||||
if (fallback == "<self>") return val;
|
|
||||||
else return fallback;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
to(val: string | null | undefined): string {
|
|
||||||
const valueToEncrypt = val || fallback;
|
|
||||||
if (valueToEncrypt === "") return "";
|
|
||||||
|
|
||||||
try {
|
|
||||||
return CodingHelper.encrypt(key, valueToEncrypt, true);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Encryption error in database-read - can be ignored");
|
|
||||||
if (fallback == "<self>") return val;
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static encrypt(phrase: string, content: string, passError = false): string {
|
|
||||||
if (!content) return "";
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 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");
|
|
||||||
} catch (error) {
|
|
||||||
if (passError) throw error;
|
|
||||||
console.error("Encryption failed:", error);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static decrypt(phrase: string, content: string, passError = false): 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) {
|
|
||||||
if (passError) throw error;
|
|
||||||
console.error("Decryption failed:", error);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
import ms from "ms";
|
|
||||||
import validator from "validator";
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class EmailTypeConverter extends TypeConverter<string> {
|
|
||||||
fromString(value: string): string {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
toString(value: string): string {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
validate(value: string): boolean {
|
|
||||||
return validator.isEmail(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {}
|
|
||||||
export class EmailConverter extends EmailTypeConverter {}
|
|
|
@ -2,7 +2,6 @@ import { Brackets, DataSource, NotBrackets, ObjectLiteral, SelectQueryBuilder, W
|
||||||
import { dataSource } from "../data-source";
|
import { dataSource } from "../data-source";
|
||||||
import { ConditionStructure, DynamicQueryStructure, FieldType, QueryResult } from "../type/dynamicQueries";
|
import { ConditionStructure, DynamicQueryStructure, FieldType, QueryResult } from "../type/dynamicQueries";
|
||||||
import { TableMeta } from "../type/tableMeta";
|
import { TableMeta } from "../type/tableMeta";
|
||||||
import { StringHelper } from "./stringHelper";
|
|
||||||
|
|
||||||
export default abstract class DynamicQueryBuilder {
|
export default abstract class DynamicQueryBuilder {
|
||||||
public static allowedTables: Array<string> = [
|
public static allowedTables: Array<string> = [
|
||||||
|
@ -63,18 +62,11 @@ export default abstract class DynamicQueryBuilder {
|
||||||
count?: number;
|
count?: number;
|
||||||
noLimit?: boolean;
|
noLimit?: boolean;
|
||||||
}): SelectQueryBuilder<ObjectLiteral> {
|
}): SelectQueryBuilder<ObjectLiteral> {
|
||||||
let affix = queryObj.id ?? StringHelper.random(10);
|
let affix = Math.random().toString(36).substring(2);
|
||||||
let query = dataSource.getRepository(queryObj.table).createQueryBuilder(`${affix}_${queryObj.table}`);
|
let query = dataSource.getRepository(queryObj.table).createQueryBuilder(`${queryObj.table}_${affix}`);
|
||||||
|
|
||||||
this.buildDynamicQuery(query, queryObj, affix);
|
this.buildDynamicQuery(query, queryObj, affix);
|
||||||
|
|
||||||
if (queryObj.orderBy) {
|
|
||||||
queryObj.orderBy.forEach((order) => {
|
|
||||||
// compatability layer for none id (old) queries
|
|
||||||
if (order.id) query.addOrderBy(`${order.id}_${order.table}.${order.column}`, order.order);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!noLimit) {
|
if (!noLimit) {
|
||||||
query.offset(offset);
|
query.offset(offset);
|
||||||
query.limit(count);
|
query.limit(count);
|
||||||
|
@ -86,10 +78,10 @@ export default abstract class DynamicQueryBuilder {
|
||||||
private static buildDynamicQuery(
|
private static buildDynamicQuery(
|
||||||
query: SelectQueryBuilder<ObjectLiteral>,
|
query: SelectQueryBuilder<ObjectLiteral>,
|
||||||
queryObject: DynamicQueryStructure,
|
queryObject: DynamicQueryStructure,
|
||||||
affix: string = "", // table id
|
affix: string = "",
|
||||||
depth: number = 0
|
depth: number = 0
|
||||||
): void {
|
): void {
|
||||||
const alias = `${affix}_${queryObject.table}`;
|
const alias = queryObject.table + "_" + affix;
|
||||||
let firstSelect = true;
|
let firstSelect = true;
|
||||||
let selects: Array<string> = [];
|
let selects: Array<string> = [];
|
||||||
|
|
||||||
|
@ -116,21 +108,18 @@ export default abstract class DynamicQueryBuilder {
|
||||||
|
|
||||||
if (queryObject.join) {
|
if (queryObject.join) {
|
||||||
for (const join of queryObject.join) {
|
for (const join of queryObject.join) {
|
||||||
let subaffix = join.id ?? StringHelper.random(10);
|
let subaffix = Math.random().toString(36).substring(2);
|
||||||
if (join.type == undefined) join.type = "defined";
|
query.leftJoin(`${alias}.${join.foreignColumn}`, join.table + "_" + subaffix);
|
||||||
if (join.type == "defined") {
|
|
||||||
query.innerJoin(`${alias}.${join.foreignColumn}`, `${subaffix}_${join.table}`);
|
|
||||||
} else {
|
|
||||||
let condition = join.condition
|
|
||||||
.replaceAll(`${join.table}.`, `${subaffix}_${join.table}.`)
|
|
||||||
.replaceAll(`${queryObject.table}.`, `${alias}.`);
|
|
||||||
|
|
||||||
query.innerJoin(join.table, `${subaffix}_${join.table}`, condition);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.buildDynamicQuery(query, join, subaffix, depth + 1);
|
this.buildDynamicQuery(query, join, subaffix, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (queryObject.orderBy) {
|
||||||
|
queryObject.orderBy.forEach((order) => {
|
||||||
|
query.addOrderBy(`${alias}.${order.column}`, order.order);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static applyWhere(
|
public static applyWhere(
|
||||||
|
@ -177,7 +166,7 @@ export default abstract class DynamicQueryBuilder {
|
||||||
condition: ConditionStructure & { structureType: "condition" },
|
condition: ConditionStructure & { structureType: "condition" },
|
||||||
alias: string
|
alias: string
|
||||||
): { query: string; parameters: Record<string, unknown> } {
|
): { query: string; parameters: Record<string, unknown> } {
|
||||||
const parameterKey = `${condition.column}_${Math.random().toString(36).substring(2)}`;
|
const parameterKey = `${alias}_${condition.column}_${Math.random().toString(36).substring(2)}`;
|
||||||
let query = `${alias}.${condition.column}`;
|
let query = `${alias}.${condition.column}`;
|
||||||
let parameters: Record<string, unknown> = {};
|
let parameters: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
@ -390,7 +379,6 @@ export default abstract class DynamicQueryBuilder {
|
||||||
count: noLimit ? total : count,
|
count: noLimit ? total : count,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
|
||||||
return {
|
return {
|
||||||
stats: "error",
|
stats: "error",
|
||||||
sql: error.sql,
|
sql: error.sql,
|
||||||
|
@ -403,31 +391,27 @@ export default abstract class DynamicQueryBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
const memberQuery: DynamicQueryStructure = {
|
const memberQuery: DynamicQueryStructure = {
|
||||||
id: "memberId",
|
|
||||||
select: "*",
|
select: "*",
|
||||||
table: "member",
|
table: "member",
|
||||||
orderBy: [
|
orderBy: [
|
||||||
{ id: "memberId", depth: 0, table: "member", column: "lastname", order: "ASC" },
|
{ column: "lastname", order: "ASC" },
|
||||||
{ id: "memberId", depth: 0, table: "member", column: "firstname", order: "ASC" },
|
{ column: "firstname", order: "ASC" },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const memberByRunningMembershipQuery: DynamicQueryStructure = {
|
const memberByRunningMembershipQuery: DynamicQueryStructure = {
|
||||||
id: "memberId",
|
|
||||||
select: "*",
|
select: "*",
|
||||||
table: "member",
|
table: "member",
|
||||||
join: [
|
join: [
|
||||||
{
|
{
|
||||||
id: "membershipId",
|
|
||||||
select: "*",
|
select: "*",
|
||||||
table: "membership",
|
table: "membership",
|
||||||
where: [{ structureType: "condition", concat: "_", operation: "null", column: "end", value: "" }],
|
where: [{ structureType: "condition", concat: "_", operation: "null", column: "end", value: "" }],
|
||||||
foreignColumn: "memberships",
|
foreignColumn: "memberships",
|
||||||
type: "defined",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
orderBy: [
|
orderBy: [
|
||||||
{ id: "memberId", depth: 0, table: "member", column: "lastname", order: "ASC" },
|
{ column: "lastname", order: "ASC" },
|
||||||
{ id: "memberId", depth: 0, table: "member", column: "firstname", order: "ASC" },
|
{ column: "firstname", order: "ASC" },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,20 +20,9 @@ export abstract class FileSystemHelper {
|
||||||
return readFileSync(this.formatPath(...filePath), "base64");
|
return readFileSync(this.formatPath(...filePath), "base64");
|
||||||
}
|
}
|
||||||
|
|
||||||
static readRootFile(filePath: string) {
|
|
||||||
return readFileSync(this.normalizePath(process.cwd(), filePath), "utf8");
|
|
||||||
}
|
|
||||||
|
|
||||||
static readTemplateFile(filePath: string) {
|
static readTemplateFile(filePath: string) {
|
||||||
return readFileSync(this.normalizePath(process.cwd(), "src", "templates", filePath), "utf8");
|
this.createFolder(filePath);
|
||||||
}
|
return readFileSync(process.cwd() + filePath, "utf8");
|
||||||
|
|
||||||
static readAssetFile(filePath: string, returnPath: boolean = false) {
|
|
||||||
let path = this.normalizePath(process.cwd(), "src", "assets", filePath);
|
|
||||||
if (returnPath) {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
return readFileSync(path, "utf8");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static writeFile(filePath: string, filename: string, file: any) {
|
static writeFile(filePath: string, filename: string, file: any) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { JWTData, JWTToken } from "../type/jwtTypes";
|
import { JWTData, JWTToken } from "../type/jwtTypes";
|
||||||
|
import { JWT_SECRET, JWT_EXPIRATION } from "../env.defaults";
|
||||||
import InternalException from "../exceptions/internalException";
|
import InternalException from "../exceptions/internalException";
|
||||||
import RolePermissionService from "../service/management/rolePermissionService";
|
import RolePermissionService from "../service/management/rolePermissionService";
|
||||||
import UserPermissionService from "../service/management/userPermissionService";
|
import UserPermissionService from "../service/management/userPermissionService";
|
||||||
|
@ -8,13 +9,11 @@ import PermissionHelper from "./permissionHelper";
|
||||||
import WebapiService from "../service/management/webapiService";
|
import WebapiService from "../service/management/webapiService";
|
||||||
import WebapiPermissionService from "../service/management/webapiPermissionService";
|
import WebapiPermissionService from "../service/management/webapiPermissionService";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import SettingHelper from "./settingsHelper";
|
|
||||||
import { APPLICATION_SECRET } from "../env.defaults";
|
|
||||||
|
|
||||||
export abstract class JWTHelper {
|
export abstract class JWTHelper {
|
||||||
static validate(token: string): Promise<string | jwt.JwtPayload> {
|
static validate(token: string): Promise<string | jwt.JwtPayload> {
|
||||||
return new Promise<string | jwt.JwtPayload>((resolve, reject) => {
|
return new Promise<string | jwt.JwtPayload>((resolve, reject) => {
|
||||||
jwt.verify(token, APPLICATION_SECRET, (err, decoded) => {
|
jwt.verify(token, JWT_SECRET, (err, decoded) => {
|
||||||
if (err) reject(err.message);
|
if (err) reject(err.message);
|
||||||
else resolve(decoded);
|
else resolve(decoded);
|
||||||
});
|
});
|
||||||
|
@ -28,11 +27,9 @@ export abstract class JWTHelper {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
jwt.sign(
|
jwt.sign(
|
||||||
data,
|
data,
|
||||||
APPLICATION_SECRET,
|
JWT_SECRET,
|
||||||
{
|
{
|
||||||
...(useExpiration ?? true
|
...(useExpiration ?? true ? { expiresIn: expOverwrite ?? JWT_EXPIRATION } : {}),
|
||||||
? { expiresIn: expOverwrite ?? (SettingHelper.getSetting("session.jwt_expiration") as ms.StringValue) }
|
|
||||||
: {}),
|
|
||||||
},
|
},
|
||||||
(err, token) => {
|
(err, token) => {
|
||||||
if (err) reject(err.message);
|
if (err) reject(err.message);
|
||||||
|
@ -61,10 +58,7 @@ export abstract class JWTHelper {
|
||||||
let rolePermissions =
|
let rolePermissions =
|
||||||
userRoles.length != 0 ? await RolePermissionService.getByRoles(userRoles.map((e) => e.id)) : [];
|
userRoles.length != 0 ? await RolePermissionService.getByRoles(userRoles.map((e) => e.id)) : [];
|
||||||
let rolePermissionStrings = rolePermissions.map((e) => e.permission);
|
let rolePermissionStrings = rolePermissions.map((e) => e.permission);
|
||||||
let permissionObject = PermissionHelper.convertToObject(
|
let permissionObject = PermissionHelper.convertToObject([...userPermissionStrings, ...rolePermissionStrings]);
|
||||||
[...userPermissionStrings, ...rolePermissionStrings],
|
|
||||||
isOwner
|
|
||||||
);
|
|
||||||
|
|
||||||
let jwtData: JWTToken = {
|
let jwtData: JWTToken = {
|
||||||
userId: id,
|
userId: id,
|
||||||
|
@ -103,8 +97,7 @@ export abstract class JWTHelper {
|
||||||
};
|
};
|
||||||
|
|
||||||
let overwriteExpiration =
|
let overwriteExpiration =
|
||||||
ms(SettingHelper.getSetting("session.jwt_expiration") as ms.StringValue) <
|
ms(JWT_EXPIRATION) < new Date().getTime() - new Date(expiration).getTime()
|
||||||
new Date().getTime() - new Date(expiration).getTime()
|
|
||||||
? null
|
? null
|
||||||
: Date.now() - new Date(expiration).getTime();
|
: Date.now() - new Date(expiration).getTime();
|
||||||
|
|
||||||
|
|
|
@ -1,78 +1,17 @@
|
||||||
import { Transporter, createTransport, TransportOptions } from "nodemailer";
|
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 { Attachment } from "nodemailer/lib/mailer";
|
||||||
import SettingHelper from "./settingsHelper";
|
|
||||||
import validator from "validator";
|
|
||||||
|
|
||||||
export default abstract class MailHelper {
|
export default abstract class MailHelper {
|
||||||
private static transporter: Transporter;
|
private static readonly transporter: Transporter = createTransport({
|
||||||
|
host: MAIL_HOST,
|
||||||
static createTransport() {
|
port: MAIL_PORT,
|
||||||
this.transporter?.close();
|
secure: (MAIL_SECURE as "true" | "false") == "true",
|
||||||
|
auth: {
|
||||||
this.transporter = createTransport({
|
user: MAIL_USERNAME,
|
||||||
host: SettingHelper.getSetting("mail.host"),
|
pass: MAIL_PASSWORD,
|
||||||
port: SettingHelper.getSetting("mail.port"),
|
},
|
||||||
secure: SettingHelper.getSetting("mail.secure"),
|
} as TransportOptions);
|
||||||
auth: {
|
|
||||||
user: SettingHelper.getSetting("mail.username"),
|
|
||||||
pass: SettingHelper.getSetting("mail.password"),
|
|
||||||
},
|
|
||||||
} as TransportOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async verifyTransport({
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
secure,
|
|
||||||
user,
|
|
||||||
password,
|
|
||||||
}: {
|
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
secure: boolean;
|
|
||||||
user: string;
|
|
||||||
password: string;
|
|
||||||
}): Promise<boolean> {
|
|
||||||
let transport = createTransport({
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
secure,
|
|
||||||
auth: { user, pass: password },
|
|
||||||
});
|
|
||||||
|
|
||||||
return await transport
|
|
||||||
.verify()
|
|
||||||
.then(() => {
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
try {
|
|
||||||
transport?.close();
|
|
||||||
} catch (error) {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static async checkMail(mail: string): Promise<boolean> {
|
|
||||||
return validator.isEmail(mail);
|
|
||||||
// return await emailCheck(mail)
|
|
||||||
// .then((res) => {
|
|
||||||
// return res;
|
|
||||||
// })
|
|
||||||
// .catch((err) => {
|
|
||||||
// return false;
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
static initialize() {
|
|
||||||
SettingHelper.onSettingTopicChanged("mail", () => {
|
|
||||||
this.createTransport();
|
|
||||||
});
|
|
||||||
this.createTransport();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description send mail
|
* @description send mail
|
||||||
|
@ -90,7 +29,7 @@ export default abstract class MailHelper {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.transporter
|
this.transporter
|
||||||
.sendMail({
|
.sendMail({
|
||||||
from: `"${SettingHelper.getSetting("club.name")}" <${SettingHelper.getSetting("mail.email")}>`,
|
from: `"${CLUB_NAME}" <${MAIL_USERNAME}>`,
|
||||||
to: target,
|
to: target,
|
||||||
subject,
|
subject,
|
||||||
text: content,
|
text: content,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import Mail from "nodemailer/lib/mailer";
|
||||||
import { member } from "../entity/club/member/member";
|
import { member } from "../entity/club/member/member";
|
||||||
import { newsletter } from "../entity/club/newsletter/newsletter";
|
import { newsletter } from "../entity/club/newsletter/newsletter";
|
||||||
import { newsletterDates } from "../entity/club/newsletter/newsletterDates";
|
import { newsletterDates } from "../entity/club/newsletter/newsletterDates";
|
||||||
|
@ -10,13 +11,13 @@ import { CalendarHelper } from "./calendarHelper";
|
||||||
import DynamicQueryBuilder from "./dynamicQueryBuilder";
|
import DynamicQueryBuilder from "./dynamicQueryBuilder";
|
||||||
import { FileSystemHelper } from "./fileSystemHelper";
|
import { FileSystemHelper } from "./fileSystemHelper";
|
||||||
import MailHelper from "./mailHelper";
|
import MailHelper from "./mailHelper";
|
||||||
|
import { CLUB_NAME } from "../env.defaults";
|
||||||
import { TemplateHelper } from "./templateHelper";
|
import { TemplateHelper } from "./templateHelper";
|
||||||
import { PdfExport } from "./pdfExport";
|
import { PdfExport } from "./pdfExport";
|
||||||
import NewsletterConfigService from "../service/configuration/newsletterConfigService";
|
import NewsletterConfigService from "../service/configuration/newsletterConfigService";
|
||||||
import { NewsletterConfigEnum } from "../enums/newsletterConfigEnum";
|
import { NewsletterConfigType } from "../enums/newsletterConfigType";
|
||||||
import InternalException from "../exceptions/internalException";
|
import InternalException from "../exceptions/internalException";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import SettingHelper from "./settingsHelper";
|
|
||||||
|
|
||||||
export interface NewsletterEventType {
|
export interface NewsletterEventType {
|
||||||
kind: "pdf" | "mail";
|
kind: "pdf" | "mail";
|
||||||
|
@ -61,41 +62,39 @@ export abstract class NewsletterHelper {
|
||||||
newsletterTitle: newsletter.newsletterTitle,
|
newsletterTitle: newsletter.newsletterTitle,
|
||||||
newsletterText: newsletter.newsletterText,
|
newsletterText: newsletter.newsletterText,
|
||||||
newsletterSignatur: newsletter.newsletterSignatur,
|
newsletterSignatur: newsletter.newsletterSignatur,
|
||||||
dates: dates
|
dates: dates.map((d) => ({
|
||||||
.map((d) => ({
|
title: d.diffTitle ?? d.calendar.title,
|
||||||
title: d.diffTitle || d.calendar.title,
|
content: d.diffDescription ?? d.calendar.content,
|
||||||
content: d.diffDescription || d.calendar.content,
|
starttime: d.calendar.starttime,
|
||||||
starttime: d.calendar.starttime,
|
formattedStarttime: new Date(d.calendar.starttime).toLocaleDateString("de-DE", {
|
||||||
formattedStarttime: new Date(d.calendar.starttime).toLocaleDateString("de-DE", {
|
weekday: "long",
|
||||||
weekday: "long",
|
day: "2-digit",
|
||||||
day: "2-digit",
|
month: "long",
|
||||||
month: "long",
|
}),
|
||||||
}),
|
formattedFullStarttime: new Date(d.calendar.starttime).toLocaleDateString("de-DE", {
|
||||||
formattedFullStarttime: new Date(d.calendar.starttime).toLocaleDateString("de-DE", {
|
weekday: "long",
|
||||||
weekday: "long",
|
day: "2-digit",
|
||||||
day: "2-digit",
|
month: "long",
|
||||||
month: "long",
|
year: "numeric",
|
||||||
year: "numeric",
|
hour: "2-digit",
|
||||||
hour: "2-digit",
|
minute: "2-digit",
|
||||||
minute: "2-digit",
|
}),
|
||||||
}),
|
endtime: d.calendar.endtime,
|
||||||
endtime: d.calendar.endtime,
|
formattedEndtime: new Date(d.calendar.endtime).toLocaleDateString("de-DE", {
|
||||||
formattedEndtime: new Date(d.calendar.endtime).toLocaleDateString("de-DE", {
|
weekday: "long",
|
||||||
weekday: "long",
|
day: "2-digit",
|
||||||
day: "2-digit",
|
month: "long",
|
||||||
month: "long",
|
}),
|
||||||
}),
|
formattedFullEndtime: new Date(d.calendar.endtime).toLocaleDateString("de-DE", {
|
||||||
formattedFullEndtime: new Date(d.calendar.endtime).toLocaleDateString("de-DE", {
|
weekday: "long",
|
||||||
weekday: "long",
|
day: "2-digit",
|
||||||
day: "2-digit",
|
month: "long",
|
||||||
month: "long",
|
year: "numeric",
|
||||||
year: "numeric",
|
hour: "2-digit",
|
||||||
hour: "2-digit",
|
minute: "2-digit",
|
||||||
minute: "2-digit",
|
}),
|
||||||
}),
|
location: d.calendar.location,
|
||||||
location: d.calendar.location,
|
})),
|
||||||
}))
|
|
||||||
.sort((a, b) => a.starttime.getTime() - b.starttime.getTime()),
|
|
||||||
...(recipient
|
...(recipient
|
||||||
? {
|
? {
|
||||||
recipient: {
|
recipient: {
|
||||||
|
@ -105,9 +104,9 @@ export abstract class NewsletterHelper {
|
||||||
nameaffix: recipient.nameaffix,
|
nameaffix: recipient.nameaffix,
|
||||||
...(showAdress
|
...(showAdress
|
||||||
? {
|
? {
|
||||||
street: recipient.sendNewsletter.street || "",
|
street: recipient.sendNewsletter.street ?? "",
|
||||||
streetNumber: recipient.sendNewsletter.streetNumber || "",
|
streetNumber: recipient.sendNewsletter.streetNumber ?? "",
|
||||||
streetNumberAdd: recipient.sendNewsletter.streetNumberAddition || "",
|
streetNumberAdd: recipient.sendNewsletter.streetNumberAddition ?? "",
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
},
|
},
|
||||||
|
@ -141,77 +140,42 @@ export abstract class NewsletterHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queryMemberIds.length == 0) {
|
let members = await MemberService.getAll({ noLimit: true, ids: queryMemberIds });
|
||||||
return [];
|
|
||||||
} else {
|
|
||||||
let members = await MemberService.getAll({ noLimit: true, ids: queryMemberIds });
|
|
||||||
return members[0].filter((m) => m.sendNewsletter != null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async getMailRecipients(newsletterId: number) {
|
return members[0];
|
||||||
let newsletter = await NewsletterService.getById(newsletterId);
|
|
||||||
let recipients = await NewsletterRecipientsService.getAll(newsletterId);
|
|
||||||
let config = await NewsletterConfigService.getAll();
|
|
||||||
|
|
||||||
let allowedForMail = config.filter((c) => c.config == NewsletterConfigEnum.mail).map((c) => c.comTypeId);
|
|
||||||
|
|
||||||
const members = await this.transformRecipientsToMembers(newsletter, recipients);
|
|
||||||
const mailRecipients = members.filter(
|
|
||||||
(m) => m.sendNewsletter?.email != "" && allowedForMail.includes(m.sendNewsletter?.type?.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
return mailRecipients;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async getPrintRecipients(newsletterId: number) {
|
|
||||||
let newsletter = await NewsletterService.getById(newsletterId);
|
|
||||||
let recipients = await NewsletterRecipientsService.getAll(newsletterId);
|
|
||||||
let config = await NewsletterConfigService.getAll();
|
|
||||||
|
|
||||||
let notAllowedForPdf = config
|
|
||||||
.filter((c) => c.config == NewsletterConfigEnum.none || c.config == NewsletterConfigEnum.mail)
|
|
||||||
.map((c) => c.comTypeId);
|
|
||||||
|
|
||||||
const members = await this.transformRecipientsToMembers(newsletter, recipients);
|
|
||||||
const pdfRecipients = members.filter((m) => !notAllowedForPdf.includes(m.sendNewsletter?.type?.id));
|
|
||||||
|
|
||||||
pdfRecipients.unshift({
|
|
||||||
id: "0",
|
|
||||||
firstname: "Alle Mitglieder",
|
|
||||||
lastname: SettingHelper.getSetting("club.name"),
|
|
||||||
nameaffix: "",
|
|
||||||
salutation: { salutation: "" },
|
|
||||||
} as member);
|
|
||||||
|
|
||||||
return pdfRecipients;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getICSFilePath(newsletter: newsletter) {
|
public static getICSFilePath(newsletter: newsletter) {
|
||||||
return FileSystemHelper.formatPath(
|
return FileSystemHelper.formatPath(
|
||||||
"newsletter",
|
"newsletter",
|
||||||
`${newsletter.id}_${newsletter.title.replaceAll(" ", "")}`,
|
`${newsletter.id}_${newsletter.title.replace(" ", "")}`,
|
||||||
`events.ics`
|
`events.ics`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static saveIcsToFile(newsletter: newsletter, ics: string) {
|
public static saveIcsToFile(newsletter: newsletter, ics: string) {
|
||||||
FileSystemHelper.writeFile(
|
FileSystemHelper.writeFile(`newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`, "events.ics", ics);
|
||||||
`newsletter/${newsletter.id}_${newsletter.title.replaceAll(" ", "")}`,
|
|
||||||
"events.ics",
|
|
||||||
ics
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async sendMails(newsletterId: number) {
|
public static async sendMails(newsletterId: number) {
|
||||||
let newsletter = await NewsletterService.getById(newsletterId);
|
let newsletter = await NewsletterService.getById(newsletterId);
|
||||||
let dates = await NewsletterDatesService.getAll(newsletterId);
|
let dates = await NewsletterDatesService.getAll(newsletterId);
|
||||||
|
let recipients = await NewsletterRecipientsService.getAll(newsletterId);
|
||||||
|
let config = await NewsletterConfigService.getAll();
|
||||||
|
|
||||||
const { value, error } = CalendarHelper.buildICS(dates.map((r) => r.calendar));
|
const { value, error } = CalendarHelper.buildICS(dates.map((r) => r.calendar));
|
||||||
if (error) throw new InternalException("Failed Building ICS form Mail", error);
|
if (error) throw new InternalException("Failed Building ICS form Mail", error);
|
||||||
this.saveIcsToFile(newsletter, value);
|
this.saveIcsToFile(newsletter, value);
|
||||||
|
|
||||||
const mailRecipients = await this.getMailRecipients(newsletterId);
|
let allowedForMail = config.filter((c) => c.config == NewsletterConfigType.mail).map((c) => c.comTypeId);
|
||||||
|
|
||||||
|
const members = await this.transformRecipientsToMembers(newsletter, recipients);
|
||||||
|
const mailRecipients = members.filter(
|
||||||
|
(m) =>
|
||||||
|
m.sendNewsletter != null &&
|
||||||
|
m.sendNewsletter?.email != null &&
|
||||||
|
allowedForMail.includes(m.sendNewsletter?.type?.id)
|
||||||
|
);
|
||||||
|
|
||||||
this.formatJobEmit("progress", "mail", "info", newsletterId, mailRecipients.length, 0, "starting sending");
|
this.formatJobEmit("progress", "mail", "info", newsletterId, mailRecipients.length, 0, "starting sending");
|
||||||
|
|
||||||
|
@ -221,14 +185,11 @@ export abstract class NewsletterHelper {
|
||||||
const { body } = await TemplateHelper.renderFileForModule({
|
const { body } = await TemplateHelper.renderFileForModule({
|
||||||
module: "newsletter",
|
module: "newsletter",
|
||||||
bodyData: data,
|
bodyData: data,
|
||||||
title: `Newsletter von ${SettingHelper.getSetting("club.name")}`,
|
title: `Newsletter von ${CLUB_NAME}`,
|
||||||
});
|
});
|
||||||
await MailHelper.sendMail(
|
await MailHelper.sendMail(rec.sendNewsletter.email, `Newsletter von ${CLUB_NAME}`, body, [
|
||||||
rec.sendNewsletter.email,
|
{ filename: "events.ics", path: this.getICSFilePath(newsletter) },
|
||||||
`Newsletter von ${SettingHelper.getSetting("club.name")}`,
|
])
|
||||||
body,
|
|
||||||
[{ filename: "events.ics", path: this.getICSFilePath(newsletter) }]
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.formatJobEmit(
|
this.formatJobEmit(
|
||||||
"progress",
|
"progress",
|
||||||
|
@ -267,10 +228,11 @@ export abstract class NewsletterHelper {
|
||||||
public static async printPdfs(newsletterId: number) {
|
public static async printPdfs(newsletterId: number) {
|
||||||
let newsletter = await NewsletterService.getById(newsletterId);
|
let newsletter = await NewsletterService.getById(newsletterId);
|
||||||
let dates = await NewsletterDatesService.getAll(newsletterId);
|
let dates = await NewsletterDatesService.getAll(newsletterId);
|
||||||
|
let recipients = await NewsletterRecipientsService.getAll(newsletterId);
|
||||||
let config = await NewsletterConfigService.getAll();
|
let config = await NewsletterConfigService.getAll();
|
||||||
|
|
||||||
FileSystemHelper.clearDirectoryByFiletype(
|
FileSystemHelper.clearDirectoryByFiletype(
|
||||||
`newsletter/${newsletter.id}_${newsletter.title.replaceAll(" ", "")}`,
|
`newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`,
|
||||||
".pdf"
|
".pdf"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -278,20 +240,33 @@ export abstract class NewsletterHelper {
|
||||||
if (error) throw new InternalException("Failed Building ICS form Pdf", error);
|
if (error) throw new InternalException("Failed Building ICS form Pdf", error);
|
||||||
this.saveIcsToFile(newsletter, value);
|
this.saveIcsToFile(newsletter, value);
|
||||||
|
|
||||||
let printWithAdress = config.filter((c) => c.config == NewsletterConfigEnum.pdf).map((c) => c.comTypeId);
|
let notAllowedForPdf = config.filter((c) => c.config == NewsletterConfigType.mail).map((c) => c.comTypeId);
|
||||||
|
let printWithAdress = config.filter((c) => c.config == NewsletterConfigType.pdf).map((c) => c.comTypeId);
|
||||||
|
|
||||||
const pdfRecipients = await this.getPrintRecipients(newsletterId);
|
const members = await this.transformRecipientsToMembers(newsletter, recipients);
|
||||||
|
const pdfRecipients = members.filter(
|
||||||
|
(m) => !notAllowedForPdf.includes(m.sendNewsletter?.type?.id) || m.sendNewsletter == null
|
||||||
|
);
|
||||||
|
|
||||||
this.formatJobEmit("progress", "pdf", "info", newsletterId, pdfRecipients.length, 0, "starting printing");
|
this.formatJobEmit("progress", "pdf", "info", newsletterId, pdfRecipients.length + 1, 0, "starting sending");
|
||||||
|
|
||||||
for (const [index, rec] of pdfRecipients.entries()) {
|
for (const [index, rec] of [
|
||||||
|
...pdfRecipients,
|
||||||
|
{
|
||||||
|
id: "0",
|
||||||
|
firstname: "Alle Mitglieder",
|
||||||
|
lastname: CLUB_NAME,
|
||||||
|
nameaffix: "",
|
||||||
|
salutation: { salutation: "" },
|
||||||
|
} as member,
|
||||||
|
].entries()) {
|
||||||
let data = this.buildData(newsletter, dates, rec, printWithAdress.includes(rec.sendNewsletter?.type?.id));
|
let data = this.buildData(newsletter, dates, rec, printWithAdress.includes(rec.sendNewsletter?.type?.id));
|
||||||
|
|
||||||
await PdfExport.renderFile({
|
await PdfExport.renderFile({
|
||||||
template: "newsletter",
|
template: "newsletter",
|
||||||
title: `Newsletter von ${SettingHelper.getSetting("club.name")}`,
|
title: `Newsletter von ${CLUB_NAME}`,
|
||||||
filename: `${rec.lastname}_${rec.firstname}_${rec.id}`.replaceAll(" ", "-"),
|
filename: `${rec.lastname}_${rec.firstname}_${rec.id}`,
|
||||||
folder: `newsletter/${newsletter.id}_${newsletter.title.replaceAll(" ", "")}`,
|
folder: `newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`,
|
||||||
data: data,
|
data: data,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -319,9 +294,9 @@ export abstract class NewsletterHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
await PdfExport.sqashToSingleFile(
|
await PdfExport.sqashToSingleFile(
|
||||||
`newsletter/${newsletter.id}_${newsletter.title.replaceAll(" ", "")}`,
|
`newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`,
|
||||||
"allPdfsTogether",
|
"allPdfsTogether",
|
||||||
`newsletter/${newsletter.id}_${newsletter.title.replaceAll(" ", "")}`
|
`newsletter/${newsletter.id}_${newsletter.title.replace(" ", "")}`
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.formatJobEmit(
|
this.formatJobEmit(
|
||||||
|
|
|
@ -1,47 +1,10 @@
|
||||||
import puppeteer, { Browser } from "puppeteer";
|
import puppeteer from "puppeteer";
|
||||||
import { TemplateHelper } from "./templateHelper";
|
import { TemplateHelper } from "./templateHelper";
|
||||||
import { PermissionModule } from "../type/permissionTypes";
|
import { PermissionModule } from "../type/permissionTypes";
|
||||||
import { FileSystemHelper } from "./fileSystemHelper";
|
import { FileSystemHelper } from "./fileSystemHelper";
|
||||||
import { PDFDocument } from "pdf-lib";
|
import { PDFDocument } from "pdf-lib";
|
||||||
import { StringHelper } from "./stringHelper";
|
|
||||||
|
|
||||||
export abstract class PdfExport {
|
export abstract class PdfExport {
|
||||||
private static browserInstance: undefined | Browser = undefined;
|
|
||||||
private static timeout: undefined | NodeJS.Timeout = undefined;
|
|
||||||
private static printing = new Map<string, string>();
|
|
||||||
|
|
||||||
private static async renderTemplate(
|
|
||||||
template: `${PermissionModule}` | `${PermissionModule}.${string}`,
|
|
||||||
title: string,
|
|
||||||
data: any,
|
|
||||||
customTemplate?: {
|
|
||||||
headerId?: number;
|
|
||||||
footerId?: number;
|
|
||||||
bodyId?: string | number;
|
|
||||||
headerHeight: number;
|
|
||||||
footerHeight: number;
|
|
||||||
}
|
|
||||||
): Promise<{ header: string; footer: string; body: string; headerMargin?: number; footerMargin?: number }> {
|
|
||||||
if (!customTemplate) {
|
|
||||||
return await TemplateHelper.renderFileForModule({
|
|
||||||
module: template,
|
|
||||||
headerData: data,
|
|
||||||
bodyData: data,
|
|
||||||
footerData: data,
|
|
||||||
title: title,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return await TemplateHelper.renderFileForCustom({
|
|
||||||
module: template,
|
|
||||||
customTemplate,
|
|
||||||
headerData: data,
|
|
||||||
bodyData: data,
|
|
||||||
footerData: data,
|
|
||||||
title: title,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async renderFile({
|
static async renderFile({
|
||||||
template,
|
template,
|
||||||
title = "pdf-export FF Admin",
|
title = "pdf-export FF Admin",
|
||||||
|
@ -65,25 +28,33 @@ export abstract class PdfExport {
|
||||||
footerHeight: number;
|
footerHeight: number;
|
||||||
};
|
};
|
||||||
}) {
|
}) {
|
||||||
try {
|
|
||||||
clearTimeout(this.timeout);
|
|
||||||
} catch (err) {}
|
|
||||||
let id = StringHelper.random(32);
|
|
||||||
this.printing.set(id, "printing");
|
|
||||||
|
|
||||||
if (folder != "") FileSystemHelper.createFolder(folder);
|
if (folder != "") FileSystemHelper.createFolder(folder);
|
||||||
|
|
||||||
const renderedTemplate = await this.renderTemplate(template, title, data, customTemplate);
|
let header: string, footer: string, body: string, headerMargin: number, footerMargin: number;
|
||||||
let { header, footer, body, headerMargin, footerMargin } = renderedTemplate;
|
if (!customTemplate) {
|
||||||
|
({ header, footer, body, headerMargin, footerMargin } = await TemplateHelper.renderFileForModule({
|
||||||
if (!this.browserInstance || !this.browserInstance.connected) {
|
module: template,
|
||||||
this.browserInstance = await puppeteer.launch({
|
headerData: data,
|
||||||
headless: true,
|
bodyData: data,
|
||||||
args: ["--no-sandbox", "--disable-gpu", "--disable-setuid-sandbox"],
|
footerData: data,
|
||||||
});
|
title: title,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
({ header, footer, body, headerMargin, footerMargin } = await TemplateHelper.renderFileForCustom({
|
||||||
|
module: template,
|
||||||
|
customTemplate,
|
||||||
|
headerData: data,
|
||||||
|
bodyData: data,
|
||||||
|
footerData: data,
|
||||||
|
title: title,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const page = await this.browserInstance.newPage();
|
const browser = await puppeteer.launch({
|
||||||
|
headless: true,
|
||||||
|
args: ["--no-sandbox", "--disable-gpu", "--disable-setuid-sandbox"],
|
||||||
|
});
|
||||||
|
const page = await browser.newPage();
|
||||||
await page.setContent(body, { waitUntil: "domcontentloaded" });
|
await page.setContent(body, { waitUntil: "domcontentloaded" });
|
||||||
|
|
||||||
const exportPath = FileSystemHelper.formatPath(folder, `${filename}.pdf`);
|
const exportPath = FileSystemHelper.formatPath(folder, `${filename}.pdf`);
|
||||||
|
@ -103,16 +74,7 @@ export abstract class PdfExport {
|
||||||
footerTemplate: footer,
|
footerTemplate: footer,
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.close();
|
await browser.close();
|
||||||
|
|
||||||
this.printing.delete(id);
|
|
||||||
|
|
||||||
if (this.printing.size == 0) {
|
|
||||||
this.timeout = setTimeout(() => {
|
|
||||||
this.browserInstance.close();
|
|
||||||
this.browserInstance = undefined;
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pdf;
|
return pdf;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,9 @@ import {
|
||||||
permissionModules,
|
permissionModules,
|
||||||
PermissionObject,
|
PermissionObject,
|
||||||
PermissionSection,
|
PermissionSection,
|
||||||
permissionSections,
|
|
||||||
PermissionString,
|
PermissionString,
|
||||||
PermissionType,
|
PermissionType,
|
||||||
permissionTypes,
|
permissionTypes,
|
||||||
sectionsAndModules,
|
|
||||||
} from "../type/permissionTypes";
|
} from "../type/permissionTypes";
|
||||||
import ForbiddenRequestException from "../exceptions/forbiddenRequestException";
|
import ForbiddenRequestException from "../exceptions/forbiddenRequestException";
|
||||||
|
|
||||||
|
@ -17,31 +15,33 @@ export default class PermissionHelper {
|
||||||
permissions: PermissionObject,
|
permissions: PermissionObject,
|
||||||
type: PermissionType | "admin",
|
type: PermissionType | "admin",
|
||||||
section: PermissionSection,
|
section: PermissionSection,
|
||||||
module: PermissionModule
|
module?: PermissionModule
|
||||||
) {
|
) {
|
||||||
if (type == "admin") return permissions?.admin ?? permissions?.adminByOwner ?? false;
|
if (type == "admin") return permissions?.admin ?? false;
|
||||||
if (permissions?.admin || permissions?.adminByOwner) return true;
|
if (permissions?.admin) return true;
|
||||||
if (
|
if (
|
||||||
|
(!module &&
|
||||||
|
permissions[section] != undefined &&
|
||||||
|
(permissions[section]?.all == "*" || permissions[section]?.all?.includes(type))) ||
|
||||||
permissions[section]?.all == "*" ||
|
permissions[section]?.all == "*" ||
|
||||||
permissions[section]?.all?.includes(type) ||
|
permissions[section]?.all?.includes(type)
|
||||||
permissions[section]?.[module] == "*" ||
|
|
||||||
permissions[section]?.[module]?.includes(type)
|
|
||||||
)
|
)
|
||||||
return true;
|
return true;
|
||||||
|
if (module && (permissions[section]?.[module] == "*" || permissions[section]?.[module]?.includes(type)))
|
||||||
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static canSome(
|
static canSome(
|
||||||
permissions: PermissionObject,
|
permissions: PermissionObject,
|
||||||
checks: Array<{
|
checks: Array<{
|
||||||
requiredPermission: PermissionType | "admin";
|
requiredPermissions: PermissionType | "admin";
|
||||||
section: PermissionSection;
|
section: PermissionSection;
|
||||||
module: PermissionModule;
|
module?: PermissionModule;
|
||||||
}>
|
}>
|
||||||
) {
|
) {
|
||||||
return checks.reduce<boolean>((prev, curr) => {
|
checks.reduce<boolean>((prev, curr) => {
|
||||||
return prev || this.can(permissions, curr.requiredPermission, curr.section, curr.module);
|
return prev || this.can(permissions, curr.requiredPermissions, curr.section, curr.module);
|
||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,8 +50,8 @@ export default class PermissionHelper {
|
||||||
type: PermissionType | "admin",
|
type: PermissionType | "admin",
|
||||||
section: PermissionSection
|
section: PermissionSection
|
||||||
): boolean {
|
): boolean {
|
||||||
if (type == "admin") return permissions?.admin ?? permissions?.adminByOwner ?? false;
|
if (type == "admin") return permissions?.admin ?? false;
|
||||||
if (permissions?.admin || permissions?.adminByOwner) return true;
|
if (permissions?.admin) return true;
|
||||||
if (
|
if (
|
||||||
permissions[section]?.all == "*" ||
|
permissions[section]?.all == "*" ||
|
||||||
permissions[section]?.all?.includes(type) ||
|
permissions[section]?.all?.includes(type) ||
|
||||||
|
@ -64,24 +64,19 @@ export default class PermissionHelper {
|
||||||
static canSomeSection(
|
static canSomeSection(
|
||||||
permissions: PermissionObject,
|
permissions: PermissionObject,
|
||||||
checks: Array<{
|
checks: Array<{
|
||||||
requiredPermission: PermissionType | "admin";
|
requiredPermissions: PermissionType | "admin";
|
||||||
section: PermissionSection;
|
section: PermissionSection;
|
||||||
}>
|
}>
|
||||||
): boolean {
|
): boolean {
|
||||||
return checks.reduce<boolean>((prev, curr) => {
|
return checks.reduce<boolean>((prev, curr) => {
|
||||||
return prev || this.canSection(permissions, curr.requiredPermission, curr.section);
|
return prev || this.can(permissions, curr.requiredPermissions, curr.section);
|
||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static canValue(permissions: PermissionObject, key: string, emptyIfAdmin: boolean = false): string {
|
|
||||||
if (emptyIfAdmin && (permissions.admin || permissions.adminByOwner)) return "";
|
|
||||||
return permissions?.additional?.[key] ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
static passCheckMiddleware(
|
static passCheckMiddleware(
|
||||||
requiredPermissions: PermissionType | "admin",
|
requiredPermissions: PermissionType | "admin",
|
||||||
section: PermissionSection,
|
section: PermissionSection,
|
||||||
module: PermissionModule
|
module?: PermissionModule
|
||||||
): (req: Request, res: Response, next: Function) => void {
|
): (req: Request, res: Response, next: Function) => void {
|
||||||
return (req: Request, res: Response, next: Function) => {
|
return (req: Request, res: Response, next: Function) => {
|
||||||
const permissions = req.permissions;
|
const permissions = req.permissions;
|
||||||
|
@ -97,9 +92,9 @@ export default class PermissionHelper {
|
||||||
|
|
||||||
static passCheckSomeMiddleware(
|
static passCheckSomeMiddleware(
|
||||||
checks: Array<{
|
checks: Array<{
|
||||||
requiredPermission: PermissionType | "admin";
|
requiredPermissions: PermissionType | "admin";
|
||||||
section: PermissionSection;
|
section: PermissionSection;
|
||||||
module: PermissionModule;
|
module?: PermissionModule;
|
||||||
}>
|
}>
|
||||||
): (req: Request, res: Response, next: Function) => void {
|
): (req: Request, res: Response, next: Function) => void {
|
||||||
return (req: Request, res: Response, next: Function) => {
|
return (req: Request, res: Response, next: Function) => {
|
||||||
|
@ -109,7 +104,9 @@ export default class PermissionHelper {
|
||||||
if (isOwner || this.canSome(permissions, checks)) {
|
if (isOwner || this.canSome(permissions, checks)) {
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
let permissionsToPass = checks.map((c) => `${c.section}.${c.module}.${c.requiredPermission}`).join(" or ");
|
let permissionsToPass = checks.reduce<string>((prev, curr) => {
|
||||||
|
return prev + (prev != " or " ? "" : "") + `${curr.section}.${curr.module}.${curr.requiredPermissions}`;
|
||||||
|
}, "");
|
||||||
throw new ForbiddenRequestException(`missing permission for ${permissionsToPass}`);
|
throw new ForbiddenRequestException(`missing permission for ${permissionsToPass}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -132,7 +129,7 @@ export default class PermissionHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
static sectionPassCheckSomeMiddleware(
|
static sectionPassCheckSomeMiddleware(
|
||||||
checks: Array<{ requiredPermission: PermissionType | "admin"; section: PermissionSection }>
|
checks: Array<{ requiredPermissions: PermissionType | "admin"; section: PermissionSection }>
|
||||||
): (req: Request, res: Response, next: Function) => void {
|
): (req: Request, res: Response, next: Function) => void {
|
||||||
return (req: Request, res: Response, next: Function) => {
|
return (req: Request, res: Response, next: Function) => {
|
||||||
const permissions = req.permissions;
|
const permissions = req.permissions;
|
||||||
|
@ -141,7 +138,9 @@ export default class PermissionHelper {
|
||||||
if (isOwner || this.canSomeSection(permissions, checks)) {
|
if (isOwner || this.canSomeSection(permissions, checks)) {
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
let permissionsToPass = checks.map((c) => `${c.section}.${c.requiredPermission}`).join(" or ");
|
let permissionsToPass = checks.reduce<string>((prev, curr) => {
|
||||||
|
return prev + (prev != " or " ? "" : "") + `${curr.section}.${curr.requiredPermissions}`;
|
||||||
|
}, "");
|
||||||
throw new ForbiddenRequestException(`missing permission for ${permissionsToPass}`);
|
throw new ForbiddenRequestException(`missing permission for ${permissionsToPass}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -160,28 +159,14 @@ export default class PermissionHelper {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static convertToObject(permissions: Array<PermissionString>, isOwner: boolean = false): PermissionObject {
|
static convertToObject(permissions: Array<PermissionString>): PermissionObject {
|
||||||
let isAdmin = permissions.includes("*");
|
if (permissions.includes("*")) {
|
||||||
|
|
||||||
let additional: { [key: string]: string } = {};
|
|
||||||
let additionalPermissions = permissions.map((e) => e.split(".")).filter((e) => e[0] == "additional") as Array<
|
|
||||||
["additional", string, string]
|
|
||||||
>;
|
|
||||||
for (let split of additionalPermissions) {
|
|
||||||
let module = sectionsAndModules.additional.find((a) => a.key == split[1]);
|
|
||||||
if (!isAdmin || (isAdmin && !module.emptyIfAdmin)) additional[split[1]] = split[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAdmin) {
|
|
||||||
return {
|
return {
|
||||||
admin: true,
|
admin: true,
|
||||||
adminByOwner: isOwner,
|
|
||||||
...(Object.keys(additional).length > 0 && { additional }),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let output: PermissionObject = {};
|
let output: PermissionObject = {};
|
||||||
let splitPermissions = permissions.map((e) => e.split(".")).filter((e) => e[0] != "additional") as Array<
|
let splitPermissions = permissions.map((e) => e.split(".")) as Array<
|
||||||
[PermissionSection, PermissionModule | PermissionType | "*", PermissionType | "*"]
|
[PermissionSection, PermissionModule | PermissionType | "*", PermissionType | "*"]
|
||||||
>;
|
>;
|
||||||
for (let split of splitPermissions) {
|
for (let split of splitPermissions) {
|
||||||
|
@ -223,31 +208,15 @@ export default class PermissionHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return output;
|
||||||
return {
|
|
||||||
adminByOwner: isOwner,
|
|
||||||
...output,
|
|
||||||
...(Object.keys(additional).length > 0 && { additional }),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static convertToStringArray(permissions: PermissionObject): Array<PermissionString> {
|
static convertToStringArray(permissions: PermissionObject): Array<PermissionString> {
|
||||||
let isAdmin = permissions?.admin;
|
if (permissions?.admin) {
|
||||||
|
return ["*"];
|
||||||
let additional: Array<PermissionString> = [];
|
|
||||||
let additionalPermissions = Object.entries(permissions?.additional ?? {});
|
|
||||||
for (let add of additionalPermissions) {
|
|
||||||
additional.push(`additional.${add[0]}.${add[1]}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAdmin) {
|
|
||||||
return ["*", ...additional];
|
|
||||||
}
|
|
||||||
|
|
||||||
let output: Array<PermissionString> = [];
|
let output: Array<PermissionString> = [];
|
||||||
let sections = Object.keys(permissions).filter((m: PermissionSection) =>
|
let sections = Object.keys(permissions) as Array<PermissionSection>;
|
||||||
permissionSections.includes(m)
|
|
||||||
) as Array<PermissionSection>;
|
|
||||||
for (let section of sections) {
|
for (let section of sections) {
|
||||||
if (permissions[section].all) {
|
if (permissions[section].all) {
|
||||||
let types = permissions[section].all;
|
let types = permissions[section].all;
|
||||||
|
@ -273,8 +242,7 @@ export default class PermissionHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return output;
|
||||||
return [...output, ...additional];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static getWhatToAdd(before: Array<PermissionString>, after: Array<PermissionString>): Array<PermissionString> {
|
static getWhatToAdd(before: Array<PermissionString>, after: Array<PermissionString>): Array<PermissionString> {
|
||||||
|
|
|
@ -1,288 +0,0 @@
|
||||||
import { SettingString, settingsType, SettingTopic, SettingTypeAtom, SettingValueMapping } from "../type/settingTypes";
|
|
||||||
import { CodingHelper } from "./codingHelper";
|
|
||||||
import SettingCommandHandler from "../command/management/setting/settingCommandHandler";
|
|
||||||
import SettingService from "../service/management/settingService";
|
|
||||||
import { APPLICATION_SECRET } from "../env.defaults";
|
|
||||||
import {
|
|
||||||
BooleanConverter,
|
|
||||||
EmailConverter,
|
|
||||||
LongStringConverter,
|
|
||||||
MsConverter,
|
|
||||||
NumberConverter,
|
|
||||||
StringConverter,
|
|
||||||
TypeConverter,
|
|
||||||
UrlConverter,
|
|
||||||
} from "./convertHelper";
|
|
||||||
import cloneDeep from "lodash.clonedeep";
|
|
||||||
import { rejects } from "assert";
|
|
||||||
import InternalException from "../exceptions/internalException";
|
|
||||||
import MailHelper from "./mailHelper";
|
|
||||||
|
|
||||||
export default abstract class SettingHelper {
|
|
||||||
private static settings: { [key in SettingString]?: string } = {};
|
|
||||||
|
|
||||||
private static listeners: Map<SettingString, Array<(newValue: any, oldValue: any) => void>> = new Map();
|
|
||||||
private static topicListeners: Map<SettingTopic, Array<() => void>> = new Map();
|
|
||||||
|
|
||||||
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(),
|
|
||||||
email: new EmailConverter(),
|
|
||||||
};
|
|
||||||
|
|
||||||
public static getAllSettings(): { [key in SettingString]: SettingValueMapping[key] } {
|
|
||||||
return Object.keys(settingsType).reduce((acc, key) => {
|
|
||||||
const typedKey = key as SettingString;
|
|
||||||
//@ts-expect-error
|
|
||||||
acc[typedKey] = this.getSetting(typedKey);
|
|
||||||
return acc;
|
|
||||||
}, {} as { [key in SettingString]: SettingValueMapping[key] });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)) {
|
|
||||||
return rawValue as unknown as SettingValueMapping[K];
|
|
||||||
}
|
|
||||||
|
|
||||||
let processedValue = rawValue;
|
|
||||||
if (typeof settingType.type === "string" && settingType.type.includes("/crypt")) {
|
|
||||||
processedValue = CodingHelper.decrypt(APPLICATION_SECRET, processedValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseType =
|
|
||||||
typeof settingType.type === "string"
|
|
||||||
? (settingType.type.split("/")[0] as SettingTypeAtom)
|
|
||||||
: (settingType.type as SettingTypeAtom);
|
|
||||||
|
|
||||||
return this.converters[baseType].fromString(processedValue) as unknown as SettingValueMapping[K];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a setting
|
|
||||||
* undefined value leads to reset of key
|
|
||||||
* @param key The key of the setting
|
|
||||||
* @param value The value to set
|
|
||||||
*/
|
|
||||||
public static async setSetting<K extends SettingString>(key: K, value: SettingValueMapping[K]): Promise<void> {
|
|
||||||
if (value === undefined || value === null) {
|
|
||||||
if (key != "mail.password") this.resetSetting(key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stringValue = String(value);
|
|
||||||
|
|
||||||
const settingType = settingsType[key];
|
|
||||||
this.validateSetting(key, stringValue);
|
|
||||||
|
|
||||||
const oldValue = cloneDeep(this.settings[key]);
|
|
||||||
let newValue = stringValue;
|
|
||||||
|
|
||||||
if (typeof settingType.type === "string" && settingType.type.includes("/crypt")) {
|
|
||||||
newValue = CodingHelper.encrypt(APPLICATION_SECRET, stringValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.settings[key] = stringValue;
|
|
||||||
const [topic, settingKey] = key.split(".") as [SettingTopic, string];
|
|
||||||
|
|
||||||
await SettingCommandHandler.create({
|
|
||||||
topic,
|
|
||||||
key: settingKey,
|
|
||||||
value: newValue,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.notifyListeners(key, newValue, oldValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets a setting to its default value
|
|
||||||
* @param key The key of the setting
|
|
||||||
*/
|
|
||||||
public static async resetSetting(key: SettingString): Promise<void> {
|
|
||||||
if (this.getSetting(key) == String(settingsType[key].default ?? "")) return;
|
|
||||||
|
|
||||||
const oldValue = this.getSetting(key);
|
|
||||||
|
|
||||||
const settingType = settingsType[key];
|
|
||||||
this.settings[key] = String(settingType.default ?? "");
|
|
||||||
|
|
||||||
const [topic, settingKey] = key.split(".") as [SettingTopic, string];
|
|
||||||
await SettingCommandHandler.delete({
|
|
||||||
topic,
|
|
||||||
key: settingKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
const newValue = this.getSetting(key);
|
|
||||||
this.notifyListeners(key, newValue, oldValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async configure(): Promise<void> {
|
|
||||||
console.log("Configuring Settings");
|
|
||||||
const settings = await SettingService.getSettings();
|
|
||||||
|
|
||||||
for (const element of settings) {
|
|
||||||
const ref = `${element.topic}.${element.key}` as SettingString;
|
|
||||||
this.settings[ref] = element.value;
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.validateSetting(ref);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`Invalid setting ${ref}: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async checkMail<K extends SettingString>(
|
|
||||||
setting: Array<{ key: K; value: SettingValueMapping[K] }>
|
|
||||||
): Promise<void> {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
if (setting.some((t) => t.key == "mail.email" && t.value != undefined)) {
|
|
||||||
let emailValue = setting.find((t) => t.key == "mail.email").value as string;
|
|
||||||
let checkMail = await MailHelper.checkMail(emailValue);
|
|
||||||
if (!checkMail) {
|
|
||||||
return reject("mail");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setting.some((t) => t.key.startsWith("mail"))) {
|
|
||||||
let checkConfig = await MailHelper.verifyTransport({
|
|
||||||
user:
|
|
||||||
(setting.find((t) => t.key == "mail.username").value as string) ??
|
|
||||||
SettingHelper.getSetting("mail.username"),
|
|
||||||
password:
|
|
||||||
(setting.find((t) => t.key == "mail.password").value as string) ??
|
|
||||||
SettingHelper.getSetting("mail.password"),
|
|
||||||
host: (setting.find((t) => t.key == "mail.host").value as string) ?? SettingHelper.getSetting("mail.host"),
|
|
||||||
port: (setting.find((t) => t.key == "mail.port").value as number) ?? SettingHelper.getSetting("mail.port"),
|
|
||||||
secure:
|
|
||||||
(setting.find((t) => t.key == "mail.secure").value as boolean) ?? SettingHelper.getSetting("mail.secure"),
|
|
||||||
});
|
|
||||||
if (!checkConfig) {
|
|
||||||
return reject("Config is not valid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a setting
|
|
||||||
* @param key The key of the setting
|
|
||||||
* @param value Optional value to validate
|
|
||||||
*/
|
|
||||||
private static validateSetting(key: SettingString, value?: string): void {
|
|
||||||
const settingType = settingsType[key];
|
|
||||||
const valueToCheck = value ?? this.settings[key] ?? String(settingType.default ?? "");
|
|
||||||
|
|
||||||
if (Array.isArray(settingType.type)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseType =
|
|
||||||
typeof settingType.type === "string"
|
|
||||||
? (settingType.type.split("/")[0] as SettingTypeAtom)
|
|
||||||
: (settingType.type as SettingTypeAtom);
|
|
||||||
|
|
||||||
if (!this.converters[baseType].validate(valueToCheck)) {
|
|
||||||
throw new Error(`Invalid value for ${key} of type ${baseType}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseType === "number" && settingType.min !== undefined) {
|
|
||||||
const numValue = Number(valueToCheck);
|
|
||||||
if (numValue < settingType.min) {
|
|
||||||
throw new Error(`${key} must be at least ${settingType.min}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,11 +9,9 @@ export abstract class TemplateHelper {
|
||||||
static getTemplateFromFile(template: string) {
|
static getTemplateFromFile(template: string) {
|
||||||
let tmpFile;
|
let tmpFile;
|
||||||
try {
|
try {
|
||||||
tmpFile = FileSystemHelper.readTemplateFile(`${template}.template.html`);
|
tmpFile = FileSystemHelper.readTemplateFile(`/src/templates/${template}.template.html`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
tmpFile = FileSystemHelper.readTemplateFile(
|
tmpFile = FileSystemHelper.readTemplateFile(`/src/templates/${template.split(".")[1]}.template.html`);
|
||||||
`${template.split(".")[template.split(".").length - 1]}.template.html`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return tmpFile;
|
return tmpFile;
|
||||||
}
|
}
|
||||||
|
@ -29,9 +27,9 @@ export abstract class TemplateHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
static normalizeTemplate(template: string): string {
|
static normalizeTemplate(template: string): string {
|
||||||
template = template.replaceAll(/<listend>.*?<\/listend>/g, "{{/each}}");
|
template = template.replace(/<listend>.*?<\/listend>/g, "{{/each}}");
|
||||||
template = template.replaceAll(/<liststart\b[^>]*>(WDH Start: )?/g, "{{#each ");
|
template = template.replace(/<liststart\b[^>]*>(WDH Start: )?/g, "{{#each ");
|
||||||
template = template.replaceAll(/<\/liststart>/g, "}}");
|
template = template.replace(/<\/liststart>/g, "}}");
|
||||||
|
|
||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
|
|
14
src/index.ts
14
src/index.ts
|
@ -2,7 +2,7 @@ import "dotenv/config";
|
||||||
import "./handlebars.config";
|
import "./handlebars.config";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
|
|
||||||
import { configCheck } from "./env.defaults";
|
import { BACKUP_AUTO_RESTORE, configCheck, SERVER_PORT } from "./env.defaults";
|
||||||
configCheck();
|
configCheck();
|
||||||
|
|
||||||
import { PermissionObject } from "./type/permissionTypes";
|
import { PermissionObject } from "./type/permissionTypes";
|
||||||
|
@ -21,29 +21,23 @@ declare global {
|
||||||
|
|
||||||
import { dataSource } from "./data-source";
|
import { dataSource } from "./data-source";
|
||||||
import BackupHelper from "./helpers/backupHelper";
|
import BackupHelper from "./helpers/backupHelper";
|
||||||
import SettingHelper from "./helpers/settingsHelper";
|
|
||||||
dataSource.initialize().then(async () => {
|
dataSource.initialize().then(async () => {
|
||||||
if (await dataSource.createQueryRunner().hasTable("user")) {
|
if ((BACKUP_AUTO_RESTORE as "true" | "false") == "true" && (await dataSource.createQueryRunner().hasTable("user"))) {
|
||||||
await BackupHelper.autoRestoreBackup().catch((err) => {
|
await BackupHelper.autoRestoreBackup().catch((err) => {
|
||||||
console.log(`${new Date().toISOString()}: failed auto-restoring database`, err);
|
console.log(`${new Date().toISOString()}: failed auto-restoring database`, err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await SettingHelper.configure();
|
|
||||||
MailHelper.initialize();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
import router from "./routes/index";
|
import router from "./routes/index";
|
||||||
router(app);
|
router(app);
|
||||||
app.listen(process.env.NODE_ENV ? process.env.SERVER_PORT ?? 5000 : 5000, () => {
|
app.listen(process.env.NODE_ENV ? SERVER_PORT : 5000, () => {
|
||||||
console.log(
|
console.log(`${new Date().toISOString()}: listening on port ${process.env.NODE_ENV ? SERVER_PORT : 5000}`);
|
||||||
`${new Date().toISOString()}: listening on port ${process.env.NODE_ENV ? process.env.SERVER_PORT ?? 5000 : 5000}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
import schedule from "node-schedule";
|
import schedule from "node-schedule";
|
||||||
import RefreshCommandHandler from "./command/refreshCommandHandler";
|
import RefreshCommandHandler from "./command/refreshCommandHandler";
|
||||||
import MailHelper from "./helpers/mailHelper";
|
|
||||||
const job = schedule.scheduleJob("0 0 * * *", async () => {
|
const job = schedule.scheduleJob("0 0 * * *", async () => {
|
||||||
console.log(`${new Date().toISOString()}: running Cron`);
|
console.log(`${new Date().toISOString()}: running Cron`);
|
||||||
await RefreshCommandHandler.deleteExpired();
|
await RefreshCommandHandler.deleteExpired();
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
import multer from "multer";
|
|
||||||
import { FileSystemHelper } from "../helpers/fileSystemHelper";
|
|
||||||
import path from "path";
|
|
||||||
import BadRequestException from "../exceptions/badRequestException";
|
|
||||||
|
|
||||||
export const clubImageStorage = multer.diskStorage({
|
|
||||||
destination: FileSystemHelper.formatPath("/app"),
|
|
||||||
filename: function (req, file, cb) {
|
|
||||||
const fileExtension = path.extname(file.originalname).toLowerCase();
|
|
||||||
|
|
||||||
if (file.fieldname === "icon") {
|
|
||||||
cb(null, "admin-icon" + fileExtension);
|
|
||||||
} else if (file.fieldname === "logo") {
|
|
||||||
cb(null, "admin-logo" + fileExtension);
|
|
||||||
} else {
|
|
||||||
cb(null, file.originalname);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const clubImageMulter = multer({
|
|
||||||
storage: clubImageStorage,
|
|
||||||
fileFilter(req, file, cb) {
|
|
||||||
if (file.mimetype.startsWith("image/png")) {
|
|
||||||
cb(null, true);
|
|
||||||
} else {
|
|
||||||
cb(new BadRequestException("Wrong file format"));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const clubImageUpload = clubImageMulter.fields([
|
|
||||||
{ name: "icon", maxCount: 1 },
|
|
||||||
{ name: "logo", maxCount: 1 },
|
|
||||||
]);
|
|
|
@ -10,8 +10,8 @@ export class QueryToUUID1742922178643 implements MigrationInterface {
|
||||||
const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("recipientsByQueryId") !== -1);
|
const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("recipientsByQueryId") !== -1);
|
||||||
await queryRunner.dropForeignKey("newsletter", foreignKey);
|
await queryRunner.dropForeignKey("newsletter", foreignKey);
|
||||||
|
|
||||||
// const entries = await queryRunner.manager.getRepository("query").find({ select: { title: true, query: true } });
|
const entries = await queryRunner.manager.getRepository(query).find({ select: { title: true, query: true } });
|
||||||
// await queryRunner.clearTable("query");
|
await queryRunner.clearTable("query");
|
||||||
|
|
||||||
await queryRunner.dropColumn("newsletter", "recipientsByQueryId");
|
await queryRunner.dropColumn("newsletter", "recipientsByQueryId");
|
||||||
await queryRunner.dropColumn("query", "id");
|
await queryRunner.dropColumn("query", "id");
|
||||||
|
@ -32,7 +32,7 @@ export class QueryToUUID1742922178643 implements MigrationInterface {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// await queryRunner.manager.getRepository("query").save(entries);
|
await queryRunner.manager.createQueryBuilder().insert().into("query").values(entries).execute();
|
||||||
|
|
||||||
await queryRunner.createForeignKey(
|
await queryRunner.createForeignKey(
|
||||||
"newsletter",
|
"newsletter",
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
|
|
||||||
import { getDefaultByORM, getTypeByORM } from "./ormHelper";
|
|
||||||
import { newsletter } from "../entity/club/newsletter/newsletter";
|
|
||||||
|
|
||||||
export class NewsletterColumnType1744351418751 implements MigrationInterface {
|
|
||||||
name = "NewsletterColumnType1744351418751";
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
let newsletters = await queryRunner.manager.getRepository("newsletter").find();
|
|
||||||
|
|
||||||
await queryRunner.dropColumn("newsletter", "newsletterTitle");
|
|
||||||
await queryRunner.dropColumn("newsletter", "newsletterSignatur");
|
|
||||||
|
|
||||||
await queryRunner.addColumn(
|
|
||||||
"newsletter",
|
|
||||||
new TableColumn({ name: "newsletterTitle", ...getTypeByORM("text"), default: getDefaultByORM("string") })
|
|
||||||
);
|
|
||||||
await queryRunner.addColumn(
|
|
||||||
"newsletter",
|
|
||||||
new TableColumn({ name: "newsletterSignatur", ...getTypeByORM("text"), default: getDefaultByORM("string") })
|
|
||||||
);
|
|
||||||
|
|
||||||
await queryRunner.manager.getRepository("newsletter").save(newsletters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
let newsletters = await queryRunner.manager.getRepository("newsletter").find();
|
|
||||||
|
|
||||||
await queryRunner.dropColumn("newsletter", "newsletterTitle");
|
|
||||||
await queryRunner.dropColumn("newsletter", "newsletterSignatur");
|
|
||||||
|
|
||||||
await queryRunner.addColumn(
|
|
||||||
"newsletter",
|
|
||||||
new TableColumn({ name: "newsletterTitle", ...getTypeByORM("varchar"), default: getDefaultByORM("string") })
|
|
||||||
);
|
|
||||||
await queryRunner.addColumn(
|
|
||||||
"newsletter",
|
|
||||||
new TableColumn({ name: "newsletterSignatur", ...getTypeByORM("varchar"), default: getDefaultByORM("string") })
|
|
||||||
);
|
|
||||||
|
|
||||||
await queryRunner.manager.getRepository("newsletter").save(newsletters);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
|
|
||||||
import { getTypeByORM, getDefaultByORM } from "./ormHelper";
|
|
||||||
|
|
||||||
export class QueryUpdatedAt1744795756230 implements MigrationInterface {
|
|
||||||
name = "QueryUpdatedAt1744795756230";
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.addColumn(
|
|
||||||
"query",
|
|
||||||
new TableColumn({
|
|
||||||
name: "updatedAt",
|
|
||||||
...getTypeByORM("datetime", false, 6),
|
|
||||||
default: getDefaultByORM("currentTimestamp", 6),
|
|
||||||
onUpdate: getDefaultByORM<string>("currentTimestamp", 6),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.dropColumn("query", "updatedAt");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
import { setting_table } from "./baseSchemaTables/admin";
|
|
||||||
import SettingHelper from "../helpers/settingsHelper";
|
|
||||||
import ms from "ms";
|
|
||||||
|
|
||||||
export class SettingsFromEnv1745059495807 implements MigrationInterface {
|
|
||||||
name = "SettingsFromEnv1745059495807";
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.createTable(setting_table, true, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.dropTable(setting_table.name, true, true, true);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
import { setting_table } from "./baseSchemaTables/admin";
|
|
||||||
import SettingHelper from "../helpers/settingsHelper";
|
|
||||||
import ms from "ms";
|
|
||||||
|
|
||||||
export class SettingsFromEnv_SET1745059495808 implements MigrationInterface {
|
|
||||||
name = "SettingsFromEnv_SET1745059495808";
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
// 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 as ms.StringValue);
|
|
||||||
await SettingHelper.setSetting("session.refresh_expiration", process.env.REFRESH_EXPIRATION as ms.StringValue);
|
|
||||||
await SettingHelper.setSetting(
|
|
||||||
"session.pwa_refresh_expiration",
|
|
||||||
process.env.PWA_REFRESH_EXPIRATION as ms.StringValue
|
|
||||||
);
|
|
||||||
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", Number(process.env.MAIL_PORT ?? "578"));
|
|
||||||
await SettingHelper.setSetting("mail.secure", process.env.MAIL_SECURE == "true");
|
|
||||||
await SettingHelper.setSetting("backup.interval", Number(process.env.BACKUP_INTERVAL ?? "1"));
|
|
||||||
await SettingHelper.setSetting("backup.copies", Number(process.env.BACKUP_COPIES ?? "7"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
|
|
||||||
import { getTypeByORM, getDefaultByORM } from "./ormHelper";
|
|
||||||
|
|
||||||
export class MemberCreatedAt1746006549262 implements MigrationInterface {
|
|
||||||
name = "MemberCreatedAt1746006549262";
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.addColumn(
|
|
||||||
"member",
|
|
||||||
new TableColumn({
|
|
||||||
name: "createdAt",
|
|
||||||
...getTypeByORM("datetime", false, 6),
|
|
||||||
default: getDefaultByORM("currentTimestamp", 6),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.dropColumn("member", "createdAt");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
|
|
||||||
import { getDefaultByORM, getTypeByORM } from "./ormHelper";
|
|
||||||
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
|
|
||||||
import { CodingHelper } from "../helpers/codingHelper";
|
|
||||||
import { APPLICATION_SECRET } from "../env.defaults";
|
|
||||||
|
|
||||||
export class UserLoginRoutine1746252454922 implements MigrationInterface {
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
let users = await queryRunner.manager.getRepository("user").find({ select: ["id", "secret"] });
|
|
||||||
|
|
||||||
await queryRunner.dropColumns("user", ["secret", "static"]);
|
|
||||||
|
|
||||||
await queryRunner.addColumns("user", [
|
|
||||||
new TableColumn({ name: "secret", ...getTypeByORM("text"), default: getDefaultByORM("string") }),
|
|
||||||
new TableColumn({
|
|
||||||
name: "routine",
|
|
||||||
...getTypeByORM("varchar"),
|
|
||||||
default: getDefaultByORM("string", LoginRoutineEnum.totp),
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await queryRunner.manager.getRepository("user").save(users.map((u) => ({ id: u.id, secret: u.secret })));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
let users = await queryRunner.manager.getRepository("user").find({ select: ["id", "secret"] });
|
|
||||||
|
|
||||||
await queryRunner.dropColumn("user", "secret");
|
|
||||||
|
|
||||||
await queryRunner.addColumns("user", [
|
|
||||||
new TableColumn({ name: "secret", ...getTypeByORM("varchar"), default: getDefaultByORM("string") }),
|
|
||||||
new TableColumn({ name: "static", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await queryRunner.manager.getRepository("user").save(users.map((u) => ({ id: u.id, secret: u.secret })));
|
|
||||||
|
|
||||||
await queryRunner.dropColumn("user", "routine");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -148,12 +148,3 @@ export const reset_table = new Table({
|
||||||
{ name: "secret", ...getTypeByORM("varchar") },
|
{ 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") },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { DB_TYPE } from "../env.defaults";
|
|
||||||
|
|
||||||
export type ORMType = "int" | "bigint" | "boolean" | "date" | "datetime" | "time" | "text" | "varchar" | "uuid";
|
export type ORMType = "int" | "bigint" | "boolean" | "date" | "datetime" | "time" | "text" | "varchar" | "uuid";
|
||||||
export type ORMDefault = "currentTimestamp" | "string" | "boolean" | "number" | "null";
|
export type ORMDefault = "currentTimestamp" | "string" | "boolean" | "number" | "null";
|
||||||
export type ColumnConfig = {
|
export type ColumnConfig = {
|
||||||
|
@ -15,7 +13,7 @@ export type Primary = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getTypeByORM(type: ORMType, nullable: boolean = false, length: number = 255): ColumnConfig {
|
export function getTypeByORM(type: ORMType, nullable: boolean = false, length: number = 255): ColumnConfig {
|
||||||
const dbType = DB_TYPE;
|
const dbType = process.env.DB_TYPE;
|
||||||
|
|
||||||
const typeMap: Record<string, Record<ORMType, string>> = {
|
const typeMap: Record<string, Record<ORMType, string>> = {
|
||||||
mysql: {
|
mysql: {
|
||||||
|
@ -65,7 +63,7 @@ export function getTypeByORM(type: ORMType, nullable: boolean = false, length: n
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultByORM<T = string | null>(type: ORMDefault, data?: string | number | boolean): T {
|
export function getDefaultByORM<T = string | null>(type: ORMDefault, data?: string | number | boolean): T {
|
||||||
const dbType = DB_TYPE;
|
const dbType = process.env.DB_TYPE;
|
||||||
|
|
||||||
const typeMap: Record<string, Record<ORMDefault, string | null>> = {
|
const typeMap: Record<string, Record<ORMDefault, string | null>> = {
|
||||||
mysql: {
|
mysql: {
|
||||||
|
|
|
@ -20,7 +20,6 @@ import {
|
||||||
getExecutivePositionByMemberAndRecord,
|
getExecutivePositionByMemberAndRecord,
|
||||||
getExecutivePositionsByMember,
|
getExecutivePositionsByMember,
|
||||||
getMemberById,
|
getMemberById,
|
||||||
getMemberLastInternalId,
|
|
||||||
getMemberPrintoutById,
|
getMemberPrintoutById,
|
||||||
getMembersByIds,
|
getMembersByIds,
|
||||||
getMembershipByMemberAndRecord,
|
getMembershipByMemberAndRecord,
|
||||||
|
@ -44,10 +43,6 @@ router.get("/", async (req: Request, res: Response) => {
|
||||||
await getAllMembers(req, res);
|
await getAllMembers(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/last/internalId", async (req: Request, res: Response) => {
|
|
||||||
await getMemberLastInternalId(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/ids", async (req: Request, res: Response) => {
|
router.post("/ids", async (req: Request, res: Response) => {
|
||||||
await getMembersByIds(req, res);
|
await getMembersByIds(req, res);
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,8 +16,6 @@ import {
|
||||||
createNewsletterPrintoutPreviewById,
|
createNewsletterPrintoutPreviewById,
|
||||||
getNewsletterPrintoutProgressById,
|
getNewsletterPrintoutProgressById,
|
||||||
getNewsletterSendingProgressById,
|
getNewsletterSendingProgressById,
|
||||||
getNewsletterMailReceiversById,
|
|
||||||
getNewsletterPrintReceiversById,
|
|
||||||
} from "../../../controller/admin/club/newsletterController";
|
} from "../../../controller/admin/club/newsletterController";
|
||||||
import PermissionHelper from "../../../helpers/permissionHelper";
|
import PermissionHelper from "../../../helpers/permissionHelper";
|
||||||
|
|
||||||
|
@ -59,14 +57,6 @@ router.get("/:newsletterId/sendprogress", async (req: Request, res: Response) =>
|
||||||
await getNewsletterSendingProgressById(req, res);
|
await getNewsletterSendingProgressById(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/:newsletterId/printrecipients", async (req: Request, res: Response) => {
|
|
||||||
await getNewsletterPrintReceiversById(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/:newsletterId/mailrecipients", async (req: Request, res: Response) => {
|
|
||||||
await getNewsletterMailReceiversById(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
PermissionHelper.passCheckMiddleware("create", "club", "protocol"),
|
PermissionHelper.passCheckMiddleware("create", "club", "protocol"),
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
import express, { Request, Response } from "express";
|
import express, { Request, Response } from "express";
|
||||||
import {
|
import { executeQuery, getAllTableMeta, getTableMetaByTablename } from "../../../controller/admin/club/queryBuilderController";
|
||||||
executeQuery,
|
|
||||||
executeQueryByStoreId,
|
|
||||||
getAllTableMeta,
|
|
||||||
getTableMetaByTablename,
|
|
||||||
} from "../../../controller/admin/club/queryBuilderController";
|
|
||||||
|
|
||||||
var router = express.Router({ mergeParams: true });
|
var router = express.Router({ mergeParams: true });
|
||||||
|
|
||||||
|
@ -20,8 +15,4 @@ router.post("/query", async (req: Request, res: Response) => {
|
||||||
await executeQuery(req, res);
|
await executeQuery(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/query/:storeId", async (req: Request, res: Response) => {
|
|
||||||
await executeQueryByStoreId(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -26,71 +26,70 @@ import user from "./management/user";
|
||||||
import invite from "./management/invite";
|
import invite from "./management/invite";
|
||||||
import api from "./management/webapi";
|
import api from "./management/webapi";
|
||||||
import backup from "./management/backup";
|
import backup from "./management/backup";
|
||||||
import setting from "./management/setting";
|
|
||||||
|
|
||||||
var router = express.Router({ mergeParams: true });
|
var router = express.Router({ mergeParams: true });
|
||||||
|
|
||||||
router.use(
|
router.use(
|
||||||
"/award",
|
"/award",
|
||||||
PermissionHelper.passCheckSomeMiddleware([
|
PermissionHelper.passCheckSomeMiddleware([
|
||||||
{ requiredPermission: "read", section: "configuration", module: "award" },
|
{ requiredPermissions: "read", section: "configuration", module: "award" },
|
||||||
{ requiredPermission: "read", section: "club", module: "member" },
|
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||||
]),
|
]),
|
||||||
award
|
award
|
||||||
);
|
);
|
||||||
router.use(
|
router.use(
|
||||||
"/communicationtype",
|
"/communicationtype",
|
||||||
PermissionHelper.passCheckSomeMiddleware([
|
PermissionHelper.passCheckSomeMiddleware([
|
||||||
{ requiredPermission: "read", section: "configuration", module: "communication_type" },
|
{ requiredPermissions: "read", section: "configuration", module: "communication_type" },
|
||||||
{ requiredPermission: "read", section: "club", module: "member" },
|
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||||
]),
|
]),
|
||||||
communicationType
|
communicationType
|
||||||
);
|
);
|
||||||
router.use(
|
router.use(
|
||||||
"/executiveposition",
|
"/executiveposition",
|
||||||
PermissionHelper.passCheckSomeMiddleware([
|
PermissionHelper.passCheckSomeMiddleware([
|
||||||
{ requiredPermission: "read", section: "configuration", module: "executive_position" },
|
{ requiredPermissions: "read", section: "configuration", module: "executive_position" },
|
||||||
{ requiredPermission: "read", section: "club", module: "member" },
|
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||||
]),
|
]),
|
||||||
executivePosition
|
executivePosition
|
||||||
);
|
);
|
||||||
router.use(
|
router.use(
|
||||||
"/membershipstatus",
|
"/membershipstatus",
|
||||||
PermissionHelper.passCheckSomeMiddleware([
|
PermissionHelper.passCheckSomeMiddleware([
|
||||||
{ requiredPermission: "read", section: "configuration", module: "membership_status" },
|
{ requiredPermissions: "read", section: "configuration", module: "membership_status" },
|
||||||
{ requiredPermission: "read", section: "club", module: "member" },
|
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||||
]),
|
]),
|
||||||
membershipStatus
|
membershipStatus
|
||||||
);
|
);
|
||||||
router.use(
|
router.use(
|
||||||
"/qualification",
|
"/qualification",
|
||||||
PermissionHelper.passCheckSomeMiddleware([
|
PermissionHelper.passCheckSomeMiddleware([
|
||||||
{ requiredPermission: "read", section: "configuration", module: "qualification" },
|
{ requiredPermissions: "read", section: "configuration", module: "qualification" },
|
||||||
{ requiredPermission: "read", section: "club", module: "member" },
|
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||||
]),
|
]),
|
||||||
qualification
|
qualification
|
||||||
);
|
);
|
||||||
router.use(
|
router.use(
|
||||||
"/salutation",
|
"/salutation",
|
||||||
PermissionHelper.passCheckSomeMiddleware([
|
PermissionHelper.passCheckSomeMiddleware([
|
||||||
{ requiredPermission: "read", section: "configuration", module: "salutation" },
|
{ requiredPermissions: "read", section: "configuration", module: "salutation" },
|
||||||
{ requiredPermission: "read", section: "club", module: "member" },
|
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||||
]),
|
]),
|
||||||
salutation
|
salutation
|
||||||
);
|
);
|
||||||
router.use(
|
router.use(
|
||||||
"/calendartype",
|
"/calendartype",
|
||||||
PermissionHelper.passCheckSomeMiddleware([
|
PermissionHelper.passCheckSomeMiddleware([
|
||||||
{ requiredPermission: "read", section: "configuration", module: "calendar_type" },
|
{ requiredPermissions: "read", section: "configuration", module: "calendar_type" },
|
||||||
{ requiredPermission: "read", section: "club", module: "calendar" },
|
{ requiredPermissions: "read", section: "club", module: "calendar" },
|
||||||
]),
|
]),
|
||||||
calendarType
|
calendarType
|
||||||
);
|
);
|
||||||
router.use(
|
router.use(
|
||||||
"/querystore",
|
"/querystore",
|
||||||
PermissionHelper.passCheckSomeMiddleware([
|
PermissionHelper.passCheckSomeMiddleware([
|
||||||
{ requiredPermission: "read", section: "configuration", module: "query_store" },
|
{ requiredPermissions: "read", section: "configuration", module: "query_store" },
|
||||||
{ requiredPermission: "read", section: "club", module: "listprint" },
|
{ requiredPermissions: "read", section: "club", module: "listprint" },
|
||||||
]),
|
]),
|
||||||
queryStore
|
queryStore
|
||||||
);
|
);
|
||||||
|
@ -98,16 +97,16 @@ router.use("/template", PermissionHelper.passCheckMiddleware("read", "configurat
|
||||||
router.use(
|
router.use(
|
||||||
"/templateusage",
|
"/templateusage",
|
||||||
PermissionHelper.passCheckSomeMiddleware([
|
PermissionHelper.passCheckSomeMiddleware([
|
||||||
{ requiredPermission: "read", section: "configuration", module: "template_usage" },
|
{ requiredPermissions: "read", section: "configuration", module: "template_usage" },
|
||||||
{ requiredPermission: "read", section: "configuration", module: "template" },
|
{ requiredPermissions: "read", section: "configuration", module: "template" },
|
||||||
]),
|
]),
|
||||||
templateUsage
|
templateUsage
|
||||||
);
|
);
|
||||||
router.use(
|
router.use(
|
||||||
"/newsletterconfig",
|
"/newsletterconfig",
|
||||||
PermissionHelper.passCheckSomeMiddleware([
|
PermissionHelper.passCheckSomeMiddleware([
|
||||||
{ requiredPermission: "read", section: "configuration", module: "newsletter_config" },
|
{ requiredPermissions: "read", section: "configuration", module: "newsletter_config" },
|
||||||
{ requiredPermission: "read", section: "configuration", module: "communication_type" },
|
{ requiredPermissions: "read", section: "configuration", module: "communication_type" },
|
||||||
]),
|
]),
|
||||||
newsletterConfig
|
newsletterConfig
|
||||||
);
|
);
|
||||||
|
@ -116,8 +115,8 @@ router.use("/member", PermissionHelper.passCheckMiddleware("read", "club", "memb
|
||||||
router.use(
|
router.use(
|
||||||
"/protocol",
|
"/protocol",
|
||||||
PermissionHelper.passCheckSomeMiddleware([
|
PermissionHelper.passCheckSomeMiddleware([
|
||||||
{ requiredPermission: "read", section: "club", module: "protocol" },
|
{ requiredPermissions: "read", section: "club", module: "protocol" },
|
||||||
{ requiredPermission: "read", section: "club", module: "member" },
|
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||||
]),
|
]),
|
||||||
protocol
|
protocol
|
||||||
);
|
);
|
||||||
|
@ -125,19 +124,19 @@ router.use("/calendar", PermissionHelper.passCheckMiddleware("read", "club", "ca
|
||||||
router.use(
|
router.use(
|
||||||
"/querybuilder",
|
"/querybuilder",
|
||||||
PermissionHelper.passCheckSomeMiddleware([
|
PermissionHelper.passCheckSomeMiddleware([
|
||||||
{ requiredPermission: "read", section: "club", module: "query" },
|
{ requiredPermissions: "read", section: "club", module: "query" },
|
||||||
{ requiredPermission: "read", section: "configuration", module: "query_store" },
|
{ requiredPermissions: "read", section: "configuration", module: "query_store" },
|
||||||
]),
|
]),
|
||||||
queryBuilder
|
queryBuilder
|
||||||
);
|
);
|
||||||
router.use(
|
router.use(
|
||||||
"/newsletter",
|
"/newsletter",
|
||||||
PermissionHelper.passCheckSomeMiddleware([
|
PermissionHelper.passCheckSomeMiddleware([
|
||||||
{ requiredPermission: "read", section: "club", module: "newsletter" },
|
{ requiredPermissions: "read", section: "club", module: "newsletter" },
|
||||||
{ requiredPermission: "read", section: "club", module: "member" },
|
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||||
{ requiredPermission: "read", section: "club", module: "calendar" },
|
{ requiredPermissions: "read", section: "club", module: "calendar" },
|
||||||
{ requiredPermission: "read", section: "club", module: "query" },
|
{ requiredPermissions: "read", section: "club", module: "query" },
|
||||||
{ requiredPermission: "read", section: "configuration", module: "query_store" },
|
{ requiredPermissions: "read", section: "configuration", module: "query_store" },
|
||||||
]),
|
]),
|
||||||
newsletter
|
newsletter
|
||||||
);
|
);
|
||||||
|
@ -147,8 +146,8 @@ router.use("/role", PermissionHelper.passCheckMiddleware("read", "management", "
|
||||||
router.use(
|
router.use(
|
||||||
"/user",
|
"/user",
|
||||||
PermissionHelper.passCheckSomeMiddleware([
|
PermissionHelper.passCheckSomeMiddleware([
|
||||||
{ requiredPermission: "read", section: "management", module: "user" },
|
{ requiredPermissions: "read", section: "management", module: "user" },
|
||||||
{ requiredPermission: "read", section: "management", module: "role" },
|
{ requiredPermissions: "read", section: "management", module: "role" },
|
||||||
]),
|
]),
|
||||||
user
|
user
|
||||||
);
|
);
|
||||||
|
@ -160,6 +159,5 @@ router.use(
|
||||||
PermissionHelper.passCheckMiddleware("read", "management", "backup"),
|
PermissionHelper.passCheckMiddleware("read", "management", "backup"),
|
||||||
backup
|
backup
|
||||||
);
|
);
|
||||||
router.use("/setting", PermissionHelper.passCheckMiddleware("read", "management", "setting"), setting);
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
import express, { Request, Response } from "express";
|
|
||||||
import PermissionHelper from "../../../helpers/permissionHelper";
|
|
||||||
import {
|
|
||||||
getSetting,
|
|
||||||
getSettings,
|
|
||||||
resetSetting,
|
|
||||||
setImages,
|
|
||||||
setSetting,
|
|
||||||
setSettings,
|
|
||||||
} from "../../../controller/admin/management/settingController";
|
|
||||||
import { clubImageUpload } from "../../../middleware/multer";
|
|
||||||
|
|
||||||
var router = express.Router({ mergeParams: true });
|
|
||||||
|
|
||||||
router.get("/", async (req: Request, res: Response) => {
|
|
||||||
await getSettings(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/:setting", async (req: Request, res: Response) => {
|
|
||||||
await getSetting(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.put(
|
|
||||||
"/",
|
|
||||||
PermissionHelper.passCheckMiddleware("create", "management", "setting"),
|
|
||||||
async (req: Request, res: Response) => {
|
|
||||||
await setSetting(req, res);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
router.put(
|
|
||||||
"/multi",
|
|
||||||
PermissionHelper.passCheckMiddleware("create", "management", "setting"),
|
|
||||||
async (req: Request, res: Response) => {
|
|
||||||
await setSettings(req, res);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
router.put(
|
|
||||||
"/images",
|
|
||||||
PermissionHelper.passCheckMiddleware("create", "management", "setting"),
|
|
||||||
clubImageUpload,
|
|
||||||
async (req: Request, res: Response) => {
|
|
||||||
await setImages(req, res);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
router.delete(
|
|
||||||
"/:setting",
|
|
||||||
PermissionHelper.passCheckMiddleware("delete", "management", "setting"),
|
|
||||||
async (req: Request, res: Response) => {
|
|
||||||
await resetSetting(req, res);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default router;
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
updateUserPermissions,
|
updateUserPermissions,
|
||||||
updateUserRoles,
|
updateUserRoles,
|
||||||
} from "../../../controller/admin/management/userController";
|
} from "../../../controller/admin/management/userController";
|
||||||
|
import { inviteUser } from "../../../controller/inviteController";
|
||||||
|
|
||||||
var router = express.Router({ mergeParams: true });
|
var router = express.Router({ mergeParams: true });
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { kickof, login, logout, refresh } from "../controller/authController";
|
import { login, logout, refresh } from "../controller/authController";
|
||||||
|
|
||||||
var router = express.Router({ mergeParams: true });
|
var router = express.Router({ mergeParams: true });
|
||||||
|
|
||||||
router.post("/kickof", async (req, res) => {
|
|
||||||
await kickof(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/login", async (req, res) => {
|
router.post("/login", async (req, res) => {
|
||||||
await login(req, res);
|
await login(req, res);
|
||||||
});
|
});
|
||||||
|
|
|
@ -68,7 +68,7 @@ export default (app: Express) => {
|
||||||
}
|
}
|
||||||
app.set("query parser", "extended");
|
app.set("query parser", "extended");
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.options("*splat", cors());
|
app.options("*", cors());
|
||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
app.use(morgan("short"));
|
app.use(morgan("short"));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { finishInvite, verifyInvite } from "../controller/inviteController";
|
import { isSetup } from "../controller/setupController";
|
||||||
|
import { finishInvite, inviteUser, verifyInvite } from "../controller/inviteController";
|
||||||
import ParamaterPassCheckHelper from "../helpers/parameterPassCheckHelper";
|
import ParamaterPassCheckHelper from "../helpers/parameterPassCheckHelper";
|
||||||
|
|
||||||
var router = express.Router({ mergeParams: true });
|
var router = express.Router({ mergeParams: true });
|
||||||
|
@ -8,12 +9,8 @@ router.post("/verify", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mai
|
||||||
await verifyInvite(req, res);
|
await verifyInvite(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.put(
|
router.put("/", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "totp"]), async (req, res) => {
|
||||||
"/",
|
await finishInvite(req, res);
|
||||||
ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "secret", "routine "]),
|
});
|
||||||
async (req, res) => {
|
|
||||||
await finishInvite(req, res);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -1,12 +1,5 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import {
|
import { getCalendarItemsByTypes } from "../controller/publicController";
|
||||||
getApplicationConfig,
|
|
||||||
getApplicationFavicon,
|
|
||||||
getApplicationIcon,
|
|
||||||
getApplicationLogo,
|
|
||||||
getApplicationManifest,
|
|
||||||
getCalendarItemsByTypes,
|
|
||||||
} from "../controller/publicController";
|
|
||||||
|
|
||||||
var router = express.Router({ mergeParams: true });
|
var router = express.Router({ mergeParams: true });
|
||||||
|
|
||||||
|
@ -14,24 +7,4 @@ router.get("/calendar", async (req, res) => {
|
||||||
await getCalendarItemsByTypes(req, res);
|
await getCalendarItemsByTypes(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/configuration", async (req, res) => {
|
|
||||||
await getApplicationConfig(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/manifest.webmanifest", async (req, res) => {
|
|
||||||
await getApplicationManifest(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/applogo.png", async (req, res) => {
|
|
||||||
await getApplicationLogo(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/favicon.ico", async (req, res) => {
|
|
||||||
await getApplicationFavicon(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/icon.png", async (req, res) => {
|
|
||||||
await getApplicationIcon(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -12,12 +12,8 @@ router.post("/", ParamaterPassCheckHelper.requiredIncludedMiddleware(["username"
|
||||||
await startReset(req, res);
|
await startReset(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.put(
|
router.put("/", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "totp"]), async (req, res) => {
|
||||||
"/",
|
await finishReset(req, res);
|
||||||
ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "secret", "routine"]),
|
});
|
||||||
async (req, res) => {
|
|
||||||
await finishReset(req, res);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Parser from "rss-parser";
|
||||||
var router = express.Router({ mergeParams: true });
|
var router = express.Router({ mergeParams: true });
|
||||||
|
|
||||||
router.get("/version", async (req: Request, res: Response) => {
|
router.get("/version", async (req: Request, res: Response) => {
|
||||||
let serverPackage = FileSystemHelper.readRootFile("/package.json");
|
let serverPackage = FileSystemHelper.readTemplateFile("/package.json");
|
||||||
let serverJson = JSON.parse(serverPackage);
|
let serverJson = JSON.parse(serverPackage);
|
||||||
res.send({
|
res.send({
|
||||||
name: serverJson.name,
|
name: serverJson.name,
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import {
|
import { isSetup } from "../controller/setupController";
|
||||||
isSetup,
|
|
||||||
setAppIdentity,
|
|
||||||
setClubIdentity,
|
|
||||||
setMailConfig,
|
|
||||||
uploadClubImages,
|
|
||||||
} from "../controller/setupController";
|
|
||||||
import { finishInvite, inviteUser, verifyInvite } from "../controller/inviteController";
|
import { finishInvite, inviteUser, verifyInvite } from "../controller/inviteController";
|
||||||
import ParamaterPassCheckHelper from "../helpers/parameterPassCheckHelper";
|
import ParamaterPassCheckHelper from "../helpers/parameterPassCheckHelper";
|
||||||
import { clubImageUpload } from "../middleware/multer";
|
|
||||||
|
|
||||||
var router = express.Router({ mergeParams: true });
|
var router = express.Router({ mergeParams: true });
|
||||||
|
|
||||||
|
@ -16,44 +9,20 @@ router.get("/", async (req, res) => {
|
||||||
await isSetup(req, res);
|
await isSetup(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/club", async (req, res) => {
|
|
||||||
await setClubIdentity(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/club/images", clubImageUpload, async (req, res) => {
|
|
||||||
await uploadClubImages(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/app", async (req, res) => {
|
|
||||||
await setAppIdentity(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post(
|
|
||||||
"/mail",
|
|
||||||
ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "username", "password", "host", "port", "secure"]),
|
|
||||||
async (req, res) => {
|
|
||||||
await setMailConfig(req, res);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
router.post("/verify", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token"]), async (req, res) => {
|
router.post("/verify", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token"]), async (req, res) => {
|
||||||
await verifyInvite(req, res);
|
await verifyInvite(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/me",
|
"/",
|
||||||
ParamaterPassCheckHelper.requiredIncludedMiddleware(["username", "mail", "firstname", "lastname"]),
|
ParamaterPassCheckHelper.requiredIncludedMiddleware(["username", "mail", "firstname", "lastname"]),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
await inviteUser(req, res, true);
|
await inviteUser(req, res, false);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.put("/", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "totp"]), async (req, res) => {
|
||||||
"/finish",
|
await finishInvite(req, res, true);
|
||||||
ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "totp"]),
|
});
|
||||||
async (req, res) => {
|
|
||||||
await finishInvite(req, res, true);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -1,16 +1,5 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import {
|
import { getMeById, getMyTotp, transferOwnership, updateMe, verifyMyTotp } from "../controller/userController";
|
||||||
changeMyPassword,
|
|
||||||
changeToPW,
|
|
||||||
changeToTOTP,
|
|
||||||
getChangeToTOTP,
|
|
||||||
getMeById,
|
|
||||||
getMyRoutine,
|
|
||||||
getMyTotp,
|
|
||||||
transferOwnership,
|
|
||||||
updateMe,
|
|
||||||
verifyMyTotp,
|
|
||||||
} from "../controller/userController";
|
|
||||||
|
|
||||||
var router = express.Router({ mergeParams: true });
|
var router = express.Router({ mergeParams: true });
|
||||||
|
|
||||||
|
@ -18,34 +7,14 @@ router.get("/me", async (req, res) => {
|
||||||
await getMeById(req, res);
|
await getMeById(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/routine", async (req, res) => {
|
|
||||||
await getMyRoutine(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/totp", async (req, res) => {
|
router.get("/totp", async (req, res) => {
|
||||||
await getMyTotp(req, res);
|
await getMyTotp(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/changeToTOTP", async (req, res) => {
|
|
||||||
await getChangeToTOTP(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/verify", async (req, res) => {
|
router.post("/verify", async (req, res) => {
|
||||||
await verifyMyTotp(req, res);
|
await verifyMyTotp(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.patch("/changepw", async (req, res) => {
|
|
||||||
await changeMyPassword(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.patch("/changeToTOTP", async (req, res) => {
|
|
||||||
await changeToTOTP(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.patch("/changeToPW", async (req, res) => {
|
|
||||||
await changeToPW(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.put("/transferOwner", async (req, res) => {
|
router.put("/transferOwner", async (req, res) => {
|
||||||
await transferOwnership(req, res);
|
await transferOwnership(req, res);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { Brackets, Like, Not, SelectQueryBuilder } from "typeorm";
|
import { Brackets, Like, SelectQueryBuilder } from "typeorm";
|
||||||
import { dataSource } from "../../../data-source";
|
import { dataSource } from "../../../data-source";
|
||||||
import { member } from "../../../entity/club/member/member";
|
import { member } from "../../../entity/club/member/member";
|
||||||
|
import { membership } from "../../../entity/club/member/membership";
|
||||||
import DatabaseActionException from "../../../exceptions/databaseActionException";
|
import DatabaseActionException from "../../../exceptions/databaseActionException";
|
||||||
|
import InternalException from "../../../exceptions/internalException";
|
||||||
import { memberView } from "../../../views/memberView";
|
import { memberView } from "../../../views/memberView";
|
||||||
import { DB_TYPE } from "../../../env.defaults";
|
import { DB_TYPE } from "../../../env.defaults";
|
||||||
|
|
||||||
|
@ -29,12 +31,9 @@ export default abstract class MemberService {
|
||||||
let searchBits = search.split(" ");
|
let searchBits = search.split(" ");
|
||||||
|
|
||||||
if (searchBits.length < 2) {
|
if (searchBits.length < 2) {
|
||||||
query = query.where(
|
query = query.where(`member.firstname LIKE :searchQuery OR member.lastname LIKE :searchQuery`, {
|
||||||
`member.firstname LIKE :searchQuery OR member.lastname LIKE :searchQuery OR member.internalId LIKE :searchQuery`,
|
searchQuery: `%${searchBits[0]}%`,
|
||||||
{
|
});
|
||||||
searchQuery: `%${searchBits[0]}%`,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
searchBits
|
searchBits
|
||||||
.flatMap((v, i) => searchBits.slice(i + 1).map((w) => [v, w]))
|
.flatMap((v, i) => searchBits.slice(i + 1).map((w) => [v, w]))
|
||||||
|
@ -158,28 +157,6 @@ export default abstract class MemberService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description get latest inserted memberId
|
|
||||||
* @returns {Promise<string>}
|
|
||||||
*/
|
|
||||||
static async getLatestInternalId(): Promise<string> {
|
|
||||||
return await dataSource
|
|
||||||
.getRepository(member)
|
|
||||||
.createQueryBuilder("member")
|
|
||||||
.where("member.internalId IS NOT NULL")
|
|
||||||
.andWhere({ internalId: Not("") })
|
|
||||||
.orderBy("member.createdAt", "DESC")
|
|
||||||
.addOrderBy("member.internalId", "DESC")
|
|
||||||
.limit(1)
|
|
||||||
.getOne()
|
|
||||||
.then((res) => {
|
|
||||||
return res?.internalId ?? "";
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
throw new DatabaseActionException("SELECT", "memberId", err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description apply member joins to query
|
* @description apply member joins to query
|
||||||
* @returns {SelectQueryBuilder<member>}
|
* @returns {SelectQueryBuilder<member>}
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
import { dataSource } from "../../data-source";
|
|
||||||
import { setting } from "../../entity/management/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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -129,27 +129,4 @@ export default abstract class UserService {
|
||||||
throw new DatabaseActionException("SELECT", "userRoles", err);
|
throw new DatabaseActionException("SELECT", "userRoles", err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description get secret and routine by iser
|
|
||||||
* @param userId string
|
|
||||||
* @returns {Promise<user>}
|
|
||||||
*/
|
|
||||||
static async getUserSecretAndRoutine(userId: string): Promise<user> {
|
|
||||||
return await dataSource
|
|
||||||
.getRepository(user)
|
|
||||||
.createQueryBuilder("user")
|
|
||||||
.select("user.id")
|
|
||||||
.addSelect("user.secret")
|
|
||||||
.addSelect("user.routine")
|
|
||||||
.where("user.id = :id", { id: userId })
|
|
||||||
.getOneOrFail()
|
|
||||||
.then((res) => {
|
|
||||||
return res;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
throw new DatabaseActionException("SELECT", "user credentials", err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
export interface DynamicQueryStructure {
|
export interface DynamicQueryStructure {
|
||||||
id: string;
|
|
||||||
select: string[] | "*";
|
select: string[] | "*";
|
||||||
table: string;
|
table: string;
|
||||||
where?: Array<ConditionStructure>;
|
where?: Array<ConditionStructure>;
|
||||||
join?: Array<DynamicQueryStructure & JoinStructure>;
|
join?: Array<DynamicQueryStructure & { foreignColumn: string }>;
|
||||||
orderBy?: Array<OrderByStructure>; // only at top level
|
orderBy?: Array<OrderByStructure>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ConditionStructure = (
|
export type ConditionStructure = (
|
||||||
|
@ -48,12 +47,7 @@ export type WhereOperation =
|
||||||
| "timespanEq"; // Date before x years (YYYY-01-01 <bis> YYYY-12-31)
|
| "timespanEq"; // Date before x years (YYYY-01-01 <bis> YYYY-12-31)
|
||||||
// TODO: age between | age equals | age greater | age smaller
|
// TODO: age between | age equals | age greater | age smaller
|
||||||
|
|
||||||
export type JoinStructure = { foreignColumn: string; type: "defined" } | { condition: string; type: "custom" };
|
|
||||||
|
|
||||||
export type OrderByStructure = {
|
export type OrderByStructure = {
|
||||||
id: string;
|
|
||||||
depth: number;
|
|
||||||
table: string;
|
|
||||||
column: string;
|
column: string;
|
||||||
order: OrderByType;
|
order: OrderByType;
|
||||||
};
|
};
|
||||||
|
@ -65,7 +59,6 @@ export type QueryResult = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const exampleQuery: DynamicQueryStructure = {
|
export const exampleQuery: DynamicQueryStructure = {
|
||||||
id: "1234",
|
|
||||||
select: ["firstname", "lastname"],
|
select: ["firstname", "lastname"],
|
||||||
table: "member",
|
table: "member",
|
||||||
where: [
|
where: [
|
||||||
|
@ -99,25 +92,19 @@ export const exampleQuery: DynamicQueryStructure = {
|
||||||
],
|
],
|
||||||
join: [
|
join: [
|
||||||
{
|
{
|
||||||
id: "5678",
|
|
||||||
select: "*",
|
select: "*",
|
||||||
table: "communication",
|
table: "communication",
|
||||||
foreignColumn: "sendNewsletter",
|
foreignColumn: "sendNewsletter",
|
||||||
type: "defined",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "91011",
|
|
||||||
select: "*",
|
select: "*",
|
||||||
table: "membership",
|
table: "membership",
|
||||||
foreignColumn: "memberships",
|
foreignColumn: "memberships",
|
||||||
type: "defined",
|
|
||||||
join: [
|
join: [
|
||||||
{
|
{
|
||||||
id: "121314",
|
|
||||||
select: "*",
|
select: "*",
|
||||||
table: "membership_status",
|
table: "membership_status",
|
||||||
foreignColumn: "status",
|
foreignColumn: "status",
|
||||||
type: "defined",
|
|
||||||
where: [
|
where: [
|
||||||
{
|
{
|
||||||
structureType: "condition",
|
structureType: "condition",
|
||||||
|
@ -133,16 +120,10 @@ export const exampleQuery: DynamicQueryStructure = {
|
||||||
],
|
],
|
||||||
orderBy: [
|
orderBy: [
|
||||||
{
|
{
|
||||||
id: "1234",
|
|
||||||
depth: 0,
|
|
||||||
table: "member",
|
|
||||||
column: "firstname",
|
column: "firstname",
|
||||||
order: "ASC",
|
order: "ASC",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "1234",
|
|
||||||
depth: 0,
|
|
||||||
table: "member",
|
|
||||||
column: "lastname",
|
column: "lastname",
|
||||||
order: "ASC",
|
order: "ASC",
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,8 +21,7 @@ export type PermissionModule =
|
||||||
| "query_store"
|
| "query_store"
|
||||||
| "template"
|
| "template"
|
||||||
| "template_usage"
|
| "template_usage"
|
||||||
| "backup"
|
| "backup";
|
||||||
| "setting";
|
|
||||||
|
|
||||||
export type PermissionType = "read" | "create" | "update" | "delete";
|
export type PermissionType = "read" | "create" | "update" | "delete";
|
||||||
|
|
||||||
|
@ -31,7 +30,6 @@ export type PermissionString =
|
||||||
| `${PermissionSection}.${PermissionModule}.*` // für alle Berechtigungen in einem Modul
|
| `${PermissionSection}.${PermissionModule}.*` // für alle Berechtigungen in einem Modul
|
||||||
| `${PermissionSection}.${PermissionType}` // für spezifische Berechtigungen in einem Abschnitt
|
| `${PermissionSection}.${PermissionType}` // für spezifische Berechtigungen in einem Abschnitt
|
||||||
| `${PermissionSection}.*` // für alle Berechtigungen in einem Abschnitt
|
| `${PermissionSection}.*` // für alle Berechtigungen in einem Abschnitt
|
||||||
| `additional.${string}.${string}` // additional
|
|
||||||
| "*"; // für Admin
|
| "*"; // für Admin
|
||||||
|
|
||||||
export type PermissionObject = {
|
export type PermissionObject = {
|
||||||
|
@ -40,20 +38,10 @@ export type PermissionObject = {
|
||||||
} & { all?: Array<PermissionType> | "*" };
|
} & { all?: Array<PermissionType> | "*" };
|
||||||
} & {
|
} & {
|
||||||
admin?: boolean;
|
admin?: boolean;
|
||||||
adminByOwner?: boolean;
|
|
||||||
} & {
|
|
||||||
additional?: { [key: string]: string };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SectionsAndModulesObject = {
|
export type SectionsAndModulesObject = {
|
||||||
[section in PermissionSection]: Array<PermissionModule>;
|
[section in PermissionSection]: Array<PermissionModule>;
|
||||||
} & {
|
|
||||||
additional?: Array<{
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
type: "number" | "string";
|
|
||||||
emptyIfAdmin: boolean;
|
|
||||||
}>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const permissionSections: Array<PermissionSection> = ["club", "configuration", "management"];
|
export const permissionSections: Array<PermissionSection> = ["club", "configuration", "management"];
|
||||||
|
@ -79,7 +67,6 @@ export const permissionModules: Array<PermissionModule> = [
|
||||||
"template",
|
"template",
|
||||||
"template_usage",
|
"template_usage",
|
||||||
"backup",
|
"backup",
|
||||||
"setting",
|
|
||||||
];
|
];
|
||||||
export const permissionTypes: Array<PermissionType> = ["read", "create", "update", "delete"];
|
export const permissionTypes: Array<PermissionType> = ["read", "create", "update", "delete"];
|
||||||
export const sectionsAndModules: SectionsAndModulesObject = {
|
export const sectionsAndModules: SectionsAndModulesObject = {
|
||||||
|
@ -97,8 +84,5 @@ export const sectionsAndModules: SectionsAndModulesObject = {
|
||||||
"template_usage",
|
"template_usage",
|
||||||
"newsletter_config",
|
"newsletter_config",
|
||||||
],
|
],
|
||||||
management: ["user", "role", "webapi", "backup", "setting"],
|
management: ["user", "role", "webapi", "backup"],
|
||||||
additional: [
|
|
||||||
//{ key: "val", name: "name", type: "number", emptyIfAdmin: true },
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,121 +0,0 @@
|
||||||
import ms from "ms";
|
|
||||||
|
|
||||||
export type SettingTopic = "club" | "app" | "session" | "mail" | "backup" | "security";
|
|
||||||
export type SettingString =
|
|
||||||
| "club.icon"
|
|
||||||
| "club.logo"
|
|
||||||
| "club.name"
|
|
||||||
| "club.imprint"
|
|
||||||
| "club.privacy"
|
|
||||||
| "club.website"
|
|
||||||
| "app.custom_login_message"
|
|
||||||
| "app.show_link_to_calendar"
|
|
||||||
| "session.jwt_expiration"
|
|
||||||
| "session.refresh_expiration"
|
|
||||||
| "session.pwa_refresh_expiration"
|
|
||||||
| "mail.email"
|
|
||||||
| "mail.username"
|
|
||||||
| "mail.password"
|
|
||||||
| "mail.host"
|
|
||||||
| "mail.port"
|
|
||||||
| "mail.secure"
|
|
||||||
| "backup.interval"
|
|
||||||
| "backup.copies";
|
|
||||||
|
|
||||||
export type SettingTypeAtom = "longstring" | "string" | "ms" | "number" | "boolean" | "url" | "email";
|
|
||||||
export type SettingType = SettingTypeAtom | `${SettingTypeAtom}/crypt` | `${SettingTypeAtom}/rand`;
|
|
||||||
|
|
||||||
export type SettingValueMapping = {
|
|
||||||
"club.icon": string;
|
|
||||||
"club.logo": string;
|
|
||||||
"club.name": string;
|
|
||||||
"club.imprint": string;
|
|
||||||
"club.privacy": string;
|
|
||||||
"club.website": string;
|
|
||||||
"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.email": string;
|
|
||||||
"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 | ms.StringValue;
|
|
||||||
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.icon": { type: "string", optional: true },
|
|
||||||
"club.logo": { type: "string", optional: true },
|
|
||||||
"club.name": { type: "string", default: "FF Admin" },
|
|
||||||
"club.imprint": { type: "url", optional: true },
|
|
||||||
"club.privacy": { type: "url", optional: true },
|
|
||||||
"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_expiration": { type: "ms", default: "15m" },
|
|
||||||
"session.refresh_expiration": { type: "ms", default: "1d" },
|
|
||||||
"session.pwa_refresh_expiration": { type: "ms", default: "5d" },
|
|
||||||
"mail.email": { type: "email", optional: false },
|
|
||||||
"mail.username": { type: "string", optional: false },
|
|
||||||
"mail.password": { type: "string/crypt", optional: false },
|
|
||||||
"mail.host": { type: "url", optional: false },
|
|
||||||
"mail.port": { type: "number", default: 587 },
|
|
||||||
"mail.secure": { type: "boolean", default: false },
|
|
||||||
"backup.interval": { type: "number", default: 1, min: 1 },
|
|
||||||
"backup.copies": { type: "number", default: 7, min: 1 },
|
|
||||||
};
|
|
||||||
|
|
||||||
/** ENV Settings */
|
|
||||||
export type EnvSettingString =
|
|
||||||
| "database.type"
|
|
||||||
| "database.host"
|
|
||||||
| "database.port"
|
|
||||||
| "database.name"
|
|
||||||
| "database.username"
|
|
||||||
| "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 | number | boolean;
|
|
||||||
};
|
|
||||||
} = {
|
|
||||||
"database.type": { type: "string", default: "postgres" },
|
|
||||||
"database.host": { type: "string" },
|
|
||||||
"database.port": { type: "string", default: "5432" },
|
|
||||||
"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"] },
|
|
||||||
};
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { QueryStoreViewModel } from "../../configuration/queryStore.models";
|
||||||
|
|
||||||
export interface NewsletterViewModel {
|
export interface NewsletterViewModel {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -7,4 +9,5 @@ export interface NewsletterViewModel {
|
||||||
newsletterSignatur: string;
|
newsletterSignatur: string;
|
||||||
isSent: boolean;
|
isSent: boolean;
|
||||||
recipientsByQueryId?: string;
|
recipientsByQueryId?: string;
|
||||||
|
recipientsByQuery?: QueryStoreViewModel;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { NewsletterConfigEnum } from "../../../enums/newsletterConfigEnum";
|
import { NewsletterConfigType } from "../../../enums/newsletterConfigType";
|
||||||
import { CommunicationTypeViewModel } from "./communicationType.models";
|
import { CommunicationTypeViewModel } from "./communicationType.models";
|
||||||
|
|
||||||
export interface NewsletterConfigViewModel {
|
export interface NewsletterConfigViewModel {
|
||||||
comTypeId: number;
|
comTypeId: number;
|
||||||
config: NewsletterConfigEnum;
|
config: NewsletterConfigType;
|
||||||
comType: CommunicationTypeViewModel;
|
comType: CommunicationTypeViewModel;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,5 +4,4 @@ export interface QueryStoreViewModel {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
query: string | DynamicQueryStructure;
|
query: string | DynamicQueryStructure;
|
||||||
updatedAt: Date;
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue