backend is building

This commit is contained in:
Ulf Gebhardt 2022-02-23 21:46:40 +01:00
parent ebde61c664
commit 7a837be7b2
Signed by: ulfgebhardt
GPG Key ID: DA6B843E748679C9
4 changed files with 108 additions and 127 deletions

View File

@ -3,6 +3,7 @@ import { registerEnumType } from 'type-graphql'
export enum TransactionTypeId {
CREATION = 1,
SEND = 2,
RECEIVE = 3,
}
registerEnumType(TransactionTypeId, {

View File

@ -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) {

View File

@ -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<dbUserTransaction> {
): Promise<BigInt> {
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()
}

View File

@ -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<UserTransaction> {
@EntityRepository(Transaction)
export class TransactionRepository extends Repository<Transaction> {
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<UserTransaction> {
.getManyAndCount()
}
findLastForUser(userId: number): Promise<UserTransaction | undefined> {
findLastForUser(userId: number): Promise<Transaction | undefined> {
return this.createQueryBuilder('userTransaction')
.where('userTransaction.userId = :userId', { userId })
.orderBy('userTransaction.transactionId', 'DESC')
.orderBy('userTransaction.balanceDate', 'DESC')
.getOne()
}
}