diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts index 6bcd3fa43..df4d4a2c1 100644 --- a/backend/src/auth/RIGHTS.ts +++ b/backend/src/auth/RIGHTS.ts @@ -20,6 +20,7 @@ export enum RIGHTS { HAS_ELOPAGE = 'HAS_ELOPAGE', CREATE_TRANSACTION_LINK = 'CREATE_TRANSACTION_LINK', QUERY_TRANSACTION_LINK = 'QUERY_TRANSACTION_LINK', + // Admin SEARCH_USERS = 'SEARCH_USERS', CREATE_PENDING_CREATION = 'CREATE_PENDING_CREATION', diff --git a/backend/src/graphql/model/TransactionLink.ts b/backend/src/graphql/model/TransactionLink.ts index 35560136e..f41d34acc 100644 --- a/backend/src/graphql/model/TransactionLink.ts +++ b/backend/src/graphql/model/TransactionLink.ts @@ -5,17 +5,19 @@ 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 + this.holdAvailableAmount = transactionLink.holdAvailableAmount 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 + this.deletedAt = transactionLink.deletedAt + this.redeemedAt = transactionLink.redeemedAt + this.redeemedBy = redeemedBy } @Field(() => Number) 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 3091465d5..afcfe48b2 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -11,20 +11,23 @@ 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 CODE_VALID_DAYS_DURATION = 14 + 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() + CODE_VALID_DAYS_DURATION)) } @Resolver() @@ -38,26 +41,28 @@ 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 sendBalance = await calculateBalance(user.id, amount.mul(-1), createdDate) + const validUntil = transactionLinkExpireDate(createdDate) + + const holdAvailableAmount = amount.minus(calculateDecay(amount, createdDate, validUntil).decay) + + // validate amount + const sendBalance = await calculateBalance(user.id, holdAvailableAmount.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 + await dbTransactionLink.save(transactionLink).catch(() => { + throw new Error('Unable to save transaction link') }) return new TransactionLink(transactionLink, new User(user)) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index d674fc69e..1d1a00c39 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -19,6 +19,7 @@ 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' @@ -127,9 +128,14 @@ 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, + calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now).balance.minus( + toHoldAvailable.toString(), + ), transactions, userTransactionsCount, balanceGDT, 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 3699fe511..0858ea50f 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 { getCustomRepository } from '@dbTools/typeorm' +import { TransactionLinkRepository } from '@repository/TransactionLink' function isStringBoolean(value: string): boolean { const lowerValue = value.toLowerCase() @@ -24,9 +26,13 @@ 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 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 } 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 }