From 5c68f0c54fc9066c7e21eb0b3ad64d81bb31d056 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Wed, 27 Nov 2024 10:07:59 +0100 Subject: [PATCH 1/7] sms alarming flag --- src/command/communicationCommand.ts | 2 ++ src/command/communicationCommandHandler.ts | 2 ++ src/command/memberCommandHandler.ts | 21 +++++++++++++++- src/controller/admin/memberController.ts | 8 ++++++ src/data-source.ts | 2 ++ src/entity/communication.ts | 3 +++ src/entity/member.ts | 1 + src/factory/admin/communication.ts | 1 + src/factory/admin/member.ts | 1 + src/migrations/1732696919191-SMSAlarming.ts | 21 ++++++++++++++++ src/service/communicationService.ts | 2 +- src/service/memberService.ts | 27 ++++++++++++++++++++- src/viewmodel/admin/communication.models.ts | 1 + src/viewmodel/admin/member.models.ts | 1 + 14 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 src/migrations/1732696919191-SMSAlarming.ts diff --git a/src/command/communicationCommand.ts b/src/command/communicationCommand.ts index 1dfffd6..6700110 100644 --- a/src/command/communicationCommand.ts +++ b/src/command/communicationCommand.ts @@ -1,5 +1,6 @@ export interface CreateCommunicationCommand { preferred: boolean; + isSMSAlarming: boolean; mobile: string; email: string; city: string; @@ -13,6 +14,7 @@ export interface CreateCommunicationCommand { export interface UpdateCommunicationCommand { id: number; preferred: boolean; + isSMSAlarming: boolean; mobile: string; email: string; city: string; diff --git a/src/command/communicationCommandHandler.ts b/src/command/communicationCommandHandler.ts index 4f2f801..c13bec6 100644 --- a/src/command/communicationCommandHandler.ts +++ b/src/command/communicationCommandHandler.ts @@ -22,6 +22,7 @@ export default abstract class CommunicationCommandHandler { .into(communication) .values({ preferred: createCommunication.preferred, + isSMSAlarming: createCommunication.isSMSAlarming, mobile: createCommunication.mobile, email: createCommunication.email, city: createCommunication.city, @@ -59,6 +60,7 @@ export default abstract class CommunicationCommandHandler { .update(communication) .set({ preferred: updateCommunication.preferred, + isSMSAlarming: updateCommunication.isSMSAlarming, mobile: updateCommunication.mobile, email: updateCommunication.email, city: updateCommunication.city, diff --git a/src/command/memberCommandHandler.ts b/src/command/memberCommandHandler.ts index b37d89f..4c396cf 100644 --- a/src/command/memberCommandHandler.ts +++ b/src/command/memberCommandHandler.ts @@ -68,7 +68,6 @@ export default abstract class MemberCommandHandler { * @returns {Promise} */ static async updateNewsletter(updateMember: UpdateMemberNewsletterCommand): Promise { - console.log(updateMember); return await dataSource .createQueryBuilder() .update(member) @@ -88,6 +87,26 @@ export default abstract class MemberCommandHandler { }); } + /** + * @description update member newsletter to unset + * @param memberId string + * @returns {Promise} + */ + static async unsetNewsletter(memberId: number): Promise { + 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); + }); + } + /** * @description delete member * @param DeleteMemberCommand diff --git a/src/controller/admin/memberController.ts b/src/controller/admin/memberController.ts index 0fc309e..6644568 100644 --- a/src/controller/admin/memberController.ts +++ b/src/controller/admin/memberController.ts @@ -346,6 +346,7 @@ export async function addExecutivePositionToMember(req: Request, res: Response): export async function addCommunicationToMember(req: Request, res: Response): Promise { const memberId = parseInt(req.params.memberId); const preferred = req.body.preferred; + const isSMSAlarming = req.body.isSMSAlarming; const mobile = req.body.mobile; const email = req.body.email; const city = req.body.city; @@ -357,6 +358,7 @@ export async function addCommunicationToMember(req: Request, res: Response): Pro let createCommunication: CreateCommunicationCommand = { preferred, + isSMSAlarming, mobile, email, city, @@ -528,6 +530,7 @@ export async function updateCommunicationOfMember(req: Request, res: Response): const memberId = parseInt(req.params.memberId); const recordId = parseInt(req.params.recordId); const preferred = req.body.preferred; + const isSMSAlarming = req.body.isSMSAlarming; const mobile = req.body.mobile; const email = req.body.email; const city = req.body.city; @@ -540,6 +543,7 @@ export async function updateCommunicationOfMember(req: Request, res: Response): let updateCommunication: UpdateCommunicationCommand = { id: recordId, preferred, + isSMSAlarming, mobile, email, city, @@ -550,12 +554,16 @@ export async function updateCommunicationOfMember(req: Request, res: Response): }; 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); diff --git a/src/data-source.ts b/src/data-source.ts index 39a56c7..c7fec9f 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -42,6 +42,7 @@ import { calendarType } from "./entity/calendarType"; import { Calendar1729947763295 } from "./migrations/1729947763295-calendar"; import { reset } from "./entity/reset"; import { ResetToken1732358596823 } from "./migrations/1732358596823-resetToken"; +import { SMSAlarming1732696919191 } from "./migrations/1732696919191-SMSAlarming"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -94,6 +95,7 @@ const dataSource = new DataSource({ Protocol1729347911107, Calendar1729947763295, ResetToken1732358596823, + SMSAlarming1732696919191, ], migrationsRun: true, migrationsTransactionMode: "each", diff --git a/src/entity/communication.ts b/src/entity/communication.ts index dbf3983..be006c1 100644 --- a/src/entity/communication.ts +++ b/src/entity/communication.ts @@ -10,6 +10,9 @@ export class communication { @Column({ type: "boolean", default: false }) preferred: boolean; + @Column({ type: "boolean", default: false }) + isSMSAlarming: boolean; + @Column({ type: "varchar", length: 255, nullable: true }) mobile: string; diff --git a/src/entity/member.ts b/src/entity/member.ts index d2be9b0..7bb0d78 100644 --- a/src/entity/member.ts +++ b/src/entity/member.ts @@ -65,4 +65,5 @@ export class member { firstMembershipEntry?: membership; lastMembershipEntry?: membership; preferredCommunication?: Array; + smsAlarming?: Array; } diff --git a/src/factory/admin/communication.ts b/src/factory/admin/communication.ts index 1333515..6fe454e 100644 --- a/src/factory/admin/communication.ts +++ b/src/factory/admin/communication.ts @@ -20,6 +20,7 @@ export default abstract class CommunicationFactory { streetNumberAddition: record.streetNumberAddition, type: CommunicationTypeFactory.mapToSingle(record.type), isNewsletterMain: isMain ? isMain : record?.member?.sendNewsletter?.id == record.id, + isSMSAlarming: record.isSMSAlarming, }; } diff --git a/src/factory/admin/member.ts b/src/factory/admin/member.ts index 0745d10..44ddc70 100644 --- a/src/factory/admin/member.ts +++ b/src/factory/admin/member.ts @@ -27,6 +27,7 @@ export default abstract class MemberFactory { preferredCommunication: record?.preferredCommunication ? CommunicationFactory.mapToBase(record.preferredCommunication) : null, + smsAlarming: record?.smsAlarming ? CommunicationFactory.mapToBase(record.smsAlarming) : null, }; } diff --git a/src/migrations/1732696919191-SMSAlarming.ts b/src/migrations/1732696919191-SMSAlarming.ts new file mode 100644 index 0000000..2ef756e --- /dev/null +++ b/src/migrations/1732696919191-SMSAlarming.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; + +export class SMSAlarming1732696919191 implements MigrationInterface { + name = "SMSAlarming1732696919191"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumn( + "communication", + new TableColumn({ + name: "isSMSAlarming", + type: "tinyint", + default: 0, + isNullable: false, + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn("communication", "isSMSAlarming"); + } +} diff --git a/src/service/communicationService.ts b/src/service/communicationService.ts index de86e49..03073fa 100644 --- a/src/service/communicationService.ts +++ b/src/service/communicationService.ts @@ -56,6 +56,6 @@ export default abstract class CommunicationService { static getAvailableColumnsForCommunication(): Array { let metadata = dataSource.getMetadata(communication); let columns = metadata.columns.map((c) => c.propertyName); - return columns.filter((c) => !["id", "preferred", "type", "member"].includes(c)); + return columns.filter((c) => !["id", "preferred", "isSMSAlarming", "type", "member"].includes(c)); } } diff --git a/src/service/memberService.ts b/src/service/memberService.ts index ae9e2d4..633497d 100644 --- a/src/service/memberService.ts +++ b/src/service/memberService.ts @@ -35,6 +35,8 @@ export default abstract class MemberService { "preferredCommunication.preferred = 1" ) .leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred") + .leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1") + .leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming") .offset(offset) .limit(count) .orderBy("member.lastname") @@ -52,7 +54,7 @@ export default abstract class MemberService { /** * @description get member by id * @param {number} id - * @returns {Promise>} + * @returns {Promise} */ static async getById(id: number): Promise { return await dataSource @@ -80,6 +82,9 @@ export default abstract class MemberService { "preferredCommunication", "preferredCommunication.preferred = 1" ) + + .leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1") + .leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming") .leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred") .where("member.id = :id", { id: id }) .getOneOrFail() @@ -90,4 +95,24 @@ export default abstract class MemberService { throw new InternalException("member not found by id", err); }); } + + /** + * @description get newsletter by member by id + * @param {number} id + * @returns {Promise} + */ + static async getNewsletterById(id: number): Promise { + return await dataSource + .getRepository(member) + .createQueryBuilder("member") + .leftJoinAndSelect("member.sendNewsletter", "sendNewsletter") + .where("member.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("member not found by id", err); + }); + } } diff --git a/src/viewmodel/admin/communication.models.ts b/src/viewmodel/admin/communication.models.ts index 50e9ac5..64295f8 100644 --- a/src/viewmodel/admin/communication.models.ts +++ b/src/viewmodel/admin/communication.models.ts @@ -11,4 +11,5 @@ export interface CommunicationViewModel { streetNumberAddition: string; type: CommunicationTypeViewModel; isNewsletterMain: boolean; + isSMSAlarming: boolean; } diff --git a/src/viewmodel/admin/member.models.ts b/src/viewmodel/admin/member.models.ts index 5772ff5..c005d9d 100644 --- a/src/viewmodel/admin/member.models.ts +++ b/src/viewmodel/admin/member.models.ts @@ -12,5 +12,6 @@ export interface MemberViewModel { firstMembershipEntry?: MembershipViewModel; lastMembershipEntry?: MembershipViewModel; sendNewsletter?: CommunicationViewModel; + smsAlarming?: Array; preferredCommunication?: Array; } From f58032e1e5d2f02635f75965569afaa39191f6a6 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Wed, 27 Nov 2024 10:09:06 +0100 Subject: [PATCH 2/7] 0.0.8 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 75955bd..fa5d225 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "member-administration-server", - "version": "0.0.7", + "version": "0.0.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "member-administration-server", - "version": "0.0.7", + "version": "0.0.8", "license": "GPL-3.0-only", "dependencies": { "cors": "^2.8.5", diff --git a/package.json b/package.json index b73bff5..a798c12 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "member-administration-server", - "version": "0.0.7", + "version": "0.0.8", "description": "Feuerwehr/Verein Mitgliederverwaltung Server", "main": "dist/index.js", "scripts": { From 8b7bbf8edd2d1d67757018e04140152ba90711f8 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Wed, 27 Nov 2024 17:06:41 +0100 Subject: [PATCH 3/7] docker build test --- Dockerfile | 1 - README.md | 95 ++++++++++++++++++++++++++++++++++++--------- src/routes/index.ts | 14 +++---- 3 files changed, 83 insertions(+), 27 deletions(-) diff --git a/Dockerfile b/Dockerfile index 250b814..8d60e07 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,6 @@ WORKDIR /app COPY --from=build /app/dist /app/dist COPY --from=build /app/node_modules /app/node_modules COPY --from=build /app/package.json /app/package.json -COPY --from=build /app/.env /app/.env EXPOSE 5000 diff --git a/README.md b/README.md index 08b682c..dc68a7b 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,87 @@ # member-administration-server -Memberadministration +Mitgliederverwaltung für Feuerwehren und Vereine (Backend). -Authentications is realized via JWT-Tokens. The server is able to send Mails to the members. -Login is possible via Username and TOTP. +## Einleitung + +Dieses Projekt, `member-administration-server`, ist das Backend zur Verwaltung von Mitgliederdaten. Die zugehörige Webapp ist im Repository [member-administration-ui](https://forgejo.jk-effects.cloud/Ehrenamt/member-administration-ui) zu finden. + +Eine Demo zusammen mit der `member-administration-ui` finden Sie unter [ff-admin-demo.jk-effects.cloud](ff-admin-demo.jk-effects.cloud). ## Installation -### Requirements +### Docker Compose Setup -1. MySql Database -2. Access to the internet for sending Mails +Um den Container hochzufahren, erstellen Sie eine `docker-compose.yml` Datei mit folgendem Inhalt: -### Configuration +```yaml +version: "3" -1. Copy the .env.example file to .env and fill in the required information -2. Create a new Database in MySql named as in the .env file -3. Install all packages via `npm install` -4. Start the application to create the database schema +services: + ff-member-administration-server: + image: docker.registry.jk-effects.cloud/ehrenamt/member-administration/server:latest + container_name: ff_member_administration_server + restart: unless-stopped + environment: + - DB_TYPE = mysql + - DB_HOST=ffm-db + - DB_NAME=administration + - DB_USERNAME=administration_backend + - DB_PASSWORD= + - JWT_SECRET= + - JWT_EXPIRATION= + - REFRESH_EXPIRATION= + - MAIL_USERNAME= + - MAIL_PASSWORD= + - MAIL_HOST= + - MAIL_PORT= + - MAIL_SECURE= + - CLUB_NAME= + volumes: + - :/app/export + networks: + - ff_internal + depends_on: + - ff-db -## Testing + ff-db: + image: mariadb:11.2 + container_name: ff_db + restart: unless-stopped + environment: + - MYSQL_DATABASE=ffadmin + - MYSQL_USER=administration_backend + - MYSQL_PASSWORD= + - MYSQL_ROOT_PASSWORD= + volumes: + - :/var/lib/mysql + networks: + - ff_internal -1. Install the database-system-package you like (e.g. mysql, mariadb, postgresql, sqlite3) -2. Configure type inside src/data-source.ts to run the database-system you like. -3. Set migrationsRun to false and synchronize to true for rapid prototyping -4. Building the schema via CLI: - - Run `npm run update-database` to build the schema using the migrations without starting the application - - Run `npm run synchronize-database` to build the schema without using migrations without starting the application -5. Run `npm run dev` to run inside dev-environment (runs migrations if migrationsRun is set to true) +networks: + ff_internal: +``` + +Führen Sie dann den folgenden Befehl im Verzeichnis der compose-Datei aus, um den Container zu starten: + +```sh +docker-compose up -d +``` + +### Manuelle Installation + +Klonen Sie dieses Repository und installieren Sie die Abhängigkeiten: + +```sh +git clone https://forgejo.jk-effects.cloud/Ehrenamt/member-administration-server.git +cd member-administration-server +npm install +npm run build +npm run start +``` + +## Fragen und Wünsche + +Bei Fragen, Anregungen oder Wünschen können Sie sich gerne melden.\ +Wir freuen uns über Ihr Feedback und helfen Ihnen gerne weiter.\ +Schreiben Sie dafür eine Mail an julian.krauser@jk-effects.com. diff --git a/src/routes/index.ts b/src/routes/index.ts index eee3e7d..72cb526 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -25,13 +25,13 @@ export default (app: Express) => { app.use(cors()); app.options("*", cors()); - app.use("/public", publicAvailable); - app.use("/setup", allowSetup, setup); - app.use("/reset", reset); - app.use("/invite", invite); - app.use("/auth", auth); + app.use("/api/public", publicAvailable); + app.use("/api/setup", allowSetup, setup); + app.use("/api/reset", reset); + app.use("/api/invite", invite); + app.use("/api/auth", auth); app.use(authenticate); - app.use("/admin", admin); - app.use("/user", user); + app.use("/api/admin", admin); + app.use("/api/user", user); app.use(errorHandler); }; From 597feef9ef53837974c0c5efdb4df32cf507bd77 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Tue, 3 Dec 2024 19:11:09 +0100 Subject: [PATCH 4/7] return json or ics and provide all day entries --- src/controller/publicController.ts | 102 ++++++++++++++++++----------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/src/controller/publicController.ts b/src/controller/publicController.ts index ef675fa..1ba5134 100644 --- a/src/controller/publicController.ts +++ b/src/controller/publicController.ts @@ -4,6 +4,8 @@ import CalendarTypeService from "../service/calendarTypeService"; import { calendar } from "../entity/calendar"; import { createEvents } from "ics"; import moment from "moment"; +import InternalException from "../exceptions/internalException"; +import CalendarFactory from "../factory/admin/calendar"; /** * @description get all calendar items by types or nscdr @@ -13,6 +15,11 @@ import moment from "moment"; */ export async function getCalendarItemsByTypes(req: Request, res: Response): Promise { let types = Array.isArray(req.query.types) ? req.query.types : [req.query.types]; + let output = (req.query.output as "ics" | "json") ?? "ics"; + + if (output != "ics" && output != "json") { + throw new InternalException("set output query value to `ics` or `json` (defaults to `ics`)"); + } let items: Array = []; if (types.length == 0) { @@ -22,45 +29,62 @@ export async function getCalendarItemsByTypes(req: Request, res: Response): Prom items = await CalendarService.getByTypeNSCDR(); } - let events = createEvents( - items.map((i) => ({ - calName: process.env.CLUB_NAME, - uid: i.id, - sequence: 1, - start: moment(i.starttime) - .format("YYYY-M-D-H-m") - .split("-") - .map((a) => parseInt(a)) as [number, number, number, number, number], - end: moment(i.endtime) - .format("YYYY-M-D-H-m") - .split("-") - .map((a) => parseInt(a)) as [number, number, number, number, number], - title: i.title, - description: i.content, - location: i.location, - categories: [i.type.type], - created: moment(i.createdAt) - .format("YYYY-M-D-H-m") - .split("-") - .map((a) => parseInt(a)) as [number, number, number, number, number], - lastModified: moment(i.updatedAt) - .format("YYYY-M-D-H-m") - .split("-") - .map((a) => parseInt(a)) as [number, number, number, number, number], - transp: "OPAQUE" as "OPAQUE", - url: "https://www.ff-merching.de", - alarms: [ - { - action: "display", - description: "Erinnerung", - trigger: { - minutes: 30, - before: true, + if (output == "json") { + res.json(CalendarFactory.mapToBase(items)); + } else { + let events = createEvents( + items.map((i) => ({ + calName: process.env.CLUB_NAME, + uid: i.id, + sequence: 1, + ...(i.allDay + ? { + start: moment(i.starttime) + .format("YYYY-M-D") + .split("-") + .map((a) => parseInt(a)) as [number, number, number], + end: moment(i.endtime) + .format("YYYY-M-D") + .split("-") + .map((a) => parseInt(a)) as [number, number, number], + } + : { + start: moment(i.starttime) + .format("YYYY-M-D-H-m") + .split("-") + .map((a) => parseInt(a)) as [number, number, number, number, number], + end: moment(i.endtime) + .format("YYYY-M-D-H-m") + .split("-") + .map((a) => parseInt(a)) as [number, number, number, number, number], + }), + title: i.title, + description: i.content, + location: i.location, + categories: [i.type.type], + created: moment(i.createdAt) + .format("YYYY-M-D-H-m") + .split("-") + .map((a) => parseInt(a)) as [number, number, number, number, number], + lastModified: moment(i.updatedAt) + .format("YYYY-M-D-H-m") + .split("-") + .map((a) => parseInt(a)) as [number, number, number, number, number], + transp: "OPAQUE" as "OPAQUE", + url: "https://www.ff-merching.de", + alarms: [ + { + action: "display", + description: "Erinnerung", + trigger: { + minutes: 30, + before: true, + }, }, - }, - ], - })) - ); + ], + })) + ); - res.type("ics").send(events.value); + res.type("ics").send(events.value); + } } From 8f49533fcbe2a36d8899bccb9cf9d318e4a71b43 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Tue, 3 Dec 2024 19:29:38 +0100 Subject: [PATCH 5/7] secure type with passphrase --- src/command/calendarTypeCommand.ts | 2 ++ src/command/calendarTypeCommandHandler.ts | 2 ++ src/controller/admin/calendarController.ts | 4 ++++ src/controller/publicController.ts | 11 ++++++++-- src/data-source.ts | 2 ++ src/entity/calendarType.ts | 3 +++ src/factory/admin/calendarType.ts | 1 + .../1733249553766-securingCalendarType.ts | 20 +++++++++++++++++++ src/viewmodel/admin/calendarType.models.ts | 1 + 9 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 src/migrations/1733249553766-securingCalendarType.ts diff --git a/src/command/calendarTypeCommand.ts b/src/command/calendarTypeCommand.ts index fdab618..d796058 100644 --- a/src/command/calendarTypeCommand.ts +++ b/src/command/calendarTypeCommand.ts @@ -2,6 +2,7 @@ export interface CreateCalendarTypeCommand { type: string; nscdr: boolean; color: string; + passphrase?: string; } export interface UpdateCalendarTypeCommand { @@ -9,6 +10,7 @@ export interface UpdateCalendarTypeCommand { type: string; nscdr: boolean; color: string; + passphrase?: string; } export interface DeleteCalendarTypeCommand { diff --git a/src/command/calendarTypeCommandHandler.ts b/src/command/calendarTypeCommandHandler.ts index 0425425..223288c 100644 --- a/src/command/calendarTypeCommandHandler.ts +++ b/src/command/calendarTypeCommandHandler.ts @@ -18,6 +18,7 @@ export default abstract class CalendarTypeCommandHandler { type: createCalendarType.type, nscdr: createCalendarType.nscdr, color: createCalendarType.color, + passphrase: createCalendarType.nscdr ? null : createCalendarType.passphrase, }) .execute() .then((result) => { @@ -41,6 +42,7 @@ export default abstract class CalendarTypeCommandHandler { type: updateCalendarType.type, nscdr: updateCalendarType.nscdr, color: updateCalendarType.color, + passphrase: updateCalendarType.nscdr ? null : updateCalendarType.passphrase, }) .where("id = :id", { id: updateCalendarType.id }) .execute() diff --git a/src/controller/admin/calendarController.ts b/src/controller/admin/calendarController.ts index 0b319b1..f6a3bcd 100644 --- a/src/controller/admin/calendarController.ts +++ b/src/controller/admin/calendarController.ts @@ -101,11 +101,13 @@ export async function createCalendarType(req: Request, res: Response): Promise} @@ -22,8 +23,14 @@ export async function getCalendarItemsByTypes(req: Request, res: Response): Prom } let items: Array = []; - if (types.length == 0) { - let typeIds = await CalendarTypeService.getByTypes(types as Array); + if (types.length != 0) { + let typeIds = await CalendarTypeService.getByTypes((types as Array).map((t) => t.split(":")[0])); + typeIds = typeIds.filter( + (ti) => + ti.passphrase == null || + ti.passphrase == "" || + ti.passphrase == (types as Array).find((t) => t.includes(ti.type)).split(":")[1] + ); items = await CalendarService.getByTypes(typeIds.map((t) => t.id)); } else { items = await CalendarService.getByTypeNSCDR(); diff --git a/src/data-source.ts b/src/data-source.ts index c7fec9f..5271c7c 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -43,6 +43,7 @@ import { Calendar1729947763295 } from "./migrations/1729947763295-calendar"; import { reset } from "./entity/reset"; import { ResetToken1732358596823 } from "./migrations/1732358596823-resetToken"; import { SMSAlarming1732696919191 } from "./migrations/1732696919191-SMSAlarming"; +import { SecuringCalendarType1733249553766 } from "./migrations/1733249553766-securingCalendarType"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -96,6 +97,7 @@ const dataSource = new DataSource({ Calendar1729947763295, ResetToken1732358596823, SMSAlarming1732696919191, + SecuringCalendarType1733249553766, ], migrationsRun: true, migrationsTransactionMode: "each", diff --git a/src/entity/calendarType.ts b/src/entity/calendarType.ts index 5f696cc..0214117 100644 --- a/src/entity/calendarType.ts +++ b/src/entity/calendarType.ts @@ -15,6 +15,9 @@ export class calendarType { @Column({ type: "varchar", length: 255 }) color: string; + @Column({ type: "varchar", length: 255, nullable: true, default: null }) + passphrase: string | null; + @OneToMany(() => calendar, (c) => c.type, { nullable: false, onDelete: "RESTRICT", diff --git a/src/factory/admin/calendarType.ts b/src/factory/admin/calendarType.ts index d1f6850..25aed4e 100644 --- a/src/factory/admin/calendarType.ts +++ b/src/factory/admin/calendarType.ts @@ -13,6 +13,7 @@ export default abstract class CalendarTypeFactory { type: record.type, nscdr: record.nscdr, color: record.color, + passphrase: record.passphrase, }; } diff --git a/src/migrations/1733249553766-securingCalendarType.ts b/src/migrations/1733249553766-securingCalendarType.ts new file mode 100644 index 0000000..2b486de --- /dev/null +++ b/src/migrations/1733249553766-securingCalendarType.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; + +export class SecuringCalendarType1733249553766 implements MigrationInterface { + name = "SecuringCalendarType1733249553766"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumns("calendar_type", [ + new TableColumn({ + name: "passphrase", + type: "varchar", + length: "255", + isNullable: true, + }), + ]); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn("calendar_type", "passphrase"); + } +} diff --git a/src/viewmodel/admin/calendarType.models.ts b/src/viewmodel/admin/calendarType.models.ts index e57dcb1..54dc465 100644 --- a/src/viewmodel/admin/calendarType.models.ts +++ b/src/viewmodel/admin/calendarType.models.ts @@ -3,4 +3,5 @@ export interface CalendarTypeViewModel { type: string; nscdr: boolean; color: string; + passphrase: string | null; } From 7810f05513da1b51d33b723773b32708f11186c7 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Wed, 4 Dec 2024 18:58:31 +0100 Subject: [PATCH 6/7] query types --- src/controller/publicController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controller/publicController.ts b/src/controller/publicController.ts index 5b3769a..8c627fc 100644 --- a/src/controller/publicController.ts +++ b/src/controller/publicController.ts @@ -22,6 +22,8 @@ export async function getCalendarItemsByTypes(req: Request, res: Response): Prom throw new InternalException("set output query value to `ics` or `json` (defaults to `ics`)"); } + types = types.filter((t) => t); + let items: Array = []; if (types.length != 0) { let typeIds = await CalendarTypeService.getByTypes((types as Array).map((t) => t.split(":")[0])); From 7dbacfe795a905420e7f93a4f11908c0a833f46e Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Thu, 12 Dec 2024 15:08:47 +0100 Subject: [PATCH 7/7] 0.0.9 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index fa5d225..c2cad47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "member-administration-server", - "version": "0.0.8", + "version": "0.0.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "member-administration-server", - "version": "0.0.8", + "version": "0.0.9", "license": "GPL-3.0-only", "dependencies": { "cors": "^2.8.5", diff --git a/package.json b/package.json index a798c12..8644e07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "member-administration-server", - "version": "0.0.8", + "version": "0.0.9", "description": "Feuerwehr/Verein Mitgliederverwaltung Server", "main": "dist/index.js", "scripts": {