From 7a837be7b28064b4bb474e7317b922043919afee Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 23 Feb 2022 21:46:40 +0100 Subject: [PATCH] backend is building --- backend/src/graphql/enum/TransactionTypeId.ts | 1 + backend/src/graphql/resolver/AdminResolver.ts | 50 ++--- .../graphql/resolver/TransactionResolver.ts | 172 +++++++++--------- .../{UserTransaction.ts => Transaction.ts} | 12 +- 4 files changed, 108 insertions(+), 127 deletions(-) rename backend/src/typeorm/repository/{UserTransaction.ts => Transaction.ts} (76%) diff --git a/backend/src/graphql/enum/TransactionTypeId.ts b/backend/src/graphql/enum/TransactionTypeId.ts index 4ff3671cf..b5b75aa20 100644 --- a/backend/src/graphql/enum/TransactionTypeId.ts +++ b/backend/src/graphql/enum/TransactionTypeId.ts @@ -3,6 +3,7 @@ import { registerEnumType } from 'type-graphql' export enum TransactionTypeId { CREATION = 1, SEND = 2, + RECEIVE = 3, } registerEnumType(TransactionTypeId, { diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 0b8e3f52b..f547e1b3f 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -13,8 +13,7 @@ import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs' import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs' import SearchUsersArgs from '../arg/SearchUsersArgs' import { Transaction } from '@entity/Transaction' -import { UserTransaction } from '@entity/UserTransaction' -import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction' +import { TransactionRepository } from '../../typeorm/repository/Transaction' import { calculateDecay } from '../../util/decay' import { AdminPendingCreation } from '@entity/AdminPendingCreation' import { hasElopageBuys } from '../../util/hasElopageBuys' @@ -22,6 +21,7 @@ import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' import { User } from '@entity/User' import { TransactionTypeId } from '../enum/TransactionTypeId' import { Balance } from '@entity/Balance' +import { randomInt } from 'crypto' // const EMAIL_OPT_IN_REGISTER = 1 // const EMAIL_OPT_UNKNOWN = 3 // elopage? @@ -246,6 +246,20 @@ export class AdminResolver { } const receivedCallDate = new Date() + + const transactionRepository = getCustomRepository(TransactionRepository) + const lastUserTransaction = await transactionRepository.findLastForUser(pendingCreation.userId) + + let newBalance = 0 + if (lastUserTransaction) { + newBalance = calculateDecay( + Number(lastUserTransaction.balance), + lastUserTransaction.balanceDate, + receivedCallDate, + ).balance + } + newBalance = Number(newBalance) + Number(parseInt(pendingCreation.amount.toString())) + let transaction = new Transaction() transaction.transactionTypeId = TransactionTypeId.CREATION transaction.memo = pendingCreation.memo @@ -253,35 +267,11 @@ export class AdminResolver { transaction.userId = pendingCreation.userId transaction.amount = BigInt(parseInt(pendingCreation.amount.toString())) transaction.creationDate = pendingCreation.date + transaction.transactionId = randomInt(99999) + transaction.balance = BigInt(newBalance) + transaction.balanceDate = receivedCallDate transaction = await transaction.save() - if (!transaction) throw new Error('Could not create transaction') - - const userTransactionRepository = getCustomRepository(UserTransactionRepository) - const lastUserTransaction = await userTransactionRepository.findLastForUser( - pendingCreation.userId, - ) - let newBalance = 0 - if (!lastUserTransaction) { - newBalance = 0 - } else { - newBalance = calculateDecay( - lastUserTransaction.balance, - lastUserTransaction.balanceDate, - receivedCallDate, - ).balance - } - newBalance = Number(newBalance) + Number(parseInt(pendingCreation.amount.toString())) - - const newUserTransaction = new UserTransaction() - newUserTransaction.userId = pendingCreation.userId - newUserTransaction.transactionId = transaction.id - newUserTransaction.transactionTypeId = transaction.transactionTypeId - newUserTransaction.balance = Number(newBalance) - newUserTransaction.balanceDate = transaction.received - - await userTransactionRepository.save(newUserTransaction).catch((error) => { - throw new Error('Error saving user transaction: ' + error) - }) + // if (!transaction) throw new Error('Could not create transaction') let userBalance = await Balance.findOne({ userId: pendingCreation.userId }) if (!userBalance) { diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 1a1f20251..34443d9ae 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -17,10 +17,9 @@ import Paginated from '../arg/Paginated' import { Order } from '../enum/Order' import { UserRepository } from '../../typeorm/repository/User' -import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction' +import { TransactionRepository } from '../../typeorm/repository/Transaction' import { User as dbUser } from '@entity/User' -import { UserTransaction as dbUserTransaction } from '@entity/UserTransaction' import { Transaction as dbTransaction } from '@entity/Transaction' import { Balance as dbBalance } from '@entity/Balance' @@ -31,10 +30,11 @@ import { TransactionTypeId } from '../enum/TransactionTypeId' import { TransactionType } from '../enum/TransactionType' import { hasUserAmount, isHexPublicKey } from '../../util/validate' import { RIGHTS } from '../../auth/RIGHTS' +import { randomInt } from 'crypto' // Helper function async function calculateAndAddDecayTransactions( - userTransactions: dbUserTransaction[], + userTransactions: dbTransaction[], user: dbUser, decay: boolean, skipFirstTransaction: boolean, @@ -43,7 +43,7 @@ async function calculateAndAddDecayTransactions( const transactionIds: number[] = [] const involvedUserIds: number[] = [] - userTransactions.forEach((userTransaction: dbUserTransaction) => { + userTransactions.forEach((userTransaction: dbTransaction) => { transactionIds.push(userTransaction.transactionId) }) @@ -52,9 +52,12 @@ async function calculateAndAddDecayTransactions( transactions.forEach((transaction: dbTransaction) => { transactionIndiced[transaction.id] = transaction involvedUserIds.push(transaction.userId) - if (transaction.transactionTypeId === TransactionTypeId.SEND) { + if ( + transaction.transactionTypeId === TransactionTypeId.SEND || + transaction.transactionTypeId === TransactionTypeId.RECEIVE + ) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - involvedUserIds.push(transaction.sendReceiverUserId!) // TODO ensure not null properly + involvedUserIds.push(transaction.linkedUserId!) // TODO ensure not null properly } }) // remove duplicates @@ -70,17 +73,17 @@ async function calculateAndAddDecayTransactions( finalTransaction.transactionId = transaction.id finalTransaction.date = transaction.received.toISOString() finalTransaction.memo = transaction.memo - finalTransaction.totalBalance = roundFloorFrom4(userTransaction.balance) + finalTransaction.totalBalance = roundFloorFrom4(Number(userTransaction.balance)) const previousTransaction = i > 0 ? userTransactions[i - 1] : null if (previousTransaction) { const currentTransaction = userTransaction const decay = calculateDecay( - previousTransaction.balance, + Number(previousTransaction.balance), previousTransaction.balanceDate, currentTransaction.balanceDate, ) - const balance = previousTransaction.balance - decay.balance + const balance = Number(previousTransaction.balance) - decay.balance if (CONFIG.DECAY_START_TIME < currentTransaction.balanceDate) { finalTransaction.decay = decay @@ -110,23 +113,23 @@ async function calculateAndAddDecayTransactions( finalTransaction.balance = roundFloorFrom4(Number(transaction.amount)) // Todo unsafe conversion } else if (userTransaction.transactionTypeId === TransactionTypeId.SEND) { // send coin - let otherUser: dbUser | undefined + const otherUser = userIndiced.find((u) => u.id === transaction.linkedUserId) finalTransaction.balance = roundFloorFrom4(Number(transaction.amount)) // Todo unsafe conversion - if (transaction.userId === user.id) { - finalTransaction.type = TransactionType.SEND - otherUser = userIndiced.find((u) => u.id === transaction.sendReceiverUserId) - // finalTransaction.pubkey = sendCoin.recipiantPublic - } else if (transaction.sendReceiverUserId === user.id) { - finalTransaction.type = TransactionType.RECIEVE - otherUser = userIndiced.find((u) => u.id === transaction.userId) - // finalTransaction.pubkey = sendCoin.senderPublic - } else { - throw new Error('invalid transaction') - } + finalTransaction.type = TransactionType.SEND if (otherUser) { finalTransaction.name = otherUser.firstName + ' ' + otherUser.lastName finalTransaction.email = otherUser.email } + } else if (userTransaction.transactionTypeId === TransactionTypeId.RECEIVE) { + const otherUser = userIndiced.find((u) => u.id === transaction.linkedUserId) + finalTransaction.balance = roundFloorFrom4(Number(transaction.amount)) // Todo unsafe conversion + finalTransaction.type = TransactionType.RECIEVE + if (otherUser) { + finalTransaction.name = otherUser.firstName + ' ' + otherUser.lastName + finalTransaction.email = otherUser.email + } + } else { + throw new Error('invalid transaction') } if (i > 0 || !skipFirstTransaction) { finalTransactions.push(finalTransaction) @@ -134,8 +137,12 @@ async function calculateAndAddDecayTransactions( if (i === userTransactions.length - 1 && decay) { const now = new Date() - const decay = calculateDecay(userTransaction.balance, userTransaction.balanceDate, now) - const balance = userTransaction.balance - decay.balance + const decay = calculateDecay( + Number(userTransaction.balance), + userTransaction.balanceDate, + now, + ) + const balance = Number(userTransaction.balance) - decay.balance const decayTransaction = new Transaction() decayTransaction.type = 'decay' @@ -176,22 +183,20 @@ async function updateStateBalance( }) } -// helper helper function -async function addUserTransaction( - user: dbUser, - transaction: dbTransaction, +async function calculateNewBalance( + userId: number, + transactionDate: Date, centAmount: number, - queryRunner: QueryRunner, -): Promise { +): Promise { let newBalance = centAmount - const userTransactionRepository = getCustomRepository(UserTransactionRepository) - const lastUserTransaction = await userTransactionRepository.findLastForUser(user.id) + const transactionRepository = getCustomRepository(TransactionRepository) + const lastUserTransaction = await transactionRepository.findLastForUser(userId) if (lastUserTransaction) { newBalance += Number( calculateDecay( Number(lastUserTransaction.balance), lastUserTransaction.balanceDate, - transaction.received, + transactionDate, ).balance, ) } @@ -200,16 +205,7 @@ async function addUserTransaction( throw new Error('error new balance <= 0') } - const newUserTransaction = new dbUserTransaction() - newUserTransaction.userId = user.id - newUserTransaction.transactionId = transaction.id - newUserTransaction.transactionTypeId = transaction.transactionTypeId - newUserTransaction.balance = newBalance - newUserTransaction.balanceDate = transaction.received - - return queryRunner.manager.save(newUserTransaction).catch((error) => { - throw new Error('Error saving user transaction: ' + error) - }) + return BigInt(newBalance) } @Resolver() @@ -242,9 +238,14 @@ export class TransactionResolver { if (offset && order === Order.ASC) { offset-- } - const userTransactionRepository = getCustomRepository(UserTransactionRepository) - const [userTransactions, userTransactionsCount] = - await userTransactionRepository.findByUserPaged(user.id, limit, offset, order, onlyCreations) + const transactionRepository = getCustomRepository(TransactionRepository) + const [userTransactions, userTransactionsCount] = await transactionRepository.findByUserPaged( + user.id, + limit, + offset, + order, + onlyCreations, + ) skipFirstTransaction = userTransactionsCount > offset + limit const decay = !(currentPage > 1) let transactions: Transaction[] = [] @@ -326,39 +327,49 @@ export class TransactionResolver { await queryRunner.connect() await queryRunner.startTransaction('READ UNCOMMITTED') try { + const receivedCallDate = new Date() // transaction - const transaction = new dbTransaction() - transaction.transactionTypeId = TransactionTypeId.SEND - transaction.memo = memo - transaction.userId = senderUser.id - transaction.pubkey = senderUser.pubKey - transaction.sendReceiverUserId = recipientUser.id - transaction.sendReceiverPublicKey = recipientUser.pubKey - transaction.amount = BigInt(centAmount) - - await queryRunner.manager.insert(dbTransaction, transaction) - - // Insert Transaction: sender - amount - const senderUserTransactionBalance = await addUserTransaction( - senderUser, - transaction, + const transactionSend = new dbTransaction() + transactionSend.transactionTypeId = TransactionTypeId.SEND + transactionSend.memo = memo + transactionSend.userId = senderUser.id + transactionSend.pubkey = senderUser.pubKey + transactionSend.linkedUserId = recipientUser.id + transactionSend.amount = BigInt(centAmount) + transactionSend.received = receivedCallDate + transactionSend.transactionId = randomInt(99999) + transactionSend.balance = await calculateNewBalance( + senderUser.id, + receivedCallDate, -centAmount, - queryRunner, ) + transactionSend.balanceDate = receivedCallDate + transactionSend.sendSenderFinalBalance = transactionSend.balance + await queryRunner.manager.insert(dbTransaction, transactionSend) - // Insert Transaction: recipient + amount - const recipiantUserTransactionBalance = await addUserTransaction( - recipientUser, - transaction, + const transactionReceive = new dbTransaction() + transactionReceive.transactionTypeId = TransactionTypeId.RECEIVE + transactionReceive.memo = memo + transactionReceive.userId = recipientUser.id + transactionReceive.pubkey = recipientUser.pubKey + transactionReceive.linkedUserId = senderUser.id + transactionReceive.amount = BigInt(centAmount) + transactionReceive.received = receivedCallDate + transactionReceive.transactionId = randomInt(99999) + transactionReceive.balance = await calculateNewBalance( + senderUser.id, + receivedCallDate, centAmount, - queryRunner, ) + transactionReceive.balanceDate = receivedCallDate + transactionReceive.sendSenderFinalBalance = transactionSend.balance + await queryRunner.manager.insert(dbTransaction, transactionReceive) // Update Balance: sender - amount const senderStateBalance = await updateStateBalance( senderUser, -centAmount, - transaction.received, + receivedCallDate, queryRunner, ) @@ -366,41 +377,20 @@ export class TransactionResolver { const recipiantStateBalance = await updateStateBalance( recipientUser, centAmount, - transaction.received, + receivedCallDate, queryRunner, ) - if (senderStateBalance.amount !== senderUserTransactionBalance.balance) { + if (senderStateBalance.amount !== Number(transactionSend.balance)) { throw new Error('db data corrupted, sender') } - if (recipiantStateBalance.amount !== recipiantUserTransactionBalance.balance) { + if (recipiantStateBalance.amount !== Number(transactionReceive.balance)) { throw new Error('db data corrupted, recipiant') } - // TODO: WTF? - // I just assume that due to implicit type conversion the decimal places were cut. - // Using `Math.trunc` to simulate this behaviour - transaction.sendSenderFinalBalance = BigInt(Math.trunc(senderStateBalance.amount)) - - await queryRunner.manager.save(transaction).catch((error) => { - throw new Error('error saving transaction with tx hash: ' + error) - }) - await queryRunner.commitTransaction() } catch (e) { await queryRunner.rollbackTransaction() - // TODO: This is broken code - we should never correct an autoincrement index in production - // according to dario it is required tho to properly work. The index of the table is used as - // index for the transaction which requires a chain without gaps - const count = await queryRunner.manager.count(dbTransaction) - // fix autoincrement value which seems not effected from rollback - await queryRunner - .query('ALTER TABLE `transactions` auto_increment = ?', [count]) - .catch((error) => { - // eslint-disable-next-line no-console - console.log('problems with reset auto increment: %o', error) - }) - throw e } finally { await queryRunner.release() } diff --git a/backend/src/typeorm/repository/UserTransaction.ts b/backend/src/typeorm/repository/Transaction.ts similarity index 76% rename from backend/src/typeorm/repository/UserTransaction.ts rename to backend/src/typeorm/repository/Transaction.ts index d699d07ea..2abcb4090 100644 --- a/backend/src/typeorm/repository/UserTransaction.ts +++ b/backend/src/typeorm/repository/Transaction.ts @@ -1,17 +1,17 @@ import { EntityRepository, Repository } from '@dbTools/typeorm' +import { Transaction } from '@entity/Transaction' import { Order } from '../../graphql/enum/Order' -import { UserTransaction } from '@entity/UserTransaction' import { TransactionTypeId } from '../../graphql/enum/TransactionTypeId' -@EntityRepository(UserTransaction) -export class UserTransactionRepository extends Repository { +@EntityRepository(Transaction) +export class TransactionRepository extends Repository { findByUserPaged( userId: number, limit: number, offset: number, order: Order, onlyCreation?: boolean, - ): Promise<[UserTransaction[], number]> { + ): Promise<[Transaction[], number]> { if (onlyCreation) { return this.createQueryBuilder('userTransaction') .where('userTransaction.userId = :userId', { userId }) @@ -31,10 +31,10 @@ export class UserTransactionRepository extends Repository { .getManyAndCount() } - findLastForUser(userId: number): Promise { + findLastForUser(userId: number): Promise { return this.createQueryBuilder('userTransaction') .where('userTransaction.userId = :userId', { userId }) - .orderBy('userTransaction.transactionId', 'DESC') + .orderBy('userTransaction.balanceDate', 'DESC') .getOne() } }