From 75b91a2db3c18c8a81ff82d0db5194fc25f5d20f Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 9 Mar 2022 14:27:53 +0100 Subject: [PATCH 01/18] set type for redeemed by, change order --- .../0030-transaction_link/TransactionLink.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/database/entity/0030-transaction_link/TransactionLink.ts b/database/entity/0030-transaction_link/TransactionLink.ts index d04c00d7b..a3ab5cd1a 100644 --- a/database/entity/0030-transaction_link/TransactionLink.ts +++ b/database/entity/0030-transaction_link/TransactionLink.ts @@ -39,13 +39,6 @@ export class TransactionLink extends BaseEntity { }) validUntil: Date - @Column({ - type: 'datetime', - default: () => 'CURRENT_TIMESTAMP', - nullable: true, - }) - redeemedAt: Date - @Column({ type: 'boolean', default: () => false, @@ -53,6 +46,13 @@ export class TransactionLink extends BaseEntity { }) showEmail: boolean - @Column({ unsigned: true, nullable: true }) - redeemedBy: number + @Column({ + type: 'datetime', + default: () => 'CURRENT_TIMESTAMP', + nullable: true, + }) + redeemedAt?: Date | null + + @Column({ type: 'int', unsigned: true, nullable: true }) + redeemedBy?: number | null } From e8e86d6351c42553d967782e722fcba33854764e Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 9 Mar 2022 14:28:28 +0100 Subject: [PATCH 02/18] set DB version --- 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 82fb9ff2b..4cd428153 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -10,7 +10,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0029-clean_transaction_table', + DB_VERSION: '0030-transaction_link', DECAY_START_TIME: new Date('2021-05-13 17:46:31'), // GMT+0 } From c9d24c3b61d5a9436d93ee86c432f607cb00d185 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 9 Mar 2022 14:29:20 +0100 Subject: [PATCH 03/18] add constructor --- backend/src/graphql/model/TransactionLink.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/backend/src/graphql/model/TransactionLink.ts b/backend/src/graphql/model/TransactionLink.ts index c46728407..98f86a772 100644 --- a/backend/src/graphql/model/TransactionLink.ts +++ b/backend/src/graphql/model/TransactionLink.ts @@ -1,9 +1,23 @@ import { ObjectType, Field } from 'type-graphql' import Decimal from 'decimal.js-light' +import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import { User } from './User' @ObjectType() export class TransactionLink { + constructor(transactionLink: dbTransactionLink, user: User) { + this.id = transactionLink.id + this.user = user + this.amount = transactionLink.amount + this.memo = transactionLink.memo + this.code = transactionLink.code + this.createdAt = transactionLink.createdAt + this.validUntil = transactionLink.validUntil + this.showEmail = transactionLink.showEmail + this.redeemedAt = null + this.redeemedBy = null + } + @Field(() => Number) id: number From e88f5610030e7568c2d49dd8f841b49513e46b9f Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 9 Mar 2022 14:29:57 +0100 Subject: [PATCH 04/18] rights for createTransactionLink --- backend/src/auth/RIGHTS.ts | 1 + backend/src/auth/ROLES.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts index 3b3f7580c..a18f0132a 100644 --- a/backend/src/auth/RIGHTS.ts +++ b/backend/src/auth/RIGHTS.ts @@ -18,6 +18,7 @@ export enum RIGHTS { SET_PASSWORD = 'SET_PASSWORD', UPDATE_USER_INFOS = 'UPDATE_USER_INFOS', HAS_ELOPAGE = 'HAS_ELOPAGE', + CREATE_TRANSACTION_LINK = 'CREATE_TRANSACTION_LINK', // 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 ada6a2cef..37a4e3a67 100644 --- a/backend/src/auth/ROLES.ts +++ b/backend/src/auth/ROLES.ts @@ -18,6 +18,7 @@ export const ROLE_USER = new Role('user', [ RIGHTS.LOGOUT, RIGHTS.UPDATE_USER_INFOS, RIGHTS.HAS_ELOPAGE, + RIGHTS.CREATE_TRANSACTION_LINK, ]) export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights From 1a715f8b7108fce62605404a307d429982f1a5e6 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 9 Mar 2022 14:30:25 +0100 Subject: [PATCH 05/18] simple resolver for createTransactionLink --- .../src/graphql/arg/TransactionLinkArgs.ts | 14 ++++ .../resolver/TransactionLinkResolver.test.ts | 14 ++++ .../resolver/TransactionLinkResolver.ts | 65 +++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 backend/src/graphql/arg/TransactionLinkArgs.ts create mode 100644 backend/src/graphql/resolver/TransactionLinkResolver.test.ts create mode 100644 backend/src/graphql/resolver/TransactionLinkResolver.ts diff --git a/backend/src/graphql/arg/TransactionLinkArgs.ts b/backend/src/graphql/arg/TransactionLinkArgs.ts new file mode 100644 index 000000000..5ccb967d3 --- /dev/null +++ b/backend/src/graphql/arg/TransactionLinkArgs.ts @@ -0,0 +1,14 @@ +import { ArgsType, Field } from 'type-graphql' +import Decimal from 'decimal.js-light' + +@ArgsType() +export default class TransactionLinkArgs { + @Field(() => Decimal) + amount: Decimal + + @Field(() => String) + memo: string + + @Field(() => Boolean, { nullable: true }) + showEmail?: boolean +} diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts new file mode 100644 index 000000000..51790502d --- /dev/null +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -0,0 +1,14 @@ +import { transactionLinkCode } from './TransactionLinkResolver' + +describe('transactionLinkCode', () => { + const date = new Date() + + it('returns a string of length 96', () => { + expect(transactionLinkCode(date)).toHaveLength(96) + }) + + it('returns a string that ends with the hex value of date', () => { + const regexp = new RegExp(date.getTime().toString(16) + '$') + expect(transactionLinkCode(date)).toEqual(expect.stringMatching(regexp)) + }) +}) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts new file mode 100644 index 000000000..920e18569 --- /dev/null +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -0,0 +1,65 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +import { Resolver, Args, Authorized, Ctx, Mutation } from 'type-graphql' +import { getCustomRepository } from '@dbTools/typeorm' +import { TransactionLink } from '@model/TransactionLink' +import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' +import TransactionLinkArgs from '@arg/TransactionLinkArgs' +import { UserRepository } from '@repository/User' +import { calculateBalance } from '@/util/validate' +import { RIGHTS } from '@/auth/RIGHTS' +import { randomBytes } from 'crypto' +import { User } from '@model/User' + +// TODO: do not export, test it inside the resolver +export const transactionLinkCode = (date: Date): string => { + const time = date.getTime().toString(16) + return ( + randomBytes(48) + .toString('hex') + .substring(0, 96 - time.length) + time + ) +} + +const transactionLinkExpireDate = (date: Date): Date => { + // valid for 14 days + return new Date(date.setDate(date.getDate() + 14)) +} + +@Resolver() +export class TransactionLinkResolver { + @Authorized([RIGHTS.CREATE_TRANSACTION_LINK]) + @Mutation(() => TransactionLink) + async createTransactionLink( + @Args() { amount, memo, showEmail = false }: TransactionLinkArgs, + @Ctx() context: any, + ): Promise { + const userRepository = getCustomRepository(UserRepository) + const user = await userRepository.findByPubkeyHex(context.pubKey) + + // validate amount + // TODO taken from transaction resolver, duplicate code + const createdDate = new Date() + const sendBalance = await calculateBalance(user.id, amount.mul(-1), createdDate) + if (!sendBalance) { + throw new Error("user hasn't enough GDD or amount is < 0") + } + + // TODO!!!! Test balance for pending transaction links + + const transactionLink = dbTransactionLink.create() + transactionLink.userId = user.id + transactionLink.amount = amount + transactionLink.memo = memo + transactionLink.code = transactionLinkCode(createdDate) + transactionLink.createdAt = createdDate + transactionLink.validUntil = transactionLinkExpireDate(createdDate) + transactionLink.showEmail = showEmail + await dbTransactionLink.save(transactionLink).catch((error) => { + throw error + }) + + return new TransactionLink(transactionLink, new User(user)) + } +} From dee6fd9d0a9f73b0adea13de42f94f0c8e3fb42a Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 9 Mar 2022 14:59:21 +0100 Subject: [PATCH 06/18] fix createdAt is now --- backend/src/graphql/resolver/TransactionLinkResolver.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 920e18569..46cdf5071 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -24,7 +24,8 @@ export const transactionLinkCode = (date: Date): string => { const transactionLinkExpireDate = (date: Date): Date => { // valid for 14 days - return new Date(date.setDate(date.getDate() + 14)) + const validUntil = new Date(date) + return new Date(validUntil.setDate(date.getDate() + 14)) } @Resolver() From 91c7b051850498a4c7125dbfb41b5cb885d9a51c Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 9 Mar 2022 15:00:34 +0100 Subject: [PATCH 07/18] remove default dates --- database/entity/0030-transaction_link/TransactionLink.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/database/entity/0030-transaction_link/TransactionLink.ts b/database/entity/0030-transaction_link/TransactionLink.ts index a3ab5cd1a..5e8690270 100644 --- a/database/entity/0030-transaction_link/TransactionLink.ts +++ b/database/entity/0030-transaction_link/TransactionLink.ts @@ -27,14 +27,12 @@ export class TransactionLink extends BaseEntity { @Column({ type: 'datetime', - default: () => 'CURRENT_TIMESTAMP', nullable: false, }) createdAt: Date @Column({ type: 'datetime', - default: () => 'CURRENT_TIMESTAMP', nullable: false, }) validUntil: Date @@ -48,7 +46,6 @@ export class TransactionLink extends BaseEntity { @Column({ type: 'datetime', - default: () => 'CURRENT_TIMESTAMP', nullable: true, }) redeemedAt?: Date | null From 6d6f58a9322ac79467f2fcd53b4ee8dc9cd4319c Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 9 Mar 2022 15:43:13 +0100 Subject: [PATCH 08/18] save hold available amount --- backend/src/graphql/model/TransactionLink.ts | 1 + .../resolver/TransactionLinkResolver.test.ts | 4 ++-- .../resolver/TransactionLinkResolver.ts | 18 +++++++++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/backend/src/graphql/model/TransactionLink.ts b/backend/src/graphql/model/TransactionLink.ts index c518f0f6c..1670e9a23 100644 --- a/backend/src/graphql/model/TransactionLink.ts +++ b/backend/src/graphql/model/TransactionLink.ts @@ -9,6 +9,7 @@ export class TransactionLink { this.id = transactionLink.id this.user = user this.amount = transactionLink.amount + this.holdAvailableAmount = transactionLink.holdAvailableAmount this.memo = transactionLink.memo this.code = transactionLink.code this.createdAt = transactionLink.createdAt diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index 51790502d..5a1a39dca 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -3,8 +3,8 @@ import { transactionLinkCode } from './TransactionLinkResolver' describe('transactionLinkCode', () => { const date = new Date() - it('returns a string of length 96', () => { - expect(transactionLinkCode(date)).toHaveLength(96) + it('returns a string of length 24', () => { + expect(transactionLinkCode(date)).toHaveLength(24) }) it('returns a string that ends with the hex value of date', () => { diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 46cdf5071..657337d74 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -11,20 +11,21 @@ import { calculateBalance } from '@/util/validate' import { RIGHTS } from '@/auth/RIGHTS' import { randomBytes } from 'crypto' import { User } from '@model/User' +import { calculateDecay } from '@/util/decay' // TODO: do not export, test it inside the resolver export const transactionLinkCode = (date: Date): string => { const time = date.getTime().toString(16) return ( - randomBytes(48) + randomBytes(12) .toString('hex') - .substring(0, 96 - time.length) + time + .substring(0, 24 - time.length) + time ) } const transactionLinkExpireDate = (date: Date): Date => { - // valid for 14 days const validUntil = new Date(date) + // valid for 14 days return new Date(validUntil.setDate(date.getDate() + 14)) } @@ -42,7 +43,13 @@ export class TransactionLinkResolver { // validate amount // TODO taken from transaction resolver, duplicate code const createdDate = new Date() - const sendBalance = await calculateBalance(user.id, amount.mul(-1), createdDate) + const validUntil = transactionLinkExpireDate(createdDate) + + const holdAvailableAmount = amount.add( + calculateDecay(amount, createdDate, validUntil).decay.mul(-1), + ) + + const sendBalance = await calculateBalance(user.id, holdAvailableAmount.mul(-1), createdDate) if (!sendBalance) { throw new Error("user hasn't enough GDD or amount is < 0") } @@ -53,9 +60,10 @@ export class TransactionLinkResolver { transactionLink.userId = user.id transactionLink.amount = amount transactionLink.memo = memo + transactionLink.holdAvailableAmount = holdAvailableAmount transactionLink.code = transactionLinkCode(createdDate) transactionLink.createdAt = createdDate - transactionLink.validUntil = transactionLinkExpireDate(createdDate) + transactionLink.validUntil = validUntil transactionLink.showEmail = showEmail await dbTransactionLink.save(transactionLink).catch((error) => { throw error From f4f63a9519ad84f224a80a984f21d53eda93e875 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 9 Mar 2022 16:26:23 +0100 Subject: [PATCH 09/18] take pending transaction links into acount when testing for available balance --- .../resolver/TransactionLinkResolver.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 657337d74..7cd76a954 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, Authorized, Ctx, Mutation } 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' @@ -40,8 +40,6 @@ export class TransactionLinkResolver { const userRepository = getCustomRepository(UserRepository) const user = await userRepository.findByPubkeyHex(context.pubKey) - // validate amount - // TODO taken from transaction resolver, duplicate code const createdDate = new Date() const validUntil = transactionLinkExpireDate(createdDate) @@ -49,13 +47,24 @@ export class TransactionLinkResolver { calculateDecay(amount, createdDate, validUntil).decay.mul(-1), ) - const sendBalance = await calculateBalance(user.id, holdAvailableAmount.mul(-1), createdDate) + const openTransactionLinks = await dbTransactionLink.find({ + select: ['holdAvailableAmount'], + where: { userId: user.id, redeemedAt: null, validUntil: MoreThan(createdDate) }, + }) + + const holdAvailable = openTransactionLinks.reduce( + (previousValue, currentValue) => + previousValue.add(currentValue.holdAvailableAmount.toString()), + holdAvailableAmount, + ) + + // validate amount + // TODO taken from transaction resolver, duplicate code + const sendBalance = await calculateBalance(user.id, holdAvailable.mul(-1), createdDate) if (!sendBalance) { throw new Error("user hasn't enough GDD or amount is < 0") } - // TODO!!!! Test balance for pending transaction links - const transactionLink = dbTransactionLink.create() transactionLink.userId = user.id transactionLink.amount = amount From 96dcdf01702def086a0d1e4c6fe87f5b5eba5dd1 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 9 Mar 2022 17:09:57 +0100 Subject: [PATCH 10/18] add deletedAt to constructor --- backend/src/graphql/model/TransactionLink.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/graphql/model/TransactionLink.ts b/backend/src/graphql/model/TransactionLink.ts index 52fc6ce78..25cf5ac36 100644 --- a/backend/src/graphql/model/TransactionLink.ts +++ b/backend/src/graphql/model/TransactionLink.ts @@ -15,6 +15,7 @@ export class TransactionLink { this.createdAt = transactionLink.createdAt this.validUntil = transactionLink.validUntil this.showEmail = transactionLink.showEmail + this.deletedAt = null this.redeemedAt = null this.redeemedBy = null } From 8107066d2bfc958438aa82cf7c1f3f39492592d4 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 9 Mar 2022 18:18:22 +0100 Subject: [PATCH 11/18] balance includes transactions to hold available, calculateBalance checks for pending transaction links --- .../resolver/TransactionLinkResolver.ts | 20 +++---------------- .../graphql/resolver/TransactionResolver.ts | 8 ++++++-- backend/src/util/validate.ts | 20 +++++++++++++++++-- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 7cd76a954..76ed41308 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, Authorized, Ctx, Mutation } 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 TransactionLinkArgs from '@arg/TransactionLinkArgs' @@ -43,24 +43,10 @@ export class TransactionLinkResolver { const createdDate = new Date() const validUntil = transactionLinkExpireDate(createdDate) - const holdAvailableAmount = amount.add( - calculateDecay(amount, createdDate, validUntil).decay.mul(-1), - ) - - const openTransactionLinks = await dbTransactionLink.find({ - select: ['holdAvailableAmount'], - where: { userId: user.id, redeemedAt: null, validUntil: MoreThan(createdDate) }, - }) - - const holdAvailable = openTransactionLinks.reduce( - (previousValue, currentValue) => - previousValue.add(currentValue.holdAvailableAmount.toString()), - holdAvailableAmount, - ) + const holdAvailableAmount = amount.minus(calculateDecay(amount, createdDate, validUntil).decay) // validate amount - // TODO taken from transaction resolver, duplicate code - const sendBalance = await calculateBalance(user.id, holdAvailable.mul(-1), createdDate) + const sendBalance = await calculateBalance(user.id, holdAvailableAmount.mul(-1), createdDate) if (!sendBalance) { throw new Error("user hasn't enough GDD or amount is < 0") } diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index d674fc69e..f04050fbf 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -25,7 +25,7 @@ import { Transaction as dbTransaction } from '@entity/Transaction' import { apiPost } from '@/apis/HttpRequest' import { TransactionTypeId } from '@enum/TransactionTypeId' -import { calculateBalance, isHexPublicKey } from '@/util/validate' +import { calculateBalance, isHexPublicKey, holdAvailable } from '@/util/validate' import { RIGHTS } from '@/auth/RIGHTS' import { User } from '@model/User' import { communityUser } from '@/util/communityUser' @@ -127,9 +127,13 @@ export class TransactionResolver { transactions.push(new Transaction(userTransaction, self, linkedUser)) }) + const toHoldAvailable = await holdAvailable(user.id, now) + // Construct Result return new TransactionList( - calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now).balance, + calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now).balance.minus( + toHoldAvailable.toString(), + ), transactions, userTransactionsCount, balanceGDT, diff --git a/backend/src/util/validate.ts b/backend/src/util/validate.ts index 3699fe511..c79cca076 100644 --- a/backend/src/util/validate.ts +++ b/backend/src/util/validate.ts @@ -2,6 +2,8 @@ import { calculateDecay } from './decay' import Decimal from 'decimal.js-light' import { Transaction } from '@entity/Transaction' import { Decay } from '@model/Decay' +import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' +import { MoreThan } from '@dbTools/typeorm' function isStringBoolean(value: string): boolean { const lowerValue = value.toLowerCase() @@ -24,12 +26,26 @@ async function calculateBalance( if (!lastTransaction) return null const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time) + // TODO why we have to use toString() here? const balance = decay.balance.add(amount.toString()) - if (balance.lessThan(0)) { + const toHoldAvailable = await holdAvailable(userId, time) + if (balance.minus(toHoldAvailable.toString()).lessThan(0)) { return null } return { balance, lastTransactionId: lastTransaction.id, decay } } -export { isHexPublicKey, calculateBalance, isStringBoolean } +async function holdAvailable(userId: number, date: Date): Promise { + const openTransactionLinks = await dbTransactionLink.find({ + select: ['holdAvailableAmount'], + where: { userId, redeemedAt: null, validUntil: MoreThan(date) }, + }) + + return openTransactionLinks.reduce( + (previousValue, currentValue) => previousValue.add(currentValue.holdAvailableAmount.toString()), + new Decimal(0), + ) +} + +export { isHexPublicKey, calculateBalance, isStringBoolean, holdAvailable } From 5f42748358f6db3adb619b12805f4c195c80b54a Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 9 Mar 2022 18:25:17 +0100 Subject: [PATCH 12/18] error message and constant for transaction link duration --- backend/src/graphql/resolver/TransactionLinkResolver.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 76ed41308..d60146096 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -23,10 +23,11 @@ export const transactionLinkCode = (date: Date): string => { ) } +const CODE_VALID_DAYS_DURATION = 14 + const transactionLinkExpireDate = (date: Date): Date => { const validUntil = new Date(date) - // valid for 14 days - return new Date(validUntil.setDate(date.getDate() + 14)) + return new Date(validUntil.setDate(date.getDate() + CODE_VALID_DAYS_DURATION)) } @Resolver() @@ -60,8 +61,8 @@ export class TransactionLinkResolver { transactionLink.createdAt = createdDate transactionLink.validUntil = validUntil transactionLink.showEmail = showEmail - await dbTransactionLink.save(transactionLink).catch((error) => { - throw error + await dbTransactionLink.save(transactionLink).catch(() => { + throw new Error('Unable to save transaction link') }) return new TransactionLink(transactionLink, new User(user)) From 91fe38fe8f6675194ae99fc2530696d787d76b28 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 10 Mar 2022 14:28:42 +0100 Subject: [PATCH 13/18] use db values instead of null --- backend/src/graphql/model/TransactionLink.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/model/TransactionLink.ts b/backend/src/graphql/model/TransactionLink.ts index 25cf5ac36..97cded71a 100644 --- a/backend/src/graphql/model/TransactionLink.ts +++ b/backend/src/graphql/model/TransactionLink.ts @@ -15,9 +15,9 @@ export class TransactionLink { this.createdAt = transactionLink.createdAt this.validUntil = transactionLink.validUntil this.showEmail = transactionLink.showEmail - this.deletedAt = null - this.redeemedAt = null - this.redeemedBy = null + this.deletedAt = transactionLink.deletedAt + this.redeemedAt = transactionLink.redeemedAt + this.redeemedBy = transactionLink.redeemedBy } @Field(() => Number) From 9143990535f5cd85f6a065ccf3c95288360a8d62 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 10 Mar 2022 15:05:32 +0100 Subject: [PATCH 14/18] turn of logger when NODE_ENV === '"development"' --- backend/src/server/plugins.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/server/plugins.ts b/backend/src/server/plugins.ts index a407135ea..8d21178f3 100644 --- a/backend/src/server/plugins.ts +++ b/backend/src/server/plugins.ts @@ -40,6 +40,8 @@ const apolloLogPlugin = ApolloLogPlugin({ }) const plugins = - process.env.NODE_ENV === 'development' ? [setHeadersPlugin] : [setHeadersPlugin, apolloLogPlugin] + process.env.NODE_ENV === 'development' || process.env.NODE_ENV === '"development"' + ? [setHeadersPlugin] + : [setHeadersPlugin, apolloLogPlugin] export default plugins From 3436253bbef0ac945430985a3cae22afb0d2b2d6 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 10 Mar 2022 15:33:26 +0100 Subject: [PATCH 15/18] transaction link repository, use raw query to get sum of hold available amount --- backend/src/graphql/model/TransactionLink.ts | 6 ++--- .../graphql/resolver/TransactionResolver.ts | 6 +++-- .../src/typeorm/repository/TransactionLink.ts | 16 ++++++++++++++ backend/src/util/validate.ts | 22 +++++-------------- 4 files changed, 29 insertions(+), 21 deletions(-) create mode 100644 backend/src/typeorm/repository/TransactionLink.ts diff --git a/backend/src/graphql/model/TransactionLink.ts b/backend/src/graphql/model/TransactionLink.ts index 97cded71a..fec02b9fd 100644 --- a/backend/src/graphql/model/TransactionLink.ts +++ b/backend/src/graphql/model/TransactionLink.ts @@ -15,9 +15,9 @@ export class TransactionLink { this.createdAt = transactionLink.createdAt this.validUntil = transactionLink.validUntil this.showEmail = transactionLink.showEmail - this.deletedAt = transactionLink.deletedAt - this.redeemedAt = transactionLink.redeemedAt - this.redeemedBy = transactionLink.redeemedBy + this.deletedAt = null // transactionLink.deletedAt + this.redeemedAt = null // transactionLink.redeemedAt + this.redeemedBy = null // transactionLink.redeemedBy } @Field(() => Number) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index f04050fbf..1d1a00c39 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -19,13 +19,14 @@ import { Order } from '@enum/Order' import { UserRepository } from '@repository/User' import { TransactionRepository } from '@repository/Transaction' +import { TransactionLinkRepository } from '@repository/TransactionLink' import { User as dbUser } from '@entity/User' import { Transaction as dbTransaction } from '@entity/Transaction' import { apiPost } from '@/apis/HttpRequest' import { TransactionTypeId } from '@enum/TransactionTypeId' -import { calculateBalance, isHexPublicKey, holdAvailable } from '@/util/validate' +import { calculateBalance, isHexPublicKey } from '@/util/validate' import { RIGHTS } from '@/auth/RIGHTS' import { User } from '@model/User' import { communityUser } from '@/util/communityUser' @@ -127,7 +128,8 @@ export class TransactionResolver { transactions.push(new Transaction(userTransaction, self, linkedUser)) }) - const toHoldAvailable = await holdAvailable(user.id, now) + const transactionLinkRepository = getCustomRepository(TransactionLinkRepository) + const toHoldAvailable = await transactionLinkRepository.sumAmountToHoldAvailable(user.id, now) // Construct Result return new TransactionList( diff --git a/backend/src/typeorm/repository/TransactionLink.ts b/backend/src/typeorm/repository/TransactionLink.ts new file mode 100644 index 000000000..5f7531bc8 --- /dev/null +++ b/backend/src/typeorm/repository/TransactionLink.ts @@ -0,0 +1,16 @@ +import { Repository, EntityRepository } from '@dbTools/typeorm' +import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' +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') + .where('transactionLinks.userId = :userId', { userId }) + .andWhere('transactionLinks.redeemedAt is NULL') + .andWhere('transactionLinks.validUntil > :date', { date }) + .getRawOne() + return sum ? new Decimal(sum) : new Decimal(0) + } +} diff --git a/backend/src/util/validate.ts b/backend/src/util/validate.ts index c79cca076..0858ea50f 100644 --- a/backend/src/util/validate.ts +++ b/backend/src/util/validate.ts @@ -2,8 +2,8 @@ import { calculateDecay } from './decay' import Decimal from 'decimal.js-light' import { Transaction } from '@entity/Transaction' import { Decay } from '@model/Decay' -import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' -import { MoreThan } from '@dbTools/typeorm' +import { getCustomRepository } from '@dbTools/typeorm' +import { TransactionLinkRepository } from '@repository/TransactionLink' function isStringBoolean(value: string): boolean { const lowerValue = value.toLowerCase() @@ -29,23 +29,13 @@ async function calculateBalance( // TODO why we have to use toString() here? const balance = decay.balance.add(amount.toString()) - const toHoldAvailable = await holdAvailable(userId, time) + const transactionLinkRepository = getCustomRepository(TransactionLinkRepository) + const toHoldAvailable = await transactionLinkRepository.sumAmountToHoldAvailable(userId, time) + if (balance.minus(toHoldAvailable.toString()).lessThan(0)) { return null } return { balance, lastTransactionId: lastTransaction.id, decay } } -async function holdAvailable(userId: number, date: Date): Promise { - const openTransactionLinks = await dbTransactionLink.find({ - select: ['holdAvailableAmount'], - where: { userId, redeemedAt: null, validUntil: MoreThan(date) }, - }) - - return openTransactionLinks.reduce( - (previousValue, currentValue) => previousValue.add(currentValue.holdAvailableAmount.toString()), - new Decimal(0), - ) -} - -export { isHexPublicKey, calculateBalance, isStringBoolean, holdAvailable } +export { isHexPublicKey, calculateBalance, isStringBoolean } From fae9d9100466838e7e72a3b61f7427fe50d0a05a Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 10 Mar 2022 16:00:15 +0100 Subject: [PATCH 16/18] add comment to explain why set to null in constructor --- backend/src/graphql/model/TransactionLink.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/graphql/model/TransactionLink.ts b/backend/src/graphql/model/TransactionLink.ts index fec02b9fd..a87d4a68a 100644 --- a/backend/src/graphql/model/TransactionLink.ts +++ b/backend/src/graphql/model/TransactionLink.ts @@ -15,6 +15,7 @@ export class TransactionLink { this.createdAt = transactionLink.createdAt this.validUntil = transactionLink.validUntil this.showEmail = transactionLink.showEmail + // Type 'Date | null | undefined' is not assignable to type 'Date | null'. this.deletedAt = null // transactionLink.deletedAt this.redeemedAt = null // transactionLink.redeemedAt this.redeemedBy = null // transactionLink.redeemedBy From 80650e60b81a23840f87569529e2d38496f7dbb3 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 10 Mar 2022 18:00:47 +0100 Subject: [PATCH 17/18] remove apollo log plugin hack, improve model --- backend/src/graphql/model/TransactionLink.ts | 9 ++++----- backend/src/server/plugins.ts | 4 +--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/backend/src/graphql/model/TransactionLink.ts b/backend/src/graphql/model/TransactionLink.ts index a87d4a68a..f41d34acc 100644 --- a/backend/src/graphql/model/TransactionLink.ts +++ b/backend/src/graphql/model/TransactionLink.ts @@ -5,7 +5,7 @@ import { User } from './User' @ObjectType() export class TransactionLink { - constructor(transactionLink: dbTransactionLink, user: User) { + constructor(transactionLink: dbTransactionLink, user: User, redeemedBy: User | null = null) { this.id = transactionLink.id this.user = user this.amount = transactionLink.amount @@ -15,10 +15,9 @@ export class TransactionLink { this.createdAt = transactionLink.createdAt this.validUntil = transactionLink.validUntil this.showEmail = transactionLink.showEmail - // Type 'Date | null | undefined' is not assignable to type 'Date | null'. - this.deletedAt = null // transactionLink.deletedAt - this.redeemedAt = null // transactionLink.redeemedAt - this.redeemedBy = null // transactionLink.redeemedBy + this.deletedAt = transactionLink.deletedAt + this.redeemedAt = transactionLink.redeemedAt + this.redeemedBy = redeemedBy } @Field(() => Number) diff --git a/backend/src/server/plugins.ts b/backend/src/server/plugins.ts index 8d21178f3..a407135ea 100644 --- a/backend/src/server/plugins.ts +++ b/backend/src/server/plugins.ts @@ -40,8 +40,6 @@ const apolloLogPlugin = ApolloLogPlugin({ }) const plugins = - process.env.NODE_ENV === 'development' || process.env.NODE_ENV === '"development"' - ? [setHeadersPlugin] - : [setHeadersPlugin, apolloLogPlugin] + process.env.NODE_ENV === 'development' ? [setHeadersPlugin] : [setHeadersPlugin, apolloLogPlugin] export default plugins From 47e491db84b819b751bf784d97646f78624ea02c Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 10 Mar 2022 18:01:13 +0100 Subject: [PATCH 18/18] update entity --- database/entity/0030-transaction_link/TransactionLink.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database/entity/0030-transaction_link/TransactionLink.ts b/database/entity/0030-transaction_link/TransactionLink.ts index 6ea708547..177f23561 100644 --- a/database/entity/0030-transaction_link/TransactionLink.ts +++ b/database/entity/0030-transaction_link/TransactionLink.ts @@ -42,7 +42,7 @@ export class TransactionLink extends BaseEntity { createdAt: Date @DeleteDateColumn() - deletedAt?: Date | null + deletedAt: Date | null @Column({ type: 'datetime', @@ -61,8 +61,8 @@ export class TransactionLink extends BaseEntity { type: 'datetime', nullable: true, }) - redeemedAt?: Date | null + redeemedAt: Date | null @Column({ type: 'int', unsigned: true, nullable: true }) - redeemedBy?: number | null + redeemedBy: number | null }