From ca3f930c5e75c43e4f69a9d651d173042093dc34 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 1 Oct 2021 21:58:58 +0200 Subject: [PATCH 1/7] args types --- .../src/graphql/args/ChangePasswordArgs.ts | 13 ++++++++ backend/src/graphql/args/CheckUsernameArgs.ts | 10 ++++++ backend/src/graphql/args/CreateUserArgs.ts | 19 ++++++++++++ backend/src/graphql/args/Paginated.ts | 14 +++++++++ .../SubscribeNewsletterArgs.ts} | 2 +- .../src/graphql/args/TransactionSendArgs.ts | 13 ++++++++ backend/src/graphql/args/UnsecureLoginArgs.ts | 10 ++++++ .../src/graphql/args/UpdateUserInfosArgs.ts | 31 +++++++++++++++++++ 8 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 backend/src/graphql/args/ChangePasswordArgs.ts create mode 100644 backend/src/graphql/args/CheckUsernameArgs.ts create mode 100644 backend/src/graphql/args/CreateUserArgs.ts create mode 100644 backend/src/graphql/args/Paginated.ts rename backend/src/graphql/{inputs/KlickTippInputs.ts => args/SubscribeNewsletterArgs.ts} (75%) create mode 100644 backend/src/graphql/args/TransactionSendArgs.ts create mode 100644 backend/src/graphql/args/UnsecureLoginArgs.ts create mode 100644 backend/src/graphql/args/UpdateUserInfosArgs.ts diff --git a/backend/src/graphql/args/ChangePasswordArgs.ts b/backend/src/graphql/args/ChangePasswordArgs.ts new file mode 100644 index 000000000..4c2efae60 --- /dev/null +++ b/backend/src/graphql/args/ChangePasswordArgs.ts @@ -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 +} diff --git a/backend/src/graphql/args/CheckUsernameArgs.ts b/backend/src/graphql/args/CheckUsernameArgs.ts new file mode 100644 index 000000000..6aaed6d0b --- /dev/null +++ b/backend/src/graphql/args/CheckUsernameArgs.ts @@ -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 +} diff --git a/backend/src/graphql/args/CreateUserArgs.ts b/backend/src/graphql/args/CreateUserArgs.ts new file mode 100644 index 000000000..486cb023b --- /dev/null +++ b/backend/src/graphql/args/CreateUserArgs.ts @@ -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 +} diff --git a/backend/src/graphql/args/Paginated.ts b/backend/src/graphql/args/Paginated.ts new file mode 100644 index 000000000..fa6be03b5 --- /dev/null +++ b/backend/src/graphql/args/Paginated.ts @@ -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 +} diff --git a/backend/src/graphql/inputs/KlickTippInputs.ts b/backend/src/graphql/args/SubscribeNewsletterArgs.ts similarity index 75% rename from backend/src/graphql/inputs/KlickTippInputs.ts rename to backend/src/graphql/args/SubscribeNewsletterArgs.ts index cd98dd493..98a3bb2d3 100644 --- a/backend/src/graphql/inputs/KlickTippInputs.ts +++ b/backend/src/graphql/args/SubscribeNewsletterArgs.ts @@ -1,7 +1,7 @@ import { ArgsType, Field } from 'type-graphql' @ArgsType() -export class SubscribeNewsletterArguments { +export default class SubscribeNewsletterArgs { @Field(() => String) email: string diff --git a/backend/src/graphql/args/TransactionSendArgs.ts b/backend/src/graphql/args/TransactionSendArgs.ts new file mode 100644 index 000000000..cf4e43d94 --- /dev/null +++ b/backend/src/graphql/args/TransactionSendArgs.ts @@ -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 +} diff --git a/backend/src/graphql/args/UnsecureLoginArgs.ts b/backend/src/graphql/args/UnsecureLoginArgs.ts new file mode 100644 index 000000000..9e9cde0d9 --- /dev/null +++ b/backend/src/graphql/args/UnsecureLoginArgs.ts @@ -0,0 +1,10 @@ +import { ArgsType, Field } from 'type-graphql' + +@ArgsType() +export default class UnsecureLoginArgs { + @Field(() => String) + email: string + + @Field(() => String) + password: string +} diff --git a/backend/src/graphql/args/UpdateUserInfosArgs.ts b/backend/src/graphql/args/UpdateUserInfosArgs.ts new file mode 100644 index 000000000..f1f0fd603 --- /dev/null +++ b/backend/src/graphql/args/UpdateUserInfosArgs.ts @@ -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 +} From c9a56ac0a2133c96d91238862260b46f4352f148 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 1 Oct 2021 21:59:06 +0200 Subject: [PATCH 2/7] enums --- backend/src/graphql/enum/GdtEntryType.ts | 16 ++++++++++++++++ backend/src/graphql/enum/Order.ts | 11 +++++++++++ backend/src/graphql/enum/TransactionType.ts | 12 ++++++++++++ backend/src/graphql/enum/TransactionTypeId.ts | 11 +++++++++++ 4 files changed, 50 insertions(+) create mode 100644 backend/src/graphql/enum/GdtEntryType.ts create mode 100644 backend/src/graphql/enum/Order.ts create mode 100644 backend/src/graphql/enum/TransactionType.ts create mode 100644 backend/src/graphql/enum/TransactionTypeId.ts diff --git a/backend/src/graphql/enum/GdtEntryType.ts b/backend/src/graphql/enum/GdtEntryType.ts new file mode 100644 index 000000000..b82153222 --- /dev/null +++ b/backend/src/graphql/enum/GdtEntryType.ts @@ -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 +}) diff --git a/backend/src/graphql/enum/Order.ts b/backend/src/graphql/enum/Order.ts new file mode 100644 index 000000000..e425d21b2 --- /dev/null +++ b/backend/src/graphql/enum/Order.ts @@ -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 +}) diff --git a/backend/src/graphql/enum/TransactionType.ts b/backend/src/graphql/enum/TransactionType.ts new file mode 100644 index 000000000..2b6fc44d1 --- /dev/null +++ b/backend/src/graphql/enum/TransactionType.ts @@ -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 +}) diff --git a/backend/src/graphql/enum/TransactionTypeId.ts b/backend/src/graphql/enum/TransactionTypeId.ts new file mode 100644 index 000000000..4ff3671cf --- /dev/null +++ b/backend/src/graphql/enum/TransactionTypeId.ts @@ -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 +}) From 1c2db6cb151505d2784a485a8258bd078f87878a Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 1 Oct 2021 21:59:18 +0200 Subject: [PATCH 3/7] deleted inputs (now args) --- backend/src/graphql/inputs/GdtInputs.ts | 28 ------- backend/src/graphql/inputs/LoginUserInput.ts | 79 ------------------- .../src/graphql/inputs/TransactionInput.ts | 25 ------ 3 files changed, 132 deletions(-) delete mode 100644 backend/src/graphql/inputs/GdtInputs.ts delete mode 100644 backend/src/graphql/inputs/LoginUserInput.ts delete mode 100644 backend/src/graphql/inputs/TransactionInput.ts diff --git a/backend/src/graphql/inputs/GdtInputs.ts b/backend/src/graphql/inputs/GdtInputs.ts deleted file mode 100644 index e11ce24c1..000000000 --- a/backend/src/graphql/inputs/GdtInputs.ts +++ /dev/null @@ -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 -} diff --git a/backend/src/graphql/inputs/LoginUserInput.ts b/backend/src/graphql/inputs/LoginUserInput.ts deleted file mode 100644 index bafa6a851..000000000 --- a/backend/src/graphql/inputs/LoginUserInput.ts +++ /dev/null @@ -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 -} diff --git a/backend/src/graphql/inputs/TransactionInput.ts b/backend/src/graphql/inputs/TransactionInput.ts deleted file mode 100644 index 016fe9720..000000000 --- a/backend/src/graphql/inputs/TransactionInput.ts +++ /dev/null @@ -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 -} From d1da8cdfd6a6617728f1dde14aa654b0d588daab Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 1 Oct 2021 21:59:24 +0200 Subject: [PATCH 4/7] models --- backend/src/graphql/models/Decay.ts | 1 - backend/src/graphql/models/GdtEntry.ts | 13 ++------ backend/src/graphql/models/GdtEntryList.ts | 14 -------- backend/src/graphql/models/GdtSumPerEmail.ts | 19 +++++++++++ backend/src/graphql/models/Transaction.ts | 29 ---------------- backend/src/graphql/models/TransactionList.ts | 33 +++++++++++++++++++ 6 files changed, 54 insertions(+), 55 deletions(-) create mode 100644 backend/src/graphql/models/GdtSumPerEmail.ts create mode 100644 backend/src/graphql/models/TransactionList.ts diff --git a/backend/src/graphql/models/Decay.ts b/backend/src/graphql/models/Decay.ts index 860580e8f..48ed7b8c5 100644 --- a/backend/src/graphql/models/Decay.ts +++ b/backend/src/graphql/models/Decay.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { ObjectType, Field, Int } from 'type-graphql' -import { Transaction } from '../../typeorm/entity/Transaction' @ObjectType() export class Decay { diff --git a/backend/src/graphql/models/GdtEntry.ts b/backend/src/graphql/models/GdtEntry.ts index 7935d4181..2f4b31b00 100644 --- a/backend/src/graphql/models/GdtEntry.ts +++ b/backend/src/graphql/models/GdtEntry.ts @@ -1,16 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { ObjectType, Field } from 'type-graphql' - -export enum GdtEntryType { - FORM = 1, - CVS = 2, - ELOPAGE = 3, - ELOPAGE_PUBLISHER = 4, - DIGISTORE = 5, - CVS2 = 6, - GLOBAL_MODIFICATOR = 7, -} +import { GdtEntryType } from '../enum/GdtEntryType' @ObjectType() export class GdtEntry { @@ -46,7 +37,7 @@ export class GdtEntry { @Field(() => String) couponCode: string - @Field(() => Number) + @Field(() => GdtEntryType) gdtEntryType: GdtEntryType @Field(() => Number) diff --git a/backend/src/graphql/models/GdtEntryList.ts b/backend/src/graphql/models/GdtEntryList.ts index 301ac179b..9d529b6f3 100644 --- a/backend/src/graphql/models/GdtEntryList.ts +++ b/backend/src/graphql/models/GdtEntryList.ts @@ -3,20 +3,6 @@ import { GdtEntry } from './GdtEntry' 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() export class GdtEntryList { constructor(json: any) { diff --git a/backend/src/graphql/models/GdtSumPerEmail.ts b/backend/src/graphql/models/GdtSumPerEmail.ts new file mode 100644 index 000000000..c90c7293b --- /dev/null +++ b/backend/src/graphql/models/GdtSumPerEmail.ts @@ -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 +} +*/ diff --git a/backend/src/graphql/models/Transaction.ts b/backend/src/graphql/models/Transaction.ts index 69956cf9b..3aa3c429d 100644 --- a/backend/src/graphql/models/Transaction.ts +++ b/backend/src/graphql/models/Transaction.ts @@ -53,32 +53,3 @@ export class Transaction { @Field({ nullable: true }) 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[] -} diff --git a/backend/src/graphql/models/TransactionList.ts b/backend/src/graphql/models/TransactionList.ts new file mode 100644 index 000000000..0175048d1 --- /dev/null +++ b/backend/src/graphql/models/TransactionList.ts @@ -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[] +} From d391aa349155bd9b1db7639bfd1390e02fdef40b Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 1 Oct 2021 21:59:34 +0200 Subject: [PATCH 5/7] resolvers --- backend/src/graphql/resolvers/GdtResolver.ts | 5 +- .../graphql/resolvers/KlicktippResolver.ts | 4 +- .../graphql/resolvers/TransactionResolver.ts | 209 +++++++++++++++++- backend/src/graphql/resolvers/UserResolver.ts | 12 +- .../src/graphql/resolvers/listTransactions.ts | 187 ---------------- 5 files changed, 212 insertions(+), 205 deletions(-) delete mode 100644 backend/src/graphql/resolvers/listTransactions.ts diff --git a/backend/src/graphql/resolvers/GdtResolver.ts b/backend/src/graphql/resolvers/GdtResolver.ts index 8ea006313..0552c1510 100644 --- a/backend/src/graphql/resolvers/GdtResolver.ts +++ b/backend/src/graphql/resolvers/GdtResolver.ts @@ -5,9 +5,10 @@ import { Resolver, Query, Args, Ctx, Authorized } from 'type-graphql' import { getCustomRepository } from 'typeorm' import CONFIG from '../../config' import { GdtEntryList } from '../models/GdtEntryList' -import { GdtTransactionSessionIdInput } from '../inputs/GdtInputs' +import Paginated from '../args/Paginated' import { apiGet } from '../../apis/HttpRequest' import { UserRepository } from '../../typeorm/repository/User' +import { Order } from '../enum/Order' @Resolver() export class GdtResolver { @@ -16,7 +17,7 @@ export class GdtResolver { // eslint-disable-next-line @typescript-eslint/no-explicit-any async listGDTEntries( @Args() - { currentPage = 1, pageSize = 5, order = 'DESC' }: GdtTransactionSessionIdInput, + { currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated, @Ctx() context: any, ): Promise { // load user diff --git a/backend/src/graphql/resolvers/KlicktippResolver.ts b/backend/src/graphql/resolvers/KlicktippResolver.ts index 6d8381993..a76e9f6bb 100644 --- a/backend/src/graphql/resolvers/KlicktippResolver.ts +++ b/backend/src/graphql/resolvers/KlicktippResolver.ts @@ -8,7 +8,7 @@ import { unsubscribe, signIn, } from '../../apis/KlicktippController' -import { SubscribeNewsletterArguments } from '../inputs/KlickTippInputs' +import SubscribeNewsletterArgs from '../args/SubscribeNewsletterArgs' @Resolver() export class KlicktippResolver { @@ -33,7 +33,7 @@ export class KlicktippResolver { @Authorized() @Mutation(() => Boolean) async subscribeNewsletter( - @Args() { email, language }: SubscribeNewsletterArguments, + @Args() { email, language }: SubscribeNewsletterArgs, ): Promise { return await signIn(email, language) } diff --git a/backend/src/graphql/resolvers/TransactionResolver.ts b/backend/src/graphql/resolvers/TransactionResolver.ts index cd07a94f5..f079da9ca 100644 --- a/backend/src/graphql/resolvers/TransactionResolver.ts +++ b/backend/src/graphql/resolvers/TransactionResolver.ts @@ -3,22 +3,217 @@ import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql' import { getCustomRepository } from 'typeorm' + import CONFIG from '../../config' -import { TransactionList } from '../models/Transaction' -import { TransactionListInput, TransactionSendArgs } from '../inputs/TransactionInput' -import { apiGet, apiPost } from '../../apis/HttpRequest' + +import { Transaction } from '../models/Transaction' +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 { 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 { 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 { + 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 { + 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() export class TransactionResolver { @Authorized() @Query(() => TransactionList) async transactionList( - @Args() { firstPage = 1, items = 25, order = 'DESC' }: TransactionListInput, + @Args() { currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated, @Ctx() context: any, ): Promise { // get public key for current logged in user @@ -29,7 +224,7 @@ export class TransactionResolver { const userRepository = getCustomRepository(UserRepository) 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 const resultGDTSum = await apiPost(`${CONFIG.GDT_API_URL}/GdtEntries/sumPerEmailApi`, { diff --git a/backend/src/graphql/resolvers/UserResolver.ts b/backend/src/graphql/resolvers/UserResolver.ts index 8997fb264..2c6e94f1d 100644 --- a/backend/src/graphql/resolvers/UserResolver.ts +++ b/backend/src/graphql/resolvers/UserResolver.ts @@ -9,13 +9,11 @@ import { SendPasswordResetEmailResponse } from '../models/SendPasswordResetEmail import { UpdateUserInfosResponse } from '../models/UpdateUserInfosResponse' import { User } from '../models/User' import encode from '../../jwt/encode' -import { - ChangePasswordArgs, - CheckUsernameArgs, - CreateUserArgs, - UnsecureLoginArgs, - UpdateUserInfosArgs, -} from '../inputs/LoginUserInput' +import ChangePasswordArgs from '../args/ChangePasswordArgs' +import CheckUsernameArgs from '../args/CheckUsernameArgs' +import CreateUserArgs from '../args/CreateUserArgs' +import UnsecureLoginArgs from '../args/UnsecureLoginArgs' +import UpdateUserInfosArgs from '../args/UpdateUserInfosArgs' import { apiPost, apiGet } from '../../apis/HttpRequest' import { klicktippRegistrationMiddleware, diff --git a/backend/src/graphql/resolvers/listTransactions.ts b/backend/src/graphql/resolvers/listTransactions.ts deleted file mode 100644 index bf84e17d4..000000000 --- a/backend/src/graphql/resolvers/listTransactions.ts +++ /dev/null @@ -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 { - 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 { - 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 -} From 175bffcc05f6663c0a3f8c479a8ce42283c60ece Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 1 Oct 2021 21:59:57 +0200 Subject: [PATCH 6/7] repository use Order Enum --- backend/src/typeorm/repository/UserTransaction.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/typeorm/repository/UserTransaction.ts b/backend/src/typeorm/repository/UserTransaction.ts index 7efd8f12b..58fe8d3e7 100644 --- a/backend/src/typeorm/repository/UserTransaction.ts +++ b/backend/src/typeorm/repository/UserTransaction.ts @@ -1,4 +1,5 @@ import { EntityRepository, Repository } from 'typeorm' +import { Order } from '../../graphql/enum/Order' import { UserTransaction } from '../entity/UserTransaction' @EntityRepository(UserTransaction) @@ -7,7 +8,7 @@ export class UserTransactionRepository extends Repository { userId: number, limit: number, offset: number, - order: 'ASC' | 'DESC', + order: Order, ): Promise<[UserTransaction[], number]> { return this.createQueryBuilder('userTransaction') .where('userTransaction.userId = :userId', { userId }) From 69bb8b7ea7075a56107a2245d96731fc8426485b Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 1 Oct 2021 22:00:37 +0200 Subject: [PATCH 7/7] unify pagination to use `currentPage`, `pageSize` and `order` as values --- frontend/src/graphql/queries.js | 4 ++-- frontend/src/views/Layout/DashboardLayout_gdd.spec.js | 8 ++++---- frontend/src/views/Layout/DashboardLayout_gdd.vue | 4 ++-- .../Pages/AccountOverview/GddTransactionList.spec.js | 8 ++++++-- .../views/Pages/AccountOverview/GddTransactionList.vue | 4 ++-- .../src/views/Pages/UserProfileTransactionList.spec.js | 6 +++--- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index 009177cc1..0aadb3345 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -32,8 +32,8 @@ export const loginViaEmailVerificationCode = gql` ` export const transactionsQuery = gql` - query($firstPage: Int = 1, $items: Int = 25, $order: String = "DESC") { - transactionList(firstPage: $firstPage, items: $items, order: $order) { + query($currentPage: Int = 1, $pageSize: Int = 25, $order: String = "DESC") { + transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) { gdtSum count balance diff --git a/frontend/src/views/Layout/DashboardLayout_gdd.spec.js b/frontend/src/views/Layout/DashboardLayout_gdd.spec.js index 2a789b329..6555d1dda 100644 --- a/frontend/src/views/Layout/DashboardLayout_gdd.spec.js +++ b/frontend/src/views/Layout/DashboardLayout_gdd.spec.js @@ -189,7 +189,7 @@ describe('DashboardLayoutGdd', () => { }) await wrapper .findComponent({ ref: 'router-view' }) - .vm.$emit('update-transactions', { firstPage: 2, items: 5 }) + .vm.$emit('update-transactions', { currentPage: 2, pageSize: 5 }) await flushPromises() }) @@ -197,8 +197,8 @@ describe('DashboardLayoutGdd', () => { expect(apolloMock).toBeCalledWith( expect.objectContaining({ variables: { - firstPage: 2, - items: 5, + currentPage: 2, + pageSize: 5, }, }), ) @@ -233,7 +233,7 @@ describe('DashboardLayoutGdd', () => { }) await wrapper .findComponent({ ref: 'router-view' }) - .vm.$emit('update-transactions', { firstPage: 2, items: 5 }) + .vm.$emit('update-transactions', { currentPage: 2, pageSize: 5 }) await flushPromises() }) diff --git a/frontend/src/views/Layout/DashboardLayout_gdd.vue b/frontend/src/views/Layout/DashboardLayout_gdd.vue index 8784b4312..2b63bf417 100755 --- a/frontend/src/views/Layout/DashboardLayout_gdd.vue +++ b/frontend/src/views/Layout/DashboardLayout_gdd.vue @@ -110,8 +110,8 @@ export default { .query({ query: transactionsQuery, variables: { - firstPage: pagination.firstPage, - items: pagination.items, + currentPage: pagination.currentPage, + pageSize: pagination.pageSize, }, fetchPolicy: 'network-only', }) diff --git a/frontend/src/views/Pages/AccountOverview/GddTransactionList.spec.js b/frontend/src/views/Pages/AccountOverview/GddTransactionList.spec.js index 5418ff3ba..e8a74253a 100644 --- a/frontend/src/views/Pages/AccountOverview/GddTransactionList.spec.js +++ b/frontend/src/views/Pages/AccountOverview/GddTransactionList.spec.js @@ -327,7 +327,9 @@ describe('GddTransactionList', () => { it('emits update-transactions when next button is clicked', async () => { 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 () => { @@ -348,7 +350,9 @@ describe('GddTransactionList', () => { 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.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() }) }) diff --git a/frontend/src/views/Pages/AccountOverview/GddTransactionList.vue b/frontend/src/views/Pages/AccountOverview/GddTransactionList.vue index 7348f96de..3b0104068 100644 --- a/frontend/src/views/Pages/AccountOverview/GddTransactionList.vue +++ b/frontend/src/views/Pages/AccountOverview/GddTransactionList.vue @@ -134,8 +134,8 @@ export default { methods: { updateTransactions() { this.$emit('update-transactions', { - firstPage: this.currentPage, - items: this.pageSize, + currentPage: this.currentPage, + pageSize: this.pageSize, }) window.scrollTo(0, 0) }, diff --git a/frontend/src/views/Pages/UserProfileTransactionList.spec.js b/frontend/src/views/Pages/UserProfileTransactionList.spec.js index 057e1dfd5..a9ab48a19 100644 --- a/frontend/src/views/Pages/UserProfileTransactionList.spec.js +++ b/frontend/src/views/Pages/UserProfileTransactionList.spec.js @@ -36,16 +36,16 @@ describe('UserProfileTransactionList', () => { it('emits update-transactions after creation', () => { 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', () => { wrapper .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.arrayContaining([expect.arrayContaining([{ firstPage: 2, items: 25 }])]), + expect.arrayContaining([expect.arrayContaining([{ currentPage: 2, pageSize: 25 }])]), ) })