Merge branch 'master' into refactor_arithmetic_transactionlist

This commit is contained in:
Alexander Friedland 2022-03-03 19:23:25 +01:00 committed by Ulf Gebhardt
commit 6d17786c59
Signed by: ulfgebhardt
GPG Key ID: DA6B843E748679C9
8 changed files with 79 additions and 95 deletions

View File

@ -17,6 +17,6 @@ export class Balance {
@Field(() => Decimal)
decay: Decimal
@Field(() => String)
decayDate: string
@Field(() => Date)
decayDate: Date
}

View File

@ -1,6 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
import { ObjectType, Field, Int } from 'type-graphql'
import Decimal from 'decimal.js-light'
@ObjectType()
@ -31,6 +29,6 @@ export class Decay {
@Field(() => Date, { nullable: true })
end: Date | null
@Field(() => Number, { nullable: true })
@Field(() => Int, { nullable: true })
duration: number | null
}

View File

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
import { Decay } from './Decay'
import { Transaction as dbTransaction } from '@entity/Transaction'
@ -25,7 +23,7 @@ export class Transaction {
transaction.decay,
transaction.decayStart,
transaction.balanceDate,
(transaction.balanceDate.getTime() - transaction.decayStart.getTime()) / 1000,
Math.round((transaction.balanceDate.getTime() - transaction.decayStart.getTime()) / 1000),
)
}
this.memo = transaction.memo

View File

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
import CONFIG from '../../config'
import Decimal from 'decimal.js-light'

View File

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
import { KlickTipp } from './KlickTipp'
import { User as dbUser } from '@entity/User'

View File

@ -307,13 +307,13 @@ export class AdminResolver {
const receivedCallDate = new Date()
const transactionRepository = getCustomRepository(TransactionRepository)
const lastUserTransaction = await transactionRepository.findLastForUser(pendingCreation.userId)
const lastTransaction = await transactionRepository.findLastForUser(pendingCreation.userId)
let newBalance = new Decimal(0)
if (lastUserTransaction) {
if (lastTransaction) {
newBalance = calculateDecay(
lastUserTransaction.balance,
lastUserTransaction.balanceDate,
lastTransaction.balance,
lastTransaction.balanceDate,
receivedCallDate,
).balance
}
@ -367,7 +367,7 @@ async function getUserCreations(ids: number[], includePending = true): Promise<C
SELECT MONTH(date) AS month, sum(amount) AS sum, userId AS id FROM
(SELECT creation_date AS date, amount AS amount, user_id AS userId FROM transactions
WHERE user_id IN (${ids.toString()})
AND transaction_type_id = ${TransactionTypeId.CREATION}
AND type_id = ${TransactionTypeId.CREATION}
AND creation_date >= ${dateFilter}
${unionString}) AS result
GROUP BY month, userId

View File

@ -30,7 +30,8 @@ import { RIGHTS } from '../../auth/RIGHTS'
import { User } from '../model/User'
import { communityUser } from '../../util/communityUser'
import { virtualDecayTransaction } from '../../util/virtualDecayTransaction'
// import Decimal from '../scalar/Decimal'
import Decimal from 'decimal.js-light'
import { calculateDecay } from '../../util/decay'
@Resolver()
export class TransactionResolver {
@ -47,6 +48,7 @@ export class TransactionResolver {
}: Paginated,
@Ctx() context: any,
): Promise<TransactionList> {
const now = new Date()
// find user
const userRepository = getCustomRepository(UserRepository)
// TODO: separate those usecases - this is a security issue
@ -60,65 +62,6 @@ export class TransactionResolver {
{ order: { balanceDate: 'DESC' } },
)
if (!lastTransaction) {
// TODO Have proper return type here
throw new Error('User has no transactions')
}
// find transactions
const limit = currentPage === 1 && order === Order.DESC ? pageSize - 1 : pageSize
const offset =
currentPage === 1 ? 0 : (currentPage - 1) * pageSize - (order === Order.DESC ? 1 : 0)
const transactionRepository = getCustomRepository(TransactionRepository)
const [userTransactions, userTransactionsCount] = await transactionRepository.findByUserPaged(
user.id,
limit,
offset,
order,
onlyCreations,
)
// find involved users
let involvedUserIds: number[] = []
userTransactions.forEach((transaction: dbTransaction) => {
involvedUserIds.push(transaction.userId)
if (transaction.linkedUserId) {
involvedUserIds.push(transaction.linkedUserId)
}
})
// remove duplicates
involvedUserIds = involvedUserIds.filter((value, index, self) => self.indexOf(value) === index)
// We need to show the name for deleted users for old transactions
const involvedDbUsers = await dbUser
.createQueryBuilder()
.withDeleted()
.where('id IN (:...userIds)', { userIds: involvedUserIds })
.getMany()
const involvedUsers = involvedDbUsers.map((u) => new User(u))
const self = new User(user)
const transactions: Transaction[] = []
// decay transaction
if (currentPage === 1 && order === Order.DESC) {
const now = new Date()
transactions.push(
virtualDecayTransaction(lastTransaction.balance, lastTransaction.balanceDate, now, self),
)
}
// transactions
for (let i = 0; i < userTransactions.length; i++) {
const userTransaction = userTransactions[i]
let linkedUser = null
if (userTransaction.typeId === TransactionTypeId.CREATION) {
linkedUser = communityUser
} else {
linkedUser = involvedUsers.find((u) => u.id === userTransaction.linkedUserId)
}
transactions.push(new Transaction(userTransaction, self, linkedUser))
}
// get GDT
let balanceGDT = null
try {
@ -134,9 +77,59 @@ export class TransactionResolver {
console.log('Could not query GDT Server', err)
}
if (!lastTransaction) {
return new TransactionList(new Decimal(0), [], 0, balanceGDT)
}
// find transactions
// first page can contain 26 due to virtual decay transaction
const offset = (currentPage - 1) * pageSize
const transactionRepository = getCustomRepository(TransactionRepository)
const [userTransactions, userTransactionsCount] = await transactionRepository.findByUserPaged(
user.id,
pageSize,
offset,
order,
onlyCreations,
)
// find involved users; I am involved
const involvedUserIds: number[] = [user.id]
userTransactions.forEach((transaction: dbTransaction) => {
if (transaction.linkedUserId && !involvedUserIds.includes(transaction.linkedUserId)) {
involvedUserIds.push(transaction.linkedUserId)
}
})
// We need to show the name for deleted users for old transactions
const involvedDbUsers = await dbUser
.createQueryBuilder()
.withDeleted()
.where('id IN (:...userIds)', { userIds: involvedUserIds })
.getMany()
const involvedUsers = involvedDbUsers.map((u) => new User(u))
const self = new User(user)
const transactions: Transaction[] = []
// decay transaction
if (currentPage === 1 && order === Order.DESC) {
transactions.push(
virtualDecayTransaction(lastTransaction.balance, lastTransaction.balanceDate, now, self),
)
}
// transactions
userTransactions.forEach((userTransaction) => {
const linkedUser =
userTransaction.typeId === TransactionTypeId.CREATION
? communityUser
: involvedUsers.find((u) => u.id === userTransaction.linkedUserId)
transactions.push(new Transaction(userTransaction, self, linkedUser))
})
// Construct Result
return new TransactionList(
lastTransaction.balance,
calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now).balance,
transactions,
userTransactionsCount,
balanceGDT,
@ -184,7 +177,7 @@ export class TransactionResolver {
transactionSend.memo = memo
transactionSend.userId = senderUser.id
transactionSend.linkedUserId = recipientUser.id
transactionSend.amount = amount
transactionSend.amount = amount.mul(-1)
transactionSend.balance = sendBalance.balance
transactionSend.balanceDate = receivedCallDate
transactionSend.decay = sendBalance.decay.decay

View File

@ -7,36 +7,35 @@ describe('utils/decay', () => {
it('has base 0.99999997802044727', () => {
const amount = new Decimal(1.0)
const seconds = 1
expect(decayFormula(amount, seconds)).toBe(0.99999997802044727)
})
// Not sure if the following skiped tests make sence!?
it('has negative decay?', async () => {
const amount = new Decimal(1.0)
const seconds = 1
expect(decayFormula(amount, seconds)).toBe(-0.99999997802044727)
// TODO: toString() was required, we could not compare two decimals
expect(decayFormula(amount, seconds).toString()).toBe('0.999999978035040489732012')
})
it('has correct backward calculation', async () => {
const amount = new Decimal(1.0)
const seconds = -1
expect(decayFormula(amount, seconds)).toBe(1.0000000219795533)
expect(decayFormula(amount, seconds).toString()).toBe('1.000000021964959992727444')
})
// not possible, nodejs hasn't enough accuracy
it('has correct forward calculation', async () => {
const amount = new Decimal(1.0).div(0.99999997802044727)
// we get pretty close, but not exact here, skipping
it.skip('has correct forward calculation', async () => {
const amount = new Decimal(1.0).div(
new Decimal('0.99999997803504048973201202316767079413460520837376'),
)
const seconds = 1
expect(decayFormula(amount, seconds)).toBe(1.0)
expect(decayFormula(amount, seconds).toString()).toBe('1.0')
})
})
it.skip('has base 0.99999997802044727', async () => {
it('has base 0.99999997802044727', async () => {
const now = new Date()
now.setSeconds(1)
const oneSecondAgo = new Date(now.getTime())
oneSecondAgo.setSeconds(0)
expect(calculateDecay(new Decimal(1.0), oneSecondAgo, now)).toBe(0.99999997802044727)
expect(calculateDecay(new Decimal(1.0), oneSecondAgo, now).balance.toString()).toBe(
'0.999999978035040489732012',
)
})
it('returns input amount when from and to is the same', async () => {
const now = new Date()
expect(calculateDecay(new Decimal(100.0), now, now).balance).toBe(100.0)
expect(calculateDecay(new Decimal(100.0), now, now).balance.toString()).toBe('100')
})
})