mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into event_protocol_rework
This commit is contained in:
commit
9aa5da5c8f
15
CHANGELOG.md
15
CHANGELOG.md
@ -4,8 +4,23 @@ All notable changes to this project will be documented in this file. Dates are d
|
|||||||
|
|
||||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||||
|
|
||||||
|
#### [1.18.2](https://github.com/gradido/gradido/compare/1.18.1...1.18.2)
|
||||||
|
|
||||||
|
- fix(admin): deny contribution button to left [`#2699`](https://github.com/gradido/gradido/pull/2699)
|
||||||
|
|
||||||
|
#### [1.18.1](https://github.com/gradido/gradido/compare/1.18.0...1.18.1)
|
||||||
|
|
||||||
|
> 10 February 2023
|
||||||
|
|
||||||
|
- chore(release): version 1.18.1 [`#2698`](https://github.com/gradido/gradido/pull/2698)
|
||||||
|
- fix(frontend): fix is last month for empty form date [`#2697`](https://github.com/gradido/gradido/pull/2697)
|
||||||
|
- fix(frontend): community link [`#2696`](https://github.com/gradido/gradido/pull/2696)
|
||||||
|
|
||||||
#### [1.18.0](https://github.com/gradido/gradido/compare/1.17.1...1.18.0)
|
#### [1.18.0](https://github.com/gradido/gradido/compare/1.17.1...1.18.0)
|
||||||
|
|
||||||
|
> 9 February 2023
|
||||||
|
|
||||||
|
- feat(release): version 1.18.0 [`#2690`](https://github.com/gradido/gradido/pull/2690)
|
||||||
- refactor(frontend): toast by automatically logged out [`#2681`](https://github.com/gradido/gradido/pull/2681)
|
- refactor(frontend): toast by automatically logged out [`#2681`](https://github.com/gradido/gradido/pull/2681)
|
||||||
- refactor(frontend): change text for gdd_per_link.choose-amount [`#2638`](https://github.com/gradido/gradido/pull/2638)
|
- refactor(frontend): change text for gdd_per_link.choose-amount [`#2638`](https://github.com/gradido/gradido/pull/2638)
|
||||||
- fix(backend): emails for deny and delete contribution [`#2688`](https://github.com/gradido/gradido/pull/2688)
|
- fix(backend): emails for deny and delete contribution [`#2688`](https://github.com/gradido/gradido/pull/2688)
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"description": "Administraion Interface for Gradido",
|
"description": "Administraion Interface for Gradido",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "Moriz Wahl",
|
"author": "Moriz Wahl",
|
||||||
"version": "1.18.0",
|
"version": "1.18.2",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -259,7 +259,7 @@ describe('CreationConfirm', () => {
|
|||||||
|
|
||||||
describe('deny creation', () => {
|
describe('deny creation', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await wrapper.findAll('tr').at(1).findAll('button').at(2).trigger('click')
|
await wrapper.findAll('tr').at(1).findAll('button').at(1).trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('opens the overlay', () => {
|
it('opens the overlay', () => {
|
||||||
|
|||||||
@ -129,6 +129,7 @@ export default {
|
|||||||
fields() {
|
fields() {
|
||||||
return [
|
return [
|
||||||
{ key: 'bookmark', label: this.$t('delete') },
|
{ key: 'bookmark', label: this.$t('delete') },
|
||||||
|
{ key: 'deny', label: this.$t('deny') },
|
||||||
{ key: 'email', label: this.$t('e_mail') },
|
{ key: 'email', label: this.$t('e_mail') },
|
||||||
{ key: 'firstName', label: this.$t('firstname') },
|
{ key: 'firstName', label: this.$t('firstname') },
|
||||||
{ key: 'lastName', label: this.$t('lastname') },
|
{ key: 'lastName', label: this.$t('lastname') },
|
||||||
@ -149,7 +150,6 @@ export default {
|
|||||||
},
|
},
|
||||||
{ key: 'moderator', label: this.$t('moderator') },
|
{ key: 'moderator', label: this.$t('moderator') },
|
||||||
{ key: 'editCreation', label: this.$t('edit') },
|
{ key: 'editCreation', label: this.$t('edit') },
|
||||||
{ key: 'deny', label: this.$t('deny') },
|
|
||||||
{ key: 'confirm', label: this.$t('save') },
|
{ key: 'confirm', label: this.$t('save') },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-backend",
|
"name": "gradido-backend",
|
||||||
"version": "1.18.0",
|
"version": "1.18.2",
|
||||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/backend",
|
"repository": "https://github.com/gradido/gradido/backend",
|
||||||
|
|||||||
@ -42,6 +42,7 @@ import { User } from '@entity/User'
|
|||||||
import { EventProtocolType } from '@/event/EventProtocolType'
|
import { EventProtocolType } from '@/event/EventProtocolType'
|
||||||
import { logger, i18n as localization } from '@test/testSetup'
|
import { logger, i18n as localization } from '@test/testSetup'
|
||||||
import { UserInputError } from 'apollo-server-express'
|
import { UserInputError } from 'apollo-server-express'
|
||||||
|
import { ContributionStatus } from '../enum/ContributionStatus'
|
||||||
|
|
||||||
// mock account activation email to avoid console spam
|
// mock account activation email to avoid console spam
|
||||||
jest.mock('@/emails/sendEmailVariants', () => {
|
jest.mock('@/emails/sendEmailVariants', () => {
|
||||||
@ -127,13 +128,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('memo text is too short (5 characters minimum)')],
|
errors: [new GraphQLError('Memo text is too short')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith(`memo text is too short: memo.length=4 < 5`)
|
expect(logger.error).toBeCalledWith('Memo text is too short', 4)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws error when memo length greater than 255 chars', async () => {
|
it('throws error when memo length greater than 255 chars', async () => {
|
||||||
@ -150,13 +151,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('memo text is too long (255 characters maximum)')],
|
errors: [new GraphQLError('Memo text is too long')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith(`memo text is too long: memo.length=259 > 255`)
|
expect(logger.error).toBeCalledWith('Memo text is too long', 259)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws error when creationDate not-valid', async () => {
|
it('throws error when creationDate not-valid', async () => {
|
||||||
@ -417,31 +418,6 @@ describe('ContributionResolver', () => {
|
|||||||
resetToken()
|
resetToken()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('wrong contribution id', () => {
|
|
||||||
it('throws an error', async () => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
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.')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error found', () => {
|
|
||||||
expect(logger.error).toBeCalledWith('No contribution found to given id')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Memo length smaller than 5 chars', () => {
|
describe('Memo length smaller than 5 chars', () => {
|
||||||
it('throws error', async () => {
|
it('throws error', async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
@ -458,13 +434,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('memo text is too short (5 characters minimum)')],
|
errors: [new GraphQLError('Memo text is too short')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith('memo text is too short: memo.length=4 < 5')
|
expect(logger.error).toBeCalledWith('Memo text is too short', 4)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -484,13 +460,38 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('memo text is too long (255 characters maximum)')],
|
errors: [new GraphQLError('Memo text is too long')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith('memo text is too long: memo.length=259 > 255')
|
expect(logger.error).toBeCalledWith('Memo text is too long', 259)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('wrong contribution id', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
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('Contribution not found')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error found', () => {
|
||||||
|
expect(logger.error).toBeCalledWith('Contribution not found', -1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -516,18 +517,16 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [new GraphQLError('Can not update contribution of another user')],
|
||||||
new GraphQLError(
|
|
||||||
'user of the pending contribution and send user does not correspond',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith(
|
||||||
'user of the pending contribution and send user does not correspond',
|
'Can not update contribution of another user',
|
||||||
|
expect.any(Object),
|
||||||
|
expect.any(Number),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -548,12 +547,64 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('An admin is not allowed to update a user contribution.')],
|
errors: [new GraphQLError('An admin is not allowed to update an user contribution')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO check that the error is logged (need to modify AdminResolver, avoid conflicts)
|
it('logs the error found', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'An admin is not allowed to update an user contribution',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('contribution has wrong status', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
const contribution = await Contribution.findOneOrFail({
|
||||||
|
id: result.data.createContribution.id,
|
||||||
|
})
|
||||||
|
contribution.contributionStatus = ContributionStatus.DELETED
|
||||||
|
contribution.save()
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const contribution = await Contribution.findOneOrFail({
|
||||||
|
id: result.data.createContribution.id,
|
||||||
|
})
|
||||||
|
contribution.contributionStatus = ContributionStatus.PENDING
|
||||||
|
contribution.save()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
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('Contribution can not be updated due to status')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error found', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'Contribution can not be updated due to status',
|
||||||
|
ContributionStatus.DELETED,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('update too much so that the limit is exceeded', () => {
|
describe('update too much so that the limit is exceeded', () => {
|
||||||
@ -610,16 +661,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('Currently the month of the contribution cannot change.')],
|
errors: [new GraphQLError('Month of contribution can not be changed')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith('Month of contribution can not be changed')
|
||||||
'No information for available creations with the given creationDate=',
|
|
||||||
'Invalid Date',
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1153,6 +1201,7 @@ describe('ContributionResolver', () => {
|
|||||||
|
|
||||||
describe('wrong contribution id', () => {
|
describe('wrong contribution id', () => {
|
||||||
it('returns an error', async () => {
|
it('returns an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
await expect(
|
await expect(
|
||||||
mutate({
|
mutate({
|
||||||
mutation: deleteContribution,
|
mutation: deleteContribution,
|
||||||
@ -1162,18 +1211,19 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('Contribution not found for given id.')],
|
errors: [new GraphQLError('Contribution not found')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith('Contribution not found for given id')
|
expect(logger.error).toBeCalledWith('Contribution not found', -1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('other user sends a deleteContribution', () => {
|
describe('other user sends a deleteContribution', () => {
|
||||||
it('returns an error', async () => {
|
it('returns an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
await mutate({
|
await mutate({
|
||||||
mutation: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
@ -1193,7 +1243,11 @@ describe('ContributionResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith('Can not delete contribution of another user')
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'Can not delete contribution of another user',
|
||||||
|
expect.any(Object),
|
||||||
|
expect.any(Number),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1269,7 +1323,10 @@ describe('ContributionResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith('A confirmed contribution can not be deleted')
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'A confirmed contribution can not be deleted',
|
||||||
|
expect.objectContaining({ contributionStatus: 'CONFIRMED' }),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1535,15 +1592,13 @@ describe('ContributionResolver', () => {
|
|||||||
mutate({ mutation: adminCreateContribution, variables }),
|
mutate({ mutation: adminCreateContribution, variables }),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('Could not find user with email: bibi@bloxberg.de')],
|
errors: [new GraphQLError('Could not find user')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith('Could not find user', 'bibi@bloxberg.de')
|
||||||
'Could not find user with email: bibi@bloxberg.de',
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1563,7 +1618,7 @@ describe('ContributionResolver', () => {
|
|||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [
|
||||||
new GraphQLError('This user was deleted. Cannot create a contribution.'),
|
new GraphQLError('Cannot create contribution since the user was deleted'),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -1571,7 +1626,12 @@ describe('ContributionResolver', () => {
|
|||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith(
|
||||||
'This user was deleted. Cannot create a contribution.',
|
'Cannot create contribution since the user was deleted',
|
||||||
|
expect.objectContaining({
|
||||||
|
user: expect.objectContaining({
|
||||||
|
deletedAt: new Date('2018-03-14T09:17:52.000Z'),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1592,7 +1652,9 @@ describe('ContributionResolver', () => {
|
|||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [
|
||||||
new GraphQLError('Contribution could not be saved, Email is not activated'),
|
new GraphQLError(
|
||||||
|
'Cannot create contribution since the users email is not activated',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -1600,7 +1662,8 @@ describe('ContributionResolver', () => {
|
|||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith(
|
||||||
'Contribution could not be saved, Email is not activated',
|
'Cannot create contribution since the users email is not activated',
|
||||||
|
expect.objectContaining({ emailChecked: false }),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1619,13 +1682,13 @@ describe('ContributionResolver', () => {
|
|||||||
mutate({ mutation: adminCreateContribution, variables }),
|
mutate({ mutation: adminCreateContribution, variables }),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError(`invalid Date for creationDate=invalid-date`)],
|
errors: [new GraphQLError('CreationDate is invalid')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(`invalid Date for creationDate=invalid-date`)
|
expect(logger.error).toBeCalledWith('CreationDate is invalid', 'invalid-date')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1821,17 +1884,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [new GraphQLError('Could not find User')],
|
||||||
new GraphQLError('Could not find UserContact with email: bob@baumeister.de'),
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith('Could not find User', 'bob@baumeister.de')
|
||||||
'Could not find UserContact with email: bob@baumeister.de',
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1851,13 +1910,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('User was deleted (stephen@hawking.uk)')],
|
errors: [new GraphQLError('User was deleted')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith('User was deleted (stephen@hawking.uk)')
|
expect(logger.error).toBeCalledWith('User was deleted', 'stephen@hawking.uk')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1877,13 +1936,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('No contribution found to given id.')],
|
errors: [new GraphQLError('Contribution not found')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith('No contribution found to given id.')
|
expect(logger.error).toBeCalledWith('Contribution not found', -1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1907,7 +1966,7 @@ describe('ContributionResolver', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [
|
||||||
new GraphQLError(
|
new GraphQLError(
|
||||||
'user of the pending contribution and send user does not correspond',
|
'User of the pending contribution and send user does not correspond',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@ -1916,7 +1975,7 @@ describe('ContributionResolver', () => {
|
|||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith(
|
||||||
'user of the pending contribution and send user does not correspond',
|
'User of the pending contribution and send user does not correspond',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -2111,13 +2170,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('Contribution not found for given id.')],
|
errors: [new GraphQLError('Contribution not found')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith('Contribution not found for given id: -1')
|
expect(logger.error).toBeCalledWith('Contribution not found', -1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -2237,13 +2296,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('Contribution not found to given id.')],
|
errors: [new GraphQLError('Contribution not found')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith('Contribution not found for given id: -1')
|
expect(logger.error).toBeCalledWith('Contribution not found', -1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -2354,6 +2413,7 @@ describe('ContributionResolver', () => {
|
|||||||
|
|
||||||
describe('confirm same contribution again', () => {
|
describe('confirm same contribution again', () => {
|
||||||
it('throws an error', async () => {
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
await expect(
|
await expect(
|
||||||
mutate({
|
mutate({
|
||||||
mutation: confirmContribution,
|
mutation: confirmContribution,
|
||||||
@ -2363,11 +2423,18 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('Contribution already confirmd.')],
|
errors: [new GraphQLError('Contribution already confirmed')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'Contribution already confirmed',
|
||||||
|
expect.any(Number),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('confirm two creations one after the other quickly', () => {
|
describe('confirm two creations one after the other quickly', () => {
|
||||||
|
|||||||
@ -53,6 +53,7 @@ import {
|
|||||||
sendContributionDeniedEmail,
|
sendContributionDeniedEmail,
|
||||||
} from '@/emails/sendEmailVariants'
|
} from '@/emails/sendEmailVariants'
|
||||||
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||||
|
import LogError from '@/server/LogError'
|
||||||
|
|
||||||
import { getLastTransaction } from './util/getLastTransaction'
|
import { getLastTransaction } from './util/getLastTransaction'
|
||||||
|
|
||||||
@ -65,14 +66,11 @@ export class ContributionResolver {
|
|||||||
@Ctx() context: Context,
|
@Ctx() context: Context,
|
||||||
): Promise<UnconfirmedContribution> {
|
): Promise<UnconfirmedContribution> {
|
||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
if (memo.length > MEMO_MAX_CHARS) {
|
|
||||||
logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`)
|
|
||||||
throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memo.length < MEMO_MIN_CHARS) {
|
if (memo.length < MEMO_MIN_CHARS) {
|
||||||
logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`)
|
throw new LogError('Memo text is too short', memo.length)
|
||||||
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
|
}
|
||||||
|
if (memo.length > MEMO_MAX_CHARS) {
|
||||||
|
throw new LogError('Memo text is too long', memo.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = getUser(context)
|
const user = getUser(context)
|
||||||
@ -107,16 +105,13 @@ export class ContributionResolver {
|
|||||||
const user = getUser(context)
|
const user = getUser(context)
|
||||||
const contribution = await DbContribution.findOne(id)
|
const contribution = await DbContribution.findOne(id)
|
||||||
if (!contribution) {
|
if (!contribution) {
|
||||||
logger.error('Contribution not found for given id')
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error('Contribution not found for given id.')
|
|
||||||
}
|
}
|
||||||
if (contribution.userId !== user.id) {
|
if (contribution.userId !== user.id) {
|
||||||
logger.error('Can not delete contribution of another user')
|
throw new LogError('Can not delete contribution of another user', contribution, user.id)
|
||||||
throw new Error('Can not delete contribution of another user')
|
|
||||||
}
|
}
|
||||||
if (contribution.confirmedAt) {
|
if (contribution.confirmedAt) {
|
||||||
logger.error('A confirmed contribution can not be deleted')
|
throw new LogError('A confirmed contribution can not be deleted', contribution)
|
||||||
throw new Error('A confirmed contribution can not be deleted')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contribution.contributionStatus = ContributionStatus.DELETED
|
contribution.contributionStatus = ContributionStatus.DELETED
|
||||||
@ -206,14 +201,11 @@ export class ContributionResolver {
|
|||||||
@Ctx() context: Context,
|
@Ctx() context: Context,
|
||||||
): Promise<UnconfirmedContribution> {
|
): Promise<UnconfirmedContribution> {
|
||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
if (memo.length > MEMO_MAX_CHARS) {
|
|
||||||
logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`)
|
|
||||||
throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memo.length < MEMO_MIN_CHARS) {
|
if (memo.length < MEMO_MIN_CHARS) {
|
||||||
logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`)
|
throw new LogError('Memo text is too short', memo.length)
|
||||||
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
|
}
|
||||||
|
if (memo.length > MEMO_MAX_CHARS) {
|
||||||
|
throw new LogError('Memo text is too long', memo.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = getUser(context)
|
const user = getUser(context)
|
||||||
@ -222,22 +214,22 @@ export class ContributionResolver {
|
|||||||
where: { id: contributionId, confirmedAt: IsNull(), deniedAt: IsNull() },
|
where: { id: contributionId, confirmedAt: IsNull(), deniedAt: IsNull() },
|
||||||
})
|
})
|
||||||
if (!contributionToUpdate) {
|
if (!contributionToUpdate) {
|
||||||
logger.error('No contribution found to given id')
|
throw new LogError('Contribution not found', contributionId)
|
||||||
throw new Error('No contribution found to given id.')
|
|
||||||
}
|
}
|
||||||
if (contributionToUpdate.userId !== user.id) {
|
if (contributionToUpdate.userId !== user.id) {
|
||||||
logger.error('user of the pending contribution and send user does not correspond')
|
throw new LogError(
|
||||||
throw new Error('user of the pending contribution and send user does not correspond')
|
'Can not update contribution of another user',
|
||||||
|
contributionToUpdate,
|
||||||
|
user.id,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
contributionToUpdate.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
contributionToUpdate.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
||||||
contributionToUpdate.contributionStatus !== ContributionStatus.PENDING
|
contributionToUpdate.contributionStatus !== ContributionStatus.PENDING
|
||||||
) {
|
) {
|
||||||
logger.error(
|
throw new LogError(
|
||||||
`Contribution can not be updated since the state is ${contributionToUpdate.contributionStatus}`,
|
'Contribution can not be updated due to status',
|
||||||
)
|
contributionToUpdate.contributionStatus,
|
||||||
throw new Error(
|
|
||||||
`Contribution can not be updated since the state is ${contributionToUpdate.contributionStatus}`,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const creationDateObj = new Date(creationDate)
|
const creationDateObj = new Date(creationDate)
|
||||||
@ -245,8 +237,7 @@ export class ContributionResolver {
|
|||||||
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
||||||
creations = updateCreations(creations, contributionToUpdate, clientTimezoneOffset)
|
creations = updateCreations(creations, contributionToUpdate, clientTimezoneOffset)
|
||||||
} else {
|
} else {
|
||||||
logger.error('Currently the month of the contribution cannot change.')
|
throw new LogError('Month of contribution can not be changed')
|
||||||
throw new Error('Currently the month of the contribution cannot change.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// all possible cases not to be true are thrown in this function
|
// all possible cases not to be true are thrown in this function
|
||||||
@ -291,29 +282,24 @@ export class ContributionResolver {
|
|||||||
)
|
)
|
||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
if (!isValidDateString(creationDate)) {
|
if (!isValidDateString(creationDate)) {
|
||||||
logger.error(`invalid Date for creationDate=${creationDate}`)
|
throw new LogError('CreationDate is invalid', creationDate)
|
||||||
throw new Error(`invalid Date for creationDate=${creationDate}`)
|
|
||||||
}
|
}
|
||||||
const emailContact = await UserContact.findOne({
|
const emailContact = await UserContact.findOne({
|
||||||
where: { email },
|
where: { email },
|
||||||
withDeleted: true,
|
withDeleted: true,
|
||||||
relations: ['user'],
|
relations: ['user'],
|
||||||
})
|
})
|
||||||
if (!emailContact) {
|
if (!emailContact || !emailContact.user) {
|
||||||
logger.error(`Could not find user with email: ${email}`)
|
throw new LogError('Could not find user', email)
|
||||||
throw new Error(`Could not find user with email: ${email}`)
|
|
||||||
}
|
}
|
||||||
if (emailContact.deletedAt) {
|
if (emailContact.deletedAt || emailContact.user.deletedAt) {
|
||||||
logger.error('This emailContact was deleted. Cannot create a contribution.')
|
throw new LogError('Cannot create contribution since the user was deleted', emailContact)
|
||||||
throw new Error('This emailContact was deleted. Cannot create a contribution.')
|
|
||||||
}
|
|
||||||
if (emailContact.user.deletedAt) {
|
|
||||||
logger.error('This user was deleted. Cannot create a contribution.')
|
|
||||||
throw new Error('This user was deleted. Cannot create a contribution.')
|
|
||||||
}
|
}
|
||||||
if (!emailContact.emailChecked) {
|
if (!emailContact.emailChecked) {
|
||||||
logger.error('Contribution could not be saved, Email is not activated')
|
throw new LogError(
|
||||||
throw new Error('Contribution could not be saved, Email is not activated')
|
'Cannot create contribution since the users email is not activated',
|
||||||
|
emailContact,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const moderator = getUser(context)
|
const moderator = getUser(context)
|
||||||
@ -381,18 +367,11 @@ export class ContributionResolver {
|
|||||||
withDeleted: true,
|
withDeleted: true,
|
||||||
relations: ['user'],
|
relations: ['user'],
|
||||||
})
|
})
|
||||||
if (!emailContact) {
|
if (!emailContact || !emailContact.user) {
|
||||||
logger.error(`Could not find UserContact with email: ${email}`)
|
throw new LogError('Could not find User', email)
|
||||||
throw new Error(`Could not find UserContact with email: ${email}`)
|
|
||||||
}
|
}
|
||||||
const user = emailContact.user
|
if (emailContact.deletedAt || emailContact.user.deletedAt) {
|
||||||
if (!user) {
|
throw new LogError('User was deleted', email)
|
||||||
logger.error(`Could not find User to emailContact: ${email}`)
|
|
||||||
throw new Error(`Could not find User to emailContact: ${email}`)
|
|
||||||
}
|
|
||||||
if (user.deletedAt) {
|
|
||||||
logger.error(`User was deleted (${email})`)
|
|
||||||
throw new Error(`User was deleted (${email})`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const moderator = getUser(context)
|
const moderator = getUser(context)
|
||||||
@ -401,28 +380,25 @@ export class ContributionResolver {
|
|||||||
where: { id, confirmedAt: IsNull(), deniedAt: IsNull() },
|
where: { id, confirmedAt: IsNull(), deniedAt: IsNull() },
|
||||||
})
|
})
|
||||||
if (!contributionToUpdate) {
|
if (!contributionToUpdate) {
|
||||||
logger.error('No contribution found to given id.')
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error('No contribution found to given id.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contributionToUpdate.userId !== user.id) {
|
if (contributionToUpdate.userId !== emailContact.user.id) {
|
||||||
logger.error('user of the pending contribution and send user does not correspond')
|
throw new LogError('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) {
|
if (contributionToUpdate.moderatorId === null) {
|
||||||
logger.error('An admin is not allowed to update a user contribution.')
|
throw new LogError('An admin is not allowed to update an user contribution')
|
||||||
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, clientTimezoneOffset)
|
let creations = await getUserCreation(emailContact.user.id, clientTimezoneOffset)
|
||||||
|
|
||||||
|
// TODO: remove this restriction
|
||||||
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
||||||
creations = updateCreations(creations, contributionToUpdate, clientTimezoneOffset)
|
creations = updateCreations(creations, contributionToUpdate, clientTimezoneOffset)
|
||||||
} else {
|
} else {
|
||||||
logger.error('Currently the month of the contribution cannot change.')
|
throw new LogError('Month of contribution can not be changed')
|
||||||
throw new Error('Currently the month of the contribution cannot change.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// all possible cases not to be true are thrown in this function
|
// all possible cases not to be true are thrown in this function
|
||||||
@ -440,9 +416,9 @@ export class ContributionResolver {
|
|||||||
result.memo = contributionToUpdate.memo
|
result.memo = contributionToUpdate.memo
|
||||||
result.date = contributionToUpdate.contributionDate
|
result.date = contributionToUpdate.contributionDate
|
||||||
|
|
||||||
result.creation = await getUserCreation(user.id, clientTimezoneOffset)
|
result.creation = await getUserCreation(emailContact.user.id, clientTimezoneOffset)
|
||||||
|
|
||||||
await EVENT_ADMIN_CONTRIBUTION_UPDATE(user.id, contributionToUpdate.id, amount)
|
await EVENT_ADMIN_CONTRIBUTION_UPDATE(emailContact.user.id, contributionToUpdate.id, amount)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -492,19 +468,17 @@ export class ContributionResolver {
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const contribution = await DbContribution.findOne(id)
|
const contribution = await DbContribution.findOne(id)
|
||||||
if (!contribution) {
|
if (!contribution) {
|
||||||
logger.error(`Contribution not found for given id: ${id}`)
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error('Contribution not found for given id.')
|
|
||||||
}
|
}
|
||||||
if (contribution.confirmedAt) {
|
if (contribution.confirmedAt) {
|
||||||
logger.error('A confirmed contribution can not be deleted')
|
throw new LogError('A confirmed contribution can not be deleted')
|
||||||
throw new Error('A confirmed contribution can not be deleted')
|
|
||||||
}
|
}
|
||||||
const moderator = getUser(context)
|
const moderator = getUser(context)
|
||||||
if (
|
if (
|
||||||
contribution.contributionType === ContributionType.USER &&
|
contribution.contributionType === ContributionType.USER &&
|
||||||
contribution.userId === moderator.id
|
contribution.userId === moderator.id
|
||||||
) {
|
) {
|
||||||
throw new Error('Own contribution can not be deleted as admin')
|
throw new LogError('Own contribution can not be deleted as admin')
|
||||||
}
|
}
|
||||||
const user = await DbUser.findOneOrFail(
|
const user = await DbUser.findOneOrFail(
|
||||||
{ id: contribution.userId },
|
{ id: contribution.userId },
|
||||||
@ -542,29 +516,24 @@ export class ContributionResolver {
|
|||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
const contribution = await DbContribution.findOne(id)
|
const contribution = await DbContribution.findOne(id)
|
||||||
if (!contribution) {
|
if (!contribution) {
|
||||||
logger.error(`Contribution not found for given id: ${id}`)
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error('Contribution not found to given id.')
|
|
||||||
}
|
}
|
||||||
if (contribution.confirmedAt) {
|
if (contribution.confirmedAt) {
|
||||||
logger.error(`Contribution already confirmd: ${id}`)
|
throw new LogError('Contribution already confirmed', id)
|
||||||
throw new Error('Contribution already confirmd.')
|
|
||||||
}
|
}
|
||||||
if (contribution.contributionStatus === 'DENIED') {
|
if (contribution.contributionStatus === 'DENIED') {
|
||||||
logger.error(`Contribution already denied: ${id}`)
|
throw new LogError('Contribution already denied', id)
|
||||||
throw new Error('Contribution already denied.')
|
|
||||||
}
|
}
|
||||||
const moderatorUser = getUser(context)
|
const moderatorUser = getUser(context)
|
||||||
if (moderatorUser.id === contribution.userId) {
|
if (moderatorUser.id === contribution.userId) {
|
||||||
logger.error('Moderator can not confirm own contribution')
|
throw new LogError('Moderator can not confirm own contribution')
|
||||||
throw new Error('Moderator can not confirm own contribution')
|
|
||||||
}
|
}
|
||||||
const user = await DbUser.findOneOrFail(
|
const user = await DbUser.findOneOrFail(
|
||||||
{ id: contribution.userId },
|
{ id: contribution.userId },
|
||||||
{ withDeleted: true, relations: ['emailContact'] },
|
{ withDeleted: true, relations: ['emailContact'] },
|
||||||
)
|
)
|
||||||
if (user.deletedAt) {
|
if (user.deletedAt) {
|
||||||
logger.error('This user was deleted. Cannot confirm a contribution.')
|
throw new LogError('Can not confirm contribution since the user was deleted')
|
||||||
throw new Error('This user was deleted. Cannot confirm a contribution.')
|
|
||||||
}
|
}
|
||||||
const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false)
|
const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false)
|
||||||
validateContribution(
|
validateContribution(
|
||||||
@ -628,8 +597,7 @@ export class ContributionResolver {
|
|||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await queryRunner.rollbackTransaction()
|
await queryRunner.rollbackTransaction()
|
||||||
logger.error('Creation was not successful', e)
|
throw new LogError('Creation was not successful', e)
|
||||||
throw new Error('Creation was not successful.')
|
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
}
|
}
|
||||||
@ -699,17 +667,16 @@ export class ContributionResolver {
|
|||||||
deniedBy: IsNull(),
|
deniedBy: IsNull(),
|
||||||
})
|
})
|
||||||
if (!contributionToUpdate) {
|
if (!contributionToUpdate) {
|
||||||
logger.error(`Contribution not found for given id: ${id}`)
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error(`Contribution not found for given id.`)
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
contributionToUpdate.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
contributionToUpdate.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
||||||
contributionToUpdate.contributionStatus !== ContributionStatus.PENDING
|
contributionToUpdate.contributionStatus !== ContributionStatus.PENDING
|
||||||
) {
|
) {
|
||||||
logger.error(
|
throw new LogError(
|
||||||
`Contribution state (${contributionToUpdate.contributionStatus}) is not allowed.`,
|
'Status of the contribution is not allowed',
|
||||||
|
contributionToUpdate.contributionStatus,
|
||||||
)
|
)
|
||||||
throw new Error(`State of the contribution is not allowed.`)
|
|
||||||
}
|
}
|
||||||
const moderator = getUser(context)
|
const moderator = getUser(context)
|
||||||
const user = await DbUser.findOne(
|
const user = await DbUser.findOne(
|
||||||
@ -717,10 +684,7 @@ export class ContributionResolver {
|
|||||||
{ relations: ['emailContact'] },
|
{ relations: ['emailContact'] },
|
||||||
)
|
)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
logger.error(
|
throw new LogError('Could not find User of the Contribution', contributionToUpdate.userId)
|
||||||
`Could not find User for the Contribution (userId: ${contributionToUpdate.userId}).`,
|
|
||||||
)
|
|
||||||
throw new Error('Could not find User for the Contribution.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contributionToUpdate.contributionStatus = ContributionStatus.DENIED
|
contributionToUpdate.contributionStatus = ContributionStatus.DENIED
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-database",
|
"name": "gradido-database",
|
||||||
"version": "1.18.0",
|
"version": "1.18.2",
|
||||||
"description": "Gradido Database Tool to execute database migrations",
|
"description": "Gradido Database Tool to execute database migrations",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/database",
|
"repository": "https://github.com/gradido/gradido/database",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bootstrap-vue-gradido-wallet",
|
"name": "bootstrap-vue-gradido-wallet",
|
||||||
"version": "1.18.0",
|
"version": "1.18.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node run/server.js",
|
"start": "node run/server.js",
|
||||||
|
|||||||
@ -24,11 +24,6 @@ describe('ContributionForm', () => {
|
|||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
$d: jest.fn((d) => d),
|
$d: jest.fn((d) => d),
|
||||||
$n: jest.fn((n) => n),
|
$n: jest.fn((n) => n),
|
||||||
$store: {
|
|
||||||
state: {
|
|
||||||
creation: ['1000', '1000', '1000'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
$i18n: {
|
$i18n: {
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
},
|
},
|
||||||
@ -61,7 +56,7 @@ describe('ContributionForm', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('dates', () => {
|
describe('dates and max amounts', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await wrapper.setData({
|
await wrapper.setData({
|
||||||
form: {
|
form: {
|
||||||
@ -73,204 +68,176 @@ describe('ContributionForm', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('actual date', () => {
|
describe('max amount reached for both months', () => {
|
||||||
describe('same month', () => {
|
beforeEach(() => {
|
||||||
beforeEach(async () => {
|
wrapper.setProps({
|
||||||
const now = new Date().toISOString()
|
maxGddLastMonth: 0,
|
||||||
await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now)
|
maxGddThisMonth: 0,
|
||||||
})
|
})
|
||||||
|
wrapper.setData({
|
||||||
describe('isThisMonth', () => {
|
form: {
|
||||||
it('has true', () => {
|
id: null,
|
||||||
expect(wrapper.vm.isThisMonth).toBe(true)
|
date: 'set',
|
||||||
})
|
memo: '',
|
||||||
|
amount: '',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.skip('month before', () => {
|
it('shows message that no contributions are available', () => {
|
||||||
beforeEach(async () => {
|
expect(wrapper.find('[data-test="contribtion-message"]').text()).toBe(
|
||||||
await wrapper
|
'contribution.noOpenCreation.allMonth',
|
||||||
.findComponent({ name: 'BFormDatepicker' })
|
)
|
||||||
.vm.$emit('input', wrapper.vm.minimalDate)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('isThisMonth', () => {
|
|
||||||
it('has false', () => {
|
|
||||||
expect(wrapper.vm.isThisMonth).toBe(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.skip('date in middle of year', () => {
|
describe('max amount reached for last month, no date selected', () => {
|
||||||
describe('same month', () => {
|
beforeEach(() => {
|
||||||
beforeEach(async () => {
|
wrapper.setProps({
|
||||||
// jest.useFakeTimers('modern')
|
maxGddLastMonth: 0,
|
||||||
// jest.setSystemTime(new Date('2020-07-06'))
|
|
||||||
// await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now)
|
|
||||||
await wrapper.setData({
|
|
||||||
maximalDate: new Date(2020, 6, 6),
|
|
||||||
form: { date: new Date(2020, 6, 6) },
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('minimalDate', () => {
|
|
||||||
it('has "2020-06-01T00:00:00.000Z"', () => {
|
|
||||||
expect(wrapper.vm.minimalDate.toISOString()).toBe('2020-06-01T00:00:00.000Z')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('isThisMonth', () => {
|
|
||||||
it('has true', () => {
|
|
||||||
expect(wrapper.vm.isThisMonth).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('month before', () => {
|
it('shows no message', () => {
|
||||||
beforeEach(async () => {
|
expect(wrapper.find('[data-test="contribtion-message"]').exists()).toBe(false)
|
||||||
// jest.useFakeTimers('modern')
|
|
||||||
// jest.setSystemTime(new Date('2020-07-06'))
|
|
||||||
// console.log('middle of year date – now:', wrapper.vm.minimalDate)
|
|
||||||
// await wrapper
|
|
||||||
// .findComponent({ name: 'BFormDatepicker' })
|
|
||||||
// .vm.$emit('input', wrapper.vm.minimalDate)
|
|
||||||
await wrapper.setData({
|
|
||||||
maximalDate: new Date(2020, 6, 6),
|
|
||||||
form: { date: new Date(2020, 5, 6) },
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('minimalDate', () => {
|
|
||||||
it('has "2020-06-01T00:00:00.000Z"', () => {
|
|
||||||
expect(wrapper.vm.minimalDate.toISOString()).toBe('2020-06-01T00:00:00.000Z')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('isThisMonth', () => {
|
|
||||||
it('has false', () => {
|
|
||||||
expect(wrapper.vm.isThisMonth).toBe(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.skip('date in january', () => {
|
describe('max amount reached for last month, last month selected', () => {
|
||||||
describe('same month', () => {
|
beforeEach(async () => {
|
||||||
beforeEach(async () => {
|
wrapper.setProps({
|
||||||
await wrapper.setData({
|
maxGddLastMonth: 0,
|
||||||
maximalDate: new Date(2020, 0, 6),
|
isThisMonth: false,
|
||||||
form: { date: new Date(2020, 0, 6) },
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
await wrapper.setData({
|
||||||
describe('minimalDate', () => {
|
form: {
|
||||||
it('has "2019-12-01T00:00:00.000Z"', () => {
|
id: null,
|
||||||
expect(wrapper.vm.minimalDate.toISOString()).toBe('2019-12-01T00:00:00.000Z')
|
date: 'set',
|
||||||
})
|
memo: '',
|
||||||
})
|
amount: '',
|
||||||
|
},
|
||||||
describe('isThisMonth', () => {
|
|
||||||
it('has true', () => {
|
|
||||||
expect(wrapper.vm.isThisMonth).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('month before', () => {
|
it('shows message that no contributions are available for last month', () => {
|
||||||
beforeEach(async () => {
|
expect(wrapper.find('[data-test="contribtion-message"]').text()).toBe(
|
||||||
// jest.useFakeTimers('modern')
|
'contribution.noOpenCreation.lastMonth',
|
||||||
// jest.setSystemTime(new Date('2020-07-06'))
|
)
|
||||||
// console.log('middle of year date – now:', wrapper.vm.minimalDate)
|
|
||||||
// await wrapper
|
|
||||||
// .findComponent({ name: 'BFormDatepicker' })
|
|
||||||
// .vm.$emit('input', wrapper.vm.minimalDate)
|
|
||||||
await wrapper.setData({
|
|
||||||
maximalDate: new Date(2020, 0, 6),
|
|
||||||
form: { date: new Date(2019, 11, 6) },
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('minimalDate', () => {
|
|
||||||
it('has "2019-12-01T00:00:00.000Z"', () => {
|
|
||||||
expect(wrapper.vm.minimalDate.toISOString()).toBe('2019-12-01T00:00:00.000Z')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('isThisMonth', () => {
|
|
||||||
it('has false', () => {
|
|
||||||
expect(wrapper.vm.isThisMonth).toBe(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.skip('date with the 31st day of the month', () => {
|
describe('max amount reached for last month, this month selected', () => {
|
||||||
describe('same month', () => {
|
beforeEach(async () => {
|
||||||
beforeEach(async () => {
|
wrapper.setProps({
|
||||||
await wrapper.setData({
|
maxGddLastMonth: 0,
|
||||||
maximalDate: new Date('2022-10-31T00:00:00.000Z'),
|
isThisMonth: true,
|
||||||
form: { date: new Date('2022-10-31T00:00:00.000Z') },
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
await wrapper.setData({
|
||||||
|
form: {
|
||||||
|
id: null,
|
||||||
|
date: 'set',
|
||||||
|
memo: '',
|
||||||
|
amount: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('minimalDate', () => {
|
it('shows no message', () => {
|
||||||
it('has "2022-09-01T00:00:00.000Z"', () => {
|
expect(wrapper.find('[data-test="contribtion-message"]').exists()).toBe(false)
|
||||||
expect(wrapper.vm.minimalDate.toISOString()).toBe('2022-09-01T00:00:00.000Z')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('isThisMonth', () => {
|
|
||||||
it('has true', () => {
|
|
||||||
expect(wrapper.vm.isThisMonth).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.skip('date with the 28th day of the month', () => {
|
describe('max amount reached for this month, no date selected', () => {
|
||||||
describe('same month', () => {
|
beforeEach(() => {
|
||||||
beforeEach(async () => {
|
wrapper.setProps({
|
||||||
await wrapper.setData({
|
maxGddThisMonth: 0,
|
||||||
maximalDate: new Date('2023-02-28T00:00:00.000Z'),
|
|
||||||
form: { date: new Date('2023-02-28T00:00:00.000Z') },
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('minimalDate', () => {
|
it('shows no message', () => {
|
||||||
it('has "2023-01-01T00:00:00.000Z"', () => {
|
expect(wrapper.find('[data-test="contribtion-message"]').exists()).toBe(false)
|
||||||
expect(wrapper.vm.minimalDate.toISOString()).toBe('2023-01-01T00:00:00.000Z')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('isThisMonth', () => {
|
|
||||||
it('has true', () => {
|
|
||||||
expect(wrapper.vm.isThisMonth).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.skip('date with 29.02.2024 leap year', () => {
|
describe('max amount reached for this month, this month selected', () => {
|
||||||
describe('same month', () => {
|
beforeEach(async () => {
|
||||||
beforeEach(async () => {
|
wrapper.setProps({
|
||||||
await wrapper.setData({
|
maxGddThisMonth: 0,
|
||||||
maximalDate: new Date('2024-02-29T00:00:00.000Z'),
|
isThisMonth: true,
|
||||||
form: { date: new Date('2024-02-29T00:00:00.000Z') },
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
await wrapper.setData({
|
||||||
|
form: {
|
||||||
|
id: null,
|
||||||
|
date: 'set',
|
||||||
|
memo: '',
|
||||||
|
amount: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('minimalDate', () => {
|
it('shows message that no contributions are available for last month', () => {
|
||||||
it('has "2024-01-01T00:00:00.000Z"', () => {
|
expect(wrapper.find('[data-test="contribtion-message"]').text()).toBe(
|
||||||
expect(wrapper.vm.minimalDate.toISOString()).toBe('2024-01-01T00:00:00.000Z')
|
'contribution.noOpenCreation.thisMonth',
|
||||||
})
|
)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('isThisMonth', () => {
|
describe('max amount reached for this month, last month selected', () => {
|
||||||
it('has true', () => {
|
beforeEach(async () => {
|
||||||
expect(wrapper.vm.isThisMonth).toBe(true)
|
wrapper.setProps({
|
||||||
})
|
maxGddThisMonth: 0,
|
||||||
|
isThisMonth: false,
|
||||||
})
|
})
|
||||||
|
await wrapper.setData({
|
||||||
|
form: {
|
||||||
|
id: null,
|
||||||
|
date: 'set',
|
||||||
|
memo: '',
|
||||||
|
amount: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows no message', () => {
|
||||||
|
expect(wrapper.find('[data-test="contribtion-message"]').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('default return message', () => {
|
||||||
|
it('returns an empty string', () => {
|
||||||
|
expect(wrapper.vm.noOpenCreation).toBe('')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('update amount', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper.findComponent({ name: 'InputHour' }).vm.$emit('updateAmount', 20)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updates form amount', () => {
|
||||||
|
expect(wrapper.vm.form.amount).toBe('400.00')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('watch value', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper.setProps({
|
||||||
|
value: {
|
||||||
|
id: 42,
|
||||||
|
date: 'set',
|
||||||
|
memo: 'Some Memo',
|
||||||
|
amount: '400.00',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updates form', () => {
|
||||||
|
expect(wrapper.vm.form).toEqual({
|
||||||
|
id: 42,
|
||||||
|
date: 'set',
|
||||||
|
memo: 'Some Memo',
|
||||||
|
amount: '400.00',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -477,24 +444,23 @@ describe('ContributionForm', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.skip('on trigger submit', () => {
|
describe('on trigger submit', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await wrapper.find('form').trigger('submit')
|
await wrapper.find('form').trigger('submit')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('emits "update-contribution"', () => {
|
it('emits "update-contribution"', () => {
|
||||||
expect(wrapper.emitted('update-contribution')).toEqual(
|
expect(wrapper.emitted('update-contribution')).toEqual([
|
||||||
expect.arrayContaining([
|
[
|
||||||
expect.arrayContaining([
|
{
|
||||||
{
|
id: 2,
|
||||||
id: 2,
|
date: now,
|
||||||
date: now,
|
hours: 0,
|
||||||
memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...',
|
memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...',
|
||||||
amount: '200',
|
amount: '200',
|
||||||
},
|
},
|
||||||
]),
|
],
|
||||||
]),
|
])
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -23,10 +23,7 @@
|
|||||||
<template #nav-next-year><span></span></template>
|
<template #nav-next-year><span></span></template>
|
||||||
</b-form-datepicker>
|
</b-form-datepicker>
|
||||||
|
|
||||||
<div
|
<div v-if="showMessage" class="p-3" data-test="contribtion-message">
|
||||||
v-if="(isThisMonth && maxGddThisMonth <= 0) || (!isThisMonth && maxGddLastMonth <= 0)"
|
|
||||||
class="p-3"
|
|
||||||
>
|
|
||||||
{{ noOpenCreation }}
|
{{ noOpenCreation }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
@ -118,8 +115,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateAmount(amount) {
|
updateAmount(hours) {
|
||||||
this.form.amount = (amount * 20).toFixed(2).toString()
|
this.form.amount = (hours * 20).toFixed(2).toString()
|
||||||
},
|
},
|
||||||
submit() {
|
submit() {
|
||||||
this.$emit(this.form.id ? 'update-contribution' : 'set-contribution', { ...this.form })
|
this.$emit(this.form.id ? 'update-contribution' : 'set-contribution', { ...this.form })
|
||||||
@ -135,6 +132,15 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
showMessage() {
|
||||||
|
if (this.maxGddThisMonth <= 0 && this.maxGddLastMonth <= 0) return true
|
||||||
|
if (this.form.date)
|
||||||
|
return (
|
||||||
|
(this.isThisMonth && this.maxGddThisMonth <= 0) ||
|
||||||
|
(!this.isThisMonth && this.maxGddLastMonth <= 0)
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
},
|
||||||
disabled() {
|
disabled() {
|
||||||
return (
|
return (
|
||||||
this.form.date === '' ||
|
this.form.date === '' ||
|
||||||
|
|||||||
@ -6,9 +6,9 @@
|
|||||||
{{ CONFIG.COMMUNITY_DESCRIPTION }}
|
{{ CONFIG.COMMUNITY_DESCRIPTION }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<router-link :to="CONFIG.COMMUNITY_URL">
|
<b-link :href="CONFIG.COMMUNITY_URL">
|
||||||
{{ CONFIG.COMMUNITY_URL }}
|
{{ CONFIG.COMMUNITY_URL }}
|
||||||
</router-link>
|
</b-link>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="h3">{{ $t('community.openContributionLinks') }}</div>
|
<div class="h3">{{ $t('community.openContributionLinks') }}</div>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido",
|
"name": "gradido",
|
||||||
"version": "1.18.0",
|
"version": "1.18.2",
|
||||||
"description": "Gradido",
|
"description": "Gradido",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": "git@github.com:gradido/gradido.git",
|
"repository": "git@github.com:gradido/gradido.git",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user