mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 01:46:07 +00:00
Merge branch 'master' into federation-fix-database-public-key-length
This commit is contained in:
commit
dd87313d41
@ -1,6 +1,7 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionMessagesFormular from './ContributionMessagesFormular'
|
||||
import { toastErrorSpy, toastSuccessSpy } from '../../../test/testSetup'
|
||||
import { adminCreateContributionMessage } from '@/graphql/adminCreateContributionMessage'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
@ -34,6 +35,7 @@ describe('ContributionMessagesFormular', () => {
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('has a DIV .contribution-messages-formular', () => {
|
||||
@ -73,13 +75,65 @@ describe('ContributionMessagesFormular', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('emitted "update-state" with data', async () => {
|
||||
expect(wrapper.emitted('update-state')).toEqual(
|
||||
it('emitted "update-status" with data', async () => {
|
||||
expect(wrapper.emitted('update-status')).toEqual(
|
||||
expect.arrayContaining([expect.arrayContaining([42])]),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('send DIALOG contribution message with success', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
await wrapper.find('button[data-test="submit-dialog"]').trigger('click')
|
||||
})
|
||||
|
||||
it('moderatorMesage has `DIALOG`', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith({
|
||||
mutation: adminCreateContributionMessage,
|
||||
variables: {
|
||||
contributionId: 42,
|
||||
message: 'text form message',
|
||||
messageType: 'DIALOG',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('toasts an success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('message.request')
|
||||
})
|
||||
})
|
||||
|
||||
describe('send MODERATOR contribution message with success', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
await wrapper.find('button[data-test="submit-moderator"]').trigger('click')
|
||||
})
|
||||
|
||||
it('moderatorMesage has `MODERATOR`', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith({
|
||||
mutation: adminCreateContributionMessage,
|
||||
variables: {
|
||||
contributionId: 42,
|
||||
message: 'text form message',
|
||||
messageType: 'MODERATOR',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('toasts an success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('message.request')
|
||||
})
|
||||
})
|
||||
|
||||
describe('send contribution message with error', () => {
|
||||
beforeEach(async () => {
|
||||
apolloMutateMock.mockRejectedValue({ message: 'OUCH!' })
|
||||
@ -91,21 +145,5 @@ describe('ContributionMessagesFormular', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('OUCH!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('send contribution message with success', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
wrapper = Wrapper()
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('toasts an success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('message.request')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="contribution-messages-formular">
|
||||
<div class="mt-5">
|
||||
<b-form @submit.prevent="onSubmit" @reset.prevent="onReset">
|
||||
<b-form @reset.prevent="onReset" @submit="onSubmit(messageType.DIALOG)">
|
||||
<b-form-textarea
|
||||
id="textarea"
|
||||
v-model="form.text"
|
||||
@ -12,8 +12,27 @@
|
||||
<b-col>
|
||||
<b-button type="reset" variant="danger">{{ $t('form.cancel') }}</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-center">
|
||||
<b-button
|
||||
type="button"
|
||||
variant="warning"
|
||||
class="text-black"
|
||||
:disabled="disabled"
|
||||
@click.prevent="onSubmit(messageType.MODERATOR)"
|
||||
data-test="submit-moderator"
|
||||
>
|
||||
{{ $t('moderator.notice') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
|
||||
<b-col class="text-right">
|
||||
<b-button type="submit" variant="primary" :disabled="disabled">
|
||||
<b-button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
:disabled="disabled"
|
||||
@click.prevent="onSubmit(messageType.DIALOG)"
|
||||
data-test="submit-dialog"
|
||||
>
|
||||
{{ $t('form.submit') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
@ -39,10 +58,14 @@ export default {
|
||||
text: '',
|
||||
},
|
||||
loading: false,
|
||||
messageType: {
|
||||
DIALOG: 'DIALOG',
|
||||
MODERATOR: 'MODERATOR',
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit(event) {
|
||||
onSubmit(mType) {
|
||||
this.loading = true
|
||||
this.$apollo
|
||||
.mutate({
|
||||
@ -50,11 +73,12 @@ export default {
|
||||
variables: {
|
||||
contributionId: this.contributionId,
|
||||
message: this.form.text,
|
||||
messageType: mType,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
this.$emit('get-list-contribution-messages', this.contributionId)
|
||||
this.$emit('update-state', this.contributionId)
|
||||
this.$emit('update-status', this.contributionId)
|
||||
this.form.text = ''
|
||||
this.toastSuccess(this.$t('message.request'))
|
||||
this.loading = false
|
||||
|
||||
@ -1,26 +1,102 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionMessagesList from './ContributionMessagesList'
|
||||
import VueApollo from 'vue-apollo'
|
||||
import { createMockClient } from 'mock-apollo-client'
|
||||
import { adminListContributionMessages } from '../../graphql/adminListContributionMessages.js'
|
||||
import { toastErrorSpy } from '../../../test/testSetup'
|
||||
|
||||
const mockClient = createMockClient()
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: mockClient,
|
||||
})
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue()
|
||||
localVue.use(VueApollo)
|
||||
|
||||
const defaultData = () => {
|
||||
return {
|
||||
adminListContributionMessages: {
|
||||
count: 4,
|
||||
messages: [
|
||||
{
|
||||
id: 43,
|
||||
message: 'A DIALOG message',
|
||||
createdAt: new Date().toString(),
|
||||
updatedAt: null,
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
userId: 1,
|
||||
isModerator: true,
|
||||
},
|
||||
{
|
||||
id: 44,
|
||||
message: 'Another DIALOG message',
|
||||
createdAt: new Date().toString(),
|
||||
updatedAt: null,
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Bibi',
|
||||
userLastName: 'Bloxberg',
|
||||
userId: 2,
|
||||
isModerator: false,
|
||||
},
|
||||
{
|
||||
id: 45,
|
||||
message: `DATE
|
||||
---
|
||||
A HISTORY message
|
||||
---
|
||||
AMOUNT`,
|
||||
createdAt: new Date().toString(),
|
||||
updatedAt: null,
|
||||
type: 'HISTORY',
|
||||
userFirstName: 'Bibi',
|
||||
userLastName: 'Bloxberg',
|
||||
userId: 2,
|
||||
isModerator: false,
|
||||
},
|
||||
{
|
||||
id: 46,
|
||||
message: 'A MODERATOR message',
|
||||
createdAt: new Date().toString(),
|
||||
updatedAt: null,
|
||||
type: 'MODERATOR',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
userId: 1,
|
||||
isModerator: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe('ContributionMessagesList', () => {
|
||||
let wrapper
|
||||
|
||||
const adminListContributionMessagessMock = jest.fn()
|
||||
|
||||
mockClient.setRequestHandler(
|
||||
adminListContributionMessages,
|
||||
adminListContributionMessagessMock
|
||||
.mockRejectedValueOnce({ message: 'Auaa!' })
|
||||
.mockResolvedValue({ data: defaultData() }),
|
||||
)
|
||||
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
contributionState: 'PENDING',
|
||||
contributionUserId: 108,
|
||||
contributionStatus: 'PENDING',
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
$n: jest.fn((n) => n),
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
$apollo: {
|
||||
query: apolloQueryMock,
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
@ -28,30 +104,34 @@ describe('ContributionMessagesList', () => {
|
||||
localVue,
|
||||
mocks,
|
||||
propsData,
|
||||
apolloProvider,
|
||||
})
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('sends query to Apollo when created', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
contributionId: propsData.contributionId,
|
||||
},
|
||||
}),
|
||||
)
|
||||
describe('server response for admin list contribution messages is error', () => {
|
||||
it('toast an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Auaa!')
|
||||
})
|
||||
})
|
||||
|
||||
it('has a DIV .contribution-messages-list', () => {
|
||||
expect(wrapper.find('div.contribution-messages-list').exists()).toBe(true)
|
||||
})
|
||||
describe('server response is succes', () => {
|
||||
it('has a DIV .contribution-messages-list', () => {
|
||||
expect(wrapper.find('div.contribution-messages-list').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('has a Component ContributionMessagesFormular', () => {
|
||||
expect(wrapper.findComponent({ name: 'ContributionMessagesFormular' }).exists()).toBe(true)
|
||||
it('has 4 messages', () => {
|
||||
expect(wrapper.findAll('div.contribution-messages-list-item')).toHaveLength(4)
|
||||
})
|
||||
|
||||
it('has a Component ContributionMessagesFormular', () => {
|
||||
expect(wrapper.findComponent({ name: 'ContributionMessagesFormular' }).exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,15 +2,17 @@
|
||||
<div class="contribution-messages-list">
|
||||
<b-container>
|
||||
<div v-for="message in messages" v-bind:key="message.id">
|
||||
<contribution-messages-list-item :message="message" />
|
||||
<contribution-messages-list-item
|
||||
:message="message"
|
||||
:contributionUserId="contributionUserId"
|
||||
/>
|
||||
</div>
|
||||
</b-container>
|
||||
|
||||
<div v-if="contributionState === 'PENDING' || contributionState === 'IN_PROGRESS'">
|
||||
<div v-if="contributionStatus === 'PENDING' || contributionStatus === 'IN_PROGRESS'">
|
||||
<contribution-messages-formular
|
||||
:contributionId="contributionId"
|
||||
@get-list-contribution-messages="getListContributionMessages"
|
||||
@update-state="updateState"
|
||||
@get-list-contribution-messages="$apollo.queries.Messages.refetch()"
|
||||
@update-status="updateStatus"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -18,7 +20,7 @@
|
||||
<script>
|
||||
import ContributionMessagesListItem from './slots/ContributionMessagesListItem'
|
||||
import ContributionMessagesFormular from '../ContributionMessages/ContributionMessagesFormular'
|
||||
import { listContributionMessages } from '../../graphql/listContributionMessages.js'
|
||||
import { adminListContributionMessages } from '../../graphql/adminListContributionMessages.js'
|
||||
|
||||
export default {
|
||||
name: 'ContributionMessagesList',
|
||||
@ -31,39 +33,43 @@ export default {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
contributionState: {
|
||||
contributionStatus: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
contributionUserId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
messages: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getListContributionMessages(id) {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: listContributionMessages,
|
||||
variables: {
|
||||
contributionId: id,
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
})
|
||||
.then((result) => {
|
||||
this.messages = result.data.listContributionMessages.messages
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
updateState(id) {
|
||||
this.$emit('update-state', id)
|
||||
apollo: {
|
||||
Messages: {
|
||||
query() {
|
||||
return adminListContributionMessages
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
contributionId: this.contributionId,
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
update({ adminListContributionMessages }) {
|
||||
this.messages = adminListContributionMessages.messages
|
||||
},
|
||||
error({ message }) {
|
||||
this.toastError(message)
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getListContributionMessages(this.contributionId)
|
||||
methods: {
|
||||
updateStatus(id) {
|
||||
this.$emit('update-status', id)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -13,11 +13,20 @@ describe('ContributionMessagesListItem', () => {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: dateMock,
|
||||
$n: numberMock,
|
||||
$store: {
|
||||
state: {
|
||||
moderator: {
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
describe('if message author has moderator role', () => {
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
contributionUserId: 108,
|
||||
state: 'PENDING',
|
||||
message: {
|
||||
id: 111,
|
||||
@ -51,27 +60,21 @@ describe('ContributionMessagesListItem', () => {
|
||||
})
|
||||
|
||||
it('has the complete user name', () => {
|
||||
expect(wrapper.find('div.text-right.is-moderator > span:nth-child(2)').text()).toBe(
|
||||
'Peter Lustig',
|
||||
)
|
||||
expect(wrapper.find('[data-test="moderator-name"]').text()).toBe('Peter Lustig')
|
||||
})
|
||||
|
||||
it('has the message creation date', () => {
|
||||
expect(wrapper.find('div.text-right.is-moderator > span:nth-child(3)').text()).toMatch(
|
||||
expect(wrapper.find('[data-test="moderator-date"]').text()).toMatch(
|
||||
'Mon Aug 29 2022 12:23:27 GMT+0000',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the moderator label', () => {
|
||||
expect(wrapper.find('div.text-right.is-moderator > small:nth-child(4)').text()).toBe(
|
||||
'moderator',
|
||||
)
|
||||
expect(wrapper.find('[data-test="moderator-label"]').text()).toBe('moderator.moderator')
|
||||
})
|
||||
|
||||
it('has the message', () => {
|
||||
expect(wrapper.find('div.text-right.is-moderator > div:nth-child(5)').text()).toBe(
|
||||
'Lorem ipsum?',
|
||||
)
|
||||
expect(wrapper.find('[data-test="moderator-message"]').text()).toBe('Lorem ipsum?')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -79,6 +82,7 @@ describe('ContributionMessagesListItem', () => {
|
||||
describe('if message author does not have moderator role', () => {
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
contributionUserId: 108,
|
||||
state: 'PENDING',
|
||||
message: {
|
||||
id: 113,
|
||||
@ -107,23 +111,21 @@ describe('ContributionMessagesListItem', () => {
|
||||
})
|
||||
|
||||
it('has a DIV .text-left.is-not-moderator', () => {
|
||||
expect(wrapper.find('div.text-left.is-not-moderator').exists()).toBe(true)
|
||||
expect(wrapper.find('div.text-left.is-user').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('has the complete user name', () => {
|
||||
expect(wrapper.find('div.is-not-moderator.text-left > span:nth-child(2)').text()).toBe(
|
||||
'Bibi Bloxberg',
|
||||
)
|
||||
expect(wrapper.find('[data-test="user-name"]').text()).toBe('Bibi Bloxberg')
|
||||
})
|
||||
|
||||
it('has the message creation date', () => {
|
||||
expect(wrapper.find('div.is-not-moderator.text-left > span:nth-child(3)').text()).toMatch(
|
||||
expect(wrapper.find('[data-test="user-date"]').text()).toMatch(
|
||||
'Mon Aug 29 2022 12:25:34 GMT+0000',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the message', () => {
|
||||
expect(wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)').text()).toBe(
|
||||
expect(wrapper.find('[data-test="user-message"]').text()).toBe(
|
||||
'Asda sdad ad asdasd, das Ass das Das.',
|
||||
)
|
||||
})
|
||||
@ -132,6 +134,7 @@ describe('ContributionMessagesListItem', () => {
|
||||
|
||||
describe('links in contribtion message', () => {
|
||||
const propsData = {
|
||||
contributionUserId: 108,
|
||||
message: {
|
||||
id: 111,
|
||||
message: 'Lorem ipsum?',
|
||||
@ -159,7 +162,7 @@ describe('ContributionMessagesListItem', () => {
|
||||
beforeEach(() => {
|
||||
propsData.message.message = 'https://gradido.net/de/'
|
||||
wrapper = ModeratorItemWrapper()
|
||||
messageField = wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)')
|
||||
messageField = wrapper.find('[data-test="moderator-message"]')
|
||||
})
|
||||
|
||||
it('contains the link as text', () => {
|
||||
@ -176,7 +179,7 @@ describe('ContributionMessagesListItem', () => {
|
||||
propsData.message.message = `Here you find all you need to know about Gradido: https://gradido.net/de/
|
||||
and here is the link to the repository: https://github.com/gradido/gradido`
|
||||
wrapper = ModeratorItemWrapper()
|
||||
messageField = wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)')
|
||||
messageField = wrapper.find('[data-test="moderator-message"]')
|
||||
})
|
||||
|
||||
it('contains the whole text', () => {
|
||||
@ -196,6 +199,7 @@ and here is the link to the repository: https://github.com/gradido/gradido`)
|
||||
|
||||
describe('contribution message type HISTORY', () => {
|
||||
const propsData = {
|
||||
contributionUserId: 108,
|
||||
message: {
|
||||
id: 111,
|
||||
message: `Sun Nov 13 2022 13:05:48 GMT+0100 (Central European Standard Time)
|
||||
@ -227,7 +231,7 @@ This message also contains a link: https://gradido.net/de/
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
wrapper = itemWrapper()
|
||||
messageField = wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)')
|
||||
messageField = wrapper
|
||||
})
|
||||
|
||||
it('renders the date', () => {
|
||||
|
||||
@ -1,17 +1,37 @@
|
||||
<template>
|
||||
<div class="contribution-messages-list-item">
|
||||
<div v-if="message.isModerator" class="text-right is-moderator">
|
||||
<div v-if="isModeratorMessage" class="text-right p-2 rounded-sm mb-3" :class="boxClass">
|
||||
<small class="ml-4" data-test="moderator-label">
|
||||
{{ $t('moderator.moderator') }}
|
||||
</small>
|
||||
<small class="ml-2" data-test="moderator-date">
|
||||
{{ $d(new Date(message.createdAt), 'short') }}
|
||||
</small>
|
||||
<span class="ml-2 mr-2" data-test="moderator-name">
|
||||
{{ message.userFirstName }} {{ message.userLastName }}
|
||||
</span>
|
||||
<b-avatar square variant="warning"></b-avatar>
|
||||
<span class="ml-2 mr-2">{{ message.userFirstName }} {{ message.userLastName }}</span>
|
||||
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
|
||||
<small class="ml-4 text-success">{{ $t('moderator') }}</small>
|
||||
<parse-message v-bind="message"></parse-message>
|
||||
|
||||
<parse-message v-bind="message" data-test="moderator-message"></parse-message>
|
||||
<small v-if="isModeratorHiddenMessage">
|
||||
<hr />
|
||||
{{ $t('moderator.request') }}
|
||||
</small>
|
||||
</div>
|
||||
<div v-else class="text-left is-not-moderator">
|
||||
<div v-else class="text-left p-2 rounded-sm mb-3" :class="boxClass">
|
||||
<b-avatar variant="info"></b-avatar>
|
||||
<span class="ml-2 mr-2">{{ message.userFirstName }} {{ message.userLastName }}</span>
|
||||
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
|
||||
<parse-message v-bind="message"></parse-message>
|
||||
<span class="ml-2 mr-2" data-test="user-name">
|
||||
{{ message.userFirstName }} {{ message.userLastName }}
|
||||
</span>
|
||||
<small class="ml-2" data-test="user-date">
|
||||
{{ $d(new Date(message.createdAt), 'short') }}
|
||||
</small>
|
||||
<small v-if="isHistory">
|
||||
<hr />
|
||||
{{ $t('moderator.history') }}
|
||||
<hr />
|
||||
</small>
|
||||
<parse-message v-bind="message" data-test="user-message"></parse-message>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -28,22 +48,50 @@ export default {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
contributionUserId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isModeratorMessage() {
|
||||
return this.contributionUserId !== this.message.userId
|
||||
},
|
||||
isModeratorHiddenMessage() {
|
||||
return this.message.type === 'MODERATOR'
|
||||
},
|
||||
isHistory() {
|
||||
return this.message.type === 'HISTORY'
|
||||
},
|
||||
boxClass() {
|
||||
if (this.isModeratorHiddenMessage) return 'is-moderator is-moderator-hidden-message'
|
||||
if (this.isHistory) return 'is-user is-user-history-message'
|
||||
if (this.isModeratorMessage) return 'is-moderator is-moderator-message'
|
||||
return 'is-user is-user-message'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.is-not-moderator {
|
||||
clear: both;
|
||||
width: 75%;
|
||||
margin-top: 20px;
|
||||
/* background-color: rgb(261, 204, 221); */
|
||||
}
|
||||
.is-moderator {
|
||||
clear: both;
|
||||
float: right;
|
||||
width: 75%;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
/* background-color: rgb(255, 255, 128); */
|
||||
}
|
||||
.is-moderator-message {
|
||||
background-color: rgb(228, 237, 245);
|
||||
}
|
||||
.is-moderator-hidden-message {
|
||||
background-color: rgb(217, 161, 228);
|
||||
}
|
||||
.is-user {
|
||||
clear: both;
|
||||
width: 75%;
|
||||
}
|
||||
.is-user-message {
|
||||
background-color: rgb(236, 235, 213);
|
||||
}
|
||||
.is-user-history-message {
|
||||
background-color: rgb(235, 226, 57);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -28,7 +28,7 @@ const defaultData = () => {
|
||||
memo: 'Danke für alles',
|
||||
date: new Date(),
|
||||
moderator: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -39,6 +39,7 @@ const defaultData = () => {
|
||||
deletedBy: null,
|
||||
deletedAt: null,
|
||||
createdAt: new Date(),
|
||||
moderatorId: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@ -50,7 +51,7 @@ const defaultData = () => {
|
||||
memo: 'Gut Ergattert',
|
||||
date: new Date(),
|
||||
moderator: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -61,6 +62,7 @@ const defaultData = () => {
|
||||
deletedBy: null,
|
||||
deletedAt: null,
|
||||
createdAt: new Date(),
|
||||
moderatorId: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -34,8 +34,8 @@
|
||||
{{ $t('help.transactionlist.confirmed') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('transactionlist.state') }} {{ $t('math.equals') }}
|
||||
{{ $t('help.transactionlist.state') }}
|
||||
{{ $t('transactionlist.status') }} {{ $t('math.equals') }}
|
||||
{{ $t('help.transactionlist.status') }}
|
||||
</div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
@ -78,8 +78,8 @@ export default {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'state',
|
||||
label: this.$t('transactionlist.state'),
|
||||
key: 'status',
|
||||
label: this.$t('transactionlist.status'),
|
||||
},
|
||||
{
|
||||
key: 'amount',
|
||||
|
||||
@ -131,13 +131,13 @@ describe('OpenCreationsTable', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('call updateState', () => {
|
||||
describe('call updateStatus', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.updateState(4)
|
||||
wrapper.vm.updateStatus(4)
|
||||
})
|
||||
|
||||
it('emits update-state', () => {
|
||||
expect(wrapper.vm.$root.$emit('update-state', 4)).toBeTruthy()
|
||||
it('emits update-status', () => {
|
||||
expect(wrapper.vm.$root.$emit('update-status', 4)).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
stacked="md"
|
||||
:tbody-tr-class="rowClass"
|
||||
>
|
||||
<template #cell(state)="row">
|
||||
<b-icon :icon="getStatusIcon(row.item.state)"></b-icon>
|
||||
<template #cell(status)="row">
|
||||
<b-icon :icon="getStatusIcon(row.item.status)"></b-icon>
|
||||
</template>
|
||||
<template #cell(bookmark)="row">
|
||||
<div v-if="!myself(row.item)">
|
||||
@ -39,12 +39,12 @@
|
||||
<b-button v-else @click="rowToggleDetails(row, 0)">
|
||||
<b-icon icon="chat-dots"></b-icon>
|
||||
<b-icon
|
||||
v-if="row.item.state === 'PENDING' && row.item.messagesCount > 0"
|
||||
v-if="row.item.status === 'PENDING' && row.item.messagesCount > 0"
|
||||
icon="exclamation-circle-fill"
|
||||
variant="warning"
|
||||
></b-icon>
|
||||
<b-icon
|
||||
v-if="row.item.state === 'IN_PROGRESS' && row.item.messagesCount > 0"
|
||||
v-if="row.item.status === 'IN_PROGRESS' && row.item.messagesCount > 0"
|
||||
icon="question-diamond"
|
||||
variant="warning"
|
||||
class="pl-1"
|
||||
@ -102,8 +102,9 @@
|
||||
<div v-else>
|
||||
<contribution-messages-list
|
||||
:contributionId="row.item.id"
|
||||
:contributionState="row.item.state"
|
||||
@update-state="updateState"
|
||||
:contributionStatus="row.item.status"
|
||||
:contributionUserId="row.item.userId"
|
||||
@update-status="updateStatus"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@ -154,15 +155,21 @@ export default {
|
||||
},
|
||||
rowClass(item, type) {
|
||||
if (!item || type !== 'row') return
|
||||
if (item.state === 'CONFIRMED') return 'table-success'
|
||||
if (item.state === 'DENIED') return 'table-warning'
|
||||
if (item.state === 'DELETED') return 'table-danger'
|
||||
if (item.state === 'IN_PROGRESS') return 'table-primary'
|
||||
if (item.state === 'PENDING') return 'table-primary'
|
||||
if (item.status === 'CONFIRMED') return 'table-success'
|
||||
if (item.status === 'DENIED') return 'table-warning'
|
||||
if (item.status === 'DELETED') return 'table-danger'
|
||||
if (item.status === 'IN_PROGRESS') return 'table-primary'
|
||||
if (item.status === 'PENDING') return 'table-primary'
|
||||
},
|
||||
updateState(id) {
|
||||
this.$emit('update-state', id)
|
||||
updateStatus(id) {
|
||||
this.$emit('update-status', id)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.btn-warning {
|
||||
background-color: #e1a908;
|
||||
border-color: #e1a908;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const adminCreateContributionMessage = gql`
|
||||
mutation ($contributionId: Int!, $message: String!) {
|
||||
adminCreateContributionMessage(contributionId: $contributionId, message: $message) {
|
||||
mutation ($contributionId: Int!, $message: String!, $messageType: ContributionMessageType) {
|
||||
adminCreateContributionMessage(
|
||||
contributionId: $contributionId
|
||||
message: $message
|
||||
messageType: $messageType
|
||||
) {
|
||||
id
|
||||
message
|
||||
createdAt
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const listContributionMessages = gql`
|
||||
export const adminListContributionMessages = gql`
|
||||
query ($contributionId: Int!, $pageSize: Int = 25, $currentPage: Int = 1, $order: Order = ASC) {
|
||||
listContributionMessages(
|
||||
adminListContributionMessages(
|
||||
contributionId: $contributionId
|
||||
pageSize: $pageSize
|
||||
currentPage: $currentPage
|
||||
@ -26,7 +26,7 @@ export const adminListContributions = gql`
|
||||
contributionDate
|
||||
confirmedAt
|
||||
confirmedBy
|
||||
state
|
||||
status
|
||||
messagesCount
|
||||
deniedAt
|
||||
deniedBy
|
||||
|
||||
@ -94,7 +94,7 @@
|
||||
"transactionlist": {
|
||||
"confirmed": "Wann wurde es von einem Moderator / Admin bestätigt.",
|
||||
"periods": "Für welchen Zeitraum wurde vom Mitglied eingereicht.",
|
||||
"state": "[PENDING = eingereicht, DELETED = gelöscht, IN_PROGRESS = im Dialog mit Moderator, DENIED = abgelehnt, CONFIRMED = bestätigt]",
|
||||
"status": "[PENDING = eingereicht, DELETED = gelöscht, IN_PROGRESS = im Dialog mit Moderator, DENIED = abgelehnt, CONFIRMED = bestätigt]",
|
||||
"submitted": "Wann wurde es vom Mitglied eingereicht"
|
||||
}
|
||||
},
|
||||
@ -108,7 +108,12 @@
|
||||
"message": {
|
||||
"request": "Die Anfrage wurde gesendet."
|
||||
},
|
||||
"moderator": "Moderator",
|
||||
"moderator": {
|
||||
"history": "Die Daten wurden geändert. Dies sind die alten Daten.",
|
||||
"moderator": "Moderator",
|
||||
"notice": "Moderator Notiz",
|
||||
"request": "Diese Nachricht ist nur für die Moderatoren sichtbar!"
|
||||
},
|
||||
"name": "Name",
|
||||
"navbar": {
|
||||
"automaticContributions": "Automatische Beiträge",
|
||||
@ -184,7 +189,7 @@
|
||||
"confirmed": "Bestätigt",
|
||||
"memo": "Nachricht",
|
||||
"period": "Zeitraum",
|
||||
"state": "Status",
|
||||
"status": "Status",
|
||||
"submitted": "Eingereicht",
|
||||
"title": "Alle geschöpften Transaktionen für den Nutzer"
|
||||
},
|
||||
|
||||
@ -94,7 +94,7 @@
|
||||
"transactionlist": {
|
||||
"confirmed": "When was it confirmed by a moderator / admin.",
|
||||
"periods": "For what period was it submitted by the member.",
|
||||
"state": "[PENDING = submitted, DELETED = deleted, IN_PROGRESS = in dialogue with moderator, DENIED = rejected, CONFIRMED = confirmed]",
|
||||
"status": "[PENDING = submitted, DELETED = deleted, IN_PROGRESS = in dialogue with moderator, DENIED = rejected, CONFIRMED = confirmed]",
|
||||
"submitted": "When was it submitted by the member"
|
||||
}
|
||||
},
|
||||
@ -108,7 +108,12 @@
|
||||
"message": {
|
||||
"request": "Request has been sent."
|
||||
},
|
||||
"moderator": "Moderator",
|
||||
"moderator": {
|
||||
"history": "The data has been changed. This is the old data.",
|
||||
"moderator": "Moderator",
|
||||
"notice": "Moderator note",
|
||||
"request": "This message is only visible to the moderators!"
|
||||
},
|
||||
"name": "Name",
|
||||
"navbar": {
|
||||
"automaticContributions": "Automatic Contributions",
|
||||
@ -184,7 +189,7 @@
|
||||
"confirmed": "Confirmed",
|
||||
"memo": "Message",
|
||||
"period": "Period",
|
||||
"state": "State",
|
||||
"status": "State",
|
||||
"submitted": "Submitted",
|
||||
"title": "All creation-transactions for the user"
|
||||
},
|
||||
|
||||
@ -51,7 +51,7 @@ const defaultData = () => {
|
||||
memo: 'Danke für alles',
|
||||
date: new Date(),
|
||||
moderator: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -73,7 +73,7 @@ const defaultData = () => {
|
||||
memo: 'Gut Ergattert',
|
||||
date: new Date(),
|
||||
moderator: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -451,12 +451,12 @@ describe('CreationConfirm', () => {
|
||||
|
||||
describe('update status', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findComponent({ name: 'OpenCreationsTable' }).vm.$emit('update-state', 2)
|
||||
await wrapper.findComponent({ name: 'OpenCreationsTable' }).vm.$emit('update-status', 2)
|
||||
})
|
||||
|
||||
it('updates the status', () => {
|
||||
expect(wrapper.vm.items.find((obj) => obj.id === 2).messagesCount).toBe(1)
|
||||
expect(wrapper.vm.items.find((obj) => obj.id === 2).state).toBe('IN_PROGRESS')
|
||||
expect(wrapper.vm.items.find((obj) => obj.id === 2).status).toBe('IN_PROGRESS')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
:items="items"
|
||||
:fields="fields"
|
||||
@show-overlay="showOverlay"
|
||||
@update-state="updateStatus"
|
||||
@update-status="updateStatus"
|
||||
@update-contributions="$apollo.queries.ListAllContributions.refetch()"
|
||||
/>
|
||||
|
||||
@ -187,7 +187,7 @@ export default {
|
||||
},
|
||||
updateStatus(id) {
|
||||
this.items.find((obj) => obj.id === id).messagesCount++
|
||||
this.items.find((obj) => obj.id === id).state = 'IN_PROGRESS'
|
||||
this.items.find((obj) => obj.id === id).status = 'IN_PROGRESS'
|
||||
},
|
||||
formatDateOrDash(value) {
|
||||
return value ? this.$d(new Date(value), 'short') : '—'
|
||||
@ -217,7 +217,7 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'moderatorId', label: this.$t('moderator') },
|
||||
{ key: 'moderatorId', label: this.$t('moderator.moderator') },
|
||||
{ key: 'editCreation', label: this.$t('chat') },
|
||||
{ key: 'confirm', label: this.$t('save') },
|
||||
],
|
||||
@ -254,7 +254,7 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'confirmedBy', label: this.$t('moderator') },
|
||||
{ key: 'confirmedBy', label: this.$t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
[
|
||||
@ -290,7 +290,7 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'deniedBy', label: this.$t('moderator') },
|
||||
{ key: 'deniedBy', label: this.$t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
[
|
||||
@ -326,12 +326,12 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'deletedBy', label: this.$t('moderator') },
|
||||
{ key: 'deletedBy', label: this.$t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
[
|
||||
// all contributions
|
||||
{ key: 'state', label: this.$t('status') },
|
||||
{ key: 'status', label: this.$t('status') },
|
||||
{ key: 'firstName', label: this.$t('firstname') },
|
||||
{ key: 'lastName', label: this.$t('lastname') },
|
||||
{
|
||||
@ -363,7 +363,7 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'confirmedBy', label: this.$t('moderator') },
|
||||
{ key: 'confirmedBy', label: this.$t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
][this.tabIndex]
|
||||
|
||||
@ -43,7 +43,7 @@ const defaultData = () => {
|
||||
memo: 'Danke für alles',
|
||||
date: new Date(),
|
||||
moderatorId: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -65,7 +65,7 @@ const defaultData = () => {
|
||||
memo: 'Gut Ergattert',
|
||||
date: new Date(),
|
||||
moderatorId: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
|
||||
@ -40,7 +40,7 @@ module.exports = {
|
||||
],
|
||||
// import
|
||||
'import/export': 'error',
|
||||
'import/no-deprecated': 'error',
|
||||
// 'import/no-deprecated': 'error',
|
||||
'import/no-empty-named-blocks': 'error',
|
||||
'import/no-extraneous-dependencies': 'error',
|
||||
'import/no-mutable-exports': 'error',
|
||||
@ -197,6 +197,9 @@ module.exports = {
|
||||
{
|
||||
files: ['*.test.ts'],
|
||||
plugins: ['jest'],
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'jest/no-disabled-tests': 'error',
|
||||
'jest/no-focused-tests': 'error',
|
||||
|
||||
@ -53,4 +53,5 @@ export enum RIGHTS {
|
||||
ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',
|
||||
DENY_CONTRIBUTION = 'DENY_CONTRIBUTION',
|
||||
ADMIN_OPEN_CREATIONS = 'ADMIN_OPEN_CREATIONS',
|
||||
ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES = 'ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES',
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { ArgsType, Field, Int, InputType } from 'type-graphql'
|
||||
|
||||
import { ContributionMessageType } from '@enum/ContributionMessageType'
|
||||
|
||||
@InputType()
|
||||
@ArgsType()
|
||||
export class ContributionMessageArgs {
|
||||
@ -8,4 +10,7 @@ export class ContributionMessageArgs {
|
||||
|
||||
@Field(() => String)
|
||||
message: string
|
||||
|
||||
@Field(() => ContributionMessageType, { defaultValue: ContributionMessageType.DIALOG })
|
||||
messageType: ContributionMessageType
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import { registerEnumType } from 'type-graphql'
|
||||
export enum ContributionMessageType {
|
||||
HISTORY = 'HISTORY',
|
||||
DIALOG = 'DIALOG',
|
||||
MODERATOR = 'MODERATOR', // messages for moderator communication, can only be seen by moderators
|
||||
}
|
||||
|
||||
registerEnumType(ContributionMessageType, {
|
||||
@ -15,7 +15,7 @@ export class Contribution {
|
||||
this.confirmedAt = contribution.confirmedAt
|
||||
this.confirmedBy = contribution.confirmedBy
|
||||
this.contributionDate = contribution.contributionDate
|
||||
this.state = contribution.contributionStatus
|
||||
this.status = contribution.contributionStatus
|
||||
this.messagesCount = contribution.messages ? contribution.messages.length : 0
|
||||
this.deniedAt = contribution.deniedAt
|
||||
this.deniedBy = contribution.deniedBy
|
||||
@ -68,7 +68,7 @@ export class Contribution {
|
||||
messagesCount: number
|
||||
|
||||
@Field(() => String)
|
||||
state: string
|
||||
status: string
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
moderatorId: number | null
|
||||
|
||||
@ -1,25 +1,39 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field, Float, Int } from 'type-graphql'
|
||||
|
||||
import { GdtEntryType } from '@enum/GdtEntryType'
|
||||
|
||||
@ObjectType()
|
||||
export class GdtEntry {
|
||||
constructor(json: any) {
|
||||
this.id = json.id
|
||||
this.amount = json.amount
|
||||
this.date = json.date
|
||||
this.email = json.email
|
||||
this.comment = json.comment
|
||||
this.couponCode = json.coupon_code
|
||||
this.gdtEntryType = json.gdt_entry_type_id
|
||||
this.factor = json.factor
|
||||
this.amount2 = json.amount2
|
||||
this.factor2 = json.factor2
|
||||
this.gdt = json.gdt
|
||||
constructor({
|
||||
id,
|
||||
amount,
|
||||
date,
|
||||
email,
|
||||
comment,
|
||||
// eslint-disable-next-line camelcase
|
||||
coupon_code,
|
||||
// eslint-disable-next-line camelcase
|
||||
gdt_entry_type_id,
|
||||
factor,
|
||||
amount2,
|
||||
factor2,
|
||||
gdt,
|
||||
}: any) {
|
||||
this.id = id
|
||||
this.amount = amount
|
||||
this.date = date
|
||||
this.email = email
|
||||
this.comment = comment
|
||||
// eslint-disable-next-line camelcase
|
||||
this.couponCode = coupon_code
|
||||
// eslint-disable-next-line camelcase
|
||||
this.gdtEntryType = gdt_entry_type_id
|
||||
this.factor = factor
|
||||
this.amount2 = amount2
|
||||
this.factor2 = factor2
|
||||
this.gdt = gdt
|
||||
}
|
||||
|
||||
@Field(() => Int)
|
||||
|
||||
@ -1,24 +1,19 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field, Int, Float } from 'type-graphql'
|
||||
|
||||
import { GdtEntry } from './GdtEntry'
|
||||
|
||||
@ObjectType()
|
||||
export class GdtEntryList {
|
||||
constructor(json: any) {
|
||||
this.state = json.state
|
||||
this.count = json.count
|
||||
this.gdtEntries = json.gdtEntries ? json.gdtEntries.map((json: any) => new GdtEntry(json)) : []
|
||||
this.gdtSum = json.gdtSum
|
||||
this.timeUsed = json.timeUsed
|
||||
constructor(status = '', count = 0, gdtEntries = [], gdtSum = 0, timeUsed = 0) {
|
||||
this.status = status
|
||||
this.count = count
|
||||
this.gdtEntries = gdtEntries
|
||||
this.gdtSum = gdtSum
|
||||
this.timeUsed = timeUsed
|
||||
}
|
||||
|
||||
@Field(() => String)
|
||||
state: string
|
||||
status: string
|
||||
|
||||
@Field(() => Int)
|
||||
count: number
|
||||
|
||||
@ -16,7 +16,7 @@ export class UnconfirmedContribution {
|
||||
this.email = user ? user.emailContact.email : ''
|
||||
this.moderator = contribution.moderatorId
|
||||
this.creation = creations
|
||||
this.state = contribution.contributionStatus
|
||||
this.status = contribution.contributionStatus
|
||||
this.messageCount = contribution.messages ? contribution.messages.length : 0
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ export class UnconfirmedContribution {
|
||||
creation: Decimal[]
|
||||
|
||||
@Field(() => String)
|
||||
state: string
|
||||
status: string
|
||||
|
||||
@Field(() => Int)
|
||||
messageCount: number
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { getCustomRepository } from '@dbTools/typeorm'
|
||||
import { IsNull } from '@dbTools/typeorm'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import { Resolver, Query, Ctx, Authorized } from 'type-graphql'
|
||||
|
||||
import { Balance } from '@model/Balance'
|
||||
import { TransactionLinkRepository } from '@repository/TransactionLink'
|
||||
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import { Context, getUser } from '@/server/context'
|
||||
@ -15,6 +14,7 @@ import { calculateDecay } from '@/util/decay'
|
||||
|
||||
import { GdtResolver } from './GdtResolver'
|
||||
import { getLastTransaction } from './util/getLastTransaction'
|
||||
import { transactionLinkSummary } from './util/transactionLinkSummary'
|
||||
|
||||
@Resolver()
|
||||
export class BalanceResolver {
|
||||
@ -57,7 +57,7 @@ export class BalanceResolver {
|
||||
const linkCount = await dbTransactionLink.count({
|
||||
where: {
|
||||
userId: user.id,
|
||||
redeemedAt: null,
|
||||
redeemedAt: IsNull(),
|
||||
// validUntil: MoreThan(new Date()),
|
||||
},
|
||||
})
|
||||
@ -77,10 +77,9 @@ export class BalanceResolver {
|
||||
)
|
||||
|
||||
// The final balance is reduced by the link amount withheld
|
||||
const transactionLinkRepository = getCustomRepository(TransactionLinkRepository)
|
||||
const { sumHoldAvailableAmount } = context.sumHoldAvailableAmount
|
||||
? { sumHoldAvailableAmount: context.sumHoldAvailableAmount }
|
||||
: await transactionLinkRepository.summary(user.id, now)
|
||||
: await transactionLinkSummary(user.id, now)
|
||||
|
||||
logger.debug(`context.sumHoldAvailableAmount=${context.sumHoldAvailableAmount}`)
|
||||
logger.debug(`sumHoldAvailableAmount=${sumHoldAvailableAmount}`)
|
||||
|
||||
@ -542,7 +542,7 @@ describe('Contribution Links', () => {
|
||||
})
|
||||
|
||||
it('updated the DB record', async () => {
|
||||
await expect(DbContributionLink.findOne(linkId)).resolves.toEqual(
|
||||
await expect(DbContributionLink.findOne({ where: { id: linkId } })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: linkId,
|
||||
name: 'Dokumenta 2023',
|
||||
|
||||
@ -103,7 +103,7 @@ export class ContributionLinkResolver {
|
||||
@Arg('id', () => Int) id: number,
|
||||
@Ctx() context: Context,
|
||||
): Promise<boolean> {
|
||||
const dbContributionLink = await DbContributionLink.findOne(id)
|
||||
const dbContributionLink = await DbContributionLink.findOne({ where: { id } })
|
||||
if (!dbContributionLink) {
|
||||
throw new LogError('Contribution Link not found', id)
|
||||
}
|
||||
@ -130,7 +130,7 @@ export class ContributionLinkResolver {
|
||||
@Arg('id', () => Int) id: number,
|
||||
@Ctx() context: Context,
|
||||
): Promise<ContributionLink> {
|
||||
const dbContributionLink = await DbContributionLink.findOne(id)
|
||||
const dbContributionLink = await DbContributionLink.findOne({ where: { id } })
|
||||
if (!dbContributionLink) {
|
||||
throw new LogError('Contribution Link not found', id)
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ import {
|
||||
createContributionMessage,
|
||||
login,
|
||||
} from '@/seeds/graphql/mutations'
|
||||
import { listContributionMessages } from '@/seeds/graphql/queries'
|
||||
import { listContributionMessages, adminListContributionMessages } from '@/seeds/graphql/queries'
|
||||
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||
|
||||
@ -217,6 +217,33 @@ describe('ContributionMessageResolver', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('contribution message type MODERATOR', () => {
|
||||
it('creates ContributionMessage', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminCreateContributionMessage,
|
||||
variables: {
|
||||
contributionId: result.data.createContribution.id,
|
||||
message: 'Internal moderator communication',
|
||||
messageType: 'MODERATOR',
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
adminCreateContributionMessage: expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
message: 'Internal moderator communication',
|
||||
type: 'MODERATOR',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
}),
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -385,7 +412,7 @@ describe('ContributionMessageResolver', () => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
it('returns a list of contributionmessages', async () => {
|
||||
it('returns a list of contributionmessages without type MODERATOR', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: listContributionMessages,
|
||||
@ -419,4 +446,96 @@ describe('ContributionMessageResolver', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('adminListContributionMessages', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminListContributionMessages,
|
||||
variables: { contributionId: 1 },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated as user', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
it('returns an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminListContributionMessages,
|
||||
variables: { contributionId: 1 },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated as admin', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
it('returns a list of contributionmessages with type MODERATOR', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminListContributionMessages,
|
||||
variables: { contributionId: result.data.createContribution.id },
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
adminListContributionMessages: {
|
||||
count: 3,
|
||||
messages: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
message: 'Admin Test',
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
message: 'User Test',
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Bibi',
|
||||
userLastName: 'Bloxberg',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
message: 'Internal moderator communication',
|
||||
type: 'MODERATOR',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -8,8 +8,8 @@ import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type
|
||||
|
||||
import { ContributionMessageArgs } from '@arg/ContributionMessageArgs'
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { ContributionMessageType } from '@enum/ContributionMessageType'
|
||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||
import { ContributionMessageType } from '@enum/MessageType'
|
||||
import { Order } from '@enum/Order'
|
||||
import { ContributionMessage, ContributionMessageListResult } from '@model/ContributionMessage'
|
||||
|
||||
@ -22,6 +22,8 @@ import {
|
||||
import { Context, getUser } from '@/server/context'
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
import { findContributionMessages } from './util/findContributionMessages'
|
||||
|
||||
@Resolver()
|
||||
export class ContributionMessageResolver {
|
||||
@Authorized([RIGHTS.CREATE_CONTRIBUTION_MESSAGE])
|
||||
@ -36,7 +38,7 @@ export class ContributionMessageResolver {
|
||||
await queryRunner.startTransaction('REPEATABLE READ')
|
||||
const contributionMessage = DbContributionMessage.create()
|
||||
try {
|
||||
const contribution = await DbContribution.findOne({ id: contributionId })
|
||||
const contribution = await DbContribution.findOne({ where: { id: contributionId } })
|
||||
if (!contribution) {
|
||||
throw new LogError('Contribution not found', contributionId)
|
||||
}
|
||||
@ -82,16 +84,35 @@ export class ContributionMessageResolver {
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
): Promise<ContributionMessageListResult> {
|
||||
const [contributionMessages, count] = await getConnection()
|
||||
.createQueryBuilder()
|
||||
.select('cm')
|
||||
.from(DbContributionMessage, 'cm')
|
||||
.leftJoinAndSelect('cm.user', 'u')
|
||||
.where({ contributionId })
|
||||
.orderBy('cm.createdAt', order)
|
||||
.limit(pageSize)
|
||||
.offset((currentPage - 1) * pageSize)
|
||||
.getManyAndCount()
|
||||
const [contributionMessages, count] = await findContributionMessages({
|
||||
contributionId,
|
||||
currentPage,
|
||||
pageSize,
|
||||
order,
|
||||
})
|
||||
|
||||
return {
|
||||
count,
|
||||
messages: contributionMessages.map(
|
||||
(message) => new ContributionMessage(message, message.user),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES])
|
||||
@Query(() => ContributionMessageListResult)
|
||||
async adminListContributionMessages(
|
||||
@Arg('contributionId', () => Int) contributionId: number,
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
): Promise<ContributionMessageListResult> {
|
||||
const [contributionMessages, count] = await findContributionMessages({
|
||||
contributionId,
|
||||
currentPage,
|
||||
pageSize,
|
||||
order,
|
||||
showModeratorType: true,
|
||||
})
|
||||
|
||||
return {
|
||||
count,
|
||||
@ -104,7 +125,7 @@ export class ContributionMessageResolver {
|
||||
@Authorized([RIGHTS.ADMIN_CREATE_CONTRIBUTION_MESSAGE])
|
||||
@Mutation(() => ContributionMessage)
|
||||
async adminCreateContributionMessage(
|
||||
@Args() { contributionId, message }: ContributionMessageArgs,
|
||||
@Args() { contributionId, message, messageType }: ContributionMessageArgs,
|
||||
@Ctx() context: Context,
|
||||
): Promise<ContributionMessage> {
|
||||
const moderator = getUser(context)
|
||||
@ -124,7 +145,7 @@ export class ContributionMessageResolver {
|
||||
if (contribution.userId === moderator.id) {
|
||||
throw new LogError('Admin can not answer on his own contribution', contributionId)
|
||||
}
|
||||
if (!contribution.user.emailContact) {
|
||||
if (!contribution.user.emailContact && contribution.user.emailId) {
|
||||
contribution.user.emailContact = await DbUserContact.findOneOrFail({
|
||||
where: { id: contribution.user.emailId },
|
||||
})
|
||||
@ -133,7 +154,7 @@ export class ContributionMessageResolver {
|
||||
contributionMessage.createdAt = new Date()
|
||||
contributionMessage.message = message
|
||||
contributionMessage.userId = moderator.id
|
||||
contributionMessage.type = ContributionMessageType.DIALOG
|
||||
contributionMessage.type = messageType
|
||||
contributionMessage.isModerator = true
|
||||
await queryRunner.manager.insert(DbContributionMessage, contributionMessage)
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Connection } from '@dbTools/typeorm'
|
||||
import { Connection, Equal } from '@dbTools/typeorm'
|
||||
import { Contribution } from '@entity/Contribution'
|
||||
import { Event as DbEvent } from '@entity/Event'
|
||||
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||
@ -457,7 +457,7 @@ describe('ContributionResolver', () => {
|
||||
describe('contribution has wrong status', () => {
|
||||
beforeAll(async () => {
|
||||
const contribution = await Contribution.findOneOrFail({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
where: { id: pendingContribution.data.createContribution.id },
|
||||
})
|
||||
contribution.contributionStatus = ContributionStatus.DELETED
|
||||
await contribution.save()
|
||||
@ -469,7 +469,7 @@ describe('ContributionResolver', () => {
|
||||
|
||||
afterAll(async () => {
|
||||
const contribution = await Contribution.findOneOrFail({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
where: { id: pendingContribution.data.createContribution.id },
|
||||
})
|
||||
contribution.contributionStatus = ContributionStatus.PENDING
|
||||
await contribution.save()
|
||||
@ -1092,29 +1092,29 @@ describe('ContributionResolver', () => {
|
||||
contributionCount: 4,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
memo: 'Test PENDING contribution update',
|
||||
amount: '10',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: contributionToDeny.data.createContribution.id,
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Test contribution to deny',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: contributionToDelete.data.createContribution.id,
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
memo: 'Test contribution to delete',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: inProgressContribution.data.createContribution.id,
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
memo: 'Test IN_PROGRESS contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
@ -1223,47 +1223,47 @@ describe('ContributionResolver', () => {
|
||||
contributionCount: 7,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: '100',
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
id: contributionToConfirm.data.createContribution.id,
|
||||
memo: 'Test contribution to confirm',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
memo: 'Test PENDING contribution update',
|
||||
amount: '10',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: contributionToDeny.data.createContribution.id,
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Test contribution to deny',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: inProgressContribution.data.createContribution.id,
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
memo: 'Test IN_PROGRESS contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: bibiCreatedContribution.id,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
@ -1288,47 +1288,47 @@ describe('ContributionResolver', () => {
|
||||
contributionCount: 7,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: '100',
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
id: contributionToConfirm.data.createContribution.id,
|
||||
memo: 'Test contribution to confirm',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
memo: 'Test PENDING contribution update',
|
||||
amount: '10',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: contributionToDeny.data.createContribution.id,
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Test contribution to deny',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: inProgressContribution.data.createContribution.id,
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
memo: 'Test IN_PROGRESS contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: bibiCreatedContribution.id,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
@ -1353,47 +1353,47 @@ describe('ContributionResolver', () => {
|
||||
contributionCount: 7,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: '100',
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
id: contributionToConfirm.data.createContribution.id,
|
||||
memo: 'Test contribution to confirm',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
memo: 'Test PENDING contribution update',
|
||||
amount: '10',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: contributionToDeny.data.createContribution.id,
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Test contribution to deny',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: inProgressContribution.data.createContribution.id,
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
memo: 'Test IN_PROGRESS contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: bibiCreatedContribution.id,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
@ -1419,33 +1419,33 @@ describe('ContributionResolver', () => {
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: '100',
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
id: contributionToConfirm.data.createContribution.id,
|
||||
memo: 'Test contribution to confirm',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: bibiCreatedContribution.id,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
@ -1468,20 +1468,20 @@ describe('ContributionResolver', () => {
|
||||
contributionCount: 1,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
memo: 'Test PENDING contribution update',
|
||||
amount: '10',
|
||||
}),
|
||||
@ -1506,20 +1506,20 @@ describe('ContributionResolver', () => {
|
||||
contributionCount: 1,
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: inProgressContribution.data.createContribution.id,
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
memo: 'Test IN_PROGRESS contribution',
|
||||
amount: '100',
|
||||
}),
|
||||
@ -1545,27 +1545,27 @@ describe('ContributionResolver', () => {
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: contributionToDeny.data.createContribution.id,
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Test contribution to deny',
|
||||
amount: '100',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
@ -1608,36 +1608,36 @@ describe('ContributionResolver', () => {
|
||||
contributionList: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: '100',
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
id: contributionToConfirm.data.createContribution.id,
|
||||
memo: 'Test contribution to confirm',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
memo: 'Test PENDING contribution update',
|
||||
amount: '10',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: bibiCreatedContribution.id,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '1000',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
memo: 'Whatever contribution',
|
||||
amount: '166',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
@ -1828,7 +1828,7 @@ describe('ContributionResolver', () => {
|
||||
creation = await Contribution.findOneOrFail({
|
||||
where: {
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: 400,
|
||||
amount: Equal(new Decimal('400')),
|
||||
},
|
||||
})
|
||||
})
|
||||
@ -2691,7 +2691,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Herzlich Willkommen bei Gradido liebe Bibi!',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(50),
|
||||
@ -2700,7 +2700,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Herzlich Willkommen bei Gradido liebe Bibi!',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(450),
|
||||
@ -2709,7 +2709,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Herzlich Willkommen bei Gradido liebe Bibi!',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(400),
|
||||
@ -2718,7 +2718,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Lustig',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
messagesCount: 0,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
@ -2727,7 +2727,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'der Baumeister',
|
||||
memo: 'Confirmed Contribution',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
@ -2736,7 +2736,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Lustig',
|
||||
memo: 'Test env contribution',
|
||||
messagesCount: 0,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(200),
|
||||
@ -2745,7 +2745,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Aktives Grundeinkommen',
|
||||
messagesCount: 0,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(200),
|
||||
@ -2754,7 +2754,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Lustig',
|
||||
memo: 'Das war leider zu Viel!',
|
||||
messagesCount: 0,
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(166),
|
||||
@ -2763,7 +2763,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Hotzenplotz',
|
||||
memo: 'Whatever contribution',
|
||||
messagesCount: 0,
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(166),
|
||||
@ -2772,7 +2772,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Hotzenplotz',
|
||||
memo: 'Whatever contribution',
|
||||
messagesCount: 0,
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(166),
|
||||
@ -2781,7 +2781,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Hotzenplotz',
|
||||
memo: 'Whatever contribution',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
@ -2790,7 +2790,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test contribution to delete',
|
||||
messagesCount: 0,
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
@ -2799,7 +2799,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test contribution to deny',
|
||||
messagesCount: 0,
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
@ -2808,7 +2808,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test contribution to confirm',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
@ -2817,7 +2817,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test IN_PROGRESS contribution',
|
||||
messagesCount: 1,
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(10),
|
||||
@ -2826,7 +2826,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test PENDING contribution update',
|
||||
messagesCount: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(1000),
|
||||
@ -2835,7 +2835,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
@ -2864,7 +2864,7 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Lustig',
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
messagesCount: 0,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: '100',
|
||||
@ -2873,19 +2873,19 @@ describe('ContributionResolver', () => {
|
||||
lastName: 'Lustig',
|
||||
memo: 'Test env contribution',
|
||||
messagesCount: 0,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DENIED',
|
||||
status: 'DENIED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'DELETED',
|
||||
status: 'DELETED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'CONFIRMED',
|
||||
status: 'CONFIRMED',
|
||||
}),
|
||||
expect.not.objectContaining({
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
|
||||
@ -11,9 +11,9 @@ import { AdminCreateContributionArgs } from '@arg/AdminCreateContributionArgs'
|
||||
import { AdminUpdateContributionArgs } from '@arg/AdminUpdateContributionArgs'
|
||||
import { ContributionArgs } from '@arg/ContributionArgs'
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { ContributionMessageType } from '@enum/ContributionMessageType'
|
||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||
import { ContributionType } from '@enum/ContributionType'
|
||||
import { ContributionMessageType } from '@enum/MessageType'
|
||||
import { Order } from '@enum/Order'
|
||||
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||
import { AdminUpdateContribution } from '@model/AdminUpdateContribution'
|
||||
@ -101,7 +101,7 @@ export class ContributionResolver {
|
||||
@Ctx() context: Context,
|
||||
): Promise<boolean> {
|
||||
const user = getUser(context)
|
||||
const contribution = await DbContribution.findOne(id)
|
||||
const contribution = await DbContribution.findOne({ where: { id } })
|
||||
if (!contribution) {
|
||||
throw new LogError('Contribution not found', id)
|
||||
}
|
||||
@ -395,7 +395,7 @@ export class ContributionResolver {
|
||||
@Arg('id', () => Int) id: number,
|
||||
@Ctx() context: Context,
|
||||
): Promise<boolean> {
|
||||
const contribution = await DbContribution.findOne(id)
|
||||
const contribution = await DbContribution.findOne({ where: { id } })
|
||||
if (!contribution) {
|
||||
throw new LogError('Contribution not found', id)
|
||||
}
|
||||
@ -409,10 +409,10 @@ export class ContributionResolver {
|
||||
) {
|
||||
throw new LogError('Own contribution can not be deleted as admin')
|
||||
}
|
||||
const user = await DbUser.findOneOrFail(
|
||||
{ id: contribution.userId },
|
||||
{ relations: ['emailContact'] },
|
||||
)
|
||||
const user = await DbUser.findOneOrFail({
|
||||
where: { id: contribution.userId },
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
contribution.contributionStatus = ContributionStatus.DELETED
|
||||
contribution.deletedBy = moderator.id
|
||||
await contribution.save()
|
||||
@ -447,7 +447,7 @@ export class ContributionResolver {
|
||||
const releaseLock = await TRANSACTIONS_LOCK.acquire()
|
||||
try {
|
||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||
const contribution = await DbContribution.findOne(id)
|
||||
const contribution = await DbContribution.findOne({ where: { id } })
|
||||
if (!contribution) {
|
||||
throw new LogError('Contribution not found', id)
|
||||
}
|
||||
@ -461,10 +461,11 @@ export class ContributionResolver {
|
||||
if (moderatorUser.id === contribution.userId) {
|
||||
throw new LogError('Moderator can not confirm own contribution')
|
||||
}
|
||||
const user = await DbUser.findOneOrFail(
|
||||
{ id: contribution.userId },
|
||||
{ withDeleted: true, relations: ['emailContact'] },
|
||||
)
|
||||
const user = await DbUser.findOneOrFail({
|
||||
where: { id: contribution.userId },
|
||||
withDeleted: true,
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
if (user.deletedAt) {
|
||||
throw new LogError('Can not confirm contribution since the user was deleted')
|
||||
}
|
||||
@ -565,9 +566,11 @@ export class ContributionResolver {
|
||||
@Ctx() context: Context,
|
||||
): Promise<boolean> {
|
||||
const contributionToUpdate = await DbContribution.findOne({
|
||||
id,
|
||||
confirmedAt: IsNull(),
|
||||
deniedBy: IsNull(),
|
||||
where: {
|
||||
id,
|
||||
confirmedAt: IsNull(),
|
||||
deniedBy: IsNull(),
|
||||
},
|
||||
})
|
||||
if (!contributionToUpdate) {
|
||||
throw new LogError('Contribution not found', id)
|
||||
@ -582,10 +585,10 @@ export class ContributionResolver {
|
||||
)
|
||||
}
|
||||
const moderator = getUser(context)
|
||||
const user = await DbUser.findOne(
|
||||
{ id: contributionToUpdate.userId },
|
||||
{ relations: ['emailContact'] },
|
||||
)
|
||||
const user = await DbUser.findOne({
|
||||
where: { id: contributionToUpdate.userId },
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
if (!user) {
|
||||
throw new LogError('Could not find User of the Contribution', contributionToUpdate.userId)
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import { Resolver, Query, Args, Ctx, Authorized, Arg, Int, Float } from 'type-gr
|
||||
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { Order } from '@enum/Order'
|
||||
import { GdtEntry } from '@model/GdtEntry'
|
||||
import { GdtEntryList } from '@model/GdtEntryList'
|
||||
|
||||
import { apiGet, apiPost } from '@/apis/HttpRequest'
|
||||
@ -31,9 +32,17 @@ export class GdtResolver {
|
||||
`${CONFIG.GDT_API_URL}/GdtEntries/listPerEmailApi/${userEntity.emailContact.email}/${currentPage}/${pageSize}/${order}`,
|
||||
)
|
||||
if (!resultGDT.success) {
|
||||
throw new LogError(resultGDT.data)
|
||||
return new GdtEntryList()
|
||||
}
|
||||
return new GdtEntryList(resultGDT.data)
|
||||
const { state, count, gdtEntries, gdtSum, timeUsed } = resultGDT.data
|
||||
return new GdtEntryList(
|
||||
state,
|
||||
count,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any
|
||||
gdtEntries ? gdtEntries.map((data: any) => new GdtEntry(data)) : [],
|
||||
gdtSum,
|
||||
timeUsed,
|
||||
)
|
||||
} catch (err) {
|
||||
throw new LogError('GDT Server is not reachable')
|
||||
}
|
||||
|
||||
@ -72,10 +72,10 @@ describe('KlicktippResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the NEWSLETTER_SUBSCRIBE event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.NEWSLETTER_SUBSCRIBE,
|
||||
@ -121,10 +121,10 @@ describe('KlicktippResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the NEWSLETTER_UNSUBSCRIBE event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.NEWSLETTER_UNSUBSCRIBE,
|
||||
|
||||
@ -456,10 +456,10 @@ describe('TransactionLinkResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the CONTRIBUTION_LINK_REDEEM event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.CONTRIBUTION_LINK_REDEEM,
|
||||
@ -611,10 +611,10 @@ describe('TransactionLinkResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the TRANSACTION_LINK_CREATE event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.TRANSACTION_LINK_CREATE,
|
||||
@ -664,10 +664,10 @@ describe('TransactionLinkResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the TRANSACTION_LINK_DELETE event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.TRANSACTION_LINK_DELETE,
|
||||
@ -719,14 +719,14 @@ describe('TransactionLinkResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the TRANSACTION_LINK_REDEEM event in the database', async () => {
|
||||
const creator = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const redeemer = await UserContact.findOneOrFail(
|
||||
{ email: 'peter@lustig.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const creator = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
const redeemer = await UserContact.findOneOrFail({
|
||||
where: { email: 'peter@lustig.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.TRANSACTION_LINK_REDEEM,
|
||||
|
||||
@ -33,6 +33,7 @@ import { Context, getUser, getClientTimezoneOffset } from '@/server/context'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { calculateDecay } from '@/util/decay'
|
||||
import { TRANSACTION_LINK_LOCK } from '@/util/TRANSACTION_LINK_LOCK'
|
||||
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||
import { fullName } from '@/util/utilities'
|
||||
import { calculateBalance } from '@/util/validate'
|
||||
@ -80,6 +81,7 @@ export class TransactionLinkResolver {
|
||||
|
||||
// validate amount
|
||||
const sendBalance = await calculateBalance(user.id, holdAvailableAmount.mul(-1), createdDate)
|
||||
|
||||
if (!sendBalance) {
|
||||
throw new LogError('User has not enough GDD', user.id)
|
||||
}
|
||||
@ -108,7 +110,7 @@ export class TransactionLinkResolver {
|
||||
): Promise<boolean> {
|
||||
const user = getUser(context)
|
||||
|
||||
const transactionLink = await DbTransactionLink.findOne({ id })
|
||||
const transactionLink = await DbTransactionLink.findOne({ where: { id } })
|
||||
if (!transactionLink) {
|
||||
throw new LogError('Transaction link not found', id)
|
||||
}
|
||||
@ -138,17 +140,22 @@ export class TransactionLinkResolver {
|
||||
@Query(() => QueryLinkResult)
|
||||
async queryTransactionLink(@Arg('code') code: string): Promise<typeof QueryLinkResult> {
|
||||
if (code.match(/^CL-/)) {
|
||||
const contributionLink = await DbContributionLink.findOneOrFail(
|
||||
{ code: code.replace('CL-', '') },
|
||||
{ withDeleted: true },
|
||||
)
|
||||
const contributionLink = await DbContributionLink.findOneOrFail({
|
||||
where: { code: code.replace('CL-', '') },
|
||||
withDeleted: true,
|
||||
})
|
||||
return new ContributionLink(contributionLink)
|
||||
} else {
|
||||
const transactionLink = await DbTransactionLink.findOneOrFail({ code }, { withDeleted: true })
|
||||
const user = await DbUser.findOneOrFail({ id: transactionLink.userId })
|
||||
const transactionLink = await DbTransactionLink.findOneOrFail({
|
||||
where: { code },
|
||||
withDeleted: true,
|
||||
})
|
||||
const user = await DbUser.findOneOrFail({ where: { id: transactionLink.userId } })
|
||||
let redeemedBy: User | null = null
|
||||
if (transactionLink?.redeemedBy) {
|
||||
redeemedBy = new User(await DbUser.findOneOrFail({ id: transactionLink.redeemedBy }))
|
||||
redeemedBy = new User(
|
||||
await DbUser.findOneOrFail({ where: { id: transactionLink.redeemedBy } }),
|
||||
)
|
||||
}
|
||||
return new TransactionLink(transactionLink, new User(user), redeemedBy)
|
||||
}
|
||||
@ -191,7 +198,7 @@ export class TransactionLinkResolver {
|
||||
throw new LogError('Contribution link is no longer valid', contributionLink.validTo)
|
||||
}
|
||||
}
|
||||
let alreadyRedeemed: DbContribution | undefined
|
||||
let alreadyRedeemed: DbContribution | null
|
||||
switch (contributionLink.cycle) {
|
||||
case ContributionCycleType.ONCE: {
|
||||
alreadyRedeemed = await queryRunner.manager
|
||||
@ -302,49 +309,51 @@ export class TransactionLinkResolver {
|
||||
return true
|
||||
} else {
|
||||
const now = new Date()
|
||||
const transactionLink = await DbTransactionLink.findOne({ code })
|
||||
if (!transactionLink) {
|
||||
throw new LogError('Transaction link not found', code)
|
||||
const releaseLinkLock = await TRANSACTION_LINK_LOCK.acquire()
|
||||
try {
|
||||
const transactionLink = await DbTransactionLink.findOne({ where: { code } })
|
||||
if (!transactionLink) {
|
||||
throw new LogError('Transaction link not found', code)
|
||||
}
|
||||
|
||||
const linkedUser = await DbUser.findOne({
|
||||
where: {
|
||||
id: transactionLink.userId,
|
||||
},
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
|
||||
if (!linkedUser) {
|
||||
throw new LogError('Linked user not found for given link', transactionLink.userId)
|
||||
}
|
||||
|
||||
if (user.id === linkedUser.id) {
|
||||
throw new LogError('Cannot redeem own transaction link', user.id)
|
||||
}
|
||||
|
||||
if (transactionLink.validUntil.getTime() < now.getTime()) {
|
||||
throw new LogError('Transaction link is not valid anymore', transactionLink.validUntil)
|
||||
}
|
||||
|
||||
if (transactionLink.redeemedBy) {
|
||||
throw new LogError('Transaction link already redeemed', transactionLink.redeemedBy)
|
||||
}
|
||||
await executeTransaction(
|
||||
transactionLink.amount,
|
||||
transactionLink.memo,
|
||||
linkedUser,
|
||||
user,
|
||||
transactionLink,
|
||||
)
|
||||
await EVENT_TRANSACTION_LINK_REDEEM(
|
||||
user,
|
||||
{ id: transactionLink.userId } as DbUser,
|
||||
transactionLink,
|
||||
transactionLink.amount,
|
||||
)
|
||||
} finally {
|
||||
releaseLinkLock()
|
||||
}
|
||||
|
||||
const linkedUser = await DbUser.findOne(
|
||||
{ id: transactionLink.userId },
|
||||
{ relations: ['emailContact'] },
|
||||
)
|
||||
|
||||
if (!linkedUser) {
|
||||
throw new LogError('Linked user not found for given link', transactionLink.userId)
|
||||
}
|
||||
|
||||
if (user.id === linkedUser.id) {
|
||||
throw new LogError('Cannot redeem own transaction link', user.id)
|
||||
}
|
||||
|
||||
// TODO: The now check should be done within the semaphore lock,
|
||||
// since the program might wait a while till it is ready to proceed
|
||||
// writing the transaction.
|
||||
if (transactionLink.validUntil.getTime() < now.getTime()) {
|
||||
throw new LogError('Transaction link is not valid anymore', transactionLink.validUntil)
|
||||
}
|
||||
|
||||
if (transactionLink.redeemedBy) {
|
||||
throw new LogError('Transaction link already redeemed', transactionLink.redeemedBy)
|
||||
}
|
||||
|
||||
await executeTransaction(
|
||||
transactionLink.amount,
|
||||
transactionLink.memo,
|
||||
linkedUser,
|
||||
user,
|
||||
transactionLink,
|
||||
)
|
||||
await EVENT_TRANSACTION_LINK_REDEEM(
|
||||
user,
|
||||
{ id: transactionLink.userId } as DbUser,
|
||||
transactionLink,
|
||||
transactionLink.amount,
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -378,7 +387,7 @@ export class TransactionLinkResolver {
|
||||
@Arg('userId', () => Int)
|
||||
userId: number,
|
||||
): Promise<TransactionLinkResult> {
|
||||
const user = await DbUser.findOne({ id: userId })
|
||||
const user = await DbUser.findOne({ where: { id: userId } })
|
||||
if (!user) {
|
||||
throw new LogError('Could not find requested User', userId)
|
||||
}
|
||||
|
||||
@ -346,8 +346,10 @@ describe('send coins', () => {
|
||||
it('stores the TRANSACTION_SEND event in the database', async () => {
|
||||
// Find the exact transaction (sent one is the one with user[1] as user)
|
||||
const transaction = await Transaction.find({
|
||||
userId: user[1].id,
|
||||
memo: 'unrepeatable memo',
|
||||
where: {
|
||||
userId: user[1].id,
|
||||
memo: 'unrepeatable memo',
|
||||
},
|
||||
})
|
||||
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
@ -364,8 +366,10 @@ describe('send coins', () => {
|
||||
it('stores the TRANSACTION_RECEIVE event in the database', async () => {
|
||||
// Find the exact transaction (received one is the one with user[0] as user)
|
||||
const transaction = await Transaction.find({
|
||||
userId: user[0].id,
|
||||
memo: 'unrepeatable memo',
|
||||
where: {
|
||||
userId: user[0].id,
|
||||
memo: 'unrepeatable memo',
|
||||
},
|
||||
})
|
||||
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
/* eslint-disable new-cap */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import { getCustomRepository, getConnection, In } from '@dbTools/typeorm'
|
||||
import { getConnection, In } from '@dbTools/typeorm'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
||||
import { User as dbUser } from '@entity/User'
|
||||
@ -16,7 +16,6 @@ import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||
import { Transaction } from '@model/Transaction'
|
||||
import { TransactionList } from '@model/TransactionList'
|
||||
import { User } from '@model/User'
|
||||
import { TransactionLinkRepository } from '@repository/TransactionLink'
|
||||
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import {
|
||||
@ -38,6 +37,7 @@ import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
|
||||
import { findUserByIdentifier } from './util/findUserByIdentifier'
|
||||
import { getLastTransaction } from './util/getLastTransaction'
|
||||
import { getTransactionList } from './util/getTransactionList'
|
||||
import { transactionLinkSummary } from './util/transactionLinkSummary'
|
||||
|
||||
export const executeTransaction = async (
|
||||
amount: Decimal,
|
||||
@ -245,9 +245,8 @@ export class TransactionResolver {
|
||||
const self = new User(user)
|
||||
const transactions: Transaction[] = []
|
||||
|
||||
const transactionLinkRepository = getCustomRepository(TransactionLinkRepository)
|
||||
const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate, transactionLinkcount } =
|
||||
await transactionLinkRepository.summary(user.id, now)
|
||||
await transactionLinkSummary(user.id, now)
|
||||
context.linkCount = transactionLinkcount
|
||||
logger.debug(`transactionLinkcount=${transactionLinkcount}`)
|
||||
context.sumHoldAvailableAmount = sumHoldAvailableAmount
|
||||
|
||||
@ -195,10 +195,12 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the USER_REGISTER event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'peter@lustig.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: {
|
||||
email: 'peter@lustig.de',
|
||||
},
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.USER_REGISTER,
|
||||
@ -271,10 +273,10 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the EMAIL_ACCOUNT_MULTIREGISTRATION event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'peter@lustig.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'peter@lustig.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.EMAIL_ACCOUNT_MULTIREGISTRATION,
|
||||
@ -292,7 +294,7 @@ describe('UserResolver', () => {
|
||||
variables: { ...variables, email: 'bibi@bloxberg.de', language: 'it' },
|
||||
})
|
||||
await expect(
|
||||
UserContact.findOne({ email: 'bibi@bloxberg.de' }, { relations: ['user'] }),
|
||||
UserContact.findOne({ where: { email: 'bibi@bloxberg.de' }, relations: ['user'] }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
email: 'bibi@bloxberg.de',
|
||||
@ -334,7 +336,7 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
// make Peter Lustig Admin
|
||||
const peter = await User.findOneOrFail({ id: user[0].id })
|
||||
const peter = await User.findOneOrFail({ where: { id: user[0].id } })
|
||||
peter.isAdmin = new Date()
|
||||
await peter.save()
|
||||
|
||||
@ -365,7 +367,7 @@ describe('UserResolver', () => {
|
||||
|
||||
it('sets the contribution link id', async () => {
|
||||
await expect(
|
||||
UserContact.findOne({ email: 'ein@besucher.de' }, { relations: ['user'] }),
|
||||
UserContact.findOne({ where: { email: 'ein@besucher.de' }, relations: ['user'] }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
user: expect.objectContaining({
|
||||
@ -445,7 +447,7 @@ describe('UserResolver', () => {
|
||||
memo: `testing transaction link`,
|
||||
})
|
||||
|
||||
transactionLink = await TransactionLink.findOneOrFail()
|
||||
transactionLink = await TransactionLink.findOneOrFail({ where: { userId: bob.id } })
|
||||
|
||||
resetToken()
|
||||
|
||||
@ -462,7 +464,7 @@ describe('UserResolver', () => {
|
||||
|
||||
it('sets the referrer id to bob baumeister id', async () => {
|
||||
await expect(
|
||||
UserContact.findOne({ email: 'which@ever.de' }, { relations: ['user'] }),
|
||||
UserContact.findOne({ where: { email: 'which@ever.de' }, relations: ['user'] }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
user: expect.objectContaining({ referrerId: bob.data.login.id }),
|
||||
@ -529,16 +531,18 @@ describe('UserResolver', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await mutate({ mutation: createUser, variables: createUserVariables })
|
||||
const emailContact = await UserContact.findOneOrFail({ email: createUserVariables.email })
|
||||
const emailContact = await UserContact.findOneOrFail({
|
||||
where: { email: createUserVariables.email },
|
||||
})
|
||||
emailVerificationCode = emailContact.emailVerificationCode.toString()
|
||||
result = await mutate({
|
||||
mutation: setPassword,
|
||||
variables: { code: emailVerificationCode, password: 'Aa12345_' },
|
||||
})
|
||||
newUser = await User.findOneOrFail(
|
||||
{ id: emailContact.userId },
|
||||
{ relations: ['emailContact'] },
|
||||
)
|
||||
newUser = await User.findOneOrFail({
|
||||
where: { id: emailContact.userId },
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -571,7 +575,9 @@ describe('UserResolver', () => {
|
||||
describe('no valid password', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({ mutation: createUser, variables: createUserVariables })
|
||||
const emailContact = await UserContact.findOneOrFail({ email: createUserVariables.email })
|
||||
const emailContact = await UserContact.findOneOrFail({
|
||||
where: { email: createUserVariables.email },
|
||||
})
|
||||
emailVerificationCode = emailContact.emailVerificationCode.toString()
|
||||
})
|
||||
|
||||
@ -697,10 +703,10 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the USER_LOGIN event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.USER_LOGIN,
|
||||
@ -879,10 +885,10 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the USER_LOGOUT event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.USER_LOGOUT,
|
||||
@ -1047,10 +1053,10 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the EMAIL_FORGOT_PASSWORD event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.EMAIL_FORGOT_PASSWORD,
|
||||
@ -1083,7 +1089,7 @@ describe('UserResolver', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
await userFactory(testEnv, bibiBloxberg)
|
||||
emailContact = await UserContact.findOneOrFail({ email: bibiBloxberg.email })
|
||||
emailContact = await UserContact.findOneOrFail({ where: { email: bibiBloxberg.email } })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -1100,7 +1106,9 @@ describe('UserResolver', () => {
|
||||
errors: [
|
||||
// keep Whitspace in error message!
|
||||
new GraphQLError(`Could not find any entity of type "UserContact" matching: {
|
||||
"emailVerificationCode": "not-valid"
|
||||
"where": {
|
||||
"emailVerificationCode": "not-valid"
|
||||
}
|
||||
}`),
|
||||
],
|
||||
}),
|
||||
@ -1175,20 +1183,20 @@ describe('UserResolver', () => {
|
||||
locale: 'en',
|
||||
},
|
||||
})
|
||||
await expect(User.findOne()).resolves.toEqual(
|
||||
await expect(User.find()).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
firstName: 'Benjamin',
|
||||
lastName: 'Blümchen',
|
||||
language: 'en',
|
||||
}),
|
||||
)
|
||||
])
|
||||
})
|
||||
|
||||
it('stores the USER_INFO_UPDATE event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.USER_INFO_UPDATE,
|
||||
@ -1212,11 +1220,11 @@ describe('UserResolver', () => {
|
||||
alias: 'bibi_Bloxberg',
|
||||
},
|
||||
})
|
||||
await expect(User.findOne()).resolves.toEqual(
|
||||
await expect(User.find()).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
alias: 'bibi_Bloxberg',
|
||||
}),
|
||||
)
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1433,10 +1441,10 @@ describe('UserResolver', () => {
|
||||
|
||||
let bibi: User
|
||||
beforeAll(async () => {
|
||||
const usercontact = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const usercontact = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
bibi = usercontact.user
|
||||
bibi.passwordEncryptionType = PasswordEncryptionType.EMAIL
|
||||
bibi.password = SecretKeyCryptographyCreateKey(
|
||||
@ -1450,10 +1458,10 @@ describe('UserResolver', () => {
|
||||
it('changes to gradidoID on login', async () => {
|
||||
await mutate({ mutation: login, variables })
|
||||
|
||||
const usercontact = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const usercontact = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
bibi = usercontact.user
|
||||
|
||||
expect(bibi).toEqual(
|
||||
@ -1590,14 +1598,14 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the ADMIN_USER_ROLE_SET event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const adminConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'peter@lustig.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
const adminConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'peter@lustig.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.ADMIN_USER_ROLE_SET,
|
||||
@ -1792,14 +1800,15 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the ADMIN_USER_DELETE event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'], withDeleted: true },
|
||||
)
|
||||
const adminConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'peter@lustig.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
withDeleted: true,
|
||||
})
|
||||
const adminConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'peter@lustig.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.ADMIN_USER_DELETE,
|
||||
@ -1943,10 +1952,10 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('sends an account activation email', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
|
||||
/{optin}/g,
|
||||
userConatct.emailVerificationCode.toString(),
|
||||
@ -1965,10 +1974,10 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the EMAIL_ADMIN_CONFIRMATION event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.EMAIL_ADMIN_CONFIRMATION,
|
||||
@ -2086,14 +2095,14 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the ADMIN_USER_UNDELETE event in the database', async () => {
|
||||
const userConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const adminConatct = await UserContact.findOneOrFail(
|
||||
{ email: 'peter@lustig.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
const userConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'bibi@bloxberg.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
const adminConatct = await UserContact.findOneOrFail({
|
||||
where: { email: 'peter@lustig.de' },
|
||||
relations: ['user'],
|
||||
})
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventType.ADMIN_USER_UNDELETE,
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { getConnection, getCustomRepository, IsNull, Not } from '@dbTools/typeorm'
|
||||
import { getConnection, IsNull, Not } from '@dbTools/typeorm'
|
||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
@ -23,7 +23,6 @@ import { UserContactType } from '@enum/UserContactType'
|
||||
import { SearchAdminUsersResult } from '@model/AdminUser'
|
||||
import { User } from '@model/User'
|
||||
import { UserAdmin, SearchUsersResult } from '@model/UserAdmin'
|
||||
import { UserRepository } from '@repository/User'
|
||||
|
||||
import { subscribe } from '@/apis/KlicktippController'
|
||||
import { encode } from '@/auth/JWT'
|
||||
@ -65,6 +64,7 @@ import { randombytes_random } from 'sodium-native'
|
||||
import { FULL_CREATION_AVAILABLE } from './const/const'
|
||||
import { getUserCreations } from './util/creations'
|
||||
import { findUserByIdentifier } from './util/findUserByIdentifier'
|
||||
import { findUsers } from './util/findUsers'
|
||||
import { getKlicktippState } from './util/getKlicktippState'
|
||||
import { validateAlias } from './util/validateAlias'
|
||||
|
||||
@ -82,13 +82,13 @@ const newEmailContact = (email: string, userId: number): DbUserContact => {
|
||||
emailContact.type = UserContactType.USER_CONTACT_EMAIL
|
||||
emailContact.emailChecked = false
|
||||
emailContact.emailOptInTypeId = OptInType.EMAIL_OPT_IN_REGISTER
|
||||
emailContact.emailVerificationCode = random(64)
|
||||
emailContact.emailVerificationCode = random(64).toString()
|
||||
logger.debug('newEmailContact...successful', emailContact)
|
||||
return emailContact
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export const activationLink = (verificationCode: BigInt): string => {
|
||||
export const activationLink = (verificationCode: string): string => {
|
||||
logger.debug(`activationLink(${verificationCode})...`)
|
||||
return CONFIG.EMAIL_LINK_SETPASSWORD.replace(/{optin}/g, verificationCode.toString())
|
||||
}
|
||||
@ -270,7 +270,7 @@ export class UserResolver {
|
||||
if (redeemCode) {
|
||||
if (redeemCode.match(/^CL-/)) {
|
||||
const contributionLink = await DbContributionLink.findOne({
|
||||
code: redeemCode.replace('CL-', ''),
|
||||
where: { code: redeemCode.replace('CL-', '') },
|
||||
})
|
||||
logger.info('redeemCode found contributionLink', contributionLink)
|
||||
if (contributionLink) {
|
||||
@ -278,7 +278,7 @@ export class UserResolver {
|
||||
eventRegisterRedeem.involvedContributionLink = contributionLink
|
||||
}
|
||||
} else {
|
||||
const transactionLink = await DbTransactionLink.findOne({ code: redeemCode })
|
||||
const transactionLink = await DbTransactionLink.findOne({ where: { code: redeemCode } })
|
||||
logger.info('redeemCode found transactionLink', transactionLink)
|
||||
if (transactionLink) {
|
||||
dbUser.referrerId = transactionLink.userId
|
||||
@ -352,7 +352,8 @@ export class UserResolver {
|
||||
const user = await findUserByEmail(email).catch(() => {
|
||||
logger.warn(`fail on find UserContact per ${email}`)
|
||||
})
|
||||
if (!user) {
|
||||
|
||||
if (!user || user.deletedAt) {
|
||||
logger.warn(`no user found with ${email}`)
|
||||
return true
|
||||
}
|
||||
@ -365,7 +366,7 @@ export class UserResolver {
|
||||
|
||||
user.emailContact.updatedAt = new Date()
|
||||
user.emailContact.emailResendCount++
|
||||
user.emailContact.emailVerificationCode = random(64)
|
||||
user.emailContact.emailVerificationCode = random(64).toString()
|
||||
user.emailContact.emailOptInTypeId = OptInType.EMAIL_OPT_IN_RESET_PASSWORD
|
||||
await user.emailContact.save().catch(() => {
|
||||
throw new LogError('Unable to save email verification code', user.emailContact)
|
||||
@ -403,10 +404,10 @@ export class UserResolver {
|
||||
}
|
||||
|
||||
// load code
|
||||
const userContact = await DbUserContact.findOneOrFail(
|
||||
{ emailVerificationCode: code },
|
||||
{ relations: ['user'] },
|
||||
).catch(() => {
|
||||
const userContact = await DbUserContact.findOneOrFail({
|
||||
where: { emailVerificationCode: code },
|
||||
relations: ['user'],
|
||||
}).catch(() => {
|
||||
throw new LogError('Could not login with emailVerificationCode')
|
||||
})
|
||||
logger.debug('userContact loaded...')
|
||||
@ -474,7 +475,9 @@ export class UserResolver {
|
||||
@Query(() => Boolean)
|
||||
async queryOptIn(@Arg('optIn') optIn: string): Promise<boolean> {
|
||||
logger.info(`queryOptIn(${optIn})...`)
|
||||
const userContact = await DbUserContact.findOneOrFail({ emailVerificationCode: optIn })
|
||||
const userContact = await DbUserContact.findOneOrFail({
|
||||
where: { emailVerificationCode: optIn },
|
||||
})
|
||||
logger.debug('found optInCode', userContact)
|
||||
// Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes
|
||||
if (!isEmailVerificationCodeValid(userContact.updatedAt || userContact.createdAt)) {
|
||||
@ -601,9 +604,7 @@ export class UserResolver {
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
|
||||
): Promise<SearchAdminUsersResult> {
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
|
||||
const [users, count] = await userRepository.findAndCount({
|
||||
const [users, count] = await DbUser.findAndCount({
|
||||
where: {
|
||||
isAdmin: Not(IsNull()),
|
||||
},
|
||||
@ -636,7 +637,6 @@ export class UserResolver {
|
||||
@Ctx() context: Context,
|
||||
): Promise<SearchUsersResult> {
|
||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const userFields = [
|
||||
'id',
|
||||
'firstName',
|
||||
@ -646,7 +646,7 @@ export class UserResolver {
|
||||
'deletedAt',
|
||||
'isAdmin',
|
||||
]
|
||||
const [users, count] = await userRepository.findBySearchCriteriaPagedFiltered(
|
||||
const [users, count] = await findUsers(
|
||||
userFields.map((fieldName) => {
|
||||
return 'user.' + fieldName
|
||||
}),
|
||||
@ -705,7 +705,7 @@ export class UserResolver {
|
||||
@Ctx()
|
||||
context: Context,
|
||||
): Promise<Date | null> {
|
||||
const user = await DbUser.findOne({ id: userId })
|
||||
const user = await DbUser.findOne({ where: { id: userId } })
|
||||
// user exists ?
|
||||
if (!user) {
|
||||
throw new LogError('Could not find user with given ID', userId)
|
||||
@ -734,7 +734,7 @@ export class UserResolver {
|
||||
}
|
||||
await user.save()
|
||||
await EVENT_ADMIN_USER_ROLE_SET(user, moderator)
|
||||
const newUser = await DbUser.findOne({ id: userId })
|
||||
const newUser = await DbUser.findOne({ where: { id: userId } })
|
||||
return newUser ? newUser.isAdmin : null
|
||||
}
|
||||
|
||||
@ -744,7 +744,7 @@ export class UserResolver {
|
||||
@Arg('userId', () => Int) userId: number,
|
||||
@Ctx() context: Context,
|
||||
): Promise<Date | null> {
|
||||
const user = await DbUser.findOne({ id: userId })
|
||||
const user = await DbUser.findOne({ where: { id: userId } })
|
||||
// user exists ?
|
||||
if (!user) {
|
||||
throw new LogError('Could not find user with given ID', userId)
|
||||
@ -757,7 +757,7 @@ export class UserResolver {
|
||||
// soft-delete user
|
||||
await user.softRemove()
|
||||
await EVENT_ADMIN_USER_DELETE(user, moderator)
|
||||
const newUser = await DbUser.findOne({ id: userId }, { withDeleted: true })
|
||||
const newUser = await DbUser.findOne({ where: { id: userId }, withDeleted: true })
|
||||
return newUser ? newUser.deletedAt : null
|
||||
}
|
||||
|
||||
@ -767,7 +767,7 @@ export class UserResolver {
|
||||
@Arg('userId', () => Int) userId: number,
|
||||
@Ctx() context: Context,
|
||||
): Promise<Date | null> {
|
||||
const user = await DbUser.findOne({ id: userId }, { withDeleted: true })
|
||||
const user = await DbUser.findOne({ where: { id: userId }, withDeleted: true })
|
||||
if (!user) {
|
||||
throw new LogError('Could not find user with given ID', userId)
|
||||
}
|
||||
@ -819,10 +819,11 @@ export class UserResolver {
|
||||
}
|
||||
|
||||
export async function findUserByEmail(email: string): Promise<DbUser> {
|
||||
const dbUserContact = await DbUserContact.findOneOrFail(
|
||||
{ email },
|
||||
{ withDeleted: true, relations: ['user'] },
|
||||
).catch(() => {
|
||||
const dbUserContact = await DbUserContact.findOneOrFail({
|
||||
where: { email },
|
||||
withDeleted: true,
|
||||
relations: ['user'],
|
||||
}).catch(() => {
|
||||
throw new LogError('No user with this credentials', email)
|
||||
})
|
||||
const dbUser = dbUserContact.user
|
||||
@ -831,7 +832,10 @@ export async function findUserByEmail(email: string): Promise<DbUser> {
|
||||
}
|
||||
|
||||
async function checkEmailExists(email: string): Promise<boolean> {
|
||||
const userContact = await DbUserContact.findOne({ email }, { withDeleted: true })
|
||||
const userContact = await DbUserContact.findOne({
|
||||
where: { email },
|
||||
withDeleted: true,
|
||||
})
|
||||
if (userContact) {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
import { Connection } from '@dbTools/typeorm'
|
||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import { GraphQLError } from 'graphql'
|
||||
|
||||
import { cleanDB, testEnvironment, contributionDateFormatter } from '@test/helpers'
|
||||
|
||||
@ -219,7 +220,7 @@ describe('semaphore', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('does not throw, but should', async () => {
|
||||
it('does throw error on second redeem call', async () => {
|
||||
const redeem1 = mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
@ -236,7 +237,7 @@ describe('semaphore', () => {
|
||||
errors: undefined,
|
||||
})
|
||||
await expect(redeem2).resolves.toMatchObject({
|
||||
errors: undefined,
|
||||
errors: [new GraphQLError('Transaction link already redeemed')],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
import { In } from '@dbTools/typeorm'
|
||||
import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage'
|
||||
|
||||
import { ContributionMessageType } from '@enum/ContributionMessageType'
|
||||
import { Order } from '@enum/Order'
|
||||
|
||||
interface FindContributionMessagesOptions {
|
||||
contributionId: number
|
||||
pageSize: number
|
||||
currentPage: number
|
||||
order: Order
|
||||
showModeratorType?: boolean
|
||||
}
|
||||
|
||||
export const findContributionMessages = async (
|
||||
options: FindContributionMessagesOptions,
|
||||
): Promise<[DbContributionMessage[], number]> => {
|
||||
const { contributionId, pageSize, currentPage, order, showModeratorType } = options
|
||||
|
||||
const messageTypes = [ContributionMessageType.DIALOG, ContributionMessageType.HISTORY]
|
||||
|
||||
if (showModeratorType) messageTypes.push(ContributionMessageType.MODERATOR)
|
||||
|
||||
return DbContributionMessage.findAndCount({
|
||||
where: {
|
||||
contributionId,
|
||||
type: In(messageTypes),
|
||||
},
|
||||
relations: ['user'],
|
||||
order: {
|
||||
createdAt: order,
|
||||
},
|
||||
skip: (currentPage - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
}
|
||||
@ -7,20 +7,20 @@ import { LogError } from '@/server/LogError'
|
||||
import { VALID_ALIAS_REGEX } from './validateAlias'
|
||||
|
||||
export const findUserByIdentifier = async (identifier: string): Promise<DbUser> => {
|
||||
let user: DbUser | undefined
|
||||
let user: DbUser | null
|
||||
if (validate(identifier) && version(identifier) === 4) {
|
||||
user = await DbUser.findOne({ where: { gradidoID: identifier }, relations: ['emailContact'] })
|
||||
if (!user) {
|
||||
throw new LogError('No user found to given identifier', identifier)
|
||||
}
|
||||
} else if (/^.{2,}@.{2,}\..{2,}$/.exec(identifier)) {
|
||||
const userContact = await DbUserContact.findOne(
|
||||
{
|
||||
const userContact = await DbUserContact.findOne({
|
||||
where: {
|
||||
email: identifier,
|
||||
emailChecked: true,
|
||||
},
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
relations: ['user'],
|
||||
})
|
||||
if (!userContact) {
|
||||
throw new LogError('No user with this credentials', identifier)
|
||||
}
|
||||
|
||||
@ -1,20 +1,24 @@
|
||||
import { Brackets, EntityRepository, IsNull, Not, Repository } from '@dbTools/typeorm'
|
||||
import { getConnection, Brackets, IsNull, Not } from '@dbTools/typeorm'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
|
||||
import { SearchUsersFilters } from '@arg/SearchUsersFilters'
|
||||
import { Order } from '@enum/Order'
|
||||
|
||||
@EntityRepository(DbUser)
|
||||
export class UserRepository extends Repository<DbUser> {
|
||||
async findBySearchCriteriaPagedFiltered(
|
||||
select: string[],
|
||||
searchCriteria: string,
|
||||
filters: SearchUsersFilters | null,
|
||||
currentPage: number,
|
||||
pageSize: number,
|
||||
order = Order.ASC,
|
||||
): Promise<[DbUser[], number]> {
|
||||
const query = this.createQueryBuilder('user')
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
export const findUsers = async (
|
||||
select: string[],
|
||||
searchCriteria: string,
|
||||
filters: SearchUsersFilters | null,
|
||||
currentPage: number,
|
||||
pageSize: number,
|
||||
order = Order.ASC,
|
||||
): Promise<[DbUser[], number]> => {
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
try {
|
||||
await queryRunner.connect()
|
||||
const query = queryRunner.manager
|
||||
.createQueryBuilder(DbUser, 'user')
|
||||
.select(select)
|
||||
.withDeleted()
|
||||
.leftJoinAndSelect('user.emailContact', 'emailContact')
|
||||
@ -30,27 +34,24 @@ export class UserRepository extends Repository<DbUser> {
|
||||
)
|
||||
}),
|
||||
)
|
||||
/*
|
||||
filterCriteria.forEach((filter) => {
|
||||
query.andWhere(filter)
|
||||
})
|
||||
*/
|
||||
if (filters) {
|
||||
if (filters.byActivated !== null) {
|
||||
query.andWhere('emailContact.emailChecked = :value', { value: filters.byActivated })
|
||||
// filterCriteria.push({ 'emailContact.emailChecked': filters.byActivated })
|
||||
}
|
||||
|
||||
if (filters.byDeleted !== null) {
|
||||
// filterCriteria.push({ deletedAt: filters.byDeleted ? Not(IsNull()) : IsNull() })
|
||||
query.andWhere({ deletedAt: filters.byDeleted ? Not(IsNull()) : IsNull() })
|
||||
}
|
||||
}
|
||||
|
||||
return query
|
||||
return await query
|
||||
.orderBy({ 'user.id': order })
|
||||
.take(pageSize)
|
||||
.skip((currentPage - 1) * pageSize)
|
||||
.getManyAndCount()
|
||||
} catch (err) {
|
||||
throw new LogError('Unable to search users', err)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
}
|
||||
@ -3,12 +3,10 @@ import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||
export const getLastTransaction = async (
|
||||
userId: number,
|
||||
relations?: string[],
|
||||
): Promise<DbTransaction | undefined> => {
|
||||
return DbTransaction.findOne(
|
||||
{ userId },
|
||||
{
|
||||
order: { balanceDate: 'DESC', id: 'DESC' },
|
||||
relations,
|
||||
},
|
||||
)
|
||||
): Promise<DbTransaction | null> => {
|
||||
return DbTransaction.findOne({
|
||||
where: { userId },
|
||||
order: { balanceDate: 'DESC', id: 'DESC' },
|
||||
relations,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { MoreThan } from '@dbTools/typeorm'
|
||||
import { MoreThan, IsNull } from '@dbTools/typeorm'
|
||||
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
|
||||
@ -22,7 +22,7 @@ export async function transactionLinkList(
|
||||
const [transactionLinks, count] = await DbTransactionLink.findAndCount({
|
||||
where: {
|
||||
userId: user.id,
|
||||
...(!withRedeemed && { redeemedBy: null }),
|
||||
...(!withRedeemed && { redeemedBy: IsNull() }),
|
||||
...(!withExpired && { validUntil: MoreThan(new Date()) }),
|
||||
},
|
||||
withDeleted,
|
||||
|
||||
49
backend/src/graphql/resolver/util/transactionLinkSummary.ts
Normal file
49
backend/src/graphql/resolver/util/transactionLinkSummary.ts
Normal file
@ -0,0 +1,49 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
export const transactionLinkSummary = async (
|
||||
userId: number,
|
||||
date: Date,
|
||||
): Promise<{
|
||||
sumHoldAvailableAmount: Decimal
|
||||
sumAmount: Decimal
|
||||
lastDate: Date | null
|
||||
firstDate: Date | null
|
||||
transactionLinkcount: number
|
||||
}> => {
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
try {
|
||||
await queryRunner.connect()
|
||||
const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate, count } =
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder(DbTransactionLink, 'transactionLink')
|
||||
.select('SUM(transactionLink.holdAvailableAmount)', 'sumHoldAvailableAmount')
|
||||
.addSelect('SUM(transactionLink.amount)', 'sumAmount')
|
||||
.addSelect('MAX(transactionLink.validUntil)', 'lastDate')
|
||||
.addSelect('MIN(transactionLink.createdAt)', 'firstDate')
|
||||
.addSelect('COUNT(*)', 'count')
|
||||
.where('transactionLink.userId = :userId', { userId })
|
||||
.andWhere('transactionLink.redeemedAt is NULL')
|
||||
.andWhere('transactionLink.validUntil > :date', { date })
|
||||
.orderBy('transactionLink.createdAt', 'DESC')
|
||||
.getRawOne()
|
||||
return {
|
||||
sumHoldAvailableAmount: sumHoldAvailableAmount
|
||||
? new Decimal(sumHoldAvailableAmount)
|
||||
: new Decimal(0),
|
||||
sumAmount: sumAmount ? new Decimal(sumAmount) : new Decimal(0),
|
||||
lastDate: lastDate || null,
|
||||
firstDate: firstDate || null,
|
||||
transactionLinkcount: count || 0,
|
||||
}
|
||||
} catch (err) {
|
||||
throw new LogError('Unable to get transaction link summary', err)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
}
|
||||
@ -95,7 +95,7 @@ describe('validate alias', () => {
|
||||
describe('test against existing alias in database', () => {
|
||||
beforeAll(async () => {
|
||||
const bibi = await userFactory(testEnv, bibiBloxberg)
|
||||
const user = await User.findOne({ id: bibi.id })
|
||||
const user = await User.findOne({ where: { id: bibi.id } })
|
||||
if (user) {
|
||||
user.alias = 'b-b'
|
||||
await user.save()
|
||||
|
||||
@ -19,7 +19,10 @@ export const creationFactory = async (
|
||||
creation: CreationInterface,
|
||||
): Promise<Contribution> => {
|
||||
const { mutate } = client
|
||||
await mutate({ mutation: login, variables: { email: creation.email, password: 'Aa12345_' } })
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: creation.email, password: 'Aa12345_' },
|
||||
})
|
||||
|
||||
const {
|
||||
data: { createContribution: contribution },
|
||||
@ -30,7 +33,9 @@ export const creationFactory = async (
|
||||
|
||||
await mutate({ mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' } })
|
||||
await mutate({ mutation: confirmContribution, variables: { id: contribution.id } })
|
||||
const confirmedContribution = await Contribution.findOneOrFail({ id: contribution.id })
|
||||
const confirmedContribution = await Contribution.findOneOrFail({
|
||||
where: { id: contribution.id },
|
||||
})
|
||||
|
||||
if (creation.moveCreationDate) {
|
||||
const transaction = await Transaction.findOneOrFail({
|
||||
|
||||
@ -32,7 +32,7 @@ export const transactionLinkFactory = async (
|
||||
} = await mutate({ mutation: createTransactionLink, variables })
|
||||
|
||||
if (transactionLink.createdAt || transactionLink.deletedAt) {
|
||||
const dbTransactionLink = await TransactionLink.findOneOrFail({ id })
|
||||
const dbTransactionLink = await TransactionLink.findOneOrFail({ where: { id } })
|
||||
|
||||
if (transactionLink.createdAt) {
|
||||
dbTransactionLink.createdAt = transactionLink.createdAt
|
||||
|
||||
@ -19,7 +19,7 @@ export const userFactory = async (
|
||||
} = await mutate({ mutation: createUser, variables: user })
|
||||
// console.log('creatUser:', { id }, { user })
|
||||
// get user from database
|
||||
let dbUser = await User.findOneOrFail({ id }, { relations: ['emailContact'] })
|
||||
let dbUser = await User.findOneOrFail({ where: { id }, relations: ['emailContact'] })
|
||||
// console.log('dbUser:', dbUser)
|
||||
|
||||
const emailContact = dbUser.emailContact
|
||||
@ -33,7 +33,7 @@ export const userFactory = async (
|
||||
}
|
||||
|
||||
// get last changes of user from database
|
||||
dbUser = await User.findOneOrFail({ id })
|
||||
dbUser = await User.findOneOrFail({ where: { id } })
|
||||
|
||||
if (user.createdAt || user.deletedAt || user.isAdmin) {
|
||||
if (user.createdAt) dbUser.createdAt = user.createdAt
|
||||
|
||||
@ -284,8 +284,12 @@ export const createContributionMessage = gql`
|
||||
`
|
||||
|
||||
export const adminCreateContributionMessage = gql`
|
||||
mutation ($contributionId: Int!, $message: String!) {
|
||||
adminCreateContributionMessage(contributionId: $contributionId, message: $message) {
|
||||
mutation ($contributionId: Int!, $message: String!, $messageType: ContributionMessageType) {
|
||||
adminCreateContributionMessage(
|
||||
contributionId: $contributionId
|
||||
message: $message
|
||||
messageType: $messageType
|
||||
) {
|
||||
id
|
||||
message
|
||||
createdAt
|
||||
|
||||
@ -195,7 +195,7 @@ export const listContributions = gql`
|
||||
confirmedAt
|
||||
confirmedBy
|
||||
deletedAt
|
||||
state
|
||||
status
|
||||
messagesCount
|
||||
deniedAt
|
||||
deniedBy
|
||||
@ -218,7 +218,7 @@ query ($currentPage: Int = 1, $pageSize: Int = 5, $order: Order = DESC, $statusF
|
||||
confirmedAt
|
||||
confirmedBy
|
||||
contributionDate
|
||||
state
|
||||
status
|
||||
messagesCount
|
||||
deniedAt
|
||||
deniedBy
|
||||
@ -254,7 +254,7 @@ export const adminListContributions = gql`
|
||||
confirmedAt
|
||||
confirmedBy
|
||||
contributionDate
|
||||
state
|
||||
status
|
||||
messagesCount
|
||||
deniedAt
|
||||
deniedBy
|
||||
@ -349,6 +349,29 @@ export const listContributionMessages = gql`
|
||||
}
|
||||
`
|
||||
|
||||
export const adminListContributionMessages = gql`
|
||||
query ($contributionId: Int!, $pageSize: Int = 25, $currentPage: Int = 1, $order: Order = ASC) {
|
||||
adminListContributionMessages(
|
||||
contributionId: $contributionId
|
||||
pageSize: $pageSize
|
||||
currentPage: $currentPage
|
||||
order: $order
|
||||
) {
|
||||
count
|
||||
messages {
|
||||
id
|
||||
message
|
||||
createdAt
|
||||
updatedAt
|
||||
type
|
||||
userFirstName
|
||||
userLastName
|
||||
userId
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const user = gql`
|
||||
query ($identifier: String!) {
|
||||
user(identifier: $identifier) {
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
|
||||
import { entities } from '@entity/index'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import { name, internet, datatype } from 'faker'
|
||||
@ -36,12 +42,10 @@ export const cleanDB = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const [entityTypes] = entities
|
||||
|
||||
const resetEntity = async (entity: typeof entityTypes) => {
|
||||
const resetEntity = async (entity: any) => {
|
||||
const items = await entity.find({ withDeleted: true })
|
||||
if (items.length > 0) {
|
||||
const ids = items.map((i) => i.id)
|
||||
const ids = items.map((e: any) => e.id)
|
||||
await entity.delete(ids)
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ export interface Context {
|
||||
clientTimezoneOffset?: number
|
||||
gradidoID?: string
|
||||
// hack to use less DB calls for Balance Resolver
|
||||
lastTransaction?: dbTransaction
|
||||
lastTransaction?: dbTransaction | null
|
||||
transactionCount?: number
|
||||
linkCount?: number
|
||||
sumHoldAvailableAmount?: Decimal
|
||||
|
||||
@ -4,7 +4,7 @@ import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
const getDBVersion = async (): Promise<string | null> => {
|
||||
try {
|
||||
const dbVersion = await Migration.findOne({ order: { version: 'DESC' } })
|
||||
const [dbVersion] = await Migration.find({ order: { version: 'DESC' }, take: 1 })
|
||||
return dbVersion ? dbVersion.fileName : null
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
import { Repository, EntityRepository } from '@dbTools/typeorm'
|
||||
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
@EntityRepository(dbTransactionLink)
|
||||
export class TransactionLinkRepository extends Repository<dbTransactionLink> {
|
||||
async summary(
|
||||
userId: number,
|
||||
date: Date,
|
||||
): Promise<{
|
||||
sumHoldAvailableAmount: Decimal
|
||||
sumAmount: Decimal
|
||||
lastDate: Date | null
|
||||
firstDate: Date | null
|
||||
transactionLinkcount: number
|
||||
}> {
|
||||
const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate, count } =
|
||||
await this.createQueryBuilder('transactionLinks')
|
||||
.select('SUM(transactionLinks.holdAvailableAmount)', 'sumHoldAvailableAmount')
|
||||
.addSelect('SUM(transactionLinks.amount)', 'sumAmount')
|
||||
.addSelect('MAX(transactionLinks.validUntil)', 'lastDate')
|
||||
.addSelect('MIN(transactionLinks.createdAt)', 'firstDate')
|
||||
.addSelect('COUNT(*)', 'count')
|
||||
.where('transactionLinks.userId = :userId', { userId })
|
||||
.andWhere('transactionLinks.redeemedAt is NULL')
|
||||
.andWhere('transactionLinks.validUntil > :date', { date })
|
||||
.orderBy('transactionLinks.createdAt', 'DESC')
|
||||
.getRawOne()
|
||||
return {
|
||||
sumHoldAvailableAmount: sumHoldAvailableAmount
|
||||
? new Decimal(sumHoldAvailableAmount)
|
||||
: new Decimal(0),
|
||||
sumAmount: sumAmount ? new Decimal(sumAmount) : new Decimal(0),
|
||||
lastDate: lastDate || null,
|
||||
firstDate: firstDate || null,
|
||||
transactionLinkcount: count || 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
4
backend/src/util/TRANSACTION_LINK_LOCK.ts
Normal file
4
backend/src/util/TRANSACTION_LINK_LOCK.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { Semaphore } from 'await-semaphore'
|
||||
|
||||
const CONCURRENT_TRANSACTIONS = 1
|
||||
export const TRANSACTION_LINK_LOCK = new Semaphore(CONCURRENT_TRANSACTIONS)
|
||||
@ -1,6 +1,6 @@
|
||||
import { LoginElopageBuys } from '@entity/LoginElopageBuys'
|
||||
|
||||
export async function hasElopageBuys(email: string): Promise<boolean> {
|
||||
const elopageBuyCount = await LoginElopageBuys.count({ payerEmail: email })
|
||||
const elopageBuyCount = await LoginElopageBuys.count({ where: { payerEmail: email } })
|
||||
return elopageBuyCount > 0
|
||||
}
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { getCustomRepository } from '@dbTools/typeorm'
|
||||
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { Decay } from '@model/Decay'
|
||||
import { TransactionLinkRepository } from '@repository/TransactionLink'
|
||||
|
||||
import { getLastTransaction } from '@/graphql/resolver/util/getLastTransaction'
|
||||
import { transactionLinkSummary } from '@/graphql/resolver/util/transactionLinkSummary'
|
||||
|
||||
import { calculateDecay } from './decay'
|
||||
|
||||
@ -29,8 +28,7 @@ async function calculateBalance(
|
||||
const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time)
|
||||
|
||||
const balance = decay.balance.add(amount.toString())
|
||||
const transactionLinkRepository = getCustomRepository(TransactionLinkRepository)
|
||||
const { sumHoldAvailableAmount } = await transactionLinkRepository.summary(userId, time)
|
||||
const { sumHoldAvailableAmount } = await transactionLinkSummary(userId, time)
|
||||
|
||||
// If we want to redeem a link we need to make sure that the link amount is not considered as blocked
|
||||
// else we cannot redeem links which are more or equal to half of what an account actually owns
|
||||
|
||||
@ -135,7 +135,7 @@ export const elopageWebhook = async (req: any, res: any): Promise<void> => {
|
||||
|
||||
// Do we already have such a user?
|
||||
// if ((await dbUser.count({ email })) !== 0) {
|
||||
if ((await dbUserContact.count({ email })) !== 0) {
|
||||
if ((await dbUserContact.count({ where: { email } })) !== 0) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Did not create User - already exists with email: ${email}`)
|
||||
return
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
|
||||
import { entities } from '@entity/index'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
@ -7,7 +13,6 @@ import { createServer } from '@/server/createServer'
|
||||
import { i18n, logger } from './testSetup'
|
||||
|
||||
export const headerPushMock = jest.fn((t) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||
context.token = t.value
|
||||
})
|
||||
|
||||
@ -21,7 +26,7 @@ const context = {
|
||||
}
|
||||
|
||||
export const cleanDB = async () => {
|
||||
// this only works as lond we do not have foreign key constraints
|
||||
// this only works as long we do not have foreign key constraints
|
||||
for (const entity of entities) {
|
||||
await resetEntity(entity)
|
||||
}
|
||||
@ -36,12 +41,10 @@ export const testEnvironment = async (testLogger = logger, testI18n = i18n) => {
|
||||
return { mutate, query, con }
|
||||
}
|
||||
|
||||
const [entityTypes] = entities
|
||||
|
||||
export const resetEntity = async (entity: typeof entityTypes) => {
|
||||
export const resetEntity = async (entity: any) => {
|
||||
const items = await entity.find({ withDeleted: true })
|
||||
if (items.length > 0) {
|
||||
const ids = items.map((i) => i.id)
|
||||
const ids = items.map((e: any) => e.id)
|
||||
await entity.delete(ids)
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ export class UserContact extends BaseEntity {
|
||||
email: string
|
||||
|
||||
@Column({ name: 'email_verification_code', type: 'bigint', unsigned: true, unique: true })
|
||||
emailVerificationCode: BigInt
|
||||
emailVerificationCode: string
|
||||
|
||||
@Column({ name: 'email_opt_in_type_id' })
|
||||
emailOptInTypeId: number
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
"mysql2": "^2.3.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"ts-mysql-migrate": "^1.0.2",
|
||||
"typeorm": "^0.2.38",
|
||||
"typeorm": "^0.3.16",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@ -2,6 +2,13 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@babel/runtime@^7.21.0":
|
||||
version "7.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec"
|
||||
integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@cspotcode/source-map-consumer@0.8.0":
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
|
||||
@ -106,10 +113,10 @@
|
||||
picocolors "^1.0.0"
|
||||
tslib "^2.5.0"
|
||||
|
||||
"@sqltools/formatter@^1.2.2":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.3.tgz#1185726610acc37317ddab11c3c7f9066966bd20"
|
||||
integrity sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==
|
||||
"@sqltools/formatter@^1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12"
|
||||
integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==
|
||||
|
||||
"@tsconfig/node10@^1.0.7":
|
||||
version "1.0.8"
|
||||
@ -173,11 +180,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
|
||||
|
||||
"@types/zen-observable@0.8.3":
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3"
|
||||
integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.57.1":
|
||||
version "5.59.9"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.9.tgz#2604cfaf2b306e120044f901e20c8ed926debf15"
|
||||
@ -292,11 +294,6 @@ ajv@^6.10.0, ajv@^6.12.4:
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ansi-regex@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
|
||||
integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
|
||||
|
||||
ansi-regex@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
|
||||
@ -307,11 +304,6 @@ ansi-regex@^5.0.1:
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||
|
||||
ansi-styles@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||
integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
|
||||
|
||||
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
|
||||
@ -324,10 +316,10 @@ any-promise@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
|
||||
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
|
||||
|
||||
app-root-path@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.0.0.tgz#210b6f43873227e18a4b810a032283311555d5ad"
|
||||
integrity sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==
|
||||
app-root-path@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86"
|
||||
integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==
|
||||
|
||||
arg@^4.1.0:
|
||||
version "4.1.3"
|
||||
@ -423,6 +415,13 @@ brace-expansion@^1.1.7:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
brace-expansion@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
|
||||
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
|
||||
braces@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||
@ -465,18 +464,7 @@ callsites@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||
|
||||
chalk@^1.1.1:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
|
||||
integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
|
||||
dependencies:
|
||||
ansi-styles "^2.2.1"
|
||||
escape-string-regexp "^1.0.2"
|
||||
has-ansi "^2.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.1.0:
|
||||
chalk@^4.0.0, chalk@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||
@ -505,6 +493,15 @@ cliui@^7.0.2:
|
||||
strip-ansi "^6.0.0"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
cliui@^8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
|
||||
integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
|
||||
dependencies:
|
||||
string-width "^4.2.0"
|
||||
strip-ansi "^6.0.1"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
color-convert@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
|
||||
@ -553,6 +550,13 @@ crypto@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037"
|
||||
integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==
|
||||
|
||||
date-fns@^2.29.3:
|
||||
version "2.30.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
|
||||
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.21.0"
|
||||
|
||||
debug@^3.2.7:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
|
||||
@ -560,7 +564,7 @@ debug@^3.2.7:
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^4.1.1, debug@^4.3.1:
|
||||
debug@^4.1.1:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
|
||||
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
|
||||
@ -658,10 +662,10 @@ dotenv@^10.0.0:
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
|
||||
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
|
||||
|
||||
dotenv@^8.2.0:
|
||||
version "8.6.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
|
||||
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
|
||||
dotenv@^16.0.3:
|
||||
version "16.3.1"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
|
||||
integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
@ -746,7 +750,7 @@ escalade@^3.1.1:
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
|
||||
|
||||
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
|
||||
escape-string-regexp@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
@ -1054,11 +1058,6 @@ fastq@^1.6.0:
|
||||
dependencies:
|
||||
reusify "^1.0.4"
|
||||
|
||||
figlet@^1.1.1:
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.5.2.tgz#dda34ff233c9a48e36fcff6741aeb5bafe49b634"
|
||||
integrity sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==
|
||||
|
||||
file-entry-cache@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
|
||||
@ -1203,17 +1202,16 @@ glob@^7.1.3:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^7.1.6:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
|
||||
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
|
||||
glob@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
|
||||
integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
minimatch "^5.0.1"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
globals@^13.19.0:
|
||||
version "13.20.0"
|
||||
@ -1274,13 +1272,6 @@ graphemer@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
|
||||
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
|
||||
|
||||
has-ansi@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
|
||||
integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=
|
||||
dependencies:
|
||||
ansi-regex "^2.0.0"
|
||||
|
||||
has-bigints@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
|
||||
@ -1594,7 +1585,7 @@ isexe@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
|
||||
|
||||
js-yaml@^4.0.0, js-yaml@^4.1.0:
|
||||
js-yaml@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
|
||||
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
|
||||
@ -1705,6 +1696,13 @@ minimatch@^3.0.5, minimatch@^3.1.2:
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@^5.0.1:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
|
||||
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist@^1.2.0:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
@ -1715,10 +1713,10 @@ minimist@^1.2.6:
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
|
||||
mkdirp@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
mkdirp@^2.1.3:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19"
|
||||
integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
@ -1897,11 +1895,6 @@ parent-module@^1.0.0:
|
||||
dependencies:
|
||||
callsites "^3.0.0"
|
||||
|
||||
parent-require@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/parent-require/-/parent-require-1.0.0.tgz#746a167638083a860b0eef6732cb27ed46c32977"
|
||||
integrity sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=
|
||||
|
||||
parse5-htmlparser2-tree-adapter@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
|
||||
@ -2014,6 +2007,11 @@ reflect-metadata@^0.1.13:
|
||||
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
|
||||
integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
|
||||
|
||||
regenerator-runtime@^0.13.11:
|
||||
version "0.13.11"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
|
||||
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
|
||||
|
||||
regexp-tree@~0.1.1:
|
||||
version "0.1.27"
|
||||
resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd"
|
||||
@ -2114,11 +2112,6 @@ safe-regex@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
sax@>=0.6.0:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||
|
||||
semver@^6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
@ -2199,6 +2192,15 @@ string-width@^4.1.0, string-width@^4.2.0:
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string.prototype.trim@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533"
|
||||
@ -2233,13 +2235,6 @@ string_decoder@~1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
strip-ansi@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
||||
integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
|
||||
dependencies:
|
||||
ansi-regex "^2.0.0"
|
||||
|
||||
strip-ansi@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
|
||||
@ -2274,11 +2269,6 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||
|
||||
supports-color@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
||||
integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
|
||||
|
||||
supports-color@^7.1.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||
@ -2376,11 +2366,6 @@ tslib@^1.8.1:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.1.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
||||
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
|
||||
|
||||
tslib@^2.5.0:
|
||||
version "2.5.3"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913"
|
||||
@ -2414,28 +2399,26 @@ typed-array-length@^1.0.4:
|
||||
for-each "^0.3.3"
|
||||
is-typed-array "^1.1.9"
|
||||
|
||||
typeorm@^0.2.38:
|
||||
version "0.2.38"
|
||||
resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.38.tgz#2af08079919f6ab04cd17017f9faa2c8d5cd566f"
|
||||
integrity sha512-M6Y3KQcAREQcphOVJciywf4mv6+A0I/SeR+lWNjKsjnQ+a3XcMwGYMGL0Jonsx3H0Cqlf/3yYqVki1jIXSK/xg==
|
||||
typeorm@^0.3.16:
|
||||
version "0.3.17"
|
||||
resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.17.tgz#a73c121a52e4fbe419b596b244777be4e4b57949"
|
||||
integrity sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig==
|
||||
dependencies:
|
||||
"@sqltools/formatter" "^1.2.2"
|
||||
app-root-path "^3.0.0"
|
||||
"@sqltools/formatter" "^1.2.5"
|
||||
app-root-path "^3.1.0"
|
||||
buffer "^6.0.3"
|
||||
chalk "^4.1.0"
|
||||
chalk "^4.1.2"
|
||||
cli-highlight "^2.1.11"
|
||||
debug "^4.3.1"
|
||||
dotenv "^8.2.0"
|
||||
glob "^7.1.6"
|
||||
js-yaml "^4.0.0"
|
||||
mkdirp "^1.0.4"
|
||||
date-fns "^2.29.3"
|
||||
debug "^4.3.4"
|
||||
dotenv "^16.0.3"
|
||||
glob "^8.1.0"
|
||||
mkdirp "^2.1.3"
|
||||
reflect-metadata "^0.1.13"
|
||||
sha.js "^2.4.11"
|
||||
tslib "^2.1.0"
|
||||
xml2js "^0.4.23"
|
||||
yargonaut "^1.1.4"
|
||||
yargs "^17.0.1"
|
||||
zen-observable-ts "^1.0.0"
|
||||
tslib "^2.5.0"
|
||||
uuid "^9.0.0"
|
||||
yargs "^17.6.2"
|
||||
|
||||
typescript@^4.3.5:
|
||||
version "4.3.5"
|
||||
@ -2474,6 +2457,11 @@ uuid@^8.3.2:
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
uuid@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
||||
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||
|
||||
which-boxed-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
||||
@ -2523,19 +2511,6 @@ wrappy@1:
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
xml2js@^0.4.23:
|
||||
version "0.4.23"
|
||||
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
|
||||
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
|
||||
dependencies:
|
||||
sax ">=0.6.0"
|
||||
xmlbuilder "~11.0.0"
|
||||
|
||||
xmlbuilder@~11.0.0:
|
||||
version "11.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
|
||||
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
|
||||
|
||||
y18n@^5.0.5:
|
||||
version "5.0.8"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||
@ -2551,20 +2526,16 @@ yallist@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yargonaut@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/yargonaut/-/yargonaut-1.1.4.tgz#c64f56432c7465271221f53f5cc517890c3d6e0c"
|
||||
integrity sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==
|
||||
dependencies:
|
||||
chalk "^1.1.1"
|
||||
figlet "^1.1.1"
|
||||
parent-require "^1.0.0"
|
||||
|
||||
yargs-parser@^20.2.2:
|
||||
version "20.2.9"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
|
||||
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
|
||||
|
||||
yargs-parser@^21.1.1:
|
||||
version "21.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
|
||||
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
|
||||
|
||||
yargs@^16.0.0:
|
||||
version "16.2.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
|
||||
@ -2578,18 +2549,18 @@ yargs@^16.0.0:
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^20.2.2"
|
||||
|
||||
yargs@^17.0.1:
|
||||
version "17.2.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea"
|
||||
integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==
|
||||
yargs@^17.6.2:
|
||||
version "17.7.2"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
|
||||
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
|
||||
dependencies:
|
||||
cliui "^7.0.2"
|
||||
cliui "^8.0.1"
|
||||
escalade "^3.1.1"
|
||||
get-caller-file "^2.0.5"
|
||||
require-directory "^2.1.1"
|
||||
string-width "^4.2.0"
|
||||
string-width "^4.2.3"
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^20.2.2"
|
||||
yargs-parser "^21.1.1"
|
||||
|
||||
yn@3.1.1:
|
||||
version "3.1.1"
|
||||
@ -2600,16 +2571,3 @@ yocto-queue@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
zen-observable-ts@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz#2d1aa9d79b87058e9b75698b92791c1838551f83"
|
||||
integrity sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==
|
||||
dependencies:
|
||||
"@types/zen-observable" "0.8.3"
|
||||
zen-observable "0.8.15"
|
||||
|
||||
zen-observable@0.8.15:
|
||||
version "0.8.15"
|
||||
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
|
||||
integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==
|
||||
|
||||
@ -234,7 +234,8 @@ crontab -l
|
||||
|
||||
This show all existing entries of the crontab for user `gradido`
|
||||
|
||||
To install/add the cronjob for a daily backup at 3:00am please
|
||||
To install/add the cronjob for a daily backup at 3:00am please,
|
||||
To install/add the cronjob for a daily klicktipp export at 4:00am please,
|
||||
|
||||
Run:
|
||||
|
||||
@ -244,4 +245,5 @@ crontab -e
|
||||
and insert the following line
|
||||
```bash
|
||||
0 3 * * * ~/gradido/deployment/bare_metal/backup.sh
|
||||
0 4 * * * cd ~/gradido/backend/ && yarn klicktipp && cd
|
||||
```
|
||||
|
||||
@ -21,9 +21,7 @@
|
||||
"dotenv": "10.0.0",
|
||||
"log4js": "^6.7.1",
|
||||
"nodemon": "^2.0.20",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.1.2",
|
||||
"typescript": "^4.9.4",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -46,7 +44,9 @@
|
||||
"eslint-plugin-security": "^1.7.1",
|
||||
"prettier": "^2.8.7",
|
||||
"jest": "^27.2.4",
|
||||
"ts-jest": "^27.0.5"
|
||||
"ts-jest": "^27.0.5",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
|
||||
@ -340,7 +340,7 @@ describe('federation', () => {
|
||||
},
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
result = await DbFederatedCommunity.find({ foreign: true })
|
||||
result = await DbFederatedCommunity.find({ where: { foreign: true } })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -601,7 +601,7 @@ describe('federation', () => {
|
||||
{ api: 'toolong api', url: 'some valid url' },
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
result = await DbFederatedCommunity.find({ foreign: true })
|
||||
result = await DbFederatedCommunity.find({ where: { foreign: true } })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -655,7 +655,7 @@ describe('federation', () => {
|
||||
},
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
result = await DbFederatedCommunity.find({ foreign: true })
|
||||
result = await DbFederatedCommunity.find({ where: { foreign: true } })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -791,7 +791,7 @@ describe('federation', () => {
|
||||
]),
|
||||
),
|
||||
)
|
||||
result = await DbFederatedCommunity.find({ foreign: true })
|
||||
result = await DbFederatedCommunity.find({ where: { foreign: true } })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
@ -217,7 +217,7 @@ async function writeFederatedHomeCommunityEntries(pubKey: string): Promise<Commu
|
||||
async function writeHomeCommunityEntry(keyPair: KeyPair): Promise<void> {
|
||||
try {
|
||||
// check for existing homeCommunity entry
|
||||
let homeCom = await DbCommunity.findOne({ foreign: false })
|
||||
let homeCom = await DbCommunity.findOne({ where: { foreign: false } })
|
||||
if (homeCom) {
|
||||
// simply update the existing entry, but it MUST keep the ID and UUID because of possible relations
|
||||
homeCom.publicKey = keyPair.publicKey
|
||||
|
||||
@ -3,7 +3,7 @@ import { logger } from '@/server/logger'
|
||||
|
||||
const getDBVersion = async (): Promise<string | null> => {
|
||||
try {
|
||||
const dbVersion = await Migration.findOne({ order: { version: 'DESC' } })
|
||||
const [dbVersion] = await Migration.find({ order: { version: 'DESC' }, take: 1 })
|
||||
return dbVersion ? dbVersion.fileName : null
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
|
||||
@ -1 +1,2 @@
|
||||
node_modules
|
||||
playwright
|
||||
|
||||
@ -2,13 +2,13 @@ module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
cypress: true,
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['cypress', 'prettier', '@typescript-eslint' /*, 'jest' */],
|
||||
extends: [
|
||||
'standard',
|
||||
'eslint:recommended',
|
||||
'plugin:cypress/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
],
|
||||
|
||||
@ -6,7 +6,7 @@ let emailLink: string
|
||||
|
||||
async function setupNodeEvents(
|
||||
on: Cypress.PluginEvents,
|
||||
config: Cypress.PluginConfigOptions
|
||||
config: Cypress.PluginConfigOptions,
|
||||
): Promise<Cypress.PluginConfigOptions> {
|
||||
await addCucumberPreprocessorPlugin(on, config)
|
||||
|
||||
@ -14,7 +14,7 @@ async function setupNodeEvents(
|
||||
'file:preprocessor',
|
||||
browserify(config, {
|
||||
typescript: require.resolve('typescript'),
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
on('task', {
|
||||
@ -41,7 +41,6 @@ export default defineConfig({
|
||||
e2e: {
|
||||
specPattern: '**/*.feature',
|
||||
excludeSpecPattern: '*.js',
|
||||
experimentalSessionAndOrigin: true,
|
||||
baseUrl: 'http://localhost:3000',
|
||||
chromeWebSecurity: false,
|
||||
defaultCommandTimeout: 10000,
|
||||
@ -49,10 +48,7 @@ export default defineConfig({
|
||||
viewportHeight: 720,
|
||||
viewportWidth: 1280,
|
||||
video: false,
|
||||
retries: {
|
||||
runMode: 2,
|
||||
openMode: 0,
|
||||
},
|
||||
retries: 0,
|
||||
env: {
|
||||
backendURL: 'http://localhost:4000',
|
||||
mailserverURL: 'http://localhost:1080',
|
||||
|
||||
38
e2e-tests/cypress/e2e/SendCoins.feature
Normal file
38
e2e-tests/cypress/e2e/SendCoins.feature
Normal file
@ -0,0 +1,38 @@
|
||||
Feature: Send coins
|
||||
As a user
|
||||
I want to send and receive GDD
|
||||
I want to see transaction details on overview and transactions pages
|
||||
|
||||
# Background:
|
||||
# Given the following "users" are in the database:
|
||||
# | email | password | name |
|
||||
# | bob@baumeister.de | Aa12345_ | Bob Baumeister |
|
||||
# | raeuber@hotzenplotz.de | Aa12345_ | Räuber Hotzenplotz |
|
||||
|
||||
Scenario: Send GDD to other user
|
||||
Given the user is logged in as "bob@baumeister.de" "Aa12345_"
|
||||
And the user navigates to page "/send"
|
||||
When the user fills the send form with "<receiverEmail>" "<amount>" "<memoText>"
|
||||
And the user submits the send form
|
||||
Then the transaction details are presented for confirmation "<receiverEmail>" "<amount>" "<memoText>" "<senderBalance>" "<newSenderBalance>"
|
||||
When the user submits the transaction by confirming
|
||||
Then the "<receiverName>" and "<amount>" are displayed on the "send" page
|
||||
When the user navigates to page "/transactions"
|
||||
Then the "<receiverName>" and "<amount>" are displayed on the "transactions" page
|
||||
|
||||
Examples:
|
||||
| receiverName | receiverEmail | amount | memoText | senderBalance | newSenderBalance |
|
||||
| Räuber Hotzenplotz | raeuber@hotzenplotz.de | 120.50 | Some memo text | 515.11 | 394.61 |
|
||||
|
||||
Scenario: Receive GDD from other user
|
||||
Given the user is logged in as "raeuber@hotzenplotz.de" "Aa12345_"
|
||||
And the user receives the transaction e-mail about "<amount>" GDD from "<senderName>"
|
||||
When the user opens the "transaction" link in the browser
|
||||
Then the "<senderName>" and "120.50" are displayed on the "overview" page
|
||||
When the user navigates to page "/transactions"
|
||||
Then the "<senderName>" and "120.50" are displayed on the "transactions" page
|
||||
|
||||
Examples:
|
||||
| senderName | amount |
|
||||
| Bob der Baumeister | 120,50 |
|
||||
|
||||
@ -2,13 +2,27 @@ Feature: User Authentication - reset password
|
||||
As a user
|
||||
I want to reset my password from the sign in page
|
||||
|
||||
# TODO for these pre-conditions utilize seeding or API check, if user exists in test system
|
||||
# Background:
|
||||
# Given the following "users" are in the database:
|
||||
# | email | password | name |
|
||||
# | raeuber@hotzenplotz.de | Aa12345_ | Räuber Hotzenplotz |
|
||||
|
||||
Scenario: Reset password as not registered user
|
||||
Given the user navigates to page "/login"
|
||||
And the user navigates to the forgot password page
|
||||
When the user enters the e-mail address "not@registered.com"
|
||||
And the user submits the e-mail form
|
||||
Then the user receives no password reset e-mail
|
||||
|
||||
Scenario: Reset password as deleted user
|
||||
# Given the following "users" are in the database:
|
||||
# | email | password | name |
|
||||
# | stephen@hawking.uk | Aa12345_ | Stephen Hawking |
|
||||
Given the user navigates to page "/login"
|
||||
And the user navigates to the forgot password page
|
||||
When the user enters the e-mail address "stephen@hawking.uk"
|
||||
And the user submits the e-mail form
|
||||
Then the user receives no password reset e-mail
|
||||
|
||||
Scenario: Reset password from signin page successfully
|
||||
# Given the following "users" are in the database:
|
||||
# | email | password | name |
|
||||
# | raeuber@hotzenplotz.de | Aa12345_ | Räuber Hotzenplotz |
|
||||
Given the user navigates to page "/login"
|
||||
And the user navigates to the forgot password page
|
||||
When the user enters the e-mail address "raeuber@hotzenplotz.de"
|
||||
@ -23,3 +37,6 @@ Feature: User Authentication - reset password
|
||||
And the user cannot login
|
||||
But the user submits the credentials "raeuber@hotzenplotz.de" "12345Aa_"
|
||||
And the user is logged in with username "Räuber Hotzenplotz"
|
||||
|
||||
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
export class OverviewPage {
|
||||
navbarName = '[data-test="navbar-item-username"]'
|
||||
rightLastTransactionsList = '.rightside-last-transactions'
|
||||
|
||||
goto() {
|
||||
cy.visit('/overview')
|
||||
|
||||
@ -14,9 +14,7 @@ export class ResetPasswordPage {
|
||||
}
|
||||
|
||||
repeatNewPassword(password: string) {
|
||||
cy.get(this.newPasswordRepeatInput)
|
||||
.find('input[type=password]')
|
||||
.type(password)
|
||||
cy.get(this.newPasswordRepeatInput).find('input[type=password]').type(password)
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
25
e2e-tests/cypress/e2e/models/SendPage.ts
Normal file
25
e2e-tests/cypress/e2e/models/SendPage.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/// <reference types='cypress' />
|
||||
|
||||
export class SendPage {
|
||||
confirmationBox = '.transaction-confirm-send'
|
||||
submitBtn = '.btn-gradido'
|
||||
|
||||
enterReceiverEmail(email: string) {
|
||||
cy.get('[data-test="input-identifier"]').find('input').clear().type(email)
|
||||
return this
|
||||
}
|
||||
|
||||
enterAmount(amount: string) {
|
||||
cy.get('[data-test="input-amount"]').find('input').clear().type(amount)
|
||||
return this
|
||||
}
|
||||
|
||||
enterMemoText(text: string) {
|
||||
cy.get('[data-test="input-textarea"]').find('textarea').clear().type(text)
|
||||
return this
|
||||
}
|
||||
|
||||
submit() {
|
||||
cy.get(this.submitBtn).click()
|
||||
}
|
||||
}
|
||||
@ -8,10 +8,7 @@ export class UserEMailSite {
|
||||
emailSubject = '.subject'
|
||||
|
||||
openRecentPasswordResetEMail() {
|
||||
cy.get(this.emailList)
|
||||
.find('email-item')
|
||||
.filter(':contains(asswor)')
|
||||
.click()
|
||||
cy.get(this.emailList).find('email-item').filter(':contains(asswor)').click()
|
||||
expect(cy.get(this.emailSubject)).to('contain', 'asswor')
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import './e2e'
|
||||
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface Chainable<Subject> {
|
||||
login(email: string, password: string): Chainable<any>
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Given, Then, When } from '@badeball/cypress-cucumber-preprocessor'
|
||||
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
||||
import { OverviewPage } from '../../e2e/models/OverviewPage'
|
||||
import { SideNavMenu } from '../../e2e/models/SideNavMenu'
|
||||
import { Toasts } from '../../e2e/models/Toasts'
|
||||
@ -9,12 +9,9 @@ Given('the user navigates to page {string}', (page: string) => {
|
||||
|
||||
// login related
|
||||
|
||||
Given(
|
||||
'the user is logged in as {string} {string}',
|
||||
(email: string, password: string) => {
|
||||
cy.login(email, password)
|
||||
}
|
||||
)
|
||||
Given('the user is logged in as {string} {string}', (email: string, password: string) => {
|
||||
cy.login(email, password)
|
||||
})
|
||||
|
||||
Then('the user is logged in with username {string}', (username: string) => {
|
||||
const overviewPage = new OverviewPage()
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Then, When } from '@badeball/cypress-cucumber-preprocessor'
|
||||
import { OverviewPage } from '../../e2e/models/OverviewPage'
|
||||
import { ResetPasswordPage } from '../../e2e/models/ResetPasswordPage'
|
||||
import { UserEMailSite } from '../../e2e/models/UserEMailSite'
|
||||
|
||||
const userEMailSite = new UserEMailSite()
|
||||
const resetPasswordPage = new ResetPasswordPage()
|
||||
|
||||
Then('the user receives an e-mail containing the {string} link', (linkName: string) => {
|
||||
let emailSubject: string
|
||||
@ -18,14 +18,20 @@ Then('the user receives an e-mail containing the {string} link', (linkName: stri
|
||||
emailSubject = 'asswor'
|
||||
linkPattern = /\/reset-password\/[0-9]+\d/
|
||||
break
|
||||
case 'transaction':
|
||||
emailSubject = 'Gradido gesendet'
|
||||
linkPattern = /\/overview/
|
||||
break
|
||||
default:
|
||||
throw new Error(`Error in "Then the user receives an e-mail containing the {string} link" step: incorrect linkname string "${linkName}"`)
|
||||
throw new Error(
|
||||
`Error in "Then the user receives an e-mail containing the {string} link" step: incorrect linkname string "${linkName}"`,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
cy.origin(
|
||||
Cypress.env('mailserverURL'),
|
||||
{ args: { emailSubject, linkPattern, userEMailSite } },
|
||||
({ emailSubject, linkPattern, userEMailSite }) => {
|
||||
({ emailSubject, linkPattern, userEMailSite }) => {
|
||||
cy.visit('/') // navigate to user's e-mail site (on fake mail server)
|
||||
cy.get(userEMailSite.emailInbox).should('be.visible')
|
||||
|
||||
@ -35,11 +41,9 @@ Then('the user receives an e-mail containing the {string} link', (linkName: stri
|
||||
.first()
|
||||
.click()
|
||||
|
||||
cy.get(userEMailSite.emailMeta)
|
||||
.find(userEMailSite.emailSubject)
|
||||
.contains(emailSubject)
|
||||
cy.get(userEMailSite.emailMeta).find(userEMailSite.emailSubject).contains(emailSubject)
|
||||
|
||||
cy.get('.email-content', { timeout: 2000})
|
||||
cy.get('.email-content', { timeout: 2000 })
|
||||
.find('.plain-text')
|
||||
.contains(linkPattern)
|
||||
.invoke('text')
|
||||
@ -47,13 +51,79 @@ Then('the user receives an e-mail containing the {string} link', (linkName: stri
|
||||
const emailLink = text.match(linkPattern)[0]
|
||||
cy.task('setEmailLink', emailLink)
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
Then('the user receives no password reset e-mail', () => {
|
||||
cy.origin(Cypress.env('mailserverURL'), { args: { userEMailSite } }, ({ userEMailSite }) => {
|
||||
cy.visit('/')
|
||||
cy.wait(300)
|
||||
cy.get(userEMailSite.emailInbox).should('be.visible')
|
||||
|
||||
cy.get(userEMailSite.emailList).then(($emailList) => {
|
||||
const emailItems = $emailList.find('.email-item')
|
||||
if (emailItems.length > 0) {
|
||||
expect(emailItems.filter(`:contains("asswor")`).length).to.equal(0)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When(
|
||||
'the user receives the transaction e-mail about {string} GDD from {string}',
|
||||
(amount: string, senderName: string) => {
|
||||
cy.origin(
|
||||
Cypress.env('mailserverURL'),
|
||||
{ args: { amount, senderName, userEMailSite } },
|
||||
({ amount, senderName, userEMailSite }) => {
|
||||
const subject = `${senderName} hat dir ${amount} Gradido gesendet`
|
||||
const linkPattern = /\/transactions/
|
||||
cy.visit('/')
|
||||
cy.get(userEMailSite.emailInbox).should('be.visible')
|
||||
|
||||
cy.get(userEMailSite.emailList)
|
||||
.find('.email-item')
|
||||
.filter(`:contains(${subject})`)
|
||||
.first()
|
||||
.click()
|
||||
|
||||
cy.get(userEMailSite.emailMeta).find(userEMailSite.emailSubject).contains(subject)
|
||||
|
||||
cy.get('.email-content', { timeout: 2000 })
|
||||
.find('.plain-text')
|
||||
.contains(linkPattern)
|
||||
.invoke('text')
|
||||
.then((text) => {
|
||||
const emailLink = text.match(linkPattern)[0]
|
||||
cy.task('setEmailLink', emailLink)
|
||||
})
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
When('the user opens the {string} link in the browser', (linkName: string) => {
|
||||
const resetPasswordPage = new ResetPasswordPage()
|
||||
cy.task('getEmailLink').then((emailLink) => {
|
||||
cy.visit(emailLink)
|
||||
})
|
||||
cy.get(resetPasswordPage.newPasswordInput).should('be.visible')
|
||||
|
||||
switch (linkName) {
|
||||
case 'activation':
|
||||
cy.get(resetPasswordPage.newPasswordInput).should('be.visible')
|
||||
break
|
||||
case 'password reset':
|
||||
cy.get(resetPasswordPage.newPasswordInput).should('be.visible')
|
||||
break
|
||||
case 'transaction':
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const overviewPage = new OverviewPage()
|
||||
cy.get(overviewPage.rightLastTransactionsList).should('be.visible')
|
||||
break
|
||||
default:
|
||||
throw new Error(
|
||||
`Error in "Then the user receives an e-mail containing the {string} link" step: incorrect link name string "${linkName}"`,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@ -0,0 +1,90 @@
|
||||
import { Then, When } from '@badeball/cypress-cucumber-preprocessor'
|
||||
import { SendPage } from '../../e2e/models/SendPage'
|
||||
|
||||
const sendPage = new SendPage()
|
||||
|
||||
When(
|
||||
'the user fills the send form with {string} {string} {string}',
|
||||
(email: string, amount: string, memoText: string) => {
|
||||
sendPage.enterReceiverEmail(email)
|
||||
sendPage.enterAmount(amount)
|
||||
sendPage.enterMemoText(memoText)
|
||||
},
|
||||
)
|
||||
|
||||
When('the user submits the send form', () => {
|
||||
sendPage.submit()
|
||||
cy.get(sendPage.confirmationBox).should('be.visible')
|
||||
})
|
||||
|
||||
Then(
|
||||
'the transaction details are presented for confirmation {string} {string} {string} {string} {string}',
|
||||
(
|
||||
receiverEmail: string,
|
||||
sendAmount: string,
|
||||
memoText: string,
|
||||
senderBalance: string,
|
||||
newSenderBalance: string,
|
||||
) => {
|
||||
cy.get('.transaction-confirm-send').contains(receiverEmail)
|
||||
cy.get('.transaction-confirm-send').contains(`+ ${sendAmount} GDD`)
|
||||
cy.get('.transaction-confirm-send').contains(memoText)
|
||||
cy.get('.transaction-confirm-send').contains(`+ ${senderBalance} GDD`)
|
||||
cy.get('.transaction-confirm-send').contains(`− ${sendAmount} GDD`)
|
||||
cy.get('.transaction-confirm-send').contains(`+ ${newSenderBalance} GDD`)
|
||||
},
|
||||
)
|
||||
|
||||
When('the user submits the transaction by confirming', () => {
|
||||
cy.intercept({
|
||||
method: 'POST',
|
||||
url: '/graphql',
|
||||
hostname: 'localhost',
|
||||
}).as('sendCoins')
|
||||
|
||||
sendPage.submit()
|
||||
|
||||
cy.wait('@sendCoins').then((interception) => {
|
||||
cy.wrap(interception.response?.statusCode).should('eq', 200)
|
||||
cy.wrap(interception.request.body).should(
|
||||
'have.property',
|
||||
'query',
|
||||
`mutation ($identifier: String!, $amount: Decimal!, $memo: String!) {
|
||||
sendCoins(identifier: $identifier, amount: $amount, memo: $memo)
|
||||
}
|
||||
`,
|
||||
)
|
||||
cy.wrap(interception.response?.body)
|
||||
.should('have.nested.property', 'data.sendCoins')
|
||||
.and('equal', true)
|
||||
})
|
||||
cy.get('[data-test="send-transaction-success-text"]').should('be.visible')
|
||||
})
|
||||
|
||||
Then(
|
||||
'the {string} and {string} are displayed on the {string} page',
|
||||
(name: string, amount: string, page: string) => {
|
||||
switch (page) {
|
||||
case 'overview':
|
||||
cy.get('.align-items-center').contains(`${name}`)
|
||||
cy.get('.align-items-center').contains(`${amount} GDD`)
|
||||
break
|
||||
case 'send':
|
||||
cy.get('.align-items-center').contains(`${name}`)
|
||||
cy.get('.align-items-center').contains(`${amount} GDD`)
|
||||
break
|
||||
case 'transactions':
|
||||
cy.get('div.mt-3 > div > div.test-list-group-item')
|
||||
.eq(0)
|
||||
.contains('div.gdd-transaction-list-item-name', `${name}`)
|
||||
cy.get('div.mt-3 > div > div.test-list-group-item')
|
||||
.eq(0)
|
||||
.contains('[data-test="transaction-amount"]', `${amount} GDD`)
|
||||
break
|
||||
default:
|
||||
throw new Error(
|
||||
`Error in "Then the {string} and {string} are displayed on the {string}} page" step: incorrect page name string "${page}"`,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
@ -1,4 +1,4 @@
|
||||
import { When, And } from '@badeball/cypress-cucumber-preprocessor'
|
||||
import { When } from '@badeball/cypress-cucumber-preprocessor'
|
||||
import { ForgotPasswordPage } from '../../e2e/models/ForgotPasswordPage'
|
||||
import { LoginPage } from '../../e2e/models/LoginPage'
|
||||
import { ResetPasswordPage } from '../../e2e/models/ResetPasswordPage'
|
||||
@ -13,30 +13,25 @@ When('the user submits no credentials', () => {
|
||||
loginPage.submitLogin()
|
||||
})
|
||||
|
||||
When(
|
||||
'the user submits the credentials {string} {string}',
|
||||
(email: string, password: string) => {
|
||||
cy.intercept('POST', '/graphql', (req) => {
|
||||
if (
|
||||
req.body.hasOwnProperty('query') &&
|
||||
req.body.query.includes('mutation')
|
||||
) {
|
||||
req.alias = 'login'
|
||||
}
|
||||
})
|
||||
When('the user submits the credentials {string} {string}', (email: string, password: string) => {
|
||||
cy.intercept('POST', '/graphql', (req) => {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (req.body.hasOwnProperty('query') && req.body.query.includes('mutation')) {
|
||||
req.alias = 'login'
|
||||
}
|
||||
})
|
||||
|
||||
loginPage.enterEmail(email)
|
||||
loginPage.enterPassword(password)
|
||||
loginPage.submitLogin()
|
||||
cy.wait('@login').then((interception) => {
|
||||
expect(interception.response.statusCode).equals(200)
|
||||
})
|
||||
}
|
||||
)
|
||||
loginPage.enterEmail(email)
|
||||
loginPage.enterPassword(password)
|
||||
loginPage.submitLogin()
|
||||
cy.wait('@login').then((interception) => {
|
||||
expect(interception.response.statusCode).equals(200)
|
||||
})
|
||||
})
|
||||
|
||||
// password reset related
|
||||
|
||||
And('the user navigates to the forgot password page', () => {
|
||||
When('the user navigates to the forgot password page', () => {
|
||||
loginPage.openForgotPasswordPage()
|
||||
cy.url().should('include', '/forgot-password')
|
||||
})
|
||||
@ -45,25 +40,25 @@ When('the user enters the e-mail address {string}', (email: string) => {
|
||||
forgotPasswordPage.enterEmail(email)
|
||||
})
|
||||
|
||||
And('the user submits the e-mail form', () => {
|
||||
When('the user submits the e-mail form', () => {
|
||||
forgotPasswordPage.submitEmail()
|
||||
cy.get(forgotPasswordPage.successComponent).should('be.visible')
|
||||
})
|
||||
|
||||
And('the user enters the password {string}', (password: string) => {
|
||||
When('the user enters the password {string}', (password: string) => {
|
||||
resetPasswordPage.enterNewPassword(password)
|
||||
})
|
||||
|
||||
And('the user repeats the password {string}', (password: string) => {
|
||||
When('the user repeats the password {string}', (password: string) => {
|
||||
resetPasswordPage.repeatNewPassword(password)
|
||||
})
|
||||
|
||||
And('the user submits the new password', () => {
|
||||
When('the user submits the new password', () => {
|
||||
resetPasswordPage.submitNewPassword()
|
||||
cy.get(resetPasswordPage.resetPasswordMessageBlock).should('be.visible')
|
||||
})
|
||||
|
||||
And('the user clicks the sign in button', () => {
|
||||
When('the user clicks the sign in button', () => {
|
||||
resetPasswordPage.openSigninPage()
|
||||
cy.url().should('contain', '/login')
|
||||
})
|
||||
|
||||
@ -1,28 +1,28 @@
|
||||
import { And, When } from '@badeball/cypress-cucumber-preprocessor'
|
||||
import { DataTable, When } from '@badeball/cypress-cucumber-preprocessor'
|
||||
import { ProfilePage } from '../../e2e/models/ProfilePage'
|
||||
import { Toasts } from '../../e2e/models/Toasts'
|
||||
|
||||
const profilePage = new ProfilePage()
|
||||
|
||||
And('the user opens the change password menu', () => {
|
||||
When('the user opens the change password menu', () => {
|
||||
cy.get(profilePage.openChangePassword).click()
|
||||
cy.get(profilePage.newPasswordRepeatInput).should('be.visible')
|
||||
cy.get(profilePage.submitNewPasswordBtn).should('be.disabled')
|
||||
})
|
||||
|
||||
When('the user fills the password form with:', (table) => {
|
||||
let hashedTableRows = table.rowsHash()
|
||||
When('the user fills the password form with:', (table: DataTable) => {
|
||||
const hashedTableRows = table.rowsHash()
|
||||
profilePage.enterOldPassword(hashedTableRows['Old password'])
|
||||
profilePage.enterNewPassword(hashedTableRows['New password'])
|
||||
profilePage.enterRepeatPassword(hashedTableRows['Repeat new password'])
|
||||
cy.get(profilePage.submitNewPasswordBtn).should('be.enabled')
|
||||
})
|
||||
|
||||
And('the user submits the password form', () => {
|
||||
When('the user submits the password form', () => {
|
||||
profilePage.submitPasswordForm()
|
||||
})
|
||||
|
||||
When('the user is presented a {string} message', (type: string) => {
|
||||
When('the user is presented a {string} message', () => {
|
||||
const toast = new Toasts()
|
||||
cy.get(toast.toastSlot).within(() => {
|
||||
cy.get(toast.toastTypeSuccess)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { And, When } from '@badeball/cypress-cucumber-preprocessor'
|
||||
import { When } from '@badeball/cypress-cucumber-preprocessor'
|
||||
import { RegistrationPage } from '../../e2e/models/RegistrationPage'
|
||||
|
||||
const registrationPage = new RegistrationPage()
|
||||
@ -10,14 +10,14 @@ When(
|
||||
registrationPage.enterFirstname(firstname)
|
||||
registrationPage.enterLastname(lastname)
|
||||
registrationPage.enterEmail(email)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
And('the user agrees to the privacy policy', () => {
|
||||
When('the user agrees to the privacy policy', () => {
|
||||
registrationPage.checkPrivacyCheckbox()
|
||||
})
|
||||
|
||||
And('the user submits the registration form', () => {
|
||||
When('the user submits the registration form', () => {
|
||||
registrationPage.submitRegistrationForm()
|
||||
cy.get(registrationPage.RegistrationThanxHeadline).should('be.visible')
|
||||
cy.get(registrationPage.RegistrationThanxText).should('be.visible')
|
||||
|
||||
@ -18,20 +18,20 @@
|
||||
"lint": "eslint --max-warnings=0 --ext .js,.ts ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@badeball/cypress-cucumber-preprocessor": "^12.0.0",
|
||||
"@badeball/cypress-cucumber-preprocessor": "^18.0.1",
|
||||
"@cypress/browserify-preprocessor": "^3.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.38.0",
|
||||
"@typescript-eslint/parser": "^5.38.0",
|
||||
"cypress": "^12.7.0",
|
||||
"cypress": "^12.16.0",
|
||||
"eslint": "^8.23.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-cypress": "^2.13.3",
|
||||
"eslint-plugin-import": "^2.23.4",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.7.4"
|
||||
|
||||
1089
e2e-tests/yarn.lock
1089
e2e-tests/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -11,8 +11,10 @@ export class PublicKeyResolver {
|
||||
async getPublicKey(): Promise<GetPublicKeyResult> {
|
||||
logger.debug(`getPublicKey() via apiVersion=1_0 ...`)
|
||||
const homeCom = await DbFederatedCommunity.findOneOrFail({
|
||||
foreign: false,
|
||||
apiVersion: '1_0',
|
||||
where: {
|
||||
foreign: false,
|
||||
apiVersion: '1_0',
|
||||
},
|
||||
})
|
||||
logger.info(`getPublicKey()-1_0... return publicKey=${homeCom.publicKey}`)
|
||||
return new GetPublicKeyResult(homeCom.publicKey.toString())
|
||||
|
||||
@ -3,7 +3,7 @@ import { federationLogger as logger } from '@/server/logger'
|
||||
|
||||
const getDBVersion = async (): Promise<string | null> => {
|
||||
try {
|
||||
const dbVersion = await Migration.findOne({ order: { version: 'DESC' } })
|
||||
const [dbVersion] = await Migration.find({ order: { version: 'DESC' }, take: 1 })
|
||||
return dbVersion ? dbVersion.fileName : null
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
|
||||
@ -73,8 +73,8 @@ describe('ContributionMessagesFormular', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('emitted "update-state" with data', async () => {
|
||||
expect(wrapper.emitted('update-state')).toEqual(
|
||||
it('emitted "update-status" with data', async () => {
|
||||
expect(wrapper.emitted('update-status')).toEqual(
|
||||
expect.arrayContaining([expect.arrayContaining([42])]),
|
||||
)
|
||||
})
|
||||
|
||||
@ -55,7 +55,7 @@ export default {
|
||||
})
|
||||
.then((result) => {
|
||||
this.$emit('get-list-contribution-messages', false)
|
||||
this.$emit('update-state', this.contributionId)
|
||||
this.$emit('update-status', this.contributionId)
|
||||
this.form.text = ''
|
||||
this.toastSuccess(this.$t('message.reply'))
|
||||
this.isSubmitting = false
|
||||
|
||||
@ -8,7 +8,7 @@ describe('ContributionMessagesList', () => {
|
||||
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
state: 'IN_PROGRESS',
|
||||
status: 'IN_PROGRESS',
|
||||
messages: [],
|
||||
}
|
||||
|
||||
@ -40,13 +40,13 @@ describe('ContributionMessagesList', () => {
|
||||
expect(wrapper.findComponent({ name: 'ContributionMessagesFormular' }).exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('update State', () => {
|
||||
describe('update Status', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.updateState()
|
||||
wrapper.vm.updateStatus()
|
||||
})
|
||||
|
||||
it('emits getListContributionMessages', async () => {
|
||||
expect(wrapper.vm.$emit('update-state')).toBeTruthy()
|
||||
expect(wrapper.vm.$emit('update-status')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -7,10 +7,10 @@
|
||||
</div>
|
||||
<div>
|
||||
<contribution-messages-formular
|
||||
v-if="['PENDING', 'IN_PROGRESS'].includes(state)"
|
||||
v-if="['PENDING', 'IN_PROGRESS'].includes(status)"
|
||||
:contributionId="contributionId"
|
||||
v-on="$listeners"
|
||||
@update-state="updateState"
|
||||
@update-status="updateStatus"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -37,7 +37,7 @@ export default {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
state: {
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
@ -47,8 +47,8 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateState(id) {
|
||||
this.$emit('update-state', id)
|
||||
updateStatus(id) {
|
||||
this.$emit('update-status', id)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ const mocks = {
|
||||
describe('ContributionMessagesList', () => {
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
messages: [
|
||||
{
|
||||
id: 111,
|
||||
|
||||
@ -119,11 +119,11 @@ describe('ContributionList', () => {
|
||||
|
||||
describe('update status', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.findComponent({ name: 'ContributionListItem' }).vm.$emit('update-state', { id: 2 })
|
||||
wrapper.findComponent({ name: 'ContributionListItem' }).vm.$emit('update-status', { id: 2 })
|
||||
})
|
||||
|
||||
it('emits update status', () => {
|
||||
expect(wrapper.emitted('update-state')).toEqual([[{ id: 2 }]])
|
||||
expect(wrapper.emitted('update-status')).toEqual([[{ id: 2 }]])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,26 +2,26 @@
|
||||
<div class="contribution-list">
|
||||
<div class="mb-3" v-for="item in items" :key="item.id + 'a'">
|
||||
<contribution-list-item
|
||||
v-if="item.state === 'IN_PROGRESS'"
|
||||
v-if="item.status === 'IN_PROGRESS'"
|
||||
v-bind="item"
|
||||
@closeAllOpenCollapse="$emit('closeAllOpenCollapse')"
|
||||
:contributionId="item.id"
|
||||
:allContribution="allContribution"
|
||||
@update-contribution-form="updateContributionForm"
|
||||
@delete-contribution="deleteContribution"
|
||||
@update-state="updateState"
|
||||
@update-status="updateStatus"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3" v-for="item2 in items" :key="item2.id">
|
||||
<contribution-list-item
|
||||
v-if="item2.state !== 'IN_PROGRESS'"
|
||||
v-if="item2.status !== 'IN_PROGRESS'"
|
||||
v-bind="item2"
|
||||
@closeAllOpenCollapse="$emit('closeAllOpenCollapse')"
|
||||
:contributionId="item2.id"
|
||||
:allContribution="allContribution"
|
||||
@update-contribution-form="updateContributionForm"
|
||||
@delete-contribution="deleteContribution"
|
||||
@update-state="updateState"
|
||||
@update-status="updateStatus"
|
||||
/>
|
||||
</div>
|
||||
<b-pagination
|
||||
@ -85,8 +85,8 @@ export default {
|
||||
deleteContribution(item) {
|
||||
this.$emit('delete-contribution', item)
|
||||
},
|
||||
updateState(id) {
|
||||
this.$emit('update-state', id)
|
||||
updateStatus(id) {
|
||||
this.$emit('update-status', id)
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
||||
@ -14,7 +14,7 @@ describe('ContributionListItem', () => {
|
||||
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
messagesCount: 2,
|
||||
id: 1,
|
||||
createdAt: '26/07/2022',
|
||||
@ -72,8 +72,8 @@ describe('ContributionListItem', () => {
|
||||
expect(wrapper.vm.variant).toBe('success')
|
||||
})
|
||||
|
||||
it('is warning at when state is IN_PROGRESS', async () => {
|
||||
await wrapper.setProps({ state: 'IN_PROGRESS' })
|
||||
it('is warning at when status is IN_PROGRESS', async () => {
|
||||
await wrapper.setProps({ status: 'IN_PROGRESS' })
|
||||
expect(wrapper.vm.variant).toBe('205')
|
||||
})
|
||||
})
|
||||
@ -134,13 +134,13 @@ describe('ContributionListItem', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateState', () => {
|
||||
describe('updateStatus', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.vm.updateState()
|
||||
await wrapper.vm.updateStatus()
|
||||
})
|
||||
|
||||
it('emit update-state', () => {
|
||||
expect(wrapper.vm.$emit('update-state')).toBeTruthy()
|
||||
it('emit update-status', () => {
|
||||
expect(wrapper.vm.$emit('update-status')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<div
|
||||
class="contribution-list-item bg-white appBoxShadow gradido-border-radius pt-3 px-3"
|
||||
:class="state === 'IN_PROGRESS' && !allContribution ? 'pulse border border-205' : ''"
|
||||
:class="status === 'IN_PROGRESS' && !allContribution ? 'pulse border border-205' : ''"
|
||||
>
|
||||
<b-row>
|
||||
<b-col cols="3" lg="2" md="2">
|
||||
@ -26,7 +26,7 @@
|
||||
<div class="mt-3 font-weight-bold">{{ $t('contributionText') }}</div>
|
||||
<div class="mb-3 text-break word-break">{{ memo }}</div>
|
||||
<div
|
||||
v-if="state === 'IN_PROGRESS'"
|
||||
v-if="status === 'IN_PROGRESS'"
|
||||
class="text-205 pointer hover-font-bold"
|
||||
@click="visible = !visible"
|
||||
>
|
||||
@ -37,11 +37,11 @@
|
||||
<div class="small">
|
||||
{{ $t('creation') }} {{ $t('(') }}{{ amount / 20 }} {{ $t('h') }}{{ $t(')') }}
|
||||
</div>
|
||||
<div v-if="state === 'DENIED' && allContribution" class="font-weight-bold">
|
||||
<div v-if="status === 'DENIED' && allContribution" class="font-weight-bold">
|
||||
<b-icon icon="x-circle" variant="danger"></b-icon>
|
||||
{{ $t('contribution.alert.denied') }}
|
||||
</div>
|
||||
<div v-if="state === 'DELETED'" class="small">
|
||||
<div v-if="status === 'DELETED'" class="small">
|
||||
{{ $t('contribution.deleted') }}
|
||||
</div>
|
||||
<div v-else class="font-weight-bold">{{ amount | GDD }}</div>
|
||||
@ -53,12 +53,12 @@
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row
|
||||
v-if="(!['CONFIRMED', 'DELETED'].includes(state) && !allContribution) || messagesCount > 0"
|
||||
v-if="(!['CONFIRMED', 'DELETED'].includes(status) && !allContribution) || messagesCount > 0"
|
||||
class="p-2"
|
||||
>
|
||||
<b-col cols="3" class="mr-auto text-center">
|
||||
<div
|
||||
v-if="!['CONFIRMED', 'DELETED'].includes(state) && !allContribution && !moderatorId"
|
||||
v-if="!['CONFIRMED', 'DELETED'].includes(status) && !allContribution && !moderatorId"
|
||||
class="test-delete-contribution pointer mr-3"
|
||||
@click="deleteContribution({ id })"
|
||||
>
|
||||
@ -69,7 +69,7 @@
|
||||
</b-col>
|
||||
<b-col cols="3" class="text-center">
|
||||
<div
|
||||
v-if="!['CONFIRMED', 'DELETED'].includes(state) && !allContribution && !moderatorId"
|
||||
v-if="!['CONFIRMED', 'DELETED'].includes(status) && !allContribution && !moderatorId"
|
||||
class="test-edit-contribution pointer mr-3"
|
||||
@click="
|
||||
$emit('update-contribution-form', {
|
||||
@ -95,10 +95,10 @@
|
||||
<b-collapse :id="collapsId" class="mt-2" v-model="visible">
|
||||
<contribution-messages-list
|
||||
:messages="messages_get"
|
||||
:state="state"
|
||||
:status="status"
|
||||
:contributionId="contributionId"
|
||||
@get-list-contribution-messages="getListContributionMessages"
|
||||
@update-state="updateState"
|
||||
@update-status="updateStatus"
|
||||
/>
|
||||
</b-collapse>
|
||||
</div>
|
||||
@ -161,7 +161,7 @@ export default {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
state: {
|
||||
status: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
@ -197,14 +197,14 @@ export default {
|
||||
if (this.deletedAt) return 'trash'
|
||||
if (this.deniedAt) return 'x-circle'
|
||||
if (this.confirmedAt) return 'check'
|
||||
if (this.state === 'IN_PROGRESS') return 'question'
|
||||
if (this.status === 'IN_PROGRESS') return 'question'
|
||||
return 'bell-fill'
|
||||
},
|
||||
variant() {
|
||||
if (this.deletedAt) return 'danger'
|
||||
if (this.deniedAt) return 'warning'
|
||||
if (this.confirmedAt) return 'success'
|
||||
if (this.state === 'IN_PROGRESS') return '205'
|
||||
if (this.status === 'IN_PROGRESS') return '205'
|
||||
return 'primary'
|
||||
},
|
||||
date() {
|
||||
@ -245,8 +245,8 @@ export default {
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
updateState(id) {
|
||||
this.$emit('update-state', id)
|
||||
updateStatus(id) {
|
||||
this.$emit('update-status', id)
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user