mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge master into this branch.
This commit is contained in:
commit
22602c45b9
@ -28,6 +28,7 @@ export enum RIGHTS {
|
||||
CREATE_CONTRIBUTION = 'CREATE_CONTRIBUTION',
|
||||
LIST_CONTRIBUTIONS = 'LIST_CONTRIBUTIONS',
|
||||
LIST_ALL_CONTRIBUTIONS = 'LIST_ALL_CONTRIBUTIONS',
|
||||
UPDATE_CONTRIBUTION = 'UPDATE_CONTRIBUTION',
|
||||
// Admin
|
||||
SEARCH_USERS = 'SEARCH_USERS',
|
||||
SET_USER_ROLE = 'SET_USER_ROLE',
|
||||
|
||||
@ -26,6 +26,7 @@ export const ROLE_USER = new Role('user', [
|
||||
RIGHTS.CREATE_CONTRIBUTION,
|
||||
RIGHTS.LIST_CONTRIBUTIONS,
|
||||
RIGHTS.LIST_ALL_CONTRIBUTIONS,
|
||||
RIGHTS.UPDATE_CONTRIBUTION,
|
||||
])
|
||||
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights
|
||||
|
||||
|
||||
@ -47,11 +47,11 @@ import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
|
||||
import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver'
|
||||
import CONFIG from '@/config'
|
||||
import {
|
||||
getCreationIndex,
|
||||
getUserCreation,
|
||||
getUserCreations,
|
||||
validateContribution,
|
||||
isStartEndDateValid,
|
||||
updateCreations,
|
||||
} from './util/creations'
|
||||
import {
|
||||
CONTRIBUTIONLINK_MEMO_MAX_CHARS,
|
||||
@ -321,6 +321,10 @@ export class AdminResolver {
|
||||
throw new Error('user of the pending contribution and send user does not correspond')
|
||||
}
|
||||
|
||||
if (contributionToUpdate.moderatorId === null) {
|
||||
throw new Error('An admin is not allowed to update a user contribution.')
|
||||
}
|
||||
|
||||
const creationDateObj = new Date(creationDate)
|
||||
let creations = await getUserCreation(user.id)
|
||||
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
||||
@ -688,13 +692,3 @@ export class AdminResolver {
|
||||
return new ContributionLink(dbContributionLink)
|
||||
}
|
||||
}
|
||||
|
||||
function updateCreations(creations: Decimal[], contribution: Contribution): Decimal[] {
|
||||
const index = getCreationIndex(contribution.contributionDate.getMonth())
|
||||
|
||||
if (index < 0) {
|
||||
throw new Error('You cannot create GDD for a month older than the last three months.')
|
||||
}
|
||||
creations[index] = creations[index].plus(contribution.amount.toString())
|
||||
return creations
|
||||
}
|
||||
|
||||
@ -2,8 +2,17 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||
<<<<<<< HEAD
|
||||
import { createContribution } from '@/seeds/graphql/mutations'
|
||||
import { listAllContributions, listContributions, login } from '@/seeds/graphql/queries'
|
||||
=======
|
||||
import {
|
||||
adminUpdateContribution,
|
||||
createContribution,
|
||||
updateContribution,
|
||||
} from '@/seeds/graphql/mutations'
|
||||
import { listContributions, login } from '@/seeds/graphql/queries'
|
||||
>>>>>>> master
|
||||
import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
|
||||
import { GraphQLError } from 'graphql'
|
||||
import { userFactory } from '@/seeds/factory/user'
|
||||
@ -13,6 +22,7 @@ import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||
|
||||
let mutate: any, query: any, con: any
|
||||
let testEnv: any
|
||||
let result: any
|
||||
|
||||
beforeAll(async () => {
|
||||
testEnv = await testEnvironment()
|
||||
@ -236,6 +246,7 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
<<<<<<< HEAD
|
||||
describe('listAllContribution', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('returns an error', async () => {
|
||||
@ -247,6 +258,19 @@ describe('ContributionResolver', () => {
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filterConfirmed: false,
|
||||
=======
|
||||
describe('updateContribution', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateContribution,
|
||||
variables: {
|
||||
contributionId: 1,
|
||||
amount: 100.0,
|
||||
memo: 'Test Contribution',
|
||||
creationDate: 'not-valid',
|
||||
>>>>>>> master
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
@ -259,16 +283,25 @@ describe('ContributionResolver', () => {
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeAll(async () => {
|
||||
<<<<<<< HEAD
|
||||
await userFactory(testEnv, bibiBloxberg)
|
||||
await userFactory(testEnv, peterLustig)
|
||||
creations.forEach(async (creation) => await creationFactory(testEnv, creation!))
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
// await creationFactory(testEnv, creations!)
|
||||
=======
|
||||
await userFactory(testEnv, peterLustig)
|
||||
await userFactory(testEnv, bibiBloxberg)
|
||||
>>>>>>> master
|
||||
await query({
|
||||
query: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
})
|
||||
<<<<<<< HEAD
|
||||
await mutate({
|
||||
=======
|
||||
result = await mutate({
|
||||
>>>>>>> master
|
||||
mutation: createContribution,
|
||||
variables: {
|
||||
amount: 100.0,
|
||||
@ -283,6 +316,7 @@ describe('ContributionResolver', () => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
<<<<<<< HEAD
|
||||
it('returns allCreation', async () => {
|
||||
await expect(
|
||||
query({
|
||||
@ -312,6 +346,158 @@ describe('ContributionResolver', () => {
|
||||
},
|
||||
}),
|
||||
)
|
||||
=======
|
||||
describe('wrong contribution id', () => {
|
||||
it('throws an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateContribution,
|
||||
variables: {
|
||||
contributionId: -1,
|
||||
amount: 100.0,
|
||||
memo: 'Test env contribution',
|
||||
creationDate: new Date().toString(),
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('No contribution found to given id.')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('wrong user tries to update the contribution', () => {
|
||||
beforeAll(async () => {
|
||||
await query({
|
||||
query: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateContribution,
|
||||
variables: {
|
||||
contributionId: result.data.createContribution.id,
|
||||
amount: 10.0,
|
||||
memo: 'Test env contribution',
|
||||
creationDate: new Date().toString(),
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'user of the pending contribution and send user does not correspond',
|
||||
),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('admin tries to update a user contribution', () => {
|
||||
it('throws an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminUpdateContribution,
|
||||
variables: {
|
||||
id: result.data.createContribution.id,
|
||||
email: 'bibi@bloxberg.de',
|
||||
amount: 10.0,
|
||||
memo: 'Test env contribution',
|
||||
creationDate: new Date().toString(),
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('An admin is not allowed to update a user contribution.')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('update too much so that the limit is exceeded', () => {
|
||||
beforeAll(async () => {
|
||||
await query({
|
||||
query: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateContribution,
|
||||
variables: {
|
||||
contributionId: result.data.createContribution.id,
|
||||
amount: 1019.0,
|
||||
memo: 'Test env contribution',
|
||||
creationDate: new Date().toString(),
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'The amount (1019 GDD) to be created exceeds the amount (1000 GDD) still available for this month.',
|
||||
),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('update creation to a date that is older than 3 months', () => {
|
||||
it('throws an error', async () => {
|
||||
const date = new Date()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateContribution,
|
||||
variables: {
|
||||
contributionId: result.data.createContribution.id,
|
||||
amount: 10.0,
|
||||
memo: 'Test env contribution',
|
||||
creationDate: date.setMonth(date.getMonth() - 3).toString(),
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError('No information for available creations for the given date'),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid input', () => {
|
||||
it('updates contribution', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: updateContribution,
|
||||
variables: {
|
||||
contributionId: result.data.createContribution.id,
|
||||
amount: 10.0,
|
||||
memo: 'Test contribution',
|
||||
creationDate: new Date().toString(),
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
updateContribution: {
|
||||
id: result.data.createContribution.id,
|
||||
amount: '10',
|
||||
memo: 'Test contribution',
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
>>>>>>> master
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -3,7 +3,7 @@ import { Context, getUser } from '@/server/context'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { Contribution as dbContribution } from '@entity/Contribution'
|
||||
import { User as dbUser } from '@entity/User'
|
||||
import { Arg, Args, Authorized, Ctx, Mutation, Query, Resolver } from 'type-graphql'
|
||||
import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'
|
||||
import { FindOperator, IsNull, Not } from '@dbTools/typeorm'
|
||||
import ContributionArgs from '@arg/ContributionArgs'
|
||||
import Paginated from '@arg/Paginated'
|
||||
@ -11,7 +11,7 @@ import { Order } from '@enum/Order'
|
||||
import { Contribution, ContributionListResult } from '@model/Contribution'
|
||||
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
|
||||
import { User } from '@model/User'
|
||||
import { validateContribution, getUserCreation } from './util/creations'
|
||||
import { validateContribution, getUserCreation, updateCreations } from './util/creations'
|
||||
|
||||
@Resolver()
|
||||
export class ContributionResolver {
|
||||
@ -97,4 +97,40 @@ export class ContributionResolver {
|
||||
})
|
||||
return new ContributionListResult(contributions.length, contributions)
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.UPDATE_CONTRIBUTION])
|
||||
@Mutation(() => UnconfirmedContribution)
|
||||
async updateContribution(
|
||||
@Arg('contributionId', () => Int)
|
||||
contributionId: number,
|
||||
@Args() { amount, memo, creationDate }: ContributionArgs,
|
||||
@Ctx() context: Context,
|
||||
): Promise<UnconfirmedContribution> {
|
||||
const user = getUser(context)
|
||||
|
||||
const contributionToUpdate = await dbContribution.findOne({
|
||||
where: { id: contributionId, confirmedAt: IsNull() },
|
||||
})
|
||||
if (!contributionToUpdate) {
|
||||
throw new Error('No contribution found to given id.')
|
||||
}
|
||||
if (contributionToUpdate.userId !== user.id) {
|
||||
throw new Error('user of the pending contribution and send user does not correspond')
|
||||
}
|
||||
|
||||
const creationDateObj = new Date(creationDate)
|
||||
let creations = await getUserCreation(user.id)
|
||||
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
||||
creations = updateCreations(creations, contributionToUpdate)
|
||||
}
|
||||
|
||||
// all possible cases not to be true are thrown in this function
|
||||
validateContribution(creations, amount, creationDateObj)
|
||||
contributionToUpdate.amount = amount
|
||||
contributionToUpdate.memo = memo
|
||||
contributionToUpdate.contributionDate = new Date(creationDate)
|
||||
dbContribution.save(contributionToUpdate)
|
||||
|
||||
return new UnconfirmedContribution(contributionToUpdate, user, creations)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
import { Contribution } from '@entity/Contribution'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { FULL_CREATION_AVAILABLE, MAX_CREATION_AMOUNT } from '../const/const'
|
||||
|
||||
@ -117,3 +118,13 @@ export const isStartEndDateValid = (
|
||||
throw new Error(`The value of validFrom must before or equals the validTo!`)
|
||||
}
|
||||
}
|
||||
|
||||
export const updateCreations = (creations: Decimal[], contribution: Contribution): Decimal[] => {
|
||||
const index = getCreationIndex(contribution.contributionDate.getMonth())
|
||||
|
||||
if (index < 0) {
|
||||
throw new Error('You cannot create GDD for a month older than the last three months.')
|
||||
}
|
||||
creations[index] = creations[index].plus(contribution.amount.toString())
|
||||
return creations
|
||||
}
|
||||
|
||||
@ -240,3 +240,18 @@ export const createContribution = gql`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const updateContribution = gql`
|
||||
mutation ($contributionId: Int!, $amount: Decimal!, $memo: String!, $creationDate: String!) {
|
||||
updateContribution(
|
||||
contributionId: $contributionId
|
||||
amount: $amount
|
||||
memo: $memo
|
||||
creationDate: $creationDate
|
||||
) {
|
||||
id
|
||||
amount
|
||||
memo
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -62,12 +62,16 @@ describe('SessionLogoutTimeout', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('token is expired', () => {
|
||||
describe('token is expired for several seconds', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$store.state.tokenTime = setTokenTime(-60)
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has value for remaining seconds equal 0', () => {
|
||||
expect(wrapper.tokenExpiresInSeconds === 0)
|
||||
})
|
||||
|
||||
it('emits logout', () => {
|
||||
expect(wrapper.emitted('logout')).toBeTruthy()
|
||||
})
|
||||
|
||||
@ -65,7 +65,7 @@ export default {
|
||||
this.$timer.restart('tokenExpires')
|
||||
this.$bvModal.show('modalSessionTimeOut')
|
||||
}
|
||||
if (this.tokenExpiresInSeconds <= 0) {
|
||||
if (this.tokenExpiresInSeconds === 0) {
|
||||
this.$timer.stop('tokenExpires')
|
||||
this.$emit('logout')
|
||||
}
|
||||
@ -90,7 +90,10 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
tokenExpiresInSeconds() {
|
||||
return Math.floor((new Date(this.$store.state.tokenTime * 1000).getTime() - this.now) / 1000)
|
||||
const remainingSecs = Math.floor(
|
||||
(new Date(this.$store.state.tokenTime * 1000).getTime() - this.now) / 1000,
|
||||
)
|
||||
return remainingSecs <= 0 ? 0 : remainingSecs
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user