implement listTransactions and neccessary changes for it

This commit is contained in:
Einhornimmond 2021-09-27 18:57:36 +02:00
parent f862736662
commit 06c80ebdfd
10 changed files with 232 additions and 69 deletions

View File

@ -1,29 +1,42 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field, Int } from 'type-graphql' import { ObjectType, Field, Int } from 'type-graphql'
import { Transaction } from '../../typeorm/entity/Transaction'
@ObjectType() @ObjectType()
export class Decay { export class Decay {
constructor(json: any) { constructor()
this.balance = Number(json.balance) constructor(json?: any) {
this.decayStart = json.decay_start if (json) {
this.decayEnd = json.decay_end this.balance = Number(json.balance)
this.decayDuration = json.decay_duration this.decayStart = json.decay_start
this.decayStartBlock = json.decay_start_block this.decayEnd = json.decay_end
this.decayDuration = json.decay_duration
this.decayStartBlock = json.decay_start_block
}
}
static async getDecayStartBlock(): Promise<Transaction | undefined> {
if (!this.decayStartBlockTransaction) {
this.decayStartBlockTransaction = await Transaction.getDecayStartBlock()
}
return this.decayStartBlockTransaction
} }
@Field(() => Number) @Field(() => Number)
balance: number balance: number
@Field(() => Int, { nullable: true }) @Field(() => Int, { nullable: true })
decayStart?: number decayStart: number
@Field(() => Int, { nullable: true }) @Field(() => Int, { nullable: true })
decayEnd?: number decayEnd: number
@Field(() => String, { nullable: true }) @Field(() => String, { nullable: true })
decayDuration?: string decayDuration?: number
@Field(() => Int, { nullable: true }) @Field(() => Int, { nullable: true })
decayStartBlock?: number decayStartBlock?: number
static decayStartBlockTransaction: Transaction | undefined
} }

View File

@ -13,12 +13,12 @@ export class Transaction {
constructor() constructor()
constructor(json: any) constructor(json: any)
constructor(json?: any) { constructor(json?: any) {
if(json) { if (json) {
this.type = json.type this.type = json.type
this.balance = Number(json.balance) this.balance = Number(json.balance)
this.decayStart = json.decay_start this.decayStart = json.decay_start
this.decayEnd = json.decay_end this.decayEnd = json.decay_end
this.decayDuration = json.decay_duration this.decayDuration = parseFloat(json.decay_duration)
this.memo = json.memo this.memo = json.memo
this.transactionId = json.transaction_id this.transactionId = json.transaction_id
this.name = json.name this.name = json.name
@ -44,7 +44,7 @@ export class Transaction {
decayEnd?: number decayEnd?: number
@Field({ nullable: true }) @Field({ nullable: true })
decayDuration?: string decayDuration?: number
@Field(() => String) @Field(() => String)
memo: string memo: string

View File

@ -25,7 +25,9 @@ export class BalanceResolver {
const now = new Date() const now = new Date()
const balance = new Balance({ const balance = new Balance({
balance: roundFloorFrom4(balanceEntity.amount), balance: roundFloorFrom4(balanceEntity.amount),
decay: roundFloorFrom4(calculateDecay(balanceEntity.amount, balanceEntity.recordDate, now)), decay: roundFloorFrom4(
await calculateDecay(balanceEntity.amount, balanceEntity.recordDate, now),
),
decay_date: now.toString(), decay_date: now.toString(),
}) })

View File

@ -2,9 +2,8 @@ import { User as dbUser } from '../../typeorm/entity/User'
import { TransactionList, Transaction } from '../models/Transaction' import { TransactionList, Transaction } from '../models/Transaction'
import { UserTransaction as dbUserTransaction } from '../../typeorm/entity/UserTransaction' import { UserTransaction as dbUserTransaction } from '../../typeorm/entity/UserTransaction'
import { Transaction as dbTransaction } from '../../typeorm/entity/Transaction' import { Transaction as dbTransaction } from '../../typeorm/entity/Transaction'
import { TransactionSendCoin as dbTransactionSendCoin} from '../../typeorm/entity/TransactionSendCoin' import { Decay } from '../models/Decay'
import { TransactionCreation as dbTransactionCreation} from '../../typeorm/entity/TransactionCreation' import { calculateDecayWithInterval } from '../../util/decay'
import calculateDecay from '../../util/decay'
import { roundFloorFrom4 } from '../../util/round' import { roundFloorFrom4 } from '../../util/round'
async function calculateAndAddDecayTransactions( async function calculateAndAddDecayTransactions(
@ -13,9 +12,9 @@ async function calculateAndAddDecayTransactions(
decay: boolean, decay: boolean,
skipFirstTransaction: boolean, skipFirstTransaction: boolean,
): Promise<Transaction[]> { ): Promise<Transaction[]> {
let finalTransactions: Transaction[] = [] const finalTransactions: Transaction[] = []
let transactionIds: number[] = [] const transactionIds: number[] = []
let involvedUserIds: number[] = [] const involvedUserIds: number[] = []
userTransactions.forEach((userTransaction: dbUserTransaction) => { userTransactions.forEach((userTransaction: dbUserTransaction) => {
transactionIds.push(userTransaction.transactionId) transactionIds.push(userTransaction.transactionId)
@ -24,39 +23,120 @@ async function calculateAndAddDecayTransactions(
// remove duplicates // remove duplicates
// https://stackoverflow.com/questions/1960473/get-all-unique-values-in-a-javascript-array-remove-duplicates // https://stackoverflow.com/questions/1960473/get-all-unique-values-in-a-javascript-array-remove-duplicates
const involvedUsersUnique = involvedUserIds.filter((v, i, a) => a.indexOf(v) === i) const involvedUsersUnique = involvedUserIds.filter((v, i, a) => a.indexOf(v) === i)
const userIndiced = dbUser.getUsersIndiced(involvedUsersUnique) const userIndiced = await dbUser.getUsersIndiced(involvedUsersUnique)
const transactions = await dbTransaction const transactions = await dbTransaction
.createQueryBuilder('transaction') .createQueryBuilder('transaction')
.where('transaction.id IN (:...transactions)', { transactions: transactionIds}) .where('transaction.id IN (:...transactions)', { transactions: transactionIds })
.leftJoinAndSelect('transaction.sendCoin', 'transactionSendCoin', 'transactionSendCoin.transactionid = transaction.id') .leftJoinAndSelect(
.leftJoinAndSelect('transaction.creation', 'transactionCreation', 'transactionSendCoin.transactionid = transaction.id') 'transaction.sendCoin',
.getMany() 'transactionSendCoin',
'transactionSendCoin.transactionid = transaction.id',
)
.leftJoinAndSelect(
'transaction.creation',
'transactionCreation',
'transactionSendCoin.transactionid = transaction.id',
)
.getMany()
let transactionIndiced: dbTransaction[] = [] const transactionIndiced: dbTransaction[] = []
transactions.forEach((transaction: dbTransaction) => { transactions.forEach((transaction: dbTransaction) => {
transactionIndiced[transaction.id] = transaction transactionIndiced[transaction.id] = transaction
}) })
const decayStartTransaction = await dbTransaction.createQueryBuilder('transaction') const decayStartTransaction = await Decay.getDecayStartBlock()
.where('transaction.transactionTypeId = :transactionTypeId', { transactionTypeId: 9})
.orderBy('received', 'ASC')
.getOne()
userTransactions.forEach((userTransaction: dbUserTransaction, i:number) => { userTransactions.forEach(async (userTransaction: dbUserTransaction, i: number) => {
const transaction = transactionIndiced[userTransaction.transactionId] const transaction = transactionIndiced[userTransaction.transactionId]
let finalTransaction = new Transaction const finalTransaction = new Transaction()
finalTransaction.transactionId = transaction.id finalTransaction.transactionId = transaction.id
finalTransaction.date = transaction.received.toString() finalTransaction.date = transaction.received.toString()
finalTransaction.memo = transaction.memo finalTransaction.memo = transaction.memo
finalTransaction.totalBalance = roundFloorFrom4(userTransaction.balance)
let prev = i > 0 ? userTransactions[i-1] : null const prev = i > 0 ? userTransactions[i - 1] : null
if(prev && prev.balance > 0) { if (prev && prev.balance > 0) {
const current = userTransaction
const decay = await calculateDecayWithInterval(
prev.balance,
prev.balanceDate,
current.balanceDate,
)
const balance = prev.balance - decay.balance
if (balance) {
finalTransaction.decay = decay
finalTransaction.decay.balance = roundFloorFrom4(finalTransaction.decay.balance)
finalTransaction.decay.balance = roundFloorFrom4(balance)
if (
decayStartTransaction &&
prev.transactionId < decayStartTransaction.id &&
current.transactionId > decayStartTransaction.id
) {
finalTransaction.decay.decayStartBlock = decayStartTransaction.received.getTime()
}
}
} }
}) // sender or receiver when user has sended money
// group name if creation
// type: gesendet / empfangen / geschöpft
// transaktion nr / id
// date
// balance
if (userTransaction.transactionTypeId === 1) {
// creation
const creation = transaction.transactionCreation
finalTransaction.name = 'Gradido Akademie'
finalTransaction.type = 'creation'
// finalTransaction.targetDate = creation.targetDate
finalTransaction.balance = roundFloorFrom4(creation.amount)
} else if (userTransaction.transactionTypeId === 2) {
// send coin
const sendCoin = transaction.transactionSendCoin
let otherUser: dbUser | undefined
finalTransaction.balance = roundFloorFrom4(sendCoin.amount)
if (sendCoin.userId === user.id) {
finalTransaction.type = 'send'
otherUser = userIndiced[sendCoin.recipiantUserId]
// finalTransaction.pubkey = sendCoin.recipiantPublic
} else if (sendCoin.recipiantUserId === user.id) {
finalTransaction.type = 'receive'
otherUser = userIndiced[sendCoin.userId]
// finalTransaction.pubkey = sendCoin.senderPublic
} else {
throw new Error('invalid transaction')
}
if (otherUser) {
finalTransaction.name = otherUser.firstName + ' ' + otherUser.lastName
finalTransaction.email = otherUser.email
}
}
if (i > 0 || !skipFirstTransaction) {
finalTransactions.push(finalTransaction)
}
if (i === userTransactions.length - 1 && decay) {
const now = new Date()
const decay = await calculateDecayWithInterval(
userTransaction.balance,
userTransaction.balanceDate,
now.getTime(),
)
const balance = userTransaction.balance - decay.balance
if (balance) {
const decayTransaction = new Transaction()
decayTransaction.type = 'decay'
decayTransaction.balance = roundFloorFrom4(balance)
decayTransaction.decayDuration = decay.decayDuration
decayTransaction.decayStart = decay.decayStart
decayTransaction.decayEnd = decay.decayEnd
finalTransactions.push(decayTransaction)
}
}
return finalTransactions
})
return finalTransactions return finalTransactions
} }
@ -78,7 +158,7 @@ export default async function listTransactions(
if (offset && order === 'ASC') { if (offset && order === 'ASC') {
offset-- offset--
} }
let [userTransactions, userTransactionsCount] = await UserTransaction.findByUserPaged( let [userTransactions, userTransactionsCount] = await dbUserTransaction.findByUserPaged(
user.id, user.id,
limit, limit,
offset, offset,
@ -86,12 +166,12 @@ export default async function listTransactions(
) )
skipFirstTransaction = userTransactionsCount > offset + limit skipFirstTransaction = userTransactionsCount > offset + limit
const decay = !(firstPage > 1) const decay = !(firstPage > 1)
const transactions: Transaction[] = [] let transactions: Transaction[] = []
if (userTransactions.length) { if (userTransactions.length) {
if (order === 'DESC') { if (order === 'DESC') {
userTransactions = userTransactions.reverse() userTransactions = userTransactions.reverse()
} }
let transactions = calculateAndAddDecayTransactions( transactions = await calculateAndAddDecayTransactions(
userTransactions, userTransactions,
user, user,
decay, decay,

View File

@ -1,4 +1,6 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, Timestamp } from 'typeorm' import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm'
import { TransactionCreation } from './TransactionCreation'
import { TransactionSendCoin } from './TransactionSendCoin'
@Entity('transactions') @Entity('transactions')
export class Transaction extends BaseEntity { export class Transaction extends BaseEntity {
@ -15,16 +17,29 @@ export class Transaction extends BaseEntity {
memo: string memo: string
@Column({ type: 'timestamp' }) @Column({ type: 'timestamp' })
received: Timestamp received: Date
@Column({ name: 'blockchain_type_id' }) @Column({ name: 'blockchain_type_id' })
blockchainTypeId: number blockchainTypeId: number
@OneToOne(() => TransactionSendCoin, (transactionSendCoin) => transactionSendCoin.transaction)
transactionSendCoin: TransactionSendCoin
@OneToOne(() => TransactionCreation, (transactionCreation) => transactionCreation.transaction)
transactionCreation: TransactionCreation
static async findByTransactionTypeId(transactionTypeId: number): Promise<Transaction[]> { static async findByTransactionTypeId(transactionTypeId: number): Promise<Transaction[]> {
return this.createQueryBuilder('transaction') return this.createQueryBuilder('transaction')
.where('transaction.transactionTypeId = :transactionTypeId', { transactionTypeId: transactionTypeId}) .where('transaction.transactionTypeId = :transactionTypeId', {
transactionTypeId: transactionTypeId,
})
.getMany() .getMany()
} }
static async getDecayStartBlock(): Promise<Transaction | undefined> {
return this.createQueryBuilder('transaction')
.where('transaction.transactionTypeId = :transactionTypeId', { transactionTypeId: 9 })
.orderBy('received', 'ASC')
.getOne()
}
} }

View File

@ -1,4 +1,12 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, Timestamp, OneToOne, JoinColumn } from 'typeorm' import {
BaseEntity,
Entity,
PrimaryGeneratedColumn,
Column,
Timestamp,
OneToOne,
JoinColumn,
} from 'typeorm'
import { Transaction } from './Transaction' import { Transaction } from './Transaction'
@Entity('transaction_creations') @Entity('transaction_creations')
@ -21,5 +29,4 @@ export class TransactionCreation extends BaseEntity {
@OneToOne(() => Transaction) @OneToOne(() => Transaction)
@JoinColumn() @JoinColumn()
transaction: Transaction transaction: Transaction
} }

View File

@ -15,7 +15,7 @@ export class TransactionSendCoin extends BaseEntity {
@Column({ name: 'state_user_id' }) @Column({ name: 'state_user_id' })
userId: number userId: number
@Column({ name: 'receiver_public_key', type: 'binary', length: 32}) @Column({ name: 'receiver_public_key', type: 'binary', length: 32 })
recipiantPublic: Buffer recipiantPublic: Buffer
@Column({ name: 'receiver_user_id' }) @Column({ name: 'receiver_user_id' })
@ -27,5 +27,4 @@ export class TransactionSendCoin extends BaseEntity {
@OneToOne(() => Transaction) @OneToOne(() => Transaction)
@JoinColumn() @JoinColumn()
transaction: Transaction transaction: Transaction
} }

View File

@ -36,10 +36,10 @@ export class User extends BaseEntity {
static async getUsersIndiced(userIds: number[]): Promise<User[]> { static async getUsersIndiced(userIds: number[]): Promise<User[]> {
const users = await this.createQueryBuilder('user') const users = await this.createQueryBuilder('user')
.select(['user.id', 'user.firstName', 'user.lastName', 'user.email']) .select(['user.id', 'user.firstName', 'user.lastName', 'user.email'])
.where('user.id IN (:...users)', { users: userIds}) .where('user.id IN (:...users)', { users: userIds })
.getMany() .getMany()
let usersIndiced: User[] = [] const usersIndiced: User[] = []
users.forEach((value, index) => { users.forEach((value, index) => {
usersIndiced[index] = value usersIndiced[index] = value
}) })
return usersIndiced return usersIndiced

View File

@ -1,4 +1,4 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, Timestamp } from 'typeorm' import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity('state_user_transactions') @Entity('state_user_transactions')
export class UserTransaction extends BaseEntity { export class UserTransaction extends BaseEntity {
@ -18,7 +18,7 @@ export class UserTransaction extends BaseEntity {
balance: number balance: number
@Column({ name: 'balance_date', type: 'timestamp' }) @Column({ name: 'balance_date', type: 'timestamp' })
balanceDate: Timestamp balanceDate: number
static findByUserPaged( static findByUserPaged(
userId: number, userId: number,

View File

@ -1,4 +1,51 @@
export default function (amount: number, from: Date, to: Date): number { import { Decay } from '../graphql/models/Decay'
const decayDuration = (to.getTime() - from.getTime()) / 1000
return amount * Math.pow(0.99999997802044727, decayDuration) function decayFormula(amount: number, durationInSeconds: number): number {
return amount * Math.pow(0.99999997802044727, durationInSeconds)
} }
async function calculateDecay(amount: number, from: Date, to: Date): Promise<number> {
// load decay start block
const decayStartBlock = await Decay.getDecayStartBlock()
// if decay hasn't started yet we return input amount
if (!decayStartBlock) return amount
const decayDuration = (to.getTime() - from.getTime()) / 1000
return decayFormula(amount, decayDuration)
}
async function calculateDecayWithInterval(
amount: number,
from: number,
to: number,
): Promise<Decay> {
const decayStartBlock = await Decay.getDecayStartBlock()
const result = new Decay()
result.balance = amount
result.decayStart = from
result.decayEnd = from
// (amount, from.getTime(), to.getTime())
// if no decay start block exist or decay startet after end date
if (decayStartBlock === undefined || decayStartBlock.received.getTime() > to) {
return result
}
// if decay start date is before start date we calculate decay for full duration
if (decayStartBlock.received.getTime() < from) {
result.decayDuration = to - from
}
// if decay start in between start date and end date we caculcate decay from decay start time to end date
else {
result.decayDuration = to - decayStartBlock.received.getTime()
}
// js use timestamp in milliseconds but we calculate with seconds
result.decayDuration /= 1000
result.balance = decayFormula(amount, result.decayDuration)
return result
}
export { calculateDecay, calculateDecayWithInterval }