From 923b886baf47fd6277d134878acb45054022f38b Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 10 Mar 2022 21:04:36 +0100 Subject: [PATCH 01/18] create virtual transaction for transaction links --- backend/src/util/virtualLinkTransaction.ts | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 backend/src/util/virtualLinkTransaction.ts diff --git a/backend/src/util/virtualLinkTransaction.ts b/backend/src/util/virtualLinkTransaction.ts new file mode 100644 index 000000000..a41b33bbc --- /dev/null +++ b/backend/src/util/virtualLinkTransaction.ts @@ -0,0 +1,52 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { Transaction } from '@model/Transaction' +import { SaveOptions, RemoveOptions } from '@dbTools/typeorm' +import { Transaction as dbTransaction } from '@entity/Transaction' +import { TransactionTypeId } from '@enum/TransactionTypeId' +import { User } from '@model/User' +import Decimal from 'decimal.js-light' + +const virtualLinkTransaction = ( + balance: Decimal, + amount: Decimal, + holdAvailableAmount: Decimal, + decay: Decimal, + createdAt: Date, + validUntil: Date, + user: User, +): Transaction => { + const linkDbTransaction: dbTransaction = { + id: -2, + userId: -1, + previous: -1, + typeId: TransactionTypeId.TRANSACTION_LINK, + amount: amount, + balance: balance.minus(holdAvailableAmount.toString()), + balanceDate: validUntil, + decayStart: createdAt, + decay: decay, + memo: '', + creationDate: null, + hasId: function (): boolean { + throw new Error('Function not implemented.') + }, + save: function (options?: SaveOptions): Promise { + throw new Error('Function not implemented.') + }, + remove: function (options?: RemoveOptions): Promise { + throw new Error('Function not implemented.') + }, + softRemove: function (options?: SaveOptions): Promise { + throw new Error('Function not implemented.') + }, + recover: function (options?: SaveOptions): Promise { + throw new Error('Function not implemented.') + }, + reload: function (): Promise { + throw new Error('Function not implemented.') + }, + } + return new Transaction(linkDbTransaction, user) +} + +export { virtualLinkTransaction } From 3aa3ead86e061339e68eb35b6871e5f0ed3c2742 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 10 Mar 2022 21:06:09 +0100 Subject: [PATCH 02/18] refactor transaction link sum query, add virtual transaction for transaction links to transaction list --- backend/src/graphql/enum/TransactionTypeId.ts | 1 + .../graphql/resolver/TransactionResolver.ts | 39 ++++++++++++++++--- .../src/typeorm/repository/TransactionLink.ts | 18 +++++++-- backend/src/util/validate.ts | 4 +- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/backend/src/graphql/enum/TransactionTypeId.ts b/backend/src/graphql/enum/TransactionTypeId.ts index 497ad5055..38ee99cc6 100644 --- a/backend/src/graphql/enum/TransactionTypeId.ts +++ b/backend/src/graphql/enum/TransactionTypeId.ts @@ -6,6 +6,7 @@ export enum TransactionTypeId { RECEIVE = 3, // This is a virtual property, never occurring on the database DECAY = 4, + TRANSACTION_LINK, } registerEnumType(TransactionTypeId, { diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 1d1a00c39..33aa94211 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql' -import { getCustomRepository, getConnection } from '@dbTools/typeorm' +import { getCustomRepository, getConnection, MoreThan } from '@dbTools/typeorm' import CONFIG from '@/config' import { sendTransactionReceivedEmail } from '@/mailer/sendTransactionReceivedEmail' @@ -23,6 +23,7 @@ import { TransactionLinkRepository } from '@repository/TransactionLink' import { User as dbUser } from '@entity/User' import { Transaction as dbTransaction } from '@entity/Transaction' +import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import { apiPost } from '@/apis/HttpRequest' import { TransactionTypeId } from '@enum/TransactionTypeId' @@ -30,6 +31,7 @@ import { calculateBalance, isHexPublicKey } from '@/util/validate' import { RIGHTS } from '@/auth/RIGHTS' import { User } from '@model/User' import { communityUser } from '@/util/communityUser' +import { virtualLinkTransaction } from '@/util/virtualLinkTransaction' import { virtualDecayTransaction } from '@/util/virtualDecayTransaction' import Decimal from 'decimal.js-light' import { calculateDecay } from '@/util/decay' @@ -112,11 +114,41 @@ export class TransactionResolver { const self = new User(user) const transactions: Transaction[] = [] + const transactionLinkRepository = getCustomRepository(TransactionLinkRepository) + const { sumHoldAvailableAmount, sumAmount } = await transactionLinkRepository.sumAmounts( + user.id, + now, + ) + // decay transaction if (!onlyCreations && currentPage === 1 && order === Order.DESC) { transactions.push( virtualDecayTransaction(lastTransaction.balance, lastTransaction.balanceDate, now, self), ) + // open transaction link sum transaction + if (sumHoldAvailableAmount.greaterThan(0)) { + const lastTransactionLink = await dbTransactionLink.find({ + where: { userId: self.id, redeemedBy: null, validUntil: MoreThan(now) }, + order: { createdAt: 'DESC' }, + take: 1, + }) + const firstTransactionLink = await dbTransactionLink.find({ + where: { userId: self.id, redeemedBy: null, validUntil: MoreThan(now) }, + order: { createdAt: 'ASC' }, + take: 1, + }) + transactions.push( + virtualLinkTransaction( + lastTransaction.balance, + sumAmount, + sumHoldAvailableAmount, + sumHoldAvailableAmount.minus(sumAmount.toString()), + firstTransactionLink[0].createdAt, + lastTransactionLink[0].validUntil, + self, + ), + ) + } } // transactions @@ -128,13 +160,10 @@ export class TransactionResolver { transactions.push(new Transaction(userTransaction, self, linkedUser)) }) - const transactionLinkRepository = getCustomRepository(TransactionLinkRepository) - const toHoldAvailable = await transactionLinkRepository.sumAmountToHoldAvailable(user.id, now) - // Construct Result return new TransactionList( calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now).balance.minus( - toHoldAvailable.toString(), + sumHoldAvailableAmount.toString(), ), transactions, userTransactionsCount, diff --git a/backend/src/typeorm/repository/TransactionLink.ts b/backend/src/typeorm/repository/TransactionLink.ts index 5f7531bc8..13a06ad0d 100644 --- a/backend/src/typeorm/repository/TransactionLink.ts +++ b/backend/src/typeorm/repository/TransactionLink.ts @@ -4,13 +4,23 @@ import Decimal from 'decimal.js-light' @EntityRepository(dbTransactionLink) export class TransactionLinkRepository extends Repository { - async sumAmountToHoldAvailable(userId: number, date: Date): Promise { - const { sum } = await this.createQueryBuilder('transactionLinks') - .select('SUM(transactionLinks.holdAvailableAmount)', 'sum') + async sumAmounts( + userId: number, + date: Date, + ): Promise<{ sumHoldAvailableAmount: Decimal; sumAmount: Decimal }> { + const { sumHoldAvailableAmount, sumAmount } = await this.createQueryBuilder('transactionLinks') + .select('SUM(transactionLinks.holdAvailableAmount)', 'sumHoldAvailableAmount') + .addSelect('SUM(transactionLinks.amount)', 'sumAmount') .where('transactionLinks.userId = :userId', { userId }) .andWhere('transactionLinks.redeemedAt is NULL') .andWhere('transactionLinks.validUntil > :date', { date }) + .orderBy('transactionLinks.createdAt', 'DESC') .getRawOne() - return sum ? new Decimal(sum) : new Decimal(0) + return { + sumHoldAvailableAmount: sumHoldAvailableAmount + ? new Decimal(sumHoldAvailableAmount) + : new Decimal(0), + sumAmount: sumAmount ? new Decimal(sumAmount) : new Decimal(0), + } } } diff --git a/backend/src/util/validate.ts b/backend/src/util/validate.ts index 0858ea50f..64524b12a 100644 --- a/backend/src/util/validate.ts +++ b/backend/src/util/validate.ts @@ -30,9 +30,9 @@ async function calculateBalance( // TODO why we have to use toString() here? const balance = decay.balance.add(amount.toString()) const transactionLinkRepository = getCustomRepository(TransactionLinkRepository) - const toHoldAvailable = await transactionLinkRepository.sumAmountToHoldAvailable(userId, time) + const { sumHoldAvailableAmount } = await transactionLinkRepository.sumAmounts(userId, time) - if (balance.minus(toHoldAvailable.toString()).lessThan(0)) { + if (balance.minus(sumHoldAvailableAmount.toString()).lessThan(0)) { return null } return { balance, lastTransactionId: lastTransaction.id, decay } From df94fa8e37f40f6fa953421974af035e25cdd81f Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 10:21:07 +0100 Subject: [PATCH 03/18] improved query --- .../graphql/resolver/TransactionResolver.ts | 23 ++++----------- backend/src/server/plugins.ts | 3 +- .../src/typeorm/repository/TransactionLink.ts | 28 +++++++++++++------ 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 33aa94211..e98155735 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql' -import { getCustomRepository, getConnection, MoreThan } from '@dbTools/typeorm' +import { getCustomRepository, getConnection } from '@dbTools/typeorm' import CONFIG from '@/config' import { sendTransactionReceivedEmail } from '@/mailer/sendTransactionReceivedEmail' @@ -23,7 +23,6 @@ import { TransactionLinkRepository } from '@repository/TransactionLink' import { User as dbUser } from '@entity/User' import { Transaction as dbTransaction } from '@entity/Transaction' -import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import { apiPost } from '@/apis/HttpRequest' import { TransactionTypeId } from '@enum/TransactionTypeId' @@ -115,10 +114,8 @@ export class TransactionResolver { const transactions: Transaction[] = [] const transactionLinkRepository = getCustomRepository(TransactionLinkRepository) - const { sumHoldAvailableAmount, sumAmount } = await transactionLinkRepository.sumAmounts( - user.id, - now, - ) + const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate } = + await transactionLinkRepository.sumAmounts(user.id, now) // decay transaction if (!onlyCreations && currentPage === 1 && order === Order.DESC) { @@ -127,24 +124,14 @@ export class TransactionResolver { ) // open transaction link sum transaction if (sumHoldAvailableAmount.greaterThan(0)) { - const lastTransactionLink = await dbTransactionLink.find({ - where: { userId: self.id, redeemedBy: null, validUntil: MoreThan(now) }, - order: { createdAt: 'DESC' }, - take: 1, - }) - const firstTransactionLink = await dbTransactionLink.find({ - where: { userId: self.id, redeemedBy: null, validUntil: MoreThan(now) }, - order: { createdAt: 'ASC' }, - take: 1, - }) transactions.push( virtualLinkTransaction( lastTransaction.balance, sumAmount, sumHoldAvailableAmount, sumHoldAvailableAmount.minus(sumAmount.toString()), - firstTransactionLink[0].createdAt, - lastTransactionLink[0].validUntil, + firstDate || now, + lastDate || now, self, ), ) diff --git a/backend/src/server/plugins.ts b/backend/src/server/plugins.ts index a407135ea..5902a415f 100644 --- a/backend/src/server/plugins.ts +++ b/backend/src/server/plugins.ts @@ -39,7 +39,6 @@ const apolloLogPlugin = ApolloLogPlugin({ }, }) -const plugins = - process.env.NODE_ENV === 'development' ? [setHeadersPlugin] : [setHeadersPlugin, apolloLogPlugin] +const plugins = process.env.NODE_ENV === 'development' ? [setHeadersPlugin] : [setHeadersPlugin] // , apolloLogPlugin export default plugins diff --git a/backend/src/typeorm/repository/TransactionLink.ts b/backend/src/typeorm/repository/TransactionLink.ts index 13a06ad0d..f58a1adfb 100644 --- a/backend/src/typeorm/repository/TransactionLink.ts +++ b/backend/src/typeorm/repository/TransactionLink.ts @@ -7,20 +7,30 @@ export class TransactionLinkRepository extends Repository { async sumAmounts( userId: number, date: Date, - ): Promise<{ sumHoldAvailableAmount: Decimal; sumAmount: Decimal }> { - const { sumHoldAvailableAmount, sumAmount } = await this.createQueryBuilder('transactionLinks') - .select('SUM(transactionLinks.holdAvailableAmount)', 'sumHoldAvailableAmount') - .addSelect('SUM(transactionLinks.amount)', 'sumAmount') - .where('transactionLinks.userId = :userId', { userId }) - .andWhere('transactionLinks.redeemedAt is NULL') - .andWhere('transactionLinks.validUntil > :date', { date }) - .orderBy('transactionLinks.createdAt', 'DESC') - .getRawOne() + ): Promise<{ + sumHoldAvailableAmount: Decimal + sumAmount: Decimal + lastDate: Date | null + firstDate: Date | null + }> { + const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate } = + await this.createQueryBuilder('transactionLinks') + .select('SUM(transactionLinks.holdAvailableAmount)', 'sumHoldAvailableAmount') + .addSelect('SUM(transactionLinks.amount)', 'sumAmount') + .addSelect('MAX(transactionLinks.validUntil)', 'lastDate') + .addSelect('MIN(transactionLinks.createdAt)', 'firstDate') + .where('transactionLinks.userId = :userId', { userId }) + .andWhere('transactionLinks.redeemedAt is NULL') + .andWhere('transactionLinks.validUntil > :date', { date }) + .orderBy('transactionLinks.createdAt', 'DESC') + .getRawOne() return { sumHoldAvailableAmount: sumHoldAvailableAmount ? new Decimal(sumHoldAvailableAmount) : new Decimal(0), sumAmount: sumAmount ? new Decimal(sumAmount) : new Decimal(0), + lastDate: lastDate || null, + firstDate: firstDate || null, } } } From 120ce948cdf4a7c9d8077fe1b6d69ef43674818e Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 10:38:37 +0100 Subject: [PATCH 04/18] Update backend/src/graphql/resolver/TransactionResolver.ts Co-authored-by: Ulf Gebhardt --- backend/src/graphql/resolver/TransactionResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index e98155735..314936ff1 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -122,7 +122,7 @@ export class TransactionResolver { transactions.push( virtualDecayTransaction(lastTransaction.balance, lastTransaction.balanceDate, now, self), ) - // open transaction link sum transaction + // virtual transaction for pending transaction-links sum if (sumHoldAvailableAmount.greaterThan(0)) { transactions.push( virtualLinkTransaction( From 24a0c2d1e48cf64b7238d494c9c338bf54981f44 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 10:46:24 +0100 Subject: [PATCH 05/18] no calculation in virtual link transaction --- backend/src/graphql/resolver/TransactionResolver.ts | 4 ++-- backend/src/server/plugins.ts | 3 ++- backend/src/util/virtualLinkTransaction.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 314936ff1..2afabd6a7 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -122,11 +122,11 @@ export class TransactionResolver { transactions.push( virtualDecayTransaction(lastTransaction.balance, lastTransaction.balanceDate, now, self), ) - // virtual transaction for pending transaction-links sum + // virtual transaction for pending transaction-links sum if (sumHoldAvailableAmount.greaterThan(0)) { transactions.push( virtualLinkTransaction( - lastTransaction.balance, + lastTransaction.balance.minus(sumHoldAvailableAmount.toString()), sumAmount, sumHoldAvailableAmount, sumHoldAvailableAmount.minus(sumAmount.toString()), diff --git a/backend/src/server/plugins.ts b/backend/src/server/plugins.ts index 5902a415f..a407135ea 100644 --- a/backend/src/server/plugins.ts +++ b/backend/src/server/plugins.ts @@ -39,6 +39,7 @@ const apolloLogPlugin = ApolloLogPlugin({ }, }) -const plugins = process.env.NODE_ENV === 'development' ? [setHeadersPlugin] : [setHeadersPlugin] // , apolloLogPlugin +const plugins = + process.env.NODE_ENV === 'development' ? [setHeadersPlugin] : [setHeadersPlugin, apolloLogPlugin] export default plugins diff --git a/backend/src/util/virtualLinkTransaction.ts b/backend/src/util/virtualLinkTransaction.ts index a41b33bbc..648825261 100644 --- a/backend/src/util/virtualLinkTransaction.ts +++ b/backend/src/util/virtualLinkTransaction.ts @@ -21,7 +21,7 @@ const virtualLinkTransaction = ( previous: -1, typeId: TransactionTypeId.TRANSACTION_LINK, amount: amount, - balance: balance.minus(holdAvailableAmount.toString()), + balance: balance, balanceDate: validUntil, decayStart: createdAt, decay: decay, From 80944978ac48965f88a3b015a3bf4a11d5f0bdd7 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 10:58:14 +0100 Subject: [PATCH 06/18] only one file for virtual transactions --- .../graphql/resolver/TransactionResolver.ts | 3 +- backend/src/util/virtualDecayTransaction.ts | 52 ------------ backend/src/util/virtualLinkTransaction.ts | 52 ------------ backend/src/util/virtualTransactions.ts | 82 +++++++++++++++++++ 4 files changed, 83 insertions(+), 106 deletions(-) delete mode 100644 backend/src/util/virtualDecayTransaction.ts delete mode 100644 backend/src/util/virtualLinkTransaction.ts create mode 100644 backend/src/util/virtualTransactions.ts diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 2afabd6a7..3f9e4e258 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -30,8 +30,7 @@ import { calculateBalance, isHexPublicKey } from '@/util/validate' import { RIGHTS } from '@/auth/RIGHTS' import { User } from '@model/User' import { communityUser } from '@/util/communityUser' -import { virtualLinkTransaction } from '@/util/virtualLinkTransaction' -import { virtualDecayTransaction } from '@/util/virtualDecayTransaction' +import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions' import Decimal from 'decimal.js-light' import { calculateDecay } from '@/util/decay' diff --git a/backend/src/util/virtualDecayTransaction.ts b/backend/src/util/virtualDecayTransaction.ts deleted file mode 100644 index ff51ea4f5..000000000 --- a/backend/src/util/virtualDecayTransaction.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import Decimal from 'decimal.js-light' -import { SaveOptions, RemoveOptions } from '@dbTools/typeorm' -import { Transaction as dbTransaction } from '@entity/Transaction' -import { calculateDecay } from './decay' -import { TransactionTypeId } from '@enum/TransactionTypeId' -import { Transaction } from '@model/Transaction' -import { User } from '@model/User' - -const virtualDecayTransaction = ( - balance: Decimal, - balanceDate: Date, - time: Date = new Date(), - user: User, -): Transaction => { - const decay = calculateDecay(balance, balanceDate, time) - // const balance = decay.balance.minus(lastTransaction.balance) - const decayDbTransaction: dbTransaction = { - id: -1, - userId: -1, - previous: -1, - typeId: TransactionTypeId.DECAY, - amount: decay.decay ? decay.decay : new Decimal(0), // new Decimal(0), // this kinda is wrong, but helps with the frontend query - balance: decay.balance, - balanceDate: time, - decay: decay.decay ? decay.decay : new Decimal(0), - decayStart: decay.start, - memo: '', - creationDate: null, - hasId: function (): boolean { - throw new Error('Function not implemented.') - }, - save: function (options?: SaveOptions): Promise { - throw new Error('Function not implemented.') - }, - remove: function (options?: RemoveOptions): Promise { - throw new Error('Function not implemented.') - }, - softRemove: function (options?: SaveOptions): Promise { - throw new Error('Function not implemented.') - }, - recover: function (options?: SaveOptions): Promise { - throw new Error('Function not implemented.') - }, - reload: function (): Promise { - throw new Error('Function not implemented.') - }, - } - return new Transaction(decayDbTransaction, user) -} - -export { virtualDecayTransaction } diff --git a/backend/src/util/virtualLinkTransaction.ts b/backend/src/util/virtualLinkTransaction.ts deleted file mode 100644 index 648825261..000000000 --- a/backend/src/util/virtualLinkTransaction.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { Transaction } from '@model/Transaction' -import { SaveOptions, RemoveOptions } from '@dbTools/typeorm' -import { Transaction as dbTransaction } from '@entity/Transaction' -import { TransactionTypeId } from '@enum/TransactionTypeId' -import { User } from '@model/User' -import Decimal from 'decimal.js-light' - -const virtualLinkTransaction = ( - balance: Decimal, - amount: Decimal, - holdAvailableAmount: Decimal, - decay: Decimal, - createdAt: Date, - validUntil: Date, - user: User, -): Transaction => { - const linkDbTransaction: dbTransaction = { - id: -2, - userId: -1, - previous: -1, - typeId: TransactionTypeId.TRANSACTION_LINK, - amount: amount, - balance: balance, - balanceDate: validUntil, - decayStart: createdAt, - decay: decay, - memo: '', - creationDate: null, - hasId: function (): boolean { - throw new Error('Function not implemented.') - }, - save: function (options?: SaveOptions): Promise { - throw new Error('Function not implemented.') - }, - remove: function (options?: RemoveOptions): Promise { - throw new Error('Function not implemented.') - }, - softRemove: function (options?: SaveOptions): Promise { - throw new Error('Function not implemented.') - }, - recover: function (options?: SaveOptions): Promise { - throw new Error('Function not implemented.') - }, - reload: function (): Promise { - throw new Error('Function not implemented.') - }, - } - return new Transaction(linkDbTransaction, user) -} - -export { virtualLinkTransaction } diff --git a/backend/src/util/virtualTransactions.ts b/backend/src/util/virtualTransactions.ts new file mode 100644 index 000000000..72c21b9e4 --- /dev/null +++ b/backend/src/util/virtualTransactions.ts @@ -0,0 +1,82 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { Transaction } from '@model/Transaction' +import { SaveOptions, RemoveOptions } from '@dbTools/typeorm' +import { Transaction as dbTransaction } from '@entity/Transaction' +import { TransactionTypeId } from '@enum/TransactionTypeId' +import { calculateDecay } from './decay' +import { User } from '@model/User' +import Decimal from 'decimal.js-light' + +const defaultModelFunctions = { + hasId: function (): boolean { + throw new Error('Function not implemented.') + }, + save: function (options?: SaveOptions): Promise { + throw new Error('Function not implemented.') + }, + remove: function (options?: RemoveOptions): Promise { + throw new Error('Function not implemented.') + }, + softRemove: function (options?: SaveOptions): Promise { + throw new Error('Function not implemented.') + }, + recover: function (options?: SaveOptions): Promise { + throw new Error('Function not implemented.') + }, + reload: function (): Promise { + throw new Error('Function not implemented.') + }, +} + +const virtualLinkTransaction = ( + balance: Decimal, + amount: Decimal, + holdAvailableAmount: Decimal, + decay: Decimal, + createdAt: Date, + validUntil: Date, + user: User, +): Transaction => { + const linkDbTransaction: dbTransaction = { + id: -2, + userId: -1, + previous: -1, + typeId: TransactionTypeId.TRANSACTION_LINK, + amount: amount, + balance: balance, + balanceDate: validUntil, + decayStart: createdAt, + decay: decay, + memo: '', + creationDate: null, + ...defaultModelFunctions, + } + return new Transaction(linkDbTransaction, user) +} + +const virtualDecayTransaction = ( + balance: Decimal, + balanceDate: Date, + time: Date = new Date(), + user: User, +): Transaction => { + const decay = calculateDecay(balance, balanceDate, time) + // const balance = decay.balance.minus(lastTransaction.balance) + const decayDbTransaction: dbTransaction = { + id: -1, + userId: -1, + previous: -1, + typeId: TransactionTypeId.DECAY, + amount: decay.decay ? decay.decay : new Decimal(0), // new Decimal(0), // this kinda is wrong, but helps with the frontend query + balance: decay.balance, + balanceDate: time, + decay: decay.decay ? decay.decay : new Decimal(0), + decayStart: decay.start, + memo: '', + creationDate: null, + ...defaultModelFunctions, + } + return new Transaction(decayDbTransaction, user) +} + +export { virtualLinkTransaction, virtualDecayTransaction } From e42c91f6e0072e6e18e6dee3ce17a8b3e2580801 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 11:07:15 +0100 Subject: [PATCH 07/18] rename summary query --- backend/src/graphql/resolver/TransactionResolver.ts | 2 +- backend/src/typeorm/repository/TransactionLink.ts | 2 +- backend/src/util/validate.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 3f9e4e258..b740aa867 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -114,7 +114,7 @@ export class TransactionResolver { const transactionLinkRepository = getCustomRepository(TransactionLinkRepository) const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate } = - await transactionLinkRepository.sumAmounts(user.id, now) + await transactionLinkRepository.summary(user.id, now) // decay transaction if (!onlyCreations && currentPage === 1 && order === Order.DESC) { diff --git a/backend/src/typeorm/repository/TransactionLink.ts b/backend/src/typeorm/repository/TransactionLink.ts index f58a1adfb..2ce937d8d 100644 --- a/backend/src/typeorm/repository/TransactionLink.ts +++ b/backend/src/typeorm/repository/TransactionLink.ts @@ -4,7 +4,7 @@ import Decimal from 'decimal.js-light' @EntityRepository(dbTransactionLink) export class TransactionLinkRepository extends Repository { - async sumAmounts( + async summary( userId: number, date: Date, ): Promise<{ diff --git a/backend/src/util/validate.ts b/backend/src/util/validate.ts index 64524b12a..95e1bf699 100644 --- a/backend/src/util/validate.ts +++ b/backend/src/util/validate.ts @@ -30,7 +30,7 @@ async function calculateBalance( // TODO why we have to use toString() here? const balance = decay.balance.add(amount.toString()) const transactionLinkRepository = getCustomRepository(TransactionLinkRepository) - const { sumHoldAvailableAmount } = await transactionLinkRepository.sumAmounts(userId, time) + const { sumHoldAvailableAmount } = await transactionLinkRepository.summary(userId, time) if (balance.minus(sumHoldAvailableAmount.toString()).lessThan(0)) { return null From 428e29f5d90466f4c1cf45b593258e86f9f2c8f4 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 11:55:06 +0100 Subject: [PATCH 08/18] refactor: Transaction Link Query --- .../graphql/arg/QueryTransactionLinkArgs.ts | 10 ----- .../resolver/TransactionLinkResolver.ts | 37 ++++++------------- 2 files changed, 12 insertions(+), 35 deletions(-) delete mode 100644 backend/src/graphql/arg/QueryTransactionLinkArgs.ts diff --git a/backend/src/graphql/arg/QueryTransactionLinkArgs.ts b/backend/src/graphql/arg/QueryTransactionLinkArgs.ts deleted file mode 100644 index 2dcd29572..000000000 --- a/backend/src/graphql/arg/QueryTransactionLinkArgs.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ArgsType, Field, Int } from 'type-graphql' - -@ArgsType() -export default class QueryTransactionLinkArgs { - @Field(() => String) - code: string - - @Field(() => Int, { nullable: true }) - redeemUserId?: number -} diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 071444de9..7deb27912 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -5,9 +5,9 @@ import { Resolver, Args, Arg, Authorized, Ctx, Mutation, Query } from 'type-grap import { getCustomRepository } from '@dbTools/typeorm' import { TransactionLink } from '@model/TransactionLink' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' -import TransactionLinkArgs from '@arg/TransactionLinkArgs' -import QueryTransactionLinkArgs from '@arg/QueryTransactionLinkArgs' +import { User as dbUser } from '@entity/User' import { UserRepository } from '@repository/User' +import TransactionLinkArgs from '@arg/TransactionLinkArgs' import { calculateBalance } from '@/util/validate' import { RIGHTS } from '@/auth/RIGHTS' import { randomBytes } from 'crypto' @@ -96,30 +96,17 @@ export class TransactionLinkResolver { @Authorized([RIGHTS.QUERY_TRANSACTION_LINK]) @Query(() => TransactionLink) - async queryTransactionLink( - @Args() { code, redeemUserId }: QueryTransactionLinkArgs, - ): Promise { + async queryTransactionLink(@Arg('code') code: string): Promise { const transactionLink = await dbTransactionLink.findOneOrFail({ code }) - const userRepository = getCustomRepository(UserRepository) - const user = await userRepository.findOneOrFail({ id: transactionLink.userId }) - let userRedeem = null - if (redeemUserId && !transactionLink.redeemedBy) { - const redeemedByUser = await userRepository.findOne({ id: redeemUserId }) - if (!redeemedByUser) { - throw new Error('Unable to find user that redeem the link') - } - userRedeem = new User(redeemedByUser) - transactionLink.redeemedBy = userRedeem.id - await dbTransactionLink.save(transactionLink).catch(() => { - throw new Error('Unable to save transaction link') - }) - } else if (transactionLink.redeemedBy) { - const redeemedByUser = await userRepository.findOne({ id: redeemUserId }) - if (!redeemedByUser) { - throw new Error('Unable to find user that has redeemed the link') - } - userRedeem = new User(redeemedByUser) + const user = await dbUser.findOneOrFail({ id: transactionLink.userId }) + let redeemedBy: dbUser | null = null + if (transactionLink && transactionLink.redeemedBy) { + redeemedBy = await dbUser.findOneOrFail({ id: transactionLink.redeemedBy }) } - return new TransactionLink(transactionLink, new User(user), userRedeem) + return new TransactionLink( + transactionLink, + new User(user), + redeemedBy ? new User(redeemedBy) : null, + ) } } From 6880fb628e761d39ce68dec464e5c77166b4edaa Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 12:29:10 +0100 Subject: [PATCH 09/18] feat: List Transaction Links Query --- backend/src/auth/RIGHTS.ts | 1 + backend/src/auth/ROLES.ts | 1 + .../resolver/TransactionLinkResolver.ts | 26 ++++++++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts index 4b9036eed..55b5761be 100644 --- a/backend/src/auth/RIGHTS.ts +++ b/backend/src/auth/RIGHTS.ts @@ -21,6 +21,7 @@ export enum RIGHTS { CREATE_TRANSACTION_LINK = 'CREATE_TRANSACTION_LINK', DELETE_TRANSACTION_LINK = 'DELETE_TRANSACTION_LINK', QUERY_TRANSACTION_LINK = 'QUERY_TRANSACTION_LINK', + LIST_TRANSACTION_LINKS = 'LIST_TRANSACTION_LINKS', // Admin SEARCH_USERS = 'SEARCH_USERS', CREATE_PENDING_CREATION = 'CREATE_PENDING_CREATION', diff --git a/backend/src/auth/ROLES.ts b/backend/src/auth/ROLES.ts index 2a86b5bab..56d9d695c 100644 --- a/backend/src/auth/ROLES.ts +++ b/backend/src/auth/ROLES.ts @@ -20,6 +20,7 @@ export const ROLE_USER = new Role('user', [ RIGHTS.HAS_ELOPAGE, RIGHTS.CREATE_TRANSACTION_LINK, RIGHTS.DELETE_TRANSACTION_LINK, + RIGHTS.LIST_TRANSACTION_LINKS, ]) export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 071444de9..e43848a97 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { Resolver, Args, Arg, Authorized, Ctx, Mutation, Query } from 'type-graphql' -import { getCustomRepository } from '@dbTools/typeorm' +import { getCustomRepository, MoreThan } from '@dbTools/typeorm' import { TransactionLink } from '@model/TransactionLink' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import TransactionLinkArgs from '@arg/TransactionLinkArgs' @@ -122,4 +122,28 @@ export class TransactionLinkResolver { } return new TransactionLink(transactionLink, new User(user), userRedeem) } + + @Authorized([RIGHTS.LIST_TRANSACTION_LINKS]) + @Query(() => [TransactionLink]) + async listTransactionLinks( + @Arg('offset', { nullable: true }) offset = 0, + @Ctx() context: any, + ): Promise { + const userRepository = getCustomRepository(UserRepository) + const user = await userRepository.findByPubkeyHex(context.pubKey) + const now = new Date() + const transactionLinks = await dbTransactionLink.find({ + where: { + userId: user.id, + redeemedBy: null, + validUntil: MoreThan(now), + }, + order: { + createdAt: 'DESC', + }, + skip: offset, + take: 5, + }) + return transactionLinks.map((tl) => new TransactionLink(tl, new User(user))) + } } From cda9ee196d5249a8f74f3bef8d3b270bc58ec4b5 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 12:44:32 +0100 Subject: [PATCH 10/18] tpye number for offset arg --- backend/src/graphql/resolver/TransactionLinkResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index e43848a97..b0e197a68 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -126,7 +126,7 @@ export class TransactionLinkResolver { @Authorized([RIGHTS.LIST_TRANSACTION_LINKS]) @Query(() => [TransactionLink]) async listTransactionLinks( - @Arg('offset', { nullable: true }) offset = 0, + @Arg('offset', { nullable: true }) offset: number = 0, @Ctx() context: any, ): Promise { const userRepository = getCustomRepository(UserRepository) From fed268afb6b339d0197c16be0d60ae00d72b154e Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 12:44:55 +0100 Subject: [PATCH 11/18] tpye number for offset arg --- backend/src/graphql/resolver/TransactionLinkResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index b0e197a68..e43848a97 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -126,7 +126,7 @@ export class TransactionLinkResolver { @Authorized([RIGHTS.LIST_TRANSACTION_LINKS]) @Query(() => [TransactionLink]) async listTransactionLinks( - @Arg('offset', { nullable: true }) offset: number = 0, + @Arg('offset', { nullable: true }) offset = 0, @Ctx() context: any, ): Promise { const userRepository = getCustomRepository(UserRepository) From c042df87144e41ec274cfa5a5f13879c25d64644 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 11 Mar 2022 13:12:44 +0100 Subject: [PATCH 12/18] remove one unnecessary conditional check --- .../src/graphql/resolver/TransactionLinkResolver.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 7deb27912..9d1b72f6f 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -99,14 +99,10 @@ export class TransactionLinkResolver { async queryTransactionLink(@Arg('code') code: string): Promise { const transactionLink = await dbTransactionLink.findOneOrFail({ code }) const user = await dbUser.findOneOrFail({ id: transactionLink.userId }) - let redeemedBy: dbUser | null = null + let redeemedBy: User | null = null if (transactionLink && transactionLink.redeemedBy) { - redeemedBy = await dbUser.findOneOrFail({ id: transactionLink.redeemedBy }) + redeemedBy = new User(await dbUser.findOneOrFail({ id: transactionLink.redeemedBy })) } - return new TransactionLink( - transactionLink, - new User(user), - redeemedBy ? new User(redeemedBy) : null, - ) + return new TransactionLink(transactionLink, new User(user), redeemedBy) } } From 489ab233b9d144d9bbc9e5d369e567ae2483b945 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 13:59:33 +0100 Subject: [PATCH 13/18] define type of arg offset --- backend/src/graphql/resolver/TransactionLinkResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 71e21eae3..09588b0c8 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -109,7 +109,7 @@ export class TransactionLinkResolver { @Authorized([RIGHTS.LIST_TRANSACTION_LINKS]) @Query(() => [TransactionLink]) async listTransactionLinks( - @Arg('offset', { nullable: true }) offset = 0, + @Arg('offset', { nullable: true }) offset: number = 0, @Ctx() context: any, ): Promise { const userRepository = getCustomRepository(UserRepository) From e78337d9ef034b833da7e1341bb1a12d9b5de94c Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 14:01:04 +0100 Subject: [PATCH 14/18] Update backend/src/graphql/enum/TransactionTypeId.ts Co-authored-by: Ulf Gebhardt --- backend/src/graphql/enum/TransactionTypeId.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/enum/TransactionTypeId.ts b/backend/src/graphql/enum/TransactionTypeId.ts index 38ee99cc6..ff64f3158 100644 --- a/backend/src/graphql/enum/TransactionTypeId.ts +++ b/backend/src/graphql/enum/TransactionTypeId.ts @@ -6,7 +6,7 @@ export enum TransactionTypeId { RECEIVE = 3, // This is a virtual property, never occurring on the database DECAY = 4, - TRANSACTION_LINK, + TRANSACTION_LINK = 5, } registerEnumType(TransactionTypeId, { From 4affd35a44a3d741b57e6160eba77e1c0a684397 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 14:01:35 +0100 Subject: [PATCH 15/18] Update backend/src/graphql/resolver/TransactionResolver.ts Co-authored-by: Ulf Gebhardt --- backend/src/graphql/resolver/TransactionResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index b740aa867..97bb2d49c 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -116,7 +116,7 @@ export class TransactionResolver { const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate } = await transactionLinkRepository.summary(user.id, now) - // decay transaction + // decay & link transactions if (!onlyCreations && currentPage === 1 && order === Order.DESC) { transactions.push( virtualDecayTransaction(lastTransaction.balance, lastTransaction.balanceDate, now, self), From 9912c8913be8ee1315fda2646e96d8637f6ccae7 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 14:10:31 +0100 Subject: [PATCH 16/18] prevent linter --- backend/src/graphql/resolver/TransactionLinkResolver.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 09588b0c8..eb1dc8479 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -109,6 +109,7 @@ export class TransactionLinkResolver { @Authorized([RIGHTS.LIST_TRANSACTION_LINKS]) @Query(() => [TransactionLink]) async listTransactionLinks( + // eslint-disable-next-line @typescript-eslint/no-inferrable-types @Arg('offset', { nullable: true }) offset: number = 0, @Ctx() context: any, ): Promise { From 0267a31515d7526a4d3a61a1ff0c986f72d0f951 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 14:19:48 +0100 Subject: [PATCH 17/18] use paginated for args --- .../src/graphql/resolver/TransactionLinkResolver.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index eb1dc8479..64c5026ac 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -8,11 +8,13 @@ import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import { User as dbUser } from '@entity/User' import { UserRepository } from '@repository/User' import TransactionLinkArgs from '@arg/TransactionLinkArgs' +import Paginated from '@arg/Paginated' import { calculateBalance } from '@/util/validate' import { RIGHTS } from '@/auth/RIGHTS' import { randomBytes } from 'crypto' import { User } from '@model/User' import { calculateDecay } from '@/util/decay' +import { Order } from '@enum/Order' // TODO: do not export, test it inside the resolver export const transactionLinkCode = (date: Date): string => { @@ -109,8 +111,8 @@ export class TransactionLinkResolver { @Authorized([RIGHTS.LIST_TRANSACTION_LINKS]) @Query(() => [TransactionLink]) async listTransactionLinks( - // eslint-disable-next-line @typescript-eslint/no-inferrable-types - @Arg('offset', { nullable: true }) offset: number = 0, + @Args() + { currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated, @Ctx() context: any, ): Promise { const userRepository = getCustomRepository(UserRepository) @@ -123,10 +125,10 @@ export class TransactionLinkResolver { validUntil: MoreThan(now), }, order: { - createdAt: 'DESC', + createdAt: order, }, - skip: offset, - take: 5, + skip: (currentPage - 1) * pageSize, + take: pageSize, }) return transactionLinks.map((tl) => new TransactionLink(tl, new User(user))) } From 5d21c25240467b79c450a0f12f60e93f83864bec Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Mar 2022 14:39:12 +0100 Subject: [PATCH 18/18] also list depricated links --- backend/src/graphql/resolver/TransactionLinkResolver.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 64c5026ac..8501da675 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { Resolver, Args, Arg, Authorized, Ctx, Mutation, Query } from 'type-graphql' -import { getCustomRepository, MoreThan } from '@dbTools/typeorm' +import { getCustomRepository } from '@dbTools/typeorm' import { TransactionLink } from '@model/TransactionLink' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import { User as dbUser } from '@entity/User' @@ -117,12 +117,12 @@ export class TransactionLinkResolver { ): Promise { const userRepository = getCustomRepository(UserRepository) const user = await userRepository.findByPubkeyHex(context.pubKey) - const now = new Date() + // const now = new Date() const transactionLinks = await dbTransactionLink.find({ where: { userId: user.id, redeemedBy: null, - validUntil: MoreThan(now), + // validUntil: MoreThan(now), }, order: { createdAt: order,