From 8c6a2348611a16bfc9b65cca6a8a67b81c84ace3 Mon Sep 17 00:00:00 2001 From: elweyn Date: Fri, 23 Sep 2022 16:20:41 +0200 Subject: [PATCH 01/13] Change contributions table to have deleted_by in the object. --- .../Contribution.ts | 92 +++++++++++++++++++ database/entity/Contribution.ts | 2 +- .../0049-add_delete_by_to_contributions.ts | 12 +++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 database/entity/0049-add_delete_by_to_contributions/Contribution.ts create mode 100644 database/migrations/0049-add_delete_by_to_contributions.ts diff --git a/database/entity/0049-add_delete_by_to_contributions/Contribution.ts b/database/entity/0049-add_delete_by_to_contributions/Contribution.ts new file mode 100644 index 000000000..32c6f32a3 --- /dev/null +++ b/database/entity/0049-add_delete_by_to_contributions/Contribution.ts @@ -0,0 +1,92 @@ +import Decimal from 'decimal.js-light' +import { + BaseEntity, + Column, + Entity, + PrimaryGeneratedColumn, + DeleteDateColumn, + JoinColumn, + ManyToOne, + OneToMany, +} from 'typeorm' +import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer' +import { User } from '../User' +import { ContributionMessage } from '../ContributionMessage' + +@Entity('contributions') +export class Contribution extends BaseEntity { + @PrimaryGeneratedColumn('increment', { unsigned: true }) + id: number + + @Column({ unsigned: true, nullable: false, name: 'user_id' }) + userId: number + + @ManyToOne(() => User, (user) => user.contributions) + @JoinColumn({ name: 'user_id' }) + user: User + + @Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP', name: 'created_at' }) + createdAt: Date + + @Column({ type: 'datetime', nullable: false, name: 'contribution_date' }) + contributionDate: Date + + @Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' }) + memo: string + + @Column({ + type: 'decimal', + precision: 40, + scale: 20, + nullable: false, + transformer: DecimalTransformer, + }) + amount: Decimal + + @Column({ unsigned: true, nullable: true, name: 'moderator_id' }) + moderatorId: number + + @Column({ unsigned: true, nullable: true, name: 'contribution_link_id' }) + contributionLinkId: number + + @Column({ unsigned: true, nullable: true, name: 'confirmed_by' }) + confirmedBy: number + + @Column({ nullable: true, name: 'confirmed_at' }) + confirmedAt: Date + + @Column({ unsigned: true, nullable: true, name: 'denied_by' }) + deniedBy: number + + @Column({ nullable: true, name: 'denied_at' }) + deniedAt: Date + + @Column({ + name: 'contribution_type', + length: 12, + nullable: false, + collation: 'utf8mb4_unicode_ci', + }) + contributionType: string + + @Column({ + name: 'contribution_status', + length: 12, + nullable: false, + collation: 'utf8mb4_unicode_ci', + }) + contributionStatus: string + + @Column({ unsigned: true, nullable: true, name: 'transaction_id' }) + transactionId: number + + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt: Date | null + + @DeleteDateColumn({ unsigned: true, nullable: true, name: 'deleted_by' }) + deletedBy: number + + @OneToMany(() => ContributionMessage, (message) => message.contribution) + @JoinColumn({ name: 'contribution_id' }) + messages?: ContributionMessage[] +} diff --git a/database/entity/Contribution.ts b/database/entity/Contribution.ts index f6530f00b..b6d2c36c9 100644 --- a/database/entity/Contribution.ts +++ b/database/entity/Contribution.ts @@ -1 +1 @@ -export { Contribution } from './0047-messages_tables/Contribution' +export { Contribution } from './0049-add_delete_by_to_contributions/Contribution' diff --git a/database/migrations/0049-add_delete_by_to_contributions.ts b/database/migrations/0049-add_delete_by_to_contributions.ts new file mode 100644 index 000000000..21d0eda97 --- /dev/null +++ b/database/migrations/0049-add_delete_by_to_contributions.ts @@ -0,0 +1,12 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn( + `ALTER TABLE \`contributions\` ADD COLUMN \`deleted_by\` int(10) unsigned DEFAULT NULL;`, + ) +} + +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn(`ALTER TABLE \`contributions\` DROP COLUMN \`deleted_by\`;`) +} From 8c69b94e8ed374007645076de23a6a998692403b Mon Sep 17 00:00:00 2001 From: elweyn Date: Fri, 23 Sep 2022 16:25:22 +0200 Subject: [PATCH 02/13] Throw error if moderator tries to deleted own contribution created as user. Add deletedby value. --- backend/src/graphql/resolver/AdminResolver.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index d71ffc72c..ba7baa703 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -393,12 +393,23 @@ export class AdminResolver { @Authorized([RIGHTS.ADMIN_DELETE_CONTRIBUTION]) @Mutation(() => Boolean) - async adminDeleteContribution(@Arg('id', () => Int) id: number): Promise { + async adminDeleteContribution( + @Arg('id', () => Int) id: number, + @Ctx() context: Context, + ): Promise { const contribution = await Contribution.findOne(id) if (!contribution) { throw new Error('Contribution not found for given id.') } + const moderator = getUser(context) + if ( + contribution.contributionType === ContributionType.USER && + contribution.userId === moderator.id + ) { + throw new Error('Own contribution can not be deleted as admin') + } contribution.contributionStatus = ContributionStatus.DELETED + contribution.deletedBy = moderator.id await contribution.save() const res = await contribution.softRemove() return !!res From e7b5400db2a2ad1d1c216401267f34464b4e5381 Mon Sep 17 00:00:00 2001 From: elweyn Date: Fri, 23 Sep 2022 16:49:12 +0200 Subject: [PATCH 03/13] Change the database version needed for the backend. --- backend/src/config/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 6a2ebba87..cd5183693 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -10,7 +10,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0048-add_is_moderator_to_contribution_messages', + DB_VERSION: '0049-add_delete_by_to_contributions', // '0048-add_is_moderator_to_contribution_messages', DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info From 6df8b9a83f7229eacf41afc05456dd1b64d05941 Mon Sep 17 00:00:00 2001 From: elweyn Date: Sat, 24 Sep 2022 11:32:07 +0200 Subject: [PATCH 04/13] Test that when an admin user create a contribution in FE he can not delete it in admin interface. --- .../graphql/resolver/AdminResolver.test.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/backend/src/graphql/resolver/AdminResolver.test.ts b/backend/src/graphql/resolver/AdminResolver.test.ts index 75c672bd5..57ccd788f 100644 --- a/backend/src/graphql/resolver/AdminResolver.test.ts +++ b/backend/src/graphql/resolver/AdminResolver.test.ts @@ -16,6 +16,7 @@ import { setUserRole, deleteUser, unDeleteUser, + createContribution, adminCreateContribution, adminCreateContributions, adminUpdateContribution, @@ -77,6 +78,7 @@ afterAll(async () => { let admin: User let user: User let creation: Contribution | void +let result: any describe('AdminResolver', () => { describe('set user role', () => { @@ -1360,6 +1362,38 @@ describe('AdminResolver', () => { }) }) + describe('admin deletes own user contribution', () => { + beforeAll(async () => { + await query({ + query: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + result = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test env contribution', + creationDate: new Date().toString(), + }, + }) + }) + + it('throws an error', async () => { + await expect( + mutate({ + mutation: adminDeleteContribution, + variables: { + id: result.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Own contribution can not be deleted as admin')], + }), + ) + }) + }) + describe('creation id does exist', () => { it('returns true', async () => { await expect( From f136f3c4adde15e09dea37bbb7a8a1c1b24727da Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 26 Sep 2022 08:59:46 +0200 Subject: [PATCH 05/13] Remove unused migration. --- .../0049-add_delete_by_to_contributions.ts | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 database/migrations/0049-add_delete_by_to_contributions.ts diff --git a/database/migrations/0049-add_delete_by_to_contributions.ts b/database/migrations/0049-add_delete_by_to_contributions.ts deleted file mode 100644 index 21d0eda97..000000000 --- a/database/migrations/0049-add_delete_by_to_contributions.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { - await queryFn( - `ALTER TABLE \`contributions\` ADD COLUMN \`deleted_by\` int(10) unsigned DEFAULT NULL;`, - ) -} - -export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { - await queryFn(`ALTER TABLE \`contributions\` DROP COLUMN \`deleted_by\`;`) -} From eb18bb8e7cd20cb4f1c014341281b1f2a3e747aa Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 13 Oct 2022 17:18:56 +0200 Subject: [PATCH 06/13] Add deletedBy value on deleteContribution mutation. --- backend/src/graphql/resolver/ContributionResolver.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 41af5d249..9ed6a7d82 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -70,6 +70,7 @@ export class ContributionResolver { throw new Error('A confirmed contribution can not be deleted') } contribution.contributionStatus = ContributionStatus.DELETED + contribution.deletedBy = user.id await contribution.save() const res = await contribution.softRemove() return !!res From 02f9439bd2a3c9844738b0040bd52530b55a2326 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 19 Oct 2022 19:16:21 +0200 Subject: [PATCH 07/13] correctly evaluate to false --- backend/src/config/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index b622293ad..c9543902e 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -67,7 +67,7 @@ const loginServer = { const email = { EMAIL: process.env.EMAIL === 'true' || false, - EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true' || 'false', + EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true' || false, EMAIL_TEST_RECEIVER: process.env.EMAIL_TEST_RECEIVER || 'stage1@gradido.net', EMAIL_USERNAME: process.env.EMAIL_USERNAME || 'gradido_email', EMAIL_SENDER: process.env.EMAIL_SENDER || 'info@gradido.net', From 60a9bd1f4e7430df39b938f8555786eaee95adf0 Mon Sep 17 00:00:00 2001 From: ogerly Date: Thu, 20 Oct 2022 06:20:14 +0200 Subject: [PATCH 08/13] changed text --- frontend/src/locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index a9ad47911..a9aee274d 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -34,7 +34,7 @@ "contribution": { "activity": "Tätigkeit", "alert": { - "answerQuestion": "Bitte beantworte die Nachfrage", + "answerQuestion": "Bitte beantworte die Rückfrage!", "communityNoteList": "Hier findest du alle eingereichten und bestätigten Beiträge von allen Mitgliedern aus dieser Gemeinschaft.", "confirm": "bestätigt", "in_progress": "Es gibt eine Rückfrage der Moderatoren.", From e9f40bc63811766d98d3eab0ebb55710fe1a982d Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 20 Oct 2022 09:47:59 +0200 Subject: [PATCH 09/13] fix unit test --- backend/src/mailer/sendEMail.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/mailer/sendEMail.test.ts b/backend/src/mailer/sendEMail.test.ts index a6f4bb62a..5746f1ead 100644 --- a/backend/src/mailer/sendEMail.test.ts +++ b/backend/src/mailer/sendEMail.test.ts @@ -73,7 +73,7 @@ describe('sendEMail', () => { it('calls sendMail of transporter', () => { expect((createTransport as jest.Mock).mock.results[0].value.sendMail).toBeCalledWith({ from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`, - to: `${CONFIG.EMAIL_TEST_RECEIVER}`, + to: 'receiver@mail.org', cc: 'support@gradido.net', subject: 'Subject', text: 'Text text text', From 2d7bef3e7ef4d16aa3fc5bbe9bdc315c22b8f372 Mon Sep 17 00:00:00 2001 From: ogerly Date: Thu, 20 Oct 2022 10:00:53 +0200 Subject: [PATCH 10/13] dleted contributions are displayed to the user --- backend/src/graphql/resolver/ContributionResolver.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 5cc39ed17..0741533cb 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -127,6 +127,7 @@ export class ContributionResolver { .from(dbContribution, 'c') .leftJoinAndSelect('c.messages', 'm') .where(where) + .withDeleted() .orderBy('c.createdAt', order) .limit(pageSize) .offset((currentPage - 1) * pageSize) From 39df2a4e96c35351fdfb480ad8613abf3072bccc Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 20 Oct 2022 10:21:39 +0200 Subject: [PATCH 11/13] test send email with test modus true --- backend/src/mailer/sendEMail.test.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/backend/src/mailer/sendEMail.test.ts b/backend/src/mailer/sendEMail.test.ts index 5746f1ead..8e3b0c4a2 100644 --- a/backend/src/mailer/sendEMail.test.ts +++ b/backend/src/mailer/sendEMail.test.ts @@ -29,6 +29,7 @@ describe('sendEMail', () => { let result: boolean describe('config email is false', () => { beforeEach(async () => { + jest.clearAllMocks() result = await sendEMail({ to: 'receiver@mail.org', cc: 'support@gradido.net', @@ -48,6 +49,7 @@ describe('sendEMail', () => { describe('config email is true', () => { beforeEach(async () => { + jest.clearAllMocks() CONFIG.EMAIL = true result = await sendEMail({ to: 'receiver@mail.org', @@ -84,4 +86,28 @@ describe('sendEMail', () => { expect(result).toBeTruthy() }) }) + + describe('with email EMAIL_TEST_MODUS true', () => { + beforeEach(async () => { + jest.clearAllMocks() + CONFIG.EMAIL = true + CONFIG.EMAIL_TEST_MODUS = true + result = await sendEMail({ + to: 'receiver@mail.org', + cc: 'support@gradido.net', + subject: 'Subject', + text: 'Text text text', + }) + }) + + it('calls sendMail of transporter with faked to', () => { + expect((createTransport as jest.Mock).mock.results[0].value.sendMail).toBeCalledWith({ + from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`, + to: CONFIG.EMAIL_TEST_RECEIVER, + cc: 'support@gradido.net', + subject: 'Subject', + text: 'Text text text', + }) + }) + }) }) From 886a8291c9a9a5fad5b609997314e997857b7af4 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 20 Oct 2022 10:56:19 +0200 Subject: [PATCH 12/13] release: Version 1.13.1 --- CHANGELOG.md | 8 ++++++++ admin/package.json | 2 +- backend/package.json | 2 +- database/package.json | 2 +- frontend/package.json | 2 +- package.json | 2 +- 6 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index feb54d798..d4eb48283 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,16 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [1.13.1](https://github.com/gradido/gradido/compare/1.13.0...1.13.1) + +- Fix: correctly evaluate to EMAIL_TEST_MODE to false [`#2273`](https://github.com/gradido/gradido/pull/2273) +- Refactor: Contribution resolver logs and events [`#2231`](https://github.com/gradido/gradido/pull/2231) + #### [1.13.0](https://github.com/gradido/gradido/compare/1.12.1...1.13.0) +> 18 October 2022 + +- release: Version 1.13.0 [`#2269`](https://github.com/gradido/gradido/pull/2269) - fix: Linked User Email in Transaction List [`#2268`](https://github.com/gradido/gradido/pull/2268) - concept capturing alias [`#2148`](https://github.com/gradido/gradido/pull/2148) - fix: 🍰 Daily Redeem Of Contribution Link [`#2265`](https://github.com/gradido/gradido/pull/2265) diff --git a/admin/package.json b/admin/package.json index 3d592361e..2db889771 100644 --- a/admin/package.json +++ b/admin/package.json @@ -3,7 +3,7 @@ "description": "Administraion Interface for Gradido", "main": "index.js", "author": "Moriz Wahl", - "version": "1.13.0", + "version": "1.13.1", "license": "Apache-2.0", "private": false, "scripts": { diff --git a/backend/package.json b/backend/package.json index 57516263c..6cd886735 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "gradido-backend", - "version": "1.13.0", + "version": "1.13.1", "description": "Gradido unified backend providing an API-Service for Gradido Transactions", "main": "src/index.ts", "repository": "https://github.com/gradido/gradido/backend", diff --git a/database/package.json b/database/package.json index 9341b6445..0a97b5135 100644 --- a/database/package.json +++ b/database/package.json @@ -1,6 +1,6 @@ { "name": "gradido-database", - "version": "1.13.0", + "version": "1.13.1", "description": "Gradido Database Tool to execute database migrations", "main": "src/index.ts", "repository": "https://github.com/gradido/gradido/database", diff --git a/frontend/package.json b/frontend/package.json index 0c1cd305c..2fc892f9f 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "bootstrap-vue-gradido-wallet", - "version": "1.13.0", + "version": "1.13.1", "private": true, "scripts": { "start": "node run/server.js", diff --git a/package.json b/package.json index 98f256324..f306a1e06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gradido", - "version": "1.13.0", + "version": "1.13.1", "description": "Gradido", "main": "index.js", "repository": "git@github.com:gradido/gradido.git", From a4bf122bf4855dcfdaabda249db9d85d21a9907c Mon Sep 17 00:00:00 2001 From: ogerly Date: Thu, 20 Oct 2022 11:46:19 +0200 Subject: [PATCH 13/13] change E-Mail Subject --- backend/src/mailer/sendAddedContributionMessageEmail.test.ts | 2 +- backend/src/mailer/text/contributionMessageReceived.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/mailer/sendAddedContributionMessageEmail.test.ts b/backend/src/mailer/sendAddedContributionMessageEmail.test.ts index 1151a0abc..bed8f6214 100644 --- a/backend/src/mailer/sendAddedContributionMessageEmail.test.ts +++ b/backend/src/mailer/sendAddedContributionMessageEmail.test.ts @@ -26,7 +26,7 @@ describe('sendAddedContributionMessageEmail', () => { it('calls sendEMail', () => { expect(sendEMail).toBeCalledWith({ to: `Bibi Bloxberg `, - subject: 'Gradido Frage zur Schöpfung', + subject: 'Rückfrage zu Deinem Gemeinwohl-Beitrag', text: expect.stringContaining('Hallo Bibi Bloxberg') && expect.stringContaining('Peter Lustig') && diff --git a/backend/src/mailer/text/contributionMessageReceived.ts b/backend/src/mailer/text/contributionMessageReceived.ts index b0c9c4d30..af1cabb9f 100644 --- a/backend/src/mailer/text/contributionMessageReceived.ts +++ b/backend/src/mailer/text/contributionMessageReceived.ts @@ -1,6 +1,6 @@ export const contributionMessageReceived = { de: { - subject: 'Gradido Frage zur Schöpfung', + subject: 'Rückfrage zu Deinem Gemeinwohl-Beitrag', text: (data: { senderFirstName: string senderLastName: string