extract transaction process in function

This commit is contained in:
Moriz Wahl 2022-03-11 15:19:01 +01:00
parent 81cd858eb4
commit 13fe4300d2
3 changed files with 87 additions and 82 deletions

View File

@ -1,4 +1,4 @@
import { ArgsType, Field, Int } from 'type-graphql' import { ArgsType, Field } from 'type-graphql'
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
@ArgsType() @ArgsType()
@ -11,7 +11,4 @@ export default class TransactionSendArgs {
@Field(() => String) @Field(() => String)
memo: string memo: string
@Field(() => Int, { nullable: true })
senderId?: number
} }

View File

@ -13,7 +13,8 @@ import { RIGHTS } from '@/auth/RIGHTS'
import { randomBytes } from 'crypto' import { randomBytes } from 'crypto'
import { User } from '@model/User' import { User } from '@model/User'
import { calculateDecay } from '@/util/decay' import { calculateDecay } from '@/util/decay'
import { TransactionResolver } from './TransactionResolver' import { executeTransaction } from './TransactionResolver'
import { User as dbUser } from '@entity/User'
// TODO: do not export, test it inside the resolver // TODO: do not export, test it inside the resolver
export const transactionLinkCode = (date: Date): string => { export const transactionLinkCode = (date: Date): string => {
@ -130,10 +131,11 @@ export class TransactionLinkResolver {
const userRepository = getCustomRepository(UserRepository) const userRepository = getCustomRepository(UserRepository)
const user = await userRepository.findByPubkeyHex(context.pubKey) const user = await userRepository.findByPubkeyHex(context.pubKey)
const transactionLink = await dbTransactionLink.findOneOrFail({ id }) const transactionLink = await dbTransactionLink.findOneOrFail({ id })
const linkedUser = await dbUser.findOneOrFail({ id: transactionLink.userId })
const now = new Date() const now = new Date()
if (user.id === transactionLink.userId) { if (user.id === linkedUser.id) {
throw new Error('Cannot redeem own transaction link.') throw new Error('Cannot redeem own transaction link.')
} }
@ -145,16 +147,8 @@ export class TransactionLinkResolver {
throw new Error('Transaction Link already redeemed.') throw new Error('Transaction Link already redeemed.')
} }
const transactionResolver = new TransactionResolver() await executeTransaction(transactionLink.amount, transactionLink.memo, linkedUser, user)
transactionResolver.sendCoins(
{
email: user.email,
amount: transactionLink.amount,
memo: transactionLink.memo,
senderId: transactionLink.userId,
},
context,
)
transactionLink.redeemedAt = now transactionLink.redeemedAt = now
transactionLink.redeemedBy = user.id transactionLink.redeemedBy = user.id
transactionLink.save().catch(() => { transactionLink.save().catch(() => {

View File

@ -34,6 +34,83 @@ import { virtualDecayTransaction } from '@/util/virtualDecayTransaction'
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
import { calculateDecay } from '@/util/decay' import { calculateDecay } from '@/util/decay'
export const executeTransaction = async (
amount: Decimal,
memo: string,
sender: dbUser,
recipient: dbUser,
): Promise<boolean> => {
if (sender.id === recipient.id) {
throw new Error('Sender and Recipient are the same.')
}
// validate amount
const receivedCallDate = new Date()
const sendBalance = await calculateBalance(sender.id, amount.mul(-1), receivedCallDate)
if (!sendBalance) {
throw new Error("user hasn't enough GDD or amount is < 0")
}
const queryRunner = getConnection().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('READ UNCOMMITTED')
try {
// transaction
const transactionSend = new dbTransaction()
transactionSend.typeId = TransactionTypeId.SEND
transactionSend.memo = memo
transactionSend.userId = sender.id
transactionSend.linkedUserId = recipient.id
transactionSend.amount = amount.mul(-1)
transactionSend.balance = sendBalance.balance
transactionSend.balanceDate = receivedCallDate
transactionSend.decay = sendBalance.decay.decay
transactionSend.decayStart = sendBalance.decay.start
transactionSend.previous = sendBalance.lastTransactionId
await queryRunner.manager.insert(dbTransaction, transactionSend)
const transactionReceive = new dbTransaction()
transactionReceive.typeId = TransactionTypeId.RECEIVE
transactionReceive.memo = memo
transactionReceive.userId = recipient.id
transactionReceive.linkedUserId = sender.id
transactionReceive.amount = amount
const receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate)
transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount
transactionReceive.balanceDate = receivedCallDate
transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0)
transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null
transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null
transactionReceive.linkedTransactionId = transactionSend.id
await queryRunner.manager.insert(dbTransaction, transactionReceive)
// Save linked transaction id for send
transactionSend.linkedTransactionId = transactionReceive.id
await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend)
await queryRunner.commitTransaction()
} catch (e) {
await queryRunner.rollbackTransaction()
throw new Error(`Transaction was not successful: ${e}`)
} finally {
await queryRunner.release()
}
// send notification email
// TODO: translate
await sendTransactionReceivedEmail({
senderFirstName: sender.firstName,
senderLastName: sender.lastName,
recipientFirstName: recipient.firstName,
recipientLastName: recipient.lastName,
email: recipient.email,
amount,
memo,
})
return true
}
@Resolver() @Resolver()
export class TransactionResolver { export class TransactionResolver {
@Authorized([RIGHTS.TRANSACTION_LIST]) @Authorized([RIGHTS.TRANSACTION_LIST])
@ -145,23 +222,15 @@ export class TransactionResolver {
@Authorized([RIGHTS.SEND_COINS]) @Authorized([RIGHTS.SEND_COINS])
@Mutation(() => String) @Mutation(() => String)
async sendCoins( async sendCoins(
@Args() { email, amount, memo, senderId = 0 }: TransactionSendArgs, @Args() { email, amount, memo }: TransactionSendArgs,
@Ctx() context: any, @Ctx() context: any,
): Promise<boolean> { ): Promise<boolean> {
// TODO this is subject to replay attacks // TODO this is subject to replay attacks
const userRepository = getCustomRepository(UserRepository) const userRepository = getCustomRepository(UserRepository)
const senderUser = senderId const senderUser = await userRepository.findByPubkeyHex(context.pubKey)
? await dbUser.findOneOrFail({ id: senderId })
: await userRepository.findByPubkeyHex(context.pubKey)
if (senderUser.pubKey.length !== 32) { if (senderUser.pubKey.length !== 32) {
throw new Error('invalid sender public key') throw new Error('invalid sender public key')
} }
// validate amount
const receivedCallDate = new Date()
const sendBalance = await calculateBalance(senderUser.id, amount.mul(-1), receivedCallDate)
if (!sendBalance) {
throw new Error("user hasn't enough GDD or amount is < 0")
}
// validate recipient user // validate recipient user
const recipientUser = await dbUser.findOne({ email: email }, { withDeleted: true }) const recipientUser = await dbUser.findOne({ email: email }, { withDeleted: true })
@ -175,62 +244,7 @@ export class TransactionResolver {
throw new Error('invalid recipient public key') throw new Error('invalid recipient public key')
} }
const queryRunner = getConnection().createQueryRunner() await executeTransaction(amount, memo, senderUser, recipientUser)
await queryRunner.connect()
await queryRunner.startTransaction('READ UNCOMMITTED')
try {
// transaction
const transactionSend = new dbTransaction()
transactionSend.typeId = TransactionTypeId.SEND
transactionSend.memo = memo
transactionSend.userId = senderUser.id
transactionSend.linkedUserId = recipientUser.id
transactionSend.amount = amount.mul(-1)
transactionSend.balance = sendBalance.balance
transactionSend.balanceDate = receivedCallDate
transactionSend.decay = sendBalance.decay.decay
transactionSend.decayStart = sendBalance.decay.start
transactionSend.previous = sendBalance.lastTransactionId
await queryRunner.manager.insert(dbTransaction, transactionSend)
const transactionReceive = new dbTransaction()
transactionReceive.typeId = TransactionTypeId.RECEIVE
transactionReceive.memo = memo
transactionReceive.userId = recipientUser.id
transactionReceive.linkedUserId = senderUser.id
transactionReceive.amount = amount
const receiveBalance = await calculateBalance(recipientUser.id, amount, receivedCallDate)
transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount
transactionReceive.balanceDate = receivedCallDate
transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0)
transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null
transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null
transactionReceive.linkedTransactionId = transactionSend.id
await queryRunner.manager.insert(dbTransaction, transactionReceive)
// Save linked transaction id for send
transactionSend.linkedTransactionId = transactionReceive.id
await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend)
await queryRunner.commitTransaction()
} catch (e) {
await queryRunner.rollbackTransaction()
throw new Error(`Transaction was not successful: ${e}`)
} finally {
await queryRunner.release()
}
// send notification email
// TODO: translate
await sendTransactionReceivedEmail({
senderFirstName: senderUser.firstName,
senderLastName: senderUser.lastName,
recipientFirstName: recipientUser.firstName,
recipientLastName: recipientUser.lastName,
email: recipientUser.email,
amount,
memo,
})
return true return true
} }