new graphql interface,

things build
This commit is contained in:
Ulf Gebhardt 2022-02-26 17:17:43 +01:00
parent dd8ddbad42
commit 97b4e16f40
Signed by: ulfgebhardt
GPG Key ID: DA6B843E748679C9
10 changed files with 279 additions and 197 deletions

View File

@ -4,6 +4,8 @@ export enum TransactionTypeId {
CREATION = 1,
SEND = 2,
RECEIVE = 3,
// This is a virtual property, never occurring on the database
DECAY = 4,
}
registerEnumType(TransactionTypeId, {

View File

@ -1,33 +1,36 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field, Int } from 'type-graphql'
import Decimal from 'decimal.js-light'
import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class Decay {
constructor(json?: any) {
if (json) {
this.balance = Number(json.balance)
this.decayStart = json.decay_start
this.decayEnd = json.decay_end
this.decayDuration = json.decay_duration
this.decayStartBlock = json.decay_start_block
}
constructor(
balance: Decimal,
decay: Decimal | null,
start: Date | null,
end: Date | null,
duration: number | null,
) {
this.balance = balance
this.decay = decay
this.start = start
this.end = end
this.duration = duration
}
@Field(() => Number)
balance: number
@Field(() => Decimal)
balance: Decimal
// timestamp in seconds
@Field(() => Int, { nullable: true })
decayStart: string
@Field(() => Decimal, { nullable: true })
decay: Decimal | null
// timestamp in seconds
@Field(() => Int, { nullable: true })
decayEnd: string
@Field(() => Date, { nullable: true })
start: Date | null
@Field(() => String, { nullable: true })
decayDuration?: number
@Field(() => Date, { nullable: true })
end: Date | null
@Field(() => Int, { nullable: true })
decayStartBlock?: string
@Field(() => Number, { nullable: true })
duration: number | null
}

View File

@ -3,54 +3,70 @@
import Decimal from 'decimal.js-light'
import { ObjectType, Field } from 'type-graphql'
import { Decay } from './Decay'
// we need a better solution for the decay block:
// the first transaction on the first page shows the decay since the last transaction
// the format is actually a Decay and not a Transaction.
// Therefore we have a lot of nullable fields, which should be always present
import { Transaction as dbTransaction } from '@entity/Transaction'
import { TransactionTypeId } from '../enum/TransactionTypeId'
import { User } from './User'
@ObjectType()
export class Transaction {
constructor() {
this.type = ''
this.balance = new Decimal(0)
this.totalBalance = new Decimal(0)
this.memo = ''
constructor(transaction: dbTransaction, user: User, linkedUser: User | null = null) {
this.id = transaction.id
this.user = user
this.previous = transaction.previous
this.typeId = transaction.typeId
this.amount = transaction.amount
this.balance = transaction.balance
this.balanceDate = transaction.balanceDate
if (!transaction.decayStart) {
this.decay = new Decay(transaction.balance, null, null, null, null)
} else {
this.decay = new Decay(
transaction.balance,
transaction.decay,
transaction.decayStart,
transaction.balanceDate,
(transaction.balanceDate.getTime() - transaction.decayStart.getTime()) / 1000,
)
}
this.memo = transaction.memo
this.creationDate = transaction.creationDate
this.linkedUser = linkedUser
this.linkedTransactionId = transaction.linkedTransactionId
}
@Field(() => String)
type: string
@Field(() => Number)
id: number
@Field(() => User)
user: User
@Field(() => Number, { nullable: true })
previous: number | null
@Field(() => TransactionTypeId)
typeId: TransactionTypeId
@Field(() => Decimal)
amount: Decimal
@Field(() => Decimal)
balance: Decimal
@Field(() => Decimal)
totalBalance: Decimal
@Field(() => Date)
balanceDate: Date
@Field({ nullable: true })
decayStart?: string
@Field({ nullable: true })
decayEnd?: string
@Field({ nullable: true })
decayDuration?: number
@Field(() => Decay)
decay: Decay
@Field(() => String)
memo: string
@Field(() => Date, { nullable: true })
creationDate: Date | null
@Field(() => User, { nullable: true })
linkedUser: User | null
@Field(() => Number, { nullable: true })
transactionId?: number
@Field({ nullable: true })
name?: string
@Field({ nullable: true })
email?: string
@Field({ nullable: true })
date?: string
@Field({ nullable: true })
decay?: Decay
linkedTransactionId?: number | null
}

View File

@ -2,19 +2,27 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import Decimal from 'decimal.js-light'
import { ObjectType, Field } from 'type-graphql'
import CONFIG from '../../config'
import { Transaction } from './Transaction'
@ObjectType()
export class TransactionList {
constructor() {
this.gdtSum = 0
this.count = 0
this.balance = new Decimal(0)
this.decayStartBlock = null
constructor(
balance: Decimal,
transactions: Transaction[],
count: number,
balanceGDT?: number | null,
decayStartBlock: Date = CONFIG.DECAY_START_TIME,
) {
this.balance = balance
this.transactions = transactions
this.count = count
this.balanceGDT = balanceGDT || null
this.decayStartBlock = decayStartBlock
}
@Field(() => Number, { nullable: true })
gdtSum: number | null
balanceGDT: number | null
@Field(() => Number)
count: number
@ -22,8 +30,8 @@ export class TransactionList {
@Field(() => Number)
balance: Decimal
@Field(() => Date, { nullable: true })
decayStartBlock: Date | null
@Field(() => Date)
decayStartBlock: Date
@Field(() => [Transaction])
transactions: Transaction[]

View File

@ -1,75 +1,76 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field, Int } from 'type-graphql'
import { ObjectType, Field } from 'type-graphql'
import { KlickTipp } from './KlickTipp'
import { User as dbUser } from '@entity/User'
@ObjectType()
export class User {
/*
@Field(() => ID)
@PrimaryGeneratedColumn()
id: number
*/
constructor(json?: any) {
if (json) {
this.id = json.id
this.email = json.email
this.firstName = json.first_name
this.lastName = json.last_name
this.pubkey = json.public_hex
this.language = json.language
this.publisherId = json.publisher_id
this.isAdmin = json.isAdmin
}
constructor(user: dbUser) {
this.id = user.id
this.email = user.email
this.firstName = user.firstName
this.lastName = user.lastName
this.deletedAt = user.deletedAt
this.createdAt = user.createdAt
this.emailChecked = user.emailChecked
this.language = user.language
this.publisherId = user.publisherId
// TODO
this.isAdmin = null
this.coinanimation = null
this.klickTipp = null
this.hasElopage = null
}
@Field(() => Number)
id: number
// `public_key` binary(32) DEFAULT NULL,
// `privkey` binary(80) DEFAULT NULL,
// TODO privacy issue here
@Field(() => String)
email: string
@Field(() => String)
firstName: string
@Field(() => String, { nullable: true })
firstName: string | null
@Field(() => String)
lastName: string
@Field(() => String, { nullable: true })
lastName: string | null
@Field(() => String)
pubkey: string
/*
@Field(() => String)
pubkey: string
@Field(() => Date, { nullable: true })
deletedAt: Date | null
// not sure about the type here. Maybe better to have a string
@Field(() => number)
created: number
// `password` bigint(20) unsigned DEFAULT 0,
// `email_hash` binary(32) DEFAULT NULL,
@Field(() =>>> Boolean)
@Field(() => Date)
createdAt: Date
@Field(() => Boolean)
emailChecked: boolean
*/
@Field(() => String)
language: string
/*
@Field(() => Boolean)
disabled: boolean
*/
// This is not the users publisherId, but the one of the users who recommend him
@Field(() => Number, { nullable: true })
publisherId: number | null
// what is publisherId?
@Field(() => Int, { nullable: true })
publisherId?: number
// `passphrase` text COLLATE utf8mb4_unicode_ci DEFAULT NULL,
@Field(() => Boolean)
isAdmin: boolean
@Field(() => Boolean)
coinanimation: boolean
@Field(() => KlickTipp)
klickTipp: KlickTipp
// TODO this is a bit inconsistent with what we query from the database
// therefore all those fields are now nullable with default value null
@Field(() => Boolean, { nullable: true })
isAdmin: boolean | null
@Field(() => Boolean, { nullable: true })
hasElopage?: boolean
coinanimation: boolean | null
@Field(() => KlickTipp, { nullable: true })
klickTipp: KlickTipp | null
@Field(() => Boolean, { nullable: true })
hasElopage: boolean | null
}

View File

@ -20,15 +20,17 @@ import { Order } from '../enum/Order'
import { UserRepository } from '../../typeorm/repository/User'
import { TransactionRepository } from '../../typeorm/repository/Transaction'
import { User as dbUser, User } from '@entity/User'
import { User as dbUser } from '@entity/User'
import { Transaction as dbTransaction } from '@entity/Transaction'
import { apiPost } from '../../apis/HttpRequest'
import { calculateDecay } from '../../util/decay'
import { TransactionTypeId } from '../enum/TransactionTypeId'
import { TransactionType } from '../enum/TransactionType'
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'
@Resolver()
export class TransactionResolver {
@ -87,88 +89,57 @@ export class TransactionResolver {
// 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 involvedUsers = await User.createQueryBuilder()
const involvedDbUsers = await dbUser
.createQueryBuilder()
.withDeleted()
.where('user.id IN (:...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()
const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now)
const balance = decay.balance.minus(lastTransaction.balance)
const decayTransaction = new Transaction()
decayTransaction.type = 'decay'
decayTransaction.balance = balance
// TODO
// decayTransaction.decayDuration = decay.duration
// decayTransaction.decayStart = decay.start
// decayTransaction.decayEnd = decay.end
transactions.push(decayTransaction)
transactions.push(
virtualDecayTransaction(lastTransaction.balance, lastTransaction.balanceDate, now, self),
)
}
if (userTransactions.length) {
for (let i = 0; i < userTransactions.length; i++) {
const userTransaction = userTransactions[i]
const finalTransaction = new Transaction()
finalTransaction.transactionId = userTransaction.id
finalTransaction.date = userTransaction.balanceDate.toISOString()
finalTransaction.memo = userTransaction.memo
finalTransaction.totalBalance = userTransaction.balance
finalTransaction.balance = userTransaction.amount
const otherUser = involvedUsers.find((u) => u.id === userTransaction.linkedUserId)
switch (userTransaction.typeId) {
case TransactionTypeId.CREATION:
finalTransaction.name = 'Gradido Akademie'
finalTransaction.type = TransactionType.CREATION
break
case TransactionTypeId.SEND:
finalTransaction.type = TransactionType.SEND
if (otherUser) {
finalTransaction.name = otherUser.firstName + ' ' + otherUser.lastName
finalTransaction.email = otherUser.email
}
break
case TransactionTypeId.RECEIVE:
finalTransaction.type = TransactionType.RECIEVE
if (otherUser) {
finalTransaction.name = otherUser.firstName + ' ' + otherUser.lastName
finalTransaction.email = otherUser.email
}
break
default:
throw new Error('invalid transaction')
}
transactions.push(finalTransaction)
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))
}
const transactionList = new TransactionList()
transactionList.count = userTransactionsCount
transactionList.transactions = transactions
// get gdt sum
transactionList.gdtSum = null
// get GDT
let balanceGDT = null
try {
const resultGDTSum = await apiPost(`${CONFIG.GDT_API_URL}/GdtEntries/sumPerEmailApi`, {
email: user.email,
})
if (resultGDTSum.success) transactionList.gdtSum = Number(resultGDTSum.data.sum) || 0
} catch (err: any) {}
if (!resultGDTSum.success) {
throw new Error('Call not successful')
}
balanceGDT = Number(resultGDTSum.data.sum) || 0
} catch (err: any) {
// eslint-disable-next-line no-console
console.log('Could not query GDT Server', err)
}
// get balance
transactionList.balance = lastTransaction.balance
transactionList.decayStartBlock = CONFIG.DECAY_START_TIME
// const now = new Date()
// TODO this seems duplicated
// transactionList.decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now)
// transactionList.decayDate = now.toString()
return transactionList
// Construct Result
return new TransactionList(
lastTransaction.balance,
transactions,
userTransactionsCount,
balanceGDT,
)
}
@Authorized([RIGHTS.SEND_COINS])

View File

@ -216,14 +216,8 @@ export class UserResolver {
// TODO refactor and do not have duplicate code with login(see below)
const userRepository = getCustomRepository(UserRepository)
const userEntity = await userRepository.findByPubkeyHex(context.pubKey)
const user = new User()
user.id = userEntity.id
user.email = userEntity.email
user.firstName = userEntity.firstName
user.lastName = userEntity.lastName
user.pubkey = userEntity.pubKey.toString('hex')
user.language = userEntity.language
const user = new User(userEntity)
// user.pubkey = userEntity.pubKey.toString('hex')
// Elopage Status & Stored PublisherId
user.hasElopage = await this.hasElopage(context)
@ -271,12 +265,9 @@ export class UserResolver {
throw new Error('No user with this credentials')
}
const user = new User()
user.id = dbUser.id
user.email = email
user.firstName = dbUser.firstName
user.lastName = dbUser.lastName
user.pubkey = dbUser.pubKey.toString('hex')
const user = new User(dbUser)
// user.email = email
// user.pubkey = dbUser.pubKey.toString('hex')
user.language = dbUser.language
// Elopage Status & Stored PublisherId

View File

@ -0,0 +1,44 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { SaveOptions, RemoveOptions } from '@dbTools/typeorm'
import { User as dbUser } from '@entity/User'
import { User } from '../graphql/model/User'
const communityDbUser: dbUser = {
id: -1,
email: 'support@gradido.net',
firstName: 'Gradido',
lastName: 'Akademie',
pubKey: Buffer.from(''),
privKey: Buffer.from(''),
deletedAt: null,
password: BigInt(0),
emailHash: Buffer.from(''),
createdAt: new Date(),
emailChecked: false,
language: '',
publisherId: 0,
passphrase: '',
settings: [],
hasId: function (): boolean {
throw new Error('Function not implemented.')
},
save: function (options?: SaveOptions): Promise<dbUser> {
throw new Error('Function not implemented.')
},
remove: function (options?: RemoveOptions): Promise<dbUser> {
throw new Error('Function not implemented.')
},
softRemove: function (options?: SaveOptions): Promise<dbUser> {
throw new Error('Function not implemented.')
},
recover: function (options?: SaveOptions): Promise<dbUser> {
throw new Error('Function not implemented.')
},
reload: function (): Promise<void> {
throw new Error('Function not implemented.')
},
}
const communityUser = new User(communityDbUser)
export { communityDbUser, communityUser }

View File

@ -1,14 +1,8 @@
import Decimal from 'decimal.js-light'
import CONFIG from '../config'
import { Decay } from '../graphql/model/Decay'
// TODO: externalize all those definitions and functions into an external decay library
interface Decay {
balance: Decimal
decay: Decimal | null
start: Date | null
end: Date | null
duration: number | null
}
function decayFormula(value: Decimal, seconds: number): Decimal {
return value.mul(new Decimal('0.99999997803504048973201202316767079413460520837376').pow(seconds))

View File

@ -0,0 +1,52 @@
/* 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 '../graphql/enum/TransactionTypeId'
import { Transaction } from '../graphql/model/Transaction'
import { User } from '../graphql/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: new Decimal(0),
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 }