Compare commits

..

No commits in common. "main" and "v1.1.1" have entirely different histories.
main ... v1.1.1

70 changed files with 439 additions and 2236 deletions

View file

@ -18,5 +18,5 @@ MAIL_HOST = mail_hoststring
MAIL_PORT = mail_portnumber MAIL_PORT = mail_portnumber
MAIL_SECURE (true|false) // true for port 465, fals for other ports MAIL_SECURE (true|false) // true for port 465, fals for other ports
CLUB_NAME = clubname #default FF Admin CLUB_NAME = clubname
CLUB_WEBSITE = https://my-club-website-url CLUB_WEBSITE = https://my-club-website-url

View file

@ -20,13 +20,13 @@ Um den Container hochzufahren, erstellen Sie eine `docker-compose.yml` Datei mit
version: "3" version: "3"
services: services:
ff-admin-server: ff-ff-admin-server:
image: docker.registry.jk-effects.cloud/ehrenamt/ff-admin/server:latest image: docker.registry.jk-effects.cloud/ehrenamt/ff-admin/server:latest
container_name: ff_member_administration_server container_name: ff_member_administration_server
restart: unless-stopped restart: unless-stopped
environment: environment:
- DB_TYPE=<mysql|sqlite> # default ist auf mysql gesetzt - DB_TYPE=<mysql|sqlite> # default ist auf mysql gesetzt
- DB_HOST=ff-db - DB_HOST=ffm-db
- DB_PORT=<number> # default ist auf 3306 gesetzt - DB_PORT=<number> # default ist auf 3306 gesetzt
- DB_NAME=ffadmin - DB_NAME=ffadmin
- DB_USERNAME=administration_backend - DB_USERNAME=administration_backend
@ -40,7 +40,7 @@ services:
- MAIL_HOST=<url> - MAIL_HOST=<url>
- MAIL_PORT=<port> # default ist auf 578 gesetzt - MAIL_PORT=<port> # default ist auf 578 gesetzt
- MAIL_SECURE=<boolean> # default ist auf false gesetzt - MAIL_SECURE=<boolean> # default ist auf false gesetzt
- CLUB_NAME=<tobemodified> # default ist auf FF Admin gesetzt - CLUB_NAME=<tobemodified>
- CLUB_WEBSITE=<tobemodified> - CLUB_WEBSITE=<tobemodified>
volumes: volumes:
- <volume|local path>:/app/files - <volume|local path>:/app/files

52
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "ff-admin-server", "name": "ff-admin-server",
"version": "1.2.2", "version": "1.1.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ff-admin-server", "name": "ff-admin-server",
"version": "1.2.2", "version": "1.1.1",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"dependencies": { "dependencies": {
"cors": "^2.8.5", "cors": "^2.8.5",
@ -24,7 +24,6 @@
"puppeteer": "^23.11.1", "puppeteer": "^23.11.1",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rss-parser": "^3.13.0",
"socket.io": "^4.7.5", "socket.io": "^4.7.5",
"speakeasy": "^2.0.0", "speakeasy": "^2.0.0",
"typeorm": "^0.3.20", "typeorm": "^0.3.20",
@ -1497,15 +1496,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}, },
"node_modules/entities": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
"license": "BSD-2-Clause",
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/env-paths": { "node_modules/env-paths": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
@ -3162,16 +3152,6 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/rss-parser": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/rss-parser/-/rss-parser-3.13.0.tgz",
"integrity": "sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==",
"license": "MIT",
"dependencies": {
"entities": "^2.0.3",
"xml2js": "^0.5.0"
}
},
"node_modules/runes2": { "node_modules/runes2": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/runes2/-/runes2-1.1.4.tgz", "resolved": "https://registry.npmjs.org/runes2/-/runes2-1.1.4.tgz",
@ -3202,12 +3182,6 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"license": "ISC"
},
"node_modules/semver": { "node_modules/semver": {
"version": "7.6.3", "version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
@ -4279,28 +4253,6 @@
} }
} }
}, },
"node_modules/xml2js": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
"license": "MIT",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"license": "MIT",
"engines": {
"node": ">=4.0"
}
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",

View file

@ -1,6 +1,6 @@
{ {
"name": "ff-admin-server", "name": "ff-admin-server",
"version": "1.2.2", "version": "1.1.1",
"description": "Feuerwehr/Verein Mitgliederverwaltung Server", "description": "Feuerwehr/Verein Mitgliederverwaltung Server",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
@ -39,7 +39,6 @@
"puppeteer": "^23.11.1", "puppeteer": "^23.11.1",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rss-parser": "^3.13.0",
"socket.io": "^4.7.5", "socket.io": "^4.7.5",
"speakeasy": "^2.0.0", "speakeasy": "^2.0.0",
"typeorm": "^0.3.20", "typeorm": "^0.3.20",

View file

@ -1,7 +1,6 @@
export interface CreateCommunicationCommand { export interface CreateCommunicationCommand {
preferred: boolean; preferred: boolean;
isSMSAlarming: boolean; isSMSAlarming: boolean;
isSendNewsletter: boolean;
mobile: string; mobile: string;
email: string; email: string;
postalCode: string; postalCode: string;
@ -17,7 +16,6 @@ export interface UpdateCommunicationCommand {
id: number; id: number;
preferred: boolean; preferred: boolean;
isSMSAlarming: boolean; isSMSAlarming: boolean;
isSendNewsletter: boolean;
mobile: string; mobile: string;
email: string; email: string;
postalCode: string; postalCode: string;

View file

@ -14,44 +14,26 @@ export default abstract class CommunicationCommandHandler {
* @returns {Promise<number>} * @returns {Promise<number>}
*/ */
static async create(createCommunication: CreateCommunicationCommand): Promise<number> { static async create(createCommunication: CreateCommunicationCommand): Promise<number> {
let insertId = -1;
return await dataSource return await dataSource
.transaction(async (manager) => { .createQueryBuilder()
await manager .insert()
.createQueryBuilder() .into(communication)
.insert() .values({
.into(communication) preferred: createCommunication.preferred,
.values({ isSMSAlarming: createCommunication.isSMSAlarming,
preferred: createCommunication.preferred, mobile: createCommunication.mobile,
isSMSAlarming: createCommunication.isSMSAlarming, email: createCommunication.email,
isSendNewsletter: createCommunication.isSendNewsletter, postalCode: createCommunication.postalCode,
mobile: createCommunication.mobile, city: createCommunication.city,
email: createCommunication.email, street: createCommunication.street,
postalCode: createCommunication.postalCode, streetNumber: createCommunication.streetNumber,
city: createCommunication.city, streetNumberAddition: createCommunication.streetNumberAddition,
street: createCommunication.street, memberId: createCommunication.memberId,
streetNumber: createCommunication.streetNumber, typeId: createCommunication.typeId,
streetNumberAddition: createCommunication.streetNumberAddition,
memberId: createCommunication.memberId,
typeId: createCommunication.typeId,
})
.execute()
.then((result) => {
insertId = result.identifiers[0].id;
});
await manager
.createQueryBuilder()
.update(communication)
.set({
isSendNewsletter: false,
})
.where("memberId = :memberId", { memberId: createCommunication.memberId })
.andWhere("id <> :id", { id: insertId })
.execute();
}) })
.then(() => { .execute()
return insertId; .then((result) => {
return result.identifiers[0].id;
}) })
.catch((err) => { .catch((err) => {
throw new InternalException("Failed creating communication", err); throw new InternalException("Failed creating communication", err);
@ -65,36 +47,22 @@ export default abstract class CommunicationCommandHandler {
*/ */
static async update(updateCommunication: UpdateCommunicationCommand): Promise<void> { static async update(updateCommunication: UpdateCommunicationCommand): Promise<void> {
return await dataSource return await dataSource
.transaction(async (manager) => { .createQueryBuilder()
await manager .update(communication)
.createQueryBuilder() .set({
.update(communication) preferred: updateCommunication.preferred,
.set({ isSMSAlarming: updateCommunication.isSMSAlarming,
preferred: updateCommunication.preferred, mobile: updateCommunication.mobile,
isSMSAlarming: updateCommunication.isSMSAlarming, email: updateCommunication.email,
isSendNewsletter: updateCommunication.isSendNewsletter, postalCode: updateCommunication.postalCode,
mobile: updateCommunication.mobile, city: updateCommunication.city,
email: updateCommunication.email, street: updateCommunication.street,
postalCode: updateCommunication.postalCode, streetNumber: updateCommunication.streetNumber,
city: updateCommunication.city, streetNumberAddition: updateCommunication.streetNumberAddition,
street: updateCommunication.street,
streetNumber: updateCommunication.streetNumber,
streetNumberAddition: updateCommunication.streetNumberAddition,
})
.where("id = :id", { id: updateCommunication.id })
.andWhere("memberId = :memberId", { memberId: updateCommunication.memberId })
.execute();
await manager
.createQueryBuilder()
.update(communication)
.set({
isSendNewsletter: false,
})
.where("memberId = :memberId", { memberId: updateCommunication.memberId })
.andWhere("id <> :id", { id: updateCommunication.id })
.execute();
}) })
.where("id = :id", { id: updateCommunication.id })
.andWhere("memberId = :memberId", { memberId: updateCommunication.memberId })
.execute()
.then(() => {}) .then(() => {})
.catch((err) => { .catch((err) => {
throw new InternalException("Failed updating communication", err); throw new InternalException("Failed updating communication", err);

View file

@ -1,5 +1,7 @@
import { Salutation } from "../../../enums/salutation";
export interface CreateMemberCommand { export interface CreateMemberCommand {
salutationId: number; salutation: Salutation;
firstname: string; firstname: string;
lastname: string; lastname: string;
nameaffix: string; nameaffix: string;
@ -9,7 +11,7 @@ export interface CreateMemberCommand {
export interface UpdateMemberCommand { export interface UpdateMemberCommand {
id: number; id: number;
salutationId: number; salutation: Salutation;
firstname: string; firstname: string;
lastname: string; lastname: string;
nameaffix: string; nameaffix: string;
@ -17,6 +19,11 @@ export interface UpdateMemberCommand {
internalId?: string; internalId?: string;
} }
export interface UpdateMemberNewsletterCommand {
id: number;
communicationId: number;
}
export interface DeleteMemberCommand { export interface DeleteMemberCommand {
id: number; id: number;
} }

View file

@ -2,7 +2,12 @@ import { dataSource } from "../../../data-source";
import { communication } from "../../../entity/club/member/communication"; import { communication } from "../../../entity/club/member/communication";
import { member } from "../../../entity/club/member/member"; import { member } from "../../../entity/club/member/member";
import InternalException from "../../../exceptions/internalException"; import InternalException from "../../../exceptions/internalException";
import { CreateMemberCommand, DeleteMemberCommand, UpdateMemberCommand } from "./memberCommand"; import {
CreateMemberCommand,
DeleteMemberCommand,
UpdateMemberCommand,
UpdateMemberNewsletterCommand,
} from "./memberCommand";
export default abstract class MemberCommandHandler { export default abstract class MemberCommandHandler {
/** /**
@ -16,7 +21,7 @@ export default abstract class MemberCommandHandler {
.insert() .insert()
.into(member) .into(member)
.values({ .values({
salutationId: createMember.salutationId, salutation: createMember.salutation,
firstname: createMember.firstname, firstname: createMember.firstname,
lastname: createMember.lastname, lastname: createMember.lastname,
nameaffix: createMember.nameaffix, nameaffix: createMember.nameaffix,
@ -28,10 +33,7 @@ export default abstract class MemberCommandHandler {
return result.identifiers[0].id; return result.identifiers[0].id;
}) })
.catch((err) => { .catch((err) => {
throw new InternalException( throw new InternalException("Failed creating member", err);
`Failed creating member${err.code.includes("ER_DUP_ENTRY") ? " due to duplicate entry for column" : ""}`,
err
);
}); });
} }
@ -45,7 +47,7 @@ export default abstract class MemberCommandHandler {
.createQueryBuilder() .createQueryBuilder()
.update(member) .update(member)
.set({ .set({
salutationId: updateMember.salutationId, salutation: updateMember.salutation,
firstname: updateMember.firstname, firstname: updateMember.firstname,
lastname: updateMember.lastname, lastname: updateMember.lastname,
nameaffix: updateMember.nameaffix, nameaffix: updateMember.nameaffix,
@ -56,10 +58,52 @@ export default abstract class MemberCommandHandler {
.execute() .execute()
.then(() => {}) .then(() => {})
.catch((err) => { .catch((err) => {
throw new InternalException( throw new InternalException("Failed updating member", err);
`Failed updating member${err.code.includes("ER_DUP_ENTRY") ? " due to duplicate entry for column" : ""}`, });
err }
);
/**
* @description update member newsletter
* @param {UpdateMemberCommand} updateMember
* @returns {Promise<void>}
*/
static async updateNewsletter(updateMember: UpdateMemberNewsletterCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.update(member)
.set({
sendNewsletter: await dataSource
.getRepository(communication)
.createQueryBuilder("communication")
.where("id = :id", { id: updateMember.communicationId })
.andWhere("memberId = :memberId", { memberId: updateMember.id })
.getOneOrFail(),
})
.where("id = :id", { id: updateMember.id })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException("Failed updating member", err);
});
}
/**
* @description update member newsletter to unset
* @param {number} memberId
* @returns {Promise<void>}
*/
static async unsetNewsletter(memberId: number): Promise<void> {
return await dataSource
.createQueryBuilder()
.update(member)
.set({
sendNewsletter: null,
})
.where("id = :id", { id: memberId })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException("Failed updating member", err);
}); });
} }

View file

@ -1,12 +0,0 @@
export interface CreateSalutationCommand {
salutation: string;
}
export interface UpdateSalutationCommand {
id: number;
salutation: string;
}
export interface DeleteSalutationCommand {
id: number;
}

View file

@ -1,69 +0,0 @@
import { dataSource } from "../../../data-source";
import { salutation } from "../../../entity/settings/salutation";
import InternalException from "../../../exceptions/internalException";
import { CreateSalutationCommand, DeleteSalutationCommand, UpdateSalutationCommand } from "./salutationCommand";
export default abstract class SalutationCommandHandler {
/**
* @description create salutation
* @param {CreateSalutationCommand} createSalutation
* @returns {Promise<number>}
*/
static async create(createSalutation: CreateSalutationCommand): Promise<number> {
return await dataSource
.createQueryBuilder()
.insert()
.into(salutation)
.values({
salutation: createSalutation.salutation,
})
.execute()
.then((result) => {
return result.identifiers[0].id;
})
.catch((err) => {
throw new InternalException("Failed creating salutation", err);
});
}
/**
* @description update salutation
* @param {UpdateSalutationCommand} updateSalutation
* @returns {Promise<void>}
*/
static async update(updateSalutation: UpdateSalutationCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.update(salutation)
.set({
salutation: updateSalutation.salutation,
})
.where("id = :id", { id: updateSalutation.id })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException("Failed updating salutation", err);
});
}
/**
* @description delete salutation
* @param {DeleteSalutationCommand} deleteSalutation
* @returns {Promise<void>}
*/
static async delete(deleteSalutation: DeleteSalutationCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.delete()
.from(salutation)
.where("id = :id", { id: deleteSalutation.id })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException(
`Failed deleting salutation ${err.code.includes("ER_ROW_IS_REFERENCED") ? " due to referenced data" : ""}`,
err
);
});
}
}

View file

@ -1,19 +0,0 @@
export interface CreateWebapiCommand {
title: string;
token: string;
expiry?: Date;
}
export interface UpdateWebapiCommand {
id: number;
title: string;
expiry?: Date;
}
export interface UpdateLastUsageWebapiCommand {
id: number;
}
export interface DeleteWebapiCommand {
id: number;
}

View file

@ -1,100 +0,0 @@
import { dataSource } from "../../../data-source";
import { webapi } from "../../../entity/user/webapi";
import InternalException from "../../../exceptions/internalException";
import {
CreateWebapiCommand,
DeleteWebapiCommand,
UpdateLastUsageWebapiCommand,
UpdateWebapiCommand,
} from "./webapiCommand";
export default abstract class WebapiCommandHandler {
/**
* @description create api
* @param {CreateWebapiCommand} createWebapi
* @returns {Promise<number>}
*/
static async create(createWebapi: CreateWebapiCommand): Promise<number> {
return await dataSource
.createQueryBuilder()
.insert()
.into(webapi)
.values({
token: createWebapi.token,
title: createWebapi.title,
expiry: createWebapi.expiry,
})
.execute()
.then((result) => {
return result.identifiers[0].token;
})
.catch((err) => {
throw new InternalException(
`Failed creating api${err.code.includes("ER_DUP_ENTRY") ? " due to duplicate entry for column" : ""}`,
err
);
});
}
/**
* @description update api
* @param {UpdateWebapiCommand} updateWebapi
* @returns {Promise<void>}
*/
static async update(updateWebapi: UpdateWebapiCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.update(webapi)
.set({
title: updateWebapi.title,
expiry: updateWebapi.expiry,
})
.where("id = :id", { id: updateWebapi.id })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException(
`Failed updating api${err.code.includes("ER_DUP_ENTRY") ? " due to duplicate entry for column" : ""}`,
err
);
});
}
/**
* @description update api usage
* @param {UpdateLastUsageWebapiCommand} updateWebapi
* @returns {Promise<void>}
*/
static async updateUsage(updateWebapi: UpdateLastUsageWebapiCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.update(webapi)
.set({
lastUsage: new Date(),
})
.where("id = :id", { id: updateWebapi.id })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException(`Failed updating api last usage`, err);
});
}
/**
* @description delete api
* @param {DeleteWebapiCommand} deleteWebapi
* @returns {Promise<void>}
*/
static async delete(deleteWebapi: DeleteWebapiCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.delete()
.from(webapi)
.where("id = :id", { id: deleteWebapi.id })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException("Failed deleting api", err);
});
}
}

View file

@ -1,16 +0,0 @@
import { PermissionString } from "../../../type/permissionTypes";
export interface CreateWebapiPermissionCommand {
permission: PermissionString;
webapiId: number;
}
export interface DeleteWebapiPermissionCommand {
permission: PermissionString;
webapiId: number;
}
export interface UpdateWebapiPermissionsCommand {
webapiId: number;
permissions: Array<PermissionString>;
}

View file

@ -1,119 +0,0 @@
import { DeleteResult, EntityManager, InsertResult } from "typeorm";
import { dataSource } from "../../../data-source";
import { webapiPermission } from "../../../entity/user/webapi_permission";
import InternalException from "../../../exceptions/internalException";
import WebapiService from "../../../service/user/webapiService";
import {
CreateWebapiPermissionCommand,
DeleteWebapiPermissionCommand,
UpdateWebapiPermissionsCommand,
} from "./webapiPermissionCommand";
import PermissionHelper from "../../../helpers/permissionHelper";
import WebapiPermissionService from "../../../service/user/webapiPermissionService";
import { PermissionString } from "../../../type/permissionTypes";
export default abstract class WebapiPermissionCommandHandler {
/**
* @description update api permissions
* @param {UpdateWebapiPermissionsCommand} updateWebapiPermissions
* @returns {Promise<void>}
*/
static async updatePermissions(updateWebapiPermissions: UpdateWebapiPermissionsCommand): Promise<void> {
let currentPermissions = (await WebapiPermissionService.getByApi(updateWebapiPermissions.webapiId)).map(
(r) => r.permission
);
return await dataSource.manager
.transaction(async (manager) => {
let newPermissions = PermissionHelper.getWhatToAdd(currentPermissions, updateWebapiPermissions.permissions);
let removePermissions = PermissionHelper.getWhatToRemove(
currentPermissions,
updateWebapiPermissions.permissions
);
if (newPermissions.length != 0) {
await this.updatePermissionsAdd(manager, updateWebapiPermissions.webapiId, newPermissions);
}
if (removePermissions.length != 0) {
await this.updatePermissionsRemove(manager, updateWebapiPermissions.webapiId, removePermissions);
}
})
.then(() => {})
.catch((err) => {
throw new InternalException("Failed saving api permissions", err);
});
}
private static async updatePermissionsAdd(
manager: EntityManager,
webapiId: number,
permissions: Array<PermissionString>
): Promise<InsertResult> {
return await manager
.createQueryBuilder()
.insert()
.into(webapiPermission)
.values(
permissions.map((p) => ({
permission: p,
webapiId: webapiId,
}))
)
.orIgnore()
.execute();
}
private static async updatePermissionsRemove(
manager: EntityManager,
webapiId: number,
permissions: Array<PermissionString>
): Promise<DeleteResult> {
return await manager
.createQueryBuilder()
.delete()
.from(webapiPermission)
.where("webapiId = :id", { id: webapiId })
.andWhere("permission IN (:...permission)", { permission: permissions })
.execute();
}
/**
* @description grant permission to user
* @param {CreateWebapiPermissionCommand} createPermission
* @returns {Promise<number>}
*/
static async create(createPermission: CreateWebapiPermissionCommand): Promise<number> {
return await dataSource
.createQueryBuilder()
.insert()
.into(webapiPermission)
.values({
permission: createPermission.permission,
webapiId: createPermission.webapiId,
})
.execute()
.then((result) => {
return result.identifiers[0].id;
})
.catch((err) => {
throw new InternalException("Failed saving api permission", err);
});
}
/**
* @description remove permission from api
* @param {DeleteWebapiPermissionCommand} deletePermission
* @returns {Promise<any>}
*/
static async delete(deletePermission: DeleteWebapiPermissionCommand): Promise<any> {
return await dataSource
.createQueryBuilder()
.delete()
.from(webapiPermission)
.where("webapiId = :id", { id: deletePermission.webapiId })
.andWhere("permission = :permission", { permission: deletePermission.permission })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException("failed api permission removal", err);
});
}
}

View file

@ -15,6 +15,7 @@ import {
CreateMemberCommand, CreateMemberCommand,
DeleteMemberCommand, DeleteMemberCommand,
UpdateMemberCommand, UpdateMemberCommand,
UpdateMemberNewsletterCommand,
} from "../../../command/club/member/memberCommand"; } from "../../../command/club/member/memberCommand";
import MemberCommandHandler from "../../../command/club/member/memberCommandHandler"; import MemberCommandHandler from "../../../command/club/member/memberCommandHandler";
import { import {
@ -89,19 +90,6 @@ export async function getMemberById(req: Request, res: Response): Promise<any> {
res.json(MemberFactory.mapToSingle(member)); res.json(MemberFactory.mapToSingle(member));
} }
/**
* @description get member statistics by id
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getMemberStatisticsById(req: Request, res: Response): Promise<any> {
const memberId = parseInt(req.params.id);
let member = await MemberService.getStatisticsById(memberId);
res.json(MemberFactory.mapToMemberStatistic(member));
}
/** /**
* @description get memberships by member * @description get memberships by member
* @param req {Request} Express req object * @param req {Request} Express req object
@ -115,19 +103,6 @@ export async function getMembershipsByMember(req: Request, res: Response): Promi
res.json(MembershipFactory.mapToBase(memberships)); res.json(MembershipFactory.mapToBase(memberships));
} }
/**
* @description get member statistics by id
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getMembershipStatisticsById(req: Request, res: Response): Promise<any> {
const memberId = parseInt(req.params.memberId);
let member = await MembershipService.getStatisticsById(memberId);
res.json(MembershipFactory.mapToBaseStatistics(member));
}
/** /**
* @description get membership by member and record * @description get membership by member and record
* @param req {Request} Express req object * @param req {Request} Express req object
@ -284,7 +259,7 @@ export async function createMemberPrintoutList(req: Request, res: Response): Pro
* @returns {Promise<*>} * @returns {Promise<*>}
*/ */
export async function createMember(req: Request, res: Response): Promise<any> { export async function createMember(req: Request, res: Response): Promise<any> {
const salutationId = parseInt(req.body.salutationId); const salutation = req.body.salutation;
const firstname = req.body.firstname; const firstname = req.body.firstname;
const lastname = req.body.lastname; const lastname = req.body.lastname;
const nameaffix = req.body.nameaffix; const nameaffix = req.body.nameaffix;
@ -292,7 +267,7 @@ export async function createMember(req: Request, res: Response): Promise<any> {
const internalId = req.body.internalId; const internalId = req.body.internalId;
let createMember: CreateMemberCommand = { let createMember: CreateMemberCommand = {
salutationId, salutation,
firstname, firstname,
lastname, lastname,
nameaffix, nameaffix,
@ -408,7 +383,6 @@ export async function addCommunicationToMember(req: Request, res: Response): Pro
const memberId = parseInt(req.params.memberId); const memberId = parseInt(req.params.memberId);
const preferred = req.body.preferred; const preferred = req.body.preferred;
const isSMSAlarming = req.body.isSMSAlarming; const isSMSAlarming = req.body.isSMSAlarming;
const isSendNewsletter = req.body.isNewsletterMain;
const mobile = req.body.mobile; const mobile = req.body.mobile;
const email = req.body.email; const email = req.body.email;
const postalCode = req.body.postalCode; const postalCode = req.body.postalCode;
@ -417,11 +391,11 @@ export async function addCommunicationToMember(req: Request, res: Response): Pro
const streetNumber = req.body.streetNumber; const streetNumber = req.body.streetNumber;
const streetNumberAddition = req.body.streetNumberAddition; const streetNumberAddition = req.body.streetNumberAddition;
const typeId = req.body.typeId; const typeId = req.body.typeId;
const isNewsletterMain = req.body.isNewsletterMain;
let createCommunication: CreateCommunicationCommand = { let createCommunication: CreateCommunicationCommand = {
preferred, preferred,
isSMSAlarming, isSMSAlarming,
isSendNewsletter,
mobile, mobile,
email, email,
postalCode, postalCode,
@ -434,6 +408,14 @@ export async function addCommunicationToMember(req: Request, res: Response): Pro
}; };
let id = await CommunicationCommandHandler.create(createCommunication); let id = await CommunicationCommandHandler.create(createCommunication);
if (isNewsletterMain) {
let updateNewsletter: UpdateMemberNewsletterCommand = {
id: memberId,
communicationId: id,
};
await MemberCommandHandler.updateNewsletter(updateNewsletter);
}
res.sendStatus(204); res.sendStatus(204);
} }
@ -445,7 +427,7 @@ export async function addCommunicationToMember(req: Request, res: Response): Pro
*/ */
export async function updateMemberById(req: Request, res: Response): Promise<any> { export async function updateMemberById(req: Request, res: Response): Promise<any> {
const memberId = parseInt(req.params.id); const memberId = parseInt(req.params.id);
const salutationId = parseInt(req.body.salutationId); const salutation = req.body.salutation;
const firstname = req.body.firstname; const firstname = req.body.firstname;
const lastname = req.body.lastname; const lastname = req.body.lastname;
const nameaffix = req.body.nameaffix; const nameaffix = req.body.nameaffix;
@ -454,7 +436,7 @@ export async function updateMemberById(req: Request, res: Response): Promise<any
let updateMember: UpdateMemberCommand = { let updateMember: UpdateMemberCommand = {
id: memberId, id: memberId,
salutationId, salutation,
firstname, firstname,
lastname, lastname,
nameaffix, nameaffix,
@ -587,7 +569,6 @@ export async function updateCommunicationOfMember(req: Request, res: Response):
const recordId = parseInt(req.params.recordId); const recordId = parseInt(req.params.recordId);
const preferred = req.body.preferred; const preferred = req.body.preferred;
const isSMSAlarming = req.body.isSMSAlarming; const isSMSAlarming = req.body.isSMSAlarming;
const isSendNewsletter = req.body.isNewsletterMain;
const mobile = req.body.mobile; const mobile = req.body.mobile;
const email = req.body.email; const email = req.body.email;
const postalCode = req.body.postalCode; const postalCode = req.body.postalCode;
@ -595,12 +576,12 @@ export async function updateCommunicationOfMember(req: Request, res: Response):
const street = req.body.street; const street = req.body.street;
const streetNumber = req.body.streetNumber; const streetNumber = req.body.streetNumber;
const streetNumberAddition = req.body.streetNumberAddition; const streetNumberAddition = req.body.streetNumberAddition;
const isNewsletterMain = req.body.isNewsletterMain;
let updateCommunication: UpdateCommunicationCommand = { let updateCommunication: UpdateCommunicationCommand = {
id: recordId, id: recordId,
preferred, preferred,
isSMSAlarming, isSMSAlarming,
isSendNewsletter,
mobile, mobile,
email, email,
postalCode, postalCode,
@ -612,6 +593,18 @@ export async function updateCommunicationOfMember(req: Request, res: Response):
}; };
await CommunicationCommandHandler.update(updateCommunication); await CommunicationCommandHandler.update(updateCommunication);
let currentUserNewsletterMain = await MemberService.getNewsletterById(memberId);
if (isNewsletterMain) {
let updateNewsletter: UpdateMemberNewsletterCommand = {
id: memberId,
communicationId: recordId,
};
await MemberCommandHandler.updateNewsletter(updateNewsletter);
} else if (currentUserNewsletterMain.sendNewsletter?.id == recordId) {
await MemberCommandHandler.unsetNewsletter(memberId);
}
res.sendStatus(204); res.sendStatus(204);
} }

View file

@ -21,6 +21,7 @@ import UserService from "../../../service/user/userService";
import { TemplateHelper } from "../../../helpers/templateHelper"; import { TemplateHelper } from "../../../helpers/templateHelper";
import MailHelper from "../../../helpers/mailHelper"; import MailHelper from "../../../helpers/mailHelper";
import { NewsletterEventType, NewsletterHelper } from "../../../helpers/newsletterHelper"; import { NewsletterEventType, NewsletterHelper } from "../../../helpers/newsletterHelper";
import { Salutation } from "../../../enums/salutation";
/** /**
* @description get all newsletters * @description get all newsletters
@ -141,7 +142,7 @@ export async function createNewsletterPrintoutPreviewById(req: Request, res: Res
data.recipient = { data.recipient = {
firstname: recipient.firstname, firstname: recipient.firstname,
lastname: recipient.lastname, lastname: recipient.lastname,
salutation: "Anrede", salutation: Salutation.none,
nameaffix: "", nameaffix: "",
street: "Straße", street: "Straße",
streetNumber: "Hausnummer", streetNumber: "Hausnummer",
@ -247,7 +248,7 @@ export async function createNewsletterMailPreviewById(req: Request, res: Respons
data.recipient = { data.recipient = {
firstname: recipient.firstname, firstname: recipient.firstname,
lastname: recipient.lastname, lastname: recipient.lastname,
salutation: "Anrede", salutation: Salutation.none,
nameaffix: "", nameaffix: "",
street: "Straße", street: "Straße",
streetNumber: "Hausnummer", streetNumber: "Hausnummer",

View file

@ -1,87 +0,0 @@
import { Request, Response } from "express";
import SalutationService from "../../../service/settings/salutationService";
import SalutationFactory from "../../../factory/admin/settings/salutation";
import {
CreateSalutationCommand,
DeleteSalutationCommand,
UpdateSalutationCommand,
} from "../../../command/settings/salutation/salutationCommand";
import SalutationCommandHandler from "../../../command/settings/salutation/salutationCommandHandler";
/**
* @description get all salutations
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getAllSalutations(req: Request, res: Response): Promise<any> {
let salutations = await SalutationService.getAll();
res.json(SalutationFactory.mapToBase(salutations));
}
/**
* @description get salutation by id
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getSalutationById(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
let salutation = await SalutationService.getById(id);
res.json(SalutationFactory.mapToSingle(salutation));
}
/**
* @description create new salutation
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function createSalutation(req: Request, res: Response): Promise<any> {
const salutation = req.body.salutation;
let createSalutation: CreateSalutationCommand = {
salutation: salutation,
};
await SalutationCommandHandler.create(createSalutation);
res.sendStatus(204);
}
/**
* @description update salutation
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function updateSalutation(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
const salutation = req.body.salutation;
let updateSalutation: UpdateSalutationCommand = {
id: id,
salutation: salutation,
};
await SalutationCommandHandler.update(updateSalutation);
res.sendStatus(204);
}
/**
* @description delete salutation
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function deleteSalutation(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
let deleteSalutation: DeleteSalutationCommand = {
id: id,
};
await SalutationCommandHandler.delete(deleteSalutation);
res.sendStatus(204);
}

View file

@ -10,7 +10,6 @@ import MailHelper from "../../../helpers/mailHelper";
import { CLUB_NAME } from "../../../env.defaults"; import { CLUB_NAME } from "../../../env.defaults";
import { UpdateUserPermissionsCommand } from "../../../command/user/user/userPermissionCommand"; import { UpdateUserPermissionsCommand } from "../../../command/user/user/userPermissionCommand";
import UserPermissionCommandHandler from "../../../command/user/user/userPermissionCommandHandler"; import UserPermissionCommandHandler from "../../../command/user/user/userPermissionCommandHandler";
import BadRequestException from "../../../exceptions/badRequestException";
/** /**
* @description get All users * @description get All users
@ -138,11 +137,7 @@ export async function updateUserRoles(req: Request, res: Response): Promise<any>
export async function deleteUser(req: Request, res: Response): Promise<any> { export async function deleteUser(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id); const id = parseInt(req.params.id);
let { mail, isOwner } = await UserService.getById(id); let user = await UserService.getById(id);
if (isOwner) {
throw new BadRequestException("Owner cannot be deleted");
}
let deleteUser: DeleteUserCommand = { let deleteUser: DeleteUserCommand = {
id: id, id: id,
@ -152,7 +147,7 @@ export async function deleteUser(req: Request, res: Response): Promise<any> {
try { try {
// sendmail // sendmail
await MailHelper.sendMail( await MailHelper.sendMail(
mail, user.mail,
`Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`, `Email Bestätigung für Mitglieder Admin-Portal von ${CLUB_NAME}`,
`Ihr Nutzerkonto des Adminportals wurde erfolgreich gelöscht.` `Ihr Nutzerkonto des Adminportals wurde erfolgreich gelöscht.`
); );

View file

@ -1,155 +0,0 @@
import { Request, Response } from "express";
import WebapiService from "../../../service/user/webapiService";
import ApiFactory from "../../../factory/admin/user/webapi";
import WebapiPermissionService from "../../../service/user/webapiPermissionService";
import PermissionHelper from "../../../helpers/permissionHelper";
import {
CreateWebapiCommand,
DeleteWebapiCommand,
UpdateWebapiCommand,
} from "../../../command/user/webapi/webapiCommand";
import WebapiCommandHandler from "../../../command/user/webapi/webapiCommandHandler";
import { UpdateWebapiPermissionsCommand } from "../../../command/user/webapi/webapiPermissionCommand";
import WebapiPermissionCommandHandler from "../../../command/user/webapi/webapiPermissionCommandHandler";
import { JWTHelper } from "../../../helpers/jwtHelper";
import { CLUB_NAME } from "../../../env.defaults";
import { StringHelper } from "../../../helpers/stringHelper";
/**
* @description get All apis
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getAllWebapis(req: Request, res: Response): Promise<any> {
let apis = await WebapiService.getAll();
res.json(ApiFactory.mapToBase(apis));
}
/**
* @description get api by id
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getWebapiById(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
let api = await WebapiService.getById(id);
res.json(ApiFactory.mapToSingle(api));
}
/**
* @description get api token by id
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getWebapiTokenById(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
let { token } = await WebapiService.getTokenById(id);
res.send(token);
}
/**
* @description get permissions by api
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getWebapiPermissions(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
let permissions = await WebapiPermissionService.getByApi(id);
res.json(PermissionHelper.convertToObject(permissions.map((p) => p.permission)));
}
/**
* @description create new api
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function createWebapi(req: Request, res: Response): Promise<any> {
let title = req.body.title;
let expiry = req.body.expiry || null;
let token = await JWTHelper.create(
{
iss: CLUB_NAME,
sub: "api_token_retrieve",
aud: StringHelper.random(32),
},
{ useExpiration: false }
);
let createApi: CreateWebapiCommand = {
token: token,
title: title,
expiry: expiry,
};
await WebapiCommandHandler.create(createApi);
res.sendStatus(204);
}
/**
* @description update api data
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function updateWebapi(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
let title = req.body.title;
let expiry = req.body.expiry || null;
let updateApi: UpdateWebapiCommand = {
id: id,
title: title,
expiry: expiry,
};
await WebapiCommandHandler.update(updateApi);
res.sendStatus(204);
}
/**
* @description update api assigned permission strings
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function updateWebapiPermissions(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
let permissions = req.body.permissions;
let permissionStrings = PermissionHelper.convertToStringArray(permissions);
let updateApiPermissions: UpdateWebapiPermissionsCommand = {
webapiId: id,
permissions: permissionStrings,
};
await WebapiPermissionCommandHandler.updatePermissions(updateApiPermissions);
res.sendStatus(204);
}
/**
* @description delete api by id
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function deleteWebapi(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
let deleteApi: DeleteWebapiCommand = {
id: id,
};
await WebapiCommandHandler.delete(deleteApi);
res.sendStatus(204);
}

View file

@ -1,37 +0,0 @@
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/user/userService";
import speakeasy from "speakeasy";
import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException";
import RefreshService from "../service/refreshService";
import WebapiService from "../service/user/webapiService";
import ForbiddenRequestException from "../exceptions/forbiddenRequestException";
import WebapiCommandHandler from "../command/user/webapi/webapiCommandHandler";
/**
* @description Check authentication status by token
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getWebApiAccess(req: Request, res: Response): Promise<any> {
const bearer = req.headers.authorization?.split(" ")?.[1] ?? undefined;
let { id, expiry } = await WebapiService.getByToken(bearer);
if (expiry != null && new Date() > new Date(expiry)) {
throw new ForbiddenRequestException("api token expired");
}
await WebapiCommandHandler.updateUsage({ id });
let accessToken = await JWTHelper.buildWebapiToken(bearer, expiry);
res.json({
accessToken,
});
}

View file

@ -68,13 +68,6 @@ import { Memberlist1736079005086 } from "./migrations/1736079005086-memberlist";
import { ExtendViewValues1736084198860 } from "./migrations/1736084198860-extendViewValues"; import { ExtendViewValues1736084198860 } from "./migrations/1736084198860-extendViewValues";
import { FinishInternalIdTransfer1736505324488 } from "./migrations/1736505324488-finishInternalIdTransfer"; import { FinishInternalIdTransfer1736505324488 } from "./migrations/1736505324488-finishInternalIdTransfer";
import { ProtocolPresenceExcuse1737287798828 } from "./migrations/1737287798828-protocolPresenceExcuse"; import { ProtocolPresenceExcuse1737287798828 } from "./migrations/1737287798828-protocolPresenceExcuse";
import { webapi } from "./entity/user/webapi";
import { webapiPermission } from "./entity/user/webapi_permission";
import { AddWebapiTokens1737453096674 } from "./migrations/1737453096674-addwebapiTokens";
import { salutation } from "./entity/settings/salutation";
import { SalutationAsTable1737796878058 } from "./migrations/1737796878058-salutationAsTable";
import { UpdateViews1737800468938 } from "./migrations/1737800468938-updateViews";
import { MoveSendNewsletterFlag1737816852011 } from "./migrations/1737816852011-moveSendNewsletterFlag";
const dataSource = new DataSource({ const dataSource = new DataSource({
type: DB_TYPE as any, type: DB_TYPE as any,
@ -100,7 +93,6 @@ const dataSource = new DataSource({
executivePosition, executivePosition,
membershipStatus, membershipStatus,
qualification, qualification,
salutation,
member, member,
memberAwards, memberAwards,
memberExecutivePositions, memberExecutivePositions,
@ -125,8 +117,6 @@ const dataSource = new DataSource({
memberExecutivePositionsView, memberExecutivePositionsView,
memberQualificationsView, memberQualificationsView,
membershipView, membershipView,
webapi,
webapiPermission,
], ],
migrations: [ migrations: [
Initial1724317398939, Initial1724317398939,
@ -156,10 +146,6 @@ const dataSource = new DataSource({
ExtendViewValues1736084198860, ExtendViewValues1736084198860,
FinishInternalIdTransfer1736505324488, FinishInternalIdTransfer1736505324488,
ProtocolPresenceExcuse1737287798828, ProtocolPresenceExcuse1737287798828,
AddWebapiTokens1737453096674,
SalutationAsTable1737796878058,
UpdateViews1737800468938,
MoveSendNewsletterFlag1737816852011,
], ],
migrationsRun: true, migrationsRun: true,
migrationsTransactionMode: "each", migrationsTransactionMode: "each",

View file

@ -1,5 +1,6 @@
import { calendar } from "../entity/club/calendar"; import { calendar } from "../entity/club/calendar";
import { member } from "../entity/club/member/member"; import { member } from "../entity/club/member/member";
import { Salutation } from "../enums/salutation";
export const newsletterDemoData: { export const newsletterDemoData: {
title: string; title: string;
@ -17,9 +18,7 @@ export const newsletterDemoData: {
} }
> >
>; >;
recipient: Partial< recipient: Partial<member & { street: string; streetNumber: string; streetNumberAdd: string }>;
Omit<member, "salutation"> & { salutation: string; street: string; streetNumber: string; streetNumberAdd: string }
>;
} = { } = {
title: "Beispiel Newsletter Daten", title: "Beispiel Newsletter Daten",
description: "Zusammenfassung der Demodaten.", description: "Zusammenfassung der Demodaten.",
@ -64,7 +63,7 @@ export const newsletterDemoData: {
recipient: { recipient: {
firstname: "Julian", firstname: "Julian",
lastname: "Krauser", lastname: "Krauser",
salutation: "Herr", salutation: Salutation.sir,
nameaffix: "", nameaffix: "",
street: "Straße", street: "Straße",
streetNumber: "Hausnummer", streetNumber: "Hausnummer",

View file

@ -13,9 +13,6 @@ export class communication {
@Column({ type: "boolean", default: false }) @Column({ type: "boolean", default: false })
isSMSAlarming: boolean; isSMSAlarming: boolean;
@Column({ type: "boolean", default: false })
isSendNewsletter: boolean;
@Column({ type: "varchar", length: 255, nullable: true }) @Column({ type: "varchar", length: 255, nullable: true })
mobile: string; mobile: string;

View file

@ -1,16 +1,32 @@
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, PrimaryColumn } from "typeorm"; import { Column, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, PrimaryColumn } from "typeorm";
import { Salutation } from "../../../enums/salutation";
import { membership } from "./membership"; import { membership } from "./membership";
import { memberAwards } from "./memberAwards"; import { memberAwards } from "./memberAwards";
import { memberQualifications } from "./memberQualifications"; import { memberQualifications } from "./memberQualifications";
import { memberExecutivePositions } from "./memberExecutivePositions"; import { memberExecutivePositions } from "./memberExecutivePositions";
import { communication } from "./communication"; import { communication } from "./communication";
import { salutation } from "../../settings/salutation"; import { CommunicationViewModel } from "../../../viewmodel/admin/club/member/communication.models";
@Entity() @Entity()
export class member { export class member {
@PrimaryColumn({ generated: "increment", type: "int" }) @PrimaryColumn({ generated: "increment", type: "int" })
id: number; id: number;
@Column({
type: "varchar",
length: "255",
default: Salutation.none.toString(),
transformer: {
to(value: Salutation) {
return value.toString();
},
from(value: string) {
return Salutation[value as keyof typeof Salutation];
},
},
})
salutation: Salutation;
@Column({ type: "varchar", length: 255 }) @Column({ type: "varchar", length: 255 })
firstname: string; firstname: string;
@ -26,14 +42,16 @@ export class member {
@Column({ type: "varchar", length: 255, unique: true, nullable: true }) @Column({ type: "varchar", length: 255, unique: true, nullable: true })
internalId?: string; internalId?: string;
@Column()
salutationId: number;
@OneToMany(() => communication, (communications) => communications.member) @OneToMany(() => communication, (communications) => communications.member)
communications: communication[]; communications: communication[];
@ManyToOne(() => salutation, (salutation) => salutation.members) @OneToOne(() => communication, {
salutation: salutation; nullable: true,
onDelete: "SET NULL",
onUpdate: "RESTRICT",
})
@JoinColumn()
sendNewsletter?: communication;
@OneToMany(() => membership, (membership) => membership.member) @OneToMany(() => membership, (membership) => membership.member)
memberships: membership[]; memberships: membership[];
@ -51,5 +69,4 @@ export class member {
lastMembershipEntry?: membership; lastMembershipEntry?: membership;
preferredCommunication?: Array<communication>; preferredCommunication?: Array<communication>;
smsAlarming?: Array<communication>; smsAlarming?: Array<communication>;
sendNewsletter?: communication;
} }

View file

@ -1,15 +0,0 @@
import { Column, Entity, OneToMany, PrimaryColumn } from "typeorm";
import { memberAwards } from "../club/member/memberAwards";
import { member } from "../club/member/member";
@Entity()
export class salutation {
@PrimaryColumn({ generated: "increment", type: "int" })
id: number;
@Column({ type: "varchar", length: 255, unique: true })
salutation: string;
@OneToMany(() => member, (member) => member.salutation)
members: member[];
}

View file

@ -1,26 +0,0 @@
import { Column, CreateDateColumn, Entity, OneToMany, PrimaryColumn } from "typeorm";
import { webapiPermission } from "./webapi_permission";
@Entity()
export class webapi {
@PrimaryColumn({ generated: "increment", type: "int" })
id: number;
@Column({ type: "text", unique: true, select: false })
token: string;
@Column({ type: "varchar", length: 255, unique: true })
title: string;
@CreateDateColumn()
createdAt: Date;
@Column({ type: "datetime", nullable: true })
lastUsage?: Date;
@Column({ type: "date", nullable: true })
expiry?: Date;
@OneToMany(() => webapiPermission, (apiPermission) => apiPermission.webapi)
permissions: webapiPermission[];
}

View file

@ -1,19 +0,0 @@
import { Column, Entity, ManyToOne, OneToMany, PrimaryColumn } from "typeorm";
import { PermissionObject, PermissionString } from "../../type/permissionTypes";
import { webapi } from "./webapi";
@Entity()
export class webapiPermission {
@PrimaryColumn({ type: "int" })
webapiId: number;
@PrimaryColumn({ type: "varchar", length: 255 })
permission: PermissionString;
@ManyToOne(() => webapi, {
nullable: false,
onDelete: "CASCADE",
onUpdate: "RESTRICT",
})
webapi: webapi;
}

6
src/enums/salutation.ts Normal file
View file

@ -0,0 +1,6 @@
export enum Salutation {
sir = "sir",
madam = "madam",
other = "other",
none = "none",
}

View file

@ -21,7 +21,7 @@ export const MAIL_HOST = process.env.MAIL_HOST ?? "";
export const MAIL_PORT = Number(process.env.MAIL_PORT ?? "587"); export const MAIL_PORT = Number(process.env.MAIL_PORT ?? "587");
export const MAIL_SECURE = process.env.MAIL_SECURE ?? "false"; export const MAIL_SECURE = process.env.MAIL_SECURE ?? "false";
export const CLUB_NAME = process.env.CLUB_NAME ?? "FF Admin"; export const CLUB_NAME = process.env.CLUB_NAME ?? "";
export const CLUB_WEBSITE = process.env.CLUB_WEBSITE ?? ""; export const CLUB_WEBSITE = process.env.CLUB_WEBSITE ?? "";
export function configCheck() { export function configCheck() {

View file

@ -20,7 +20,7 @@ export default abstract class CommunicationFactory {
streetNumber: record.streetNumber, streetNumber: record.streetNumber,
streetNumberAddition: record.streetNumberAddition, streetNumberAddition: record.streetNumberAddition,
type: CommunicationTypeFactory.mapToSingle(record.type), type: CommunicationTypeFactory.mapToSingle(record.type),
isNewsletterMain: record?.isSendNewsletter, isNewsletterMain: isMain ? isMain : record?.member?.sendNewsletter?.id == record.id,
isSMSAlarming: record.isSMSAlarming, isSMSAlarming: record.isSMSAlarming,
}; };
} }

View file

@ -1,7 +1,5 @@
import { member } from "../../../../entity/club/member/member"; import { member } from "../../../../entity/club/member/member";
import { MemberStatisticsViewModel, MemberViewModel } from "../../../../viewmodel/admin/club/member/member.models"; import { MemberViewModel } from "../../../../viewmodel/admin/club/member/member.models";
import { memberView } from "../../../../views/memberView";
import SalutationFactory from "../../settings/salutation";
import CommunicationFactory from "./communication"; import CommunicationFactory from "./communication";
import MembershipFactory from "./membership"; import MembershipFactory from "./membership";
@ -14,7 +12,7 @@ export default abstract class MemberFactory {
public static mapToSingle(record: member): MemberViewModel { public static mapToSingle(record: member): MemberViewModel {
return { return {
id: record?.id, id: record?.id,
salutation: SalutationFactory.mapToSingle(record?.salutation), salutation: record?.salutation,
firstname: record?.firstname, firstname: record?.firstname,
lastname: record?.lastname, lastname: record?.lastname,
nameaffix: record?.nameaffix, nameaffix: record?.nameaffix,
@ -42,23 +40,4 @@ export default abstract class MemberFactory {
public static mapToBase(records: Array<member>): Array<MemberViewModel> { public static mapToBase(records: Array<member>): Array<MemberViewModel> {
return records.map((r) => this.mapToSingle(r)); return records.map((r) => this.mapToSingle(r));
} }
/**
* @description map view record to MemberMembershipStatisticsViewModel
* @param {memberView} record
* @returns {MemberStatisticsViewModel}
*/
public static mapToMemberStatistic(record: memberView): MemberStatisticsViewModel {
return {
id: record.id,
salutation: record.salutation,
firstname: record.firstname,
lastname: record.lastname,
nameaffix: record.nameaffix,
birthdate: record.birthdate,
todayAge: record.todayAge,
ageThisYear: record.ageThisYear,
exactAge: record.exactAge,
};
}
} }

View file

@ -1,9 +1,5 @@
import { membership } from "../../../../entity/club/member/membership"; import { membership } from "../../../../entity/club/member/membership";
import { import { MembershipViewModel } from "../../../../viewmodel/admin/club/member/membership.models";
MembershipStatisticsViewModel,
MembershipViewModel,
} from "../../../../viewmodel/admin/club/member/membership.models";
import { membershipView } from "../../../../views/membershipsView";
export default abstract class MembershipFactory { export default abstract class MembershipFactory {
/** /**
@ -30,33 +26,4 @@ export default abstract class MembershipFactory {
public static mapToBase(records: Array<membership>): Array<MembershipViewModel> { public static mapToBase(records: Array<membership>): Array<MembershipViewModel> {
return records.map((r) => this.mapToSingle(r)); return records.map((r) => this.mapToSingle(r));
} }
/**
* @description map view record to MembershipStatisticsViewModel
* @param {membershipView} record
* @returns {MembershipStatisticsViewModel}
*/
public static mapToSingleStatistic(record: membershipView): MembershipStatisticsViewModel {
return {
durationInDays: record.durationInDays,
durationInYears: record.durationInYears,
status: record.status,
statusId: record.statusId,
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
* @returns {Array<MembershipStatisticsViewModel>}
*/
public static mapToBaseStatistics(records: Array<membershipView>): Array<MembershipStatisticsViewModel> {
return records.map((r) => this.mapToSingleStatistic(r));
}
} }

View file

@ -1,25 +0,0 @@
import { salutation } from "../../../entity/settings/salutation";
import { SalutationViewModel } from "../../../viewmodel/admin/settings/salutation.models";
export default abstract class SalutationFactory {
/**
* @description map record to salutation
* @param {salutation} record
* @returns {SalutationViewModel}
*/
public static mapToSingle(record: salutation): SalutationViewModel {
return {
id: record.id,
salutation: record.salutation,
};
}
/**
* @description map records to salutation
* @param {Array<salutation>} records
* @returns {Array<SalutationViewModel>}
*/
public static mapToBase(records: Array<salutation>): Array<SalutationViewModel> {
return records.map((r) => this.mapToSingle(r));
}
}

View file

@ -1,30 +0,0 @@
import { webapi } from "../../../entity/user/webapi";
import PermissionHelper from "../../../helpers/permissionHelper";
import { ApiViewModel } from "../../../viewmodel/admin/user/webapi.models";
export default abstract class ApiFactory {
/**
* @description map record to api
* @param {webapi} record
* @returns {apiViewModel}
*/
public static mapToSingle(record: webapi): ApiViewModel {
return {
id: record.id,
permissions: PermissionHelper.convertToObject(record.permissions.map((e) => e.permission)),
title: record.title,
expiry: record.expiry,
lastUsage: record.lastUsage,
createdAt: record.createdAt,
};
}
/**
* @description map records to api
* @param {Array<webapi>} records
* @returns {Array<apiViewModel>}
*/
public static mapToBase(records: Array<webapi>): Array<ApiViewModel> {
return records.map((r) => this.mapToSingle(r));
}
}

View file

@ -11,7 +11,6 @@ export default abstract class DynamicQueryBuilder {
"executivePosition", "executivePosition",
"membershipStatus", "membershipStatus",
"qualification", "qualification",
"salutation",
"member", "member",
"memberAwards", "memberAwards",
"memberExecutivePositions", "memberExecutivePositions",

View file

@ -6,9 +6,6 @@ import RolePermissionService from "../service/user/rolePermissionService";
import UserPermissionService from "../service/user/userPermissionService"; import UserPermissionService from "../service/user/userPermissionService";
import UserService from "../service/user/userService"; import UserService from "../service/user/userService";
import PermissionHelper from "./permissionHelper"; import PermissionHelper from "./permissionHelper";
import WebapiService from "../service/user/webapiService";
import WebapiPermissionService from "../service/user/webapiPermissionService";
import ms from "ms";
export abstract class JWTHelper { export abstract class JWTHelper {
static validate(token: string): Promise<string | jwt.JwtPayload> { static validate(token: string): Promise<string | jwt.JwtPayload> {
@ -20,16 +17,13 @@ export abstract class JWTHelper {
}); });
} }
static create( static create(data: JWTData): Promise<string> {
data: JWTData,
{ expOverwrite, useExpiration }: { expOverwrite?: number; useExpiration?: boolean } = { useExpiration: true }
): Promise<string> {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
jwt.sign( jwt.sign(
data, data,
JWT_SECRET, JWT_SECRET,
{ {
...(useExpiration ?? true ? { expiresIn: expOverwrite ?? JWT_EXPIRATION } : {}), expiresIn: JWT_EXPIRATION,
}, },
(err, token) => { (err, token) => {
if (err) reject(err.message); if (err) reject(err.message);
@ -78,35 +72,4 @@ export abstract class JWTHelper {
throw new InternalException("Failed accessToken creation", err); throw new InternalException("Failed accessToken creation", err);
}); });
} }
static async buildWebapiToken(token: string, expiration?: Date): Promise<string> {
let { id, title } = await WebapiService.getByToken(token);
let webapiPermissions = await WebapiPermissionService.getByApi(id);
let webapiPermissionStrings = webapiPermissions.map((e) => e.permission);
let permissionObject = PermissionHelper.convertToObject(webapiPermissionStrings);
let jwtData: JWTToken = {
userId: id,
mail: "",
username: title,
firstname: "",
lastname: "",
isOwner: false,
permissions: permissionObject,
sub: "webapi_access_token",
};
let overwriteExpiration =
ms(JWT_EXPIRATION) < new Date().getTime() - new Date(expiration).getTime()
? null
: Date.now() - new Date(expiration).getTime();
return await JWTHelper.create(jwtData, { expOverwrite: overwriteExpiration, useExpiration: true })
.then((result) => {
return result;
})
.catch((err) => {
throw new InternalException("Failed webapi accessToken creation", err);
});
}
} }

View file

@ -100,7 +100,7 @@ export abstract class NewsletterHelper {
recipient: { recipient: {
firstname: recipient.firstname, firstname: recipient.firstname,
lastname: recipient.lastname, lastname: recipient.lastname,
salutation: recipient.salutation.salutation, salutation: recipient.salutation,
nameaffix: recipient.nameaffix, nameaffix: recipient.nameaffix,
...(showAdress ...(showAdress
? { ? {

View file

@ -89,19 +89,6 @@ export default class PermissionHelper {
}; };
} }
static isAdminMiddleware(): (req: Request, res: Response, next: Function) => void {
return (req: Request, res: Response, next: Function) => {
const permissions = req.permissions;
const isOwner = req.isOwner;
if (isOwner || permissions.admin) {
next();
} else {
throw new ForbiddenRequestException(`missing admin permission`);
}
};
}
static convertToObject(permissions: Array<PermissionString>): PermissionObject { static convertToObject(permissions: Array<PermissionString>): PermissionObject {
if (permissions.includes("*")) { if (permissions.includes("*")) {
return { return {

View file

@ -13,7 +13,6 @@ declare global {
isOwner: boolean; isOwner: boolean;
permissions: PermissionObject; permissions: PermissionObject;
isPWA: boolean; isPWA: boolean;
isWebApiRequest: boolean;
} }
} }
} }

View file

@ -29,15 +29,10 @@ export default async function authenticate(req: Request, res: Response, next: Fu
throw new InternalException("process failed"); throw new InternalException("process failed");
} }
if (decoded?.sub == "api_token_retrieve") {
throw new BadRequestException("This token is only authorized to get temporary access tokens via GET /api/webapi");
}
req.userId = decoded.userId; req.userId = decoded.userId;
req.username = decoded.username; req.username = decoded.username;
req.isOwner = decoded.isOwner; req.isOwner = decoded.isOwner;
req.permissions = decoded.permissions; req.permissions = decoded.permissions;
req.isWebApiRequest = decoded?.sub == "webapi_access_token";
next(); next();
} }

View file

@ -1,37 +0,0 @@
import { Request, Response } from "express";
import jwt from "jsonwebtoken";
import BadRequestException from "../exceptions/badRequestException";
import UnauthorizedRequestException from "../exceptions/unauthorizedRequestException";
import InternalException from "../exceptions/internalException";
import { JWTHelper } from "../helpers/jwtHelper";
export default async function authenticateAPI(req: Request, res: Response, next: Function) {
const bearer = req.headers.authorization?.split(" ")?.[1] ?? undefined;
if (!bearer) {
throw new BadRequestException("Provide valid Authorization Header");
}
let decoded: string | jwt.JwtPayload;
await JWTHelper.validate(bearer)
.then((result) => {
decoded = result;
})
.catch((err) => {
if (err == "jwt expired") {
throw new UnauthorizedRequestException("Token expired", err);
} else {
throw new BadRequestException("Failed Authorization Header decoding", err);
}
});
if (typeof decoded == "string" || !decoded) {
throw new InternalException("process failed");
}
if (decoded?.sub != "api_token_retrieve") {
throw new BadRequestException("This route can only be accessed via a api token");
}
next();
}

View file

@ -1,10 +0,0 @@
import { Request, Response } from "express";
import ForbiddenRequestException from "../exceptions/forbiddenRequestException";
export default async function preventWebapiAccess(req: Request, res: Response, next: Function) {
if (req.isWebApiRequest) {
throw new ForbiddenRequestException("This route cannot be accessed via webapi");
} else {
next();
}
}

View file

@ -8,105 +8,105 @@ export class MemberDataViews1734520998539 implements MigrationInterface {
name = "MemberDataViews1734520998539"; name = "MemberDataViews1734520998539";
public async up(queryRunner: QueryRunner): Promise<void> { public async up(queryRunner: QueryRunner): Promise<void> {
// await queryRunner.createView( await queryRunner.createView(
// new View({ new View({
// name: "member_view", name: "member_view",
// expression: (datasource: DataSource) => expression: (datasource: DataSource) =>
// datasource datasource
// .getRepository(member) .getRepository(member)
// .createQueryBuilder("member") .createQueryBuilder("member")
// .select("member.id", "id") .select("member.id", "id")
// .addSelect("member.salutation", "salutation") .addSelect("member.salutation", "salutation")
// .addSelect("member.firstname", "firstname") .addSelect("member.firstname", "firstname")
// .addSelect("member.lastname", "lastname") .addSelect("member.lastname", "lastname")
// .addSelect("member.nameaffix", "nameaffix") .addSelect("member.nameaffix", "nameaffix")
// .addSelect("member.birthdate", "birthdate") .addSelect("member.birthdate", "birthdate")
// .addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge") .addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge")
// .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear"), .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear"),
// }), }),
// true true
// ); );
// await queryRunner.createView( await queryRunner.createView(
// new View({ new View({
// name: "member_executive_positions_view", name: "member_executive_positions_view",
// expression: (datasource: DataSource) => expression: (datasource: DataSource) =>
// datasource datasource
// .getRepository(memberExecutivePositions) .getRepository(memberExecutivePositions)
// .createQueryBuilder("memberExecutivePositions") .createQueryBuilder("memberExecutivePositions")
// .select("executivePosition.id", "positionId") .select("executivePosition.id", "positionId")
// .addSelect("executivePosition.position", "position") .addSelect("executivePosition.position", "position")
// .addSelect("member.id", "memberId") .addSelect("member.id", "memberId")
// .addSelect("member.salutation", "memberSalutation") .addSelect("member.salutation", "memberSalutation")
// .addSelect("member.firstname", "memberFirstname") .addSelect("member.firstname", "memberFirstname")
// .addSelect("member.lastname", "memberLastname") .addSelect("member.lastname", "memberLastname")
// .addSelect("member.nameaffix", "memberNameaffix") .addSelect("member.nameaffix", "memberNameaffix")
// .addSelect("member.birthdate", "memberBirthdate") .addSelect("member.birthdate", "memberBirthdate")
// .addSelect( .addSelect(
// "SUM(TIMESTAMPDIFF(DAY, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURRENT_DATE)))", "SUM(TIMESTAMPDIFF(DAY, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURRENT_DATE)))",
// "durationInDays" "durationInDays"
// ) )
// .leftJoin("memberExecutivePositions.executivePosition", "executivePosition") .leftJoin("memberExecutivePositions.executivePosition", "executivePosition")
// .leftJoin("memberExecutivePositions.member", "member") .leftJoin("memberExecutivePositions.member", "member")
// .groupBy("executivePosition.id"), .groupBy("executivePosition.id"),
// }), }),
// true true
// ); );
// await queryRunner.createView( await queryRunner.createView(
// new View({ new View({
// name: "member_qualifications_view", name: "member_qualifications_view",
// expression: (datasource: DataSource) => expression: (datasource: DataSource) =>
// datasource datasource
// .getRepository(memberQualifications) .getRepository(memberQualifications)
// .createQueryBuilder("memberQualifications") .createQueryBuilder("memberQualifications")
// .select("qualification.id", "qualificationId") .select("qualification.id", "qualificationId")
// .addSelect("qualification.qualification", "qualification") .addSelect("qualification.qualification", "qualification")
// .addSelect("member.id", "memberId") .addSelect("member.id", "memberId")
// .addSelect("member.salutation", "memberSalutation") .addSelect("member.salutation", "memberSalutation")
// .addSelect("member.firstname", "memberFirstname") .addSelect("member.firstname", "memberFirstname")
// .addSelect("member.lastname", "memberLastname") .addSelect("member.lastname", "memberLastname")
// .addSelect("member.nameaffix", "memberNameaffix") .addSelect("member.nameaffix", "memberNameaffix")
// .addSelect("member.birthdate", "memberBirthdate") .addSelect("member.birthdate", "memberBirthdate")
// .addSelect( .addSelect(
// "SUM(TIMESTAMPDIFF(DAY, memberQualifications.start, COALESCE(memberQualifications.end, CURRENT_DATE)))", "SUM(TIMESTAMPDIFF(DAY, memberQualifications.start, COALESCE(memberQualifications.end, CURRENT_DATE)))",
// "durationInDays" "durationInDays"
// ) )
// .leftJoin("memberQualifications.qualification", "qualification") .leftJoin("memberQualifications.qualification", "qualification")
// .leftJoin("memberQualifications.member", "member") .leftJoin("memberQualifications.member", "member")
// .groupBy("qualification.id"), .groupBy("qualification.id"),
// }), }),
// true true
// ); );
// await queryRunner.createView( await queryRunner.createView(
// new View({ new View({
// name: "membership_view", name: "membership_view",
// expression: (datasource: DataSource) => expression: (datasource: DataSource) =>
// datasource datasource
// .getRepository(membership) .getRepository(membership)
// .createQueryBuilder("membership") .createQueryBuilder("membership")
// .select("status.id", "statusId") .select("status.id", "statusId")
// .addSelect("status.status", "status") .addSelect("status.status", "status")
// .addSelect("member.id", "memberId") .addSelect("member.id", "memberId")
// .addSelect("member.salutation", "memberSalutation") .addSelect("member.salutation", "memberSalutation")
// .addSelect("member.firstname", "memberFirstname") .addSelect("member.firstname", "memberFirstname")
// .addSelect("member.lastname", "memberLastname") .addSelect("member.lastname", "memberLastname")
// .addSelect("member.nameaffix", "memberNameaffix") .addSelect("member.nameaffix", "memberNameaffix")
// .addSelect("member.birthdate", "memberBirthdate") .addSelect("member.birthdate", "memberBirthdate")
// .addSelect( .addSelect(
// "SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))", "SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))",
// "durationInDays" "durationInDays"
// ) )
// .leftJoin("membership.status", "status") .leftJoin("membership.status", "status")
// .leftJoin("membership.member", "member") .leftJoin("membership.member", "member")
// .groupBy("status.id"), .groupBy("status.id"),
// }), }),
// true true
// ); );
} }
public async down(queryRunner: QueryRunner): Promise<void> { public async down(queryRunner: QueryRunner): Promise<void> {
// await queryRunner.dropView("membership_view"); await queryRunner.dropView("membership_view");
// await queryRunner.dropView("member_qualifications_view"); await queryRunner.dropView("member_qualifications_view");
// await queryRunner.dropView("member_executive_positions_view"); await queryRunner.dropView("member_executive_positions_view");
// await queryRunner.dropView("member_view"); await queryRunner.dropView("member_view");
} }
} }

View file

@ -8,104 +8,106 @@ export class ExtendViewValues1736084198860 implements MigrationInterface {
name = "ExtendViewValues1736084198860"; name = "ExtendViewValues1736084198860";
public async up(queryRunner: QueryRunner): Promise<void> { public async up(queryRunner: QueryRunner): Promise<void> {
// await queryRunner.dropView("membership_view"); await queryRunner.dropView("membership_view");
// await queryRunner.dropView("member_view"); await queryRunner.dropView("member_view");
// await queryRunner.createView(
// new View({ await queryRunner.createView(
// name: "member_view", new View({
// expression: (datasource: DataSource) => name: "member_view",
// datasource expression: (datasource: DataSource) =>
// .getRepository(member) datasource
// .createQueryBuilder("member") .getRepository(member)
// .select("member.id", "id") .createQueryBuilder("member")
// .addSelect("member.salutation", "salutation") .select("member.id", "id")
// .addSelect("member.firstname", "firstname") .addSelect("member.salutation", "salutation")
// .addSelect("member.lastname", "lastname") .addSelect("member.firstname", "firstname")
// .addSelect("member.nameaffix", "nameaffix") .addSelect("member.lastname", "lastname")
// .addSelect("member.birthdate", "birthdate") .addSelect("member.nameaffix", "nameaffix")
// .addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge") .addSelect("member.birthdate", "birthdate")
// .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear") .addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge")
// .addSelect("CONCAT('_', FROM_DAYS(TIMESTAMPDIFF(DAY, member.birthdate, CURDATE())))", "exactAge"), .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear")
// }), .addSelect("CONCAT('_', FROM_DAYS(TIMESTAMPDIFF(DAY, member.birthdate, CURDATE())))", "exactAge"),
// true }),
// ); true
// await queryRunner.createView( );
// new View({ await queryRunner.createView(
// name: "membership_view", new View({
// expression: (datasource: DataSource) => name: "membership_view",
// datasource expression: (datasource: DataSource) =>
// .getRepository(membership) datasource
// .createQueryBuilder("membership") .getRepository(membership)
// .select("status.id", "statusId") .createQueryBuilder("membership")
// .addSelect("status.status", "status") .select("status.id", "statusId")
// .addSelect("member.id", "memberId") .addSelect("status.status", "status")
// .addSelect("member.salutation", "memberSalutation") .addSelect("member.id", "memberId")
// .addSelect("member.firstname", "memberFirstname") .addSelect("member.salutation", "memberSalutation")
// .addSelect("member.lastname", "memberLastname") .addSelect("member.firstname", "memberFirstname")
// .addSelect("member.nameaffix", "memberNameaffix") .addSelect("member.lastname", "memberLastname")
// .addSelect("member.birthdate", "memberBirthdate") .addSelect("member.nameaffix", "memberNameaffix")
// .addSelect( .addSelect("member.birthdate", "memberBirthdate")
// "SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))", .addSelect(
// "durationInDays" "SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))",
// ) "durationInDays"
// .addSelect( )
// "CONCAT('_', FROM_DAYS(SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))))", .addSelect(
// "durationInYears" "CONCAT('_', FROM_DAYS(SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))))",
// ) "durationInYears"
// .leftJoin("membership.status", "status") )
// .leftJoin("membership.member", "member") .leftJoin("membership.status", "status")
// .groupBy("status.id") .leftJoin("membership.member", "member")
// .addGroupBy("member.id"), .groupBy("status.id")
// }), .addGroupBy("member.id"),
// true }),
// ); true
);
} }
public async down(queryRunner: QueryRunner): Promise<void> { public async down(queryRunner: QueryRunner): Promise<void> {
// await queryRunner.dropView("membership_view"); await queryRunner.dropView("membership_view");
// await queryRunner.dropView("member_view"); await queryRunner.dropView("member_view");
// await queryRunner.createView(
// new View({ await queryRunner.createView(
// name: "member_view", new View({
// expression: (datasource: DataSource) => name: "member_view",
// datasource expression: (datasource: DataSource) =>
// .getRepository(member) datasource
// .createQueryBuilder("member") .getRepository(member)
// .select("member.id", "id") .createQueryBuilder("member")
// .addSelect("member.salutation", "salutation") .select("member.id", "id")
// .addSelect("member.firstname", "firstname") .addSelect("member.salutation", "salutation")
// .addSelect("member.lastname", "lastname") .addSelect("member.firstname", "firstname")
// .addSelect("member.nameaffix", "nameaffix") .addSelect("member.lastname", "lastname")
// .addSelect("member.birthdate", "birthdate") .addSelect("member.nameaffix", "nameaffix")
// .addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge") .addSelect("member.birthdate", "birthdate")
// .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear"), .addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge")
// }), .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear"),
// true }),
// ); true
// await queryRunner.createView( );
// new View({ await queryRunner.createView(
// name: "membership_view", new View({
// expression: (datasource: DataSource) => name: "membership_view",
// datasource expression: (datasource: DataSource) =>
// .getRepository(membership) datasource
// .createQueryBuilder("membership") .getRepository(membership)
// .select("status.id", "statusId") .createQueryBuilder("membership")
// .addSelect("status.status", "status") .select("status.id", "statusId")
// .addSelect("member.id", "memberId") .addSelect("status.status", "status")
// .addSelect("member.salutation", "memberSalutation") .addSelect("member.id", "memberId")
// .addSelect("member.firstname", "memberFirstname") .addSelect("member.salutation", "memberSalutation")
// .addSelect("member.lastname", "memberLastname") .addSelect("member.firstname", "memberFirstname")
// .addSelect("member.nameaffix", "memberNameaffix") .addSelect("member.lastname", "memberLastname")
// .addSelect("member.birthdate", "memberBirthdate") .addSelect("member.nameaffix", "memberNameaffix")
// .addSelect( .addSelect("member.birthdate", "memberBirthdate")
// "SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))", .addSelect(
// "durationInDays" "SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))",
// ) "durationInDays"
// .leftJoin("membership.status", "status") )
// .leftJoin("membership.member", "member") .leftJoin("membership.status", "status")
// .groupBy("status.id"), .leftJoin("membership.member", "member")
// }), .groupBy("status.id"),
// true }),
// ); true
);
} }
} }

View file

@ -1,55 +0,0 @@
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm";
import { DB_TYPE } from "../env.defaults";
export class AddWebapiTokens1737453096674 implements MigrationInterface {
name = "AddWebApiTokens1737453096674";
public async up(queryRunner: QueryRunner): Promise<void> {
const variableType_int = DB_TYPE == "mysql" ? "int" : "integer";
await queryRunner.createTable(
new Table({
name: "webapi",
columns: [
{ name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" },
{ name: "token", type: "text", isUnique: true, isNullable: false },
{ name: "title", type: "varchar", isUnique: true, length: "255", isNullable: false },
{ name: "createdAt", type: "datetime", default: "CURRENT_TIMESTAMP(6)", isNullable: false },
{ name: "lastUsage", type: "datetime", isNullable: true, default: null },
{ name: "expiry", type: "date", isNullable: true, default: null },
],
}),
true
);
await queryRunner.createTable(
new Table({
name: "webapi_permission",
columns: [
{ name: "webapiId", type: variableType_int, isPrimary: true, isNullable: false },
{ name: "permission", type: "varchar", length: "255", isPrimary: true, isNullable: false },
],
}),
true
);
await queryRunner.createForeignKey(
"webapi_permission",
new TableForeignKey({
columnNames: ["webapiId"],
referencedColumnNames: ["id"],
referencedTableName: "webapi",
onDelete: "CASCADE",
onUpdate: "RESTRICT",
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
const table = await queryRunner.getTable("webapi_permission");
const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("webapiId") !== -1);
await queryRunner.dropForeignKey("webapi_permission", foreignKey);
await queryRunner.dropTable("webapi_permission");
await queryRunner.dropTable("webapi");
}
}

View file

@ -1,91 +0,0 @@
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm";
import { DB_TYPE } from "../env.defaults";
import { member } from "../entity/club/member/member";
import { salutation } from "../entity/settings/salutation";
export class SalutationAsTable1737796878058 implements MigrationInterface {
name = "SalutationAsTable1737796878058";
public async up(queryRunner: QueryRunner): Promise<void> {
const variableType_int = DB_TYPE == "mysql" ? "int" : "integer";
await queryRunner.createTable(
new Table({
name: "salutation",
columns: [
{ name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" },
{ name: "salutation", type: "varchar", length: "255", isUnique: true, isNullable: false },
],
}),
true
);
// ! has to be sql. Else the column would be dropped and created - resulting in data loss.
await queryRunner.query(
`ALTER TABLE \`member\` CHANGE \`salutation\` \`salutationId\` varchar(255) NOT NULL DEFAULT ''`
);
// ! has to be sql. Else no data is returned.
const existing_salutations = await queryRunner.query(
"SELECT DISTINCT salutationId FROM `member` `member` GROUP BY salutationId"
);
for (let s of existing_salutations.map((s: any) => s.salutationId) as Array<string>) {
await queryRunner.manager.getRepository(salutation).save({ salutation: s });
}
const salutations = await queryRunner.manager.getRepository(salutation).find();
for (let salutation of salutations) {
await queryRunner.manager
.getRepository(member)
.createQueryBuilder("member")
.update({ salutationId: salutation.id })
.where({ salutationId: salutation.salutation })
.execute();
}
await queryRunner.query(
`ALTER TABLE \`member\` CHANGE \`salutationId\` \`salutationId\` ${variableType_int} NOT NULL`
);
await queryRunner.createForeignKey(
"member",
new TableForeignKey({
columnNames: ["salutationId"],
referencedColumnNames: ["id"],
referencedTableName: "salutation",
onDelete: "RESTRICT",
onUpdate: "RESTRICT",
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
const table = await queryRunner.getTable("member");
const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("salutationId") !== -1);
await queryRunner.dropForeignKey("member", foreignKey);
// ! has to be sql. Else the column would be dropped and created - resulting in data loss.
await queryRunner.query(
`ALTER TABLE \`member\` CHANGE \`salutationId\` \`salutationId\` varchar(255) NOT NULL DEFAULT ''`
);
const salutations = await queryRunner.manager.getRepository(salutation).find();
for (let salutation of salutations) {
await queryRunner.manager
.getRepository(member)
.createQueryBuilder("member")
.update({ salutationId: salutation.salutation as unknown as number })
.where({ salutationId: salutation.id })
.execute();
}
await queryRunner.query(
`ALTER TABLE \`member\` CHANGE \`salutationId\` \`salutation\` varchar(255) NOT NULL DEFAULT 'none'`
);
await queryRunner.dropTable("webapi");
}
}

View file

@ -1,233 +0,0 @@
import { DataSource, MigrationInterface, QueryRunner, View } from "typeorm";
import { member } from "../entity/club/member/member";
import { memberExecutivePositions } from "../entity/club/member/memberExecutivePositions";
import { memberQualifications } from "../entity/club/member/memberQualifications";
import { membership } from "../entity/club/member/membership";
export class UpdateViews1737800468938 implements MigrationInterface {
name = "UpdateViews1737800468938";
public async up(queryRunner: QueryRunner): Promise<void> {
// await queryRunner.dropView("membership_view");
// await queryRunner.dropView("member_executive_positions_view");
// await queryRunner.dropView("member_qualifications_view");
// await queryRunner.dropView("member_view");
await queryRunner.query(`DROP VIEW IF EXISTS membership_view`);
await queryRunner.query(`DROP VIEW IF EXISTS member_executive_positions_view`);
await queryRunner.query(`DROP VIEW IF EXISTS member_qualifications_view`);
await queryRunner.query(`DROP VIEW IF EXISTS member_view`);
await queryRunner.createView(
new View({
name: "member_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(member)
.createQueryBuilder("member")
.select("member.id", "id")
.addSelect("member.firstname", "firstname")
.addSelect("member.lastname", "lastname")
.addSelect("member.nameaffix", "nameaffix")
.addSelect("member.birthdate", "birthdate")
.addSelect("salutation.salutation", "salutation")
.addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge")
.addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear")
.addSelect("CONCAT('_', FROM_DAYS(TIMESTAMPDIFF(DAY, member.birthdate, CURDATE())))", "exactAge")
.leftJoin("member.salutation", "salutation"),
}),
true
);
await queryRunner.createView(
new View({
name: "member_executive_positions_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(memberExecutivePositions)
.createQueryBuilder("memberExecutivePositions")
.select("executivePosition.id", "positionId")
.addSelect("executivePosition.position", "position")
.addSelect("member.id", "memberId")
.addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect(
"SUM(TIMESTAMPDIFF(DAY, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURRENT_DATE)))",
"durationInDays"
)
.leftJoin("memberExecutivePositions.executivePosition", "executivePosition")
.leftJoin("memberExecutivePositions.member", "member")
.leftJoin("member.salutation", "salutation")
.groupBy("executivePosition.id")
.addGroupBy("member.id"),
}),
true
);
await queryRunner.createView(
new View({
name: "member_qualifications_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(memberQualifications)
.createQueryBuilder("memberQualifications")
.select("qualification.id", "qualificationId")
.addSelect("qualification.qualification", "qualification")
.addSelect("member.id", "memberId")
.addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect(
"SUM(TIMESTAMPDIFF(DAY, memberQualifications.start, COALESCE(memberQualifications.end, CURRENT_DATE)))",
"durationInDays"
)
.leftJoin("memberQualifications.qualification", "qualification")
.leftJoin("memberQualifications.member", "member")
.leftJoin("member.salutation", "salutation")
.groupBy("qualification.id")
.addGroupBy("member.id"),
}),
true
);
await queryRunner.createView(
new View({
name: "membership_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(membership)
.createQueryBuilder("membership")
.select("status.id", "statusId")
.addSelect("status.status", "status")
.addSelect("member.id", "memberId")
.addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect(
"SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))",
"durationInDays"
)
.addSelect(
"CONCAT('_', FROM_DAYS(SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))))",
"durationInYears"
)
.leftJoin("membership.status", "status")
.leftJoin("membership.member", "member")
.leftJoin("member.salutation", "salutation")
.groupBy("status.id")
.addGroupBy("member.id"),
}),
true
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropView("membership_view");
await queryRunner.dropView("member_executive_positions_view");
await queryRunner.dropView("member_qualifications_view");
await queryRunner.dropView("member_view");
await queryRunner.createView(
new View({
name: "member_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(member)
.createQueryBuilder("member")
.select("member.id", "id")
.addSelect("member.salutation", "salutation")
.addSelect("member.firstname", "firstname")
.addSelect("member.lastname", "lastname")
.addSelect("member.nameaffix", "nameaffix")
.addSelect("member.birthdate", "birthdate")
.addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge")
.addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear")
.addSelect("CONCAT('_', FROM_DAYS(TIMESTAMPDIFF(DAY, member.birthdate, CURDATE())))", "exactAge"),
}),
true
);
await queryRunner.createView(
new View({
name: "member_executive_positions_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(memberExecutivePositions)
.createQueryBuilder("memberExecutivePositions")
.select("executivePosition.id", "positionId")
.addSelect("executivePosition.position", "position")
.addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect(
"SUM(TIMESTAMPDIFF(DAY, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURRENT_DATE)))",
"durationInDays"
)
.leftJoin("memberExecutivePositions.executivePosition", "executivePosition")
.leftJoin("memberExecutivePositions.member", "member")
.groupBy("executivePosition.id"),
}),
true
);
await queryRunner.createView(
new View({
name: "member_qualifications_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(memberQualifications)
.createQueryBuilder("memberQualifications")
.select("qualification.id", "qualificationId")
.addSelect("qualification.qualification", "qualification")
.addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect(
"SUM(TIMESTAMPDIFF(DAY, memberQualifications.start, COALESCE(memberQualifications.end, CURRENT_DATE)))",
"durationInDays"
)
.leftJoin("memberQualifications.qualification", "qualification")
.leftJoin("memberQualifications.member", "member")
.groupBy("qualification.id"),
}),
true
);
await queryRunner.createView(
new View({
name: "membership_view",
expression: (datasource: DataSource) =>
datasource
.getRepository(membership)
.createQueryBuilder("membership")
.select("status.id", "statusId")
.addSelect("status.status", "status")
.addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate")
.addSelect(
"SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))",
"durationInDays"
)
.addSelect(
"CONCAT('_', FROM_DAYS(SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))))",
"durationInYears"
)
.leftJoin("membership.status", "status")
.leftJoin("membership.member", "member")
.groupBy("status.id")
.addGroupBy("member.id"),
}),
true
);
}
}

View file

@ -1,71 +0,0 @@
import { MigrationInterface, QueryRunner, TableColumn, TableForeignKey, TableIndex } from "typeorm";
import { communication } from "../entity/club/member/communication";
import { member } from "../entity/club/member/member";
export class MoveSendNewsletterFlag1737816852011 implements MigrationInterface {
name = "MoveSendNewsletterFlag1737816852011";
public async up(queryRunner: QueryRunner): Promise<void> {
const table = await queryRunner.getTable("member");
const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("sendNewsletterId") !== -1);
await queryRunner.dropForeignKey("member", foreignKey);
await queryRunner.addColumn(
"communication",
new TableColumn({ name: "isSendNewsletter", type: "tinyint", isNullable: false, default: 0 })
);
// ! has to be sql. Else no data is returned.
const member_newsletter_send = await queryRunner.query("SELECT sendNewsletterId, id FROM `member` `member`");
for (let assigned of member_newsletter_send.map((mns: any) => ({
id: mns.id,
sendNewsletterId: mns.sendNewsletterId,
})) as Array<{ id: number; sendNewsletterId: number }>) {
await queryRunner.manager
.getRepository(communication)
.createQueryBuilder("communication")
.update({ isSendNewsletter: true })
.where({ memberId: assigned.id, id: assigned.sendNewsletterId })
.execute();
}
await queryRunner.dropColumn("member", "sendNewsletterId");
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
"member",
new TableColumn({ name: "sendNewsletterId", type: "int", isNullable: true, isUnique: true, default: null })
);
const member_newsletter_send = await queryRunner.manager
.getRepository(communication)
.createQueryBuilder("communication")
.where("communication.isSendNewsletter = :isSendNewsletter", { isSendNewsletter: true })
.getMany();
for (let assigned of member_newsletter_send.map((mns: any) => ({
id: mns.id,
memberId: mns.memberId,
})) as Array<{ id: number; memberId: number }>) {
await queryRunner.query("UPDATE `member` SET sendNewsletterId = ? WHERE id = ?", [
assigned.id,
assigned.memberId,
]);
}
await queryRunner.createForeignKey(
"member",
new TableForeignKey({
columnNames: ["sendNewsletterId"],
referencedColumnNames: ["id"],
referencedTableName: "communication",
onDelete: "SET NULL",
onUpdate: "RESTRICT",
})
);
await queryRunner.dropColumn("communication", "isSendNewsletter");
}
}

View file

@ -1,38 +1,36 @@
import express, { Request, Response } from "express"; import express, { Request, Response } from "express";
import { import {
addAwardToMember, addAwardToMember,
addCommunicationToMember, addCommunicationToMember,
addExecutivePositionToMember, addExecutivePositionToMember,
addMembershipToMember, addMembershipToMember,
addQualificationToMember, addQualificationToMember,
createMember, createMember,
createMemberPrintoutList, createMemberPrintoutList,
deleteAwardOfMember, deleteAwardOfMember,
deleteCommunicationOfMember, deleteCommunicationOfMember,
deleteExecutivePositionOfMember, deleteExecutivePositionOfMember,
deleteMemberById, deleteMemberById,
deleteMembershipOfMember, deleteMembershipOfMember,
deleteQualificationOfMember, deleteQualificationOfMember,
getAllMembers, getAllMembers,
getAwardByMemberAndRecord, getAwardByMemberAndRecord,
getAwardsByMember, getAwardsByMember,
getCommunicationByMemberAndRecord, getCommunicationByMemberAndRecord,
getCommunicationsByMember, getCommunicationsByMember,
getExecutivePositionByMemberAndRecord, getExecutivePositionByMemberAndRecord,
getExecutivePositionsByMember, getExecutivePositionsByMember,
getMemberById, getMemberById,
getMembershipByMemberAndRecord, getMembershipByMemberAndRecord,
getMembershipsByMember, getMembershipsByMember,
getMembershipStatisticsById, getQualificationByMemberAndRecord,
getMemberStatisticsById, getQualificationsByMember,
getQualificationByMemberAndRecord, updateAwardOfMember,
getQualificationsByMember, updateCommunicationOfMember,
updateAwardOfMember, updateExecutivePositionOfMember,
updateCommunicationOfMember, updateMemberById,
updateExecutivePositionOfMember, updateMembershipOfMember,
updateMemberById, updateQualificationOfMember,
updateMembershipOfMember,
updateQualificationOfMember,
} from "../../../controller/admin/club/memberController"; } from "../../../controller/admin/club/memberController";
import PermissionHelper from "../../../helpers/permissionHelper"; import PermissionHelper from "../../../helpers/permissionHelper";
@ -46,22 +44,14 @@ router.get("/:id", async (req: Request, res: Response) => {
await getMemberById(req, res); await getMemberById(req, res);
}); });
router.get("/:id/statistics", async (req: Request, res: Response) => {
await getMemberStatisticsById(req, res);
});
router.get("/print/namelist", async (req: Request, res: Response) => { router.get("/print/namelist", async (req: Request, res: Response) => {
await createMemberPrintoutList(req, res); await createMemberPrintoutList(req, res);
}); });
router.get("/:memberId/memberships", async (req: Request, res: Response) => { router.get("/:memberId/memberships", async (req: Request, res: Response) => {
await getMembershipsByMember(req, res); await getMembershipsByMember(req, res);
}); });
router.get("/:memberId/memberships/statistics", async (req: Request, res: Response) => {
await getMembershipStatisticsById(req, res);
});
router.get("/:memberId/membership/:id", async (req: Request, res: Response) => { router.get("/:memberId/membership/:id", async (req: Request, res: Response) => {
await getMembershipByMemberAndRecord(req, res); await getMembershipByMemberAndRecord(req, res);
}); });

View file

@ -6,7 +6,6 @@ import communicationType from "./settings/communicationType";
import executivePosition from "./settings/executivePosition"; import executivePosition from "./settings/executivePosition";
import membershipStatus from "./settings/membershipStatus"; import membershipStatus from "./settings/membershipStatus";
import qualification from "./settings/qualification"; import qualification from "./settings/qualification";
import salutation from "./settings/salutation";
import calendarType from "./settings/calendarType"; import calendarType from "./settings/calendarType";
import queryStore from "./settings/queryStore"; import queryStore from "./settings/queryStore";
import template from "./settings/template"; import template from "./settings/template";
@ -22,8 +21,6 @@ import newsletter from "./club/newsletter";
import role from "./user/role"; import role from "./user/role";
import user from "./user/user"; import user from "./user/user";
import invite from "./user/invite"; import invite from "./user/invite";
import api from "./user/webapi";
import preventWebapiAccess from "../../middleware/preventWebApiAccess";
var router = express.Router({ mergeParams: true }); var router = express.Router({ mergeParams: true });
@ -44,7 +41,6 @@ router.use(
membershipStatus membershipStatus
); );
router.use("/qualification", PermissionHelper.passCheckMiddleware("read", "settings", "qualification"), qualification); router.use("/qualification", PermissionHelper.passCheckMiddleware("read", "settings", "qualification"), qualification);
router.use("/salutation", PermissionHelper.passCheckMiddleware("read", "settings", "salutation"), salutation);
router.use("/calendartype", PermissionHelper.passCheckMiddleware("read", "settings", "calendar_type"), calendarType); router.use("/calendartype", PermissionHelper.passCheckMiddleware("read", "settings", "calendar_type"), calendarType);
router.use("/querystore", PermissionHelper.passCheckMiddleware("read", "settings", "query_store"), queryStore); router.use("/querystore", PermissionHelper.passCheckMiddleware("read", "settings", "query_store"), queryStore);
router.use("/template", PermissionHelper.passCheckMiddleware("read", "settings", "template"), template); router.use("/template", PermissionHelper.passCheckMiddleware("read", "settings", "template"), template);
@ -64,6 +60,5 @@ router.use("/newsletter", PermissionHelper.passCheckMiddleware("read", "club", "
router.use("/role", PermissionHelper.passCheckMiddleware("read", "user", "role"), role); router.use("/role", PermissionHelper.passCheckMiddleware("read", "user", "role"), role);
router.use("/user", PermissionHelper.passCheckMiddleware("read", "user", "user"), user); router.use("/user", PermissionHelper.passCheckMiddleware("read", "user", "user"), user);
router.use("/invite", PermissionHelper.passCheckMiddleware("read", "user", "user"), invite); router.use("/invite", PermissionHelper.passCheckMiddleware("read", "user", "user"), invite);
router.use("/webapi", preventWebapiAccess, PermissionHelper.passCheckMiddleware("read", "user", "webapi"), api);
export default router; export default router;

View file

@ -1,45 +0,0 @@
import express, { Request, Response } from "express";
import {
createSalutation,
deleteSalutation,
getAllSalutations,
getSalutationById,
updateSalutation,
} from "../../../controller/admin/settings/salutationController";
import PermissionHelper from "../../../helpers/permissionHelper";
var router = express.Router({ mergeParams: true });
router.get("/", async (req: Request, res: Response) => {
await getAllSalutations(req, res);
});
router.get("/:id", async (req: Request, res: Response) => {
await getSalutationById(req, res);
});
router.post(
"/",
PermissionHelper.passCheckMiddleware("create", "settings", "salutation"),
async (req: Request, res: Response) => {
await createSalutation(req, res);
}
);
router.patch(
"/:id",
PermissionHelper.passCheckMiddleware("update", "settings", "salutation"),
async (req: Request, res: Response) => {
await updateSalutation(req, res);
}
);
router.delete(
"/:id",
PermissionHelper.passCheckMiddleware("delete", "settings", "salutation"),
async (req: Request, res: Response) => {
await deleteSalutation(req, res);
}
);
export default router;

View file

@ -1,64 +0,0 @@
import express, { Request, Response } from "express";
import PermissionHelper from "../../../helpers/permissionHelper";
import {
createWebapi,
deleteWebapi,
getAllWebapis,
getWebapiById,
getWebapiPermissions,
getWebapiTokenById,
updateWebapi,
updateWebapiPermissions,
} from "../../../controller/admin/user/webapiController";
var router = express.Router({ mergeParams: true });
router.get("/", async (req: Request, res: Response) => {
await getAllWebapis(req, res);
});
router.get("/:id", async (req: Request, res: Response) => {
await getWebapiById(req, res);
});
router.get("/:id/token", async (req: Request, res: Response) => {
await getWebapiTokenById(req, res);
});
router.get("/:id/permissions", async (req: Request, res: Response) => {
await getWebapiPermissions(req, res);
});
router.post(
"/",
PermissionHelper.passCheckMiddleware("create", "user", "webapi"),
async (req: Request, res: Response) => {
await createWebapi(req, res);
}
);
router.patch(
"/:id",
PermissionHelper.passCheckMiddleware("update", "user", "webapi"),
async (req: Request, res: Response) => {
await updateWebapi(req, res);
}
);
router.patch(
"/:id/permissions",
PermissionHelper.passCheckMiddleware("admin", "user", "webapi"),
async (req: Request, res: Response) => {
await updateWebapiPermissions(req, res);
}
);
router.delete(
"/:id",
PermissionHelper.passCheckMiddleware("delete", "user", "webapi"),
async (req: Request, res: Response) => {
await deleteWebapi(req, res);
}
);
export default router;

View file

@ -14,11 +14,6 @@ import auth from "./auth";
import admin from "./admin/index"; import admin from "./admin/index";
import user from "./user"; import user from "./user";
import detectPWA from "../middleware/detectPWA"; import detectPWA from "../middleware/detectPWA";
import webapi from "./webapi";
import authenticateAPI from "../middleware/authenticateAPI";
import server from "./server";
import PermissionHelper from "../helpers/permissionHelper";
import preventWebapiAccess from "../middleware/preventWebApiAccess";
export default (app: Express) => { export default (app: Express) => {
app.set("query parser", "extended"); app.set("query parser", "extended");
@ -33,14 +28,12 @@ export default (app: Express) => {
app.use(detectPWA); app.use(detectPWA);
app.use("/api/public", publicAvailable); app.use("/api/public", publicAvailable);
app.use("/api/setup", preventWebapiAccess, allowSetup, setup); app.use("/api/setup", allowSetup, setup);
app.use("/api/reset", preventWebapiAccess, reset); app.use("/api/reset", reset);
app.use("/api/invite", preventWebapiAccess, invite); app.use("/api/invite", invite);
app.use("/api/auth", preventWebapiAccess, auth); app.use("/api/auth", auth);
app.use("/api/webapi", authenticateAPI, webapi);
app.use(authenticate); app.use(authenticate);
app.use("/api/admin", admin); app.use("/api/admin", admin);
app.use("/api/user", preventWebapiAccess, user); app.use("/api/user", user);
app.use("/api/server", preventWebapiAccess, PermissionHelper.isAdminMiddleware(), server);
app.use(errorHandler); app.use(errorHandler);
}; };

View file

@ -1,35 +0,0 @@
import express, { Request, Response } from "express";
import { FileSystemHelper } from "../helpers/fileSystemHelper";
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 serverJson = JSON.parse(serverPackage);
res.send({
name: serverJson.name,
description: serverJson.description,
version: serverJson.version,
author: serverJson.author,
license: serverJson.license,
});
});
router.get("/settings", async (req: Request, res: Response) => {
res.json({});
});
router.get("/serverrss", async (req: Request, res: Response) => {
const parser = new Parser();
let feed = await parser.parseURL("https://forgejo.jk-effects.cloud/Ehrenamt/ff-admin-server/releases.rss");
res.json(feed);
});
router.get("/clientrss", async (req: Request, res: Response) => {
const parser = new Parser();
let feed = await parser.parseURL("https://forgejo.jk-effects.cloud/Ehrenamt/ff-admin/releases.rss");
res.json(feed);
});
export default router;

View file

@ -1,10 +0,0 @@
import express, { Request, Response } from "express";
import { getWebApiAccess } from "../controller/webapiController";
var router = express.Router({ mergeParams: true });
router.get("/retrieve", async (req: Request, res: Response) => {
await getWebApiAccess(req, res);
});
export default router;

View file

@ -14,6 +14,7 @@ export default abstract class CommunicationService {
.createQueryBuilder("communication") .createQueryBuilder("communication")
.leftJoinAndSelect("communication.type", "communicationType") .leftJoinAndSelect("communication.type", "communicationType")
.leftJoinAndSelect("communication.member", "member") .leftJoinAndSelect("communication.member", "member")
.leftJoinAndSelect("member.sendNewsletter", "sendNewsletter")
.where("communication.memberId = :memberId", { memberId: memberId }) .where("communication.memberId = :memberId", { memberId: memberId })
.orderBy("communicationType.type", "ASC") .orderBy("communicationType.type", "ASC")
.getMany() .getMany()
@ -37,6 +38,7 @@ export default abstract class CommunicationService {
.createQueryBuilder("communication") .createQueryBuilder("communication")
.leftJoinAndSelect("communication.type", "communicationType") .leftJoinAndSelect("communication.type", "communicationType")
.leftJoinAndSelect("communication.member", "member") .leftJoinAndSelect("communication.member", "member")
.leftJoinAndSelect("member.sendNewsletter", "sendNewsletter")
.where("communication.memberId = :memberId", { memberId: memberId }) .where("communication.memberId = :memberId", { memberId: memberId })
.andWhere("communication.id = :recordId", { recordId: recordId }) .andWhere("communication.id = :recordId", { recordId: recordId })
.getOneOrFail() .getOneOrFail()

View file

@ -2,7 +2,6 @@ import { dataSource } from "../../../data-source";
import { member } from "../../../entity/club/member/member"; import { member } from "../../../entity/club/member/member";
import { membership } from "../../../entity/club/member/membership"; import { membership } from "../../../entity/club/member/membership";
import InternalException from "../../../exceptions/internalException"; import InternalException from "../../../exceptions/internalException";
import { memberView } from "../../../views/memberView";
export default abstract class MemberService { export default abstract class MemberService {
/** /**
@ -39,6 +38,8 @@ export default abstract class MemberService {
) )
.leftJoinAndSelect("membership_first.status", "status_first") .leftJoinAndSelect("membership_first.status", "status_first")
.leftJoinAndSelect("membership_last.status", "status_last") .leftJoinAndSelect("membership_last.status", "status_last")
.leftJoinAndSelect("member.sendNewsletter", "sendNewsletter")
.leftJoinAndSelect("sendNewsletter.type", "communicationtype")
.leftJoinAndMapMany( .leftJoinAndMapMany(
"member.preferredCommunication", "member.preferredCommunication",
"member.communications", "member.communications",
@ -46,16 +47,8 @@ export default abstract class MemberService {
"preferredCommunication.preferred = 1" "preferredCommunication.preferred = 1"
) )
.leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred") .leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred")
.leftJoinAndMapOne(
"member.sendNewsletter",
"member.communications",
"sendNewsletter",
"sendNewsletter.isSendNewsletter = 1"
)
.leftJoinAndSelect("sendNewsletter.type", "communicationtype")
.leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1") .leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1")
.leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming") .leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming");
.leftJoinAndSelect("member.salutation", "salutation");
if (search != "") { if (search != "") {
search.split(" ").forEach((term, index) => { search.split(" ").forEach((term, index) => {
@ -117,23 +110,18 @@ export default abstract class MemberService {
) )
.leftJoinAndSelect("membership_first.status", "status_first") .leftJoinAndSelect("membership_first.status", "status_first")
.leftJoinAndSelect("membership_last.status", "status_last") .leftJoinAndSelect("membership_last.status", "status_last")
.leftJoinAndSelect("member.sendNewsletter", "sendNewsletter")
.leftJoinAndSelect("sendNewsletter.type", "communicationtype")
.leftJoinAndMapMany( .leftJoinAndMapMany(
"member.preferredCommunication", "member.preferredCommunication",
"member.communications", "member.communications",
"preferredCommunication", "preferredCommunication",
"preferredCommunication.preferred = 1" "preferredCommunication.preferred = 1"
) )
.leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred")
.leftJoinAndMapOne(
"member.sendNewsletter",
"member.communications",
"sendNewsletter",
"sendNewsletter.isSendNewsletter = 1"
)
.leftJoinAndSelect("sendNewsletter.type", "communicationtype")
.leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1") .leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1")
.leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming") .leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming")
.leftJoinAndSelect("member.salutation", "salutation") .leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred")
.where("member.id = :id", { id: id }) .where("member.id = :id", { id: id })
.getOneOrFail() .getOneOrFail()
.then((res) => { .then((res) => {
@ -144,25 +132,6 @@ export default abstract class MemberService {
}); });
} }
/**
* @description get member statistics by id
* @param {number} id
* @returns {Promise<memberView>}
*/
static async getStatisticsById(id: number): Promise<memberView> {
return await dataSource
.getRepository(memberView)
.createQueryBuilder("memberView")
.where("memberView.id = :id", { id: id })
.getOneOrFail()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("memberView not found by id", err);
});
}
/** /**
* @description get members where membership is setz * @description get members where membership is setz
* @returns {Promise<member>} * @returns {Promise<member>}
@ -194,12 +163,7 @@ export default abstract class MemberService {
return await dataSource return await dataSource
.getRepository(member) .getRepository(member)
.createQueryBuilder("member") .createQueryBuilder("member")
.leftJoinAndMapOne( .leftJoinAndSelect("member.sendNewsletter", "sendNewsletter")
"member.sendNewsletter",
"member.communications",
"sendNewsletter",
"sendNewsletter.isSendNewsletter = 1"
)
.where("member.id = :id", { id: id }) .where("member.id = :id", { id: id })
.getOneOrFail() .getOneOrFail()
.then((res) => { .then((res) => {

View file

@ -1,7 +1,6 @@
import { dataSource } from "../../../data-source"; import { dataSource } from "../../../data-source";
import { membership } from "../../../entity/club/member/membership"; import { membership } from "../../../entity/club/member/membership";
import InternalException from "../../../exceptions/internalException"; import InternalException from "../../../exceptions/internalException";
import { membershipView } from "../../../views/membershipsView";
export default abstract class MembershipService { export default abstract class MembershipService {
/** /**
@ -46,23 +45,4 @@ export default abstract class MembershipService {
throw new InternalException("member membership not found by id", err); throw new InternalException("member membership not found by id", err);
}); });
} }
/**
* @description get membership statistics by memberId
* @param {number} memberId
* @returns {Promise<Array<membershipView>>}
*/
static async getStatisticsById(memberId: number): Promise<Array<membershipView>> {
return await dataSource
.getRepository(membershipView)
.createQueryBuilder("membershipView")
.where("membershipView.memberId = :memberId", { memberId: memberId })
.getMany()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("membershipView not found by id", err);
});
}
} }

View file

@ -1,41 +0,0 @@
import { dataSource } from "../../data-source";
import { salutation } from "../../entity/settings/salutation";
import InternalException from "../../exceptions/internalException";
export default abstract class SalutationService {
/**
* @description get all salutations
* @returns {Promise<Array<salutation>>}
*/
static async getAll(): Promise<Array<salutation>> {
return await dataSource
.getRepository(salutation)
.createQueryBuilder("salutation")
.orderBy("salutation", "ASC")
.getMany()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("salutations not found", err);
});
}
/**
* @description get salutation by id
* @returns {Promise<salutation>}
*/
static async getById(id: number): Promise<salutation> {
return await dataSource
.getRepository(salutation)
.createQueryBuilder("salutation")
.where("salutation.id = :id", { id: id })
.getOneOrFail()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("salutation not found by id", err);
});
}
}

View file

@ -1,24 +0,0 @@
import { dataSource } from "../../data-source";
import { webapiPermission } from "../../entity/user/webapi_permission";
import InternalException from "../../exceptions/internalException";
export default abstract class WebapiPermissionService {
/**
* @description get permission by api
* @param webapiId number
* @returns {Promise<Array<webapiPermission>>}
*/
static async getByApi(webapiId: number): Promise<Array<webapiPermission>> {
return await dataSource
.getRepository(webapiPermission)
.createQueryBuilder("webapi_permission")
.where("webapi_permission.webapiId = :webapiId", { webapiId: webapiId })
.getMany()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("webapi permissions not found by api", err);
});
}
}

View file

@ -1,83 +0,0 @@
import { dataSource } from "../../data-source";
import { webapi } from "../../entity/user/webapi";
import InternalException from "../../exceptions/internalException";
export default abstract class WebapiService {
/**
* @description get apis
* @returns {Promise<Array<webapi>>}
*/
static async getAll(): Promise<Array<webapi>> {
return await dataSource
.getRepository(webapi)
.createQueryBuilder("webapi")
.leftJoinAndSelect("webapi.permissions", "permissions")
.getMany()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("webapis not found", err);
});
}
/**
* @description get api by id
* @param id number
* @returns {Promise<webapi>}
*/
static async getById(id: number): Promise<webapi> {
return await dataSource
.getRepository(webapi)
.createQueryBuilder("webapi")
.leftJoinAndSelect("webapi.permissions", "permissions")
.where("webapi.id = :id", { id: id })
.getOneOrFail()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("webapi not found by id", err);
});
}
/**
* @description get api by token
* @param token string
* @returns {Promise<webapi>}
*/
static async getByToken(token: string): Promise<webapi> {
return await dataSource
.getRepository(webapi)
.createQueryBuilder("webapi")
.leftJoinAndSelect("webapi.permissions", "permissions")
.where("webapi.token = :token", { token: token })
.getOneOrFail()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("webapi not found by token", err);
});
}
/**
* @description get api by id
* @param id number
* @returns {Promise<webapi>}
*/
static async getTokenById(id: number): Promise<webapi> {
return await dataSource
.getRepository(webapi)
.createQueryBuilder("webapi")
.select("webapi.token")
.where("webapi.id = :id", { id: id })
.getOneOrFail()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("webapi token not found by id", err);
});
}
}

View file

@ -11,11 +11,9 @@ export type PermissionModule =
| "executive_position" | "executive_position"
| "communication_type" | "communication_type"
| "membership_status" | "membership_status"
| "salutation"
| "calendar_type" | "calendar_type"
| "user" | "user"
| "role" | "role"
| "webapi"
| "query" | "query"
| "query_store" | "query_store"
| "template" | "template"
@ -54,11 +52,9 @@ export const permissionModules: Array<PermissionModule> = [
"executive_position", "executive_position",
"communication_type", "communication_type",
"membership_status", "membership_status",
"salutation",
"calendar_type", "calendar_type",
"user", "user",
"role", "role",
"webapi",
"query", "query",
"query_store", "query_store",
"template", "template",
@ -73,12 +69,11 @@ export const sectionsAndModules: SectionsAndModulesObject = {
"executive_position", "executive_position",
"communication_type", "communication_type",
"membership_status", "membership_status",
"salutation",
"calendar_type", "calendar_type",
"query_store", "query_store",
"template", "template",
"template_usage", "template_usage",
"newsletter_config", "newsletter_config",
], ],
user: ["user", "role", "webapi"], user: ["user", "role"],
}; };

View file

@ -1,10 +1,10 @@
import { SalutationViewModel } from "../../settings/salutation.models"; import { Salutation } from "../../../../enums/salutation";
import { CommunicationViewModel } from "./communication.models"; import { CommunicationViewModel } from "./communication.models";
import { MembershipViewModel } from "./membership.models"; import { MembershipViewModel } from "./membership.models";
export interface MemberViewModel { export interface MemberViewModel {
id: number; id: number;
salutation: SalutationViewModel; salutation: Salutation;
firstname: string; firstname: string;
lastname: string; lastname: string;
nameaffix: string; nameaffix: string;
@ -16,15 +16,3 @@ export interface MemberViewModel {
smsAlarming?: Array<CommunicationViewModel>; smsAlarming?: Array<CommunicationViewModel>;
preferredCommunication?: Array<CommunicationViewModel>; preferredCommunication?: Array<CommunicationViewModel>;
} }
export interface MemberStatisticsViewModel {
id: number;
salutation: string;
firstname: string;
lastname: string;
nameaffix: string;
birthdate: Date;
todayAge: number;
ageThisYear: number;
exactAge: string;
}

View file

@ -6,16 +6,3 @@ export interface MembershipViewModel {
status: string; status: string;
statusId: number; statusId: number;
} }
export interface MembershipStatisticsViewModel {
durationInDays: number;
durationInYears: string;
status: string;
statusId: number;
memberId: number;
memberSalutation: string;
memberFirstname: string;
memberLastname: string;
memberNameaffix: string;
memberBirthdate: Date;
}

View file

@ -1,4 +0,0 @@
export interface SalutationViewModel {
id: number;
salutation: string;
}

View file

@ -1,10 +0,0 @@
import { PermissionObject } from "../../../type/permissionTypes";
export interface ApiViewModel {
id: number;
permissions: PermissionObject;
title: string;
createdAt: Date;
lastUsage?: Date;
expiry?: Date;
}

View file

@ -1,5 +1,6 @@
import { DataSource, ViewColumn, ViewEntity } from "typeorm"; import { DataSource, ViewColumn, ViewEntity } from "typeorm";
import { memberExecutivePositions } from "../entity/club/member/memberExecutivePositions"; import { memberExecutivePositions } from "../entity/club/member/memberExecutivePositions";
import { Salutation } from "../enums/salutation";
@ViewEntity({ @ViewEntity({
expression: (datasource: DataSource) => expression: (datasource: DataSource) =>
@ -9,20 +10,18 @@ import { memberExecutivePositions } from "../entity/club/member/memberExecutiveP
.select("executivePosition.id", "positionId") .select("executivePosition.id", "positionId")
.addSelect("executivePosition.position", "position") .addSelect("executivePosition.position", "position")
.addSelect("member.id", "memberId") .addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname") .addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname") .addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix") .addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate") .addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect( .addSelect(
"SUM(TIMESTAMPDIFF(DAY, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURRENT_DATE)))", "SUM(TIMESTAMPDIFF(DAY, memberExecutivePositions.start, COALESCE(memberExecutivePositions.end, CURRENT_DATE)))",
"durationInDays" "durationInDays"
) )
.leftJoin("memberExecutivePositions.executivePosition", "executivePosition") .leftJoin("memberExecutivePositions.executivePosition", "executivePosition")
.leftJoin("memberExecutivePositions.member", "member") .leftJoin("memberExecutivePositions.member", "member")
.leftJoin("member.salutation", "salutation") .groupBy("executivePosition.id"),
.groupBy("executivePosition.id")
.addGroupBy("member.id"),
}) })
export class memberExecutivePositionsView { export class memberExecutivePositionsView {
@ViewColumn() @ViewColumn()
@ -38,7 +37,7 @@ export class memberExecutivePositionsView {
memberId: number; memberId: number;
@ViewColumn() @ViewColumn()
memberSalutation: string; memberSalutation: Salutation;
@ViewColumn() @ViewColumn()
memberFirstname: string; memberFirstname: string;

View file

@ -1,5 +1,6 @@
import { DataSource, ViewColumn, ViewEntity } from "typeorm"; import { DataSource, ViewColumn, ViewEntity } from "typeorm";
import { memberQualifications } from "../entity/club/member/memberQualifications"; import { memberQualifications } from "../entity/club/member/memberQualifications";
import { Salutation } from "../enums/salutation";
@ViewEntity({ @ViewEntity({
expression: (datasource: DataSource) => expression: (datasource: DataSource) =>
@ -9,20 +10,18 @@ import { memberQualifications } from "../entity/club/member/memberQualifications
.select("qualification.id", "qualificationId") .select("qualification.id", "qualificationId")
.addSelect("qualification.qualification", "qualification") .addSelect("qualification.qualification", "qualification")
.addSelect("member.id", "memberId") .addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname") .addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname") .addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix") .addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate") .addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect( .addSelect(
"SUM(TIMESTAMPDIFF(DAY, memberQualifications.start, COALESCE(memberQualifications.end, CURRENT_DATE)))", "SUM(TIMESTAMPDIFF(DAY, memberQualifications.start, COALESCE(memberQualifications.end, CURRENT_DATE)))",
"durationInDays" "durationInDays"
) )
.leftJoin("memberQualifications.qualification", "qualification") .leftJoin("memberQualifications.qualification", "qualification")
.leftJoin("memberQualifications.member", "member") .leftJoin("memberQualifications.member", "member")
.leftJoin("member.salutation", "salutation") .groupBy("qualification.id"),
.groupBy("qualification.id")
.addGroupBy("member.id"),
}) })
export class memberQualificationsView { export class memberQualificationsView {
@ViewColumn() @ViewColumn()
@ -38,7 +37,7 @@ export class memberQualificationsView {
memberId: number; memberId: number;
@ViewColumn() @ViewColumn()
memberSalutation: string; memberSalutation: Salutation;
@ViewColumn() @ViewColumn()
memberFirstname: string; memberFirstname: string;

View file

@ -1,5 +1,6 @@
import { DataSource, ViewColumn, ViewEntity } from "typeorm"; import { DataSource, ViewColumn, ViewEntity } from "typeorm";
import { member } from "../entity/club/member/member"; import { member } from "../entity/club/member/member";
import { Salutation } from "../enums/salutation";
@ViewEntity({ @ViewEntity({
expression: (datasource: DataSource) => expression: (datasource: DataSource) =>
@ -7,22 +8,21 @@ import { member } from "../entity/club/member/member";
.getRepository(member) .getRepository(member)
.createQueryBuilder("member") .createQueryBuilder("member")
.select("member.id", "id") .select("member.id", "id")
.addSelect("member.salutation", "salutation")
.addSelect("member.firstname", "firstname") .addSelect("member.firstname", "firstname")
.addSelect("member.lastname", "lastname") .addSelect("member.lastname", "lastname")
.addSelect("member.nameaffix", "nameaffix") .addSelect("member.nameaffix", "nameaffix")
.addSelect("member.birthdate", "birthdate") .addSelect("member.birthdate", "birthdate")
.addSelect("salutation.salutation", "salutation")
.addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge") .addSelect("TIMESTAMPDIFF(YEAR, member.birthdate, CURDATE())", "todayAge")
.addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear") .addSelect("YEAR(CURDATE()) - YEAR(member.birthdate)", "ageThisYear")
.addSelect("CONCAT('_', FROM_DAYS(TIMESTAMPDIFF(DAY, member.birthdate, CURDATE())))", "exactAge") .addSelect("CONCAT('_', FROM_DAYS(TIMESTAMPDIFF(DAY, member.birthdate, CURDATE())))", "exactAge"),
.leftJoin("member.salutation", "salutation"),
}) })
export class memberView { export class memberView {
@ViewColumn() @ViewColumn()
id: number; id: number;
@ViewColumn() @ViewColumn()
salutation: string; salutation: Salutation;
@ViewColumn() @ViewColumn()
firstname: string; firstname: string;
@ -43,5 +43,5 @@ export class memberView {
ageThisYear: number; ageThisYear: number;
@ViewColumn() @ViewColumn()
exactAge: string; exactAge: Date;
} }

View file

@ -1,5 +1,6 @@
import { DataSource, ViewColumn, ViewEntity } from "typeorm"; import { DataSource, ViewColumn, ViewEntity } from "typeorm";
import { membership } from "../entity/club/member/membership"; import { membership } from "../entity/club/member/membership";
import { Salutation } from "../enums/salutation";
@ViewEntity({ @ViewEntity({
expression: (datasource: DataSource) => expression: (datasource: DataSource) =>
@ -9,11 +10,11 @@ import { membership } from "../entity/club/member/membership";
.select("status.id", "statusId") .select("status.id", "statusId")
.addSelect("status.status", "status") .addSelect("status.status", "status")
.addSelect("member.id", "memberId") .addSelect("member.id", "memberId")
.addSelect("member.salutation", "memberSalutation")
.addSelect("member.firstname", "memberFirstname") .addSelect("member.firstname", "memberFirstname")
.addSelect("member.lastname", "memberLastname") .addSelect("member.lastname", "memberLastname")
.addSelect("member.nameaffix", "memberNameaffix") .addSelect("member.nameaffix", "memberNameaffix")
.addSelect("member.birthdate", "memberBirthdate") .addSelect("member.birthdate", "memberBirthdate")
.addSelect("salutation.salutation", "memberSalutation")
.addSelect("SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))", "durationInDays") .addSelect("SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))", "durationInDays")
.addSelect( .addSelect(
"CONCAT('_', FROM_DAYS(SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))))", "CONCAT('_', FROM_DAYS(SUM(TIMESTAMPDIFF(DAY, membership.start, COALESCE(membership.end, CURRENT_DATE)))))",
@ -21,7 +22,6 @@ import { membership } from "../entity/club/member/membership";
) )
.leftJoin("membership.status", "status") .leftJoin("membership.status", "status")
.leftJoin("membership.member", "member") .leftJoin("membership.member", "member")
.leftJoin("member.salutation", "salutation")
.groupBy("status.id") .groupBy("status.id")
.addGroupBy("member.id"), .addGroupBy("member.id"),
}) })
@ -30,7 +30,7 @@ export class membershipView {
durationInDays: number; durationInDays: number;
@ViewColumn() @ViewColumn()
durationInYears: string; durationInYears: Date;
@ViewColumn() @ViewColumn()
status: string; status: string;
@ -42,7 +42,7 @@ export class membershipView {
memberId: number; memberId: number;
@ViewColumn() @ViewColumn()
memberSalutation: string; memberSalutation: Salutation;
@ViewColumn() @ViewColumn()
memberFirstname: string; memberFirstname: string;