diff --git a/backend/src/graphql/model/TransactionLink.ts b/backend/src/graphql/model/TransactionLink.ts index 98f86a772..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 @@ -27,6 +28,9 @@ export class TransactionLink { @Field(() => Decimal) amount: Decimal + @Field(() => Decimal) + holdAvailableAmount: Decimal + @Field(() => String) memo: string 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 2ed19fd5f..61926ba28 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, Query, Arg } 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' @@ -11,20 +11,22 @@ 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 => { + const validUntil = new Date(date) // valid for 14 days - return new Date(date.setDate(date.getDate() + 14)) + return new Date(validUntil.setDate(date.getDate() + 14)) } @Resolver() @@ -38,23 +40,39 @@ export class TransactionLinkResolver { const userRepository = getCustomRepository(UserRepository) const user = await userRepository.findByPubkeyHex(context.pubKey) + 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, + ) + // validate amount // TODO taken from transaction resolver, duplicate code - const createdDate = new Date() - const sendBalance = await calculateBalance(user.id, amount.mul(-1), createdDate) + 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 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 diff --git a/database/entity/0030-transaction_link/TransactionLink.ts b/database/entity/0030-transaction_link/TransactionLink.ts index a3ab5cd1a..bb12277d1 100644 --- a/database/entity/0030-transaction_link/TransactionLink.ts +++ b/database/entity/0030-transaction_link/TransactionLink.ts @@ -19,22 +19,30 @@ export class TransactionLink extends BaseEntity { }) amount: Decimal + @Column({ + type: 'decimal', + name: 'hold_available_amount', + precision: 40, + scale: 20, + nullable: false, + transformer: DecimalTransformer, + }) + holdAvailableAmount: Decimal + @Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' }) memo: string - @Column({ length: 96, nullable: false, collation: 'utf8mb4_unicode_ci' }) + @Column({ length: 24, nullable: false, collation: 'utf8mb4_unicode_ci' }) code: string @Column({ type: 'datetime', - default: () => 'CURRENT_TIMESTAMP', nullable: false, }) createdAt: Date @Column({ type: 'datetime', - default: () => 'CURRENT_TIMESTAMP', nullable: false, }) validUntil: Date @@ -48,7 +56,6 @@ export class TransactionLink extends BaseEntity { @Column({ type: 'datetime', - default: () => 'CURRENT_TIMESTAMP', nullable: true, }) redeemedAt?: Date | null diff --git a/database/migrations/0030-transaction_link.ts b/database/migrations/0030-transaction_link.ts index 59eba1090..4d72cf43b 100644 --- a/database/migrations/0030-transaction_link.ts +++ b/database/migrations/0030-transaction_link.ts @@ -9,8 +9,9 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis \`id\` int UNSIGNED NOT NULL AUTO_INCREMENT, \`userId\` int UNSIGNED NOT NULL, \`amount\` DECIMAL(40,20) NOT NULL, + \`hold_available_amount\` DECIMAL(40,20) NOT NULL, \`memo\` varchar(255) NOT NULL, - \`code\` varchar(96) NOT NULL, + \`code\` varchar(24) NOT NULL, \`createdAt\` datetime NOT NULL, \`validUntil\` datetime NOT NULL, \`showEmail\` boolean NOT NULL DEFAULT false,