From f5fe88cffcbfaa768f32c08ad261b6d5807a962b Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 12 Jan 2023 15:41:41 +0100 Subject: [PATCH 01/77] setup slots for content header and right side --- frontend/src/layouts/DashboardLayout.vue | 148 ++++++++++++++++- .../src/layouts/templates/ContentHeader.vue | 105 +----------- frontend/src/layouts/templates/RightSide.vue | 156 ++---------------- 3 files changed, 157 insertions(+), 252 deletions(-) diff --git a/frontend/src/layouts/DashboardLayout.vue b/frontend/src/layouts/DashboardLayout.vue index e5bf394f2..1f37d872a 100755 --- a/frontend/src/layouts/DashboardLayout.vue +++ b/frontend/src/layouts/DashboardLayout.vue @@ -35,7 +35,87 @@ :balance="balance" :GdtBalance="GdtBalance" :totalUsers="totalUsers" - /> + > + + + + + + + @@ -46,7 +126,32 @@ :transactionCount="transactionCount" :transactionLinkCount="transactionLinkCount" @set-tunneled-email="setTunneledEmail" - /> + > + + + + @@ -75,7 +180,32 @@ :transactionCount="transactionCount" :transactionLinkCount="transactionLinkCount" @set-tunneled-email="setTunneledEmail" - /> + > + + + + @@ -102,6 +232,12 @@ import { logout } from '@/graphql/mutations' import ContentFooter from '@/components/ContentFooter.vue' import { FadeTransition } from 'vue2-transitions' import CONFIG from '@/config' +import GddAmount from '@/components/Template/ContentHeader/GddAmount.vue' +import GdtAmount from '@/components/Template/ContentHeader/GdtAmount.vue' +import CommunityMember from '@/components/Template/ContentHeader/CommunityMember.vue' +import NavCommunity from '@/components/Template/ContentHeader/NavCommunity.vue' +import LastTransactions from '@/components/Template/RightSide/LastTransactions.vue' +import ContributionInfo from '@/components/Template/RightSide/ContributionInfo.vue' export default { name: 'DashboardLayout', @@ -116,6 +252,12 @@ export default { ContentFooter, FadeTransition, Breadcrumb, + GddAmount, + GdtAmount, + CommunityMember, + NavCommunity, + LastTransactions, + ContributionInfo, }, data() { return { diff --git a/frontend/src/layouts/templates/ContentHeader.vue b/frontend/src/layouts/templates/ContentHeader.vue index 58ba90cae..407d8dcad 100644 --- a/frontend/src/layouts/templates/ContentHeader.vue +++ b/frontend/src/layouts/templates/ContentHeader.vue @@ -1,116 +1,15 @@ From dab7fe584a1bb4911a83b8eeaf89c0765714c55a Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 16 Jan 2023 11:04:14 +0100 Subject: [PATCH 04/77] try catch around semaphore lock --- .../resolver/TransactionLinkResolver.ts | 265 +++++++++--------- 1 file changed, 134 insertions(+), 131 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 897cf9252..ac57db131 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -170,148 +170,151 @@ export class TransactionLinkResolver { if (code.match(/^CL-/)) { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() - logger.info('redeem contribution link...') - const now = new Date() - const queryRunner = getConnection().createQueryRunner() - await queryRunner.connect() - await queryRunner.startTransaction('REPEATABLE READ') try { - const contributionLink = await queryRunner.manager - .createQueryBuilder() - .select('contributionLink') - .from(DbContributionLink, 'contributionLink') - .where('contributionLink.code = :code', { code: code.replace('CL-', '') }) - .getOne() - if (!contributionLink) { - logger.error('no contribution link found to given code:', code) - throw new Error('No contribution link found') - } - logger.info('...contribution link found with id', contributionLink.id) - if (new Date(contributionLink.validFrom).getTime() > now.getTime()) { - logger.error( - 'contribution link is not valid yet. Valid from: ', - contributionLink.validFrom, - ) - throw new Error('Contribution link not valid yet') - } - if (contributionLink.validTo) { - if (new Date(contributionLink.validTo).setHours(23, 59, 59) < now.getTime()) { - logger.error('contribution link is depricated. Valid to: ', contributionLink.validTo) - throw new Error('Contribution link is depricated') + logger.info('redeem contribution link...') + const now = new Date() + const queryRunner = getConnection().createQueryRunner() + await queryRunner.connect() + await queryRunner.startTransaction('REPEATABLE READ') + try { + const contributionLink = await queryRunner.manager + .createQueryBuilder() + .select('contributionLink') + .from(DbContributionLink, 'contributionLink') + .where('contributionLink.code = :code', { code: code.replace('CL-', '') }) + .getOne() + if (!contributionLink) { + logger.error('no contribution link found to given code:', code) + throw new Error('No contribution link found') } - } - let alreadyRedeemed: DbContribution | undefined - switch (contributionLink.cycle) { - case ContributionCycleType.ONCE: { - alreadyRedeemed = await queryRunner.manager - .createQueryBuilder() - .select('contribution') - .from(DbContribution, 'contribution') - .where('contribution.contributionLinkId = :linkId AND contribution.userId = :id', { - linkId: contributionLink.id, - id: user.id, - }) - .getOne() - if (alreadyRedeemed) { - logger.error( - 'contribution link with rule ONCE already redeemed by user with id', - user.id, - ) - throw new Error('Contribution link already redeemed') + logger.info('...contribution link found with id', contributionLink.id) + if (new Date(contributionLink.validFrom).getTime() > now.getTime()) { + logger.error( + 'contribution link is not valid yet. Valid from: ', + contributionLink.validFrom, + ) + throw new Error('Contribution link not valid yet') + } + if (contributionLink.validTo) { + if (new Date(contributionLink.validTo).setHours(23, 59, 59) < now.getTime()) { + logger.error('contribution link is depricated. Valid to: ', contributionLink.validTo) + throw new Error('Contribution link is depricated') } - break } - case ContributionCycleType.DAILY: { - const start = new Date() - start.setHours(0, 0, 0, 0) - const end = new Date() - end.setHours(23, 59, 59, 999) - alreadyRedeemed = await queryRunner.manager - .createQueryBuilder() - .select('contribution') - .from(DbContribution, 'contribution') - .where( - `contribution.contributionLinkId = :linkId AND contribution.userId = :id - AND Date(contribution.confirmedAt) BETWEEN :start AND :end`, - { + let alreadyRedeemed: DbContribution | undefined + switch (contributionLink.cycle) { + case ContributionCycleType.ONCE: { + alreadyRedeemed = await queryRunner.manager + .createQueryBuilder() + .select('contribution') + .from(DbContribution, 'contribution') + .where('contribution.contributionLinkId = :linkId AND contribution.userId = :id', { linkId: contributionLink.id, id: user.id, - start, - end, - }, - ) - .getOne() - if (alreadyRedeemed) { - logger.error( - 'contribution link with rule DAILY already redeemed by user with id', - user.id, - ) - throw new Error('Contribution link already redeemed today') + }) + .getOne() + if (alreadyRedeemed) { + logger.error( + 'contribution link with rule ONCE already redeemed by user with id', + user.id, + ) + throw new Error('Contribution link already redeemed') + } + break + } + case ContributionCycleType.DAILY: { + const start = new Date() + start.setHours(0, 0, 0, 0) + const end = new Date() + end.setHours(23, 59, 59, 999) + alreadyRedeemed = await queryRunner.manager + .createQueryBuilder() + .select('contribution') + .from(DbContribution, 'contribution') + .where( + `contribution.contributionLinkId = :linkId AND contribution.userId = :id + AND Date(contribution.confirmedAt) BETWEEN :start AND :end`, + { + linkId: contributionLink.id, + id: user.id, + start, + end, + }, + ) + .getOne() + if (alreadyRedeemed) { + logger.error( + 'contribution link with rule DAILY already redeemed by user with id', + user.id, + ) + throw new Error('Contribution link already redeemed today') + } + break + } + default: { + logger.error('contribution link has unknown cycle', contributionLink.cycle) + throw new Error('Contribution link has unknown cycle') } - break } - default: { - logger.error('contribution link has unknown cycle', contributionLink.cycle) - throw new Error('Contribution link has unknown cycle') + + const creations = await getUserCreation(user.id, clientTimezoneOffset) + logger.info('open creations', creations) + validateContribution(creations, contributionLink.amount, now, clientTimezoneOffset) + const contribution = new DbContribution() + contribution.userId = user.id + contribution.createdAt = now + contribution.contributionDate = now + contribution.memo = contributionLink.memo + contribution.amount = contributionLink.amount + contribution.contributionLinkId = contributionLink.id + contribution.contributionType = ContributionType.LINK + contribution.contributionStatus = ContributionStatus.CONFIRMED + + await queryRunner.manager.insert(DbContribution, contribution) + + const lastTransaction = await queryRunner.manager + .createQueryBuilder() + .select('transaction') + .from(DbTransaction, 'transaction') + .where('transaction.userId = :id', { id: user.id }) + .orderBy('transaction.id', 'DESC') + .getOne() + let newBalance = new Decimal(0) + + let decay: Decay | null = null + if (lastTransaction) { + decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now) + newBalance = decay.balance } + newBalance = newBalance.add(contributionLink.amount.toString()) + + 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 = now + transaction.decay = decay ? decay.decay : new Decimal(0) + transaction.decayStart = decay ? decay.start : null + await queryRunner.manager.insert(DbTransaction, transaction) + + contribution.confirmedAt = now + contribution.transactionId = transaction.id + await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution) + + await queryRunner.commitTransaction() + logger.info('creation from contribution link commited successfuly.') + } catch (e) { + await queryRunner.rollbackTransaction() + logger.error(`Creation from contribution link was not successful: ${e}`) + throw new Error(`Creation from contribution link was not successful. ${e}`) + } finally { + await queryRunner.release() } - - const creations = await getUserCreation(user.id, clientTimezoneOffset) - logger.info('open creations', creations) - validateContribution(creations, contributionLink.amount, now, clientTimezoneOffset) - const contribution = new DbContribution() - contribution.userId = user.id - contribution.createdAt = now - contribution.contributionDate = now - contribution.memo = contributionLink.memo - contribution.amount = contributionLink.amount - contribution.contributionLinkId = contributionLink.id - contribution.contributionType = ContributionType.LINK - contribution.contributionStatus = ContributionStatus.CONFIRMED - - await queryRunner.manager.insert(DbContribution, contribution) - - const lastTransaction = await queryRunner.manager - .createQueryBuilder() - .select('transaction') - .from(DbTransaction, 'transaction') - .where('transaction.userId = :id', { id: user.id }) - .orderBy('transaction.id', 'DESC') - .getOne() - let newBalance = new Decimal(0) - - let decay: Decay | null = null - if (lastTransaction) { - decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now) - newBalance = decay.balance - } - newBalance = newBalance.add(contributionLink.amount.toString()) - - 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 = now - transaction.decay = decay ? decay.decay : new Decimal(0) - transaction.decayStart = decay ? decay.start : null - await queryRunner.manager.insert(DbTransaction, transaction) - - contribution.confirmedAt = now - contribution.transactionId = transaction.id - await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution) - - await queryRunner.commitTransaction() - logger.info('creation from contribution link commited successfuly.') - } catch (e) { - await queryRunner.rollbackTransaction() - logger.error(`Creation from contribution link was not successful: ${e}`) - throw new Error(`Creation from contribution link was not successful. ${e}`) } finally { - await queryRunner.release() releaseLock() } return true From c3f7f1542078ecc322ecb4cf453e1d0876434357 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 16 Jan 2023 11:21:38 +0100 Subject: [PATCH 05/77] refactor all semaphore-locks --- .../graphql/resolver/ContributionResolver.ts | 1 - .../graphql/resolver/TransactionResolver.ts | 41 +++++++++---------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 7771c62ca..198d2e590 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -557,7 +557,6 @@ export class ContributionResolver { ): Promise { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() - try { const clientTimezoneOffset = getClientTimezoneOffset(context) const contribution = await DbContribution.findOne(id) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 33914583e..2f97596b2 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -45,29 +45,28 @@ export const executeTransaction = async ( recipient: dbUser, transactionLink?: dbTransactionLink | null, ): Promise => { - logger.info( - `executeTransaction(amount=${amount}, memo=${memo}, sender=${sender}, recipient=${recipient})...`, - ) - - if (sender.id === recipient.id) { - logger.error(`Sender and Recipient are the same.`) - throw new Error('Sender and Recipient are the same.') - } - - if (memo.length > MEMO_MAX_CHARS) { - logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`) - throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`) - } - - if (memo.length < MEMO_MIN_CHARS) { - logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`) - throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`) - } - // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() - try { + logger.info( + `executeTransaction(amount=${amount}, memo=${memo}, sender=${sender}, recipient=${recipient})...`, + ) + + if (sender.id === recipient.id) { + logger.error(`Sender and Recipient are the same.`) + throw new Error('Sender and Recipient are the same.') + } + + if (memo.length > MEMO_MAX_CHARS) { + logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`) + throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`) + } + + if (memo.length < MEMO_MIN_CHARS) { + logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`) + throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`) + } + // validate amount const receivedCallDate = new Date() const sendBalance = await calculateBalance( @@ -187,10 +186,10 @@ export const executeTransaction = async ( }) } logger.info(`finished executeTransaction successfully`) - return true } finally { releaseLock() } + return true } @Resolver() From 466e59a550226b647805a6707f3773a66a259740 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 16 Jan 2023 11:27:30 +0100 Subject: [PATCH 06/77] typo --- backend/src/graphql/resolver/ContributionResolver.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 198d2e590..afa786f38 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -663,7 +663,6 @@ export class ContributionResolver { } finally { releaseLock() } - return true } From 82a6c616331c94dfdba143bd91e879a4ee07ea86 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 16 Jan 2023 11:37:13 +0100 Subject: [PATCH 07/77] mock replace in route.path, stub last transactions --- frontend/src/layouts/DashboardLayout.spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/layouts/DashboardLayout.spec.js b/frontend/src/layouts/DashboardLayout.spec.js index efed52c78..68fcb5ad5 100644 --- a/frontend/src/layouts/DashboardLayout.spec.js +++ b/frontend/src/layouts/DashboardLayout.spec.js @@ -15,6 +15,7 @@ const routerPushMock = jest.fn() const stubs = { RouterLink: RouterLinkStub, RouterView: true, + LastTransactions: true, } const mocks = { @@ -29,6 +30,9 @@ const mocks = { meta: { hideFooter: false, }, + path: { + replace: jest.fn(), + }, }, $router: { push: routerPushMock, From 0d93e5563523297ca05a64ef06d460984184b447 Mon Sep 17 00:00:00 2001 From: ogerly Date: Mon, 16 Jan 2023 13:00:36 +0100 Subject: [PATCH 08/77] mobile menu has transparent background color --- frontend/src/components/Menu/Sidebar.vue | 10 +++++++- .../MobileSidebar/MobileSidebar.vue | 23 ++----------------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/Menu/Sidebar.vue b/frontend/src/components/Menu/Sidebar.vue index 864d42906..9388b0e64 100644 --- a/frontend/src/components/Menu/Sidebar.vue +++ b/frontend/src/components/Menu/Sidebar.vue @@ -1,6 +1,11 @@ -
{{ $t('lastMonth') }}
-
- - +
+
+ + - + - + - - + + +
-
+
- +