Compare commits
87 commits
Author | SHA1 | Date | |
---|---|---|---|
a59a47e663 | |||
4dcfe5febc | |||
3f54b5d243 | |||
de6ed4fba8 | |||
fff9ebc4fa | |||
dc43df8937 | |||
feb03ba99d | |||
874b863b1e | |||
97ffae009b | |||
19457824ee | |||
e4365135a1 | |||
c849b8eb18 | |||
0582cb3af7 | |||
1f8ec2eeed | |||
db7a4fff03 | |||
2998943dfa | |||
38bfa3dd75 | |||
fded8a663a | |||
5368a96d0f | |||
573f92d098 | |||
f6a0a61ed8 | |||
2357497d3a | |||
4074617145 | |||
3a82c74829 | |||
d7a0ee694f | |||
d8376a4b7b | |||
83250c1e62 | |||
a96c5266b8 | |||
5187a2dba6 | |||
496a60f2df | |||
2076412c06 | |||
1a71d32b6d | |||
1a83e4939d | |||
b778b6faa7 | |||
1a66c9fac0 | |||
7db363e3de | |||
b8df454048 | |||
0dbc726f64 | |||
025cd555e9 | |||
46bce99fd6 | |||
a36ebbd43c | |||
16ccd0d35f | |||
9ba477a9b3 | |||
69b447a2d8 | |||
56484020d8 | |||
9dd7686b67 | |||
8ead7386ca | |||
18fd56514d | |||
75d0c1f524 | |||
7da497066f | |||
676b7144bf | |||
a64567ce4e | |||
72552bdd83 | |||
cac784474c | |||
0ea12eaafc | |||
ddb460f8d0 | |||
be22c78372 | |||
a476bf6823 | |||
03a5bb3592 | |||
c35b99e0c4 | |||
c4e547b288 | |||
7dac58d958 | |||
6c0ea0b11c | |||
0ea780dd51 | |||
753cfdd5da | |||
a6229bb77c | |||
41cb4eb569 | |||
5b3a72820a | |||
be52a51055 | |||
964af82904 | |||
99eafcb352 | |||
ce9f621b8b | |||
2e3d0a755c | |||
70edd165ee | |||
b4a7986c8a | |||
7aa9038a61 | |||
a8edc19f34 | |||
730c25a9a1 | |||
f32143b7ac | |||
a827185bf1 | |||
f850fc2526 | |||
f1395357c5 | |||
63f206cc6a | |||
fa5e778d0c | |||
c2c782985c | |||
57848ff29b | |||
3a956919b2 |
122 changed files with 4385 additions and 1709 deletions
24
.env.example
24
.env.example
|
@ -1,4 +1,4 @@
|
|||
DB_TYPE = (mysql|sqlite|postgres) # default ist mysql
|
||||
DB_TYPE = (mysql|postgres) # default ist mysql
|
||||
|
||||
## BSP für mysql
|
||||
DB_PORT = 3306
|
||||
|
@ -14,28 +14,10 @@ DB_NAME = database_name
|
|||
DB_USERNAME = database_username
|
||||
DB_PASSWORD = database_password
|
||||
|
||||
## BSP für sqlite
|
||||
DB_HOST = filename.db
|
||||
|
||||
## Dev only
|
||||
SERVER_PORT = portnumber
|
||||
|
||||
JWT_SECRET = ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 # besitzt default
|
||||
JWT_EXPIRATION = [0-9]*(y|d|h|m|s) # default ist 15m
|
||||
REFRESH_EXPIRATION = [0-9]*(y|d|h|m|s) # default ist 1d
|
||||
PWA_REFRESH_EXPIRATION = [0-9]*(y|d|h|m|s) # default ist 5d
|
||||
|
||||
MAIL_USERNAME = mail_username
|
||||
MAIL_PASSWORD = mail_password
|
||||
MAIL_HOST = mail_hoststring
|
||||
MAIL_PORT = mail_portnumber # default ist 587
|
||||
MAIL_SECURE = (true|false) # true für port 465, false für anders gewählten port
|
||||
|
||||
CLUB_NAME = clubname #default FF Admin
|
||||
CLUB_WEBSITE = https://my-club-website-url #optional, muss aber mit http:// oder https:// beginnen
|
||||
|
||||
BACKUP_INTERVAL = number of days (min 1) # default 1
|
||||
BACKUP_COPIES = number of parallel copies # default 7
|
||||
BACKUP_AUTO_RESTORE = (true|false) # default ist true
|
||||
APPLICATION_SECRET = mysecret
|
||||
|
||||
USE_SECURITY_STRICT_LIMIT = (true|false) # default ist true
|
||||
SECURITY_STRICT_LIMIT_WINDOW = [0-9]*(y|d|h|m|s) # default ist 15m
|
||||
|
|
|
@ -37,6 +37,7 @@ RUN mkdir -p /app/files
|
|||
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
||||
|
||||
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/node_modules /app/node_modules
|
||||
COPY --from=build /app/package.json /app/package.json
|
||||
|
|
21
README.md
21
README.md
|
@ -8,6 +8,8 @@ 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).
|
||||
|
||||
Das Handbuch zur Anwendung finden sie unter [https://ff-admin.de/ff-admin-handbook](https://ff-admin.de/ff-admin-handbook).
|
||||
|
||||
## Installation
|
||||
|
||||
Das Image exposed nur den Port 5000. Die Env-Variable SERVER_PORT kann nur im lokal ausführenden dev-Kontext verwendet werden.
|
||||
|
@ -25,26 +27,13 @@ services:
|
|||
container_name: ff_member_administration_server
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DB_TYPE=<mysql|sqlite|postgres> # default ist auf mysql gesetzt
|
||||
- DB_TYPE=<mysql|postgres> # default ist auf mysql gesetzt
|
||||
- DB_HOST=ff-db
|
||||
- DB_PORT=<number> # default ist auf 3306 gesetzt
|
||||
- DB_NAME=ffadmin
|
||||
- DB_USERNAME=administration_backend
|
||||
- DB_PASSWORD=<dbuserpasswd>
|
||||
- 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
|
||||
- APPLICATION_SECRET=<tobemodified>
|
||||
- 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_REQUEST_COUNT = strict_request_count # default ist 15
|
||||
|
@ -91,8 +80,6 @@ networks:
|
|||
|
||||
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:
|
||||
|
||||
```sh
|
||||
|
|
1318
package-lock.json
generated
1318
package-lock.json
generated
File diff suppressed because it is too large
Load diff
49
package.json
49
package.json
|
@ -1,12 +1,13 @@
|
|||
{
|
||||
"name": "ff-admin-server",
|
||||
"version": "1.4.1",
|
||||
"version": "1.7.0",
|
||||
"description": "Feuerwehr/Verein Mitgliederverwaltung Server",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"start_ts": "ts-node src/index.ts",
|
||||
"typeorm": "typeorm-ts-node-commonjs",
|
||||
"migrate": "set DBMODE=migration && npx typeorm-ts-node-commonjs migration:generate ./src/migrations/%npm_config_name% -d ./src/data-source.ts",
|
||||
"migrate-empty": "set DBMODE=migration && npx typeorm-ts-node-commonjs migration:create ./src/migrations/%npm_config_name%",
|
||||
"synchronize-database": "set DBMODE=update-database && npx typeorm-ts-node-commonjs schema:sync -d ./src/data-source.ts",
|
||||
"update-database": "set DBMODE=update-database && npx typeorm-ts-node-commonjs migration:run -d ./src/data-source.ts",
|
||||
"revert-database": "set DBMODE=update-database && npx typeorm-ts-node-commonjs migration:revert -d ./src/data-source.ts",
|
||||
|
@ -25,52 +26,56 @@
|
|||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"crypto": "^1.0.1",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^5.1.0",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"express-validator": "^7.2.1",
|
||||
"handlebars": "^4.7.8",
|
||||
"helmet": "^8.0.0",
|
||||
"helmet": "^8.1.0",
|
||||
"ics": "^3.8.1",
|
||||
"ip": "^2.0.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.uniqby": "^4.7.0",
|
||||
"moment": "^2.30.1",
|
||||
"morgan": "^1.10.0",
|
||||
"ms": "^2.1.3",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql": "^2.18.1",
|
||||
"multer": "^2.0.1",
|
||||
"node-schedule": "^2.1.1",
|
||||
"nodemailer": "^6.10.1",
|
||||
"nodemailer": "^7.0.3",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pg": "^8.13.1",
|
||||
"puppeteer": "^24.6.1",
|
||||
"pg": "^8.16.0",
|
||||
"puppeteer": "^24.10.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rss-parser": "^3.13.0",
|
||||
"socket.io": "^4.7.5",
|
||||
"sharp": "^0.34.2",
|
||||
"sharp-ico": "^0.1.5",
|
||||
"socket.io": "^4.8.1",
|
||||
"speakeasy": "^2.0.0",
|
||||
"sqlite3": "^5.1.7",
|
||||
"typeorm": "^0.3.20",
|
||||
"uuid": "^11.1.0"
|
||||
"typeorm": "^0.3.24",
|
||||
"uuid": "^11.1.0",
|
||||
"validator": "^13.15.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.14",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/ip": "^1.1.3",
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"@types/lodash.clonedeep": "^4.5.9",
|
||||
"@types/lodash.uniqby": "^4.7.9",
|
||||
"@types/morgan": "^1.9.9",
|
||||
"@types/morgan": "^1.9.10",
|
||||
"@types/ms": "^2.1.0",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/mysql": "^2.15.21",
|
||||
"@types/node": "^22.14.1",
|
||||
"@types/node-schedule": "^2.1.6",
|
||||
"@types/nodemailer": "^6.4.14",
|
||||
"@types/pg": "~8.11.12",
|
||||
"@types/multer": "^1.4.13",
|
||||
"@types/node": "^22.15.30",
|
||||
"@types/node-schedule": "^2.1.7",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/pg": "~8.15.4",
|
||||
"@types/qrcode": "~1.5.5",
|
||||
"@types/speakeasy": "^2.0.10",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/validator": "^13.15.1",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
|
BIN
src/assets/admin-logo.png
Normal file
BIN
src/assets/admin-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
src/assets/icon.png
Normal file
BIN
src/assets/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
|
@ -5,6 +5,7 @@ export interface CreateMemberCommand {
|
|||
nameaffix: string;
|
||||
birthdate: Date;
|
||||
internalId?: string;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export interface UpdateMemberCommand {
|
||||
|
@ -15,6 +16,7 @@ export interface UpdateMemberCommand {
|
|||
nameaffix: string;
|
||||
birthdate: Date;
|
||||
internalId?: string;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export interface DeleteMemberCommand {
|
||||
|
|
|
@ -23,6 +23,7 @@ export default abstract class MemberCommandHandler {
|
|||
nameaffix: createMember.nameaffix,
|
||||
birthdate: createMember.birthdate,
|
||||
internalId: createMember.internalId,
|
||||
note: createMember.note,
|
||||
})
|
||||
.execute()
|
||||
.then((result) => {
|
||||
|
@ -49,6 +50,7 @@ export default abstract class MemberCommandHandler {
|
|||
nameaffix: updateMember.nameaffix,
|
||||
birthdate: updateMember.birthdate,
|
||||
internalId: updateMember.internalId,
|
||||
note: updateMember.note,
|
||||
})
|
||||
.where("id = :id", { id: updateMember.id })
|
||||
.execute()
|
||||
|
|
23
src/command/club/member/memberEducationCommand.ts
Normal file
23
src/command/club/member/memberEducationCommand.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
export interface CreateMemberEducationCommand {
|
||||
start: Date;
|
||||
end?: Date;
|
||||
note?: string;
|
||||
place?: string;
|
||||
memberId: string;
|
||||
educationId: number;
|
||||
}
|
||||
|
||||
export interface UpdateMemberEducationCommand {
|
||||
id: number;
|
||||
start: Date;
|
||||
end?: Date;
|
||||
note?: string;
|
||||
place?: string;
|
||||
memberId: string;
|
||||
educationId: number;
|
||||
}
|
||||
|
||||
export interface DeleteMemberEducationCommand {
|
||||
id: number;
|
||||
memberId: string;
|
||||
}
|
82
src/command/club/member/memberEducationCommandHandler.ts
Normal file
82
src/command/club/member/memberEducationCommandHandler.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { dataSource } from "../../../data-source";
|
||||
import { memberEducations } from "../../../entity/club/member/memberEducations";
|
||||
import DatabaseActionException from "../../../exceptions/databaseActionException";
|
||||
import InternalException from "../../../exceptions/internalException";
|
||||
import {
|
||||
CreateMemberEducationCommand,
|
||||
DeleteMemberEducationCommand,
|
||||
UpdateMemberEducationCommand,
|
||||
} from "./memberEducationCommand";
|
||||
|
||||
export default abstract class MemberEducationCommandHandler {
|
||||
/**
|
||||
* @description create memberEducation
|
||||
* @param {CreateMemberEducationCommand} createMemberEducation
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
static async create(createMemberEducation: CreateMemberEducationCommand): Promise<number> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(memberEducations)
|
||||
.values({
|
||||
note: createMemberEducation.note,
|
||||
place: createMemberEducation.place,
|
||||
start: createMemberEducation.start,
|
||||
end: createMemberEducation.end,
|
||||
memberId: createMemberEducation.memberId,
|
||||
educationId: createMemberEducation.educationId,
|
||||
})
|
||||
.execute()
|
||||
.then((result) => {
|
||||
return result.identifiers[0].id;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new DatabaseActionException("CREATE", "memberEducation", err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description update memberEducation
|
||||
* @param {UpdateMemberEducationCommand} updateMemberEducation
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async update(updateMemberEducation: UpdateMemberEducationCommand): Promise<void> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.update(memberEducations)
|
||||
.set({
|
||||
note: updateMemberEducation.note,
|
||||
start: updateMemberEducation.start,
|
||||
end: updateMemberEducation.end,
|
||||
place: updateMemberEducation.place,
|
||||
educationId: updateMemberEducation.educationId,
|
||||
})
|
||||
.where("id = :id", { id: updateMemberEducation.id })
|
||||
.andWhere("memberId = :memberId", { memberId: updateMemberEducation.memberId })
|
||||
.execute()
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new DatabaseActionException("UPDATE", "memberEducation", err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description delete memberEducation
|
||||
* @param {DeleteMemberEducationCommand} deleteMemberEducation
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async delete(deleteMemberEducation: DeleteMemberEducationCommand): Promise<void> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(memberEducations)
|
||||
.where("id = :id", { id: deleteMemberEducation.id })
|
||||
.andWhere("memberId = :memberId", { memberId: deleteMemberEducation.memberId })
|
||||
.execute()
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new DatabaseActionException("DELETE", "memberEducation", err);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -34,18 +34,32 @@ export default abstract class ProtocolAgendaCommandHandler {
|
|||
|
||||
/**
|
||||
* @description sync protocolAgenda
|
||||
* @param {number} protocolId
|
||||
* @param {Array<SynchronizeProtocolAgendaCommand>} syncProtocolAgenda
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async sync(syncProtocolAgenda: Array<SynchronizeProtocolAgendaCommand>): Promise<void> {
|
||||
static async sync(protocolId: number, syncProtocolAgenda: Array<SynchronizeProtocolAgendaCommand>): Promise<void> {
|
||||
let currentAgenda = await ProtocolAgendaService.getAll(protocolId);
|
||||
return await dataSource
|
||||
.transaction(async (transactionalEntityManager) => {
|
||||
let removed = currentAgenda.filter((ca) => !syncProtocolAgenda.some((spa) => spa.id == ca.id));
|
||||
|
||||
for (const agenda of syncProtocolAgenda) {
|
||||
await transactionalEntityManager
|
||||
.createQueryBuilder()
|
||||
.update(protocolAgenda)
|
||||
.set(agenda)
|
||||
.where({ id: agenda.id })
|
||||
.where({ id: agenda.id, protocolId })
|
||||
.execute();
|
||||
}
|
||||
|
||||
if (removed.length != 0) {
|
||||
await transactionalEntityManager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(protocolAgenda)
|
||||
.where("id IN (:...ids)", { ids: removed.map((m) => m.id) })
|
||||
.andWhere({ protocolId })
|
||||
.execute();
|
||||
}
|
||||
})
|
||||
|
|
|
@ -33,12 +33,19 @@ export default abstract class ProtocolDecisionCommandHandler {
|
|||
}
|
||||
/**
|
||||
* @description sync protocolDecision
|
||||
* @param {number} protocolId
|
||||
* @param {Array<SynchronizeProtocolDecisionCommand>} syncProtocolDecisions
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async sync(syncProtocolDecisions: Array<SynchronizeProtocolDecisionCommand>): Promise<void> {
|
||||
static async sync(
|
||||
protocolId: number,
|
||||
syncProtocolDecisions: Array<SynchronizeProtocolDecisionCommand>
|
||||
): Promise<void> {
|
||||
let currentDecision = await ProtocolDecisionService.getAll(protocolId);
|
||||
return await dataSource
|
||||
.transaction(async (transactionalEntityManager) => {
|
||||
let removed = currentDecision.filter((ca) => !syncProtocolDecisions.some((spa) => spa.id == ca.id));
|
||||
|
||||
for (const decision of syncProtocolDecisions) {
|
||||
await transactionalEntityManager
|
||||
.createQueryBuilder()
|
||||
|
@ -47,8 +54,17 @@ export default abstract class ProtocolDecisionCommandHandler {
|
|||
.where({ id: decision.id })
|
||||
.execute();
|
||||
}
|
||||
|
||||
if (removed.length != 0) {
|
||||
await transactionalEntityManager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(protocolDecision)
|
||||
.where("id IN (:...ids)", { ids: removed.map((m) => m.id) })
|
||||
.andWhere({ protocolId })
|
||||
.execute();
|
||||
}
|
||||
})
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new DatabaseActionException("SYNC", "protocolDecision", err);
|
||||
});
|
||||
|
|
|
@ -33,12 +33,16 @@ export default abstract class ProtocolVotingCommandHandler {
|
|||
}
|
||||
/**
|
||||
* @description sync protocolVoting
|
||||
* @param {number} protocolId
|
||||
* @param {Array<SynchronizeProtocolVotingCommand>} syncProtocolVotings
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async sync(syncProtocolVotings: Array<SynchronizeProtocolVotingCommand>): Promise<void> {
|
||||
static async sync(protocolId: number, syncProtocolVotings: Array<SynchronizeProtocolVotingCommand>): Promise<void> {
|
||||
let currentVoting = await ProtocolVotingService.getAll(protocolId);
|
||||
return await dataSource
|
||||
.transaction(async (transactionalEntityManager) => {
|
||||
let removed = currentVoting.filter((ca) => !syncProtocolVotings.some((spa) => spa.id == ca.id));
|
||||
|
||||
for (const voting of syncProtocolVotings) {
|
||||
await transactionalEntityManager
|
||||
.createQueryBuilder()
|
||||
|
@ -47,8 +51,17 @@ export default abstract class ProtocolVotingCommandHandler {
|
|||
.where({ id: voting.id })
|
||||
.execute();
|
||||
}
|
||||
|
||||
if (removed.length != 0) {
|
||||
await transactionalEntityManager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(protocolVoting)
|
||||
.where("id IN (:...ids)", { ids: removed.map((m) => m.id) })
|
||||
.andWhere({ protocolId })
|
||||
.execute();
|
||||
}
|
||||
})
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new DatabaseActionException("SYNC", "protocolVoting", err);
|
||||
});
|
||||
|
|
14
src/command/configuration/education/educationCommand.ts
Normal file
14
src/command/configuration/education/educationCommand.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
export interface CreateEducationCommand {
|
||||
education: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface UpdateEducationCommand {
|
||||
id: number;
|
||||
education: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface DeleteEducationCommand {
|
||||
id: number;
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import { dataSource } from "../../../data-source";
|
||||
import { education } from "../../../entity/configuration/education";
|
||||
import DatabaseActionException from "../../../exceptions/databaseActionException";
|
||||
import { CreateEducationCommand, DeleteEducationCommand, UpdateEducationCommand } from "./educationCommand";
|
||||
|
||||
export default abstract class EducationCommandHandler {
|
||||
/**
|
||||
* @description create education
|
||||
* @param {CreateEducationCommand} createEducation
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
static async create(createEducation: CreateEducationCommand): Promise<number> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(education)
|
||||
.values({
|
||||
education: createEducation.education,
|
||||
description: createEducation.description,
|
||||
})
|
||||
.execute()
|
||||
.then((result) => {
|
||||
return result.identifiers[0].id;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw new DatabaseActionException("CREATE", "education", err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description update education
|
||||
* @param {UpdateEducationCommand} updateEducation
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async update(updateEducation: UpdateEducationCommand): Promise<void> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.update(education)
|
||||
.set({
|
||||
education: updateEducation.education,
|
||||
description: updateEducation.description,
|
||||
})
|
||||
.where("id = :id", { id: updateEducation.id })
|
||||
.execute()
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new DatabaseActionException("UPDATE", "education", err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description delete education
|
||||
* @param {DeleteEducationCommand} deleteEducation
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async delete(deleteEducation: DeleteEducationCommand): Promise<void> {
|
||||
return await dataSource
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(education)
|
||||
.where("id = :id", { id: deleteEducation.id })
|
||||
.execute()
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
throw new DatabaseActionException("DELETE", "education", err);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import { NewsletterConfigType } from "../../../enums/newsletterConfigType";
|
||||
import { NewsletterConfigEnum } from "../../../enums/newsletterConfigEnum";
|
||||
|
||||
export interface SetNewsletterConfigCommand {
|
||||
comTypeId: number;
|
||||
config: NewsletterConfigType;
|
||||
config: NewsletterConfigEnum;
|
||||
}
|
||||
|
||||
export interface DeleteNewsletterConfigCommand {
|
||||
|
|
10
src/command/management/setting/settingCommand.ts
Normal file
10
src/command/management/setting/settingCommand.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export interface CreateOrUpdateSettingCommand {
|
||||
topic: string;
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface DeleteSettingCommand {
|
||||
topic: string;
|
||||
key: string;
|
||||
}
|
50
src/command/management/setting/settingCommandHandler.ts
Normal file
50
src/command/management/setting/settingCommandHandler.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ export default abstract class InviteCommandHandler {
|
|||
lastname: createInvite.lastname,
|
||||
secret: createInvite.secret,
|
||||
})
|
||||
.orUpdate(["firstName", "lastName", "token", "secret"], ["mail"])
|
||||
.orUpdate(["firstname", "lastname", "token", "secret"], ["mail"])
|
||||
.execute()
|
||||
.then((result) => {
|
||||
return token;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { LoginRoutineEnum } from "../../../enums/loginRoutineEnum";
|
||||
|
||||
export interface CreateUserCommand {
|
||||
mail: string;
|
||||
username: string;
|
||||
|
@ -5,6 +7,7 @@ export interface CreateUserCommand {
|
|||
lastname: string;
|
||||
secret: string;
|
||||
isOwner: boolean;
|
||||
routine: LoginRoutineEnum;
|
||||
}
|
||||
|
||||
export interface UpdateUserCommand {
|
||||
|
@ -18,6 +21,7 @@ export interface UpdateUserCommand {
|
|||
export interface UpdateUserSecretCommand {
|
||||
id: string;
|
||||
secret: string;
|
||||
routine: LoginRoutineEnum;
|
||||
}
|
||||
|
||||
export interface TransferUserOwnerCommand {
|
||||
|
|
|
@ -31,6 +31,7 @@ export default abstract class UserCommandHandler {
|
|||
lastname: createUser.lastname,
|
||||
secret: createUser.secret,
|
||||
isOwner: createUser.isOwner,
|
||||
routine: createUser.routine,
|
||||
})
|
||||
.execute()
|
||||
.then((result) => {
|
||||
|
@ -75,6 +76,7 @@ export default abstract class UserCommandHandler {
|
|||
.update(user)
|
||||
.set({
|
||||
secret: updateUser.secret,
|
||||
routine: updateUser.routine,
|
||||
})
|
||||
.where("id = :id", { id: updateUser.id })
|
||||
.execute()
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { dataSource } from "../data-source";
|
||||
import { refresh } from "../entity/refresh";
|
||||
import { PWA_REFRESH_EXPIRATION, REFRESH_EXPIRATION } from "../env.defaults";
|
||||
import DatabaseActionException from "../exceptions/databaseActionException";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import SettingHelper from "../helpers/settingsHelper";
|
||||
import { StringHelper } from "../helpers/stringHelper";
|
||||
import UserService from "../service/management/userService";
|
||||
import { CreateRefreshCommand, DeleteRefreshCommand } from "./refreshCommand";
|
||||
import ms from "ms";
|
||||
|
||||
|
@ -25,8 +23,8 @@ export default abstract class RefreshCommandHandler {
|
|||
token: refreshToken,
|
||||
userId: createRefresh.userId,
|
||||
expiry: createRefresh.isFromPwa
|
||||
? new Date(Date.now() + ms(PWA_REFRESH_EXPIRATION))
|
||||
: new Date(Date.now() + ms(REFRESH_EXPIRATION)),
|
||||
? new Date(Date.now() + ms(SettingHelper.getSetting("session.pwa_refresh_expiration")))
|
||||
: new Date(Date.now() + ms(SettingHelper.getSetting("session.refresh_expiration"))),
|
||||
})
|
||||
.execute()
|
||||
.then((result) => {
|
||||
|
|
|
@ -49,6 +49,14 @@ import {
|
|||
import CommunicationCommandHandler from "../../../command/club/member/communicationCommandHandler";
|
||||
import { PdfExport } from "../../../helpers/pdfExport";
|
||||
import { PermissionModule } from "../../../type/permissionTypes";
|
||||
import MemberEducationFactory from "../../../factory/admin/club/member/memberEducation";
|
||||
import MemberEducationService from "../../../service/club/member/memberEducationService";
|
||||
import {
|
||||
CreateMemberEducationCommand,
|
||||
DeleteMemberEducationCommand,
|
||||
UpdateMemberEducationCommand,
|
||||
} from "../../../command/club/member/memberEducationCommand";
|
||||
import MemberEducationCommandHandler from "../../../command/club/member/memberEducationCommandHandler";
|
||||
|
||||
/**
|
||||
* @description get all members
|
||||
|
@ -92,6 +100,18 @@ 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
|
||||
* @param req {Request} Express req object
|
||||
|
@ -132,6 +152,7 @@ export async function getMemberPrintoutById(req: Request, res: Response): Promis
|
|||
let qualifications = await MemberQualificationService.getAll(memberId);
|
||||
let positions = await MemberExecutivePositionService.getAll(memberId);
|
||||
let communications = await CommunicationService.getAll(memberId);
|
||||
let educations = await MemberEducationService.getAll(memberId);
|
||||
|
||||
let pdf = await PdfExport.renderFile({
|
||||
title: "Mitglieder-Ausdruck",
|
||||
|
@ -145,6 +166,7 @@ export async function getMemberPrintoutById(req: Request, res: Response): Promis
|
|||
qualifications,
|
||||
positions,
|
||||
communications,
|
||||
educations,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -183,6 +205,19 @@ export async function getMembershipStatisticsById(req: Request, res: Response):
|
|||
res.json(MembershipFactory.mapToBaseStatistics(member));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get member total statistics by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getMembershipTotalStatisticsById(req: Request, res: Response): Promise<any> {
|
||||
const memberId = req.params.memberId;
|
||||
let member = await MembershipService.getTotalStatisticsById(memberId);
|
||||
|
||||
res.json(MembershipFactory.mapToSingleTotalStatistic(member));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get membership by member and record
|
||||
* @param req {Request} Express req object
|
||||
|
@ -251,6 +286,33 @@ export async function getQualificationByMemberAndRecord(req: Request, res: Respo
|
|||
res.json(MemberQualificationFactory.mapToSingle(qualification));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get educations by member
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getEducationsByMember(req: Request, res: Response): Promise<any> {
|
||||
const memberId = req.params.memberId;
|
||||
let educations = await MemberEducationService.getAll(memberId);
|
||||
|
||||
res.json(MemberEducationFactory.mapToBase(educations));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get education by member and record
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getEducationByMemberAndRecord(req: Request, res: Response): Promise<any> {
|
||||
const memberId = req.params.memberId;
|
||||
const recordId = parseInt(req.params.id);
|
||||
let education = await MemberEducationService.getById(memberId, recordId);
|
||||
|
||||
res.json(MemberEducationFactory.mapToSingle(education));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get executive positions by member
|
||||
* @param req {Request} Express req object
|
||||
|
@ -318,6 +380,7 @@ export async function createMember(req: Request, res: Response): Promise<any> {
|
|||
const nameaffix = req.body.nameaffix;
|
||||
const birthdate = req.body.birthdate;
|
||||
const internalId = req.body.internalId || null;
|
||||
const note = req.body.note || null;
|
||||
|
||||
let createMember: CreateMemberCommand = {
|
||||
salutationId,
|
||||
|
@ -326,6 +389,7 @@ export async function createMember(req: Request, res: Response): Promise<any> {
|
|||
nameaffix,
|
||||
birthdate,
|
||||
internalId,
|
||||
note,
|
||||
};
|
||||
let memberId = await MemberCommandHandler.create(createMember);
|
||||
|
||||
|
@ -401,6 +465,33 @@ export async function addQualificationToMember(req: Request, res: Response): Pro
|
|||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description add education to member
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function addEducationToMember(req: Request, res: Response): Promise<any> {
|
||||
const memberId = req.params.memberId;
|
||||
const note = req.body.note;
|
||||
const place = req.body.place;
|
||||
const start = req.body.start;
|
||||
const end = req.body.end || null;
|
||||
const educationId = req.body.educationId;
|
||||
|
||||
let createMemberEducation: CreateMemberEducationCommand = {
|
||||
note,
|
||||
start,
|
||||
end,
|
||||
place,
|
||||
memberId,
|
||||
educationId,
|
||||
};
|
||||
await MemberEducationCommandHandler.create(createMemberEducation);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description add executive positions to member
|
||||
* @param req {Request} Express req object
|
||||
|
@ -479,6 +570,7 @@ export async function updateMemberById(req: Request, res: Response): Promise<any
|
|||
const nameaffix = req.body.nameaffix;
|
||||
const birthdate = req.body.birthdate;
|
||||
const internalId = req.body.internalId || null;
|
||||
const note = req.body.note || null;
|
||||
|
||||
let updateMember: UpdateMemberCommand = {
|
||||
id: memberId,
|
||||
|
@ -488,6 +580,7 @@ export async function updateMemberById(req: Request, res: Response): Promise<any
|
|||
nameaffix,
|
||||
birthdate,
|
||||
internalId,
|
||||
note,
|
||||
};
|
||||
await MemberCommandHandler.update(updateMember);
|
||||
|
||||
|
@ -577,6 +670,35 @@ export async function updateQualificationOfMember(req: Request, res: Response):
|
|||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description update education of member
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function updateEducationOfMember(req: Request, res: Response): Promise<any> {
|
||||
const memberId = req.params.memberId;
|
||||
const recordId = parseInt(req.params.recordId);
|
||||
const start = req.body.start;
|
||||
const end = req.body.end || null;
|
||||
const note = req.body.note;
|
||||
const place = req.body.place;
|
||||
const educationId = req.body.educationId;
|
||||
|
||||
let updateMemberEducation: UpdateMemberEducationCommand = {
|
||||
id: recordId,
|
||||
start,
|
||||
end,
|
||||
note,
|
||||
place,
|
||||
memberId,
|
||||
educationId,
|
||||
};
|
||||
await MemberEducationCommandHandler.update(updateMemberEducation);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description update executive position of member
|
||||
* @param req {Request} Express req object
|
||||
|
@ -717,6 +839,25 @@ export async function deleteQualificationOfMember(req: Request, res: Response):
|
|||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description delete education from member
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function deleteEducationsOfMember(req: Request, res: Response): Promise<any> {
|
||||
const memberId = req.params.memberId;
|
||||
const recordId = parseInt(req.params.recordId);
|
||||
|
||||
let deleteMemberEducation: DeleteMemberEducationCommand = {
|
||||
id: recordId,
|
||||
memberId,
|
||||
};
|
||||
await MemberEducationCommandHandler.delete(deleteMemberEducation);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description delete executive position from member
|
||||
* @param req {Request} Express req object
|
||||
|
|
|
@ -249,12 +249,7 @@ export async function createProtocolPrintoutById(req: Request, res: Response): P
|
|||
title: protocol.title,
|
||||
summary: protocol.summary,
|
||||
iteration: iteration + 1,
|
||||
date: new Date(protocol.date).toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
}),
|
||||
date: protocol.date,
|
||||
start: protocol.starttime,
|
||||
end: protocol.endtime,
|
||||
agenda: agenda.sort((a, b) => a.sort - b.sort),
|
||||
|
@ -324,7 +319,7 @@ export async function synchronizeProtocolAgendaById(req: Request, res: Response)
|
|||
protocolId,
|
||||
})
|
||||
);
|
||||
await ProtocolAgendaCommandHandler.sync(syncAgenda);
|
||||
await ProtocolAgendaCommandHandler.sync(protocolId, syncAgenda);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
@ -348,7 +343,7 @@ export async function synchronizeProtocolDecisonsById(req: Request, res: Respons
|
|||
protocolId,
|
||||
})
|
||||
);
|
||||
await ProtocolDecisionCommandHandler.sync(syncDecision);
|
||||
await ProtocolDecisionCommandHandler.sync(protocolId, syncDecision);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
@ -375,7 +370,7 @@ export async function synchronizeProtocolVotingsById(req: Request, res: Response
|
|||
protocolId,
|
||||
})
|
||||
);
|
||||
await ProtocolVotingCommandHandler.sync(syncVoting);
|
||||
await ProtocolVotingCommandHandler.sync(protocolId, syncVoting);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
|
91
src/controller/admin/configuration/educationController.ts
Normal file
91
src/controller/admin/configuration/educationController.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
import { Request, Response } from "express";
|
||||
import EducationService from "../../../service/configuration/education";
|
||||
import EducationFactory from "../../../factory/admin/configuration/education";
|
||||
import {
|
||||
CreateEducationCommand,
|
||||
DeleteEducationCommand,
|
||||
UpdateEducationCommand,
|
||||
} from "../../../command/configuration/education/educationCommand";
|
||||
import EducationCommandHandler from "../../../command/configuration/education/educationCommandHandler";
|
||||
|
||||
/**
|
||||
* @description get all educations
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getAllEducations(req: Request, res: Response): Promise<any> {
|
||||
let educations = await EducationService.getAll();
|
||||
|
||||
res.json(EducationFactory.mapToBase(educations));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get education by id
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getEducationById(req: Request, res: Response): Promise<any> {
|
||||
const id = parseInt(req.params.id);
|
||||
let education = await EducationService.getById(id);
|
||||
|
||||
res.json(EducationFactory.mapToSingle(education));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description create new education
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function createEducation(req: Request, res: Response): Promise<any> {
|
||||
const education = req.body.education;
|
||||
const description = req.body.description;
|
||||
|
||||
let createEducation: CreateEducationCommand = {
|
||||
education: education,
|
||||
description: description,
|
||||
};
|
||||
await EducationCommandHandler.create(createEducation);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description update education
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function updateEducation(req: Request, res: Response): Promise<any> {
|
||||
const id = parseInt(req.params.id);
|
||||
const education = req.body.education;
|
||||
const description = req.body.description;
|
||||
|
||||
let updateEducation: UpdateEducationCommand = {
|
||||
id: id,
|
||||
education: education,
|
||||
description: description,
|
||||
};
|
||||
await EducationCommandHandler.update(updateEducation);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description delete education
|
||||
* @param req {Request} Express req object
|
||||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function deleteEducation(req: Request, res: Response): Promise<any> {
|
||||
const id = parseInt(req.params.id);
|
||||
|
||||
let deleteEducation: DeleteEducationCommand = {
|
||||
id: id,
|
||||
};
|
||||
await EducationCommandHandler.delete(deleteEducation);
|
||||
|
||||
res.sendStatus(204);
|
||||
}
|
116
src/controller/admin/management/settingController.ts
Normal file
116
src/controller/admin/management/settingController.ts
Normal file
|
@ -0,0 +1,116 @@
|
|||
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";
|
||||
import UserCommandHandler from "../../../command/management/user/userCommandHandler";
|
||||
import MailHelper from "../../../helpers/mailHelper";
|
||||
import { CLUB_NAME } from "../../../env.defaults";
|
||||
import { UpdateUserPermissionsCommand } from "../../../command/management/user/userPermissionCommand";
|
||||
import UserPermissionCommandHandler from "../../../command/management/user/userPermissionCommandHandler";
|
||||
import BadRequestException from "../../../exceptions/badRequestException";
|
||||
import SettingHelper from "../../../helpers/settingsHelper";
|
||||
|
||||
/**
|
||||
* @description get All users
|
||||
|
@ -157,7 +157,7 @@ export async function deleteUser(req: Request, res: Response): Promise<any> {
|
|||
// sendmail
|
||||
await MailHelper.sendMail(
|
||||
mail,
|
||||
`Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`,
|
||||
`Email Bestätigung für Mitglieder Admin-Portal von ${SettingHelper.getSetting("club.name")}`,
|
||||
`Ihr Nutzerkonto des Adminportals wurde erfolgreich gelöscht.`
|
||||
);
|
||||
} catch (error) {}
|
||||
|
|
|
@ -12,8 +12,8 @@ import WebapiCommandHandler from "../../../command/management/webapi/webapiComma
|
|||
import { UpdateWebapiPermissionsCommand } from "../../../command/management/webapi/webapiPermissionCommand";
|
||||
import WebapiPermissionCommandHandler from "../../../command/management/webapi/webapiPermissionCommandHandler";
|
||||
import { JWTHelper } from "../../../helpers/jwtHelper";
|
||||
import { CLUB_NAME } from "../../../env.defaults";
|
||||
import { StringHelper } from "../../../helpers/stringHelper";
|
||||
import SettingHelper from "../../../helpers/settingsHelper";
|
||||
|
||||
/**
|
||||
* @description get All apis
|
||||
|
@ -78,7 +78,7 @@ export async function createWebapi(req: Request, res: Response): Promise<any> {
|
|||
|
||||
let token = await JWTHelper.create(
|
||||
{
|
||||
iss: CLUB_NAME,
|
||||
iss: SettingHelper.getSetting("club.name"),
|
||||
sub: "api_token_retrieve",
|
||||
aud: StringHelper.random(32),
|
||||
},
|
||||
|
|
|
@ -8,6 +8,25 @@ import UserService from "../service/management/userService";
|
|||
import speakeasy from "speakeasy";
|
||||
import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException";
|
||||
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
|
||||
|
@ -17,19 +36,25 @@ import RefreshService from "../service/refreshService";
|
|||
*/
|
||||
export async function login(req: Request, res: Response): Promise<any> {
|
||||
let username = req.body.username;
|
||||
let totp = req.body.totp;
|
||||
let passedSecret = req.body.secret;
|
||||
|
||||
let { id, secret } = await UserService.getByUsername(username);
|
||||
let { id } = await UserService.getByUsername(username);
|
||||
let { secret, routine } = await UserService.getUserSecretAndRoutine(id);
|
||||
|
||||
let valid = speakeasy.totp.verify({
|
||||
secret: secret,
|
||||
encoding: "base32",
|
||||
token: totp,
|
||||
window: 2,
|
||||
});
|
||||
let valid = false;
|
||||
if (routine == LoginRoutineEnum.totp) {
|
||||
valid = speakeasy.totp.verify({
|
||||
secret: secret,
|
||||
encoding: "base32",
|
||||
token: passedSecret,
|
||||
window: 2,
|
||||
});
|
||||
} else {
|
||||
valid = passedSecret == secret;
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
throw new UnauthorizedRequestException("Token not valid or expired");
|
||||
throw new UnauthorizedRequestException("Credentials not valid or expired");
|
||||
}
|
||||
|
||||
let accessToken = await JWTHelper.buildToken(id);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Request, Response } from "express";
|
||||
import { JWTHelper } from "../helpers/jwtHelper";
|
||||
import { JWTToken } from "../type/jwtTypes";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import RefreshCommandHandler from "../command/refreshCommandHandler";
|
||||
import { CreateRefreshCommand } from "../command/refreshCommand";
|
||||
|
@ -15,10 +14,9 @@ import MailHelper from "../helpers/mailHelper";
|
|||
import InviteService from "../service/management/inviteService";
|
||||
import UserService from "../service/management/userService";
|
||||
import CustomRequestException from "../exceptions/customRequestException";
|
||||
import { CLUB_NAME } from "../env.defaults";
|
||||
import { CreateUserPermissionCommand } from "../command/management/user/userPermissionCommand";
|
||||
import UserPermissionCommandHandler from "../command/management/user/userPermissionCommandHandler";
|
||||
import InviteFactory from "../factory/admin/management/invite";
|
||||
import SettingHelper from "../helpers/settingsHelper";
|
||||
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
|
||||
|
||||
/**
|
||||
* @description get all invites
|
||||
|
@ -38,7 +36,7 @@ export async function getInvites(req: Request, res: Response): Promise<any> {
|
|||
* @param res {Response} Express res object
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function inviteUser(req: Request, res: Response, isInvite: boolean = true): Promise<any> {
|
||||
export async function inviteUser(req: Request, res: Response, isSetup: boolean = false): Promise<any> {
|
||||
let origin = req.headers.origin;
|
||||
let username = req.body.username;
|
||||
let mail = req.body.mail;
|
||||
|
@ -59,7 +57,7 @@ export async function inviteUser(req: Request, res: Response, isInvite: boolean
|
|||
throw new CustomRequestException(409, "Username and Mail are already in use");
|
||||
}
|
||||
|
||||
var secret = speakeasy.generateSecret({ length: 20, name: `FF Admin ${CLUB_NAME}` });
|
||||
var secret = speakeasy.generateSecret({ length: 20, name: `FF Admin ${SettingHelper.getSetting("club.name")}` });
|
||||
|
||||
let createInvite: CreateInviteCommand = {
|
||||
username: username,
|
||||
|
@ -73,8 +71,8 @@ export async function inviteUser(req: Request, res: Response, isInvite: boolean
|
|||
// sendmail
|
||||
await MailHelper.sendMail(
|
||||
mail,
|
||||
`Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`,
|
||||
`Öffne folgenden Link: ${origin}/${isInvite ? "invite" : "setup"}/verify?mail=${mail}&token=${token}`
|
||||
`Email Bestätigung für Mitglieder Admin-Portal von ${SettingHelper.getSetting("club.name")}`,
|
||||
`Öffne folgenden Link: ${origin}/${isSetup ? "setup" : "invite"}/verify?mail=${mail}&token=${token}`
|
||||
);
|
||||
|
||||
res.sendStatus(204);
|
||||
|
@ -92,7 +90,7 @@ export async function verifyInvite(req: Request, res: Response): Promise<any> {
|
|||
|
||||
let { secret, username } = await InviteService.getByMailAndToken(mail, token);
|
||||
|
||||
const url = `otpauth://totp/FF Admin ${CLUB_NAME}?secret=${secret}`;
|
||||
const url = `otpauth://totp/FF Admin ${SettingHelper.getSetting("club.name")}?secret=${secret}`;
|
||||
|
||||
QRCode.toDataURL(url)
|
||||
.then((result) => {
|
||||
|
@ -115,20 +113,26 @@ export async function verifyInvite(req: Request, res: Response): Promise<any> {
|
|||
*/
|
||||
export async function finishInvite(req: Request, res: Response, grantAdmin: boolean = false): Promise<any> {
|
||||
let mail = req.body.mail;
|
||||
let routine = req.body.routine;
|
||||
let token = req.body.token;
|
||||
let totp = req.body.totp;
|
||||
let passedSecret = req.body.secret;
|
||||
|
||||
let { secret, username, firstname, lastname } = await InviteService.getByMailAndToken(mail, token);
|
||||
|
||||
let valid = speakeasy.totp.verify({
|
||||
secret: secret,
|
||||
encoding: "base32",
|
||||
token: totp,
|
||||
window: 2,
|
||||
});
|
||||
let valid = false;
|
||||
if (routine == LoginRoutineEnum.totp) {
|
||||
valid = speakeasy.totp.verify({
|
||||
secret: secret,
|
||||
encoding: "base32",
|
||||
token: passedSecret,
|
||||
window: 2,
|
||||
});
|
||||
} else {
|
||||
valid = passedSecret != "";
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
throw new UnauthorizedRequestException("Token not valid or expired");
|
||||
throw new UnauthorizedRequestException("Credentials not valid or expired");
|
||||
}
|
||||
|
||||
let createUser: CreateUserCommand = {
|
||||
|
@ -136,8 +140,9 @@ export async function finishInvite(req: Request, res: Response, grantAdmin: bool
|
|||
firstname: firstname,
|
||||
lastname: lastname,
|
||||
mail: mail,
|
||||
secret: secret,
|
||||
secret: routine == LoginRoutineEnum.totp ? secret : passedSecret,
|
||||
isOwner: grantAdmin,
|
||||
routine,
|
||||
};
|
||||
let id = await UserCommandHandler.create(createUser);
|
||||
|
||||
|
|
|
@ -2,11 +2,13 @@ import { Request, Response } from "express";
|
|||
import CalendarService from "../service/club/calendarService";
|
||||
import CalendarTypeService from "../service/configuration/calendarTypeService";
|
||||
import { calendar } from "../entity/club/calendar";
|
||||
import { createEvents } from "ics";
|
||||
import moment from "moment";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import CalendarFactory from "../factory/admin/club/calendar";
|
||||
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
|
||||
|
@ -51,3 +53,155 @@ export async function getCalendarItemsByTypes(req: Request, res: Response): Prom
|
|||
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,6 +1,5 @@
|
|||
import { Request, Response } from "express";
|
||||
import { JWTHelper } from "../helpers/jwtHelper";
|
||||
import { JWTToken } from "../type/jwtTypes";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import RefreshCommandHandler from "../command/refreshCommandHandler";
|
||||
import { CreateRefreshCommand } from "../command/refreshCommand";
|
||||
|
@ -12,12 +11,10 @@ import ResetCommandHandler from "../command/resetCommandHandler";
|
|||
import MailHelper from "../helpers/mailHelper";
|
||||
import ResetService from "../service/resetService";
|
||||
import UserService from "../service/management/userService";
|
||||
import { CLUB_NAME } from "../env.defaults";
|
||||
import PermissionHelper from "../helpers/permissionHelper";
|
||||
import RolePermissionService from "../service/management/rolePermissionService";
|
||||
import UserPermissionService from "../service/management/userPermissionService";
|
||||
import { UpdateUserSecretCommand } from "../command/management/user/userCommand";
|
||||
import UserCommandHandler from "../command/management/user/userCommandHandler";
|
||||
import SettingHelper from "../helpers/settingsHelper";
|
||||
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
|
||||
|
||||
/**
|
||||
* @description request totp reset
|
||||
|
@ -31,7 +28,7 @@ export async function startReset(req: Request, res: Response): Promise<any> {
|
|||
|
||||
let { mail } = await UserService.getByUsername(username);
|
||||
|
||||
var secret = speakeasy.generateSecret({ length: 20, name: `FF Admin ${CLUB_NAME}` });
|
||||
var secret = speakeasy.generateSecret({ length: 20, name: `FF Admin ${SettingHelper.getSetting("club.name")}` });
|
||||
|
||||
let createReset: CreateResetCommand = {
|
||||
username: username,
|
||||
|
@ -43,7 +40,7 @@ export async function startReset(req: Request, res: Response): Promise<any> {
|
|||
// sendmail
|
||||
await MailHelper.sendMail(
|
||||
mail,
|
||||
`Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`,
|
||||
`Email Bestätigung für Mitglieder Admin-Portal von ${SettingHelper.getSetting("club.name")}`,
|
||||
`Öffne folgenden Link: ${origin}/reset/reset?mail=${mail}&token=${token}`
|
||||
);
|
||||
|
||||
|
@ -62,7 +59,7 @@ export async function verifyReset(req: Request, res: Response): Promise<any> {
|
|||
|
||||
let { secret } = await ResetService.getByMailAndToken(mail, token);
|
||||
|
||||
const url = `otpauth://totp/FF Admin ${CLUB_NAME}?secret=${secret}`;
|
||||
const url = `otpauth://totp/FF Admin ${SettingHelper.getSetting("club.name")}?secret=${secret}`;
|
||||
|
||||
QRCode.toDataURL(url)
|
||||
.then((result) => {
|
||||
|
@ -84,27 +81,34 @@ export async function verifyReset(req: Request, res: Response): Promise<any> {
|
|||
*/
|
||||
export async function finishReset(req: Request, res: Response): Promise<any> {
|
||||
let mail = req.body.mail;
|
||||
let routine = req.body.routine;
|
||||
let token = req.body.token;
|
||||
let totp = req.body.totp;
|
||||
let passedSecret = req.body.secret;
|
||||
|
||||
let { secret, username } = await ResetService.getByMailAndToken(mail, token);
|
||||
|
||||
let valid = speakeasy.totp.verify({
|
||||
secret: secret,
|
||||
encoding: "base32",
|
||||
token: totp,
|
||||
window: 2,
|
||||
});
|
||||
let valid = false;
|
||||
if (routine == LoginRoutineEnum.totp) {
|
||||
valid = speakeasy.totp.verify({
|
||||
secret: secret,
|
||||
encoding: "base32",
|
||||
token: passedSecret,
|
||||
window: 2,
|
||||
});
|
||||
} else {
|
||||
valid = passedSecret != "";
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
throw new UnauthorizedRequestException("Token not valid or expired");
|
||||
throw new UnauthorizedRequestException("Credentials not valid or expired");
|
||||
}
|
||||
|
||||
let { id } = await UserService.getByUsername(username);
|
||||
|
||||
let updateUserSecret: UpdateUserSecretCommand = {
|
||||
id,
|
||||
secret,
|
||||
secret: routine == LoginRoutineEnum.totp ? secret : passedSecret,
|
||||
routine,
|
||||
};
|
||||
await UserCommandHandler.updateSecret(updateUserSecret);
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
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
|
||||
|
@ -9,3 +12,131 @@ import { Request, Response } from "express";
|
|||
export async function isSetup(req: Request, res: Response): Promise<any> {
|
||||
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,12 +2,17 @@ import { Request, Response } from "express";
|
|||
import speakeasy from "speakeasy";
|
||||
import QRCode from "qrcode";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import { CLUB_NAME } from "../env.defaults";
|
||||
import UserService from "../service/management/userService";
|
||||
import UserFactory from "../factory/admin/management/user";
|
||||
import { TransferUserOwnerCommand, UpdateUserCommand } from "../command/management/user/userCommand";
|
||||
import {
|
||||
TransferUserOwnerCommand,
|
||||
UpdateUserCommand,
|
||||
UpdateUserSecretCommand,
|
||||
} from "../command/management/user/userCommand";
|
||||
import UserCommandHandler from "../command/management/user/userCommandHandler";
|
||||
import ForbiddenRequestException from "../exceptions/forbiddenRequestException";
|
||||
import SettingHelper from "../helpers/settingsHelper";
|
||||
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
|
||||
|
||||
/**
|
||||
* @description get my by id
|
||||
|
@ -22,6 +27,21 @@ export async function getMeById(req: Request, res: Response): Promise<any> {
|
|||
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
|
||||
* @param req {Request} Express req object
|
||||
|
@ -31,9 +51,9 @@ export async function getMeById(req: Request, res: Response): Promise<any> {
|
|||
export async function getMyTotp(req: Request, res: Response): Promise<any> {
|
||||
const userId = req.userId;
|
||||
|
||||
let { secret } = await UserService.getById(userId);
|
||||
let { secret, routine } = await UserService.getUserSecretAndRoutine(userId);
|
||||
|
||||
const url = `otpauth://totp/FF Admin ${CLUB_NAME}?secret=${secret}`;
|
||||
const url = `otpauth://totp/FF Admin ${SettingHelper.getSetting("club.name")}?secret=${secret}`;
|
||||
|
||||
QRCode.toDataURL(url)
|
||||
.then((result) => {
|
||||
|
@ -57,7 +77,12 @@ export async function verifyMyTotp(req: Request, res: Response): Promise<any> {
|
|||
const userId = req.userId;
|
||||
let totp = req.body.totp;
|
||||
|
||||
let { secret } = await UserService.getById(userId);
|
||||
let { secret, routine } = await UserService.getUserSecretAndRoutine(userId);
|
||||
|
||||
if (routine != LoginRoutineEnum.totp) {
|
||||
throw new ForbiddenRequestException("only allowed for totp login");
|
||||
}
|
||||
|
||||
let valid = speakeasy.totp.verify({
|
||||
secret: secret,
|
||||
encoding: "base32",
|
||||
|
@ -71,6 +96,106 @@ export async function verifyMyTotp(req: Request, res: Response): Promise<any> {
|
|||
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
|
||||
* @param req {Request} Express req object
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
import { Request, Response } from "express";
|
||||
import { JWTHelper } from "../helpers/jwtHelper";
|
||||
import { JWTToken } from "../type/jwtTypes";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import RefreshCommandHandler from "../command/refreshCommandHandler";
|
||||
import { CreateRefreshCommand, 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 ForbiddenRequestException from "../exceptions/forbiddenRequestException";
|
||||
import WebapiCommandHandler from "../command/management/webapi/webapiCommandHandler";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import "dotenv/config";
|
||||
import "reflect-metadata";
|
||||
import { DataSource } from "typeorm";
|
||||
import { DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME, DB_TYPE, DB_PORT } from "./env.defaults";
|
||||
import { configCheck, DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_USERNAME } from "./env.defaults";
|
||||
|
||||
import { user } from "./entity/management/user";
|
||||
import { refresh } from "./entity/refresh";
|
||||
|
@ -34,7 +34,7 @@ import { query } from "./entity/configuration/query";
|
|||
import { memberView } from "./views/memberView";
|
||||
import { memberExecutivePositionsView } from "./views/memberExecutivePositionView";
|
||||
import { memberQualificationsView } from "./views/memberQualificationsView";
|
||||
import { membershipView } from "./views/membershipsView";
|
||||
import { membershipTotalView, membershipView } from "./views/membershipsView";
|
||||
import { template } from "./entity/configuration/template";
|
||||
import { templateUsage } from "./entity/configuration/templateUsage";
|
||||
import { newsletter } from "./entity/club/newsletter/newsletter";
|
||||
|
@ -44,16 +44,17 @@ import { newsletterConfig } from "./entity/configuration/newsletterConfig";
|
|||
import { webapi } from "./entity/management/webapi";
|
||||
import { webapiPermission } from "./entity/management/webapi_permission";
|
||||
import { salutation } from "./entity/configuration/salutation";
|
||||
import { setting } from "./entity/management/setting";
|
||||
import { education } from "./entity/configuration/education";
|
||||
import { memberEducations } from "./entity/club/member/memberEducations";
|
||||
|
||||
import { BackupAndResetDatabase1738166124200 } from "./migrations/1738166124200-BackupAndResetDatabase";
|
||||
import { CreateSchema1738166167472 } from "./migrations/1738166167472-CreateSchema";
|
||||
import { TemplatesAndProtocolSort1742549956787 } from "./migrations/1742549956787-templatesAndProtocolSort";
|
||||
import { QueryToUUID1742922178643 } from "./migrations/1742922178643-queryToUUID";
|
||||
import { NewsletterColumnType1744351418751 } from "./migrations/1744351418751-newsletterColumnType";
|
||||
import { QueryUpdatedAt1744795756230 } from "./migrations/1744795756230-QueryUpdatedAt";
|
||||
import { BackupAndResetDatabase1749296262915 } from "./migrations/1749296262915-BackupAndResetDatabase";
|
||||
import { CreateSchema1749296280721 } from "./migrations/1749296280721-CreateSchema";
|
||||
|
||||
configCheck();
|
||||
|
||||
const dataSource = new DataSource({
|
||||
type: DB_TYPE as any,
|
||||
type: "postgres",
|
||||
host: DB_HOST,
|
||||
port: DB_PORT,
|
||||
username: DB_USERNAME,
|
||||
|
@ -61,7 +62,6 @@ const dataSource = new DataSource({
|
|||
database: DB_NAME,
|
||||
synchronize: false,
|
||||
logging: process.env.NODE_ENV ? true : ["schema", "error", "warn", "log", "migration"],
|
||||
bigNumberStrings: false,
|
||||
entities: [
|
||||
user,
|
||||
refresh,
|
||||
|
@ -75,6 +75,7 @@ const dataSource = new DataSource({
|
|||
communicationType,
|
||||
executivePosition,
|
||||
membershipStatus,
|
||||
education,
|
||||
qualification,
|
||||
salutation,
|
||||
member,
|
||||
|
@ -82,6 +83,7 @@ const dataSource = new DataSource({
|
|||
memberExecutivePositions,
|
||||
memberQualifications,
|
||||
membership,
|
||||
memberEducations,
|
||||
protocol,
|
||||
protocolAgenda,
|
||||
protocolDecision,
|
||||
|
@ -101,17 +103,12 @@ const dataSource = new DataSource({
|
|||
memberExecutivePositionsView,
|
||||
memberQualificationsView,
|
||||
membershipView,
|
||||
membershipTotalView,
|
||||
webapi,
|
||||
webapiPermission,
|
||||
setting,
|
||||
],
|
||||
migrations: [
|
||||
BackupAndResetDatabase1738166124200,
|
||||
CreateSchema1738166167472,
|
||||
TemplatesAndProtocolSort1742549956787,
|
||||
QueryToUUID1742922178643,
|
||||
NewsletterColumnType1744351418751,
|
||||
QueryUpdatedAt1744795756230,
|
||||
],
|
||||
migrations: [BackupAndResetDatabase1749296262915, CreateSchema1749296280721],
|
||||
migrationsRun: true,
|
||||
migrationsTransactionMode: "each",
|
||||
subscribers: [],
|
||||
|
|
|
@ -7,7 +7,7 @@ export const protocolDemoData: {
|
|||
title: string;
|
||||
summary: string;
|
||||
iteration: number;
|
||||
date: string;
|
||||
date: Date;
|
||||
start: string;
|
||||
end: string;
|
||||
agenda: Array<Partial<protocolAgenda>>;
|
||||
|
@ -19,12 +19,7 @@ export const protocolDemoData: {
|
|||
title: "Beispiel Protokoll Daten",
|
||||
summary: "Zusammenfassung der Demodaten.",
|
||||
iteration: 1,
|
||||
date: new Date().toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
}),
|
||||
date: new Date(),
|
||||
start: "19:00:00",
|
||||
end: "21:00:00",
|
||||
agenda: [
|
||||
|
|
|
@ -45,7 +45,7 @@ export class calendar {
|
|||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
|
||||
@Column({ type: "varchar", nullable: true, default: null, unique: true })
|
||||
@Column({ type: "varchar", length: "255", nullable: true, default: null, unique: true })
|
||||
webpageId: string;
|
||||
|
||||
@ManyToOne(() => calendarType, (t) => t.calendar, {
|
||||
|
|
|
@ -1,4 +1,15 @@
|
|||
import { Column, ColumnType, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, PrimaryColumn } from "typeorm";
|
||||
import {
|
||||
Column,
|
||||
ColumnType,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
PrimaryColumn,
|
||||
PrimaryGeneratedColumn,
|
||||
} from "typeorm";
|
||||
import { membership } from "./membership";
|
||||
import { memberAwards } from "./memberAwards";
|
||||
import { memberQualifications } from "./memberQualifications";
|
||||
|
@ -6,10 +17,11 @@ import { memberExecutivePositions } from "./memberExecutivePositions";
|
|||
import { communication } from "./communication";
|
||||
import { salutation } from "../../configuration/salutation";
|
||||
import { getTypeByORM } from "../../../migrations/ormHelper";
|
||||
import { memberEducations } from "./memberEducations";
|
||||
|
||||
@Entity()
|
||||
export class member {
|
||||
@PrimaryColumn({ generated: "uuid", type: "varchar" })
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255 })
|
||||
|
@ -27,9 +39,15 @@ export class member {
|
|||
@Column({ type: "varchar", length: 255, unique: true, nullable: true })
|
||||
internalId?: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: true })
|
||||
note?: string;
|
||||
|
||||
@Column()
|
||||
salutationId: number;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@ManyToOne(() => salutation, (salutation) => salutation.members, {
|
||||
nullable: false,
|
||||
onDelete: "RESTRICT",
|
||||
|
@ -53,6 +71,9 @@ export class member {
|
|||
@OneToMany(() => memberQualifications, (qualifications) => qualifications.member, { cascade: ["insert"] })
|
||||
qualifications: memberQualifications[];
|
||||
|
||||
@OneToMany(() => memberEducations, (educations) => educations.member, { cascade: ["insert"] })
|
||||
educations: memberEducations[];
|
||||
|
||||
firstMembershipEntry?: membership;
|
||||
lastMembershipEntry?: membership;
|
||||
preferredCommunication?: Array<communication>;
|
||||
|
|
43
src/entity/club/member/memberEducations.ts
Normal file
43
src/entity/club/member/memberEducations.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { Column, ColumnType, Entity, ManyToOne, PrimaryColumn } from "typeorm";
|
||||
import { member } from "./member";
|
||||
import { education } from "../../configuration/education";
|
||||
import { getTypeByORM } from "../../../migrations/ormHelper";
|
||||
|
||||
@Entity()
|
||||
export class memberEducations {
|
||||
@PrimaryColumn({ generated: "increment", type: "int" })
|
||||
id: number;
|
||||
|
||||
@Column({ type: getTypeByORM("date").type as ColumnType })
|
||||
start: Date;
|
||||
|
||||
@Column({ type: getTypeByORM("date").type as ColumnType, nullable: true })
|
||||
end?: Date;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: true })
|
||||
note?: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: true })
|
||||
place?: string;
|
||||
|
||||
@Column()
|
||||
memberId: string;
|
||||
|
||||
@Column()
|
||||
educationId: number;
|
||||
|
||||
@ManyToOne(() => member, (member) => member.awards, {
|
||||
nullable: false,
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
member: member;
|
||||
|
||||
@ManyToOne(() => education, (education) => education.members, {
|
||||
nullable: false,
|
||||
onDelete: "RESTRICT",
|
||||
onUpdate: "RESTRICT",
|
||||
cascade: ["insert"],
|
||||
})
|
||||
education: education;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Column, Entity, ManyToOne, OneToMany, PrimaryColumn } from "typeorm";
|
||||
import { Column, CreateDateColumn, Entity, ManyToOne, OneToMany, PrimaryColumn } from "typeorm";
|
||||
import { newsletterDates } from "./newsletterDates";
|
||||
import { newsletterRecipients } from "./newsletterRecipients";
|
||||
import { query } from "../../configuration/query";
|
||||
|
@ -14,6 +14,9 @@ export class newsletter {
|
|||
@Column({ type: "varchar", length: 255, default: "" })
|
||||
description: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@Column({ type: "text", default: "" })
|
||||
newsletterTitle: string;
|
||||
|
||||
|
|
17
src/entity/configuration/education.ts
Normal file
17
src/entity/configuration/education.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { Column, Entity, OneToMany, PrimaryColumn } from "typeorm";
|
||||
import { memberEducations } from "../club/member/memberEducations";
|
||||
|
||||
@Entity()
|
||||
export class education {
|
||||
@PrimaryColumn({ generated: "increment", type: "int" })
|
||||
id: number;
|
||||
|
||||
@Column({ type: "varchar", length: 255, unique: true })
|
||||
education: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: true })
|
||||
description?: string;
|
||||
|
||||
@OneToMany(() => memberEducations, (memberEducations) => memberEducations.education)
|
||||
members: memberEducations[];
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm";
|
||||
import { NewsletterConfigType } from "../../enums/newsletterConfigType";
|
||||
import { NewsletterConfigEnum } from "../../enums/newsletterConfigEnum";
|
||||
import { communicationType } from "./communicationType";
|
||||
|
||||
@Entity()
|
||||
|
@ -11,15 +11,15 @@ export class newsletterConfig {
|
|||
type: "varchar",
|
||||
length: "255",
|
||||
transformer: {
|
||||
to(value: NewsletterConfigType) {
|
||||
to(value: NewsletterConfigEnum) {
|
||||
return value.toString();
|
||||
},
|
||||
from(value: string) {
|
||||
return NewsletterConfigType[value as keyof typeof NewsletterConfigType];
|
||||
return NewsletterConfigEnum[value as keyof typeof NewsletterConfigEnum];
|
||||
},
|
||||
},
|
||||
})
|
||||
config: NewsletterConfigType;
|
||||
config: NewsletterConfigEnum;
|
||||
|
||||
@ManyToOne(() => communicationType, {
|
||||
nullable: false,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Column, Entity, PrimaryColumn, UpdateDateColumn } from "typeorm";
|
||||
import { Column, Entity, PrimaryColumn, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
|
||||
|
||||
@Entity()
|
||||
export class query {
|
||||
@PrimaryColumn({ generated: "uuid", type: "varchar" })
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, unique: true })
|
||||
|
|
13
src/entity/management/setting.ts
Normal file
13
src/entity/management/setting.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
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,10 +1,13 @@
|
|||
import { Column, Entity, JoinTable, ManyToMany, OneToMany, PrimaryColumn } from "typeorm";
|
||||
import { Column, Entity, JoinTable, ManyToMany, OneToMany, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { role } from "./role";
|
||||
import { userPermission } from "./user_permission";
|
||||
import { LoginRoutineEnum } from "../../enums/loginRoutineEnum";
|
||||
import { CodingHelper } from "../../helpers/codingHelper";
|
||||
import { APPLICATION_SECRET } from "../../env.defaults";
|
||||
|
||||
@Entity()
|
||||
export class user {
|
||||
@PrimaryColumn({ generated: "uuid", type: "varchar" })
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id: string;
|
||||
|
||||
@Column({ type: "varchar", unique: true, length: 255 })
|
||||
|
@ -19,11 +22,27 @@ export class user {
|
|||
@Column({ type: "varchar", length: 255 })
|
||||
lastname: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255 })
|
||||
@Column({
|
||||
type: "text",
|
||||
select: false,
|
||||
transformer: CodingHelper.entityBaseCoding(APPLICATION_SECRET, "<self>"),
|
||||
})
|
||||
secret: string;
|
||||
|
||||
@Column({ type: "boolean", default: false })
|
||||
static: boolean;
|
||||
@Column({
|
||||
type: "varchar",
|
||||
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 })
|
||||
isOwner: boolean;
|
||||
|
|
4
src/enums/loginRoutineEnum.ts
Normal file
4
src/enums/loginRoutineEnum.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export enum LoginRoutineEnum {
|
||||
password = "password", // login with self defined password
|
||||
totp = "totp", // login with totp by auth apps
|
||||
}
|
5
src/enums/newsletterConfigEnum.ts
Normal file
5
src/enums/newsletterConfigEnum.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export enum NewsletterConfigEnum {
|
||||
pdf = "pdf",
|
||||
mail = "mail",
|
||||
none = "none",
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
export enum NewsletterConfigType {
|
||||
pdf = "pdf",
|
||||
mail = "mail",
|
||||
}
|
|
@ -2,32 +2,15 @@ import "dotenv/config";
|
|||
import ms from "ms";
|
||||
import ip from "ip";
|
||||
|
||||
export const DB_TYPE = process.env.DB_TYPE ?? "mysql";
|
||||
export const DB_HOST = process.env.DB_HOST ?? "";
|
||||
export const DB_PORT = Number(process.env.DB_PORT ?? 3306);
|
||||
export const DB_PORT = Number(process.env.DB_PORT ?? 5432);
|
||||
export const DB_NAME = process.env.DB_NAME ?? "";
|
||||
export const DB_USERNAME = process.env.DB_USERNAME ?? "";
|
||||
export const DB_PASSWORD = process.env.DB_PASSWORD ?? "";
|
||||
|
||||
export const SERVER_PORT = Number(process.env.SERVER_PORT ?? 5000);
|
||||
|
||||
export const JWT_SECRET = process.env.JWT_SECRET ?? "my_jwt_secret_string_ilughfnadiuhgq§$IUZGFVRweiouarbt1oub3h5q4a";
|
||||
export const JWT_EXPIRATION = (process.env.JWT_EXPIRATION ?? "15m") as ms.StringValue;
|
||||
export const REFRESH_EXPIRATION = (process.env.REFRESH_EXPIRATION ?? "1d") as ms.StringValue;
|
||||
export const PWA_REFRESH_EXPIRATION = (process.env.PWA_REFRESH_EXPIRATION ?? "5d") as ms.StringValue;
|
||||
|
||||
export const 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 APPLICATION_SECRET = process.env.APPLICATION_SECRET ?? "";
|
||||
|
||||
export const USE_SECURITY_STRICT_LIMIT = process.env.USE_SECURITY_STRICT_LIMIT ?? "true";
|
||||
export const SECURITY_STRICT_LIMIT_WINDOW = (process.env.SECURITY_STRICT_LIMIT_WINDOW ?? "15m") as ms.StringValue;
|
||||
|
@ -55,40 +38,15 @@ export const TRUST_PROXY = ((): Array<string> | string | boolean | number | null
|
|||
})();
|
||||
|
||||
export function configCheck() {
|
||||
if (DB_TYPE != "mysql" && DB_TYPE != "sqlite" && DB_TYPE != "postgres")
|
||||
throw new Error("set valid value to DB_TYPE (mysql|sqlite|postgres)");
|
||||
if ((DB_HOST == "" || typeof DB_HOST != "string") && DB_TYPE != "sqlite")
|
||||
throw new Error("set valid value to DB_HOST");
|
||||
if (DB_HOST == "" || typeof DB_HOST != "string") throw new Error("set valid value to DB_HOST");
|
||||
if (DB_NAME == "" || typeof DB_NAME != "string") throw new Error("set valid value to DB_NAME");
|
||||
if ((DB_USERNAME == "" || typeof DB_USERNAME != "string") && DB_TYPE != "sqlite")
|
||||
throw new Error("set valid value to DB_USERNAME");
|
||||
if ((DB_PASSWORD == "" || typeof DB_PASSWORD != "string") && DB_TYPE != "sqlite")
|
||||
throw new Error("set valid value to DB_PASSWORD");
|
||||
if (DB_USERNAME == "" || typeof DB_USERNAME != "string") throw new Error("set valid value to DB_USERNAME");
|
||||
if (DB_PASSWORD == "" || typeof DB_PASSWORD != "string") throw new Error("set valid value to DB_PASSWORD");
|
||||
|
||||
if (APPLICATION_SECRET == "") throw new Error("set valid APPLICATION_SECRET");
|
||||
|
||||
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")
|
||||
throw new Error("set 'true' or 'false' to USE_SECURITY_STRICT_LIMIT");
|
||||
checkMS(SECURITY_STRICT_LIMIT_WINDOW, "SECURITY_STRICT_LIMIT_WINDOW");
|
||||
|
|
|
@ -2,7 +2,7 @@ import CustomRequestException from "./customRequestException";
|
|||
|
||||
export default class DatabaseActionException extends CustomRequestException {
|
||||
constructor(action: string, table: string, err: any) {
|
||||
let errstring = `${action} on ${table} with ${err?.code ?? "XX"} at ${err?.sqlMessage ?? "XX"}`;
|
||||
let errstring = `${action} on ${table} with ${err?.code ?? "XX"} at ${err?.sqlMessage ?? err?.message ?? "XX"}`;
|
||||
super(500, errstring, err);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,5 @@
|
|||
import { DB_TYPE } from "../../../../env.defaults";
|
||||
|
||||
export default abstract class DateMappingHelper {
|
||||
static mapDate(entry: any) {
|
||||
switch (DB_TYPE) {
|
||||
case "postgres":
|
||||
return `${entry?.years ?? 0} years ${entry?.months ?? 0} months ${entry?.days ?? 0} days`;
|
||||
case "mysql":
|
||||
return entry.toString();
|
||||
case "sqlite":
|
||||
return entry;
|
||||
}
|
||||
return `${entry?.years ?? 0} years ${entry?.months ?? 0} months ${entry?.days ?? 0} days`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,8 @@ export default abstract class MemberFactory {
|
|||
lastname: record?.lastname,
|
||||
nameaffix: record?.nameaffix,
|
||||
birthdate: record?.birthdate,
|
||||
internalId: record.internalId,
|
||||
internalId: record?.internalId,
|
||||
note: record?.note,
|
||||
firstMembershipEntry: record?.firstMembershipEntry
|
||||
? MembershipFactory.mapToSingle(record.firstMembershipEntry)
|
||||
: null,
|
||||
|
|
30
src/factory/admin/club/member/memberEducation.ts
Normal file
30
src/factory/admin/club/member/memberEducation.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { memberEducations } from "../../../../entity/club/member/memberEducations";
|
||||
import { MemberEducationViewModel } from "../../../../viewmodel/admin/club/member/memberEducation.models";
|
||||
|
||||
export default abstract class MemberEducationFactory {
|
||||
/**
|
||||
* @description map record to memberEducation
|
||||
* @param {memberEducation} record
|
||||
* @returns {MemberEducationViewModel}
|
||||
*/
|
||||
public static mapToSingle(record: memberEducations): MemberEducationViewModel {
|
||||
return {
|
||||
id: record.id,
|
||||
start: record.start,
|
||||
end: record.end,
|
||||
note: record.note,
|
||||
place: record.place,
|
||||
education: record.education.education,
|
||||
educationId: record.education.id,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description map records to memberEducation
|
||||
* @param {Array<memberEducation>} records
|
||||
* @returns {Array<MemberEducationViewModel>}
|
||||
*/
|
||||
public static mapToBase(records: Array<memberEducations>): Array<MemberEducationViewModel> {
|
||||
return records.map((r) => this.mapToSingle(r));
|
||||
}
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
import { membership } from "../../../../entity/club/member/membership";
|
||||
import {
|
||||
MembershipStatisticsViewModel,
|
||||
MembershipTotalStatisticsViewModel,
|
||||
MembershipViewModel,
|
||||
} from "../../../../viewmodel/admin/club/member/membership.models";
|
||||
import { membershipView } from "../../../../views/membershipsView";
|
||||
import { membershipTotalView, membershipView } from "../../../../views/membershipsView";
|
||||
import DateMappingHelper from "./dateMappingHelper";
|
||||
|
||||
export default abstract class MembershipFactory {
|
||||
|
@ -53,6 +54,25 @@ export default abstract class MembershipFactory {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description map view record to MembershipTotalStatisticsViewModel
|
||||
* @param {membershipTotalView} record
|
||||
* @returns {MembershipTotalStatisticsViewModel}
|
||||
*/
|
||||
public static mapToSingleTotalStatistic(record: membershipTotalView): MembershipTotalStatisticsViewModel {
|
||||
return {
|
||||
durationInDays: record.durationInDays,
|
||||
durationInYears: record.durationInYears,
|
||||
exactDuration: DateMappingHelper.mapDate(record.exactDuration),
|
||||
memberId: record.memberId,
|
||||
memberSalutation: record.memberSalutation,
|
||||
memberFirstname: record.memberFirstname,
|
||||
memberLastname: record.memberLastname,
|
||||
memberNameaffix: record.memberNameaffix,
|
||||
memberBirthdate: record.memberBirthdate,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description map records to MembershipStatisticsViewModel
|
||||
* @param {Array<membershipView>} records
|
||||
|
|
|
@ -18,7 +18,7 @@ export default abstract class NewsletterFactory {
|
|||
newsletterSignatur: record.newsletterSignatur,
|
||||
isSent: record.isSent,
|
||||
recipientsByQueryId: record?.recipientsByQuery ? record.recipientsByQuery.id : null,
|
||||
recipientsByQuery: record?.recipientsByQuery ? QueryStoreFactory.mapToSingle(record.recipientsByQuery) : null,
|
||||
createdAt: record.createdAt,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
26
src/factory/admin/configuration/education.ts
Normal file
26
src/factory/admin/configuration/education.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { education } from "../../../entity/configuration/education";
|
||||
import { EducationViewModel } from "../../../viewmodel/admin/configuration/education.models";
|
||||
|
||||
export default abstract class EducationFactory {
|
||||
/**
|
||||
* @description map record to education
|
||||
* @param {education} record
|
||||
* @returns {AwardViewModel}
|
||||
*/
|
||||
public static mapToSingle(record: education): EducationViewModel {
|
||||
return {
|
||||
id: record.id,
|
||||
education: record.education,
|
||||
description: record.description,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description map records to education
|
||||
* @param {Array<education>} records
|
||||
* @returns {Array<AwardViewModel>}
|
||||
*/
|
||||
public static mapToBase(records: Array<education>): Array<EducationViewModel> {
|
||||
return records.map((r) => this.mapToSingle(r));
|
||||
}
|
||||
}
|
|
@ -8,6 +8,14 @@ Handlebars.registerHelper("date", function (aString) {
|
|||
});
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("weekdayDayMonth", function (aString) {
|
||||
return new Date(aString).toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
});
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("longdate", function (aString) {
|
||||
return new Date(aString).toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
|
@ -27,6 +35,27 @@ Handlebars.registerHelper("datetime", function (aString) {
|
|||
});
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("longdatetime", function (aString) {
|
||||
return new Date(aString).toLocaleDateString("de-DE", {
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("longdatetimeWithWeekday", function (aString) {
|
||||
return new Date(aString).toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("json", function (context) {
|
||||
return JSON.stringify(context);
|
||||
});
|
||||
|
|
|
@ -4,9 +4,11 @@ import { EntityManager } from "typeorm";
|
|||
import uniqBy from "lodash.uniqby";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import UserService from "../service/management/userService";
|
||||
import { BACKUP_COPIES, BACKUP_INTERVAL } from "../env.defaults";
|
||||
import DatabaseActionException from "../exceptions/databaseActionException";
|
||||
import { availableTemplates } from "../type/templateTypes";
|
||||
import SettingHelper from "./settingsHelper";
|
||||
import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
|
||||
import { education } from "../entity/configuration/education";
|
||||
|
||||
export type BackupSection =
|
||||
| "member"
|
||||
|
@ -18,7 +20,8 @@ export type BackupSection =
|
|||
| "query"
|
||||
| "template"
|
||||
| "user"
|
||||
| "webapi";
|
||||
| "webapi"
|
||||
| "settings";
|
||||
|
||||
export type BackupSectionRefered = {
|
||||
[key in BackupSection]?: Array<string>;
|
||||
|
@ -42,6 +45,7 @@ export default abstract class BackupHelper {
|
|||
{ type: "template", orderOnInsert: 2, orderOnClear: 1 }, // INSERT depends on member com
|
||||
{ type: "user", orderOnInsert: 1, orderOnClear: 1 },
|
||||
{ type: "webapi", orderOnInsert: 1, orderOnClear: 1 },
|
||||
{ type: "settings", orderOnInsert: 1, orderOnClear: 1 },
|
||||
];
|
||||
|
||||
private static readonly backupSectionRefered: BackupSectionRefered = {
|
||||
|
@ -52,6 +56,7 @@ export default abstract class BackupHelper {
|
|||
"member_executive_positions",
|
||||
"membership",
|
||||
"communication",
|
||||
"member_educations",
|
||||
],
|
||||
memberBase: [
|
||||
"award",
|
||||
|
@ -60,6 +65,7 @@ export default abstract class BackupHelper {
|
|||
"membership_status",
|
||||
"communication_type",
|
||||
"salutation",
|
||||
"education",
|
||||
],
|
||||
protocol: [
|
||||
"protocol",
|
||||
|
@ -76,6 +82,7 @@ export default abstract class BackupHelper {
|
|||
template: ["template", "template_usage"],
|
||||
user: ["user", "user_permission", "role", "role_permission", "invite"],
|
||||
webapi: ["webapi", "webapi_permission"],
|
||||
settings: ["setting"],
|
||||
};
|
||||
|
||||
private static transactionManager: EntityManager;
|
||||
|
@ -103,7 +110,7 @@ export default abstract class BackupHelper {
|
|||
let files = FileSystemHelper.getFilesInDirectory("backup", ".json");
|
||||
let sorted = files.sort((a, b) => new Date(b.split(".")[0]).getTime() - new Date(a.split(".")[0]).getTime());
|
||||
|
||||
const filesToDelete = sorted.slice(BACKUP_COPIES);
|
||||
const filesToDelete = sorted.slice(SettingHelper.getSetting("backup.copies"));
|
||||
for (const file of filesToDelete) {
|
||||
FileSystemHelper.deleteFile("backup", file);
|
||||
}
|
||||
|
@ -117,7 +124,7 @@ export default abstract class BackupHelper {
|
|||
let diffInMs = new Date().getTime() - lastBackup.getTime();
|
||||
let diffInDays = diffInMs / (1000 * 60 * 60 * 24);
|
||||
|
||||
if (diffInDays >= BACKUP_INTERVAL) {
|
||||
if (diffInDays >= SettingHelper.getSetting("backup.interval")) {
|
||||
await this.createBackup({});
|
||||
}
|
||||
}
|
||||
|
@ -220,6 +227,8 @@ export default abstract class BackupHelper {
|
|||
return await this.getUser(collectIds);
|
||||
case "webapi":
|
||||
return await this.getWebapi();
|
||||
case "settings":
|
||||
return await this.getSettings();
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
@ -240,6 +249,8 @@ export default abstract class BackupHelper {
|
|||
.leftJoin("positions.executivePosition", "executivePosition")
|
||||
.leftJoin("member.qualifications", "qualifications")
|
||||
.leftJoin("qualifications.qualification", "qualification")
|
||||
.leftJoin("member.educations", "educations")
|
||||
.leftJoin("educations.education", "education")
|
||||
.select([
|
||||
...(collectIds ? ["member.id"] : []),
|
||||
"member.firstname",
|
||||
|
@ -247,6 +258,7 @@ export default abstract class BackupHelper {
|
|||
"member.nameaffix",
|
||||
"member.birthdate",
|
||||
"member.internalId",
|
||||
"member.note",
|
||||
])
|
||||
.addSelect(["salutation.salutation"])
|
||||
.addSelect([
|
||||
|
@ -274,6 +286,14 @@ export default abstract class BackupHelper {
|
|||
"qualification.qualification",
|
||||
"qualification.description",
|
||||
])
|
||||
.addSelect([
|
||||
"educations.start",
|
||||
"educations.end",
|
||||
"educations.place",
|
||||
"educations.note",
|
||||
"education.education",
|
||||
"education.description",
|
||||
])
|
||||
.getMany();
|
||||
}
|
||||
private static async getMemberBase(): Promise<{ [key: string]: Array<any> }> {
|
||||
|
@ -288,6 +308,7 @@ export default abstract class BackupHelper {
|
|||
qualification: await dataSource
|
||||
.getRepository("qualification")
|
||||
.find({ select: { qualification: true, description: true } }),
|
||||
education: await dataSource.getRepository("education").find({ select: { education: true, description: true } }),
|
||||
};
|
||||
}
|
||||
private static async getProtocol(collectIds: boolean): Promise<Array<any>> {
|
||||
|
@ -338,6 +359,7 @@ export default abstract class BackupHelper {
|
|||
"newsletter.newsletterText",
|
||||
"newsletter.newsletterSignatur",
|
||||
"newsletter.isSent",
|
||||
"newsletter.createdAt",
|
||||
])
|
||||
.addSelect(["dates.calendarId", "dates.diffTitle", "dates.diffDescription"])
|
||||
.addSelect(["recipients.memberId"])
|
||||
|
@ -435,6 +457,7 @@ export default abstract class BackupHelper {
|
|||
"user.firstname",
|
||||
"user.lastname",
|
||||
"user.secret",
|
||||
"user.routine",
|
||||
"user.isOwner",
|
||||
])
|
||||
.addSelect(["permissions.permission"])
|
||||
|
@ -460,6 +483,13 @@ export default abstract class BackupHelper {
|
|||
.addSelect(["permissions.permission"])
|
||||
.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(
|
||||
section: BackupSection,
|
||||
|
@ -476,6 +506,7 @@ export default abstract class BackupHelper {
|
|||
if (section == "template" && !Array.isArray(data)) await this.setTemplate(data);
|
||||
if (section == "user" && !Array.isArray(data)) await this.setUser(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> {
|
||||
|
@ -519,6 +550,13 @@ export default abstract class BackupHelper {
|
|||
.map((d) => ({ ...d, id: undefined })),
|
||||
"qualification"
|
||||
),
|
||||
education: uniqBy(
|
||||
data
|
||||
.map((d) => (d.education ?? []).map((c: any) => c.education))
|
||||
.flat()
|
||||
.map((d) => ({ ...d, id: undefined })),
|
||||
"education"
|
||||
),
|
||||
});
|
||||
|
||||
let salutation = await this.transactionManager.getRepository("salutation").find();
|
||||
|
@ -526,6 +564,7 @@ export default abstract class BackupHelper {
|
|||
let membership = await this.transactionManager.getRepository("membership_status").find();
|
||||
let award = await this.transactionManager.getRepository("award").find();
|
||||
let qualification = await this.transactionManager.getRepository("qualification").find();
|
||||
let education = await this.transactionManager.getRepository("education").find();
|
||||
let position = await this.transactionManager.getRepository("executive_position").find();
|
||||
let dataWithMappedIds = data.map((d) => ({
|
||||
...d,
|
||||
|
@ -568,6 +607,13 @@ export default abstract class BackupHelper {
|
|||
id: qualification.find((iq) => iq.qualification == q.qualification.qualification)?.id ?? undefined,
|
||||
},
|
||||
})),
|
||||
educations: (d.educations ?? []).map((e: any) => ({
|
||||
...e,
|
||||
education: {
|
||||
...e.education,
|
||||
id: education.find((id) => id.education == e.education.education)?.id ?? undefined,
|
||||
},
|
||||
})),
|
||||
}));
|
||||
await this.transactionManager.getRepository("member").save(dataWithMappedIds);
|
||||
}
|
||||
|
@ -578,6 +624,7 @@ export default abstract class BackupHelper {
|
|||
let award = await this.transactionManager.getRepository("award").find();
|
||||
let qualification = await this.transactionManager.getRepository("qualification").find();
|
||||
let position = await this.transactionManager.getRepository("executive_position").find();
|
||||
let education = await this.transactionManager.getRepository("education").find();
|
||||
|
||||
await this.transactionManager
|
||||
.createQueryBuilder()
|
||||
|
@ -619,10 +666,19 @@ export default abstract class BackupHelper {
|
|||
.insert()
|
||||
.into("qualification")
|
||||
.values(
|
||||
(data?.["qualification"] ?? []).filter((d) => !qualification.map((q) => q.award).includes(d.qualification))
|
||||
(data?.["qualification"] ?? []).filter(
|
||||
(d) => !qualification.map((q) => q.qualification).includes(d.qualification)
|
||||
)
|
||||
)
|
||||
.orIgnore()
|
||||
.execute();
|
||||
await this.transactionManager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into("education")
|
||||
.values((data?.["education"] ?? []).filter((d) => !education.map((q) => q.education).includes(d.education)))
|
||||
.orIgnore()
|
||||
.execute();
|
||||
}
|
||||
private static async setProtocol(data: Array<any>, collectedIds: boolean): Promise<void> {
|
||||
let members = await this.transactionManager.getRepository("member").find();
|
||||
|
@ -755,11 +811,11 @@ export default abstract class BackupHelper {
|
|||
.filter((d) => availableTemplates.includes(d.scope))
|
||||
.map((d) => ({
|
||||
...d,
|
||||
headerHeightId: templates.find((template) => template.template == d.headerHeight.template)?.id ?? null,
|
||||
footerHeightId: templates.find((template) => template.template == d.footerHeight.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,
|
||||
footerId: templates.find((template) => template.template == d.footer.template)?.id ?? null,
|
||||
headerHeightId: templates.find((template) => template.template == d.headerHeight)?.id ?? null,
|
||||
footerHeightId: templates.find((template) => template.template == d.footerHeight)?.id ?? null,
|
||||
headerId: templates.find((template) => template.template == d.header?.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,
|
||||
}));
|
||||
availableTemplates.forEach((at) => {
|
||||
if (!dataWithMappedId.some((d) => d.scope == at)) {
|
||||
|
@ -793,6 +849,7 @@ export default abstract class BackupHelper {
|
|||
let roles = await this.transactionManager.getRepository("role").find();
|
||||
let dataWithMappedIds = (data?.["user"] ?? []).map((u) => ({
|
||||
...u,
|
||||
routine: u.routine ?? LoginRoutineEnum.totp,
|
||||
roles: u.roles.map((r: any) => ({
|
||||
...r,
|
||||
id: roles.find((role) => role.role == r.role)?.id ?? undefined,
|
||||
|
@ -810,4 +867,7 @@ export default abstract class BackupHelper {
|
|||
private static async setWebapi(data: Array<any>): Promise<void> {
|
||||
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 { calendar } from "../entity/club/calendar";
|
||||
import moment from "moment";
|
||||
import { CLUB_NAME, CLUB_WEBSITE, MAIL_USERNAME } from "../env.defaults";
|
||||
import SettingHelper from "./settingsHelper";
|
||||
|
||||
export abstract class CalendarHelper {
|
||||
public static buildICS(entries: Array<calendar>): { error?: Error; value?: string } {
|
||||
|
@ -35,7 +35,10 @@ export abstract class CalendarHelper {
|
|||
description: i.content,
|
||||
location: i.location,
|
||||
categories: [i.type.type],
|
||||
organizer: { name: CLUB_NAME, email: MAIL_USERNAME },
|
||||
organizer: {
|
||||
name: SettingHelper.getSetting("club.name"),
|
||||
email: SettingHelper.getSetting("mail.username"),
|
||||
},
|
||||
created: moment(i.createdAt)
|
||||
.format("YYYY-M-D-H-m")
|
||||
.split("-")
|
||||
|
@ -46,7 +49,7 @@ export abstract class CalendarHelper {
|
|||
.map((a) => parseInt(a)) as [number, number, number, number, number],
|
||||
transp: "OPAQUE" as "OPAQUE",
|
||||
status: "CONFIRMED",
|
||||
...(CLUB_WEBSITE != "" ? { url: CLUB_WEBSITE } : {}),
|
||||
...(SettingHelper.getSetting("club.website") != "" ? { url: SettingHelper.getSetting("club.website") } : {}),
|
||||
alarms: [
|
||||
{
|
||||
action: "display",
|
||||
|
|
95
src/helpers/codingHelper.ts
Normal file
95
src/helpers/codingHelper.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
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 "";
|
||||
}
|
||||
}
|
||||
}
|
83
src/helpers/convertHelper.ts
Normal file
83
src/helpers/convertHelper.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
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 {}
|
|
@ -12,6 +12,16 @@ export abstract class DemoDataHelper {
|
|||
return newsletterDemoData;
|
||||
case "member":
|
||||
return memberDemoData;
|
||||
case "listprint":
|
||||
return {
|
||||
today: new Date(),
|
||||
list: [memberDemoData.memberships],
|
||||
};
|
||||
case "listprint.member":
|
||||
return {
|
||||
today: new Date(),
|
||||
list: [memberDemoData.member],
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Brackets, DataSource, NotBrackets, ObjectLiteral, SelectQueryBuilder, WhereExpressionBuilder } from "typeorm";
|
||||
import { Brackets, NotBrackets, ObjectLiteral, SelectQueryBuilder, WhereExpressionBuilder } from "typeorm";
|
||||
import { dataSource } from "../data-source";
|
||||
import { ConditionStructure, DynamicQueryStructure, FieldType, QueryResult } from "../type/dynamicQueries";
|
||||
import { TableMeta } from "../type/tableMeta";
|
||||
|
@ -17,11 +17,13 @@ export default abstract class DynamicQueryBuilder {
|
|||
"memberAwards",
|
||||
"memberExecutivePositions",
|
||||
"memberQualifications",
|
||||
"memberEducations",
|
||||
"membership",
|
||||
"memberView",
|
||||
"memberExecutivePositionsView",
|
||||
"memberQualificationsView",
|
||||
"membershipView",
|
||||
"membershipTotalView",
|
||||
];
|
||||
|
||||
public static getTableMeta(tableName: string): TableMeta {
|
||||
|
@ -63,7 +65,7 @@ export default abstract class DynamicQueryBuilder {
|
|||
count?: number;
|
||||
noLimit?: boolean;
|
||||
}): SelectQueryBuilder<ObjectLiteral> {
|
||||
let affix = queryObj.id ?? StringHelper.random(10);
|
||||
let affix = queryObj.id.replaceAll("-", "") ?? StringHelper.random(10);
|
||||
let query = dataSource.getRepository(queryObj.table).createQueryBuilder(`${affix}_${queryObj.table}`);
|
||||
|
||||
this.buildDynamicQuery(query, queryObj, affix);
|
||||
|
@ -116,7 +118,7 @@ export default abstract class DynamicQueryBuilder {
|
|||
|
||||
if (queryObject.join) {
|
||||
for (const join of queryObject.join) {
|
||||
let subaffix = join.id ?? StringHelper.random(10);
|
||||
let subaffix = join.id.replaceAll("-", "") ?? StringHelper.random(10);
|
||||
if (join.type == undefined) join.type = "defined";
|
||||
if (join.type == "defined") {
|
||||
query.innerJoin(`${alias}.${join.foreignColumn}`, `${subaffix}_${join.table}`);
|
||||
|
@ -226,19 +228,19 @@ export default abstract class DynamicQueryBuilder {
|
|||
query += ` IS NOT NULL`;
|
||||
break;
|
||||
case "contains":
|
||||
query += ` LIKE :${parameterKey}`;
|
||||
query += ` ILIKE :${parameterKey}`;
|
||||
parameters[parameterKey] = `%${condition.value}%`;
|
||||
break;
|
||||
case "notContains":
|
||||
query += ` NOT LIKE :${parameterKey}`;
|
||||
query += ` NOT ILIKE :${parameterKey}`;
|
||||
parameters[parameterKey] = `%${condition.value}%`;
|
||||
break;
|
||||
case "startsWith":
|
||||
query += ` LIKE :${parameterKey}`;
|
||||
query += ` ILIKE :${parameterKey}`;
|
||||
parameters[parameterKey] = `${condition.value}%`;
|
||||
break;
|
||||
case "endsWith":
|
||||
query += ` LIKE :${parameterKey}`;
|
||||
query += ` ILIKE :${parameterKey}`;
|
||||
parameters[parameterKey] = `%${condition.value}`;
|
||||
break;
|
||||
case "timespanEq":
|
||||
|
@ -272,8 +274,15 @@ export default abstract class DynamicQueryBuilder {
|
|||
});
|
||||
});
|
||||
results = tempResults;
|
||||
} else if (value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
|
||||
const objResults = flatten(value as QueryResult, newKey);
|
||||
} else if (
|
||||
value &&
|
||||
typeof value === "object" &&
|
||||
!Array.isArray(value) &&
|
||||
!(value instanceof Date) &&
|
||||
!(value instanceof Buffer) &&
|
||||
!Object.keys(value).every((k) => ["years", "months", "days"].includes(k))
|
||||
) {
|
||||
const objResults = flatten(value, newKey);
|
||||
const tempResults: Array<{ [key: string]: FieldType }> = [];
|
||||
results.forEach((res) => {
|
||||
objResults.forEach((objRes) => {
|
||||
|
@ -283,7 +292,24 @@ export default abstract class DynamicQueryBuilder {
|
|||
results = tempResults;
|
||||
} else {
|
||||
results.forEach((res) => {
|
||||
if (String(value) != "undefined") res[newKey] = String(value);
|
||||
if (typeof value === "object" && value instanceof Date) {
|
||||
res[newKey] = new Date(value).toISOString();
|
||||
} else if (
|
||||
typeof value === "object" &&
|
||||
!Array.isArray(value) &&
|
||||
!(value instanceof Buffer) &&
|
||||
value !== null
|
||||
) {
|
||||
let string = "";
|
||||
for (const key of Object.keys(value)) {
|
||||
string += `${value[key]} ${key} `;
|
||||
}
|
||||
res[newKey] = string.trim();
|
||||
|
||||
// JSON.stringify(value).replace(/["\\{}]/g, "").replaceAll(",", ", ");
|
||||
} else if (String(value) != "undefined") {
|
||||
res[newKey] = value !== null ? String(value) : "";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -366,7 +392,7 @@ export default abstract class DynamicQueryBuilder {
|
|||
stats: "error",
|
||||
sql: error.sql,
|
||||
code: error.code,
|
||||
msg: error.sqlMessage,
|
||||
msg: error.sqlMessage ?? error.message,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -375,7 +401,7 @@ export default abstract class DynamicQueryBuilder {
|
|||
stats: "error",
|
||||
sql: error.sql,
|
||||
code: error.code,
|
||||
msg: error.sqlMessage,
|
||||
msg: error.sqlMessage ?? error.message,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
|
@ -395,7 +421,7 @@ export default abstract class DynamicQueryBuilder {
|
|||
stats: "error",
|
||||
sql: error.sql,
|
||||
code: error.code,
|
||||
msg: error.sqlMessage,
|
||||
msg: error.sqlMessage ?? error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,20 @@ export abstract class FileSystemHelper {
|
|||
return readFileSync(this.formatPath(...filePath), "base64");
|
||||
}
|
||||
|
||||
static readRootFile(filePath: string) {
|
||||
return readFileSync(this.normalizePath(process.cwd(), filePath), "utf8");
|
||||
}
|
||||
|
||||
static readTemplateFile(filePath: string) {
|
||||
this.createFolder(filePath);
|
||||
return readFileSync(process.cwd() + filePath, "utf8");
|
||||
return readFileSync(this.normalizePath(process.cwd(), "src", "templates", 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) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import jwt from "jsonwebtoken";
|
||||
import { JWTData, JWTToken } from "../type/jwtTypes";
|
||||
import { JWT_SECRET, JWT_EXPIRATION } from "../env.defaults";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import RolePermissionService from "../service/management/rolePermissionService";
|
||||
import UserPermissionService from "../service/management/userPermissionService";
|
||||
|
@ -9,11 +8,13 @@ import PermissionHelper from "./permissionHelper";
|
|||
import WebapiService from "../service/management/webapiService";
|
||||
import WebapiPermissionService from "../service/management/webapiPermissionService";
|
||||
import ms from "ms";
|
||||
import SettingHelper from "./settingsHelper";
|
||||
import { APPLICATION_SECRET } from "../env.defaults";
|
||||
|
||||
export abstract class JWTHelper {
|
||||
static validate(token: string): Promise<string | jwt.JwtPayload> {
|
||||
return new Promise<string | jwt.JwtPayload>((resolve, reject) => {
|
||||
jwt.verify(token, JWT_SECRET, (err, decoded) => {
|
||||
jwt.verify(token, APPLICATION_SECRET, (err, decoded) => {
|
||||
if (err) reject(err.message);
|
||||
else resolve(decoded);
|
||||
});
|
||||
|
@ -27,9 +28,11 @@ export abstract class JWTHelper {
|
|||
return new Promise<string>((resolve, reject) => {
|
||||
jwt.sign(
|
||||
data,
|
||||
JWT_SECRET,
|
||||
APPLICATION_SECRET,
|
||||
{
|
||||
...(useExpiration ?? true ? { expiresIn: expOverwrite ?? JWT_EXPIRATION } : {}),
|
||||
...(useExpiration ?? true
|
||||
? { expiresIn: expOverwrite ?? (SettingHelper.getSetting("session.jwt_expiration") as ms.StringValue) }
|
||||
: {}),
|
||||
},
|
||||
(err, token) => {
|
||||
if (err) reject(err.message);
|
||||
|
@ -100,7 +103,8 @@ export abstract class JWTHelper {
|
|||
};
|
||||
|
||||
let overwriteExpiration =
|
||||
ms(JWT_EXPIRATION) < new Date().getTime() - new Date(expiration).getTime()
|
||||
ms(SettingHelper.getSetting("session.jwt_expiration") as ms.StringValue) <
|
||||
new Date().getTime() - new Date(expiration).getTime()
|
||||
? null
|
||||
: Date.now() - new Date(expiration).getTime();
|
||||
|
||||
|
|
|
@ -1,17 +1,78 @@
|
|||
import { Transporter, createTransport, TransportOptions } from "nodemailer";
|
||||
import { CLUB_NAME, MAIL_HOST, MAIL_PASSWORD, MAIL_PORT, MAIL_SECURE, MAIL_USERNAME } from "../env.defaults";
|
||||
import { Attachment } from "nodemailer/lib/mailer";
|
||||
import SettingHelper from "./settingsHelper";
|
||||
import validator from "validator";
|
||||
|
||||
export default abstract class MailHelper {
|
||||
private static readonly transporter: Transporter = createTransport({
|
||||
host: MAIL_HOST,
|
||||
port: MAIL_PORT,
|
||||
secure: (MAIL_SECURE as "true" | "false") == "true",
|
||||
auth: {
|
||||
user: MAIL_USERNAME,
|
||||
pass: MAIL_PASSWORD,
|
||||
},
|
||||
} as TransportOptions);
|
||||
private static transporter: Transporter;
|
||||
|
||||
static createTransport() {
|
||||
this.transporter?.close();
|
||||
|
||||
this.transporter = createTransport({
|
||||
host: SettingHelper.getSetting("mail.host"),
|
||||
port: SettingHelper.getSetting("mail.port"),
|
||||
secure: SettingHelper.getSetting("mail.secure"),
|
||||
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
|
||||
|
@ -29,7 +90,7 @@ export default abstract class MailHelper {
|
|||
return new Promise((resolve, reject) => {
|
||||
this.transporter
|
||||
.sendMail({
|
||||
from: `"${CLUB_NAME}" <${MAIL_USERNAME}>`,
|
||||
from: `"${SettingHelper.getSetting("club.name")}" <${SettingHelper.getSetting("mail.email")}>`,
|
||||
to: target,
|
||||
subject,
|
||||
text: content,
|
||||
|
|
|
@ -10,13 +10,13 @@ import { CalendarHelper } from "./calendarHelper";
|
|||
import DynamicQueryBuilder from "./dynamicQueryBuilder";
|
||||
import { FileSystemHelper } from "./fileSystemHelper";
|
||||
import MailHelper from "./mailHelper";
|
||||
import { CLUB_NAME } from "../env.defaults";
|
||||
import { TemplateHelper } from "./templateHelper";
|
||||
import { PdfExport } from "./pdfExport";
|
||||
import NewsletterConfigService from "../service/configuration/newsletterConfigService";
|
||||
import { NewsletterConfigType } from "../enums/newsletterConfigType";
|
||||
import { NewsletterConfigEnum } from "../enums/newsletterConfigEnum";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import EventEmitter from "events";
|
||||
import SettingHelper from "./settingsHelper";
|
||||
|
||||
export interface NewsletterEventType {
|
||||
kind: "pdf" | "mail";
|
||||
|
@ -66,33 +66,7 @@ export abstract class NewsletterHelper {
|
|||
title: d.diffTitle || d.calendar.title,
|
||||
content: d.diffDescription || d.calendar.content,
|
||||
starttime: d.calendar.starttime,
|
||||
formattedStarttime: new Date(d.calendar.starttime).toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
}),
|
||||
formattedFullStarttime: new Date(d.calendar.starttime).toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}),
|
||||
endtime: d.calendar.endtime,
|
||||
formattedEndtime: new Date(d.calendar.endtime).toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
}),
|
||||
formattedFullEndtime: new Date(d.calendar.endtime).toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}),
|
||||
location: d.calendar.location,
|
||||
}))
|
||||
.sort((a, b) => a.starttime.getTime() - b.starttime.getTime()),
|
||||
|
@ -145,7 +119,7 @@ export abstract class NewsletterHelper {
|
|||
return [];
|
||||
} else {
|
||||
let members = await MemberService.getAll({ noLimit: true, ids: queryMemberIds });
|
||||
return members[0];
|
||||
return members[0].filter((m) => m.sendNewsletter != null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,14 +128,11 @@ export abstract class NewsletterHelper {
|
|||
let recipients = await NewsletterRecipientsService.getAll(newsletterId);
|
||||
let config = await NewsletterConfigService.getAll();
|
||||
|
||||
let allowedForMail = config.filter((c) => c.config == NewsletterConfigType.mail).map((c) => c.comTypeId);
|
||||
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 != null &&
|
||||
m.sendNewsletter?.email != null &&
|
||||
allowedForMail.includes(m.sendNewsletter?.type?.id)
|
||||
(m) => m.sendNewsletter?.email != "" && allowedForMail.includes(m.sendNewsletter?.type?.id)
|
||||
);
|
||||
|
||||
return mailRecipients;
|
||||
|
@ -172,17 +143,17 @@ export abstract class NewsletterHelper {
|
|||
let recipients = await NewsletterRecipientsService.getAll(newsletterId);
|
||||
let config = await NewsletterConfigService.getAll();
|
||||
|
||||
let notAllowedForPdf = config.filter((c) => c.config == NewsletterConfigType.mail).map((c) => c.comTypeId);
|
||||
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) || m.sendNewsletter == null
|
||||
);
|
||||
const pdfRecipients = members.filter((m) => !notAllowedForPdf.includes(m.sendNewsletter?.type?.id));
|
||||
|
||||
pdfRecipients.unshift({
|
||||
id: "0",
|
||||
firstname: "Alle Mitglieder",
|
||||
lastname: CLUB_NAME,
|
||||
lastname: SettingHelper.getSetting("club.name"),
|
||||
nameaffix: "",
|
||||
salutation: { salutation: "" },
|
||||
} as member);
|
||||
|
@ -224,11 +195,14 @@ export abstract class NewsletterHelper {
|
|||
const { body } = await TemplateHelper.renderFileForModule({
|
||||
module: "newsletter",
|
||||
bodyData: data,
|
||||
title: `Newsletter von ${CLUB_NAME}`,
|
||||
title: `Newsletter von ${SettingHelper.getSetting("club.name")}`,
|
||||
});
|
||||
await MailHelper.sendMail(rec.sendNewsletter.email, `Newsletter von ${CLUB_NAME}`, body, [
|
||||
{ filename: "events.ics", path: this.getICSFilePath(newsletter) },
|
||||
])
|
||||
await MailHelper.sendMail(
|
||||
rec.sendNewsletter.email,
|
||||
`Newsletter von ${SettingHelper.getSetting("club.name")}`,
|
||||
body,
|
||||
[{ filename: "events.ics", path: this.getICSFilePath(newsletter) }]
|
||||
)
|
||||
.then(() => {
|
||||
this.formatJobEmit(
|
||||
"progress",
|
||||
|
@ -278,7 +252,7 @@ export abstract class NewsletterHelper {
|
|||
if (error) throw new InternalException("Failed Building ICS form Pdf", error);
|
||||
this.saveIcsToFile(newsletter, value);
|
||||
|
||||
let printWithAdress = config.filter((c) => c.config == NewsletterConfigType.pdf).map((c) => c.comTypeId);
|
||||
let printWithAdress = config.filter((c) => c.config == NewsletterConfigEnum.pdf).map((c) => c.comTypeId);
|
||||
|
||||
const pdfRecipients = await this.getPrintRecipients(newsletterId);
|
||||
|
||||
|
@ -289,7 +263,7 @@ export abstract class NewsletterHelper {
|
|||
|
||||
await PdfExport.renderFile({
|
||||
template: "newsletter",
|
||||
title: `Newsletter von ${CLUB_NAME}`,
|
||||
title: `Newsletter von ${SettingHelper.getSetting("club.name")}`,
|
||||
filename: `${rec.lastname}_${rec.firstname}_${rec.id}`.replaceAll(" ", "-"),
|
||||
folder: `newsletter/${newsletter.id}_${newsletter.title.replaceAll(" ", "")}`,
|
||||
data: data,
|
||||
|
|
|
@ -17,33 +17,31 @@ export default class PermissionHelper {
|
|||
permissions: PermissionObject,
|
||||
type: PermissionType | "admin",
|
||||
section: PermissionSection,
|
||||
module?: PermissionModule
|
||||
module: PermissionModule
|
||||
) {
|
||||
if (type == "admin") return permissions?.admin ?? permissions?.adminByOwner ?? false;
|
||||
if (permissions?.admin || permissions?.adminByOwner) return true;
|
||||
if (
|
||||
(!module &&
|
||||
permissions[section] != undefined &&
|
||||
(permissions[section]?.all == "*" || permissions[section]?.all?.includes(type))) ||
|
||||
permissions[section]?.all == "*" ||
|
||||
permissions[section]?.all?.includes(type)
|
||||
permissions[section]?.all?.includes(type) ||
|
||||
permissions[section]?.[module] == "*" ||
|
||||
permissions[section]?.[module]?.includes(type)
|
||||
)
|
||||
return true;
|
||||
if (module && (permissions[section]?.[module] == "*" || permissions[section]?.[module]?.includes(type)))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static canSome(
|
||||
permissions: PermissionObject,
|
||||
checks: Array<{
|
||||
requiredPermissions: PermissionType | "admin";
|
||||
requiredPermission: PermissionType | "admin";
|
||||
section: PermissionSection;
|
||||
module?: PermissionModule;
|
||||
module: PermissionModule;
|
||||
}>
|
||||
) {
|
||||
checks.reduce<boolean>((prev, curr) => {
|
||||
return prev || this.can(permissions, curr.requiredPermissions, curr.section, curr.module);
|
||||
return checks.reduce<boolean>((prev, curr) => {
|
||||
return prev || this.can(permissions, curr.requiredPermission, curr.section, curr.module);
|
||||
}, false);
|
||||
}
|
||||
|
||||
|
@ -66,12 +64,29 @@ export default class PermissionHelper {
|
|||
static canSomeSection(
|
||||
permissions: PermissionObject,
|
||||
checks: Array<{
|
||||
requiredPermissions: PermissionType | "admin";
|
||||
requiredPermission: PermissionType | "admin";
|
||||
section: PermissionSection;
|
||||
}>
|
||||
): boolean {
|
||||
return checks.reduce<boolean>((prev, curr) => {
|
||||
return prev || this.can(permissions, curr.requiredPermissions, curr.section);
|
||||
return prev || this.canSection(permissions, curr.requiredPermission, curr.section);
|
||||
}, false);
|
||||
}
|
||||
|
||||
static canAccessSection(permissions: PermissionObject, section: PermissionSection): boolean {
|
||||
if (permissions?.admin || permissions?.adminByOwner) return true;
|
||||
if (permissions[section] != undefined) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static canAccessSomeSection(
|
||||
permissions: PermissionObject,
|
||||
checks: Array<{
|
||||
section: PermissionSection;
|
||||
}>
|
||||
): boolean {
|
||||
return checks.reduce<boolean>((prev, curr) => {
|
||||
return prev || this.canAccessSection(permissions, curr.section);
|
||||
}, false);
|
||||
}
|
||||
|
||||
|
@ -83,7 +98,7 @@ export default class PermissionHelper {
|
|||
static passCheckMiddleware(
|
||||
requiredPermissions: PermissionType | "admin",
|
||||
section: PermissionSection,
|
||||
module?: PermissionModule
|
||||
module: PermissionModule
|
||||
): (req: Request, res: Response, next: Function) => void {
|
||||
return (req: Request, res: Response, next: Function) => {
|
||||
const permissions = req.permissions;
|
||||
|
@ -99,9 +114,9 @@ export default class PermissionHelper {
|
|||
|
||||
static passCheckSomeMiddleware(
|
||||
checks: Array<{
|
||||
requiredPermissions: PermissionType | "admin";
|
||||
requiredPermission: PermissionType | "admin";
|
||||
section: PermissionSection;
|
||||
module?: PermissionModule;
|
||||
module: PermissionModule;
|
||||
}>
|
||||
): (req: Request, res: Response, next: Function) => void {
|
||||
return (req: Request, res: Response, next: Function) => {
|
||||
|
@ -111,9 +126,7 @@ export default class PermissionHelper {
|
|||
if (isOwner || this.canSome(permissions, checks)) {
|
||||
next();
|
||||
} else {
|
||||
let permissionsToPass = checks.reduce<string>((prev, curr) => {
|
||||
return prev + (prev != " or " ? "" : "") + `${curr.section}.${curr.module}.${curr.requiredPermissions}`;
|
||||
}, "");
|
||||
let permissionsToPass = checks.map((c) => `${c.section}.${c.module}.${c.requiredPermission}`).join(" or ");
|
||||
throw new ForbiddenRequestException(`missing permission for ${permissionsToPass}`);
|
||||
}
|
||||
};
|
||||
|
@ -136,7 +149,7 @@ export default class PermissionHelper {
|
|||
}
|
||||
|
||||
static sectionPassCheckSomeMiddleware(
|
||||
checks: Array<{ requiredPermissions: PermissionType | "admin"; section: PermissionSection }>
|
||||
checks: Array<{ requiredPermission: PermissionType | "admin"; section: PermissionSection }>
|
||||
): (req: Request, res: Response, next: Function) => void {
|
||||
return (req: Request, res: Response, next: Function) => {
|
||||
const permissions = req.permissions;
|
||||
|
@ -145,9 +158,38 @@ export default class PermissionHelper {
|
|||
if (isOwner || this.canSomeSection(permissions, checks)) {
|
||||
next();
|
||||
} else {
|
||||
let permissionsToPass = checks.reduce<string>((prev, curr) => {
|
||||
return prev + (prev != " or " ? "" : "") + `${curr.section}.${curr.requiredPermissions}`;
|
||||
}, "");
|
||||
let permissionsToPass = checks.map((c) => `${c.section}.${c.requiredPermission}`).join(" or ");
|
||||
throw new ForbiddenRequestException(`missing permission for ${permissionsToPass}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static sectionAccessPassCheckMiddleware(
|
||||
section: PermissionSection
|
||||
): (req: Request, res: Response, next: Function) => void {
|
||||
return (req: Request, res: Response, next: Function) => {
|
||||
const permissions = req.permissions;
|
||||
const isOwner = req.isOwner;
|
||||
|
||||
if (isOwner || this.canAccessSection(permissions, section)) {
|
||||
next();
|
||||
} else {
|
||||
throw new ForbiddenRequestException(`missing permission for ${section}.${module}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static sectionAccessPassCheckSomeMiddleware(
|
||||
checks: Array<{ section: PermissionSection }>
|
||||
): (req: Request, res: Response, next: Function) => void {
|
||||
return (req: Request, res: Response, next: Function) => {
|
||||
const permissions = req.permissions;
|
||||
const isOwner = req.isOwner;
|
||||
|
||||
if (isOwner || this.canAccessSomeSection(permissions, checks)) {
|
||||
next();
|
||||
} else {
|
||||
let permissionsToPass = checks.map((c) => `${c.section}`).join(" or ");
|
||||
throw new ForbiddenRequestException(`missing permission for ${permissionsToPass}`);
|
||||
}
|
||||
};
|
||||
|
|
286
src/helpers/settingsHelper.ts
Normal file
286
src/helpers/settingsHelper.ts
Normal file
|
@ -0,0 +1,286 @@
|
|||
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 != "") {
|
||||
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 || value === "") {
|
||||
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] = newValue;
|
||||
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 {
|
||||
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,10 +9,10 @@ export abstract class TemplateHelper {
|
|||
static getTemplateFromFile(template: string) {
|
||||
let tmpFile;
|
||||
try {
|
||||
tmpFile = FileSystemHelper.readTemplateFile(`/src/templates/${template}.template.html`);
|
||||
tmpFile = FileSystemHelper.readTemplateFile(`${template}.template.html`);
|
||||
} catch (err) {
|
||||
tmpFile = FileSystemHelper.readTemplateFile(
|
||||
`/src/templates/${template.split(".")[template.split(".").length - 1]}.template.html`
|
||||
`${template.split(".")[template.split(".").length - 1]}.template.html`
|
||||
);
|
||||
}
|
||||
return tmpFile;
|
||||
|
|
14
src/index.ts
14
src/index.ts
|
@ -2,7 +2,7 @@ import "dotenv/config";
|
|||
import "./handlebars.config";
|
||||
import express from "express";
|
||||
|
||||
import { BACKUP_AUTO_RESTORE, configCheck, SERVER_PORT } from "./env.defaults";
|
||||
import { configCheck } from "./env.defaults";
|
||||
configCheck();
|
||||
|
||||
import { PermissionObject } from "./type/permissionTypes";
|
||||
|
@ -21,23 +21,29 @@ declare global {
|
|||
|
||||
import { dataSource } from "./data-source";
|
||||
import BackupHelper from "./helpers/backupHelper";
|
||||
import SettingHelper from "./helpers/settingsHelper";
|
||||
dataSource.initialize().then(async () => {
|
||||
if ((BACKUP_AUTO_RESTORE as "true" | "false") == "true" && (await dataSource.createQueryRunner().hasTable("user"))) {
|
||||
if (await dataSource.createQueryRunner().hasTable("user")) {
|
||||
await BackupHelper.autoRestoreBackup().catch((err) => {
|
||||
console.log(`${new Date().toISOString()}: failed auto-restoring database`, err);
|
||||
});
|
||||
}
|
||||
await SettingHelper.configure();
|
||||
MailHelper.initialize();
|
||||
});
|
||||
|
||||
const app = express();
|
||||
import router from "./routes/index";
|
||||
router(app);
|
||||
app.listen(process.env.NODE_ENV ? SERVER_PORT : 5000, () => {
|
||||
console.log(`${new Date().toISOString()}: listening on port ${process.env.NODE_ENV ? SERVER_PORT : 5000}`);
|
||||
app.listen(process.env.NODE_ENV ? process.env.SERVER_PORT ?? 5000 : 5000, () => {
|
||||
console.log(
|
||||
`${new Date().toISOString()}: listening on port ${process.env.NODE_ENV ? process.env.SERVER_PORT ?? 5000 : 5000}`
|
||||
);
|
||||
});
|
||||
|
||||
import schedule from "node-schedule";
|
||||
import RefreshCommandHandler from "./command/refreshCommandHandler";
|
||||
import MailHelper from "./helpers/mailHelper";
|
||||
const job = schedule.scheduleJob("0 0 * * *", async () => {
|
||||
console.log(`${new Date().toISOString()}: running Cron`);
|
||||
await RefreshCommandHandler.deleteExpired();
|
||||
|
|
35
src/middleware/multer.ts
Normal file
35
src/middleware/multer.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
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 },
|
||||
]);
|
|
@ -1,177 +0,0 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
import {
|
||||
invite_table,
|
||||
refresh_table,
|
||||
reset_table,
|
||||
role_permission_table,
|
||||
role_table,
|
||||
user_permission_table,
|
||||
user_roles_table,
|
||||
user_table,
|
||||
webapi_permission_table,
|
||||
webapi_table,
|
||||
} from "./baseSchemaTables/admin";
|
||||
import { templateUsage } from "../entity/configuration/templateUsage";
|
||||
import {
|
||||
award_table,
|
||||
communication_type_table,
|
||||
executive_position_table,
|
||||
member_awards_table,
|
||||
member_communication_table,
|
||||
member_executive_positions_table,
|
||||
member_executive_positions_view_mysql,
|
||||
member_executive_positions_view_postgres,
|
||||
member_executive_positions_view_sqlite,
|
||||
member_qualifications_table,
|
||||
member_qualifications_view_mysql,
|
||||
member_qualifications_view_postgres,
|
||||
member_qualifications_view_sqlite,
|
||||
member_table,
|
||||
member_view_mysql,
|
||||
member_view_postgres,
|
||||
member_view_sqlite,
|
||||
membership_status_table,
|
||||
membership_table,
|
||||
membership_view_mysql,
|
||||
membership_view_postgres,
|
||||
membership_view_sqlite,
|
||||
qualification_table,
|
||||
salutation_table,
|
||||
} from "./baseSchemaTables/member";
|
||||
import { query_table, template_table, template_usage_table } from "./baseSchemaTables/query_template";
|
||||
import {
|
||||
protocol_agenda_table,
|
||||
protocol_decision_table,
|
||||
protocol_presence_table,
|
||||
protocol_printout_table,
|
||||
protocol_table,
|
||||
protocol_voting_table,
|
||||
} from "./baseSchemaTables/protocol";
|
||||
import { calendar_table, calendar_type_table } from "./baseSchemaTables/calendar";
|
||||
import {
|
||||
newsletter_config_table,
|
||||
newsletter_dates_table,
|
||||
newsletter_recipients_table,
|
||||
newsletter_table,
|
||||
} from "./baseSchemaTables/newsletter";
|
||||
import { DB_TYPE } from "../env.defaults";
|
||||
|
||||
export class CreateSchema1738166167472 implements MigrationInterface {
|
||||
name = "CreateSchema1738166167472";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.createTable(reset_table, true, true, true);
|
||||
await queryRunner.createTable(invite_table, true, true, true);
|
||||
await queryRunner.createTable(role_table, true, true, true);
|
||||
await queryRunner.createTable(role_permission_table, true, true, true);
|
||||
await queryRunner.createTable(user_table, true, true, true);
|
||||
await queryRunner.createTable(user_roles_table, true, true, true);
|
||||
await queryRunner.createTable(user_permission_table, true, true, true);
|
||||
await queryRunner.createTable(refresh_table, true, true, true);
|
||||
await queryRunner.createTable(webapi_table, true, true, true);
|
||||
await queryRunner.createTable(webapi_permission_table, true, true, true);
|
||||
|
||||
await queryRunner.createTable(salutation_table, true, true, true);
|
||||
await queryRunner.createTable(award_table, true, true, true);
|
||||
await queryRunner.createTable(communication_type_table, true, true, true);
|
||||
await queryRunner.createTable(membership_status_table, true, true, true);
|
||||
await queryRunner.createTable(executive_position_table, true, true, true);
|
||||
await queryRunner.createTable(qualification_table, true, true, true);
|
||||
await queryRunner.createTable(member_table, true, true, true);
|
||||
await queryRunner.createTable(member_awards_table, true, true, true);
|
||||
await queryRunner.createTable(member_communication_table, true, true, true);
|
||||
await queryRunner.createTable(membership_table, true, true, true);
|
||||
await queryRunner.createTable(member_executive_positions_table, true, true, true);
|
||||
await queryRunner.createTable(member_qualifications_table, true, true, true);
|
||||
|
||||
if (DB_TYPE == "postgres") await queryRunner.createView(member_view_postgres, true);
|
||||
else if (DB_TYPE == "mysql") await queryRunner.createView(member_view_mysql, true);
|
||||
else if (DB_TYPE == "sqlite") await queryRunner.createView(member_view_sqlite, true);
|
||||
if (DB_TYPE == "postgres") await queryRunner.createView(membership_view_postgres, true);
|
||||
else if (DB_TYPE == "mysql") await queryRunner.createView(membership_view_mysql, true);
|
||||
else if (DB_TYPE == "sqlite") await queryRunner.createView(membership_view_sqlite, true);
|
||||
if (DB_TYPE == "postgres") await queryRunner.createView(member_qualifications_view_postgres, true);
|
||||
else if (DB_TYPE == "mysql") await queryRunner.createView(member_qualifications_view_mysql, true);
|
||||
else if (DB_TYPE == "sqlite") await queryRunner.createView(member_qualifications_view_sqlite, true);
|
||||
if (DB_TYPE == "postgres") await queryRunner.createView(member_executive_positions_view_postgres, true);
|
||||
else if (DB_TYPE == "mysql") await queryRunner.createView(member_executive_positions_view_mysql, true);
|
||||
else if (DB_TYPE == "sqlite") await queryRunner.createView(member_executive_positions_view_sqlite, true);
|
||||
|
||||
await queryRunner.createTable(query_table, true, true, true);
|
||||
await queryRunner.createTable(template_table, true, true, true);
|
||||
await queryRunner.createTable(template_usage_table, true, true, true);
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(templateUsage)
|
||||
.values([{ scope: "newsletter" }, { scope: "protocol" }, { scope: "member.list" }])
|
||||
.orIgnore()
|
||||
.execute();
|
||||
|
||||
await queryRunner.createTable(protocol_table, true, true, true);
|
||||
await queryRunner.createTable(protocol_agenda_table, true, true, true);
|
||||
await queryRunner.createTable(protocol_decision_table, true, true, true);
|
||||
await queryRunner.createTable(protocol_presence_table, true, true, true);
|
||||
await queryRunner.createTable(protocol_voting_table, true, true, true);
|
||||
await queryRunner.createTable(protocol_printout_table, true, true, true);
|
||||
|
||||
await queryRunner.createTable(calendar_type_table, true, true, true);
|
||||
await queryRunner.createTable(calendar_table, true, true, true);
|
||||
|
||||
await queryRunner.createTable(newsletter_config_table, true, true, true);
|
||||
await queryRunner.createTable(newsletter_table, true, true, true);
|
||||
await queryRunner.createTable(newsletter_dates_table, true, true, true);
|
||||
await queryRunner.createTable(newsletter_recipients_table, true, true, true);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropTable("newsletter_dates", true, true, true);
|
||||
await queryRunner.dropTable("newsletter_recipients", true, true, true);
|
||||
await queryRunner.dropTable("newsletter", true, true, true);
|
||||
await queryRunner.dropTable("newsletter_config", true, true, true);
|
||||
|
||||
await queryRunner.dropTable("calendar", true, true, true);
|
||||
await queryRunner.dropTable("calendar_type", true, true, true);
|
||||
|
||||
await queryRunner.dropTable("protocol_agenda", true, true, true);
|
||||
await queryRunner.dropTable("protocol_decision", true, true, true);
|
||||
await queryRunner.dropTable("protocol_presence", true, true, true);
|
||||
await queryRunner.dropTable("protocol_voting", true, true, true);
|
||||
await queryRunner.dropTable("protocol_printout", true, true, true);
|
||||
await queryRunner.dropTable("protocol", true, true, true);
|
||||
|
||||
await queryRunner.dropTable("template_usage", true, true, true);
|
||||
await queryRunner.dropTable("template", true, true, true);
|
||||
await queryRunner.dropTable("query", true, true, true);
|
||||
|
||||
await queryRunner.dropView("member_view");
|
||||
await queryRunner.dropView("membership_view");
|
||||
await queryRunner.dropView("member_qualifications_view");
|
||||
await queryRunner.dropView("member_executive_positions_view");
|
||||
|
||||
await queryRunner.dropTable("member_awards", true, true, true);
|
||||
await queryRunner.dropTable("communication", true, true, true);
|
||||
await queryRunner.dropTable("membership", true, true, true);
|
||||
await queryRunner.dropTable("member_executive_positions", true, true, true);
|
||||
await queryRunner.dropTable("member_qualifications", true, true, true);
|
||||
await queryRunner.dropTable("member", true, true, true);
|
||||
await queryRunner.dropTable("salutation", true, true, true);
|
||||
await queryRunner.dropTable("award", true, true, true);
|
||||
await queryRunner.dropTable("communication_type", true, true, true);
|
||||
await queryRunner.dropTable("membership_status", true, true, true);
|
||||
await queryRunner.dropTable("executive_position", true, true, true);
|
||||
await queryRunner.dropTable("qualification", true, true, true);
|
||||
|
||||
await queryRunner.dropTable("webapi_permission", true, true, true);
|
||||
await queryRunner.dropTable("webapi", true, true, true);
|
||||
await queryRunner.dropTable("refresh", true, true, true);
|
||||
await queryRunner.dropTable("user_permission", true, true, true);
|
||||
await queryRunner.dropTable("user_roles", true, true, true);
|
||||
await queryRunner.dropTable("user", true, true, true);
|
||||
await queryRunner.dropTable("role_permission", true, true, true);
|
||||
await queryRunner.dropTable("role", true, true, true);
|
||||
await queryRunner.dropTable("invite", true, true, true);
|
||||
await queryRunner.dropTable("reset", true, true, true);
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
|
||||
import { templateUsage } from "../entity/configuration/templateUsage";
|
||||
import { getTypeByORM, getDefaultByORM } from "./ormHelper";
|
||||
|
||||
export class TemplatesAndProtocolSort1742549956787 implements MigrationInterface {
|
||||
name = "TemplatesAndProtocolSort1742549956787";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(templateUsage)
|
||||
.values([{ scope: "member" }])
|
||||
.orIgnore()
|
||||
.execute();
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(templateUsage)
|
||||
.where({ scope: "member.list" })
|
||||
.execute();
|
||||
|
||||
await queryRunner.addColumn(
|
||||
"protocol_agenda",
|
||||
new TableColumn({ name: "sort", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) })
|
||||
);
|
||||
|
||||
await queryRunner.addColumn(
|
||||
"protocol_decision",
|
||||
new TableColumn({ name: "sort", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) })
|
||||
);
|
||||
|
||||
await queryRunner.addColumn(
|
||||
"protocol_voting",
|
||||
new TableColumn({ name: "sort", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) })
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropColumn("protocol_agenda", "sort");
|
||||
await queryRunner.dropColumn("protocol_decision", "sort");
|
||||
await queryRunner.dropColumn("protocol_voting", "sort");
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(templateUsage)
|
||||
.values([{ scope: "member.list" }])
|
||||
.orIgnore()
|
||||
.execute();
|
||||
|
||||
await queryRunner.manager.createQueryBuilder().delete().from(templateUsage).where({ scope: "member" }).execute();
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
import { MigrationInterface, QueryRunner, TableColumn, TableForeignKey } from "typeorm";
|
||||
import { getTypeByORM, isIncrementPrimary, isUUIDPrimary } from "./ormHelper";
|
||||
import { query } from "../entity/configuration/query";
|
||||
|
||||
export class QueryToUUID1742922178643 implements MigrationInterface {
|
||||
name = "QueryToUUID1742922178643";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const table = await queryRunner.getTable("newsletter");
|
||||
const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("recipientsByQueryId") !== -1);
|
||||
await queryRunner.dropForeignKey("newsletter", foreignKey);
|
||||
|
||||
const entries = await queryRunner.manager.getRepository(query).find({ select: { title: true, query: true } });
|
||||
await queryRunner.clearTable("query");
|
||||
|
||||
await queryRunner.dropColumn("newsletter", "recipientsByQueryId");
|
||||
await queryRunner.dropColumn("query", "id");
|
||||
|
||||
await queryRunner.addColumn(
|
||||
"query",
|
||||
new TableColumn({
|
||||
name: "id",
|
||||
...getTypeByORM("uuid"),
|
||||
...isUUIDPrimary,
|
||||
})
|
||||
);
|
||||
await queryRunner.addColumn(
|
||||
"newsletter",
|
||||
new TableColumn({
|
||||
name: "recipientsByQueryId",
|
||||
...getTypeByORM("uuid", true),
|
||||
})
|
||||
);
|
||||
|
||||
await queryRunner.manager.createQueryBuilder().insert().into("query").values(entries).execute();
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
"newsletter",
|
||||
new TableForeignKey({
|
||||
columnNames: ["recipientsByQueryId"],
|
||||
referencedColumnNames: ["id"],
|
||||
referencedTableName: "query",
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const table = await queryRunner.getTable("newsletter");
|
||||
const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("recipientsByQueryId") !== -1);
|
||||
await queryRunner.dropForeignKey("newsletter", foreignKey);
|
||||
|
||||
const entries = await queryRunner.manager.getRepository(query).find({ select: { title: true, query: true } });
|
||||
await queryRunner.clearTable("query");
|
||||
|
||||
await queryRunner.dropColumn("newsletter", "recipientsByQueryId");
|
||||
await queryRunner.dropColumn("query", "id");
|
||||
|
||||
await queryRunner.addColumn(
|
||||
"query",
|
||||
new TableColumn({
|
||||
name: "id",
|
||||
...getTypeByORM("int"),
|
||||
...isIncrementPrimary,
|
||||
})
|
||||
);
|
||||
await queryRunner.addColumn(
|
||||
"newsletter",
|
||||
new TableColumn({
|
||||
name: "recipientsByQueryId",
|
||||
...getTypeByORM("int", true),
|
||||
})
|
||||
);
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into("query")
|
||||
.values(entries.map((e, i) => ({ ...e, id: i + 1 })))
|
||||
.execute();
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
"newsletter",
|
||||
new TableForeignKey({
|
||||
columnNames: ["recipientsByQueryId"],
|
||||
referencedColumnNames: ["id"],
|
||||
referencedTableName: "query",
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,21 +1,19 @@
|
|||
import { MigrationInterface, QueryRunner, Table } from "typeorm";
|
||||
import BackupHelper from "../helpers/backupHelper";
|
||||
import { getDefaultByORM, getTypeByORM, isIncrementPrimary } from "./ormHelper";
|
||||
import InternalException from "../exceptions/internalException";
|
||||
import { DB_TYPE } from "../env.defaults";
|
||||
import BackupHelper from "../helpers/backupHelper";
|
||||
import { getTypeByORM, isIncrementPrimary, getDefaultByORM } from "./ormHelper";
|
||||
|
||||
export class BackupAndResetDatabase1738166124200 implements MigrationInterface {
|
||||
name = "BackupAndResetDatabase1738166124200";
|
||||
export class BackupAndResetDatabase1749296262915 implements MigrationInterface {
|
||||
name = "BackupAndResetDatabase1749296262915";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
let query = DB_TYPE == "postgres" ? "SELECT name FROM migrations" : "SELECT `name` FROM `migrations`";
|
||||
let migrations = await queryRunner.query(query);
|
||||
let migrations = await queryRunner.query("SELECT name FROM migrations");
|
||||
if (
|
||||
(await queryRunner.hasTable("user")) &&
|
||||
migrations.findIndex((m: any) => m.name == "MoveSendNewsletterFlag1737816852011") == -1
|
||||
migrations.findIndex((m: any) => m.name == "MemberExtendData1748953828644") == -1
|
||||
) {
|
||||
throw new InternalException(
|
||||
"Cannot update due to skiped version. Update to v1.2.2 Version first to prevent data loss and get access to the newer Versions."
|
||||
"Cannot update due to skiped version. Update to v1.6.0 Version first to prevent data loss and get access to the newer Versions."
|
||||
);
|
||||
}
|
||||
|
176
src/migrations/1749296280721-CreateSchema.ts
Normal file
176
src/migrations/1749296280721-CreateSchema.ts
Normal file
|
@ -0,0 +1,176 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
import {
|
||||
reset_table,
|
||||
invite_table,
|
||||
role_table,
|
||||
role_permission_table,
|
||||
user_table,
|
||||
user_roles_table,
|
||||
user_permission_table,
|
||||
refresh_table,
|
||||
webapi_table,
|
||||
webapi_permission_table,
|
||||
setting_table,
|
||||
} from "./baseSchemaTables/admin";
|
||||
import { calendar_type_table, calendar_table } from "./baseSchemaTables/calendar";
|
||||
import {
|
||||
salutation_table,
|
||||
award_table,
|
||||
communication_type_table,
|
||||
membership_status_table,
|
||||
executive_position_table,
|
||||
qualification_table,
|
||||
member_table,
|
||||
member_awards_table,
|
||||
member_communication_table,
|
||||
membership_table,
|
||||
member_executive_positions_table,
|
||||
member_qualifications_table,
|
||||
member_view,
|
||||
membership_view,
|
||||
member_qualifications_view,
|
||||
member_executive_positions_view,
|
||||
education_table,
|
||||
member_educations_table,
|
||||
membership_total_view,
|
||||
} from "./baseSchemaTables/member";
|
||||
import {
|
||||
newsletter_config_table,
|
||||
newsletter_table,
|
||||
newsletter_dates_table,
|
||||
newsletter_recipients_table,
|
||||
} from "./baseSchemaTables/newsletter";
|
||||
import {
|
||||
protocol_table,
|
||||
protocol_agenda_table,
|
||||
protocol_decision_table,
|
||||
protocol_presence_table,
|
||||
protocol_voting_table,
|
||||
protocol_printout_table,
|
||||
} from "./baseSchemaTables/protocol";
|
||||
import { query_table, template_table, template_usage_table } from "./baseSchemaTables/query_template";
|
||||
import { availableTemplates } from "../type/templateTypes";
|
||||
|
||||
export class CreateSchema1749296280721 implements MigrationInterface {
|
||||
name = "CreateSchema1749296280721";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.createTable(reset_table, true, true, true);
|
||||
await queryRunner.createTable(invite_table, true, true, true);
|
||||
await queryRunner.createTable(role_table, true, true, true);
|
||||
await queryRunner.createTable(role_permission_table, true, true, true);
|
||||
await queryRunner.createTable(user_table, true, true, true);
|
||||
await queryRunner.createTable(user_roles_table, true, true, true);
|
||||
await queryRunner.createTable(user_permission_table, true, true, true);
|
||||
await queryRunner.createTable(refresh_table, true, true, true);
|
||||
await queryRunner.createTable(webapi_table, true, true, true);
|
||||
await queryRunner.createTable(webapi_permission_table, true, true, true);
|
||||
await queryRunner.createTable(setting_table, true, true, true);
|
||||
|
||||
await queryRunner.createTable(salutation_table, true, true, true);
|
||||
await queryRunner.createTable(award_table, true, true, true);
|
||||
await queryRunner.createTable(communication_type_table, true, true, true);
|
||||
await queryRunner.createTable(membership_status_table, true, true, true);
|
||||
await queryRunner.createTable(executive_position_table, true, true, true);
|
||||
await queryRunner.createTable(qualification_table, true, true, true);
|
||||
await queryRunner.createTable(education_table, true, true, true);
|
||||
await queryRunner.createTable(member_table, true, true, true);
|
||||
await queryRunner.createTable(member_awards_table, true, true, true);
|
||||
await queryRunner.createTable(member_communication_table, true, true, true);
|
||||
await queryRunner.createTable(membership_table, true, true, true);
|
||||
await queryRunner.createTable(member_executive_positions_table, true, true, true);
|
||||
await queryRunner.createTable(member_qualifications_table, true, true, true);
|
||||
await queryRunner.createTable(member_educations_table, true, true, true);
|
||||
|
||||
await queryRunner.createView(member_view, true);
|
||||
await queryRunner.createView(membership_view, true);
|
||||
await queryRunner.createView(membership_total_view, true);
|
||||
await queryRunner.createView(member_qualifications_view, true);
|
||||
await queryRunner.createView(member_executive_positions_view, true);
|
||||
|
||||
await queryRunner.createTable(query_table, true, true, true);
|
||||
await queryRunner.createTable(template_table, true, true, true);
|
||||
await queryRunner.createTable(template_usage_table, true, true, true);
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(template_usage_table.name)
|
||||
.values(
|
||||
availableTemplates.map((at) => ({
|
||||
scope: at,
|
||||
}))
|
||||
)
|
||||
.orIgnore()
|
||||
.execute();
|
||||
|
||||
await queryRunner.createTable(protocol_table, true, true, true);
|
||||
await queryRunner.createTable(protocol_agenda_table, true, true, true);
|
||||
await queryRunner.createTable(protocol_decision_table, true, true, true);
|
||||
await queryRunner.createTable(protocol_presence_table, true, true, true);
|
||||
await queryRunner.createTable(protocol_voting_table, true, true, true);
|
||||
await queryRunner.createTable(protocol_printout_table, true, true, true);
|
||||
|
||||
await queryRunner.createTable(calendar_type_table, true, true, true);
|
||||
await queryRunner.createTable(calendar_table, true, true, true);
|
||||
|
||||
await queryRunner.createTable(newsletter_config_table, true, true, true);
|
||||
await queryRunner.createTable(newsletter_table, true, true, true);
|
||||
await queryRunner.createTable(newsletter_dates_table, true, true, true);
|
||||
await queryRunner.createTable(newsletter_recipients_table, true, true, true);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropTable(newsletter_dates_table, true, true, true);
|
||||
await queryRunner.dropTable(newsletter_recipients_table, true, true, true);
|
||||
await queryRunner.dropTable(newsletter_table, true, true, true);
|
||||
await queryRunner.dropTable(newsletter_config_table, true, true, true);
|
||||
|
||||
await queryRunner.dropTable(calendar_table, true, true, true);
|
||||
await queryRunner.dropTable(calendar_type_table, true, true, true);
|
||||
|
||||
await queryRunner.dropTable(protocol_agenda_table, true, true, true);
|
||||
await queryRunner.dropTable(protocol_decision_table, true, true, true);
|
||||
await queryRunner.dropTable(protocol_presence_table, true, true, true);
|
||||
await queryRunner.dropTable(protocol_voting_table, true, true, true);
|
||||
await queryRunner.dropTable(protocol_printout_table, true, true, true);
|
||||
await queryRunner.dropTable(protocol_table, true, true, true);
|
||||
|
||||
await queryRunner.dropTable(template_usage_table, true, true, true);
|
||||
await queryRunner.dropTable(template_table, true, true, true);
|
||||
await queryRunner.dropTable(query_table, true, true, true);
|
||||
|
||||
await queryRunner.dropView(member_view);
|
||||
await queryRunner.dropView(membership_view);
|
||||
await queryRunner.dropView(membership_total_view);
|
||||
await queryRunner.dropView(member_qualifications_view);
|
||||
await queryRunner.dropView(member_executive_positions_view);
|
||||
|
||||
await queryRunner.dropTable(member_awards_table, true, true, true);
|
||||
await queryRunner.dropTable(member_communication_table, true, true, true);
|
||||
await queryRunner.dropTable(membership_table, true, true, true);
|
||||
await queryRunner.dropTable(member_executive_positions_table, true, true, true);
|
||||
await queryRunner.dropTable(member_qualifications_table, true, true, true);
|
||||
await queryRunner.dropTable(member_educations_table, true, true, true);
|
||||
await queryRunner.dropTable(member_table, true, true, true);
|
||||
await queryRunner.dropTable(salutation_table, true, true, true);
|
||||
await queryRunner.dropTable(award_table, true, true, true);
|
||||
await queryRunner.dropTable(communication_type_table, true, true, true);
|
||||
await queryRunner.dropTable(membership_status_table, true, true, true);
|
||||
await queryRunner.dropTable(executive_position_table, true, true, true);
|
||||
await queryRunner.dropTable(qualification_table, true, true, true);
|
||||
await queryRunner.dropTable(education_table, true, true, true);
|
||||
|
||||
await queryRunner.dropTable(setting_table, true, true, true);
|
||||
await queryRunner.dropTable(webapi_permission_table, true, true, true);
|
||||
await queryRunner.dropTable(webapi_table, true, true, true);
|
||||
await queryRunner.dropTable(refresh_table, true, true, true);
|
||||
await queryRunner.dropTable(user_permission_table, true, true, true);
|
||||
await queryRunner.dropTable(user_roles_table, true, true, true);
|
||||
await queryRunner.dropTable(user_table, true, true, true);
|
||||
await queryRunner.dropTable(role_permission_table, true, true, true);
|
||||
await queryRunner.dropTable(role_table, true, true, true);
|
||||
await queryRunner.dropTable(invite_table, true, true, true);
|
||||
await queryRunner.dropTable(reset_table, true, true, true);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { Table, TableForeignKey } from "typeorm";
|
||||
import { Table, TableForeignKey, TableIndex, TableUnique } from "typeorm";
|
||||
import { getDefaultByORM, getTypeByORM, isIncrementPrimary, isUUIDPrimary } from "../ormHelper";
|
||||
import { LoginRoutineEnum } from "../../enums/loginRoutineEnum";
|
||||
|
||||
export const invite_table = new Table({
|
||||
name: "invite",
|
||||
|
@ -16,7 +17,12 @@ export const role_table = new Table({
|
|||
name: "role",
|
||||
columns: [
|
||||
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
|
||||
{ name: "role", ...getTypeByORM("varchar"), isUnique: true },
|
||||
{ name: "role", ...getTypeByORM("varchar") },
|
||||
],
|
||||
uniques: [
|
||||
new TableUnique({
|
||||
columnNames: ["role"],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -45,8 +51,8 @@ export const user_table = new Table({
|
|||
{ name: "username", ...getTypeByORM("varchar"), isUnique: true },
|
||||
{ name: "firstname", ...getTypeByORM("varchar") },
|
||||
{ name: "lastname", ...getTypeByORM("varchar") },
|
||||
{ name: "secret", ...getTypeByORM("varchar") },
|
||||
{ name: "static", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) },
|
||||
{ name: "secret", ...getTypeByORM("text") },
|
||||
{ name: "routine", ...getTypeByORM("varchar"), default: getDefaultByORM("string", LoginRoutineEnum.totp) },
|
||||
{ name: "isOwner", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) },
|
||||
],
|
||||
});
|
||||
|
@ -73,6 +79,14 @@ export const user_roles_table = new Table({
|
|||
onUpdate: "RESTRICT",
|
||||
}),
|
||||
],
|
||||
indices: [
|
||||
new TableIndex({
|
||||
columnNames: ["userId"],
|
||||
}),
|
||||
new TableIndex({
|
||||
columnNames: ["roleId"],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const user_permission_table = new Table({
|
||||
|
@ -114,11 +128,19 @@ export const webapi_table = new Table({
|
|||
name: "webapi",
|
||||
columns: [
|
||||
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
|
||||
{ name: "token", ...getTypeByORM("varchar"), isUnique: true },
|
||||
{ name: "title", ...getTypeByORM("varchar"), isUnique: true },
|
||||
{ name: "token", ...getTypeByORM("text") },
|
||||
{ name: "title", ...getTypeByORM("varchar") },
|
||||
{ name: "createdAt", ...getTypeByORM("datetime", false, 6), default: getDefaultByORM("currentTimestamp", 6) },
|
||||
{ name: "lastUsage", ...getTypeByORM("datetime", true, 6), default: getDefaultByORM("null") },
|
||||
{ name: "expiry", ...getTypeByORM("date", true), default: getDefaultByORM("null") },
|
||||
{ name: "lastUsage", ...getTypeByORM("datetime", true, 6) },
|
||||
{ name: "expiry", ...getTypeByORM("date", true) },
|
||||
],
|
||||
uniques: [
|
||||
new TableUnique({
|
||||
columnNames: ["token"],
|
||||
}),
|
||||
new TableUnique({
|
||||
columnNames: ["title"],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -148,3 +170,12 @@ export const reset_table = new Table({
|
|||
{ name: "secret", ...getTypeByORM("varchar") },
|
||||
],
|
||||
});
|
||||
|
||||
export const setting_table = new Table({
|
||||
name: "setting",
|
||||
columns: [
|
||||
{ name: "topic", ...getTypeByORM("varchar"), isPrimary: true },
|
||||
{ name: "key", ...getTypeByORM("varchar"), isPrimary: true },
|
||||
{ name: "value", ...getTypeByORM("text") },
|
||||
],
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Table, TableForeignKey } from "typeorm";
|
||||
import { Table, TableForeignKey, TableUnique } from "typeorm";
|
||||
import { getDefaultByORM, getTypeByORM, isIncrementPrimary, isUUIDPrimary } from "../ormHelper";
|
||||
|
||||
export const calendar_type_table = new Table({
|
||||
|
@ -32,7 +32,7 @@ export const calendar_table = new Table({
|
|||
default: getDefaultByORM("currentTimestamp", 6),
|
||||
onUpdate: getDefaultByORM<string>("currentTimestamp", 6),
|
||||
},
|
||||
{ name: "webpageId", ...getTypeByORM("varchar", true), default: getDefaultByORM("null"), isUnique: true },
|
||||
{ name: "webpageId", ...getTypeByORM("varchar", true) },
|
||||
{ name: "typeId", ...getTypeByORM("int") },
|
||||
],
|
||||
foreignKeys: [
|
||||
|
@ -44,4 +44,9 @@ export const calendar_table = new Table({
|
|||
onUpdate: "RESTRICT",
|
||||
}),
|
||||
],
|
||||
uniques: [
|
||||
new TableUnique({
|
||||
columnNames: ["webpageId"],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Table, TableForeignKey, View } from "typeorm";
|
||||
import { Table, TableForeignKey, TableUnique, View } from "typeorm";
|
||||
import { getDefaultByORM, getTypeByORM, isIncrementPrimary, isUUIDPrimary } from "../ormHelper";
|
||||
|
||||
export const salutation_table = new Table({
|
||||
|
@ -51,17 +51,28 @@ export const qualification_table = new Table({
|
|||
],
|
||||
});
|
||||
|
||||
export const education_table = new Table({
|
||||
name: "education",
|
||||
columns: [
|
||||
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
|
||||
{ name: "education", ...getTypeByORM("varchar"), isUnique: true },
|
||||
{ name: "description", ...getTypeByORM("varchar"), isNullable: true },
|
||||
],
|
||||
});
|
||||
|
||||
/** member and relations */
|
||||
export const member_table = new Table({
|
||||
name: "member",
|
||||
columns: [
|
||||
{ name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary },
|
||||
{ name: "salutationId", ...getTypeByORM("int") },
|
||||
{ name: "internalId", ...getTypeByORM("varchar", true), default: getDefaultByORM("null"), isUnique: true },
|
||||
{ name: "internalId", ...getTypeByORM("varchar", true) },
|
||||
{ name: "firstname", ...getTypeByORM("varchar") },
|
||||
{ name: "lastname", ...getTypeByORM("varchar") },
|
||||
{ name: "nameaffix", ...getTypeByORM("varchar") },
|
||||
{ name: "birthdate", ...getTypeByORM("date") },
|
||||
{ name: "createdAt", ...getTypeByORM("datetime", false, 6), default: getDefaultByORM("currentTimestamp", 6) },
|
||||
{ name: "note", ...getTypeByORM("varchar", true) },
|
||||
],
|
||||
foreignKeys: [
|
||||
new TableForeignKey({
|
||||
|
@ -72,6 +83,11 @@ export const member_table = new Table({
|
|||
onUpdate: "RESTRICT",
|
||||
}),
|
||||
],
|
||||
uniques: [
|
||||
new TableUnique({
|
||||
columnNames: ["internalId"],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const membership_table = new Table({
|
||||
|
@ -163,7 +179,7 @@ export const member_awards_table = new Table({
|
|||
name: "member_awards",
|
||||
columns: [
|
||||
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
|
||||
{ name: "given", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) },
|
||||
{ name: "given", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", true) },
|
||||
{ name: "note", ...getTypeByORM("varchar"), isNullable: true },
|
||||
{ name: "date", ...getTypeByORM("date") },
|
||||
{ name: "memberId", ...getTypeByORM("uuid") },
|
||||
|
@ -194,13 +210,13 @@ export const member_communication_table = new Table({
|
|||
{ name: "preferred", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) },
|
||||
{ name: "isSendNewsletter", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) },
|
||||
{ name: "isSMSAlarming", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) },
|
||||
{ name: "mobile", ...getTypeByORM("varchar"), isNullable: true },
|
||||
{ name: "email", ...getTypeByORM("varchar"), isNullable: true },
|
||||
{ name: "postalCode", ...getTypeByORM("varchar"), default: getDefaultByORM("null"), isNullable: true },
|
||||
{ name: "city", ...getTypeByORM("varchar"), isNullable: true },
|
||||
{ name: "street", ...getTypeByORM("varchar"), isNullable: true },
|
||||
{ name: "mobile", ...getTypeByORM("varchar", true) },
|
||||
{ name: "email", ...getTypeByORM("varchar", true) },
|
||||
{ name: "postalCode", ...getTypeByORM("varchar", true) },
|
||||
{ name: "city", ...getTypeByORM("varchar", true) },
|
||||
{ name: "street", ...getTypeByORM("varchar", true) },
|
||||
{ name: "streetNumber", ...getTypeByORM("int", true) },
|
||||
{ name: "streetNumberAddition", ...getTypeByORM("varchar"), isNullable: true },
|
||||
{ name: "streetNumberAddition", ...getTypeByORM("varchar", true) },
|
||||
{ name: "memberId", ...getTypeByORM("uuid") },
|
||||
{ name: "typeId", ...getTypeByORM("int") },
|
||||
],
|
||||
|
@ -222,108 +238,60 @@ export const member_communication_table = new Table({
|
|||
],
|
||||
});
|
||||
|
||||
/** views */
|
||||
export const member_view_mysql = new View({
|
||||
name: "member_view",
|
||||
expression: `
|
||||
SELECT
|
||||
\`member\`.\`id\` AS \`id\`,
|
||||
\`member\`.\`internalId\` AS \`internalId\`,
|
||||
\`member\`.\`firstname\` AS \`firstname\`,
|
||||
\`member\`.\`lastname\` AS \`lastname\`,
|
||||
\`member\`.\`nameaffix\` AS \`nameaffix\`,
|
||||
\`member\`.\`birthdate\` AS \`birthdate\`,
|
||||
\`salutation\`.\`salutation\` AS \`salutation\`,
|
||||
TIMESTAMPDIFF(YEAR, \`member\`.\`birthdate\`, CURDATE()) AS \`todayAge\`,
|
||||
YEAR(CURDATE()) - YEAR(\`member\`.\`birthdate\`) AS \`ageThisYear\`,
|
||||
CONCAT(
|
||||
TIMESTAMPDIFF(YEAR, \`member\`.\`birthdate\`, CURDATE()), ' years ',
|
||||
TIMESTAMPDIFF(MONTH, \`member\`.\`birthdate\`, CURDATE()) % 12, ' months ',
|
||||
TIMESTAMPDIFF(DAY,
|
||||
DATE_ADD(
|
||||
\`member\`.\`birthdate\`,
|
||||
INTERVAL TIMESTAMPDIFF(MONTH, \`member\`.\`birthdate\`, CURDATE()) MONTH
|
||||
),
|
||||
CURDATE()
|
||||
), ' days'
|
||||
) AS \`exactAge\`
|
||||
FROM \`member\` \`member\`
|
||||
LEFT JOIN \`salutation\` \`salutation\` ON \`salutation\`.\`id\`=\`member\`.\`salutationId\`
|
||||
`,
|
||||
export const member_educations_table = new Table({
|
||||
name: "member_educations",
|
||||
columns: [
|
||||
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
|
||||
{ name: "start", ...getTypeByORM("date") },
|
||||
{ name: "end", ...getTypeByORM("date", true), default: getDefaultByORM("null") },
|
||||
{ name: "note", ...getTypeByORM("varchar"), isNullable: true },
|
||||
{ name: "place", ...getTypeByORM("varchar"), isNullable: true },
|
||||
{ name: "memberId", ...getTypeByORM("uuid") },
|
||||
{ name: "educationId", ...getTypeByORM("int") },
|
||||
],
|
||||
foreignKeys: [
|
||||
new TableForeignKey({
|
||||
columnNames: ["memberId"],
|
||||
referencedTableName: "member",
|
||||
referencedColumnNames: ["id"],
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "RESTRICT",
|
||||
}),
|
||||
new TableForeignKey({
|
||||
columnNames: ["educationId"],
|
||||
referencedTableName: "education",
|
||||
referencedColumnNames: ["id"],
|
||||
onDelete: "RESTRICT",
|
||||
onUpdate: "RESTRICT",
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const member_view_postgres = new View({
|
||||
/** views */
|
||||
|
||||
export const member_view = new View({
|
||||
name: "member_view",
|
||||
expression: `
|
||||
SELECT
|
||||
"member"."id" AS "id",
|
||||
"member"."internalId" AS "internalId",
|
||||
"member"."firstname" AS "firstname",
|
||||
"member"."lastname" AS "lastname",
|
||||
"member"."nameaffix" AS "nameaffix",
|
||||
"member"."birthdate" AS "birthdate",
|
||||
"member"."internalId" AS "internalId",
|
||||
"member"."note" AS "note",
|
||||
"salutation"."salutation" AS "salutation",
|
||||
DATE_PART('year', AGE(CURRENT_DATE, member.birthdate)) AS "todayAge",
|
||||
EXTRACT(YEAR FROM CURRENT_DATE) - EXTRACT(YEAR FROM member.birthdate) AS "ageThisYear",
|
||||
AGE(CURRENT_DATE, member.birthdate) AS "exactAge"
|
||||
DATE_PART('year', AGE(CURRENT_DATE, "member"."birthdate")) AS "todayAge",
|
||||
EXTRACT(YEAR FROM CURRENT_DATE) - EXTRACT(YEAR FROM "member"."birthdate") AS "ageThisYear",
|
||||
AGE(CURRENT_DATE, "member"."birthdate") AS "exactAge"
|
||||
FROM "member" "member"
|
||||
LEFT JOIN "salutation" "salutation" ON "salutation"."id"="member"."salutationId"
|
||||
`,
|
||||
`
|
||||
.replace(/\s+/g, " ")
|
||||
.trim(),
|
||||
});
|
||||
|
||||
export const member_view_sqlite = new View({
|
||||
name: "member_view",
|
||||
expression: `
|
||||
SELECT
|
||||
member.id AS id,
|
||||
member.internalId AS internalId,
|
||||
member.firstname AS firstname,
|
||||
member.lastname AS lastname,
|
||||
member.nameaffix AS nameaffix,
|
||||
member.birthdate AS birthdate,
|
||||
salutation.salutation AS salutation,
|
||||
(strftime('%Y', 'now') - strftime('%Y', member.birthdate) - (strftime('%m-%d', 'now') < strftime('%m-%d', member.birthdate))) AS todayAge,
|
||||
FLOOR(strftime('%Y', 'now') - strftime('%Y', member.birthdate)) AS ageThisYear,
|
||||
(strftime('%Y', 'now') - strftime('%Y', member.birthdate)) || ' years ' ||
|
||||
(strftime('%m', 'now') - strftime('%m', member.birthdate)) || ' months ' ||
|
||||
(strftime('%d', 'now') - strftime('%d', member.birthdate)) || ' days'
|
||||
AS exactAge
|
||||
FROM member member
|
||||
LEFT JOIN salutation salutation ON salutation.id=member.salutationId
|
||||
`,
|
||||
});
|
||||
|
||||
export const member_executive_positions_view_mysql = new View({
|
||||
name: "member_executive_positions_view",
|
||||
expression: `
|
||||
SELECT
|
||||
\`executivePosition\`.\`id\` AS \`positionId\`,
|
||||
\`executivePosition\`.\`position\` AS \`position\`,
|
||||
\`member\`.\`id\` AS \`memberId\`,
|
||||
\`member\`.\`firstname\` AS \`memberFirstname\`,
|
||||
\`member\`.\`lastname\` AS \`memberLastname\`,
|
||||
\`member\`.\`nameaffix\` AS \`memberNameaffix\`,
|
||||
\`member\`.\`birthdate\` AS \`memberBirthdate\`,
|
||||
\`salutation\`.\`salutation\` AS \`memberSalutation\`,
|
||||
SUM(DATEDIFF(COALESCE(\`memberExecutivePositions\`.\`end\`, CURDATE()), \`memberExecutivePositions\`.\`start\`)) AS \`durationInDays\`,
|
||||
SUM(TIMESTAMPDIFF(YEAR, \`memberExecutivePositions\`.\`start\`, COALESCE(\`memberExecutivePositions\`.\`end\`, CURDATE()))) AS \`durationInYears\`,
|
||||
CONCAT(
|
||||
SUM(FLOOR(TIMESTAMPDIFF(DAY, \`memberExecutivePositions\`.\`start\`, COALESCE(\`memberExecutivePositions\`.\`end\`, CURDATE())) / 365.25)),
|
||||
' years ',
|
||||
SUM(FLOOR(MOD(TIMESTAMPDIFF(MONTH, \`memberExecutivePositions\`.\`start\`, COALESCE(\`memberExecutivePositions\`.\`end\`, CURDATE())), 12))),
|
||||
' months ',
|
||||
SUM(FLOOR(MOD(TIMESTAMPDIFF(DAY, \`memberExecutivePositions\`.\`start\`, COALESCE(\`memberExecutivePositions\`.\`end\`, CURDATE())), 30))),
|
||||
' days'
|
||||
) AS \`exactDuration\`
|
||||
FROM \`member_executive_positions\` \`memberExecutivePositions\`
|
||||
LEFT JOIN \`executive_position\` \`executivePosition\` ON \`executivePosition\`.\`id\`=\`memberExecutivePositions\`.\`executivePositionId\`
|
||||
LEFT JOIN \`member\` \`member\` ON \`member\`.\`id\`=\`memberExecutivePositions\`.\`memberId\`
|
||||
LEFT JOIN \`salutation\` \`salutation\` ON \`salutation\`.\`id\`=\`member\`.\`salutationId\`
|
||||
GROUP BY \`executivePosition\`.\`id\`, \`member\`.\`id\`, \`salutation\`.\`id\`
|
||||
`,
|
||||
});
|
||||
|
||||
export const member_executive_positions_view_postgres = new View({
|
||||
export const member_executive_positions_view = new View({
|
||||
name: "member_executive_positions_view",
|
||||
expression: `
|
||||
SELECT
|
||||
|
@ -343,66 +311,12 @@ export const member_executive_positions_view_postgres = new View({
|
|||
LEFT JOIN "member" "member" ON "member"."id"="memberExecutivePositions"."memberId"
|
||||
LEFT JOIN "salutation" "salutation" ON "salutation"."id"="member"."salutationId"
|
||||
GROUP BY "executivePosition"."id", "member"."id", "salutation"."id"
|
||||
`,
|
||||
`
|
||||
.replace(/\s+/g, " ")
|
||||
.trim(),
|
||||
});
|
||||
|
||||
export const member_executive_positions_view_sqlite = new View({
|
||||
name: "member_executive_positions_view",
|
||||
expression: `
|
||||
SELECT
|
||||
executivePosition.id AS positionId,
|
||||
executivePosition.position AS position,
|
||||
member.id AS memberId,
|
||||
member.firstname AS memberFirstname,
|
||||
member.lastname AS memberLastname,
|
||||
member.nameaffix AS memberNameaffix,
|
||||
member.birthdate AS memberBirthdate,
|
||||
salutation.salutation AS memberSalutation,
|
||||
SUM(JULIANDAY(COALESCE(memberExecutivePositions.end, DATE('now'))) - JULIANDAY(memberExecutivePositions.start)) AS durationInDays,
|
||||
SUM(FLOOR((JULIANDAY(COALESCE(memberExecutivePositions.end, DATE('now'))) - JULIANDAY(memberExecutivePositions.start)) / 365.25)) AS durationInYears,
|
||||
SUM((strftime('%Y', COALESCE(memberExecutivePositions.end, DATE('now'))) - strftime('%Y', memberExecutivePositions.start))) || ' years ' ||
|
||||
SUM((strftime('%m', COALESCE(memberExecutivePositions.end, DATE('now'))) - strftime('%m', memberExecutivePositions.start))) || ' months ' ||
|
||||
SUM((strftime('%d', COALESCE(memberExecutivePositions.end, DATE('now'))) - strftime('%d', memberExecutivePositions.start))) || ' days'
|
||||
AS exactDuration
|
||||
FROM member_executive_positions memberExecutivePositions
|
||||
LEFT JOIN executive_position executivePosition ON executivePosition.id=memberExecutivePositions.executivePositionId
|
||||
LEFT JOIN member member ON member.id=memberExecutivePositions.memberId
|
||||
LEFT JOIN salutation salutation ON salutation.id=member.salutationId
|
||||
GROUP BY executivePosition.id, member.id, salutation.id
|
||||
`,
|
||||
});
|
||||
|
||||
export const member_qualifications_view_mysql = new View({
|
||||
name: "member_qualifications_view",
|
||||
expression: `
|
||||
SELECT
|
||||
\`qualification\`.\`id\` AS \`qualificationId\`,
|
||||
\`qualification\`.\`qualification\` AS \`qualification\`,
|
||||
\`member\`.\`id\` AS \`memberId\`,
|
||||
\`member\`.\`firstname\` AS \`memberFirstname\`,
|
||||
\`member\`.\`lastname\` AS \`memberLastname\`,
|
||||
\`member\`.\`nameaffix\` AS \`memberNameaffix\`,
|
||||
\`member\`.\`birthdate\` AS \`memberBirthdate\`,
|
||||
\`salutation\`.\`salutation\` AS \`memberSalutation\`,
|
||||
SUM(DATEDIFF(COALESCE(\`memberQualifications\`.\`end\`, CURDATE()), \`memberQualifications\`.\`start\`)) AS \`durationInDays\`,
|
||||
SUM(TIMESTAMPDIFF(YEAR, \`memberQualifications\`.\`start\`, COALESCE(\`memberQualifications\`.\`end\`, CURDATE()))) AS \`durationInYears\`,
|
||||
CONCAT(
|
||||
SUM(FLOOR(TIMESTAMPDIFF(DAY, \`memberQualifications\`.\`start\`, COALESCE(\`memberQualifications\`.\`end\`, CURDATE())) / 365.25)),
|
||||
' years ',
|
||||
SUM(FLOOR(MOD(TIMESTAMPDIFF(MONTH, \`memberQualifications\`.\`start\`, COALESCE(\`memberQualifications\`.\`end\`, CURDATE())), 12))),
|
||||
' months ',
|
||||
SUM(FLOOR(MOD(TIMESTAMPDIFF(DAY, \`memberQualifications\`.\`start\`, COALESCE(\`memberQualifications\`.\`end\`, CURDATE())), 30))),
|
||||
' days'
|
||||
) AS \`exactDuration\`
|
||||
FROM \`member_qualifications\` \`memberQualifications\`
|
||||
LEFT JOIN \`qualification\` \`qualification\` ON \`qualification\`.\`id\`=\`memberQualifications\`.\`qualificationId\`
|
||||
LEFT JOIN \`member\` \`member\` ON \`member\`.\`id\`=\`memberQualifications\`.\`memberId\`
|
||||
LEFT JOIN \`salutation\` \`salutation\` ON \`salutation\`.\`id\`=\`member\`.\`salutationId\`
|
||||
GROUP BY \`qualification\`.\`id\`, \`member\`.\`id\`, \`salutation\`.\`id\`
|
||||
`,
|
||||
});
|
||||
|
||||
export const member_qualifications_view_postgres = new View({
|
||||
export const member_qualifications_view = new View({
|
||||
name: "member_qualifications_view",
|
||||
expression: `
|
||||
SELECT
|
||||
|
@ -422,66 +336,12 @@ export const member_qualifications_view_postgres = new View({
|
|||
LEFT JOIN "member" "member" ON "member"."id"="memberQualifications"."memberId"
|
||||
LEFT JOIN "salutation" "salutation" ON "salutation"."id"="member"."salutationId"
|
||||
GROUP BY "qualification"."id", "member"."id", "salutation"."id"
|
||||
`,
|
||||
`
|
||||
.replace(/\s+/g, " ")
|
||||
.trim(),
|
||||
});
|
||||
|
||||
export const member_qualifications_view_sqlite = new View({
|
||||
name: "member_qualifications_view",
|
||||
expression: `
|
||||
SELECT
|
||||
qualification.id AS qualificationId,
|
||||
qualification.qualification AS qualification,
|
||||
member.id AS memberId,
|
||||
member.firstname AS memberFirstname,
|
||||
member.lastname AS memberLastname,
|
||||
member.nameaffix AS memberNameaffix,
|
||||
member.birthdate AS memberBirthdate,
|
||||
salutation.salutation AS memberSalutation,
|
||||
SUM(JULIANDAY(COALESCE(memberQualifications.end, DATE('now'))) - JULIANDAY(memberQualifications.start)) AS durationInDays,
|
||||
SUM(FLOOR((JULIANDAY(COALESCE(memberQualifications.end, DATE('now'))) - JULIANDAY(memberQualifications.start)) / 365.25)) AS durationInYears,
|
||||
SUM((strftime('%Y', COALESCE(memberQualifications.end, DATE('now'))) - strftime('%Y', memberQualifications.start))) || ' years ' ||
|
||||
SUM((strftime('%m', COALESCE(memberQualifications.end, DATE('now'))) - strftime('%m', memberQualifications.start))) || ' months ' ||
|
||||
SUM((strftime('%d', COALESCE(memberQualifications.end, DATE('now'))) - strftime('%d', memberQualifications.start))) || ' days'
|
||||
AS exactDuration
|
||||
FROM member_qualifications memberQualifications
|
||||
LEFT JOIN qualification qualification ON qualification.id=memberQualifications.qualificationId
|
||||
LEFT JOIN member member ON member.id=memberQualifications.memberId
|
||||
LEFT JOIN salutation salutation ON salutation.id=member.salutationId
|
||||
GROUP BY qualification.id, member.id, salutation.id
|
||||
`,
|
||||
});
|
||||
|
||||
export const membership_view_mysql = new View({
|
||||
name: "membership_view",
|
||||
expression: `
|
||||
SELECT
|
||||
\`status\`.\`id\` AS \`statusId\`,
|
||||
\`status\`.\`status\` AS \`status\`,
|
||||
\`member\`.\`id\` AS \`memberId\`,
|
||||
\`member\`.\`firstname\` AS \`memberFirstname\`,
|
||||
\`member\`.\`lastname\` AS \`memberLastname\`,
|
||||
\`member\`.\`nameaffix\` AS \`memberNameaffix\`,
|
||||
\`member\`.\`birthdate\` AS \`memberBirthdate\`,
|
||||
\`salutation\`.\`salutation\` AS \`memberSalutation\`,
|
||||
SUM(DATEDIFF(COALESCE(\`membership\`.\`end\`, CURDATE()), \`membership\`.\`start\`)) AS \`durationInDays\`,
|
||||
SUM(TIMESTAMPDIFF(YEAR, \`membership\`.\`start\`, COALESCE(\`membership\`.\`end\`, CURDATE()))) AS \`durationInYears\`,
|
||||
CONCAT(
|
||||
SUM(FLOOR(TIMESTAMPDIFF(DAY, \`membership\`.\`start\`, COALESCE(\`membership\`.\`end\`, CURDATE())) / 365.25)),
|
||||
' years ',
|
||||
SUM(FLOOR(MOD(TIMESTAMPDIFF(MONTH, \`membership\`.\`start\`, COALESCE(\`membership\`.\`end\`, CURDATE())), 12))),
|
||||
' months ',
|
||||
SUM(FLOOR(MOD(TIMESTAMPDIFF(DAY, \`membership\`.\`start\`, COALESCE(\`membership\`.\`end\`, CURDATE())), 30))),
|
||||
' days'
|
||||
) AS \`exactDuration\`
|
||||
FROM \`membership\` \`membership\`
|
||||
LEFT JOIN \`membership_status\` \`status\` ON \`status\`.\`id\`=\`membership\`.\`statusId\`
|
||||
LEFT JOIN \`member\` \`member\` ON \`member\`.\`id\`=\`membership\`.\`memberId\`
|
||||
LEFT JOIN \`salutation\` \`salutation\` ON \`salutation\`.\`id\`=\`member\`.\`salutationId\`
|
||||
GROUP BY \`status\`.\`id\`, \`member\`.\`id\`, \`salutation\`.\`id\`
|
||||
`,
|
||||
});
|
||||
|
||||
export const membership_view_postgres = new View({
|
||||
export const membership_view = new View({
|
||||
name: "membership_view",
|
||||
expression: `
|
||||
SELECT
|
||||
|
@ -500,32 +360,31 @@ export const membership_view_postgres = new View({
|
|||
LEFT JOIN "membership_status" "status" ON "status"."id"="membership"."statusId"
|
||||
LEFT JOIN "member" "member" ON "member"."id"="membership"."memberId"
|
||||
LEFT JOIN "salutation" "salutation" ON "salutation"."id"="member"."salutationId"
|
||||
GROUP BY "status"."id","member"."id", "salutation"."id"
|
||||
`,
|
||||
GROUP BY "status"."id", "member"."id", "salutation"."id"
|
||||
`
|
||||
.replace(/\s+/g, " ")
|
||||
.trim(),
|
||||
});
|
||||
|
||||
export const membership_view_sqlite = new View({
|
||||
name: "membership_view",
|
||||
export const membership_total_view = new View({
|
||||
name: "membership_total_view",
|
||||
expression: `
|
||||
SELECT
|
||||
status.id AS statusId,
|
||||
status.status AS status,
|
||||
member.id AS memberId,
|
||||
member.firstname AS memberFirstname,
|
||||
member.lastname AS memberLastname,
|
||||
member.nameaffix AS memberNameaffix,
|
||||
member.birthdate AS memberBirthdate,
|
||||
salutation.salutation AS memberSalutation,
|
||||
SUM(JULIANDAY(COALESCE(membership.end, DATE('now'))) - JULIANDAY(membership.start)) AS durationInDays,
|
||||
SUM(FLOOR((JULIANDAY(COALESCE(membership.end, DATE('now'))) - JULIANDAY(membership.start)) / 365.25)) AS durationInYears,
|
||||
SUM((strftime('%Y', COALESCE(membership.end, DATE('now'))) - strftime('%Y', membership.start))) || ' years ' ||
|
||||
SUM((strftime('%m', COALESCE(membership.end, DATE('now'))) - strftime('%m', membership.start))) || ' months ' ||
|
||||
SUM((strftime('%d', COALESCE(membership.end, DATE('now'))) - strftime('%d', membership.start))) || ' days'
|
||||
AS exactDuration
|
||||
FROM membership membership
|
||||
LEFT JOIN membership_status status ON status.id=membership.statusId
|
||||
LEFT JOIN member member ON member.id=membership.memberId
|
||||
LEFT JOIN salutation salutation ON salutation.id=member.salutationId
|
||||
GROUP BY status.id, member.id, salutation.id
|
||||
`,
|
||||
SELECT
|
||||
"member"."id" AS "memberId",
|
||||
"member"."firstname" AS "memberFirstname",
|
||||
"member"."lastname" AS "memberLastname",
|
||||
"member"."nameaffix" AS "memberNameaffix",
|
||||
"member"."birthdate" AS "memberBirthdate",
|
||||
"salutation"."salutation" AS "memberSalutation",
|
||||
SUM(COALESCE("membership"."end", CURRENT_DATE) - "membership"."start") AS "durationInDays",
|
||||
SUM(EXTRACT(YEAR FROM AGE(COALESCE("membership"."end", CURRENT_DATE), "membership"."start"))) AS "durationInYears",
|
||||
SUM(AGE(COALESCE("membership"."end", CURRENT_DATE), "membership"."start")) AS "exactDuration"
|
||||
FROM "membership" "membership"
|
||||
LEFT JOIN "membership_status" "status" ON "status"."id"="membership"."statusId"
|
||||
LEFT JOIN "member" "member" ON "member"."id"="membership"."memberId"
|
||||
LEFT JOIN "salutation" "salutation" ON "salutation"."id"="member"."salutationId"
|
||||
GROUP BY "member"."id", "salutation"."id"
|
||||
`
|
||||
.replace(/\s+/g, " ")
|
||||
.trim(),
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Table, TableForeignKey } from "typeorm";
|
||||
import { Table, TableForeignKey, TableUnique } from "typeorm";
|
||||
import { getDefaultByORM, getTypeByORM, isIncrementPrimary } from "../ormHelper";
|
||||
|
||||
export const newsletter_table = new Table({
|
||||
|
@ -7,11 +7,12 @@ export const newsletter_table = new Table({
|
|||
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
|
||||
{ name: "title", ...getTypeByORM("varchar") },
|
||||
{ name: "description", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
|
||||
{ name: "newsletterTitle", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
|
||||
{ name: "newsletterTitle", ...getTypeByORM("text"), default: getDefaultByORM("string") },
|
||||
{ name: "newsletterText", ...getTypeByORM("text"), default: getDefaultByORM("string") },
|
||||
{ name: "newsletterSignatur", ...getTypeByORM("varchar"), default: getDefaultByORM("string") },
|
||||
{ name: "newsletterSignatur", ...getTypeByORM("text"), default: getDefaultByORM("string") },
|
||||
{ name: "isSent", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) },
|
||||
{ name: "recipientsByQueryId", ...getTypeByORM("int", true) },
|
||||
{ name: "recipientsByQueryId", ...getTypeByORM("uuid", true) },
|
||||
{ name: "createdAt", ...getTypeByORM("datetime", false, 6), default: getDefaultByORM("currentTimestamp", 6) },
|
||||
],
|
||||
foreignKeys: [
|
||||
new TableForeignKey({
|
||||
|
@ -22,6 +23,11 @@ export const newsletter_table = new Table({
|
|||
onUpdate: "RESTRICT",
|
||||
}),
|
||||
],
|
||||
uniques: [
|
||||
new TableUnique({
|
||||
columnNames: ["title"],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const newsletter_dates_table = new Table({
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Table, TableForeignKey } from "typeorm";
|
||||
import { Table, TableForeignKey, TableUnique } from "typeorm";
|
||||
import { getDefaultByORM, getTypeByORM, isIncrementPrimary } from "../ormHelper";
|
||||
|
||||
export const protocol_table = new Table({
|
||||
name: "protocol",
|
||||
columns: [
|
||||
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
|
||||
{ name: "title", ...getTypeByORM("varchar") },
|
||||
{ name: "title", ...getTypeByORM("varchar"), isUnique: true },
|
||||
{ name: "date", ...getTypeByORM("date") },
|
||||
{ name: "starttime", ...getTypeByORM("time", true) },
|
||||
{ name: "endtime", ...getTypeByORM("time", true) },
|
||||
|
@ -19,6 +19,7 @@ export const protocol_agenda_table = new Table({
|
|||
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
|
||||
{ name: "topic", ...getTypeByORM("varchar") },
|
||||
{ name: "context", ...getTypeByORM("text"), default: getDefaultByORM("string") },
|
||||
{ name: "sort", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) },
|
||||
{ name: "protocolId", ...getTypeByORM("int") },
|
||||
],
|
||||
foreignKeys: [
|
||||
|
@ -38,6 +39,7 @@ export const protocol_decision_table = new Table({
|
|||
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
|
||||
{ name: "topic", ...getTypeByORM("varchar") },
|
||||
{ name: "context", ...getTypeByORM("text"), default: getDefaultByORM("string") },
|
||||
{ name: "sort", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) },
|
||||
{ name: "protocolId", ...getTypeByORM("int") },
|
||||
],
|
||||
foreignKeys: [
|
||||
|
@ -86,6 +88,7 @@ export const protocol_voting_table = new Table({
|
|||
{ name: "favour", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) },
|
||||
{ name: "abstain", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) },
|
||||
{ name: "against", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) },
|
||||
{ name: "sort", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) },
|
||||
{ name: "protocolId", ...getTypeByORM("int") },
|
||||
],
|
||||
foreignKeys: [
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
import { Table, TableForeignKey } from "typeorm";
|
||||
import { getDefaultByORM, getTypeByORM, isIncrementPrimary } from "../ormHelper";
|
||||
import { getDefaultByORM, getTypeByORM, isIncrementPrimary, isUUIDPrimary } from "../ormHelper";
|
||||
|
||||
export const query_table = new Table({
|
||||
name: "query",
|
||||
columns: [
|
||||
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
|
||||
{ name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary },
|
||||
{ name: "title", ...getTypeByORM("varchar"), isUnique: true },
|
||||
{ name: "query", ...getTypeByORM("text"), default: getDefaultByORM("string") },
|
||||
{
|
||||
name: "updatedAt",
|
||||
...getTypeByORM("datetime", false, 6),
|
||||
default: getDefaultByORM("currentTimestamp", 6),
|
||||
onUpdate: getDefaultByORM<string>("currentTimestamp", 6),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
|
|
@ -13,83 +13,38 @@ export type Primary = {
|
|||
};
|
||||
|
||||
export function getTypeByORM(type: ORMType, nullable: boolean = false, length: number = 255): ColumnConfig {
|
||||
const dbType = process.env.DB_TYPE;
|
||||
|
||||
const typeMap: Record<string, Record<ORMType, string>> = {
|
||||
mysql: {
|
||||
int: "int",
|
||||
bigint: "bigint",
|
||||
boolean: "tinyint",
|
||||
date: "date",
|
||||
datetime: "datetime",
|
||||
time: "time",
|
||||
text: "text",
|
||||
varchar: "varchar",
|
||||
uuid: "varchar",
|
||||
},
|
||||
postgres: {
|
||||
int: "integer",
|
||||
bigint: "bigint",
|
||||
boolean: "boolean",
|
||||
date: "date",
|
||||
datetime: "timestamp",
|
||||
time: "time",
|
||||
text: "text",
|
||||
varchar: "character varying",
|
||||
uuid: "uuid",
|
||||
},
|
||||
sqlite: {
|
||||
int: "integer",
|
||||
bigint: "integer",
|
||||
boolean: "integer",
|
||||
date: "date",
|
||||
datetime: "datetime",
|
||||
time: "text",
|
||||
text: "text",
|
||||
varchar: "varchar",
|
||||
uuid: "varchar",
|
||||
},
|
||||
const typeMap: Record<ORMType, string> = {
|
||||
int: "integer",
|
||||
bigint: "bigint",
|
||||
boolean: "boolean",
|
||||
date: "date",
|
||||
datetime: "timestamp",
|
||||
time: "time",
|
||||
text: "text",
|
||||
varchar: "character varying",
|
||||
uuid: "uuid",
|
||||
};
|
||||
|
||||
let obj: ColumnConfig = {
|
||||
type: typeMap[dbType]?.[type] || type,
|
||||
type: typeMap[type] || type,
|
||||
isNullable: nullable,
|
||||
};
|
||||
if (type == "datetime") obj.precision = length;
|
||||
else if (dbType != "sqlite" && (obj.type == "varchar" || type == "varchar")) obj.length = `${length}`;
|
||||
else if (dbType != "postgres" && type == "uuid") obj.length = "36";
|
||||
else if (obj.type == "varchar" || type == "varchar") obj.length = `${length}`;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
export function getDefaultByORM<T = string | null>(type: ORMDefault, data?: string | number | boolean): T {
|
||||
const dbType = process.env.DB_TYPE;
|
||||
|
||||
const typeMap: Record<string, Record<ORMDefault, string | null>> = {
|
||||
mysql: {
|
||||
currentTimestamp: `CURRENT_TIMESTAMP(${data ?? 6})`,
|
||||
string: `'${data ?? ""}'`,
|
||||
boolean: Boolean(data).toString(),
|
||||
number: Number(data).toString(),
|
||||
null: null,
|
||||
},
|
||||
postgres: {
|
||||
currentTimestamp: `now()`,
|
||||
string: `'${data ?? ""}'`,
|
||||
boolean: Boolean(data) == true ? "true" : "false",
|
||||
number: Number(data).toString(),
|
||||
null: null,
|
||||
},
|
||||
sqlite: {
|
||||
currentTimestamp: `datetime('now')`,
|
||||
string: `'${data ?? ""}'`,
|
||||
boolean: Boolean(data) == true ? "1" : "0",
|
||||
number: Number(data).toString(),
|
||||
null: null,
|
||||
},
|
||||
const typeMap: Record<ORMDefault, string | null> = {
|
||||
currentTimestamp: `now()`,
|
||||
string: `'${data ?? ""}'`,
|
||||
boolean: Boolean(data) == true ? "true" : "false",
|
||||
number: Number(data).toString(),
|
||||
null: null,
|
||||
};
|
||||
|
||||
return (typeMap[dbType]?.[type] || type) as T;
|
||||
return (typeMap[type] || type) as T;
|
||||
}
|
||||
|
||||
export const isIncrementPrimary: Primary = {
|
||||
|
|
|
@ -2,12 +2,14 @@ import express, { Request, Response } from "express";
|
|||
import {
|
||||
addAwardToMember,
|
||||
addCommunicationToMember,
|
||||
addEducationToMember,
|
||||
addExecutivePositionToMember,
|
||||
addMembershipToMember,
|
||||
addQualificationToMember,
|
||||
createMember,
|
||||
deleteAwardOfMember,
|
||||
deleteCommunicationOfMember,
|
||||
deleteEducationsOfMember,
|
||||
deleteExecutivePositionOfMember,
|
||||
deleteMemberById,
|
||||
deleteMembershipOfMember,
|
||||
|
@ -17,19 +19,24 @@ import {
|
|||
getAwardsByMember,
|
||||
getCommunicationByMemberAndRecord,
|
||||
getCommunicationsByMember,
|
||||
getEducationByMemberAndRecord,
|
||||
getEducationsByMember,
|
||||
getExecutivePositionByMemberAndRecord,
|
||||
getExecutivePositionsByMember,
|
||||
getMemberById,
|
||||
getMemberLastInternalId,
|
||||
getMemberPrintoutById,
|
||||
getMembersByIds,
|
||||
getMembershipByMemberAndRecord,
|
||||
getMembershipsByMember,
|
||||
getMembershipStatisticsById,
|
||||
getMembershipTotalStatisticsById,
|
||||
getMemberStatisticsById,
|
||||
getQualificationByMemberAndRecord,
|
||||
getQualificationsByMember,
|
||||
updateAwardOfMember,
|
||||
updateCommunicationOfMember,
|
||||
updateEducationOfMember,
|
||||
updateExecutivePositionOfMember,
|
||||
updateMemberById,
|
||||
updateMembershipOfMember,
|
||||
|
@ -43,6 +50,10 @@ router.get("/", async (req: Request, res: Response) => {
|
|||
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) => {
|
||||
await getMembersByIds(req, res);
|
||||
});
|
||||
|
@ -67,6 +78,10 @@ router.get("/:memberId/memberships/statistics", async (req: Request, res: Respon
|
|||
await getMembershipStatisticsById(req, res);
|
||||
});
|
||||
|
||||
router.get("/:memberId/memberships/totalstatistics", async (req: Request, res: Response) => {
|
||||
await getMembershipTotalStatisticsById(req, res);
|
||||
});
|
||||
|
||||
router.get("/:memberId/membership/:id", async (req: Request, res: Response) => {
|
||||
await getMembershipByMemberAndRecord(req, res);
|
||||
});
|
||||
|
@ -87,6 +102,14 @@ router.get("/:memberId/qualification/:id", async (req: Request, res: Response) =
|
|||
await getQualificationByMemberAndRecord(req, res);
|
||||
});
|
||||
|
||||
router.get("/:memberId/educations", async (req: Request, res: Response) => {
|
||||
await getEducationsByMember(req, res);
|
||||
});
|
||||
|
||||
router.get("/:memberId/education/:id", async (req: Request, res: Response) => {
|
||||
await getEducationByMemberAndRecord(req, res);
|
||||
});
|
||||
|
||||
router.get("/:memberId/positions", async (req: Request, res: Response) => {
|
||||
await getExecutivePositionsByMember(req, res);
|
||||
});
|
||||
|
@ -135,6 +158,14 @@ router.post(
|
|||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/:memberId/education",
|
||||
PermissionHelper.passCheckMiddleware("create", "club", "member"),
|
||||
async (req: Request, res: Response) => {
|
||||
await addEducationToMember(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/:memberId/position",
|
||||
PermissionHelper.passCheckMiddleware("create", "club", "member"),
|
||||
|
@ -183,6 +214,14 @@ router.patch(
|
|||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/:memberId/education/:recordId",
|
||||
PermissionHelper.passCheckMiddleware("update", "club", "member"),
|
||||
async (req: Request, res: Response) => {
|
||||
await updateEducationOfMember(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/:memberId/position/:recordId",
|
||||
PermissionHelper.passCheckMiddleware("update", "club", "member"),
|
||||
|
@ -231,6 +270,14 @@ router.delete(
|
|||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/:memberId/education/:recordId",
|
||||
PermissionHelper.passCheckMiddleware("delete", "club", "member"),
|
||||
async (req: Request, res: Response) => {
|
||||
await deleteEducationsOfMember(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/:memberId/position/:recordId",
|
||||
PermissionHelper.passCheckMiddleware("delete", "club", "member"),
|
||||
|
|
45
src/routes/admin/configuration/education.ts
Normal file
45
src/routes/admin/configuration/education.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import express, { Request, Response } from "express";
|
||||
import {
|
||||
createEducation,
|
||||
deleteEducation,
|
||||
getAllEducations,
|
||||
getEducationById,
|
||||
updateEducation,
|
||||
} from "../../../controller/admin/configuration/educationController";
|
||||
import PermissionHelper from "../../../helpers/permissionHelper";
|
||||
|
||||
var router = express.Router({ mergeParams: true });
|
||||
|
||||
router.get("/", async (req: Request, res: Response) => {
|
||||
await getAllEducations(req, res);
|
||||
});
|
||||
|
||||
router.get("/:id", async (req: Request, res: Response) => {
|
||||
await getEducationById(req, res);
|
||||
});
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
PermissionHelper.passCheckMiddleware("create", "configuration", "education"),
|
||||
async (req: Request, res: Response) => {
|
||||
await createEducation(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/:id",
|
||||
PermissionHelper.passCheckMiddleware("update", "configuration", "education"),
|
||||
async (req: Request, res: Response) => {
|
||||
await updateEducation(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/:id",
|
||||
PermissionHelper.passCheckMiddleware("delete", "configuration", "education"),
|
||||
async (req: Request, res: Response) => {
|
||||
await deleteEducation(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
|
@ -7,6 +7,7 @@ import communicationType from "./configuration/communicationType";
|
|||
import executivePosition from "./configuration/executivePosition";
|
||||
import membershipStatus from "./configuration/membershipStatus";
|
||||
import qualification from "./configuration/qualification";
|
||||
import education from "./configuration/education";
|
||||
import salutation from "./configuration/salutation";
|
||||
import calendarType from "./configuration/calendarType";
|
||||
import queryStore from "./configuration/queryStore";
|
||||
|
@ -26,70 +27,79 @@ import user from "./management/user";
|
|||
import invite from "./management/invite";
|
||||
import api from "./management/webapi";
|
||||
import backup from "./management/backup";
|
||||
import setting from "./management/setting";
|
||||
|
||||
var router = express.Router({ mergeParams: true });
|
||||
|
||||
router.use(
|
||||
"/award",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermissions: "read", section: "configuration", module: "award" },
|
||||
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||
{ requiredPermission: "read", section: "configuration", module: "award" },
|
||||
{ requiredPermission: "read", section: "club", module: "member" },
|
||||
]),
|
||||
award
|
||||
);
|
||||
router.use(
|
||||
"/communicationtype",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermissions: "read", section: "configuration", module: "communication_type" },
|
||||
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||
{ requiredPermission: "read", section: "configuration", module: "communication_type" },
|
||||
{ requiredPermission: "read", section: "club", module: "member" },
|
||||
]),
|
||||
communicationType
|
||||
);
|
||||
router.use(
|
||||
"/executiveposition",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermissions: "read", section: "configuration", module: "executive_position" },
|
||||
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||
{ requiredPermission: "read", section: "configuration", module: "executive_position" },
|
||||
{ requiredPermission: "read", section: "club", module: "member" },
|
||||
]),
|
||||
executivePosition
|
||||
);
|
||||
router.use(
|
||||
"/membershipstatus",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermissions: "read", section: "configuration", module: "membership_status" },
|
||||
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||
{ requiredPermission: "read", section: "configuration", module: "membership_status" },
|
||||
{ requiredPermission: "read", section: "club", module: "member" },
|
||||
]),
|
||||
membershipStatus
|
||||
);
|
||||
router.use(
|
||||
"/qualification",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermissions: "read", section: "configuration", module: "qualification" },
|
||||
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||
{ requiredPermission: "read", section: "configuration", module: "qualification" },
|
||||
{ requiredPermission: "read", section: "club", module: "member" },
|
||||
]),
|
||||
qualification
|
||||
);
|
||||
router.use(
|
||||
"/education",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermission: "read", section: "configuration", module: "education" },
|
||||
{ requiredPermission: "read", section: "club", module: "member" },
|
||||
]),
|
||||
education
|
||||
);
|
||||
router.use(
|
||||
"/salutation",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermissions: "read", section: "configuration", module: "salutation" },
|
||||
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||
{ requiredPermission: "read", section: "configuration", module: "salutation" },
|
||||
{ requiredPermission: "read", section: "club", module: "member" },
|
||||
]),
|
||||
salutation
|
||||
);
|
||||
router.use(
|
||||
"/calendartype",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermissions: "read", section: "configuration", module: "calendar_type" },
|
||||
{ requiredPermissions: "read", section: "club", module: "calendar" },
|
||||
{ requiredPermission: "read", section: "configuration", module: "calendar_type" },
|
||||
{ requiredPermission: "read", section: "club", module: "calendar" },
|
||||
]),
|
||||
calendarType
|
||||
);
|
||||
router.use(
|
||||
"/querystore",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermissions: "read", section: "configuration", module: "query_store" },
|
||||
{ requiredPermissions: "read", section: "club", module: "listprint" },
|
||||
{ requiredPermission: "read", section: "configuration", module: "query_store" },
|
||||
{ requiredPermission: "read", section: "club", module: "listprint" },
|
||||
]),
|
||||
queryStore
|
||||
);
|
||||
|
@ -97,16 +107,16 @@ router.use("/template", PermissionHelper.passCheckMiddleware("read", "configurat
|
|||
router.use(
|
||||
"/templateusage",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermissions: "read", section: "configuration", module: "template_usage" },
|
||||
{ requiredPermissions: "read", section: "configuration", module: "template" },
|
||||
{ requiredPermission: "read", section: "configuration", module: "template_usage" },
|
||||
{ requiredPermission: "read", section: "configuration", module: "template" },
|
||||
]),
|
||||
templateUsage
|
||||
);
|
||||
router.use(
|
||||
"/newsletterconfig",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermissions: "read", section: "configuration", module: "newsletter_config" },
|
||||
{ requiredPermissions: "read", section: "configuration", module: "communication_type" },
|
||||
{ requiredPermission: "read", section: "configuration", module: "newsletter_config" },
|
||||
{ requiredPermission: "read", section: "configuration", module: "communication_type" },
|
||||
]),
|
||||
newsletterConfig
|
||||
);
|
||||
|
@ -115,8 +125,8 @@ router.use("/member", PermissionHelper.passCheckMiddleware("read", "club", "memb
|
|||
router.use(
|
||||
"/protocol",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermissions: "read", section: "club", module: "protocol" },
|
||||
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||
{ requiredPermission: "read", section: "club", module: "protocol" },
|
||||
{ requiredPermission: "read", section: "club", module: "member" },
|
||||
]),
|
||||
protocol
|
||||
);
|
||||
|
@ -124,19 +134,19 @@ router.use("/calendar", PermissionHelper.passCheckMiddleware("read", "club", "ca
|
|||
router.use(
|
||||
"/querybuilder",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermissions: "read", section: "club", module: "query" },
|
||||
{ requiredPermissions: "read", section: "configuration", module: "query_store" },
|
||||
{ requiredPermission: "read", section: "club", module: "query" },
|
||||
{ requiredPermission: "read", section: "configuration", module: "query_store" },
|
||||
]),
|
||||
queryBuilder
|
||||
);
|
||||
router.use(
|
||||
"/newsletter",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermissions: "read", section: "club", module: "newsletter" },
|
||||
{ requiredPermissions: "read", section: "club", module: "member" },
|
||||
{ requiredPermissions: "read", section: "club", module: "calendar" },
|
||||
{ requiredPermissions: "read", section: "club", module: "query" },
|
||||
{ requiredPermissions: "read", section: "configuration", module: "query_store" },
|
||||
{ requiredPermission: "read", section: "club", module: "newsletter" },
|
||||
{ requiredPermission: "read", section: "club", module: "member" },
|
||||
{ requiredPermission: "read", section: "club", module: "calendar" },
|
||||
{ requiredPermission: "read", section: "club", module: "query" },
|
||||
{ requiredPermission: "read", section: "configuration", module: "query_store" },
|
||||
]),
|
||||
newsletter
|
||||
);
|
||||
|
@ -146,8 +156,8 @@ router.use("/role", PermissionHelper.passCheckMiddleware("read", "management", "
|
|||
router.use(
|
||||
"/user",
|
||||
PermissionHelper.passCheckSomeMiddleware([
|
||||
{ requiredPermissions: "read", section: "management", module: "user" },
|
||||
{ requiredPermissions: "read", section: "management", module: "role" },
|
||||
{ requiredPermission: "read", section: "management", module: "user" },
|
||||
{ requiredPermission: "read", section: "management", module: "role" },
|
||||
]),
|
||||
user
|
||||
);
|
||||
|
@ -159,5 +169,6 @@ router.use(
|
|||
PermissionHelper.passCheckMiddleware("read", "management", "backup"),
|
||||
backup
|
||||
);
|
||||
router.use("/setting", PermissionHelper.passCheckMiddleware("read", "management", "setting"), setting);
|
||||
|
||||
export default router;
|
||||
|
|
56
src/routes/admin/management/setting.ts
Normal file
56
src/routes/admin/management/setting.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
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,7 +10,6 @@ import {
|
|||
updateUserPermissions,
|
||||
updateUserRoles,
|
||||
} from "../../../controller/admin/management/userController";
|
||||
import { inviteUser } from "../../../controller/inviteController";
|
||||
|
||||
var router = express.Router({ mergeParams: true });
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import express from "express";
|
||||
import { login, logout, refresh } from "../controller/authController";
|
||||
import { kickof, login, logout, refresh } from "../controller/authController";
|
||||
|
||||
var router = express.Router({ mergeParams: true });
|
||||
|
||||
router.post("/kickof", async (req, res) => {
|
||||
await kickof(req, res);
|
||||
});
|
||||
|
||||
router.post("/login", async (req, res) => {
|
||||
await login(req, res);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import express from "express";
|
||||
import { isSetup } from "../controller/setupController";
|
||||
import { finishInvite, inviteUser, verifyInvite } from "../controller/inviteController";
|
||||
import { finishInvite, verifyInvite } from "../controller/inviteController";
|
||||
import ParamaterPassCheckHelper from "../helpers/parameterPassCheckHelper";
|
||||
|
||||
var router = express.Router({ mergeParams: true });
|
||||
|
@ -9,8 +8,12 @@ router.post("/verify", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mai
|
|||
await verifyInvite(req, res);
|
||||
});
|
||||
|
||||
router.put("/", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "totp"]), async (req, res) => {
|
||||
await finishInvite(req, res);
|
||||
});
|
||||
router.put(
|
||||
"/",
|
||||
ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "secret", "routine "]),
|
||||
async (req, res) => {
|
||||
await finishInvite(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
import express from "express";
|
||||
import { getCalendarItemsByTypes } from "../controller/publicController";
|
||||
import {
|
||||
getApplicationConfig,
|
||||
getApplicationFavicon,
|
||||
getApplicationIcon,
|
||||
getApplicationLogo,
|
||||
getApplicationManifest,
|
||||
getCalendarItemsByTypes,
|
||||
} from "../controller/publicController";
|
||||
|
||||
var router = express.Router({ mergeParams: true });
|
||||
|
||||
|
@ -7,4 +14,24 @@ router.get("/calendar", async (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;
|
||||
|
|
|
@ -12,8 +12,12 @@ router.post("/", ParamaterPassCheckHelper.requiredIncludedMiddleware(["username"
|
|||
await startReset(req, res);
|
||||
});
|
||||
|
||||
router.put("/", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "totp"]), async (req, res) => {
|
||||
await finishReset(req, res);
|
||||
});
|
||||
router.put(
|
||||
"/",
|
||||
ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "secret", "routine"]),
|
||||
async (req, res) => {
|
||||
await finishReset(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -5,7 +5,7 @@ import Parser from "rss-parser";
|
|||
var router = express.Router({ mergeParams: true });
|
||||
|
||||
router.get("/version", async (req: Request, res: Response) => {
|
||||
let serverPackage = FileSystemHelper.readTemplateFile("/package.json");
|
||||
let serverPackage = FileSystemHelper.readRootFile("/package.json");
|
||||
let serverJson = JSON.parse(serverPackage);
|
||||
res.send({
|
||||
name: serverJson.name,
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import express from "express";
|
||||
import { isSetup } from "../controller/setupController";
|
||||
import {
|
||||
isSetup,
|
||||
setAppIdentity,
|
||||
setClubIdentity,
|
||||
setMailConfig,
|
||||
uploadClubImages,
|
||||
} from "../controller/setupController";
|
||||
import { finishInvite, inviteUser, verifyInvite } from "../controller/inviteController";
|
||||
import ParamaterPassCheckHelper from "../helpers/parameterPassCheckHelper";
|
||||
import { clubImageUpload } from "../middleware/multer";
|
||||
|
||||
var router = express.Router({ mergeParams: true });
|
||||
|
||||
|
@ -9,20 +16,44 @@ router.get("/", async (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) => {
|
||||
await verifyInvite(req, res);
|
||||
});
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
"/me",
|
||||
ParamaterPassCheckHelper.requiredIncludedMiddleware(["username", "mail", "firstname", "lastname"]),
|
||||
async (req, res) => {
|
||||
await inviteUser(req, res, false);
|
||||
await inviteUser(req, res, true);
|
||||
}
|
||||
);
|
||||
|
||||
router.put("/", ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "totp"]), async (req, res) => {
|
||||
await finishInvite(req, res, true);
|
||||
});
|
||||
router.post(
|
||||
"/finish",
|
||||
ParamaterPassCheckHelper.requiredIncludedMiddleware(["mail", "token", "totp"]),
|
||||
async (req, res) => {
|
||||
await finishInvite(req, res, true);
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -1,5 +1,16 @@
|
|||
import express from "express";
|
||||
import { getMeById, getMyTotp, transferOwnership, updateMe, verifyMyTotp } from "../controller/userController";
|
||||
import {
|
||||
changeMyPassword,
|
||||
changeToPW,
|
||||
changeToTOTP,
|
||||
getChangeToTOTP,
|
||||
getMeById,
|
||||
getMyRoutine,
|
||||
getMyTotp,
|
||||
transferOwnership,
|
||||
updateMe,
|
||||
verifyMyTotp,
|
||||
} from "../controller/userController";
|
||||
|
||||
var router = express.Router({ mergeParams: true });
|
||||
|
||||
|
@ -7,14 +18,34 @@ router.get("/me", async (req, res) => {
|
|||
await getMeById(req, res);
|
||||
});
|
||||
|
||||
router.get("/routine", async (req, res) => {
|
||||
await getMyRoutine(req, res);
|
||||
});
|
||||
|
||||
router.get("/totp", async (req, res) => {
|
||||
await getMyTotp(req, res);
|
||||
});
|
||||
|
||||
router.get("/changeToTOTP", async (req, res) => {
|
||||
await getChangeToTOTP(req, res);
|
||||
});
|
||||
|
||||
router.post("/verify", async (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) => {
|
||||
await transferOwnership(req, res);
|
||||
});
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue