From 66a5175dc1abff1477bedc3fd655ecd0ff44faa1 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 14 Feb 2023 21:28:58 +0100 Subject: [PATCH 01/25] combine logic for both listTransactionLinks and listTransactionLinksAdmin --- backend/src/graphql/model/TransactionLink.ts | 4 +- .../resolver/TransactionLinkResolver.test.ts | 20 ++-- .../resolver/TransactionLinkResolver.ts | 108 +++++++++--------- backend/src/seeds/graphql/queries.ts | 4 +- frontend/src/graphql/queries.js | 18 +-- 5 files changed, 78 insertions(+), 76 deletions(-) diff --git a/backend/src/graphql/model/TransactionLink.ts b/backend/src/graphql/model/TransactionLink.ts index 18a601948..416527ec9 100644 --- a/backend/src/graphql/model/TransactionLink.ts +++ b/backend/src/graphql/model/TransactionLink.ts @@ -61,8 +61,8 @@ export class TransactionLink { @ObjectType() export class TransactionLinkResult { @Field(() => Int) - linkCount: number + count: number @Field(() => [TransactionLink]) - linkList: TransactionLink[] + links: TransactionLink[] } diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index 09f2f9a02..03f412a43 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -611,8 +611,8 @@ describe('TransactionLinkResolver', () => { expect.objectContaining({ data: { listTransactionLinksAdmin: { - linkCount: 6, - linkList: expect.not.arrayContaining([ + count: 6, + links: expect.not.arrayContaining([ expect.objectContaining({ memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', createdAt: expect.any(String), @@ -647,8 +647,8 @@ describe('TransactionLinkResolver', () => { expect.objectContaining({ data: { listTransactionLinksAdmin: { - linkCount: 6, - linkList: expect.not.arrayContaining([ + count: 6, + links: expect.not.arrayContaining([ expect.objectContaining({ memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', createdAt: expect.any(String), @@ -681,8 +681,8 @@ describe('TransactionLinkResolver', () => { expect.objectContaining({ data: { listTransactionLinksAdmin: { - linkCount: 7, - linkList: expect.arrayContaining([ + count: 7, + links: expect.arrayContaining([ expect.not.objectContaining({ memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', createdAt: expect.any(String), @@ -715,8 +715,8 @@ describe('TransactionLinkResolver', () => { expect.objectContaining({ data: { listTransactionLinksAdmin: { - linkCount: 7, - linkList: expect.arrayContaining([ + count: 7, + links: expect.arrayContaining([ expect.objectContaining({ memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', createdAt: expect.any(String), @@ -752,8 +752,8 @@ describe('TransactionLinkResolver', () => { expect.objectContaining({ data: { listTransactionLinksAdmin: { - linkCount: 6, - linkList: expect.arrayContaining([ + count: 6, + links: expect.arrayContaining([ expect.not.objectContaining({ memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', createdAt: expect.any(String), diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 4647dde60..945ef766c 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -35,6 +35,7 @@ import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' import LogError from '@/server/LogError' import { getLastTransaction } from './util/getLastTransaction' +import { filter } from 'lodash' // TODO: do not export, test it inside the resolver export const transactionLinkCode = (date: Date): string => { @@ -141,30 +142,6 @@ export class TransactionLinkResolver { } } - @Authorized([RIGHTS.LIST_TRANSACTION_LINKS]) - @Query(() => [TransactionLink]) - async listTransactionLinks( - @Args() - { currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated, - @Ctx() context: Context, - ): Promise { - const user = getUser(context) - // const now = new Date() - const transactionLinks = await DbTransactionLink.find({ - where: { - userId: user.id, - redeemedBy: null, - // validUntil: MoreThan(now), - }, - order: { - createdAt: order, - }, - skip: (currentPage - 1) * pageSize, - take: pageSize, - }) - return transactionLinks.map((tl) => new TransactionLink(tl, new User(user))) - } - @Authorized([RIGHTS.REDEEM_TRANSACTION_LINK]) @Mutation(() => Boolean) async redeemTransactionLink( @@ -338,43 +315,66 @@ export class TransactionLinkResolver { } } + @Authorized([RIGHTS.LIST_TRANSACTION_LINKS]) + @Query(() => [TransactionLink]) + async listTransactionLinks( + @Args() + paginated: Paginated, + @Ctx() context: Context, + ): Promise { + const user = getUser(context) + return transactionLinkList( + paginated, + { + withDeleted: false, + withExpired: true, + withRedeemed: false, + }, + user.id, + ) + } + @Authorized([RIGHTS.LIST_TRANSACTION_LINKS_ADMIN]) @Query(() => TransactionLinkResult) async listTransactionLinksAdmin( @Args() - { currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated, + paginated: Paginated, @Arg('filters', () => TransactionLinkFilters, { nullable: true }) - filters: TransactionLinkFilters, + filters: TransactionLinkFilters | null, @Arg('userId', () => Int) userId: number, ): Promise { - const user = await DbUser.findOneOrFail({ id: userId }) - const where: { - userId: number - redeemedBy?: number | null - validUntil?: FindOperator | null - } = { - userId, - redeemedBy: null, - validUntil: MoreThan(new Date()), - } - if (filters) { - if (filters.withRedeemed) delete where.redeemedBy - if (filters.withExpired) delete where.validUntil - } - const [transactionLinks, count] = await DbTransactionLink.findAndCount({ - where, - withDeleted: filters ? filters.withDeleted : false, - order: { - createdAt: order, - }, - skip: (currentPage - 1) * pageSize, - take: pageSize, - }) - - return { - linkCount: count, - linkList: transactionLinks.map((tl) => new TransactionLink(tl, new User(user))), - } + return transactionLinkList(paginated, filters, userId) + } +} + +const transactionLinkList = async ( + { currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated, + filters: TransactionLinkFilters | null, + userId: number, +): Promise => { + const user = await DbUser.findOneOrFail({ id: userId }) + const { withDeleted, withExpired, withRedeemed } = filters || { + withDeleted: false, + withExpired: false, + withRedeemed: false, + } + const [transactionLinks, count] = await DbTransactionLink.findAndCount({ + where: { + userId, + ...(!withRedeemed && { redeemedBy: null }), + ...(!withExpired && { validUntil: MoreThan(new Date()) }), + }, + withDeleted, + order: { + createdAt: order, + }, + skip: (currentPage - 1) * pageSize, + take: pageSize, + }) + + return { + count, + links: transactionLinks.map((tl) => new TransactionLink(tl, new User(user))), } } diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index 385a69479..2c4473ab5 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -224,8 +224,8 @@ export const listTransactionLinksAdmin = gql` currentPage: $currentPage pageSize: $pageSize ) { - linkCount - linkList { + count + links { id amount holdAvailableAmount diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index e199a3730..6e8d420b8 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -126,14 +126,16 @@ export const queryTransactionLink = gql` export const listTransactionLinks = gql` query($currentPage: Int = 1, $pageSize: Int = 5) { listTransactionLinks(currentPage: $currentPage, pageSize: $pageSize) { - id - amount - holdAvailableAmount - memo - link - createdAt - validUntil - redeemedAt + links { + id + amount + holdAvailableAmount + memo + link + createdAt + validUntil + redeemedAt + } } } ` From 5bba531769b7da7006f8f5b73a99e474f1f7a7ed Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 14 Feb 2023 21:29:14 +0100 Subject: [PATCH 02/25] missing graphql changes --- admin/src/components/TransactionLinkList.spec.js | 4 ++-- admin/src/components/TransactionLinkList.vue | 4 ++-- admin/src/graphql/listTransactionLinksAdmin.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/admin/src/components/TransactionLinkList.spec.js b/admin/src/components/TransactionLinkList.spec.js index 094e515b2..09a7872ca 100644 --- a/admin/src/components/TransactionLinkList.spec.js +++ b/admin/src/components/TransactionLinkList.spec.js @@ -9,8 +9,8 @@ const apolloQueryMock = jest.fn() apolloQueryMock.mockResolvedValue({ data: { listTransactionLinksAdmin: { - linkCount: 8, - linkList: [ + count: 8, + links: [ { amount: '19.99', code: '62ef8236ace7217fbd066c5a', diff --git a/admin/src/components/TransactionLinkList.vue b/admin/src/components/TransactionLinkList.vue index 564865440..eb58903c6 100644 --- a/admin/src/components/TransactionLinkList.vue +++ b/admin/src/components/TransactionLinkList.vue @@ -42,8 +42,8 @@ export default { }, }) .then((result) => { - this.rows = result.data.listTransactionLinksAdmin.linkCount - this.items = result.data.listTransactionLinksAdmin.linkList + this.rows = result.data.listTransactionLinksAdmin.count + this.items = result.data.listTransactionLinksAdmin.links }) .catch((error) => { this.toastError(error.message) diff --git a/admin/src/graphql/listTransactionLinksAdmin.js b/admin/src/graphql/listTransactionLinksAdmin.js index 2e4171f02..c069bafd9 100644 --- a/admin/src/graphql/listTransactionLinksAdmin.js +++ b/admin/src/graphql/listTransactionLinksAdmin.js @@ -8,8 +8,8 @@ export const listTransactionLinksAdmin = gql` userId: $userId filters: { withRedeemed: true, withExpired: true, withDeleted: true } ) { - linkCount - linkList { + count + links { id amount holdAvailableAmount From ae940b509abee4b7d302261bfdbf8b40d69705bf Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 14 Feb 2023 21:33:24 +0100 Subject: [PATCH 03/25] removed unused imports --- backend/src/graphql/resolver/TransactionLinkResolver.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 945ef766c..fb028ca2a 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -1,7 +1,7 @@ import { randomBytes } from 'crypto' import Decimal from 'decimal.js-light' -import { getConnection, MoreThan, FindOperator } from '@dbTools/typeorm' +import { getConnection, MoreThan } from '@dbTools/typeorm' import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink' import { User as DbUser } from '@entity/User' @@ -35,7 +35,6 @@ import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' import LogError from '@/server/LogError' import { getLastTransaction } from './util/getLastTransaction' -import { filter } from 'lodash' // TODO: do not export, test it inside the resolver export const transactionLinkCode = (date: Date): string => { From 12c128e0729b9782854ed269e26f1b4d87c88248 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 17 Feb 2023 23:03:38 +0100 Subject: [PATCH 04/25] do not requqery the user --- .../src/graphql/resolver/TransactionLinkResolver.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 4bc6721bf..174134aa6 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -333,7 +333,7 @@ export class TransactionLinkResolver { withExpired: true, withRedeemed: false, }, - user.id, + user, ) } @@ -347,16 +347,15 @@ export class TransactionLinkResolver { @Arg('userId', () => Int) userId: number, ): Promise { - return transactionLinkList(paginated, filters, userId) + return transactionLinkList(paginated, filters, await DbUser.findOneOrFail({ id: userId })) } } const transactionLinkList = async ( { currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated, filters: TransactionLinkFilters | null, - userId: number, + user: DbUser, ): Promise => { - const user = await DbUser.findOneOrFail({ id: userId }) const { withDeleted, withExpired, withRedeemed } = filters || { withDeleted: false, withExpired: false, @@ -364,7 +363,7 @@ const transactionLinkList = async ( } const [transactionLinks, count] = await DbTransactionLink.findAndCount({ where: { - userId, + user: user.id, ...(!withRedeemed && { redeemedBy: null }), ...(!withExpired && { validUntil: MoreThan(new Date()) }), }, @@ -378,6 +377,6 @@ const transactionLinkList = async ( return { count, - links: transactionLinks.map((tl) => new TransactionLink(tl, new User(user))), + links: transactionLinks.map((tl) => new TransactionLink(tl, user)), } } From d21996a045853deea3e29b5ba48a7c67bc1ddf6f Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 17 Feb 2023 23:10:31 +0100 Subject: [PATCH 05/25] externalize transactionLinkList function into utils folder --- .../resolver/TransactionLinkResolver.ts | 34 +---------------- .../resolver/util/transactionLinkList.ts | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 32 deletions(-) create mode 100644 backend/src/graphql/resolver/util/transactionLinkList.ts diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 174134aa6..e0c12d7a7 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -1,7 +1,7 @@ import { randomBytes } from 'crypto' import Decimal from 'decimal.js-light' -import { getConnection, MoreThan } from '@dbTools/typeorm' +import { getConnection } from '@dbTools/typeorm' import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink' import { User as DbUser } from '@entity/User' @@ -13,7 +13,6 @@ import { User } from '@model/User' import { ContributionLink } from '@model/ContributionLink' import { Decay } from '@model/Decay' import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink' -import { Order } from '@enum/Order' import { ContributionType } from '@enum/ContributionType' import { ContributionStatus } from '@enum/ContributionStatus' import { TransactionTypeId } from '@enum/TransactionTypeId' @@ -35,6 +34,7 @@ import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' import LogError from '@/server/LogError' import { getLastTransaction } from './util/getLastTransaction' +import transactionLinkList from './util/transactionLinkList' // TODO: do not export, test it inside the resolver export const transactionLinkCode = (date: Date): string => { @@ -350,33 +350,3 @@ export class TransactionLinkResolver { return transactionLinkList(paginated, filters, await DbUser.findOneOrFail({ id: userId })) } } - -const transactionLinkList = async ( - { currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated, - filters: TransactionLinkFilters | null, - user: DbUser, -): Promise => { - const { withDeleted, withExpired, withRedeemed } = filters || { - withDeleted: false, - withExpired: false, - withRedeemed: false, - } - const [transactionLinks, count] = await DbTransactionLink.findAndCount({ - where: { - user: user.id, - ...(!withRedeemed && { redeemedBy: null }), - ...(!withExpired && { validUntil: MoreThan(new Date()) }), - }, - withDeleted, - order: { - createdAt: order, - }, - skip: (currentPage - 1) * pageSize, - take: pageSize, - }) - - return { - count, - links: transactionLinks.map((tl) => new TransactionLink(tl, user)), - } -} diff --git a/backend/src/graphql/resolver/util/transactionLinkList.ts b/backend/src/graphql/resolver/util/transactionLinkList.ts new file mode 100644 index 000000000..48a91920d --- /dev/null +++ b/backend/src/graphql/resolver/util/transactionLinkList.ts @@ -0,0 +1,37 @@ +import { MoreThan } from '@dbTools/typeorm' +import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink' +import { User as DbUser } from '@entity/User' +import { Order } from '@enum/Order' +import Paginated from '@arg/Paginated' +import TransactionLinkFilters from '@arg/TransactionLinkFilters' +import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink' + +export default async function transactionLinkList( + { currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated, + filters: TransactionLinkFilters | null, + user: DbUser, +): Promise { + const { withDeleted, withExpired, withRedeemed } = filters || { + withDeleted: false, + withExpired: false, + withRedeemed: false, + } + const [transactionLinks, count] = await DbTransactionLink.findAndCount({ + where: { + user: user.id, + ...(!withRedeemed && { redeemedBy: null }), + ...(!withExpired && { validUntil: MoreThan(new Date()) }), + }, + withDeleted, + order: { + createdAt: order, + }, + skip: (currentPage - 1) * pageSize, + take: pageSize, + }) + + return { + count, + links: transactionLinks.map((tl) => new TransactionLink(tl, user)), + } +} From b8870682265d9758013a3beb1716a261360d8fd9 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 17 Feb 2023 23:12:11 +0100 Subject: [PATCH 06/25] remove user constant, not required --- backend/src/graphql/resolver/TransactionLinkResolver.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index e0c12d7a7..186a9bf1d 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -325,7 +325,6 @@ export class TransactionLinkResolver { paginated: Paginated, @Ctx() context: Context, ): Promise { - const user = getUser(context) return transactionLinkList( paginated, { @@ -333,7 +332,7 @@ export class TransactionLinkResolver { withExpired: true, withRedeemed: false, }, - user, + getUser(context), ) } From 16c3ba399e8881f01715ecf0f9dd0f14d1d39df3 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 17 Feb 2023 23:21:37 +0100 Subject: [PATCH 07/25] some misssing changes --- backend/src/graphql/resolver/util/transactionLinkList.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/util/transactionLinkList.ts b/backend/src/graphql/resolver/util/transactionLinkList.ts index 48a91920d..2d151b94a 100644 --- a/backend/src/graphql/resolver/util/transactionLinkList.ts +++ b/backend/src/graphql/resolver/util/transactionLinkList.ts @@ -5,6 +5,7 @@ import { Order } from '@enum/Order' import Paginated from '@arg/Paginated' import TransactionLinkFilters from '@arg/TransactionLinkFilters' import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink' +import { User } from '@/graphql/model/User' export default async function transactionLinkList( { currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated, @@ -18,7 +19,7 @@ export default async function transactionLinkList( } const [transactionLinks, count] = await DbTransactionLink.findAndCount({ where: { - user: user.id, + userId: user.id, ...(!withRedeemed && { redeemedBy: null }), ...(!withExpired && { validUntil: MoreThan(new Date()) }), }, @@ -32,6 +33,6 @@ export default async function transactionLinkList( return { count, - links: transactionLinks.map((tl) => new TransactionLink(tl, user)), + links: transactionLinks.map((tl) => new TransactionLink(tl, new User(user))), } } From c01a94aa248f7660c3d25eb4e9a2dbf3b41db6f8 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 17 Feb 2023 23:25:09 +0100 Subject: [PATCH 08/25] potential frontend fix --- .../src/components/Transactions/TransactionLinkSummary.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Transactions/TransactionLinkSummary.vue b/frontend/src/components/Transactions/TransactionLinkSummary.vue index b54c0e687..f90d6da85 100644 --- a/frontend/src/components/Transactions/TransactionLinkSummary.vue +++ b/frontend/src/components/Transactions/TransactionLinkSummary.vue @@ -90,7 +90,10 @@ export default { fetchPolicy: 'network-only', }) .then((result) => { - this.transactionLinks = [...this.transactionLinks, ...result.data.listTransactionLinks] + this.transactionLinks.links = [ + ...this.transactionLinks, + ...result.data.listTransactionLinks.links, + ] this.$emit('update-transactions') this.pending = false }) From cb058adde64df26b94aaece5a872f7e7db30a22c Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 17 Feb 2023 23:29:09 +0100 Subject: [PATCH 09/25] fix tests --- .../TransactionLinkSummary.spec.js | 184 +++++++++--------- 1 file changed, 94 insertions(+), 90 deletions(-) diff --git a/frontend/src/components/Transactions/TransactionLinkSummary.spec.js b/frontend/src/components/Transactions/TransactionLinkSummary.spec.js index d0086ceab..46041f87f 100644 --- a/frontend/src/components/Transactions/TransactionLinkSummary.spec.js +++ b/frontend/src/components/Transactions/TransactionLinkSummary.spec.js @@ -47,51 +47,53 @@ describe('TransactionLinkSummary', () => { beforeEach(() => { apolloQueryMock.mockResolvedValue({ data: { - listTransactionLinks: [ - { - amount: '75', - link: 'http://localhost/redeem/ce28664b5308c17f931c0367', - createdAt: '2022-03-16T14:22:40.000Z', - holdAvailableAmount: '5.13109484759482747111', - id: 86, - memo: - 'Hokuspokus Haselnuss, Vogelbein und Fliegenfuß, damit der Trick gelingen muss!', - redeemedAt: null, - validUntil: '2022-03-30T14:22:40.000Z', - }, - { - amount: '85', - link: 'http://localhost/redeem/ce28664b5308c17f931c0367', - createdAt: '2022-03-16T14:22:40.000Z', - holdAvailableAmount: '5.13109484759482747111', - id: 107, - memo: 'Mäusespeck und Katzenbuckel, Tricks und Tracks und Zauberkugel!', - redeemedAt: null, - validUntil: '2022-03-30T14:22:40.000Z', - }, - { - amount: '95', - link: 'http://localhost/redeem/ce28664b5308c17f931c0367', - createdAt: '2022-03-16T14:22:40.000Z', - holdAvailableAmount: '5.13109484759482747111', - id: 92, - memo: - 'Abrakadabra 1,2,3, die Sonne kommt herbei. Schweinepups und Spuckebrei, der Regen ist vorbei.', - redeemedAt: null, - validUntil: '2022-03-30T14:22:40.000Z', - }, - { - amount: '150', - link: 'http://localhost/redeem/ce28664b5308c17f931c0367', - createdAt: '2022-03-16T14:22:40.000Z', - holdAvailableAmount: '5.13109484759482747111', - id: 16, - memo: - 'Abrakadabra 1,2,3 was verschwunden ist komme herbei.Wieseldreck und Schweinemist, zaubern das ist keine List.', - redeemedAt: null, - validUntil: '2022-03-30T14:22:40.000Z', - }, - ], + listTransactionLinks: { + links: [ + { + amount: '75', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', + createdAt: '2022-03-16T14:22:40.000Z', + holdAvailableAmount: '5.13109484759482747111', + id: 86, + memo: + 'Hokuspokus Haselnuss, Vogelbein und Fliegenfuß, damit der Trick gelingen muss!', + redeemedAt: null, + validUntil: '2022-03-30T14:22:40.000Z', + }, + { + amount: '85', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', + createdAt: '2022-03-16T14:22:40.000Z', + holdAvailableAmount: '5.13109484759482747111', + id: 107, + memo: 'Mäusespeck und Katzenbuckel, Tricks und Tracks und Zauberkugel!', + redeemedAt: null, + validUntil: '2022-03-30T14:22:40.000Z', + }, + { + amount: '95', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', + createdAt: '2022-03-16T14:22:40.000Z', + holdAvailableAmount: '5.13109484759482747111', + id: 92, + memo: + 'Abrakadabra 1,2,3, die Sonne kommt herbei. Schweinepups und Spuckebrei, der Regen ist vorbei.', + redeemedAt: null, + validUntil: '2022-03-30T14:22:40.000Z', + }, + { + amount: '150', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', + createdAt: '2022-03-16T14:22:40.000Z', + holdAvailableAmount: '5.13109484759482747111', + id: 16, + memo: + 'Abrakadabra 1,2,3 was verschwunden ist komme herbei.Wieseldreck und Schweinemist, zaubern das ist keine List.', + redeemedAt: null, + validUntil: '2022-03-30T14:22:40.000Z', + }, + ], + }, }, }) @@ -166,51 +168,53 @@ describe('TransactionLinkSummary', () => { jest.clearAllMocks() apolloQueryMock.mockResolvedValue({ data: { - listTransactionLinks: [ - { - amount: '76', - link: 'http://localhost/redeem/ce28664b5308c17f931c0367', - createdAt: '2022-03-16T14:22:40.000Z', - holdAvailableAmount: '5.13109484759482747111', - id: 87, - memo: - 'Hat jemand die Nummer von der Hexe aus Schneewittchen? Ich bräuchte mal ein paar Äpfel.', - redeemedAt: null, - validUntil: '2022-03-30T14:22:40.000Z', - }, - { - amount: '86', - link: 'http://localhost/redeem/ce28664b5308c17f931c0367', - createdAt: '2022-03-16T14:22:40.000Z', - holdAvailableAmount: '5.13109484759482747111', - id: 108, - memo: - 'Die Windfahn´ krächzt am Dach, Der Uhu im Geklüfte; Was wispert wie ein Ach Verhallend in die Lüfte?', - redeemedAt: null, - validUntil: '2022-03-30T14:22:40.000Z', - }, - { - amount: '96', - link: 'http://localhost/redeem/ce28664b5308c17f931c0367', - createdAt: '2022-03-16T14:22:40.000Z', - holdAvailableAmount: '5.13109484759482747111', - id: 93, - memo: - 'Verschlafen kräht der Hahn, Ein Blitz noch, und ein trüber, Umwölbter Tag bricht an – Walpurgisnacht vorüber!', - redeemedAt: null, - validUntil: '2022-03-30T14:22:40.000Z', - }, - { - amount: '150', - link: 'http://localhost/redeem/ce28664b5308c17f931c0367', - createdAt: '2022-03-16T14:22:40.000Z', - holdAvailableAmount: '5.13109484759482747111', - id: 17, - memo: 'Eene meene Flaschenschrank, fertig ist der Hexentrank!', - redeemedAt: null, - validUntil: '2022-03-30T14:22:40.000Z', - }, - ], + listTransactionLinks: { + links: [ + { + amount: '76', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', + createdAt: '2022-03-16T14:22:40.000Z', + holdAvailableAmount: '5.13109484759482747111', + id: 87, + memo: + 'Hat jemand die Nummer von der Hexe aus Schneewittchen? Ich bräuchte mal ein paar Äpfel.', + redeemedAt: null, + validUntil: '2022-03-30T14:22:40.000Z', + }, + { + amount: '86', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', + createdAt: '2022-03-16T14:22:40.000Z', + holdAvailableAmount: '5.13109484759482747111', + id: 108, + memo: + 'Die Windfahn´ krächzt am Dach, Der Uhu im Geklüfte; Was wispert wie ein Ach Verhallend in die Lüfte?', + redeemedAt: null, + validUntil: '2022-03-30T14:22:40.000Z', + }, + { + amount: '96', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', + createdAt: '2022-03-16T14:22:40.000Z', + holdAvailableAmount: '5.13109484759482747111', + id: 93, + memo: + 'Verschlafen kräht der Hahn, Ein Blitz noch, und ein trüber, Umwölbter Tag bricht an – Walpurgisnacht vorüber!', + redeemedAt: null, + validUntil: '2022-03-30T14:22:40.000Z', + }, + { + amount: '150', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', + createdAt: '2022-03-16T14:22:40.000Z', + holdAvailableAmount: '5.13109484759482747111', + id: 17, + memo: 'Eene meene Flaschenschrank, fertig ist der Hexentrank!', + redeemedAt: null, + validUntil: '2022-03-30T14:22:40.000Z', + }, + ], + }, }, }) await wrapper.setData({ From 214970dde186063e48ccae54e0189b8d77e7c37a Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 17 Feb 2023 23:38:06 +0100 Subject: [PATCH 10/25] typo --- frontend/src/components/Transactions/TransactionLinkSummary.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Transactions/TransactionLinkSummary.vue b/frontend/src/components/Transactions/TransactionLinkSummary.vue index f90d6da85..c3a62357b 100644 --- a/frontend/src/components/Transactions/TransactionLinkSummary.vue +++ b/frontend/src/components/Transactions/TransactionLinkSummary.vue @@ -90,7 +90,7 @@ export default { fetchPolicy: 'network-only', }) .then((result) => { - this.transactionLinks.links = [ + this.transactionLinks = [ ...this.transactionLinks, ...result.data.listTransactionLinks.links, ] From 3cc549473653142db43f529c9c92cfbd7f559f03 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 20 Feb 2023 14:24:42 +0100 Subject: [PATCH 11/25] refactor(backend): remove admin create contributions --- backend/src/auth/RIGHTS.ts | 1 - .../graphql/model/AdminCreateContributions.ts | 19 ------------- .../graphql/resolver/ContributionResolver.ts | 28 ------------------- 3 files changed, 48 deletions(-) delete mode 100644 backend/src/graphql/model/AdminCreateContributions.ts diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts index 98f6cf118..8b0e82c86 100644 --- a/backend/src/auth/RIGHTS.ts +++ b/backend/src/auth/RIGHTS.ts @@ -42,7 +42,6 @@ export enum RIGHTS { DELETE_USER = 'DELETE_USER', UNDELETE_USER = 'UNDELETE_USER', ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION', - ADMIN_CREATE_CONTRIBUTIONS = 'ADMIN_CREATE_CONTRIBUTIONS', ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION', ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION', LIST_UNCONFIRMED_CONTRIBUTIONS = 'LIST_UNCONFIRMED_CONTRIBUTIONS', diff --git a/backend/src/graphql/model/AdminCreateContributions.ts b/backend/src/graphql/model/AdminCreateContributions.ts deleted file mode 100644 index aa402733a..000000000 --- a/backend/src/graphql/model/AdminCreateContributions.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ObjectType, Field } from 'type-graphql' - -@ObjectType() -export class AdminCreateContributions { - constructor() { - this.success = false - this.successfulContribution = [] - this.failedContribution = [] - } - - @Field(() => Boolean) - success: boolean - - @Field(() => [String]) - successfulContribution: string[] - - @Field(() => [String]) - failedContribution: string[] -} diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index c2f0d7d23..2e3367e1f 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -8,7 +8,6 @@ import { UserContact } from '@entity/UserContact' import { User as DbUser } from '@entity/User' import { Transaction as DbTransaction } from '@entity/Transaction' -import { AdminCreateContributions } from '@model/AdminCreateContributions' import { AdminUpdateContribution } from '@model/AdminUpdateContribution' import { Contribution, ContributionListResult } from '@model/Contribution' import { Decay } from '@model/Decay' @@ -329,33 +328,6 @@ export class ContributionResolver { return getUserCreation(emailContact.userId, clientTimezoneOffset) } - @Authorized([RIGHTS.ADMIN_CREATE_CONTRIBUTIONS]) - @Mutation(() => AdminCreateContributions) - async adminCreateContributions( - @Arg('pendingCreations', () => [AdminCreateContributionArgs]) - contributions: AdminCreateContributionArgs[], - @Ctx() context: Context, - ): Promise { - let success = false - const successfulContribution: string[] = [] - const failedContribution: string[] = [] - for (const contribution of contributions) { - await this.adminCreateContribution(contribution, context) - .then(() => { - successfulContribution.push(contribution.email) - success = true - }) - .catch(() => { - failedContribution.push(contribution.email) - }) - } - return { - success, - successfulContribution, - failedContribution, - } - } - @Authorized([RIGHTS.ADMIN_UPDATE_CONTRIBUTION]) @Mutation(() => AdminUpdateContribution) async adminUpdateContribution( From 72165ac99d5418c075c0126534fd3281515c6b69 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 20 Feb 2023 14:39:05 +0100 Subject: [PATCH 12/25] fix test --- .../resolver/ContributionResolver.test.ts | 117 +----------------- 1 file changed, 6 insertions(+), 111 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index b56180c45..de049129a 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -13,7 +13,6 @@ import { denyContribution, confirmContribution, adminCreateContribution, - adminCreateContributions, adminUpdateContribution, adminDeleteContribution, login, @@ -1653,21 +1652,6 @@ describe('ContributionResolver', () => { }) }) - describe('adminCreateContributions', () => { - it('returns an error', async () => { - await expect( - mutate({ - mutation: adminCreateContributions, - variables: { pendingCreations: [variables] }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('401 Unauthorized')], - }), - ) - }) - }) - describe('adminUpdateContribution', () => { it('returns an error', async () => { await expect( @@ -1761,21 +1745,6 @@ describe('ContributionResolver', () => { }) }) - describe('adminCreateContributions', () => { - it('returns an error', async () => { - await expect( - mutate({ - mutation: adminCreateContributions, - variables: { pendingCreations: [variables] }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('401 Unauthorized')], - }), - ) - }) - }) - describe('adminUpdateContribution', () => { it('returns an error', async () => { await expect( @@ -2120,59 +2089,13 @@ describe('ContributionResolver', () => { }) }) - describe('adminCreateContributions', () => { + describe('adminUpdateContribution', () => { // at this point we have this data in DB: // bibi@bloxberg.de: [1000, 1000, 800] // peter@lustig.de: [1000, 600, 1000] // stephen@hawking.uk: [1000, 1000, 1000] - deleted // garrick@ollivander.com: [1000, 1000, 1000] - not activated - const massCreationVariables = [ - 'bibi@bloxberg.de', - 'peter@lustig.de', - 'stephen@hawking.uk', - 'garrick@ollivander.com', - 'bob@baumeister.de', - ].map((email) => { - return { - email, - amount: new Decimal(500), - memo: 'Grundeinkommen', - creationDate: contributionDateFormatter(new Date()), - } - }) - - it('returns success, two successful creation and three failed creations', async () => { - await expect( - mutate({ - mutation: adminCreateContributions, - variables: { pendingCreations: massCreationVariables }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - adminCreateContributions: { - success: true, - successfulContribution: ['bibi@bloxberg.de', 'peter@lustig.de'], - failedContribution: [ - 'stephen@hawking.uk', - 'garrick@ollivander.com', - 'bob@baumeister.de', - ], - }, - }, - }), - ) - }) - }) - - describe('adminUpdateContribution', () => { - // at this I expect to have this data in DB: - // bibi@bloxberg.de: [1000, 1000, 300] - // peter@lustig.de: [1000, 600, 500] - // stephen@hawking.uk: [1000, 1000, 1000] - deleted - // garrick@ollivander.com: [1000, 1000, 1000] - not activated - describe('user for creation to update does not exist', () => { it('throws an error', async () => { jest.clearAllMocks() @@ -2386,7 +2309,7 @@ describe('ContributionResolver', () => { date: expect.any(String), memo: 'Das war leider zu Viel!', amount: '200', - creation: ['1000', '800', '500'], + creation: ['1000', '800', '1000'], }, }, }), @@ -2424,29 +2347,12 @@ describe('ContributionResolver', () => { memo: 'Das war leider zu Viel!', amount: '200', moderator: admin.id, - creation: ['1000', '800', '500'], - }), - expect.objectContaining({ - id: expect.any(Number), - firstName: 'Peter', - lastName: 'Lustig', - email: 'peter@lustig.de', - date: expect.any(String), - memo: 'Grundeinkommen', - amount: '500', - moderator: admin.id, - creation: ['1000', '800', '500'], + creation: ['1000', '800', '1000'], }), expect.not.objectContaining({ - id: expect.any(Number), - firstName: 'Bibi', - lastName: 'Bloxberg', email: 'bibi@bloxberg.de', - date: expect.any(String), memo: 'Test contribution to delete', amount: '100', - moderator: null, - creation: ['1000', '1000', '90'], }), expect.objectContaining({ id: expect.any(Number), @@ -2457,7 +2363,7 @@ describe('ContributionResolver', () => { memo: 'Test PENDING contribution update', amount: '10', moderator: null, - creation: ['1000', '1000', '90'], + creation: ['1000', '1000', '590'], }), expect.objectContaining({ id: expect.any(Number), @@ -2468,18 +2374,7 @@ describe('ContributionResolver', () => { memo: 'Test IN_PROGRESS contribution', amount: '100', moderator: null, - creation: ['1000', '1000', '90'], - }), - expect.objectContaining({ - id: expect.any(Number), - firstName: 'Bibi', - lastName: 'Bloxberg', - email: 'bibi@bloxberg.de', - date: expect.any(String), - memo: 'Grundeinkommen', - amount: '500', - moderator: admin.id, - creation: ['1000', '1000', '90'], + creation: ['1000', '1000', '590'], }), expect.objectContaining({ id: expect.any(Number), @@ -2490,7 +2385,7 @@ describe('ContributionResolver', () => { memo: 'Aktives Grundeinkommen', amount: '200', moderator: admin.id, - creation: ['1000', '1000', '90'], + creation: ['1000', '1000', '590'], }), ]), }, From 9d60e414adfc4712991d0a004f2061401e4dbfd6 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 20 Feb 2023 14:53:58 +0100 Subject: [PATCH 13/25] remove admin create contributions from admin interface --- admin/src/components/CreationFormular.spec.js | 117 ------------------ admin/src/components/CreationFormular.vue | 97 ++++----------- .../src/components/Tables/SearchUserTable.vue | 1 - admin/src/graphql/adminCreateContributions.js | 11 -- 4 files changed, 26 insertions(+), 200 deletions(-) delete mode 100644 admin/src/graphql/adminCreateContributions.js diff --git a/admin/src/components/CreationFormular.spec.js b/admin/src/components/CreationFormular.spec.js index 6e4c1dc6e..36bfd33f8 100644 --- a/admin/src/components/CreationFormular.spec.js +++ b/admin/src/components/CreationFormular.spec.js @@ -1,7 +1,6 @@ import { mount } from '@vue/test-utils' import CreationFormular from './CreationFormular.vue' import { adminCreateContribution } from '../graphql/adminCreateContribution' -import { adminCreateContributions } from '../graphql/adminCreateContributions' import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup' const localVue = global.localVue @@ -328,122 +327,6 @@ describe('CreationFormular', () => { }) }) }) - - describe('mass creation with success', () => { - beforeEach(async () => { - jest.clearAllMocks() - apolloMutateMock.mockResolvedValue({ - data: { - adminCreateContributions: { - success: true, - successfulContribution: ['bob@baumeister.de', 'bibi@bloxberg.de'], - failedContribution: [], - }, - }, - }) - await wrapper.setProps({ - type: 'massCreation', - creation: [200, 400, 600], - items: [{ email: 'bob@baumeister.de' }, { email: 'bibi@bloxberg.de' }], - }) - await wrapper.findAll('input[type="radio"]').at(1).setChecked() - await wrapper.find('textarea').setValue('Test mass create coins') - await wrapper.find('input[type="number"]').setValue(200) - await wrapper.find('.test-submit').trigger('click') - }) - - it('calls the API', () => { - expect(apolloMutateMock).toBeCalledWith( - expect.objectContaining({ - mutation: adminCreateContributions, - variables: { - pendingCreations: [ - { - email: 'bob@baumeister.de', - creationDate: getCreationDate(1), - amount: 200, - memo: 'Test mass create coins', - }, - { - email: 'bibi@bloxberg.de', - creationDate: getCreationDate(1), - amount: 200, - memo: 'Test mass create coins', - }, - ], - }, - }), - ) - }) - - it('updates open creations in store', () => { - expect(stateCommitMock).toBeCalledWith('openCreationsPlus', 2) - }) - - it('emits remove-all-bookmark', () => { - expect(wrapper.emitted('remove-all-bookmark')).toBeTruthy() - }) - }) - - describe('mass creation with success but all failed', () => { - beforeEach(async () => { - jest.clearAllMocks() - apolloMutateMock.mockResolvedValue({ - data: { - adminCreateContributions: { - success: true, - successfulContribution: [], - failedContribution: ['bob@baumeister.de', 'bibi@bloxberg.de'], - }, - }, - }) - await wrapper.setProps({ - type: 'massCreation', - creation: [200, 400, 600], - items: [{ email: 'bob@baumeister.de' }, { email: 'bibi@bloxberg.de' }], - }) - await wrapper.findAll('input[type="radio"]').at(1).setChecked() - await wrapper.find('textarea').setValue('Test mass create coins') - await wrapper.find('input[type="number"]').setValue(200) - await wrapper.find('.test-submit').trigger('click') - }) - - it('updates open creations in store', () => { - expect(stateCommitMock).toBeCalledWith('openCreationsPlus', 0) - }) - - it('emits remove all bookmarks', () => { - expect(wrapper.emitted('remove-all-bookmark')).toBeTruthy() - }) - - it('emits toast failed creations with two emails', () => { - expect(wrapper.emitted('toast-failed-creations')).toEqual([ - [['bob@baumeister.de', 'bibi@bloxberg.de']], - ]) - }) - }) - - describe('mass creation with error', () => { - beforeEach(async () => { - jest.clearAllMocks() - apolloMutateMock.mockRejectedValue({ - message: 'Oh no!', - }) - await wrapper.setProps({ - type: 'massCreation', - creation: [200, 400, 600], - items: [{ email: 'bob@baumeister.de' }, { email: 'bibi@bloxberg.de' }], - }) - await wrapper.findAll('input[type="radio"]').at(1).setChecked() - await wrapper.find('textarea').setValue('Test mass create coins') - await wrapper.find('input[type="number"]').setValue(200) - await wrapper.find('.test-submit').trigger('click') - }) - - it('toasts an error message', () => { - expect(toastErrorSpy).toBeCalledWith('Oh no!') - }) - }) }) }) }) diff --git a/admin/src/components/CreationFormular.vue b/admin/src/components/CreationFormular.vue index 2201838de..137b46400 100644 --- a/admin/src/components/CreationFormular.vue +++ b/admin/src/components/CreationFormular.vue @@ -86,16 +86,11 @@