mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' of github.com:gradido/gradido into 2049-remove-image-entries-in-docker-compose-files
This commit is contained in:
commit
388b9afdbb
@ -27,6 +27,8 @@ export enum RIGHTS {
|
|||||||
GDT_BALANCE = 'GDT_BALANCE',
|
GDT_BALANCE = 'GDT_BALANCE',
|
||||||
CREATE_CONTRIBUTION = 'CREATE_CONTRIBUTION',
|
CREATE_CONTRIBUTION = 'CREATE_CONTRIBUTION',
|
||||||
LIST_CONTRIBUTIONS = 'LIST_CONTRIBUTIONS',
|
LIST_CONTRIBUTIONS = 'LIST_CONTRIBUTIONS',
|
||||||
|
LIST_ALL_CONTRIBUTIONS = 'LIST_ALL_CONTRIBUTIONS',
|
||||||
|
UPDATE_CONTRIBUTION = 'UPDATE_CONTRIBUTION',
|
||||||
// Admin
|
// Admin
|
||||||
SEARCH_USERS = 'SEARCH_USERS',
|
SEARCH_USERS = 'SEARCH_USERS',
|
||||||
SET_USER_ROLE = 'SET_USER_ROLE',
|
SET_USER_ROLE = 'SET_USER_ROLE',
|
||||||
|
|||||||
@ -25,6 +25,8 @@ export const ROLE_USER = new Role('user', [
|
|||||||
RIGHTS.GDT_BALANCE,
|
RIGHTS.GDT_BALANCE,
|
||||||
RIGHTS.CREATE_CONTRIBUTION,
|
RIGHTS.CREATE_CONTRIBUTION,
|
||||||
RIGHTS.LIST_CONTRIBUTIONS,
|
RIGHTS.LIST_CONTRIBUTIONS,
|
||||||
|
RIGHTS.LIST_ALL_CONTRIBUTIONS,
|
||||||
|
RIGHTS.UPDATE_CONTRIBUTION,
|
||||||
])
|
])
|
||||||
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights
|
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights
|
||||||
|
|
||||||
|
|||||||
@ -7,18 +7,24 @@ import { User } from './User'
|
|||||||
export class Contribution {
|
export class Contribution {
|
||||||
constructor(contribution: dbContribution, user: User) {
|
constructor(contribution: dbContribution, user: User) {
|
||||||
this.id = contribution.id
|
this.id = contribution.id
|
||||||
this.user = user
|
this.firstName = user ? user.firstName : null
|
||||||
|
this.lastName = user ? user.lastName : null
|
||||||
this.amount = contribution.amount
|
this.amount = contribution.amount
|
||||||
this.memo = contribution.memo
|
this.memo = contribution.memo
|
||||||
this.createdAt = contribution.createdAt
|
this.createdAt = contribution.createdAt
|
||||||
this.deletedAt = contribution.deletedAt
|
this.deletedAt = contribution.deletedAt
|
||||||
|
this.confirmedAt = contribution.confirmedAt
|
||||||
|
this.confirmedBy = contribution.confirmedBy
|
||||||
}
|
}
|
||||||
|
|
||||||
@Field(() => Number)
|
@Field(() => Number)
|
||||||
id: number
|
id: number
|
||||||
|
|
||||||
@Field(() => User)
|
@Field(() => String, { nullable: true })
|
||||||
user: User
|
firstName: string | null
|
||||||
|
|
||||||
|
@Field(() => String, { nullable: true })
|
||||||
|
lastName: string | null
|
||||||
|
|
||||||
@Field(() => Decimal)
|
@Field(() => Decimal)
|
||||||
amount: Decimal
|
amount: Decimal
|
||||||
@ -31,10 +37,21 @@ export class Contribution {
|
|||||||
|
|
||||||
@Field(() => Date, { nullable: true })
|
@Field(() => Date, { nullable: true })
|
||||||
deletedAt: Date | null
|
deletedAt: Date | null
|
||||||
|
|
||||||
|
@Field(() => Date, { nullable: true })
|
||||||
|
confirmedAt: Date | null
|
||||||
|
|
||||||
|
@Field(() => Number, { nullable: true })
|
||||||
|
confirmedBy: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class ContributionListResult {
|
export class ContributionListResult {
|
||||||
|
constructor(count: number, list: Contribution[]) {
|
||||||
|
this.linkCount = count
|
||||||
|
this.linkList = list
|
||||||
|
}
|
||||||
|
|
||||||
@Field(() => Int)
|
@Field(() => Int)
|
||||||
linkCount: number
|
linkCount: number
|
||||||
|
|
||||||
|
|||||||
@ -47,11 +47,11 @@ import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
|
|||||||
import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver'
|
import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver'
|
||||||
import CONFIG from '@/config'
|
import CONFIG from '@/config'
|
||||||
import {
|
import {
|
||||||
getCreationIndex,
|
|
||||||
getUserCreation,
|
getUserCreation,
|
||||||
getUserCreations,
|
getUserCreations,
|
||||||
validateContribution,
|
validateContribution,
|
||||||
isStartEndDateValid,
|
isStartEndDateValid,
|
||||||
|
updateCreations,
|
||||||
} from './util/creations'
|
} from './util/creations'
|
||||||
import {
|
import {
|
||||||
CONTRIBUTIONLINK_MEMO_MAX_CHARS,
|
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')
|
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)
|
const creationDateObj = new Date(creationDate)
|
||||||
let creations = await getUserCreation(user.id)
|
let creations = await getUserCreation(user.id)
|
||||||
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
||||||
@ -688,13 +692,3 @@ export class AdminResolver {
|
|||||||
return new ContributionLink(dbContributionLink)
|
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,12 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||||
import { createContribution } from '@/seeds/graphql/mutations'
|
import {
|
||||||
import { listContributions, login } from '@/seeds/graphql/queries'
|
adminUpdateContribution,
|
||||||
|
createContribution,
|
||||||
|
updateContribution,
|
||||||
|
} from '@/seeds/graphql/mutations'
|
||||||
|
import { listAllContributions, listContributions, login } from '@/seeds/graphql/queries'
|
||||||
import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
|
import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
|
||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
import { userFactory } from '@/seeds/factory/user'
|
import { userFactory } from '@/seeds/factory/user'
|
||||||
@ -13,6 +17,7 @@ import { peterLustig } from '@/seeds/users/peter-lustig'
|
|||||||
|
|
||||||
let mutate: any, query: any, con: any
|
let mutate: any, query: any, con: any
|
||||||
let testEnv: any
|
let testEnv: any
|
||||||
|
let result: any
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
testEnv = await testEnvironment()
|
testEnv = await testEnvironment()
|
||||||
@ -168,6 +173,11 @@ describe('ContributionResolver', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
resetToken()
|
||||||
|
})
|
||||||
|
|
||||||
describe('filter confirmed is false', () => {
|
describe('filter confirmed is false', () => {
|
||||||
it('returns creations', async () => {
|
it('returns creations', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
@ -230,4 +240,280 @@ describe('ContributionResolver', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('401 Unauthorized')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('authenticated', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await userFactory(testEnv, peterLustig)
|
||||||
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
|
await query({
|
||||||
|
query: login,
|
||||||
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
result = await mutate({
|
||||||
|
mutation: createContribution,
|
||||||
|
variables: {
|
||||||
|
amount: 100.0,
|
||||||
|
memo: 'Test env contribution',
|
||||||
|
creationDate: new Date().toString(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
resetToken()
|
||||||
|
})
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('listAllContribution', () => {
|
||||||
|
describe('unauthenticated', () => {
|
||||||
|
it('returns an error', async () => {
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: listAllContributions,
|
||||||
|
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(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns allCreation', async () => {
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: listAllContributions,
|
||||||
|
variables: {
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 25,
|
||||||
|
order: 'DESC',
|
||||||
|
filterConfirmed: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
listAllContributions: {
|
||||||
|
linkCount: 2,
|
||||||
|
linkList: 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',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2,15 +2,15 @@ import { RIGHTS } from '@/auth/RIGHTS'
|
|||||||
import { Context, getUser } from '@/server/context'
|
import { Context, getUser } from '@/server/context'
|
||||||
import { backendLogger as logger } from '@/server/logger'
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
import { Contribution as dbContribution } from '@entity/Contribution'
|
import { Contribution as dbContribution } from '@entity/Contribution'
|
||||||
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 } from '@dbTools/typeorm'
|
import { FindOperator, IsNull } from '@dbTools/typeorm'
|
||||||
import ContributionArgs from '@arg/ContributionArgs'
|
import ContributionArgs from '@arg/ContributionArgs'
|
||||||
import Paginated from '@arg/Paginated'
|
import Paginated from '@arg/Paginated'
|
||||||
import { Order } from '@enum/Order'
|
import { Order } from '@enum/Order'
|
||||||
import { Contribution } from '@model/Contribution'
|
import { Contribution, ContributionListResult } from '@model/Contribution'
|
||||||
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
|
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
|
||||||
import { User } from '@model/User'
|
import { User } from '@model/User'
|
||||||
import { validateContribution, getUserCreation } from './util/creations'
|
import { validateContribution, getUserCreation, updateCreations } from './util/creations'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class ContributionResolver {
|
export class ContributionResolver {
|
||||||
@ -58,9 +58,68 @@ export class ContributionResolver {
|
|||||||
order: {
|
order: {
|
||||||
createdAt: order,
|
createdAt: order,
|
||||||
},
|
},
|
||||||
|
withDeleted: true,
|
||||||
skip: (currentPage - 1) * pageSize,
|
skip: (currentPage - 1) * pageSize,
|
||||||
take: pageSize,
|
take: pageSize,
|
||||||
})
|
})
|
||||||
return contributions.map((contribution) => new Contribution(contribution, new User(user)))
|
return contributions.map((contribution) => new Contribution(contribution, new User(user)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Authorized([RIGHTS.LIST_ALL_CONTRIBUTIONS])
|
||||||
|
@Query(() => ContributionListResult)
|
||||||
|
async listAllContributions(
|
||||||
|
@Args()
|
||||||
|
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||||
|
): Promise<ContributionListResult> {
|
||||||
|
const [dbContributions, count] = await dbContribution.findAndCount({
|
||||||
|
relations: ['user'],
|
||||||
|
order: {
|
||||||
|
createdAt: order,
|
||||||
|
},
|
||||||
|
skip: (currentPage - 1) * pageSize,
|
||||||
|
take: pageSize,
|
||||||
|
})
|
||||||
|
return new ContributionListResult(
|
||||||
|
count,
|
||||||
|
dbContributions.map(
|
||||||
|
(contribution) => new Contribution(contribution, new User(contribution.user)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
|
||||||
import { backendLogger as logger } from '@/server/logger'
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
import { getConnection } from '@dbTools/typeorm'
|
import { getConnection } from '@dbTools/typeorm'
|
||||||
|
import { Contribution } from '@entity/Contribution'
|
||||||
import Decimal from 'decimal.js-light'
|
import Decimal from 'decimal.js-light'
|
||||||
import { FULL_CREATION_AVAILABLE, MAX_CREATION_AMOUNT } from '../const/const'
|
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!`)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|||||||
@ -191,6 +191,24 @@ export const listContributions = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const listAllContributions = `
|
||||||
|
query ($currentPage: Int = 1, $pageSize: Int = 5, $order: Order = DESC) {
|
||||||
|
listAllContributions(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
|
||||||
|
linkCount
|
||||||
|
linkList {
|
||||||
|
id
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
amount
|
||||||
|
memo
|
||||||
|
createdAt
|
||||||
|
confirmedAt
|
||||||
|
confirmedBy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
// from admin interface
|
// from admin interface
|
||||||
|
|
||||||
export const listUnconfirmedContributions = gql`
|
export const listUnconfirmedContributions = gql`
|
||||||
|
|||||||
@ -31,11 +31,14 @@ const filterVariables = (variables: any) => {
|
|||||||
const logPlugin = {
|
const logPlugin = {
|
||||||
requestDidStart(requestContext: any) {
|
requestDidStart(requestContext: any) {
|
||||||
const { logger } = requestContext
|
const { logger } = requestContext
|
||||||
const { query, mutation, variables } = requestContext.request
|
const { query, mutation, variables, operationName } = requestContext.request
|
||||||
|
if (operationName !== 'IntrospectionQuery') {
|
||||||
logger.info(`Request:
|
logger.info(`Request:
|
||||||
${mutation || query}variables: ${JSON.stringify(filterVariables(variables), null, 2)}`)
|
${mutation || query}variables: ${JSON.stringify(filterVariables(variables), null, 2)}`)
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
willSendResponse(requestContext: any) {
|
willSendResponse(requestContext: any) {
|
||||||
|
if (operationName !== 'IntrospectionQuery') {
|
||||||
if (requestContext.context.user) logger.info(`User ID: ${requestContext.context.user.id}`)
|
if (requestContext.context.user) logger.info(`User ID: ${requestContext.context.user.id}`)
|
||||||
if (requestContext.response.data) {
|
if (requestContext.response.data) {
|
||||||
logger.info('Response Success!')
|
logger.info('Response Success!')
|
||||||
@ -45,6 +48,7 @@ ${JSON.stringify(requestContext.response.data, null, 2)}`)
|
|||||||
if (requestContext.response.errors)
|
if (requestContext.response.errors)
|
||||||
logger.error(`Response-Errors:
|
logger.error(`Response-Errors:
|
||||||
${JSON.stringify(requestContext.response.errors, null, 2)}`)
|
${JSON.stringify(requestContext.response.errors, null, 2)}`)
|
||||||
|
}
|
||||||
return requestContext
|
return requestContext
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,15 @@
|
|||||||
import Decimal from 'decimal.js-light'
|
import Decimal from 'decimal.js-light'
|
||||||
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn, DeleteDateColumn } from 'typeorm'
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
DeleteDateColumn,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
} from 'typeorm'
|
||||||
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||||
|
import { User } from '../User'
|
||||||
|
|
||||||
@Entity('contributions')
|
@Entity('contributions')
|
||||||
export class Contribution extends BaseEntity {
|
export class Contribution extends BaseEntity {
|
||||||
@ -10,6 +19,10 @@ export class Contribution extends BaseEntity {
|
|||||||
@Column({ unsigned: true, nullable: false, name: 'user_id' })
|
@Column({ unsigned: true, nullable: false, name: 'user_id' })
|
||||||
userId: number
|
userId: number
|
||||||
|
|
||||||
|
@ManyToOne(() => User, (user) => user.contributions)
|
||||||
|
@JoinColumn({ name: 'user_id' })
|
||||||
|
user: User
|
||||||
|
|
||||||
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP', name: 'created_at' })
|
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP', name: 'created_at' })
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,13 @@
|
|||||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, DeleteDateColumn } from 'typeorm'
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
DeleteDateColumn,
|
||||||
|
OneToMany,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm'
|
||||||
|
import { Contribution } from '../Contribution'
|
||||||
|
|
||||||
@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
||||||
export class User extends BaseEntity {
|
export class User extends BaseEntity {
|
||||||
@ -76,4 +85,8 @@ export class User extends BaseEntity {
|
|||||||
default: null,
|
default: null,
|
||||||
})
|
})
|
||||||
passphrase: string
|
passphrase: string
|
||||||
|
|
||||||
|
@OneToMany(() => Contribution, (contribution) => contribution.user)
|
||||||
|
@JoinColumn({ name: 'user_id' })
|
||||||
|
contributions?: Contribution[]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,12 +62,16 @@ describe('SessionLogoutTimeout', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('token is expired', () => {
|
describe('token is expired for several seconds', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mocks.$store.state.tokenTime = setTokenTime(-60)
|
mocks.$store.state.tokenTime = setTokenTime(-60)
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('has value for remaining seconds equal 0', () => {
|
||||||
|
expect(wrapper.tokenExpiresInSeconds === 0)
|
||||||
|
})
|
||||||
|
|
||||||
it('emits logout', () => {
|
it('emits logout', () => {
|
||||||
expect(wrapper.emitted('logout')).toBeTruthy()
|
expect(wrapper.emitted('logout')).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -65,7 +65,7 @@ export default {
|
|||||||
this.$timer.restart('tokenExpires')
|
this.$timer.restart('tokenExpires')
|
||||||
this.$bvModal.show('modalSessionTimeOut')
|
this.$bvModal.show('modalSessionTimeOut')
|
||||||
}
|
}
|
||||||
if (this.tokenExpiresInSeconds <= 0) {
|
if (this.tokenExpiresInSeconds === 0) {
|
||||||
this.$timer.stop('tokenExpires')
|
this.$timer.stop('tokenExpires')
|
||||||
this.$emit('logout')
|
this.$emit('logout')
|
||||||
}
|
}
|
||||||
@ -90,7 +90,10 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
tokenExpiresInSeconds() {
|
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() {
|
beforeDestroy() {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export const login = gql`
|
|||||||
hasElopage
|
hasElopage
|
||||||
publisherId
|
publisherId
|
||||||
isAdmin
|
isAdmin
|
||||||
|
creation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -30,6 +31,7 @@ export const verifyLogin = gql`
|
|||||||
hasElopage
|
hasElopage
|
||||||
publisherId
|
publisherId
|
||||||
isAdmin
|
isAdmin
|
||||||
|
creation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@ -47,6 +47,9 @@ export const mutations = {
|
|||||||
hasElopage: (state, hasElopage) => {
|
hasElopage: (state, hasElopage) => {
|
||||||
state.hasElopage = hasElopage
|
state.hasElopage = hasElopage
|
||||||
},
|
},
|
||||||
|
creation: (state, creation) => {
|
||||||
|
state.creation = creation
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
@ -60,6 +63,7 @@ export const actions = {
|
|||||||
commit('hasElopage', data.hasElopage)
|
commit('hasElopage', data.hasElopage)
|
||||||
commit('publisherId', data.publisherId)
|
commit('publisherId', data.publisherId)
|
||||||
commit('isAdmin', data.isAdmin)
|
commit('isAdmin', data.isAdmin)
|
||||||
|
commit('creation', data.creation)
|
||||||
},
|
},
|
||||||
logout: ({ commit, state }) => {
|
logout: ({ commit, state }) => {
|
||||||
commit('token', null)
|
commit('token', null)
|
||||||
@ -71,6 +75,7 @@ export const actions = {
|
|||||||
commit('hasElopage', false)
|
commit('hasElopage', false)
|
||||||
commit('publisherId', null)
|
commit('publisherId', null)
|
||||||
commit('isAdmin', false)
|
commit('isAdmin', false)
|
||||||
|
commit('creation', null)
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -96,6 +101,7 @@ try {
|
|||||||
newsletterState: null,
|
newsletterState: null,
|
||||||
hasElopage: false,
|
hasElopage: false,
|
||||||
publisherId: null,
|
publisherId: null,
|
||||||
|
creation: null,
|
||||||
},
|
},
|
||||||
getters: {},
|
getters: {},
|
||||||
// Syncronous mutation of the state
|
// Syncronous mutation of the state
|
||||||
|
|||||||
@ -30,6 +30,7 @@ const {
|
|||||||
publisherId,
|
publisherId,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
hasElopage,
|
hasElopage,
|
||||||
|
creation,
|
||||||
} = mutations
|
} = mutations
|
||||||
const { login, logout } = actions
|
const { login, logout } = actions
|
||||||
|
|
||||||
@ -139,6 +140,14 @@ describe('Vuex store', () => {
|
|||||||
expect(state.hasElopage).toBeTruthy()
|
expect(state.hasElopage).toBeTruthy()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('creation', () => {
|
||||||
|
it('sets the state of creation', () => {
|
||||||
|
const state = { creation: null }
|
||||||
|
creation(state, true)
|
||||||
|
expect(state.creation).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('actions', () => {
|
describe('actions', () => {
|
||||||
@ -156,11 +165,12 @@ describe('Vuex store', () => {
|
|||||||
hasElopage: false,
|
hasElopage: false,
|
||||||
publisherId: 1234,
|
publisherId: 1234,
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
|
creation: ['1000', '1000', '1000'],
|
||||||
}
|
}
|
||||||
|
|
||||||
it('calls nine commits', () => {
|
it('calls nine commits', () => {
|
||||||
login({ commit, state }, commitedData)
|
login({ commit, state }, commitedData)
|
||||||
expect(commit).toHaveBeenCalledTimes(8)
|
expect(commit).toHaveBeenCalledTimes(9)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('commits email', () => {
|
it('commits email', () => {
|
||||||
@ -202,6 +212,11 @@ describe('Vuex store', () => {
|
|||||||
login({ commit, state }, commitedData)
|
login({ commit, state }, commitedData)
|
||||||
expect(commit).toHaveBeenNthCalledWith(8, 'isAdmin', true)
|
expect(commit).toHaveBeenNthCalledWith(8, 'isAdmin', true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('commits creation', () => {
|
||||||
|
login({ commit, state }, commitedData)
|
||||||
|
expect(commit).toHaveBeenNthCalledWith(9, 'creation', ['1000', '1000', '1000'])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('logout', () => {
|
describe('logout', () => {
|
||||||
@ -210,7 +225,7 @@ describe('Vuex store', () => {
|
|||||||
|
|
||||||
it('calls nine commits', () => {
|
it('calls nine commits', () => {
|
||||||
logout({ commit, state })
|
logout({ commit, state })
|
||||||
expect(commit).toHaveBeenCalledTimes(8)
|
expect(commit).toHaveBeenCalledTimes(9)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('commits token', () => {
|
it('commits token', () => {
|
||||||
@ -253,6 +268,11 @@ describe('Vuex store', () => {
|
|||||||
expect(commit).toHaveBeenNthCalledWith(8, 'isAdmin', false)
|
expect(commit).toHaveBeenNthCalledWith(8, 'isAdmin', false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('commits creation', () => {
|
||||||
|
logout({ commit, state })
|
||||||
|
expect(commit).toHaveBeenNthCalledWith(9, 'creation', null)
|
||||||
|
})
|
||||||
|
|
||||||
// how to get this working?
|
// how to get this working?
|
||||||
it.skip('calls localStorage.clear()', () => {
|
it.skip('calls localStorage.clear()', () => {
|
||||||
const clearStorageMock = jest.fn()
|
const clearStorageMock = jest.fn()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user