From a58698e0fb2e4ac9ce1b5bbfd806af6aab2a52b8 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 24 Nov 2022 20:58:59 +0100 Subject: [PATCH 01/20] add fake mail server to docker.compose files for development and testing --- docker-compose.override.yml | 11 +++++++++++ docker-compose.test.yml | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/docker-compose.override.yml b/docker-compose.override.yml index fe2f68a8d..c69c68b50 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -112,6 +112,17 @@ services: volumes: - /sessions + ######################################################## + # MAILSERVER TO FAKE SMTP ############################## + ######################################################## + mailserver: + image: maildev/maildev + ports: + - 1080:1080 + - 1025:1025 + networks: + - external-net + volumes: frontend_node_modules: admin_node_modules: diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 790bd468d..84647ef03 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -81,6 +81,17 @@ services: nginx: image: gradido/nginx:test + ######################################################## + # MAILSERVER TO FAKE SMTP ############################## + ######################################################## + mailserver: + image: maildev/maildev + ports: + - 1080:1080 + - 1025:1025 + networks: + - external-net + networks: external-net: internal-net: From e60b455a7f2751042e62f8afacf3a9b48d0e9e21 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 24 Nov 2022 21:14:14 +0100 Subject: [PATCH 02/20] configure backend to use the fake smtp server by default --- backend/src/config/index.ts | 12 +++++++----- backend/src/mailer/sendEMail.ts | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 26227b90d..f65a0f28b 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -66,14 +66,16 @@ const loginServer = { } const email = { - EMAIL: process.env.EMAIL === 'true' || false, + // eslint-disable-next-line no-unneeded-ternary + EMAIL: process.env.EMAIL === 'false' ? false : true, 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_USERNAME: process.env.EMAIL_USERNAME || 'null', EMAIL_SENDER: process.env.EMAIL_SENDER || 'info@gradido.net', - EMAIL_PASSWORD: process.env.EMAIL_PASSWORD || 'xxx', - EMAIL_SMTP_URL: process.env.EMAIL_SMTP_URL || 'gmail.com', - EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '587', + EMAIL_PASSWORD: process.env.EMAIL_PASSWORD || 'null', + EMAIL_SMTP_URL: process.env.EMAIL_SMTP_URL || 'mailserver', + EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '1025', + EMAIL_TLS: process.env.EMAIL_TLS === 'true' || false, EMAIL_LINK_VERIFICATION: process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/checkEmail/{optin}{code}', EMAIL_LINK_SETPASSWORD: diff --git a/backend/src/mailer/sendEMail.ts b/backend/src/mailer/sendEMail.ts index 00282f232..b167cee2c 100644 --- a/backend/src/mailer/sendEMail.ts +++ b/backend/src/mailer/sendEMail.ts @@ -29,7 +29,7 @@ export const sendEMail = async (emailDef: { host: CONFIG.EMAIL_SMTP_URL, port: Number(CONFIG.EMAIL_SMTP_PORT), secure: false, // true for 465, false for other ports - requireTLS: true, + requireTLS: CONFIG.EMAIL_TLS, auth: { user: CONFIG.EMAIL_USERNAME, pass: CONFIG.EMAIL_PASSWORD, From 04514f421bc36aecd2a0cdd07c50c5c85185f049 Mon Sep 17 00:00:00 2001 From: mahula Date: Fri, 25 Nov 2022 11:53:36 +0100 Subject: [PATCH 03/20] adapt mailer unit tests to config changes --- backend/src/mailer/sendEMail.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/mailer/sendEMail.test.ts b/backend/src/mailer/sendEMail.test.ts index e062b71d8..8fa8b0d7b 100644 --- a/backend/src/mailer/sendEMail.test.ts +++ b/backend/src/mailer/sendEMail.test.ts @@ -9,6 +9,7 @@ CONFIG.EMAIL_SMTP_URL = 'EMAIL_SMTP_URL' CONFIG.EMAIL_SMTP_PORT = '1234' CONFIG.EMAIL_USERNAME = 'user' CONFIG.EMAIL_PASSWORD = 'pwd' +CONFIG.EMAIL_TLS = true jest.mock('nodemailer', () => { return { From 47f86b3faa8798fd230409133a3da2a737a41984 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 30 Nov 2022 13:33:28 +0100 Subject: [PATCH 04/20] consider node env for sending emails --- 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 f65a0f28b..8ffec33ef 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -67,7 +67,7 @@ const loginServer = { const email = { // eslint-disable-next-line no-unneeded-ternary - EMAIL: process.env.EMAIL === 'false' ? false : true, + EMAIL: process.env.EMAIL === 'false' ? false : process.env.NODE_ENV !== 'development', 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 || 'null', From 881e38f22b168a0533d61f211b63dcc2c51a8260 Mon Sep 17 00:00:00 2001 From: mahula Date: Fri, 16 Dec 2022 13:05:57 +0100 Subject: [PATCH 05/20] set email disabled and tls enabled as defaults in backend config --- backend/src/config/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 6ae6e24f0..76a87976a 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -66,8 +66,7 @@ const loginServer = { } const email = { - // eslint-disable-next-line no-unneeded-ternary - EMAIL: process.env.EMAIL === 'false' ? false : process.env.NODE_ENV !== 'development', + EMAIL: process.env.EMAIL === '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 || 'null', @@ -75,7 +74,8 @@ const email = { EMAIL_PASSWORD: process.env.EMAIL_PASSWORD || 'null', EMAIL_SMTP_URL: process.env.EMAIL_SMTP_URL || 'mailserver', EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '1025', - EMAIL_TLS: process.env.EMAIL_TLS === 'true' || false, + // eslint-disable-next-line no-unneeded-ternary + EMAIL_TLS: process.env.EMAIL_TLS === 'false' ? false : true, EMAIL_LINK_VERIFICATION: process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/checkEmail/{optin}{code}', EMAIL_LINK_SETPASSWORD: From 36204195656cc142a3d6ecbad921d5407d3e88cc Mon Sep 17 00:00:00 2001 From: mahula Date: Fri, 16 Dec 2022 13:53:19 +0100 Subject: [PATCH 06/20] adapt emailer config to config changes --- backend/src/emails/sendEmailTranslated.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/emails/sendEmailTranslated.ts b/backend/src/emails/sendEmailTranslated.ts index 69008c00e..5652b3424 100644 --- a/backend/src/emails/sendEmailTranslated.ts +++ b/backend/src/emails/sendEmailTranslated.ts @@ -41,7 +41,7 @@ export const sendEmailTranslated = async (params: { host: CONFIG.EMAIL_SMTP_URL, port: Number(CONFIG.EMAIL_SMTP_PORT), secure: false, // true for 465, false for other ports - requireTLS: true, + requireTLS: CONFIG.EMAIL_TLS, auth: { user: CONFIG.EMAIL_USERNAME, pass: CONFIG.EMAIL_PASSWORD, From 1e265b1078c10cfb2a7aeaebeaecbca4e6983780 Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 10 Jan 2023 11:07:16 +0100 Subject: [PATCH 07/20] set email username and password to empty string in backend config --- backend/src/config/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 22a509080..6258eb36a 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -69,9 +69,9 @@ const email = { EMAIL: process.env.EMAIL === '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 || 'null', + EMAIL_USERNAME: process.env.EMAIL_USERNAME || '', EMAIL_SENDER: process.env.EMAIL_SENDER || 'info@gradido.net', - EMAIL_PASSWORD: process.env.EMAIL_PASSWORD || 'null', + EMAIL_PASSWORD: process.env.EMAIL_PASSWORD || '', EMAIL_SMTP_URL: process.env.EMAIL_SMTP_URL || 'mailserver', EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '1025', // eslint-disable-next-line no-unneeded-ternary From 34014affa09c5774b80cad3460d0767b566d62b7 Mon Sep 17 00:00:00 2001 From: ogerly Date: Tue, 10 Jan 2023 12:20:55 +0100 Subject: [PATCH 08/20] send gdd and send link gdd is running --- .../GddSend/TransactionConfirmationLink.vue | 2 +- .../GddSend/TransactionConfirmationSend.vue | 2 +- frontend/src/components/GddSend/TransactionForm.vue | 2 +- .../components/GddSend/TransactionResultLink.vue | 2 +- .../GddSend/TransactionResultSendSuccess.vue | 2 +- frontend/src/pages/Send.vue | 13 +++++++------ 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/GddSend/TransactionConfirmationLink.vue b/frontend/src/components/GddSend/TransactionConfirmationLink.vue index 0fcb94459..91fea3486 100644 --- a/frontend/src/components/GddSend/TransactionConfirmationLink.vue +++ b/frontend/src/components/GddSend/TransactionConfirmationLink.vue @@ -37,7 +37,7 @@ - {{ $t('back') }} + {{ $t('back') }} - {{ $t('back') }} + {{ $t('back') }}
- + {{ $t('form.close') }}
diff --git a/frontend/src/components/GddSend/TransactionResultSendSuccess.vue b/frontend/src/components/GddSend/TransactionResultSendSuccess.vue index 36ce3f4e6..b57196db4 100644 --- a/frontend/src/components/GddSend/TransactionResultSendSuccess.vue +++ b/frontend/src/components/GddSend/TransactionResultSendSuccess.vue @@ -6,7 +6,7 @@ {{ $t('form.send_transaction_success') }}
- {{ $t('form.close') }} + {{ $t('form.close') }}
diff --git a/frontend/src/pages/Send.vue b/frontend/src/pages/Send.vue index 7131a7247..370943ad6 100644 --- a/frontend/src/pages/Send.vue +++ b/frontend/src/pages/Send.vue @@ -15,7 +15,7 @@ :amount="transactionData.amount" :memo="transactionData.memo" @send-transaction="sendTransaction" - @on-reset="onReset" + @on-back="onBack" > @@ -169,8 +169,9 @@ export default { } this.loading = false }, - onReset() { + onBack() { this.currentTransactionStep = TRANSACTION_STEPS.transactionForm + this.$mount() }, updateTransactions(pagination) { this.$emit('update-transactions', pagination) From 775240d13c68a72e8b36248fa2537e0032cf79c4 Mon Sep 17 00:00:00 2001 From: ogerly Date: Tue, 10 Jan 2023 13:58:34 +0100 Subject: [PATCH 09/20] fix test --- frontend/src/components/GddSend/TransactionForm.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/GddSend/TransactionForm.spec.js b/frontend/src/components/GddSend/TransactionForm.spec.js index 213301fda..59537dc47 100644 --- a/frontend/src/components/GddSend/TransactionForm.spec.js +++ b/frontend/src/components/GddSend/TransactionForm.spec.js @@ -322,7 +322,7 @@ Die ganze Welt bezwingen.“`) [ { email: 'someone@watches.tv', - amount: '87.23', + amount: 87.23, memo: 'Long enough', selected: 'send', }, From c0b3a93a59138c39a11abcdff6c6198a9461fdce Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 11 Jan 2023 12:35:22 +0100 Subject: [PATCH 10/20] calculate last transaction correctly --- backend/src/graphql/resolver/BalanceResolver.ts | 2 +- backend/src/graphql/resolver/TransactionResolver.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/BalanceResolver.ts b/backend/src/graphql/resolver/BalanceResolver.ts index a0016e8f2..26f9cd656 100644 --- a/backend/src/graphql/resolver/BalanceResolver.ts +++ b/backend/src/graphql/resolver/BalanceResolver.ts @@ -32,7 +32,7 @@ export class BalanceResolver { const lastTransaction = context.lastTransaction ? context.lastTransaction - : await dbTransaction.findOne({ userId: user.id }, { order: { balanceDate: 'DESC' } }) + : await dbTransaction.findOne({ userId: user.id }, { order: { id: 'DESC' } }) logger.debug(`lastTransaction=${lastTransaction}`) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 0ac5b382e..33914583e 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -211,7 +211,7 @@ export class TransactionResolver { // find current balance const lastTransaction = await dbTransaction.findOne( { userId: user.id }, - { order: { balanceDate: 'DESC' }, relations: ['contribution'] }, + { order: { id: 'DESC' }, relations: ['contribution'] }, ) logger.debug(`lastTransaction=${lastTransaction}`) From c585b2c234e1c577d1ce47d006e72e2b95f04bbb Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 11 Jan 2023 13:08:47 +0100 Subject: [PATCH 11/20] check for confirmed transaction within semaphore lock --- .../src/graphql/resolver/ContributionResolver.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 2587aab61..4c78ae1e7 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -553,11 +553,15 @@ export class ContributionResolver { @Arg('id', () => Int) id: number, @Ctx() context: Context, ): Promise { + // acquire lock + const releaseLock = await TRANSACTIONS_LOCK.acquire() + const clientTimezoneOffset = getClientTimezoneOffset(context) - const contribution = await DbContribution.findOne(id) + + const contribution = await DbContribution.findOne({ id, confirmedAt: IsNull() }) if (!contribution) { - logger.error(`Contribution not found for given id: ${id}`) - throw new Error('Contribution not found to given id.') + logger.error(`Contribution not found to given id (${id}) or already confirmed`) + throw new Error('Contribution not found to given id or already confirmed.') } const moderatorUser = getUser(context) if (moderatorUser.id === contribution.userId) { @@ -580,9 +584,6 @@ export class ContributionResolver { clientTimezoneOffset, ) - // acquire lock - const releaseLock = await TRANSACTIONS_LOCK.acquire() - const receivedCallDate = new Date() const queryRunner = getConnection().createQueryRunner() await queryRunner.connect() From c9488c8b8168152f5e49778d930c9ea9aa96a060 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 11 Jan 2023 13:11:40 +0100 Subject: [PATCH 12/20] try to minimize changeset --- backend/src/graphql/resolver/ContributionResolver.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 4c78ae1e7..ef273a60b 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -557,9 +557,8 @@ export class ContributionResolver { const releaseLock = await TRANSACTIONS_LOCK.acquire() const clientTimezoneOffset = getClientTimezoneOffset(context) - const contribution = await DbContribution.findOne({ id, confirmedAt: IsNull() }) - if (!contribution) { + if (!contribution) {P logger.error(`Contribution not found to given id (${id}) or already confirmed`) throw new Error('Contribution not found to given id or already confirmed.') } From 8628a05a219661ac33627eef5d0b43533a7ca6e2 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 11 Jan 2023 13:14:50 +0100 Subject: [PATCH 13/20] properly use findOne where --- backend/src/graphql/resolver/ContributionResolver.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index ef273a60b..f83202e1c 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -557,8 +557,8 @@ export class ContributionResolver { const releaseLock = await TRANSACTIONS_LOCK.acquire() const clientTimezoneOffset = getClientTimezoneOffset(context) - const contribution = await DbContribution.findOne({ id, confirmedAt: IsNull() }) - if (!contribution) {P + const contribution = await DbContribution.findOne({ where: { id, confirmedAt: IsNull() } }) + if (!contribution) { logger.error(`Contribution not found to given id (${id}) or already confirmed`) throw new Error('Contribution not found to given id or already confirmed.') } From 23ce6f62c11905f8dcf2901d95eedd7960c0e950 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 11 Jan 2023 13:26:29 +0100 Subject: [PATCH 14/20] fix(other): update browser list --- admin/yarn.lock | 6 +++--- backend/yarn.lock | 6 +++--- frontend/yarn.lock | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/admin/yarn.lock b/admin/yarn.lock index 7507f2559..5d8275b1a 100644 --- a/admin/yarn.lock +++ b/admin/yarn.lock @@ -4141,9 +4141,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001271: - version "1.0.30001354" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001354.tgz" - integrity sha512-mImKeCkyGDAHNywYFA4bqnLAzTUvVkqPvhY4DV47X+Gl2c5Z8c3KNETnXp14GQt11LvxE8AwjzGxJ+rsikiOzg== + version "1.0.30001442" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz" + integrity sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow== capture-exit@^2.0.0: version "2.0.0" diff --git a/backend/yarn.lock b/backend/yarn.lock index 82bcd6b1f..264cbd409 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1913,9 +1913,9 @@ camelcase@^6.2.0: integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== caniuse-lite@^1.0.30001264: - version "1.0.30001418" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz" - integrity sha512-oIs7+JL3K9JRQ3jPZjlH6qyYDp+nBTCais7hjh0s+fuBwufc7uZ7hPYMXrDOJhV360KGMTcczMRObk0/iMqZRg== + version "1.0.30001442" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz" + integrity sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow== chacha20-universal@^1.0.4: version "1.0.4" diff --git a/frontend/yarn.lock b/frontend/yarn.lock index ff3a7ff1a..f374ac7bd 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -4578,9 +4578,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001181, caniuse-lite@^1.0.30001280, caniuse-lite@^1.0.30001286: - version "1.0.30001439" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz" - integrity sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A== + version "1.0.30001442" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz" + integrity sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow== capture-exit@^2.0.0: version "2.0.0" From 340554b8150b24946ebd34b49813895c61514ba5 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 11 Jan 2023 15:39:33 +0100 Subject: [PATCH 15/20] skipped alot of test due to unknown timeout errors --- .../resolver/ContributionResolver.test.ts | 38 ++++++++++++------- .../graphql/resolver/ContributionResolver.ts | 2 +- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 3dfd09bb5..77e6e266c 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -414,7 +414,7 @@ describe('ContributionResolver', () => { resetToken() }) - describe('wrong contribution id', () => { + describe.skip('wrong contribution id', () => { it('throws an error', async () => { jest.clearAllMocks() await expect( @@ -429,13 +429,17 @@ describe('ContributionResolver', () => { }), ).resolves.toEqual( expect.objectContaining({ - errors: [new GraphQLError('No contribution found to given id.')], + errors: [ + new GraphQLError('Contribution not found to given id or already confirmed.'), + ], }), ) }) it('logs the error found', () => { - expect(logger.error).toBeCalledWith('No contribution found to given id') + expect(logger.error).toBeCalledWith( + 'Contribution not found to given id (-1) or already confirmed.', + ) }) }) @@ -790,7 +794,7 @@ describe('ContributionResolver', () => { resetToken() }) - describe('wrong contribution id', () => { + describe.skip('wrong contribution id', () => { it('returns an error', async () => { await expect( mutate({ @@ -801,13 +805,17 @@ describe('ContributionResolver', () => { }), ).resolves.toEqual( expect.objectContaining({ - errors: [new GraphQLError('Contribution not found for given id.')], + errors: [ + new GraphQLError('Contribution not found to given id or already confirmed.'), + ], }), ) }) it('logs the error found', () => { - expect(logger.error).toBeCalledWith('Contribution not found for given id') + expect(logger.error).toBeCalledWith( + 'Contribution not found to given id (-1) or already confirmed.', + ) }) }) @@ -836,7 +844,7 @@ describe('ContributionResolver', () => { }) }) - describe('User deletes own contribution', () => { + describe.skip('User deletes own contribution', () => { it('deletes successfully', async () => { await expect( mutate({ @@ -1833,17 +1841,21 @@ describe('ContributionResolver', () => { }), ).resolves.toEqual( expect.objectContaining({ - errors: [new GraphQLError('Contribution not found to given id.')], + errors: [ + new GraphQLError('Contribution not found to given id or already confirmed.'), + ], }), ) }) it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith('Contribution not found for given id: -1') + expect(logger.error).toBeCalledWith( + 'Contribution not found to given id (-1) or already confirmed.', + ) }) }) - describe('confirm own creation', () => { + describe.skip('confirm own creation', () => { beforeAll(async () => { const now = new Date() creation = await creationFactory(testEnv, { @@ -1876,7 +1888,7 @@ describe('ContributionResolver', () => { }) }) - describe('confirm creation for other user', () => { + describe.skip('confirm creation for other user', () => { beforeAll(async () => { const now = new Date() creation = await creationFactory(testEnv, { @@ -1908,7 +1920,7 @@ describe('ContributionResolver', () => { ) }) - it('stores the contribution confirm event in the database', async () => { + it.skip('stores the contribution confirm event in the database', async () => { await expect(EventProtocol.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.CONTRIBUTION_CONFIRM, @@ -1977,7 +1989,7 @@ describe('ContributionResolver', () => { }) }) - it('throws no error for the second confirmation', async () => { + it.skip('throws no error for the second confirmation', async () => { const r1 = mutate({ mutation: confirmContribution, variables: { diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index f83202e1c..5f78fff7d 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -559,7 +559,7 @@ export class ContributionResolver { const clientTimezoneOffset = getClientTimezoneOffset(context) const contribution = await DbContribution.findOne({ where: { id, confirmedAt: IsNull() } }) if (!contribution) { - logger.error(`Contribution not found to given id (${id}) or already confirmed`) + logger.error(`Contribution not found to given id (${id}) or already confirmed.`) throw new Error('Contribution not found to given id or already confirmed.') } const moderatorUser = getUser(context) From 6389697bd69f2df5f76d74a9feb1420ff9a6d9fa Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 11 Jan 2023 21:13:38 +0100 Subject: [PATCH 16/20] reset unit tests --- .../resolver/ContributionResolver.test.ts | 38 +++++++------------ .../graphql/resolver/ContributionResolver.ts | 10 +++-- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 77e6e266c..3dfd09bb5 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -414,7 +414,7 @@ describe('ContributionResolver', () => { resetToken() }) - describe.skip('wrong contribution id', () => { + describe('wrong contribution id', () => { it('throws an error', async () => { jest.clearAllMocks() await expect( @@ -429,17 +429,13 @@ describe('ContributionResolver', () => { }), ).resolves.toEqual( expect.objectContaining({ - errors: [ - new GraphQLError('Contribution not found to given id or already confirmed.'), - ], + errors: [new GraphQLError('No contribution found to given id.')], }), ) }) it('logs the error found', () => { - expect(logger.error).toBeCalledWith( - 'Contribution not found to given id (-1) or already confirmed.', - ) + expect(logger.error).toBeCalledWith('No contribution found to given id') }) }) @@ -794,7 +790,7 @@ describe('ContributionResolver', () => { resetToken() }) - describe.skip('wrong contribution id', () => { + describe('wrong contribution id', () => { it('returns an error', async () => { await expect( mutate({ @@ -805,17 +801,13 @@ describe('ContributionResolver', () => { }), ).resolves.toEqual( expect.objectContaining({ - errors: [ - new GraphQLError('Contribution not found to given id or already confirmed.'), - ], + errors: [new GraphQLError('Contribution not found for given id.')], }), ) }) it('logs the error found', () => { - expect(logger.error).toBeCalledWith( - 'Contribution not found to given id (-1) or already confirmed.', - ) + expect(logger.error).toBeCalledWith('Contribution not found for given id') }) }) @@ -844,7 +836,7 @@ describe('ContributionResolver', () => { }) }) - describe.skip('User deletes own contribution', () => { + describe('User deletes own contribution', () => { it('deletes successfully', async () => { await expect( mutate({ @@ -1841,21 +1833,17 @@ describe('ContributionResolver', () => { }), ).resolves.toEqual( expect.objectContaining({ - errors: [ - new GraphQLError('Contribution not found to given id or already confirmed.'), - ], + errors: [new GraphQLError('Contribution not found to given id.')], }), ) }) it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith( - 'Contribution not found to given id (-1) or already confirmed.', - ) + expect(logger.error).toBeCalledWith('Contribution not found for given id: -1') }) }) - describe.skip('confirm own creation', () => { + describe('confirm own creation', () => { beforeAll(async () => { const now = new Date() creation = await creationFactory(testEnv, { @@ -1888,7 +1876,7 @@ describe('ContributionResolver', () => { }) }) - describe.skip('confirm creation for other user', () => { + describe('confirm creation for other user', () => { beforeAll(async () => { const now = new Date() creation = await creationFactory(testEnv, { @@ -1920,7 +1908,7 @@ describe('ContributionResolver', () => { ) }) - it.skip('stores the contribution confirm event in the database', async () => { + it('stores the contribution confirm event in the database', async () => { await expect(EventProtocol.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.CONTRIBUTION_CONFIRM, @@ -1989,7 +1977,7 @@ describe('ContributionResolver', () => { }) }) - it.skip('throws no error for the second confirmation', async () => { + it('throws no error for the second confirmation', async () => { const r1 = mutate({ mutation: confirmContribution, variables: { diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 5f78fff7d..12da50ed0 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -557,10 +557,14 @@ export class ContributionResolver { const releaseLock = await TRANSACTIONS_LOCK.acquire() const clientTimezoneOffset = getClientTimezoneOffset(context) - const contribution = await DbContribution.findOne({ where: { id, confirmedAt: IsNull() } }) + const contribution = await DbContribution.findOne(id) if (!contribution) { - logger.error(`Contribution not found to given id (${id}) or already confirmed.`) - throw new Error('Contribution not found to given id or already confirmed.') + logger.error(`Contribution not found for given id: ${id}`) + throw new Error('Contribution not found to given id.') + } + if (contribution.confirmedAt) { + logger.error(`Contribution already confirmd: ${id}`) + throw new Error('Contribution already confirmd.') } const moderatorUser = getUser(context) if (moderatorUser.id === contribution.userId) { From 29479bafec3843ba4ac4a88c762937763c53af62 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 11 Jan 2023 21:29:11 +0100 Subject: [PATCH 17/20] release lock after each error --- backend/src/graphql/resolver/ContributionResolver.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 12da50ed0..45b3d2553 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -560,15 +560,18 @@ export class ContributionResolver { const contribution = await DbContribution.findOne(id) if (!contribution) { logger.error(`Contribution not found for given id: ${id}`) + releaseLock() throw new Error('Contribution not found to given id.') } if (contribution.confirmedAt) { logger.error(`Contribution already confirmd: ${id}`) + releaseLock() throw new Error('Contribution already confirmd.') } const moderatorUser = getUser(context) if (moderatorUser.id === contribution.userId) { logger.error('Moderator can not confirm own contribution') + releaseLock() throw new Error('Moderator can not confirm own contribution') } const user = await DbUser.findOneOrFail( @@ -577,6 +580,7 @@ export class ContributionResolver { ) if (user.deletedAt) { logger.error('This user was deleted. Cannot confirm a contribution.') + releaseLock() throw new Error('This user was deleted. Cannot confirm a contribution.') } const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false) From 8f9e77a9c1ee4862902d786fe5b3f5c2771e1e23 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 11 Jan 2023 21:40:11 +0100 Subject: [PATCH 18/20] add test: confirming a confirmed contribution throws --- .../resolver/ContributionResolver.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 3dfd09bb5..9a7fb76f2 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -1947,6 +1947,23 @@ describe('ContributionResolver', () => { }), ) }) + + describe('confirm same contribution again', () => { + it('throws an error', async () => { + await expect( + mutate({ + mutation: confirmContribution, + variables: { + id: creation ? creation.id : -1, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Contribution already confirmd.')], + }), + ) + }) + }) }) describe('confirm two creations one after the other quickly', () => { From b2f9c499dd19b9db5f738bb956acbdf9b14915d4 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 11 Jan 2023 22:47:55 +0100 Subject: [PATCH 19/20] use try finally to release lock --- .../graphql/resolver/ContributionResolver.ts | 196 +++++++++--------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 45b3d2553..3794546e2 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -556,113 +556,113 @@ export class ContributionResolver { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() - const clientTimezoneOffset = getClientTimezoneOffset(context) - const contribution = await DbContribution.findOne(id) - if (!contribution) { - logger.error(`Contribution not found for given id: ${id}`) - releaseLock() - throw new Error('Contribution not found to given id.') - } - if (contribution.confirmedAt) { - logger.error(`Contribution already confirmd: ${id}`) - releaseLock() - throw new Error('Contribution already confirmd.') - } - const moderatorUser = getUser(context) - if (moderatorUser.id === contribution.userId) { - logger.error('Moderator can not confirm own contribution') - releaseLock() - throw new Error('Moderator can not confirm own contribution') - } - const user = await DbUser.findOneOrFail( - { id: contribution.userId }, - { withDeleted: true, relations: ['emailContact'] }, - ) - if (user.deletedAt) { - logger.error('This user was deleted. Cannot confirm a contribution.') - releaseLock() - throw new Error('This user was deleted. Cannot confirm a contribution.') - } - const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false) - validateContribution( - creations, - contribution.amount, - contribution.contributionDate, - clientTimezoneOffset, - ) - - const receivedCallDate = new Date() - const queryRunner = getConnection().createQueryRunner() - await queryRunner.connect() - await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED') try { - const lastTransaction = await queryRunner.manager - .createQueryBuilder() - .select('transaction') - .from(DbTransaction, 'transaction') - .where('transaction.userId = :id', { id: contribution.userId }) - .orderBy('transaction.id', 'DESC') - .getOne() - logger.info('lastTransaction ID', lastTransaction ? lastTransaction.id : 'undefined') - - let newBalance = new Decimal(0) - let decay: Decay | null = null - if (lastTransaction) { - decay = calculateDecay( - lastTransaction.balance, - lastTransaction.balanceDate, - receivedCallDate, - ) - newBalance = decay.balance + const clientTimezoneOffset = getClientTimezoneOffset(context) + const contribution = await DbContribution.findOne(id) + if (!contribution) { + logger.error(`Contribution not found for given id: ${id}`) + throw new Error('Contribution not found to given id.') } - newBalance = newBalance.add(contribution.amount.toString()) + if (contribution.confirmedAt) { + logger.error(`Contribution already confirmd: ${id}`) + throw new Error('Contribution already confirmd.') + } + const moderatorUser = getUser(context) + if (moderatorUser.id === contribution.userId) { + logger.error('Moderator can not confirm own contribution') + throw new Error('Moderator can not confirm own contribution') + } + const user = await DbUser.findOneOrFail( + { id: contribution.userId }, + { withDeleted: true, relations: ['emailContact'] }, + ) + if (user.deletedAt) { + logger.error('This user was deleted. Cannot confirm a contribution.') + throw new Error('This user was deleted. Cannot confirm a contribution.') + } + const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false) + validateContribution( + creations, + contribution.amount, + contribution.contributionDate, + clientTimezoneOffset, + ) - const transaction = new DbTransaction() - transaction.typeId = TransactionTypeId.CREATION - transaction.memo = contribution.memo - transaction.userId = contribution.userId - transaction.previous = lastTransaction ? lastTransaction.id : null - transaction.amount = contribution.amount - transaction.creationDate = contribution.contributionDate - transaction.balance = newBalance - transaction.balanceDate = receivedCallDate - transaction.decay = decay ? decay.decay : new Decimal(0) - transaction.decayStart = decay ? decay.start : null - await queryRunner.manager.insert(DbTransaction, transaction) + const receivedCallDate = new Date() + const queryRunner = getConnection().createQueryRunner() + await queryRunner.connect() + await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED') + try { + const lastTransaction = await queryRunner.manager + .createQueryBuilder() + .select('transaction') + .from(DbTransaction, 'transaction') + .where('transaction.userId = :id', { id: contribution.userId }) + .orderBy('transaction.id', 'DESC') + .getOne() + logger.info('lastTransaction ID', lastTransaction ? lastTransaction.id : 'undefined') - contribution.confirmedAt = receivedCallDate - contribution.confirmedBy = moderatorUser.id - contribution.transactionId = transaction.id - contribution.contributionStatus = ContributionStatus.CONFIRMED - await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution) + let newBalance = new Decimal(0) + let decay: Decay | null = null + if (lastTransaction) { + decay = calculateDecay( + lastTransaction.balance, + lastTransaction.balanceDate, + receivedCallDate, + ) + newBalance = decay.balance + } + newBalance = newBalance.add(contribution.amount.toString()) - await queryRunner.commitTransaction() - logger.info('creation commited successfuly.') - sendContributionConfirmedEmail({ - firstName: user.firstName, - lastName: user.lastName, - email: user.emailContact.email, - language: user.language, - senderFirstName: moderatorUser.firstName, - senderLastName: moderatorUser.lastName, - contributionMemo: contribution.memo, - contributionAmount: contribution.amount, - }) - } catch (e) { - await queryRunner.rollbackTransaction() - logger.error('Creation was not successful', e) - throw new Error('Creation was not successful.') + const transaction = new DbTransaction() + transaction.typeId = TransactionTypeId.CREATION + transaction.memo = contribution.memo + transaction.userId = contribution.userId + transaction.previous = lastTransaction ? lastTransaction.id : null + transaction.amount = contribution.amount + transaction.creationDate = contribution.contributionDate + transaction.balance = newBalance + transaction.balanceDate = receivedCallDate + transaction.decay = decay ? decay.decay : new Decimal(0) + transaction.decayStart = decay ? decay.start : null + await queryRunner.manager.insert(DbTransaction, transaction) + + contribution.confirmedAt = receivedCallDate + contribution.confirmedBy = moderatorUser.id + contribution.transactionId = transaction.id + contribution.contributionStatus = ContributionStatus.CONFIRMED + await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution) + + await queryRunner.commitTransaction() + logger.info('creation commited successfuly.') + sendContributionConfirmedEmail({ + firstName: user.firstName, + lastName: user.lastName, + email: user.emailContact.email, + language: user.language, + senderFirstName: moderatorUser.firstName, + senderLastName: moderatorUser.lastName, + contributionMemo: contribution.memo, + contributionAmount: contribution.amount, + }) + } catch (e) { + await queryRunner.rollbackTransaction() + logger.error('Creation was not successful', e) + throw new Error('Creation was not successful.') + } finally { + await queryRunner.release() + } + + const event = new Event() + const eventContributionConfirm = new EventContributionConfirm() + eventContributionConfirm.userId = user.id + eventContributionConfirm.amount = contribution.amount + eventContributionConfirm.contributionId = contribution.id + await eventProtocol.writeEvent(event.setEventContributionConfirm(eventContributionConfirm)) } finally { - await queryRunner.release() releaseLock() } - const event = new Event() - const eventContributionConfirm = new EventContributionConfirm() - eventContributionConfirm.userId = user.id - eventContributionConfirm.amount = contribution.amount - eventContributionConfirm.contributionId = contribution.id - await eventProtocol.writeEvent(event.setEventContributionConfirm(eventContributionConfirm)) return true } From 6497960696fcadee05664fff866f553142ceea55 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 11 Jan 2023 23:08:48 +0100 Subject: [PATCH 20/20] increase backend coverage requirement to 78% --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3c486cb20..4cc28bf20 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -527,7 +527,7 @@ jobs: report_name: Coverage Backend type: lcov result_path: ./backend/coverage/lcov.info - min_coverage: 76 + min_coverage: 78 token: ${{ github.token }} ##########################################################################