Merge pull request #929 from gradido/refactor_graphql

Refactor graphql
This commit is contained in:
Ulf Gebhardt 2021-10-02 10:10:00 +02:00 committed by GitHub
commit 3042f49d0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 448 additions and 409 deletions

View File

@ -0,0 +1,13 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export default class ChangePasswordArgs {
@Field(() => Number)
sessionId: number
@Field(() => String)
email: string
@Field(() => String)
password: string
}

View File

@ -0,0 +1,10 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export default class CheckUsernameArgs {
@Field(() => String)
username: string
@Field(() => Number, { nullable: true })
groupId?: number
}

View File

@ -0,0 +1,19 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export default class CreateUserArgs {
@Field(() => String)
email: string
@Field(() => String)
firstName: string
@Field(() => String)
lastName: string
@Field(() => String)
password: string
@Field(() => String)
language: string
}

View File

@ -0,0 +1,14 @@
import { ArgsType, Field, Int } from 'type-graphql'
import { Order } from '../enum/Order'
@ArgsType()
export default class Paginated {
@Field(() => Int, { nullable: true })
currentPage?: number
@Field(() => Int, { nullable: true })
pageSize?: number
@Field(() => Order, { nullable: true })
order?: Order
}

View File

@ -1,7 +1,7 @@
import { ArgsType, Field } from 'type-graphql' import { ArgsType, Field } from 'type-graphql'
@ArgsType() @ArgsType()
export class SubscribeNewsletterArguments { export default class SubscribeNewsletterArgs {
@Field(() => String) @Field(() => String)
email: string email: string

View File

@ -0,0 +1,13 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export default class TransactionSendArgs {
@Field(() => String)
email: string
@Field(() => Number)
amount: number
@Field(() => String)
memo: string
}

View File

@ -0,0 +1,10 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export default class UnsecureLoginArgs {
@Field(() => String)
email: string
@Field(() => String)
password: string
}

View File

@ -0,0 +1,31 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export default class UpdateUserInfosArgs {
@Field(() => String)
email!: string
@Field({ nullable: true })
firstName?: string
@Field({ nullable: true })
lastName?: string
@Field({ nullable: true })
description?: string
@Field({ nullable: true })
username?: string
@Field({ nullable: true })
language?: string
@Field({ nullable: true })
publisherId?: number
@Field({ nullable: true })
password?: string
@Field({ nullable: true })
passwordNew?: string
}

View File

@ -0,0 +1,16 @@
import { registerEnumType } from 'type-graphql'
export enum GdtEntryType {
FORM = 1,
CVS = 2,
ELOPAGE = 3,
ELOPAGE_PUBLISHER = 4,
DIGISTORE = 5,
CVS2 = 6,
GLOBAL_MODIFICATOR = 7,
}
registerEnumType(GdtEntryType, {
name: 'GdtEntryType', // this one is mandatory
description: 'Gdt Entry Source Type', // this one is optional
})

View File

@ -0,0 +1,11 @@
import { registerEnumType } from 'type-graphql'
export enum Order {
ASC = 'ASC',
DESC = 'DESC',
}
registerEnumType(Order, {
name: 'Order', // this one is mandatory
description: 'Order direction - ascending or descending', // this one is optional
})

View File

@ -0,0 +1,12 @@
import { registerEnumType } from 'type-graphql'
export enum TransactionType {
CREATION = 'creation',
SEND = 'send',
RECIEVE = 'receive',
}
registerEnumType(TransactionType, {
name: 'TransactionType', // this one is mandatory
description: 'Name of the Type of the transaction', // this one is optional
})

View File

@ -0,0 +1,11 @@
import { registerEnumType } from 'type-graphql'
export enum TransactionTypeId {
CREATION = 1,
SEND = 2,
}
registerEnumType(TransactionTypeId, {
name: 'TransactionTypeId', // this one is mandatory
description: 'Type of the transaction', // this one is optional
})

View File

@ -1,28 +0,0 @@
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType()
export class GdtTransactionInput {
@Field(() => String)
email: string
@Field(() => Int, { nullable: true })
currentPage?: number
@Field(() => Int, { nullable: true })
pageSize?: number
@Field(() => String, { nullable: true })
order?: string
}
@ArgsType()
export class GdtTransactionSessionIdInput {
@Field(() => Int, { nullable: true })
currentPage?: number
@Field(() => Int, { nullable: true })
pageSize?: number
@Field(() => String, { nullable: true })
order?: string
}

View File

@ -1,79 +0,0 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export class UnsecureLoginArgs {
@Field(() => String)
email: string
@Field(() => String)
password: string
}
@ArgsType()
export class CreateUserArgs {
@Field(() => String)
email: string
@Field(() => String)
firstName: string
@Field(() => String)
lastName: string
@Field(() => String)
password: string
@Field(() => String)
language: string
}
@ArgsType()
export class ChangePasswordArgs {
@Field(() => Number)
sessionId: number
@Field(() => String)
email: string
@Field(() => String)
password: string
}
@ArgsType()
export class UpdateUserInfosArgs {
@Field(() => String)
email!: string
@Field({ nullable: true })
firstName?: string
@Field({ nullable: true })
lastName?: string
@Field({ nullable: true })
description?: string
@Field({ nullable: true })
username?: string
@Field({ nullable: true })
language?: string
@Field({ nullable: true })
publisherId?: number
@Field({ nullable: true })
password?: string
@Field({ nullable: true })
passwordNew?: string
}
@ArgsType()
export class CheckUsernameArgs {
@Field(() => String)
username: string
@Field(() => Number, { nullable: true })
groupId?: number
}

View File

@ -1,25 +0,0 @@
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType()
export class TransactionListInput {
@Field(() => Int)
firstPage: number
@Field(() => Int)
items: number
@Field(() => String)
order: 'ASC' | 'DESC'
}
@ArgsType()
export class TransactionSendArgs {
@Field(() => String)
email: string
@Field(() => Number)
amount: number
@Field(() => String)
memo: string
}

View File

@ -1,7 +1,6 @@
/* 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 {

View File

@ -1,16 +1,7 @@
/* 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 } from 'type-graphql' import { ObjectType, Field } from 'type-graphql'
import { GdtEntryType } from '../enum/GdtEntryType'
export enum GdtEntryType {
FORM = 1,
CVS = 2,
ELOPAGE = 3,
ELOPAGE_PUBLISHER = 4,
DIGISTORE = 5,
CVS2 = 6,
GLOBAL_MODIFICATOR = 7,
}
@ObjectType() @ObjectType()
export class GdtEntry { export class GdtEntry {
@ -46,7 +37,7 @@ export class GdtEntry {
@Field(() => String) @Field(() => String)
couponCode: string couponCode: string
@Field(() => Number) @Field(() => GdtEntryType)
gdtEntryType: GdtEntryType gdtEntryType: GdtEntryType
@Field(() => Number) @Field(() => Number)

View File

@ -3,20 +3,6 @@
import { GdtEntry } from './GdtEntry' import { GdtEntry } from './GdtEntry'
import { ObjectType, Field } from 'type-graphql' import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class GdtSumPerEmail {
constructor(email: string, summe: number) {
this.email = email
this.summe = summe
}
@Field(() => String)
email: string
@Field(() => Number)
summe: number
}
@ObjectType() @ObjectType()
export class GdtEntryList { export class GdtEntryList {
constructor(json: any) { constructor(json: any) {

View File

@ -0,0 +1,19 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/*
import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class GdtSumPerEmail {
constructor(email: string, summe: number) {
this.email = email
this.summe = summe
}
@Field(() => String)
email: string
@Field(() => Number)
summe: number
}
*/

View File

@ -53,32 +53,3 @@ export class Transaction {
@Field({ nullable: true }) @Field({ nullable: true })
decay?: Decay decay?: Decay
} }
@ObjectType()
export class TransactionList {
constructor() {
this.gdtSum = 0
this.count = 0
this.balance = 0
this.decay = 0
this.decayDate = ''
}
@Field(() => Number)
gdtSum: number
@Field(() => Number)
count: number
@Field(() => Number)
balance: number
@Field(() => Number)
decay: number
@Field(() => String)
decayDate: string
@Field(() => [Transaction])
transactions: Transaction[]
}

View File

@ -0,0 +1,33 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
import { Transaction } from './Transaction'
@ObjectType()
export class TransactionList {
constructor() {
this.gdtSum = 0
this.count = 0
this.balance = 0
this.decay = 0
this.decayDate = ''
}
@Field(() => Number)
gdtSum: number
@Field(() => Number)
count: number
@Field(() => Number)
balance: number
@Field(() => Number)
decay: number
@Field(() => String)
decayDate: string
@Field(() => [Transaction])
transactions: Transaction[]
}

View File

@ -5,9 +5,10 @@ import { Resolver, Query, Args, Ctx, Authorized } from 'type-graphql'
import { getCustomRepository } from 'typeorm' import { getCustomRepository } from 'typeorm'
import CONFIG from '../../config' import CONFIG from '../../config'
import { GdtEntryList } from '../models/GdtEntryList' import { GdtEntryList } from '../models/GdtEntryList'
import { GdtTransactionSessionIdInput } from '../inputs/GdtInputs' import Paginated from '../args/Paginated'
import { apiGet } from '../../apis/HttpRequest' import { apiGet } from '../../apis/HttpRequest'
import { UserRepository } from '../../typeorm/repository/User' import { UserRepository } from '../../typeorm/repository/User'
import { Order } from '../enum/Order'
@Resolver() @Resolver()
export class GdtResolver { export class GdtResolver {
@ -16,7 +17,7 @@ export class GdtResolver {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
async listGDTEntries( async listGDTEntries(
@Args() @Args()
{ currentPage = 1, pageSize = 5, order = 'DESC' }: GdtTransactionSessionIdInput, { currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
@Ctx() context: any, @Ctx() context: any,
): Promise<GdtEntryList> { ): Promise<GdtEntryList> {
// load user // load user

View File

@ -8,7 +8,7 @@ import {
unsubscribe, unsubscribe,
signIn, signIn,
} from '../../apis/KlicktippController' } from '../../apis/KlicktippController'
import { SubscribeNewsletterArguments } from '../inputs/KlickTippInputs' import SubscribeNewsletterArgs from '../args/SubscribeNewsletterArgs'
@Resolver() @Resolver()
export class KlicktippResolver { export class KlicktippResolver {
@ -33,7 +33,7 @@ export class KlicktippResolver {
@Authorized() @Authorized()
@Mutation(() => Boolean) @Mutation(() => Boolean)
async subscribeNewsletter( async subscribeNewsletter(
@Args() { email, language }: SubscribeNewsletterArguments, @Args() { email, language }: SubscribeNewsletterArgs,
): Promise<boolean> { ): Promise<boolean> {
return await signIn(email, language) return await signIn(email, language)
} }

View File

@ -3,22 +3,217 @@
import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql' import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql'
import { getCustomRepository } from 'typeorm' import { getCustomRepository } from 'typeorm'
import CONFIG from '../../config' import CONFIG from '../../config'
import { TransactionList } from '../models/Transaction'
import { TransactionListInput, TransactionSendArgs } from '../inputs/TransactionInput' import { Transaction } from '../models/Transaction'
import { apiGet, apiPost } from '../../apis/HttpRequest' import { TransactionList } from '../models/TransactionList'
import TransactionSendArgs from '../args/TransactionSendArgs'
import Paginated from '../args/Paginated'
import { Order } from '../enum/Order'
import { BalanceRepository } from '../../typeorm/repository/Balance' import { BalanceRepository } from '../../typeorm/repository/Balance'
import { UserRepository } from '../../typeorm/repository/User' import { UserRepository } from '../../typeorm/repository/User'
import listTransactions from './listTransactions' import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction'
import { TransactionRepository } from '../../typeorm/repository/Transaction'
import { User as dbUser } from '../../typeorm/entity/User'
import { UserTransaction as dbUserTransaction } from '../../typeorm/entity/UserTransaction'
import { Transaction as dbTransaction } from '../../typeorm/entity/Transaction'
import { apiGet, apiPost } from '../../apis/HttpRequest'
import { roundFloorFrom4 } from '../../util/round' import { roundFloorFrom4 } from '../../util/round'
import { calculateDecay } from '../../util/decay' import { calculateDecay, calculateDecayWithInterval } from '../../util/decay'
import { TransactionTypeId } from '../enum/TransactionTypeId'
import { TransactionType } from '../enum/TransactionType'
// Helper function
async function calculateAndAddDecayTransactions(
userTransactions: dbUserTransaction[],
user: dbUser,
decay: boolean,
skipFirstTransaction: boolean,
): Promise<Transaction[]> {
const finalTransactions: Transaction[] = []
const transactionIds: number[] = []
const involvedUserIds: number[] = []
userTransactions.forEach((userTransaction: dbUserTransaction) => {
transactionIds.push(userTransaction.transactionId)
})
const transactionRepository = getCustomRepository(TransactionRepository)
const transactions = await transactionRepository.joinFullTransactionsByIds(transactionIds)
const transactionIndiced: dbTransaction[] = []
transactions.forEach((transaction: dbTransaction) => {
transactionIndiced[transaction.id] = transaction
if (transaction.transactionTypeId === 2) {
involvedUserIds.push(transaction.transactionSendCoin.userId)
involvedUserIds.push(transaction.transactionSendCoin.recipiantUserId)
}
})
// 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 userRepository = getCustomRepository(UserRepository)
const userIndiced = await userRepository.getUsersIndiced(involvedUsersUnique)
const decayStartTransaction = await transactionRepository.findDecayStartBlock()
for (let i = 0; i < userTransactions.length; i++) {
const userTransaction = userTransactions[i]
const transaction = transactionIndiced[userTransaction.transactionId]
const finalTransaction = new Transaction()
finalTransaction.transactionId = transaction.id
finalTransaction.date = transaction.received.toISOString()
finalTransaction.memo = transaction.memo
finalTransaction.totalBalance = roundFloorFrom4(userTransaction.balance)
const prev = i > 0 ? userTransactions[i - 1] : null
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(balance)
if (
decayStartTransaction &&
prev.transactionId < decayStartTransaction.id &&
current.transactionId > decayStartTransaction.id
) {
finalTransaction.decay.decayStartBlock = (
decayStartTransaction.received.getTime() / 1000
).toString()
}
}
}
// 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 === TransactionTypeId.CREATION) {
// creation
const creation = transaction.transactionCreation
finalTransaction.name = 'Gradido Akademie'
finalTransaction.type = TransactionType.CREATION
// finalTransaction.targetDate = creation.targetDate
finalTransaction.balance = roundFloorFrom4(creation.amount)
} else if (userTransaction.transactionTypeId === TransactionTypeId.SEND) {
// send coin
const sendCoin = transaction.transactionSendCoin
let otherUser: dbUser | undefined
finalTransaction.balance = roundFloorFrom4(sendCoin.amount)
if (sendCoin.userId === user.id) {
finalTransaction.type = TransactionType.SEND
otherUser = userIndiced[sendCoin.recipiantUserId]
// finalTransaction.pubkey = sendCoin.recipiantPublic
} else if (sendCoin.recipiantUserId === user.id) {
finalTransaction.type = TransactionType.RECIEVE
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
}
// Helper function
async function listTransactions(
currentPage: number,
pageSize: number,
order: Order,
user: dbUser,
): Promise<TransactionList> {
let limit = pageSize
let offset = 0
let skipFirstTransaction = false
if (currentPage > 1) {
offset = (currentPage - 1) * pageSize - 1
limit++
}
if (offset && order === Order.ASC) {
offset--
}
const userTransactionRepository = getCustomRepository(UserTransactionRepository)
let [userTransactions, userTransactionsCount] = await userTransactionRepository.findByUserPaged(
user.id,
limit,
offset,
order,
)
skipFirstTransaction = userTransactionsCount > offset + limit
const decay = !(currentPage > 1)
let transactions: Transaction[] = []
if (userTransactions.length) {
if (order === Order.DESC) {
userTransactions = userTransactions.reverse()
}
transactions = await calculateAndAddDecayTransactions(
userTransactions,
user,
decay,
skipFirstTransaction,
)
if (order === Order.DESC) {
transactions = transactions.reverse()
}
}
const transactionList = new TransactionList()
transactionList.count = userTransactionsCount
transactionList.transactions = transactions
return transactionList
}
@Resolver() @Resolver()
export class TransactionResolver { export class TransactionResolver {
@Authorized() @Authorized()
@Query(() => TransactionList) @Query(() => TransactionList)
async transactionList( async transactionList(
@Args() { firstPage = 1, items = 25, order = 'DESC' }: TransactionListInput, @Args() { currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
@Ctx() context: any, @Ctx() context: any,
): Promise<TransactionList> { ): Promise<TransactionList> {
// get public key for current logged in user // get public key for current logged in user
@ -29,7 +224,7 @@ export class TransactionResolver {
const userRepository = getCustomRepository(UserRepository) const userRepository = getCustomRepository(UserRepository)
const userEntity = await userRepository.findByPubkeyHex(result.data.user.public_hex) const userEntity = await userRepository.findByPubkeyHex(result.data.user.public_hex)
const transactions = await listTransactions(firstPage, items, order, userEntity) const transactions = await listTransactions(currentPage, pageSize, order, userEntity)
// get gdt sum // get gdt sum
const resultGDTSum = await apiPost(`${CONFIG.GDT_API_URL}/GdtEntries/sumPerEmailApi`, { const resultGDTSum = await apiPost(`${CONFIG.GDT_API_URL}/GdtEntries/sumPerEmailApi`, {

View File

@ -9,13 +9,11 @@ import { SendPasswordResetEmailResponse } from '../models/SendPasswordResetEmail
import { UpdateUserInfosResponse } from '../models/UpdateUserInfosResponse' import { UpdateUserInfosResponse } from '../models/UpdateUserInfosResponse'
import { User } from '../models/User' import { User } from '../models/User'
import encode from '../../jwt/encode' import encode from '../../jwt/encode'
import { import ChangePasswordArgs from '../args/ChangePasswordArgs'
ChangePasswordArgs, import CheckUsernameArgs from '../args/CheckUsernameArgs'
CheckUsernameArgs, import CreateUserArgs from '../args/CreateUserArgs'
CreateUserArgs, import UnsecureLoginArgs from '../args/UnsecureLoginArgs'
UnsecureLoginArgs, import UpdateUserInfosArgs from '../args/UpdateUserInfosArgs'
UpdateUserInfosArgs,
} from '../inputs/LoginUserInput'
import { apiPost, apiGet } from '../../apis/HttpRequest' import { apiPost, apiGet } from '../../apis/HttpRequest'
import { import {
klicktippRegistrationMiddleware, klicktippRegistrationMiddleware,

View File

@ -1,187 +0,0 @@
import { getCustomRepository } from 'typeorm'
import { User as dbUser } from '../../typeorm/entity/User'
import { UserRepository } from '../../typeorm/repository/User'
import { TransactionList, Transaction } from '../models/Transaction'
import { UserTransaction as dbUserTransaction } from '../../typeorm/entity/UserTransaction'
import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction'
import { Transaction as dbTransaction } from '../../typeorm/entity/Transaction'
import { TransactionRepository } from '../../typeorm/repository/Transaction'
import { calculateDecayWithInterval } from '../../util/decay'
import { roundFloorFrom4 } from '../../util/round'
async function calculateAndAddDecayTransactions(
userTransactions: dbUserTransaction[],
user: dbUser,
decay: boolean,
skipFirstTransaction: boolean,
): Promise<Transaction[]> {
const finalTransactions: Transaction[] = []
const transactionIds: number[] = []
const involvedUserIds: number[] = []
userTransactions.forEach((userTransaction: dbUserTransaction) => {
transactionIds.push(userTransaction.transactionId)
})
const transactionRepository = getCustomRepository(TransactionRepository)
const transactions = await transactionRepository.joinFullTransactionsByIds(transactionIds)
const transactionIndiced: dbTransaction[] = []
transactions.forEach((transaction: dbTransaction) => {
transactionIndiced[transaction.id] = transaction
if (transaction.transactionTypeId === 2) {
involvedUserIds.push(transaction.transactionSendCoin.userId)
involvedUserIds.push(transaction.transactionSendCoin.recipiantUserId)
}
})
// 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 userRepository = getCustomRepository(UserRepository)
const userIndiced = await userRepository.getUsersIndiced(involvedUsersUnique)
const decayStartTransaction = await transactionRepository.findDecayStartBlock()
for (let i = 0; i < userTransactions.length; i++) {
const userTransaction = userTransactions[i]
const transaction = transactionIndiced[userTransaction.transactionId]
const finalTransaction = new Transaction()
finalTransaction.transactionId = transaction.id
finalTransaction.date = transaction.received.toISOString()
finalTransaction.memo = transaction.memo
finalTransaction.totalBalance = roundFloorFrom4(userTransaction.balance)
const prev = i > 0 ? userTransactions[i - 1] : null
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(balance)
if (
decayStartTransaction &&
prev.transactionId < decayStartTransaction.id &&
current.transactionId > decayStartTransaction.id
) {
finalTransaction.decay.decayStartBlock = (
decayStartTransaction.received.getTime() / 1000
).toString()
}
}
}
// 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
}
export default async function listTransactions(
firstPage: number,
items: number,
order: 'ASC' | 'DESC',
user: dbUser,
): Promise<TransactionList> {
let limit = items
let offset = 0
let skipFirstTransaction = false
if (firstPage > 1) {
offset = (firstPage - 1) * items - 1
limit++
}
if (offset && order === 'ASC') {
offset--
}
const userTransactionRepository = getCustomRepository(UserTransactionRepository)
let [userTransactions, userTransactionsCount] = await userTransactionRepository.findByUserPaged(
user.id,
limit,
offset,
order,
)
skipFirstTransaction = userTransactionsCount > offset + limit
const decay = !(firstPage > 1)
let transactions: Transaction[] = []
if (userTransactions.length) {
if (order === 'DESC') {
userTransactions = userTransactions.reverse()
}
transactions = await calculateAndAddDecayTransactions(
userTransactions,
user,
decay,
skipFirstTransaction,
)
if (order === 'DESC') {
transactions = transactions.reverse()
}
}
const transactionList = new TransactionList()
transactionList.count = userTransactionsCount
transactionList.transactions = transactions
return transactionList
}

View File

@ -1,4 +1,5 @@
import { EntityRepository, Repository } from 'typeorm' import { EntityRepository, Repository } from 'typeorm'
import { Order } from '../../graphql/enum/Order'
import { UserTransaction } from '../entity/UserTransaction' import { UserTransaction } from '../entity/UserTransaction'
@EntityRepository(UserTransaction) @EntityRepository(UserTransaction)
@ -7,7 +8,7 @@ export class UserTransactionRepository extends Repository<UserTransaction> {
userId: number, userId: number,
limit: number, limit: number,
offset: number, offset: number,
order: 'ASC' | 'DESC', order: Order,
): Promise<[UserTransaction[], number]> { ): Promise<[UserTransaction[], number]> {
return this.createQueryBuilder('userTransaction') return this.createQueryBuilder('userTransaction')
.where('userTransaction.userId = :userId', { userId }) .where('userTransaction.userId = :userId', { userId })

View File

@ -32,8 +32,8 @@ export const loginViaEmailVerificationCode = gql`
` `
export const transactionsQuery = gql` export const transactionsQuery = gql`
query($firstPage: Int = 1, $items: Int = 25, $order: String = "DESC") { query($currentPage: Int = 1, $pageSize: Int = 25, $order: String = "DESC") {
transactionList(firstPage: $firstPage, items: $items, order: $order) { transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
gdtSum gdtSum
count count
balance balance

View File

@ -189,7 +189,7 @@ describe('DashboardLayoutGdd', () => {
}) })
await wrapper await wrapper
.findComponent({ ref: 'router-view' }) .findComponent({ ref: 'router-view' })
.vm.$emit('update-transactions', { firstPage: 2, items: 5 }) .vm.$emit('update-transactions', { currentPage: 2, pageSize: 5 })
await flushPromises() await flushPromises()
}) })
@ -197,8 +197,8 @@ describe('DashboardLayoutGdd', () => {
expect(apolloMock).toBeCalledWith( expect(apolloMock).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
firstPage: 2, currentPage: 2,
items: 5, pageSize: 5,
}, },
}), }),
) )
@ -233,7 +233,7 @@ describe('DashboardLayoutGdd', () => {
}) })
await wrapper await wrapper
.findComponent({ ref: 'router-view' }) .findComponent({ ref: 'router-view' })
.vm.$emit('update-transactions', { firstPage: 2, items: 5 }) .vm.$emit('update-transactions', { currentPage: 2, pageSize: 5 })
await flushPromises() await flushPromises()
}) })

View File

@ -110,8 +110,8 @@ export default {
.query({ .query({
query: transactionsQuery, query: transactionsQuery,
variables: { variables: {
firstPage: pagination.firstPage, currentPage: pagination.currentPage,
items: pagination.items, pageSize: pagination.pageSize,
}, },
fetchPolicy: 'network-only', fetchPolicy: 'network-only',
}) })

View File

@ -327,7 +327,9 @@ describe('GddTransactionList', () => {
it('emits update-transactions when next button is clicked', async () => { it('emits update-transactions when next button is clicked', async () => {
await paginationButtons.find('button.next-page').trigger('click') await paginationButtons.find('button.next-page').trigger('click')
expect(wrapper.emitted('update-transactions')[1]).toEqual([{ firstPage: 2, items: 25 }]) expect(wrapper.emitted('update-transactions')[1]).toEqual([
{ currentPage: 2, pageSize: 25 },
])
}) })
it('shows text "2 / 2" when next button is clicked', async () => { it('shows text "2 / 2" when next button is clicked', async () => {
@ -348,7 +350,9 @@ describe('GddTransactionList', () => {
it('emits update-transactions when preivous button is clicked after next buton', async () => { it('emits update-transactions when preivous button is clicked after next buton', async () => {
await paginationButtons.find('button.next-page').trigger('click') await paginationButtons.find('button.next-page').trigger('click')
await paginationButtons.find('button.previous-page').trigger('click') await paginationButtons.find('button.previous-page').trigger('click')
expect(wrapper.emitted('update-transactions')[2]).toEqual([{ firstPage: 1, items: 25 }]) expect(wrapper.emitted('update-transactions')[2]).toEqual([
{ currentPage: 1, pageSize: 25 },
])
expect(scrollToMock).toBeCalled() expect(scrollToMock).toBeCalled()
}) })
}) })

View File

@ -134,8 +134,8 @@ export default {
methods: { methods: {
updateTransactions() { updateTransactions() {
this.$emit('update-transactions', { this.$emit('update-transactions', {
firstPage: this.currentPage, currentPage: this.currentPage,
items: this.pageSize, pageSize: this.pageSize,
}) })
window.scrollTo(0, 0) window.scrollTo(0, 0)
}, },

View File

@ -36,16 +36,16 @@ describe('UserProfileTransactionList', () => {
it('emits update-transactions after creation', () => { it('emits update-transactions after creation', () => {
expect(wrapper.emitted('update-transactions')).toEqual( expect(wrapper.emitted('update-transactions')).toEqual(
expect.arrayContaining([expect.arrayContaining([{ firstPage: 1, items: 25 }])]), expect.arrayContaining([expect.arrayContaining([{ currentPage: 1, pageSize: 25 }])]),
) )
}) })
it('emist update-transactions when update-transactions is called', () => { it('emist update-transactions when update-transactions is called', () => {
wrapper wrapper
.findComponent({ name: 'GddTransactionList' }) .findComponent({ name: 'GddTransactionList' })
.vm.$emit('update-transactions', { firstPage: 2, items: 25 }) .vm.$emit('update-transactions', { currentPage: 2, pageSize: 25 })
expect(wrapper.emitted('update-transactions')).toEqual( expect(wrapper.emitted('update-transactions')).toEqual(
expect.arrayContaining([expect.arrayContaining([{ firstPage: 2, items: 25 }])]), expect.arrayContaining([expect.arrayContaining([{ currentPage: 2, pageSize: 25 }])]),
) )
}) })