From fc694aa976b5b5bb354b744fcf0e30b4b691de0a Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Fri, 31 Jan 2025 11:40:59 +0100 Subject: [PATCH] change: newsletter saving --- .../club/member/communicationCommand.ts | 2 + .../member/communicationCommandHandler.ts | 98 ++++++++++++------- src/command/club/member/memberCommand.ts | 5 - .../club/member/memberCommandHandler.ts | 52 +--------- src/controller/admin/club/memberController.ts | 29 +----- src/data-source.ts | 2 + src/entity/club/member/communication.ts | 3 + src/entity/club/member/member.ts | 9 +- .../admin/club/member/communication.ts | 2 +- .../1737816852011-moveSendNewsletterFlag.ts | 71 ++++++++++++++ .../club/member/communicationService.ts | 2 - src/service/club/member/memberService.ts | 30 ++++-- 12 files changed, 173 insertions(+), 132 deletions(-) create mode 100644 src/migrations/1737816852011-moveSendNewsletterFlag.ts diff --git a/src/command/club/member/communicationCommand.ts b/src/command/club/member/communicationCommand.ts index f0fdd5a..cc80442 100644 --- a/src/command/club/member/communicationCommand.ts +++ b/src/command/club/member/communicationCommand.ts @@ -1,6 +1,7 @@ export interface CreateCommunicationCommand { preferred: boolean; isSMSAlarming: boolean; + isSendNewsletter: boolean; mobile: string; email: string; postalCode: string; @@ -16,6 +17,7 @@ export interface UpdateCommunicationCommand { id: number; preferred: boolean; isSMSAlarming: boolean; + isSendNewsletter: boolean; mobile: string; email: string; postalCode: string; diff --git a/src/command/club/member/communicationCommandHandler.ts b/src/command/club/member/communicationCommandHandler.ts index ad6341c..24402fc 100644 --- a/src/command/club/member/communicationCommandHandler.ts +++ b/src/command/club/member/communicationCommandHandler.ts @@ -14,26 +14,44 @@ export default abstract class CommunicationCommandHandler { * @returns {Promise} */ static async create(createCommunication: CreateCommunicationCommand): Promise { + let insertId = -1; return await dataSource - .createQueryBuilder() - .insert() - .into(communication) - .values({ - preferred: createCommunication.preferred, - isSMSAlarming: createCommunication.isSMSAlarming, - mobile: createCommunication.mobile, - email: createCommunication.email, - postalCode: createCommunication.postalCode, - city: createCommunication.city, - street: createCommunication.street, - streetNumber: createCommunication.streetNumber, - streetNumberAddition: createCommunication.streetNumberAddition, - memberId: createCommunication.memberId, - typeId: createCommunication.typeId, + .transaction(async (manager) => { + await manager + .createQueryBuilder() + .insert() + .into(communication) + .values({ + preferred: createCommunication.preferred, + isSMSAlarming: createCommunication.isSMSAlarming, + isSendNewsletter: createCommunication.isSendNewsletter, + mobile: createCommunication.mobile, + email: createCommunication.email, + postalCode: createCommunication.postalCode, + city: createCommunication.city, + street: createCommunication.street, + streetNumber: createCommunication.streetNumber, + 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(); }) - .execute() - .then((result) => { - return result.identifiers[0].id; + .then(() => { + return insertId; }) .catch((err) => { throw new InternalException("Failed creating communication", err); @@ -47,22 +65,36 @@ export default abstract class CommunicationCommandHandler { */ static async update(updateCommunication: UpdateCommunicationCommand): Promise { return await dataSource - .createQueryBuilder() - .update(communication) - .set({ - preferred: updateCommunication.preferred, - isSMSAlarming: updateCommunication.isSMSAlarming, - mobile: updateCommunication.mobile, - email: updateCommunication.email, - postalCode: updateCommunication.postalCode, - city: updateCommunication.city, - street: updateCommunication.street, - streetNumber: updateCommunication.streetNumber, - streetNumberAddition: updateCommunication.streetNumberAddition, + .transaction(async (manager) => { + await manager + .createQueryBuilder() + .update(communication) + .set({ + preferred: updateCommunication.preferred, + isSMSAlarming: updateCommunication.isSMSAlarming, + isSendNewsletter: updateCommunication.isSendNewsletter, + mobile: updateCommunication.mobile, + email: updateCommunication.email, + postalCode: updateCommunication.postalCode, + city: updateCommunication.city, + 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(() => {}) .catch((err) => { throw new InternalException("Failed updating communication", err); diff --git a/src/command/club/member/memberCommand.ts b/src/command/club/member/memberCommand.ts index 126b87d..90ecee0 100644 --- a/src/command/club/member/memberCommand.ts +++ b/src/command/club/member/memberCommand.ts @@ -17,11 +17,6 @@ export interface UpdateMemberCommand { internalId?: string; } -export interface UpdateMemberNewsletterCommand { - id: number; - communicationId: number; -} - export interface DeleteMemberCommand { id: number; } diff --git a/src/command/club/member/memberCommandHandler.ts b/src/command/club/member/memberCommandHandler.ts index 569e835..54862f7 100644 --- a/src/command/club/member/memberCommandHandler.ts +++ b/src/command/club/member/memberCommandHandler.ts @@ -2,12 +2,7 @@ import { dataSource } from "../../../data-source"; import { communication } from "../../../entity/club/member/communication"; import { member } from "../../../entity/club/member/member"; import InternalException from "../../../exceptions/internalException"; -import { - CreateMemberCommand, - DeleteMemberCommand, - UpdateMemberCommand, - UpdateMemberNewsletterCommand, -} from "./memberCommand"; +import { CreateMemberCommand, DeleteMemberCommand, UpdateMemberCommand } from "./memberCommand"; export default abstract class MemberCommandHandler { /** @@ -68,51 +63,6 @@ export default abstract class MemberCommandHandler { }); } - /** - * @description update member newsletter - * @param {UpdateMemberCommand} updateMember - * @returns {Promise} - */ - static async updateNewsletter(updateMember: UpdateMemberNewsletterCommand): Promise { - 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} - */ - 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} deleteMember diff --git a/src/controller/admin/club/memberController.ts b/src/controller/admin/club/memberController.ts index 18c9cf5..2e8698a 100644 --- a/src/controller/admin/club/memberController.ts +++ b/src/controller/admin/club/memberController.ts @@ -15,7 +15,6 @@ import { CreateMemberCommand, DeleteMemberCommand, UpdateMemberCommand, - UpdateMemberNewsletterCommand, } from "../../../command/club/member/memberCommand"; import MemberCommandHandler from "../../../command/club/member/memberCommandHandler"; import { @@ -290,7 +289,7 @@ export async function createMember(req: Request, res: Response): Promise { const lastname = req.body.lastname; const nameaffix = req.body.nameaffix; const birthdate = req.body.birthdate; - const internalId = req.body.internalId || null; + const internalId = req.body.internalId; let createMember: CreateMemberCommand = { salutationId, @@ -409,6 +408,7 @@ export async function addCommunicationToMember(req: Request, res: Response): Pro const memberId = parseInt(req.params.memberId); const preferred = req.body.preferred; const isSMSAlarming = req.body.isSMSAlarming; + const isSendNewsletter = req.body.isNewsletterMain; const mobile = req.body.mobile; const email = req.body.email; const postalCode = req.body.postalCode; @@ -417,11 +417,11 @@ export async function addCommunicationToMember(req: Request, res: Response): Pro const streetNumber = req.body.streetNumber; const streetNumberAddition = req.body.streetNumberAddition; const typeId = req.body.typeId; - const isNewsletterMain = req.body.isNewsletterMain; let createCommunication: CreateCommunicationCommand = { preferred, isSMSAlarming, + isSendNewsletter, mobile, email, postalCode, @@ -434,14 +434,6 @@ export async function addCommunicationToMember(req: Request, res: Response): Pro }; let id = await CommunicationCommandHandler.create(createCommunication); - if (isNewsletterMain) { - let updateNewsletter: UpdateMemberNewsletterCommand = { - id: memberId, - communicationId: id, - }; - await MemberCommandHandler.updateNewsletter(updateNewsletter); - } - res.sendStatus(204); } @@ -595,6 +587,7 @@ export async function updateCommunicationOfMember(req: Request, res: Response): const recordId = parseInt(req.params.recordId); const preferred = req.body.preferred; const isSMSAlarming = req.body.isSMSAlarming; + const isSendNewsletter = req.body.isNewsletterMain; const mobile = req.body.mobile; const email = req.body.email; const postalCode = req.body.postalCode; @@ -602,12 +595,12 @@ export async function updateCommunicationOfMember(req: Request, res: Response): const street = req.body.street; const streetNumber = req.body.streetNumber; const streetNumberAddition = req.body.streetNumberAddition; - const isNewsletterMain = req.body.isNewsletterMain; let updateCommunication: UpdateCommunicationCommand = { id: recordId, preferred, isSMSAlarming, + isSendNewsletter, mobile, email, postalCode, @@ -619,18 +612,6 @@ 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 c016ca4..851997f 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -74,6 +74,7 @@ import { AddWebapiTokens1737453096674 } from "./migrations/1737453096674-addweba 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({ type: DB_TYPE as any, @@ -158,6 +159,7 @@ const dataSource = new DataSource({ AddWebapiTokens1737453096674, SalutationAsTable1737796878058, UpdateViews1737800468938, + MoveSendNewsletterFlag1737816852011, ], migrationsRun: true, migrationsTransactionMode: "each", diff --git a/src/entity/club/member/communication.ts b/src/entity/club/member/communication.ts index f50d1a9..c3c79fc 100644 --- a/src/entity/club/member/communication.ts +++ b/src/entity/club/member/communication.ts @@ -13,6 +13,9 @@ export class communication { @Column({ type: "boolean", default: false }) isSMSAlarming: boolean; + @Column({ type: "boolean", default: false }) + isSendNewsletter: boolean; + @Column({ type: "varchar", length: 255, nullable: true }) mobile: string; diff --git a/src/entity/club/member/member.ts b/src/entity/club/member/member.ts index e5b27c2..3557d58 100644 --- a/src/entity/club/member/member.ts +++ b/src/entity/club/member/member.ts @@ -32,14 +32,6 @@ export class member { @OneToMany(() => communication, (communications) => communications.member) communications: communication[]; - @OneToOne(() => communication, { - nullable: true, - onDelete: "SET NULL", - onUpdate: "RESTRICT", - }) - @JoinColumn() - sendNewsletter?: communication; - @ManyToOne(() => salutation, (salutation) => salutation.members) salutation: salutation; @@ -59,4 +51,5 @@ export class member { lastMembershipEntry?: membership; preferredCommunication?: Array; smsAlarming?: Array; + sendNewsletter?: communication; } diff --git a/src/factory/admin/club/member/communication.ts b/src/factory/admin/club/member/communication.ts index 702cfc5..127ab82 100644 --- a/src/factory/admin/club/member/communication.ts +++ b/src/factory/admin/club/member/communication.ts @@ -20,7 +20,7 @@ export default abstract class CommunicationFactory { streetNumber: record.streetNumber, streetNumberAddition: record.streetNumberAddition, type: CommunicationTypeFactory.mapToSingle(record.type), - isNewsletterMain: isMain ? isMain : record?.member?.sendNewsletter?.id == record.id, + isNewsletterMain: record?.isSendNewsletter, isSMSAlarming: record.isSMSAlarming, }; } diff --git a/src/migrations/1737816852011-moveSendNewsletterFlag.ts b/src/migrations/1737816852011-moveSendNewsletterFlag.ts new file mode 100644 index 0000000..d294337 --- /dev/null +++ b/src/migrations/1737816852011-moveSendNewsletterFlag.ts @@ -0,0 +1,71 @@ +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 { + 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 { + 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"); + } +} diff --git a/src/service/club/member/communicationService.ts b/src/service/club/member/communicationService.ts index 5ca7bdd..b828bc7 100644 --- a/src/service/club/member/communicationService.ts +++ b/src/service/club/member/communicationService.ts @@ -14,7 +14,6 @@ export default abstract class CommunicationService { .createQueryBuilder("communication") .leftJoinAndSelect("communication.type", "communicationType") .leftJoinAndSelect("communication.member", "member") - .leftJoinAndSelect("member.sendNewsletter", "sendNewsletter") .where("communication.memberId = :memberId", { memberId: memberId }) .orderBy("communicationType.type", "ASC") .getMany() @@ -38,7 +37,6 @@ export default abstract class CommunicationService { .createQueryBuilder("communication") .leftJoinAndSelect("communication.type", "communicationType") .leftJoinAndSelect("communication.member", "member") - .leftJoinAndSelect("member.sendNewsletter", "sendNewsletter") .where("communication.memberId = :memberId", { memberId: memberId }) .andWhere("communication.id = :recordId", { recordId: recordId }) .getOneOrFail() diff --git a/src/service/club/member/memberService.ts b/src/service/club/member/memberService.ts index 9b289d9..53a2aaa 100644 --- a/src/service/club/member/memberService.ts +++ b/src/service/club/member/memberService.ts @@ -39,8 +39,6 @@ export default abstract class MemberService { ) .leftJoinAndSelect("membership_first.status", "status_first") .leftJoinAndSelect("membership_last.status", "status_last") - .leftJoinAndSelect("member.sendNewsletter", "sendNewsletter") - .leftJoinAndSelect("sendNewsletter.type", "communicationtype") .leftJoinAndMapMany( "member.preferredCommunication", "member.communications", @@ -48,6 +46,13 @@ export default abstract class MemberService { "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") .leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming") .leftJoinAndSelect("member.salutation", "salutation"); @@ -69,7 +74,7 @@ export default abstract class MemberService { } if (ids.length != 0) { - query = query.where("member.id IN (:...ids)", { ids: ids }); + query = query.where({ id: ids }); } if (!noLimit) { @@ -112,18 +117,22 @@ export default abstract class MemberService { ) .leftJoinAndSelect("membership_first.status", "status_first") .leftJoinAndSelect("membership_last.status", "status_last") - .leftJoinAndSelect("member.sendNewsletter", "sendNewsletter") - .leftJoinAndSelect("sendNewsletter.type", "communicationtype") .leftJoinAndMapMany( "member.preferredCommunication", "member.communications", "preferredCommunication", "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") .leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming") - .leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred") .leftJoinAndSelect("member.salutation", "salutation") .where("member.id = :id", { id: id }) .getOneOrFail() @@ -185,7 +194,12 @@ export default abstract class MemberService { return await dataSource .getRepository(member) .createQueryBuilder("member") - .leftJoinAndSelect("member.sendNewsletter", "sendNewsletter") + .leftJoinAndMapOne( + "member.sendNewsletter", + "member.communications", + "sendNewsletter", + "sendNewsletter.isSendNewsletter = 1" + ) .where("member.id = :id", { id: id }) .getOneOrFail() .then((res) => {