Merge branch 'master' into archive_transform_valid_transactions

This commit is contained in:
einhornimmond 2022-07-12 10:49:34 +02:00 committed by GitHub
commit b50acc1a10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 212 additions and 7 deletions

View File

@ -26,6 +26,7 @@ export enum RIGHTS {
LIST_TRANSACTION_LINKS = 'LIST_TRANSACTION_LINKS',
GDT_BALANCE = 'GDT_BALANCE',
CREATE_CONTRIBUTION = 'CREATE_CONTRIBUTION',
LIST_CONTRIBUTIONS = 'LIST_CONTRIBUTIONS',
// Admin
SEARCH_USERS = 'SEARCH_USERS',
SET_USER_ROLE = 'SET_USER_ROLE',

View File

@ -24,6 +24,7 @@ export const ROLE_USER = new Role('user', [
RIGHTS.LIST_TRANSACTION_LINKS,
RIGHTS.GDT_BALANCE,
RIGHTS.CREATE_CONTRIBUTION,
RIGHTS.LIST_CONTRIBUTIONS,
])
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights

View File

@ -0,0 +1,43 @@
import { ObjectType, Field, Int } from 'type-graphql'
import Decimal from 'decimal.js-light'
import { Contribution as dbContribution } from '@entity/Contribution'
import { User } from './User'
@ObjectType()
export class Contribution {
constructor(contribution: dbContribution, user: User) {
this.id = contribution.id
this.user = user
this.amount = contribution.amount
this.memo = contribution.memo
this.createdAt = contribution.createdAt
this.deletedAt = contribution.deletedAt
}
@Field(() => Number)
id: number
@Field(() => User)
user: User
@Field(() => Decimal)
amount: Decimal
@Field(() => String)
memo: string
@Field(() => Date)
createdAt: Date
@Field(() => Date, { nullable: true })
deletedAt: Date | null
}
@ObjectType()
export class ContributionListResult {
@Field(() => Int)
linkCount: number
@Field(() => [Contribution])
linkList: Contribution[]
}

View File

@ -3,10 +3,13 @@
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { createContribution } from '@/seeds/graphql/mutations'
import { login } from '@/seeds/graphql/queries'
import { listContributions, login } from '@/seeds/graphql/queries'
import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
import { GraphQLError } from 'graphql'
import { userFactory } from '@/seeds/factory/user'
import { creationFactory } from '@/seeds/factory/creation'
import { creations } from '@/seeds/creation/index'
import { peterLustig } from '@/seeds/users/peter-lustig'
let mutate: any, query: any, con: any
let testEnv: any
@ -111,6 +114,7 @@ describe('ContributionResolver', () => {
expect.objectContaining({
data: {
createContribution: {
id: expect.any(Number),
amount: '100',
memo: 'Test env contribution',
},
@ -121,4 +125,109 @@ describe('ContributionResolver', () => {
})
})
})
describe('listContributions', () => {
describe('unauthenticated', () => {
it('returns an error', async () => {
await expect(
query({
query: listContributions,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
filterConfirmed: false,
},
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('401 Unauthorized')],
}),
)
})
})
describe('authenticated', () => {
beforeAll(async () => {
await userFactory(testEnv, bibiBloxberg)
await userFactory(testEnv, peterLustig)
const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de')
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await creationFactory(testEnv, bibisCreation!)
await query({
query: login,
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
})
await mutate({
mutation: createContribution,
variables: {
amount: 100.0,
memo: 'Test env contribution',
creationDate: new Date().toString(),
},
})
})
describe('filter confirmed is false', () => {
it('returns creations', async () => {
await expect(
query({
query: listContributions,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
filterConfirmed: false,
},
}),
).resolves.toEqual(
expect.objectContaining({
data: {
listContributions: expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
memo: 'Herzlich Willkommen bei Gradido!',
amount: '1000',
}),
expect.objectContaining({
id: expect.any(Number),
memo: 'Test env contribution',
amount: '100',
}),
]),
},
}),
)
})
})
describe('filter confirmed is true', () => {
it('returns only unconfirmed creations', async () => {
await expect(
query({
query: listContributions,
variables: {
currentPage: 1,
pageSize: 25,
order: 'DESC',
filterConfirmed: true,
},
}),
).resolves.toEqual(
expect.objectContaining({
data: {
listContributions: expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
memo: 'Test env contribution',
amount: '100',
}),
]),
},
}),
)
})
})
})
})
})

View File

@ -1,10 +1,15 @@
import { RIGHTS } from '@/auth/RIGHTS'
import { Context, getUser } from '@/server/context'
import { backendLogger as logger } from '@/server/logger'
import { Contribution } from '@entity/Contribution'
import { Args, Authorized, Ctx, Mutation, Resolver } from 'type-graphql'
import ContributionArgs from '../arg/ContributionArgs'
import { UnconfirmedContribution } from '../model/UnconfirmedContribution'
import { Contribution as dbContribution } from '@entity/Contribution'
import { Arg, Args, Authorized, Ctx, Mutation, Query, Resolver } from 'type-graphql'
import { FindOperator, IsNull } from '@dbTools/typeorm'
import ContributionArgs from '@arg/ContributionArgs'
import Paginated from '@arg/Paginated'
import { Order } from '@enum/Order'
import { Contribution } from '@model/Contribution'
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
import { User } from '@model/User'
import { validateContribution, getUserCreation } from './util/creations'
@Resolver()
@ -21,7 +26,7 @@ export class ContributionResolver {
const creationDateObj = new Date(creationDate)
validateContribution(creations, amount, creationDateObj)
const contribution = Contribution.create()
const contribution = dbContribution.create()
contribution.userId = user.id
contribution.amount = amount
contribution.createdAt = new Date()
@ -29,7 +34,33 @@ export class ContributionResolver {
contribution.memo = memo
logger.trace('contribution to save', contribution)
await Contribution.save(contribution)
await dbContribution.save(contribution)
return new UnconfirmedContribution(contribution, user, creations)
}
@Authorized([RIGHTS.LIST_CONTRIBUTIONS])
@Query(() => [Contribution])
async listContributions(
@Args()
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
@Arg('filterConfirmed', () => Boolean)
filterConfirmed: boolean | null,
@Ctx() context: Context,
): Promise<Contribution[]> {
const user = getUser(context)
const where: {
userId: number
confirmedBy?: FindOperator<number> | null
} = { userId: user.id }
if (filterConfirmed) where.confirmedBy = IsNull()
const contributions = await dbContribution.find({
where,
order: {
createdAt: order,
},
skip: (currentPage - 1) * pageSize,
take: pageSize,
})
return contributions.map((contribution) => new Contribution(contribution, new User(user)))
}
}

View File

@ -234,6 +234,7 @@ export const deleteContributionLink = gql`
export const createContribution = gql`
mutation ($amount: Decimal!, $memo: String!, $creationDate: String!) {
createContribution(amount: $amount, memo: $memo, creationDate: $creationDate) {
id
amount
memo
}

View File

@ -172,6 +172,25 @@ export const queryTransactionLink = gql`
}
`
export const listContributions = gql`
query (
$currentPage: Int = 1
$pageSize: Int = 5
$order: Order
$filterConfirmed: Boolean = false
) {
listContributions(
currentPage: $currentPage
pageSize: $pageSize
order: $order
filterConfirmed: $filterConfirmed
) {
id
amount
memo
}
}
`
// from admin interface
export const listUnconfirmedContributions = gql`