Merge pull request #2706 from gradido/combine_listTransactionLinks_and_listTransactionLinksAdmin

refactor(backend): combine logic for `listTransactionLinks` & `listTransactionLinksAdmin`
This commit is contained in:
Ulf Gebhardt 2023-02-27 22:58:23 +01:00 committed by GitHub
commit 91593ad4bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 212 additions and 174 deletions

View File

@ -9,8 +9,8 @@ const apolloQueryMock = jest.fn()
apolloQueryMock.mockResolvedValue({ apolloQueryMock.mockResolvedValue({
data: { data: {
listTransactionLinksAdmin: { listTransactionLinksAdmin: {
linkCount: 8, count: 8,
linkList: [ links: [
{ {
amount: '19.99', amount: '19.99',
code: '62ef8236ace7217fbd066c5a', code: '62ef8236ace7217fbd066c5a',

View File

@ -42,8 +42,8 @@ export default {
}, },
}) })
.then((result) => { .then((result) => {
this.rows = result.data.listTransactionLinksAdmin.linkCount this.rows = result.data.listTransactionLinksAdmin.count
this.items = result.data.listTransactionLinksAdmin.linkList this.items = result.data.listTransactionLinksAdmin.links
}) })
.catch((error) => { .catch((error) => {
this.toastError(error.message) this.toastError(error.message)

View File

@ -8,8 +8,8 @@ export const listTransactionLinksAdmin = gql`
userId: $userId userId: $userId
filters: { withRedeemed: true, withExpired: true, withDeleted: true } filters: { withRedeemed: true, withExpired: true, withDeleted: true }
) { ) {
linkCount count
linkList { links {
id id
amount amount
holdAvailableAmount holdAvailableAmount

View File

@ -61,8 +61,8 @@ export class TransactionLink {
@ObjectType() @ObjectType()
export class TransactionLinkResult { export class TransactionLinkResult {
@Field(() => Int) @Field(() => Int)
linkCount: number count: number
@Field(() => [TransactionLink]) @Field(() => [TransactionLink])
linkList: TransactionLink[] links: TransactionLink[]
} }

View File

@ -600,6 +600,26 @@ describe('TransactionLinkResolver', () => {
resetToken() resetToken()
}) })
describe('', () => {
it('throws error when user does not exists', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: listTransactionLinksAdmin,
variables: {
userId: -1,
},
}),
).resolves.toMatchObject({
errors: [new GraphQLError('Could not find requested User')],
})
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('Could not find requested User', -1)
})
})
describe('without any filters', () => { describe('without any filters', () => {
it('finds 6 open transaction links and no deleted or redeemed', async () => { it('finds 6 open transaction links and no deleted or redeemed', async () => {
await expect( await expect(
@ -611,8 +631,8 @@ describe('TransactionLinkResolver', () => {
expect.objectContaining({ expect.objectContaining({
data: { data: {
listTransactionLinksAdmin: { listTransactionLinksAdmin: {
linkCount: 6, count: 6,
linkList: expect.not.arrayContaining([ links: expect.not.arrayContaining([
expect.objectContaining({ expect.objectContaining({
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
createdAt: expect.any(String), createdAt: expect.any(String),
@ -647,8 +667,8 @@ describe('TransactionLinkResolver', () => {
expect.objectContaining({ expect.objectContaining({
data: { data: {
listTransactionLinksAdmin: { listTransactionLinksAdmin: {
linkCount: 6, count: 6,
linkList: expect.not.arrayContaining([ links: expect.not.arrayContaining([
expect.objectContaining({ expect.objectContaining({
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
createdAt: expect.any(String), createdAt: expect.any(String),
@ -681,8 +701,8 @@ describe('TransactionLinkResolver', () => {
expect.objectContaining({ expect.objectContaining({
data: { data: {
listTransactionLinksAdmin: { listTransactionLinksAdmin: {
linkCount: 7, count: 7,
linkList: expect.arrayContaining([ links: expect.arrayContaining([
expect.not.objectContaining({ expect.not.objectContaining({
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
createdAt: expect.any(String), createdAt: expect.any(String),
@ -715,8 +735,8 @@ describe('TransactionLinkResolver', () => {
expect.objectContaining({ expect.objectContaining({
data: { data: {
listTransactionLinksAdmin: { listTransactionLinksAdmin: {
linkCount: 7, count: 7,
linkList: expect.arrayContaining([ links: expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
createdAt: expect.any(String), createdAt: expect.any(String),
@ -752,8 +772,8 @@ describe('TransactionLinkResolver', () => {
expect.objectContaining({ expect.objectContaining({
data: { data: {
listTransactionLinksAdmin: { listTransactionLinksAdmin: {
linkCount: 6, count: 6,
linkList: expect.arrayContaining([ links: expect.arrayContaining([
expect.not.objectContaining({ expect.not.objectContaining({
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
createdAt: expect.any(String), createdAt: expect.any(String),

View File

@ -1,7 +1,7 @@
import { randomBytes } from 'crypto' import { randomBytes } from 'crypto'
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
import { getConnection, MoreThan, FindOperator } from '@dbTools/typeorm' import { getConnection } from '@dbTools/typeorm'
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink' import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { User as DbUser } from '@entity/User' import { User as DbUser } from '@entity/User'
@ -13,7 +13,6 @@ import { User } from '@model/User'
import { ContributionLink } from '@model/ContributionLink' import { ContributionLink } from '@model/ContributionLink'
import { Decay } from '@model/Decay' import { Decay } from '@model/Decay'
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink' import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
import { Order } from '@enum/Order'
import { ContributionType } from '@enum/ContributionType' import { ContributionType } from '@enum/ContributionType'
import { ContributionStatus } from '@enum/ContributionStatus' import { ContributionStatus } from '@enum/ContributionStatus'
import { TransactionTypeId } from '@enum/TransactionTypeId' import { TransactionTypeId } from '@enum/TransactionTypeId'
@ -35,6 +34,7 @@ import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import LogError from '@/server/LogError' import LogError from '@/server/LogError'
import { getLastTransaction } from './util/getLastTransaction' import { getLastTransaction } from './util/getLastTransaction'
import transactionLinkList from './util/transactionLinkList'
// TODO: do not export, test it inside the resolver // TODO: do not export, test it inside the resolver
export const transactionLinkCode = (date: Date): string => { export const transactionLinkCode = (date: Date): string => {
@ -145,30 +145,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<TransactionLink[]> {
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]) @Authorized([RIGHTS.REDEEM_TRANSACTION_LINK])
@Mutation(() => Boolean) @Mutation(() => Boolean)
async redeemTransactionLink( async redeemTransactionLink(
@ -342,43 +318,38 @@ export class TransactionLinkResolver {
} }
} }
@Authorized([RIGHTS.LIST_TRANSACTION_LINKS])
@Query(() => TransactionLinkResult)
async listTransactionLinks(
@Args()
paginated: Paginated,
@Ctx() context: Context,
): Promise<TransactionLinkResult> {
return transactionLinkList(
paginated,
{
withDeleted: false,
withExpired: true,
withRedeemed: false,
},
getUser(context),
)
}
@Authorized([RIGHTS.LIST_TRANSACTION_LINKS_ADMIN]) @Authorized([RIGHTS.LIST_TRANSACTION_LINKS_ADMIN])
@Query(() => TransactionLinkResult) @Query(() => TransactionLinkResult)
async listTransactionLinksAdmin( async listTransactionLinksAdmin(
@Args() @Args()
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated, paginated: Paginated,
@Arg('filters', () => TransactionLinkFilters, { nullable: true }) @Arg('filters', () => TransactionLinkFilters, { nullable: true })
filters: TransactionLinkFilters, filters: TransactionLinkFilters | null,
@Arg('userId', () => Int) @Arg('userId', () => Int)
userId: number, userId: number,
): Promise<TransactionLinkResult> { ): Promise<TransactionLinkResult> {
const user = await DbUser.findOneOrFail({ id: userId }) const user = await DbUser.findOne({ id: userId })
const where: { if (!user) {
userId: number throw new LogError('Could not find requested User', userId)
redeemedBy?: number | null
validUntil?: FindOperator<Date> | 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, user)
} }
} }

View File

@ -0,0 +1,38 @@
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'
import { User } from '@/graphql/model/User'
export default async function transactionLinkList(
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
filters: TransactionLinkFilters | null,
user: DbUser,
): Promise<TransactionLinkResult> {
const { withDeleted, withExpired, withRedeemed } = filters || {
withDeleted: false,
withExpired: false,
withRedeemed: false,
}
const [transactionLinks, count] = await DbTransactionLink.findAndCount({
where: {
userId: 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, new User(user))),
}
}

View File

@ -250,8 +250,8 @@ export const listTransactionLinksAdmin = gql`
currentPage: $currentPage currentPage: $currentPage
pageSize: $pageSize pageSize: $pageSize
) { ) {
linkCount count
linkList { links {
id id
amount amount
holdAvailableAmount holdAvailableAmount

View File

@ -47,51 +47,53 @@ describe('TransactionLinkSummary', () => {
beforeEach(() => { beforeEach(() => {
apolloQueryMock.mockResolvedValue({ apolloQueryMock.mockResolvedValue({
data: { data: {
listTransactionLinks: [ listTransactionLinks: {
{ links: [
amount: '75', {
link: 'http://localhost/redeem/ce28664b5308c17f931c0367', amount: '75',
createdAt: '2022-03-16T14:22:40.000Z', link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
holdAvailableAmount: '5.13109484759482747111', createdAt: '2022-03-16T14:22:40.000Z',
id: 86, holdAvailableAmount: '5.13109484759482747111',
memo: id: 86,
'Hokuspokus Haselnuss, Vogelbein und Fliegenfuß, damit der Trick gelingen muss!', memo:
redeemedAt: null, 'Hokuspokus Haselnuss, Vogelbein und Fliegenfuß, damit der Trick gelingen muss!',
validUntil: '2022-03-30T14:22:40.000Z', redeemedAt: null,
}, validUntil: '2022-03-30T14:22:40.000Z',
{ },
amount: '85', {
link: 'http://localhost/redeem/ce28664b5308c17f931c0367', amount: '85',
createdAt: '2022-03-16T14:22:40.000Z', link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
holdAvailableAmount: '5.13109484759482747111', createdAt: '2022-03-16T14:22:40.000Z',
id: 107, holdAvailableAmount: '5.13109484759482747111',
memo: 'Mäusespeck und Katzenbuckel, Tricks und Tracks und Zauberkugel!', id: 107,
redeemedAt: null, memo: 'Mäusespeck und Katzenbuckel, Tricks und Tracks und Zauberkugel!',
validUntil: '2022-03-30T14:22:40.000Z', redeemedAt: null,
}, validUntil: '2022-03-30T14:22:40.000Z',
{ },
amount: '95', {
link: 'http://localhost/redeem/ce28664b5308c17f931c0367', amount: '95',
createdAt: '2022-03-16T14:22:40.000Z', link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
holdAvailableAmount: '5.13109484759482747111', createdAt: '2022-03-16T14:22:40.000Z',
id: 92, holdAvailableAmount: '5.13109484759482747111',
memo: id: 92,
'Abrakadabra 1,2,3, die Sonne kommt herbei. Schweinepups und Spuckebrei, der Regen ist vorbei.', memo:
redeemedAt: null, 'Abrakadabra 1,2,3, die Sonne kommt herbei. Schweinepups und Spuckebrei, der Regen ist vorbei.',
validUntil: '2022-03-30T14:22:40.000Z', redeemedAt: null,
}, validUntil: '2022-03-30T14:22:40.000Z',
{ },
amount: '150', {
link: 'http://localhost/redeem/ce28664b5308c17f931c0367', amount: '150',
createdAt: '2022-03-16T14:22:40.000Z', link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
holdAvailableAmount: '5.13109484759482747111', createdAt: '2022-03-16T14:22:40.000Z',
id: 16, holdAvailableAmount: '5.13109484759482747111',
memo: id: 16,
'Abrakadabra 1,2,3 was verschwunden ist komme herbei.Wieseldreck und Schweinemist, zaubern das ist keine List.', memo:
redeemedAt: null, 'Abrakadabra 1,2,3 was verschwunden ist komme herbei.Wieseldreck und Schweinemist, zaubern das ist keine List.',
validUntil: '2022-03-30T14:22:40.000Z', redeemedAt: null,
}, validUntil: '2022-03-30T14:22:40.000Z',
], },
],
},
}, },
}) })
@ -166,51 +168,53 @@ describe('TransactionLinkSummary', () => {
jest.clearAllMocks() jest.clearAllMocks()
apolloQueryMock.mockResolvedValue({ apolloQueryMock.mockResolvedValue({
data: { data: {
listTransactionLinks: [ listTransactionLinks: {
{ links: [
amount: '76', {
link: 'http://localhost/redeem/ce28664b5308c17f931c0367', amount: '76',
createdAt: '2022-03-16T14:22:40.000Z', link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
holdAvailableAmount: '5.13109484759482747111', createdAt: '2022-03-16T14:22:40.000Z',
id: 87, holdAvailableAmount: '5.13109484759482747111',
memo: id: 87,
'Hat jemand die Nummer von der Hexe aus Schneewittchen? Ich bräuchte mal ein paar Äpfel.', memo:
redeemedAt: null, 'Hat jemand die Nummer von der Hexe aus Schneewittchen? Ich bräuchte mal ein paar Äpfel.',
validUntil: '2022-03-30T14:22:40.000Z', redeemedAt: null,
}, validUntil: '2022-03-30T14:22:40.000Z',
{ },
amount: '86', {
link: 'http://localhost/redeem/ce28664b5308c17f931c0367', amount: '86',
createdAt: '2022-03-16T14:22:40.000Z', link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
holdAvailableAmount: '5.13109484759482747111', createdAt: '2022-03-16T14:22:40.000Z',
id: 108, holdAvailableAmount: '5.13109484759482747111',
memo: id: 108,
'Die Windfahn´ krächzt am Dach, Der Uhu im Geklüfte; Was wispert wie ein Ach Verhallend in die Lüfte?', memo:
redeemedAt: null, 'Die Windfahn´ krächzt am Dach, Der Uhu im Geklüfte; Was wispert wie ein Ach Verhallend in die Lüfte?',
validUntil: '2022-03-30T14:22:40.000Z', redeemedAt: null,
}, validUntil: '2022-03-30T14:22:40.000Z',
{ },
amount: '96', {
link: 'http://localhost/redeem/ce28664b5308c17f931c0367', amount: '96',
createdAt: '2022-03-16T14:22:40.000Z', link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
holdAvailableAmount: '5.13109484759482747111', createdAt: '2022-03-16T14:22:40.000Z',
id: 93, holdAvailableAmount: '5.13109484759482747111',
memo: id: 93,
'Verschlafen kräht der Hahn, Ein Blitz noch, und ein trüber, Umwölbter Tag bricht an Walpurgisnacht vorüber!', memo:
redeemedAt: null, 'Verschlafen kräht der Hahn, Ein Blitz noch, und ein trüber, Umwölbter Tag bricht an Walpurgisnacht vorüber!',
validUntil: '2022-03-30T14:22:40.000Z', redeemedAt: null,
}, validUntil: '2022-03-30T14:22:40.000Z',
{ },
amount: '150', {
link: 'http://localhost/redeem/ce28664b5308c17f931c0367', amount: '150',
createdAt: '2022-03-16T14:22:40.000Z', link: 'http://localhost/redeem/ce28664b5308c17f931c0367',
holdAvailableAmount: '5.13109484759482747111', createdAt: '2022-03-16T14:22:40.000Z',
id: 17, holdAvailableAmount: '5.13109484759482747111',
memo: 'Eene meene Flaschenschrank, fertig ist der Hexentrank!', id: 17,
redeemedAt: null, memo: 'Eene meene Flaschenschrank, fertig ist der Hexentrank!',
validUntil: '2022-03-30T14:22:40.000Z', redeemedAt: null,
}, validUntil: '2022-03-30T14:22:40.000Z',
], },
],
},
}, },
}) })
await wrapper.setData({ await wrapper.setData({

View File

@ -90,7 +90,10 @@ export default {
fetchPolicy: 'network-only', fetchPolicy: 'network-only',
}) })
.then((result) => { .then((result) => {
this.transactionLinks = [...this.transactionLinks, ...result.data.listTransactionLinks] this.transactionLinks = [
...this.transactionLinks,
...result.data.listTransactionLinks.links,
]
this.$emit('update-transactions') this.$emit('update-transactions')
this.pending = false this.pending = false
}) })

View File

@ -126,14 +126,16 @@ export const queryTransactionLink = gql`
export const listTransactionLinks = gql` export const listTransactionLinks = gql`
query($currentPage: Int = 1, $pageSize: Int = 5) { query($currentPage: Int = 1, $pageSize: Int = 5) {
listTransactionLinks(currentPage: $currentPage, pageSize: $pageSize) { listTransactionLinks(currentPage: $currentPage, pageSize: $pageSize) {
id links {
amount id
holdAvailableAmount amount
memo holdAvailableAmount
link memo
createdAt link
validUntil createdAt
redeemedAt validUntil
redeemedAt
}
} }
} }
` `