mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into frontend-generate-link-for-send-gdd
This commit is contained in:
commit
82fad1f8b0
@ -21,6 +21,7 @@ export enum RIGHTS {
|
||||
CREATE_TRANSACTION_LINK = 'CREATE_TRANSACTION_LINK',
|
||||
DELETE_TRANSACTION_LINK = 'DELETE_TRANSACTION_LINK',
|
||||
QUERY_TRANSACTION_LINK = 'QUERY_TRANSACTION_LINK',
|
||||
LIST_TRANSACTION_LINKS = 'LIST_TRANSACTION_LINKS',
|
||||
// Admin
|
||||
SEARCH_USERS = 'SEARCH_USERS',
|
||||
CREATE_PENDING_CREATION = 'CREATE_PENDING_CREATION',
|
||||
|
||||
@ -20,6 +20,7 @@ export const ROLE_USER = new Role('user', [
|
||||
RIGHTS.HAS_ELOPAGE,
|
||||
RIGHTS.CREATE_TRANSACTION_LINK,
|
||||
RIGHTS.DELETE_TRANSACTION_LINK,
|
||||
RIGHTS.LIST_TRANSACTION_LINKS,
|
||||
])
|
||||
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights
|
||||
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
import { ArgsType, Field, Int } from 'type-graphql'
|
||||
|
||||
@ArgsType()
|
||||
export default class QueryTransactionLinkArgs {
|
||||
@Field(() => String)
|
||||
code: string
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
redeemUserId?: number
|
||||
}
|
||||
@ -6,6 +6,7 @@ export enum TransactionTypeId {
|
||||
RECEIVE = 3,
|
||||
// This is a virtual property, never occurring on the database
|
||||
DECAY = 4,
|
||||
TRANSACTION_LINK = 5,
|
||||
}
|
||||
|
||||
registerEnumType(TransactionTypeId, {
|
||||
|
||||
@ -5,14 +5,16 @@ import { Resolver, Args, Arg, Authorized, Ctx, Mutation, Query } from 'type-grap
|
||||
import { getCustomRepository } from '@dbTools/typeorm'
|
||||
import { TransactionLink } from '@model/TransactionLink'
|
||||
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
||||
import TransactionLinkArgs from '@arg/TransactionLinkArgs'
|
||||
import QueryTransactionLinkArgs from '@arg/QueryTransactionLinkArgs'
|
||||
import { User as dbUser } from '@entity/User'
|
||||
import { UserRepository } from '@repository/User'
|
||||
import TransactionLinkArgs from '@arg/TransactionLinkArgs'
|
||||
import Paginated from '@arg/Paginated'
|
||||
import { calculateBalance } from '@/util/validate'
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { User } from '@model/User'
|
||||
import { calculateDecay } from '@/util/decay'
|
||||
import { Order } from '@enum/Order'
|
||||
|
||||
// TODO: do not export, test it inside the resolver
|
||||
export const transactionLinkCode = (date: Date): string => {
|
||||
@ -96,30 +98,38 @@ export class TransactionLinkResolver {
|
||||
|
||||
@Authorized([RIGHTS.QUERY_TRANSACTION_LINK])
|
||||
@Query(() => TransactionLink)
|
||||
async queryTransactionLink(
|
||||
@Args() { code, redeemUserId }: QueryTransactionLinkArgs,
|
||||
): Promise<TransactionLink> {
|
||||
async queryTransactionLink(@Arg('code') code: string): Promise<TransactionLink> {
|
||||
const transactionLink = await dbTransactionLink.findOneOrFail({ code })
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const user = await userRepository.findOneOrFail({ id: transactionLink.userId })
|
||||
let userRedeem = null
|
||||
if (redeemUserId && !transactionLink.redeemedBy) {
|
||||
const redeemedByUser = await userRepository.findOne({ id: redeemUserId })
|
||||
if (!redeemedByUser) {
|
||||
throw new Error('Unable to find user that redeem the link')
|
||||
}
|
||||
userRedeem = new User(redeemedByUser)
|
||||
transactionLink.redeemedBy = userRedeem.id
|
||||
await dbTransactionLink.save(transactionLink).catch(() => {
|
||||
throw new Error('Unable to save transaction link')
|
||||
})
|
||||
} else if (transactionLink.redeemedBy) {
|
||||
const redeemedByUser = await userRepository.findOne({ id: redeemUserId })
|
||||
if (!redeemedByUser) {
|
||||
throw new Error('Unable to find user that has redeemed the link')
|
||||
}
|
||||
userRedeem = new User(redeemedByUser)
|
||||
const user = await dbUser.findOneOrFail({ id: transactionLink.userId })
|
||||
let redeemedBy: User | null = null
|
||||
if (transactionLink && transactionLink.redeemedBy) {
|
||||
redeemedBy = new User(await dbUser.findOneOrFail({ id: transactionLink.redeemedBy }))
|
||||
}
|
||||
return new TransactionLink(transactionLink, new User(user), userRedeem)
|
||||
return new TransactionLink(transactionLink, new User(user), redeemedBy)
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.LIST_TRANSACTION_LINKS])
|
||||
@Query(() => [TransactionLink])
|
||||
async listTransactionLinks(
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
@Ctx() context: any,
|
||||
): Promise<TransactionLink[]> {
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const user = await userRepository.findByPubkeyHex(context.pubKey)
|
||||
// const now = new Date()
|
||||
const transactionLinks = await dbTransactionLink.find({
|
||||
where: {
|
||||
userId: user.id,
|
||||
redeemedBy: null,
|
||||
// validUntil: MoreThan(now),
|
||||
},
|
||||
order: {
|
||||
createdAt: order,
|
||||
},
|
||||
skip: (currentPage - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
return transactionLinks.map((tl) => new TransactionLink(tl, new User(user)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ import { calculateBalance, isHexPublicKey } from '@/util/validate'
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import { User } from '@model/User'
|
||||
import { communityUser } from '@/util/communityUser'
|
||||
import { virtualDecayTransaction } from '@/util/virtualDecayTransaction'
|
||||
import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { calculateDecay } from '@/util/decay'
|
||||
|
||||
@ -112,11 +112,29 @@ export class TransactionResolver {
|
||||
const self = new User(user)
|
||||
const transactions: Transaction[] = []
|
||||
|
||||
// decay transaction
|
||||
const transactionLinkRepository = getCustomRepository(TransactionLinkRepository)
|
||||
const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate } =
|
||||
await transactionLinkRepository.summary(user.id, now)
|
||||
|
||||
// decay & link transactions
|
||||
if (!onlyCreations && currentPage === 1 && order === Order.DESC) {
|
||||
transactions.push(
|
||||
virtualDecayTransaction(lastTransaction.balance, lastTransaction.balanceDate, now, self),
|
||||
)
|
||||
// virtual transaction for pending transaction-links sum
|
||||
if (sumHoldAvailableAmount.greaterThan(0)) {
|
||||
transactions.push(
|
||||
virtualLinkTransaction(
|
||||
lastTransaction.balance.minus(sumHoldAvailableAmount.toString()),
|
||||
sumAmount,
|
||||
sumHoldAvailableAmount,
|
||||
sumHoldAvailableAmount.minus(sumAmount.toString()),
|
||||
firstDate || now,
|
||||
lastDate || now,
|
||||
self,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// transactions
|
||||
@ -128,13 +146,10 @@ export class TransactionResolver {
|
||||
transactions.push(new Transaction(userTransaction, self, linkedUser))
|
||||
})
|
||||
|
||||
const transactionLinkRepository = getCustomRepository(TransactionLinkRepository)
|
||||
const toHoldAvailable = await transactionLinkRepository.sumAmountToHoldAvailable(user.id, now)
|
||||
|
||||
// Construct Result
|
||||
return new TransactionList(
|
||||
calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now).balance.minus(
|
||||
toHoldAvailable.toString(),
|
||||
sumHoldAvailableAmount.toString(),
|
||||
),
|
||||
transactions,
|
||||
userTransactionsCount,
|
||||
|
||||
@ -4,13 +4,33 @@ import Decimal from 'decimal.js-light'
|
||||
|
||||
@EntityRepository(dbTransactionLink)
|
||||
export class TransactionLinkRepository extends Repository<dbTransactionLink> {
|
||||
async sumAmountToHoldAvailable(userId: number, date: Date): Promise<Decimal> {
|
||||
const { sum } = await this.createQueryBuilder('transactionLinks')
|
||||
.select('SUM(transactionLinks.holdAvailableAmount)', 'sum')
|
||||
.where('transactionLinks.userId = :userId', { userId })
|
||||
.andWhere('transactionLinks.redeemedAt is NULL')
|
||||
.andWhere('transactionLinks.validUntil > :date', { date })
|
||||
.getRawOne()
|
||||
return sum ? new Decimal(sum) : new Decimal(0)
|
||||
async summary(
|
||||
userId: number,
|
||||
date: Date,
|
||||
): Promise<{
|
||||
sumHoldAvailableAmount: Decimal
|
||||
sumAmount: Decimal
|
||||
lastDate: Date | null
|
||||
firstDate: Date | null
|
||||
}> {
|
||||
const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate } =
|
||||
await this.createQueryBuilder('transactionLinks')
|
||||
.select('SUM(transactionLinks.holdAvailableAmount)', 'sumHoldAvailableAmount')
|
||||
.addSelect('SUM(transactionLinks.amount)', 'sumAmount')
|
||||
.addSelect('MAX(transactionLinks.validUntil)', 'lastDate')
|
||||
.addSelect('MIN(transactionLinks.createdAt)', 'firstDate')
|
||||
.where('transactionLinks.userId = :userId', { userId })
|
||||
.andWhere('transactionLinks.redeemedAt is NULL')
|
||||
.andWhere('transactionLinks.validUntil > :date', { date })
|
||||
.orderBy('transactionLinks.createdAt', 'DESC')
|
||||
.getRawOne()
|
||||
return {
|
||||
sumHoldAvailableAmount: sumHoldAvailableAmount
|
||||
? new Decimal(sumHoldAvailableAmount)
|
||||
: new Decimal(0),
|
||||
sumAmount: sumAmount ? new Decimal(sumAmount) : new Decimal(0),
|
||||
lastDate: lastDate || null,
|
||||
firstDate: firstDate || null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,9 +30,9 @@ async function calculateBalance(
|
||||
// TODO why we have to use toString() here?
|
||||
const balance = decay.balance.add(amount.toString())
|
||||
const transactionLinkRepository = getCustomRepository(TransactionLinkRepository)
|
||||
const toHoldAvailable = await transactionLinkRepository.sumAmountToHoldAvailable(userId, time)
|
||||
const { sumHoldAvailableAmount } = await transactionLinkRepository.summary(userId, time)
|
||||
|
||||
if (balance.minus(toHoldAvailable.toString()).lessThan(0)) {
|
||||
if (balance.minus(sumHoldAvailableAmount.toString()).lessThan(0)) {
|
||||
return null
|
||||
}
|
||||
return { balance, lastTransactionId: lastTransaction.id, decay }
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { SaveOptions, RemoveOptions } from '@dbTools/typeorm'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
import { calculateDecay } from './decay'
|
||||
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||
import { Transaction } from '@model/Transaction'
|
||||
import { User } from '@model/User'
|
||||
|
||||
const virtualDecayTransaction = (
|
||||
balance: Decimal,
|
||||
balanceDate: Date,
|
||||
time: Date = new Date(),
|
||||
user: User,
|
||||
): Transaction => {
|
||||
const decay = calculateDecay(balance, balanceDate, time)
|
||||
// const balance = decay.balance.minus(lastTransaction.balance)
|
||||
const decayDbTransaction: dbTransaction = {
|
||||
id: -1,
|
||||
userId: -1,
|
||||
previous: -1,
|
||||
typeId: TransactionTypeId.DECAY,
|
||||
amount: decay.decay ? decay.decay : new Decimal(0), // new Decimal(0), // this kinda is wrong, but helps with the frontend query
|
||||
balance: decay.balance,
|
||||
balanceDate: time,
|
||||
decay: decay.decay ? decay.decay : new Decimal(0),
|
||||
decayStart: decay.start,
|
||||
memo: '',
|
||||
creationDate: null,
|
||||
hasId: function (): boolean {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
save: function (options?: SaveOptions): Promise<dbTransaction> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
remove: function (options?: RemoveOptions): Promise<dbTransaction> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
softRemove: function (options?: SaveOptions): Promise<dbTransaction> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
recover: function (options?: SaveOptions): Promise<dbTransaction> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
reload: function (): Promise<void> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
}
|
||||
return new Transaction(decayDbTransaction, user)
|
||||
}
|
||||
|
||||
export { virtualDecayTransaction }
|
||||
82
backend/src/util/virtualTransactions.ts
Normal file
82
backend/src/util/virtualTransactions.ts
Normal file
@ -0,0 +1,82 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { Transaction } from '@model/Transaction'
|
||||
import { SaveOptions, RemoveOptions } from '@dbTools/typeorm'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||
import { calculateDecay } from './decay'
|
||||
import { User } from '@model/User'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
const defaultModelFunctions = {
|
||||
hasId: function (): boolean {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
save: function (options?: SaveOptions): Promise<dbTransaction> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
remove: function (options?: RemoveOptions): Promise<dbTransaction> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
softRemove: function (options?: SaveOptions): Promise<dbTransaction> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
recover: function (options?: SaveOptions): Promise<dbTransaction> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
reload: function (): Promise<void> {
|
||||
throw new Error('Function not implemented.')
|
||||
},
|
||||
}
|
||||
|
||||
const virtualLinkTransaction = (
|
||||
balance: Decimal,
|
||||
amount: Decimal,
|
||||
holdAvailableAmount: Decimal,
|
||||
decay: Decimal,
|
||||
createdAt: Date,
|
||||
validUntil: Date,
|
||||
user: User,
|
||||
): Transaction => {
|
||||
const linkDbTransaction: dbTransaction = {
|
||||
id: -2,
|
||||
userId: -1,
|
||||
previous: -1,
|
||||
typeId: TransactionTypeId.TRANSACTION_LINK,
|
||||
amount: amount,
|
||||
balance: balance,
|
||||
balanceDate: validUntil,
|
||||
decayStart: createdAt,
|
||||
decay: decay,
|
||||
memo: '',
|
||||
creationDate: null,
|
||||
...defaultModelFunctions,
|
||||
}
|
||||
return new Transaction(linkDbTransaction, user)
|
||||
}
|
||||
|
||||
const virtualDecayTransaction = (
|
||||
balance: Decimal,
|
||||
balanceDate: Date,
|
||||
time: Date = new Date(),
|
||||
user: User,
|
||||
): Transaction => {
|
||||
const decay = calculateDecay(balance, balanceDate, time)
|
||||
// const balance = decay.balance.minus(lastTransaction.balance)
|
||||
const decayDbTransaction: dbTransaction = {
|
||||
id: -1,
|
||||
userId: -1,
|
||||
previous: -1,
|
||||
typeId: TransactionTypeId.DECAY,
|
||||
amount: decay.decay ? decay.decay : new Decimal(0), // new Decimal(0), // this kinda is wrong, but helps with the frontend query
|
||||
balance: decay.balance,
|
||||
balanceDate: time,
|
||||
decay: decay.decay ? decay.decay : new Decimal(0),
|
||||
decayStart: decay.start,
|
||||
memo: '',
|
||||
creationDate: null,
|
||||
...defaultModelFunctions,
|
||||
}
|
||||
return new Transaction(decayDbTransaction, user)
|
||||
}
|
||||
|
||||
export { virtualLinkTransaction, virtualDecayTransaction }
|
||||
Loading…
x
Reference in New Issue
Block a user