From 4e72aafe4d787287429d8e90d0cfe91d68096b97 Mon Sep 17 00:00:00 2001 From: elweyn Date: Sun, 18 Sep 2022 11:22:25 +0200 Subject: [PATCH 1/7] Change the query so that we only look on the contributions table. --- .../src/graphql/resolver/util/creations.ts | 61 ++++++++++++------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index ad15ebec6..3a6a14fda 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -43,39 +43,54 @@ export const getUserCreations = async ( const dateFilter = 'last_day(curdate() - interval 3 month) + interval 1 day' logger.trace('getUserCreations dateFilter', dateFilter) - const unionString = includePending - ? ` - UNION - SELECT contribution_date AS date, amount AS amount, user_id AS userId FROM contributions - WHERE user_id IN (${ids.toString()}) - AND contribution_date >= ${dateFilter} - AND confirmed_at IS NULL AND deleted_at IS NULL` - : '' - - const unionQuery = await queryRunner.manager.query(` - SELECT MONTH(date) AS month, sum(amount) AS sum, userId AS id FROM - (SELECT creation_date AS date, amount AS amount, user_id AS userId FROM transactions - WHERE user_id IN (${ids.toString()}) - AND type_id = ${TransactionTypeId.CREATION} - AND creation_date >= ${dateFilter} - ${unionString}) AS result - GROUP BY month, userId - ORDER BY date DESC - `) - + /** + SELECT MONTH(contribution_date) as month, user_id, created_at, sum(amount), confirmed_at, deleted_at + FROM `contributions` + where user_id = 776 + and contribution_date >= last_day(curdate() - interval 3 month) + interval 1 day + and deleted_at IS NULL + if(!includePending) and confirmed_at IS NOT NULL + group by month, user_id; + */ + const bookedCreationQuery = queryRunner.manager + .createQueryBuilder(Contribution, 'c') + .select('month(contribution_date)', 'month') + .addSelect('user_id', 'userId') + .addSelect('sum(amount)', 'sum') + .where(`user_id in (${ids.toString()})`) + .andWhere(`contribution_date >= ${dateFilter}`) + .andWhere('deleted_at IS NULL') + .groupBy('month') + .addGroupBy('userId') + if (!includePending) { + bookedCreationQuery.andWhere('confirmed_at IS NOT NULL') + } + const bookedCreation = await bookedCreationQuery.getRawMany() + // eslint-disable-next-line no-console + console.log('openCreation', bookedCreation) await queryRunner.release() return ids.map((id) => { return { id, creations: months.map((month) => { - const creation = unionQuery.find( - (raw: { month: string; id: string; creation: number[] }) => - parseInt(raw.month) === month && parseInt(raw.id) === id, + const creation = bookedCreation.find( + (raw: { month: string; userId: string; creation: number[] }) => + parseInt(raw.month) === month && parseInt(raw.userId) === id, ) return MAX_CREATION_AMOUNT.minus(creation ? creation.sum : 0) }), } + // const creations = months.map((month) => { + // const creation = openCreation.find( + // (raw: { month: string; userId: string; creation: number[] }) => + // parseInt(raw.month) === month && parseInt(raw.userId) === id, + // ) + // return MAX_CREATION_AMOUNT.minus(creation ? creation.sum : 0) + // }) + // // eslint-disable-next-line no-console + // console.log('id: ', id, 'creations: ', creations.toString()) + // return { id, creations } }) } From e097c30003ce5162616e5df3fb6294dfaeeaa710 Mon Sep 17 00:00:00 2001 From: elweyn Date: Wed, 21 Sep 2022 11:47:24 +0200 Subject: [PATCH 2/7] Add denied_at is null to query, change variable name of query. --- backend/src/graphql/resolver/util/creations.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index 3a6a14fda..6135d282a 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -1,4 +1,3 @@ -import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId' import { backendLogger as logger } from '@/server/logger' import { getConnection } from '@dbTools/typeorm' import { Contribution } from '@entity/Contribution' @@ -49,10 +48,11 @@ export const getUserCreations = async ( where user_id = 776 and contribution_date >= last_day(curdate() - interval 3 month) + interval 1 day and deleted_at IS NULL + and denied_at IS NULL if(!includePending) and confirmed_at IS NOT NULL group by month, user_id; */ - const bookedCreationQuery = queryRunner.manager + const sumAmountContributionPerUserAndLast3MonthQuery = queryRunner.manager .createQueryBuilder(Contribution, 'c') .select('month(contribution_date)', 'month') .addSelect('user_id', 'userId') @@ -60,21 +60,22 @@ export const getUserCreations = async ( .where(`user_id in (${ids.toString()})`) .andWhere(`contribution_date >= ${dateFilter}`) .andWhere('deleted_at IS NULL') + .andWhere('denied_at IS NULL') .groupBy('month') .addGroupBy('userId') if (!includePending) { - bookedCreationQuery.andWhere('confirmed_at IS NOT NULL') + sumAmountContributionPerUserAndLast3MonthQuery.andWhere('confirmed_at IS NOT NULL') } - const bookedCreation = await bookedCreationQuery.getRawMany() - // eslint-disable-next-line no-console - console.log('openCreation', bookedCreation) + const sumAmountContributionPerUserAndLast3Month = + await sumAmountContributionPerUserAndLast3MonthQuery.getRawMany() + await queryRunner.release() return ids.map((id) => { return { id, creations: months.map((month) => { - const creation = bookedCreation.find( + const creation = sumAmountContributionPerUserAndLast3Month.find( (raw: { month: string; userId: string; creation: number[] }) => parseInt(raw.month) === month && parseInt(raw.userId) === id, ) From b5edc5f4760d4860e612774090d8da357b3a1483 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 25 Oct 2022 13:45:43 +0200 Subject: [PATCH 3/7] clean up --- .../src/graphql/resolver/util/creations.ts | 46 ++----------------- 1 file changed, 3 insertions(+), 43 deletions(-) diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index d54ee2ebf..8371f96f1 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -49,16 +49,6 @@ export const getUserCreations = async ( const dateFilter = 'last_day(curdate() - interval 3 month) + interval 1 day' logger.trace('getUserCreations dateFilter=', dateFilter) - /** - SELECT MONTH(contribution_date) as month, user_id, created_at, sum(amount), confirmed_at, deleted_at - FROM `contributions` - where user_id = 776 - and contribution_date >= last_day(curdate() - interval 3 month) + interval 1 day - and deleted_at IS NULL - and denied_at IS NULL - if(!includePending) and confirmed_at IS NOT NULL - group by month, user_id; - */ const sumAmountContributionPerUserAndLast3MonthQuery = queryRunner.manager .createQueryBuilder(Contribution, 'c') .select('month(contribution_date)', 'month') @@ -70,35 +60,15 @@ export const getUserCreations = async ( .andWhere('denied_at IS NULL') .groupBy('month') .addGroupBy('userId') + if (!includePending) { sumAmountContributionPerUserAndLast3MonthQuery.andWhere('confirmed_at IS NOT NULL') } + const sumAmountContributionPerUserAndLast3Month = await sumAmountContributionPerUserAndLast3MonthQuery.getRawMany() - /* - const unionString = includePending - ? ` - UNION - SELECT contribution_date AS date, amount AS amount, user_id AS userId FROM contributions - WHERE user_id IN (${ids.toString()}) - AND contribution_date >= ${dateFilter} - AND confirmed_at IS NULL AND deleted_at IS NULL` - : '' - logger.trace('getUserCreations unionString=', unionString) - - const unionQuery = await queryRunner.manager.query(` - SELECT MONTH(date) AS month, sum(amount) AS sum, userId AS id FROM - (SELECT creation_date AS date, amount AS amount, user_id AS userId FROM transactions - WHERE user_id IN (${ids.toString()}) - AND type_id = ${TransactionTypeId.CREATION} - AND creation_date >= ${dateFilter} - ${unionString}) AS result - GROUP BY month, userId - ORDER BY date DESC - `) - logger.trace('getUserCreations unionQuery=', unionQuery) - */ + logger.trace(sumAmountContributionPerUserAndLast3Month) await queryRunner.release() @@ -113,16 +83,6 @@ export const getUserCreations = async ( return MAX_CREATION_AMOUNT.minus(creation ? creation.sum : 0) }), } - // const creations = months.map((month) => { - // const creation = openCreation.find( - // (raw: { month: string; userId: string; creation: number[] }) => - // parseInt(raw.month) === month && parseInt(raw.userId) === id, - // ) - // return MAX_CREATION_AMOUNT.minus(creation ? creation.sum : 0) - // }) - // // eslint-disable-next-line no-console - // console.log('id: ', id, 'creations: ', creations.toString()) - // return { id, creations } }) } From b2a1d7d67234380472425832ffd47a918853f4e1 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 26 Oct 2022 06:38:49 +0200 Subject: [PATCH 4/7] include pending contributions for validation of contribution link, test it --- .../resolver/TransactionLinkResolver.test.ts | 151 ++++++++++++------ .../resolver/TransactionLinkResolver.ts | 2 +- 2 files changed, 107 insertions(+), 46 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index 3d40adbf6..275242bd3 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -6,8 +6,15 @@ import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' import { peterLustig } from '@/seeds/users/peter-lustig' import { cleanDB, testEnvironment } from '@test/helpers' import { userFactory } from '@/seeds/factory/user' -import { login, createContributionLink, redeemTransactionLink } from '@/seeds/graphql/mutations' +import { + login, + createContributionLink, + redeemTransactionLink, + createContribution, + updateContribution, +} from '@/seeds/graphql/mutations' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' +import { UnconfirmedContribution } from '@model/UnconfirmedContribution' import Decimal from 'decimal.js-light' import { GraphQLError } from 'graphql' @@ -32,6 +39,7 @@ describe('TransactionLinkResolver', () => { describe('redeem daily Contribution Link', () => { const now = new Date() let contributionLink: DbContributionLink | undefined + let contribution: UnconfirmedContribution | undefined beforeAll(async () => { await mutate({ @@ -79,56 +87,59 @@ describe('TransactionLinkResolver', () => { ) }) - it('allows the user to redeem the contribution link', async () => { - await expect( - mutate({ - mutation: redeemTransactionLink, - variables: { - code: 'CL-' + (contributionLink ? contributionLink.code : ''), - }, - }), - ).resolves.toMatchObject({ - data: { - redeemTransactionLink: true, - }, - errors: undefined, - }) - }) - - it('does not allow the user to redeem the contribution link a second time on the same day', async () => { - await expect( - mutate({ - mutation: redeemTransactionLink, - variables: { - code: 'CL-' + (contributionLink ? contributionLink.code : ''), - }, - }), - ).resolves.toMatchObject({ - errors: [ - new GraphQLError( - 'Creation from contribution link was not successful. Error: Contribution link already redeemed today', - ), - ], - }) - }) - - describe('after one day', () => { + describe('user has pending contribution of 1000 GDD', () => { beforeAll(async () => { - jest.useFakeTimers() - /* eslint-disable-next-line @typescript-eslint/no-empty-function */ - setTimeout(() => {}, 1000 * 60 * 60 * 24) - jest.runAllTimers() await mutate({ mutation: login, - variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + const result = await mutate({ + mutation: createContribution, + variables: { + amount: new Decimal(1000), + memo: 'I was brewing potions for the community the whole month', + creationDate: now.toISOString(), + }, + }) + contribution = result.data.createContribution + }) + + it('does not allow the user to redeem the contribution link', async () => { + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'CL-' + (contributionLink ? contributionLink.code : ''), + }, + }), + ).resolves.toMatchObject({ + errors: [ + new GraphQLError( + 'Creation from contribution link was not successful. Error: The amount (5 GDD) to be created exceeds the amount (0 GDD) still available for this month.', + ), + ], + }) + }) + }) + + describe('user has no pending contributions that would not allow to redeem the link', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + await mutate({ + mutation: updateContribution, + variables: { + contributionId: contribution ? contribution.id : -1, + amount: new Decimal(800), + memo: 'I was brewing potions for the community the whole month', + creationDate: now.toISOString(), + }, }) }) - afterAll(() => { - jest.useRealTimers() - }) - - it('allows the user to redeem the contribution link again', async () => { + it('allows the user to redeem the contribution link', async () => { await expect( mutate({ mutation: redeemTransactionLink, @@ -160,6 +171,56 @@ describe('TransactionLinkResolver', () => { ], }) }) + + describe('after one day', () => { + beforeAll(async () => { + jest.useFakeTimers() + /* eslint-disable-next-line @typescript-eslint/no-empty-function */ + setTimeout(() => {}, 1000 * 60 * 60 * 24) + jest.runAllTimers() + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(() => { + jest.useRealTimers() + }) + + it('allows the user to redeem the contribution link again', async () => { + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'CL-' + (contributionLink ? contributionLink.code : ''), + }, + }), + ).resolves.toMatchObject({ + data: { + redeemTransactionLink: true, + }, + errors: undefined, + }) + }) + + it('does not allow the user to redeem the contribution link a second time on the same day', async () => { + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'CL-' + (contributionLink ? contributionLink.code : ''), + }, + }), + ).resolves.toMatchObject({ + errors: [ + new GraphQLError( + 'Creation from contribution link was not successful. Error: Contribution link already redeemed today', + ), + ], + }) + }) + }) }) }) }) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 4ba5dcd0b..74c531c54 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -258,7 +258,7 @@ export class TransactionLinkResolver { } } - const creations = await getUserCreation(user.id, false) + const creations = await getUserCreation(user.id) logger.info('open creations', creations) validateContribution(creations, contributionLink.amount, now) const contribution = new DbContribution() From c3b624f36fcd0207730719628ed34dc48461323e Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 27 Oct 2022 19:55:23 +0200 Subject: [PATCH 5/7] add order by clause --- backend/src/graphql/resolver/util/creations.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index 8371f96f1..abf4017cb 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -60,6 +60,7 @@ export const getUserCreations = async ( .andWhere('denied_at IS NULL') .groupBy('month') .addGroupBy('userId') + .orderBy('month', 'DESC') if (!includePending) { sumAmountContributionPerUserAndLast3MonthQuery.andWhere('confirmed_at IS NOT NULL') From 4327c600f92a41ed3160eb397ff750a938d3e832 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 28 Oct 2022 08:59:13 +0200 Subject: [PATCH 6/7] fix: Link in Contribution Messages --- frontend/src/components/ContributionMessages/LinkifyMessage.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/ContributionMessages/LinkifyMessage.vue b/frontend/src/components/ContributionMessages/LinkifyMessage.vue index 5d6ec34cb..fc577adf9 100644 --- a/frontend/src/components/ContributionMessages/LinkifyMessage.vue +++ b/frontend/src/components/ContributionMessages/LinkifyMessage.vue @@ -1,7 +1,7 @@