mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Resolve merge conflict.
This commit is contained in:
commit
c9f8b70201
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -528,7 +528,7 @@ jobs:
|
|||||||
report_name: Coverage Backend
|
report_name: Coverage Backend
|
||||||
type: lcov
|
type: lcov
|
||||||
result_path: ./backend/coverage/lcov.info
|
result_path: ./backend/coverage/lcov.info
|
||||||
min_coverage: 68
|
min_coverage: 74
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|||||||
@ -5,6 +5,7 @@ const localVue = global.localVue
|
|||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
|
$d: jest.fn((d) => d),
|
||||||
}
|
}
|
||||||
|
|
||||||
const propsData = {
|
const propsData = {
|
||||||
|
|||||||
@ -15,7 +15,10 @@
|
|||||||
<b-collapse v-model="visible" id="newContribution" class="mt-2">
|
<b-collapse v-model="visible" id="newContribution" class="mt-2">
|
||||||
<b-card>
|
<b-card>
|
||||||
<p class="h2 ml-5">{{ $t('contributionLink.contributionLinks') }}</p>
|
<p class="h2 ml-5">{{ $t('contributionLink.contributionLinks') }}</p>
|
||||||
<contribution-link-form :contributionLinkData="contributionLinkData" />
|
<contribution-link-form
|
||||||
|
:contributionLinkData="contributionLinkData"
|
||||||
|
@get-contribution-links="$emit('get-contribution-links')"
|
||||||
|
/>
|
||||||
</b-card>
|
</b-card>
|
||||||
</b-collapse>
|
</b-collapse>
|
||||||
|
|
||||||
@ -24,6 +27,7 @@
|
|||||||
v-if="count > 0"
|
v-if="count > 0"
|
||||||
:items="items"
|
:items="items"
|
||||||
@editContributionLinkData="editContributionLinkData"
|
@editContributionLinkData="editContributionLinkData"
|
||||||
|
@get-contribution-links="$emit('get-contribution-links')"
|
||||||
/>
|
/>
|
||||||
<div v-else>{{ $t('contributionLink.noContributionLinks') }}</div>
|
<div v-else>{{ $t('contributionLink.noContributionLinks') }}</div>
|
||||||
</b-card-text>
|
</b-card-text>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ContributionLinkForm from './ContributionLinkForm.vue'
|
import ContributionLinkForm from './ContributionLinkForm.vue'
|
||||||
import { toastErrorSpy } from '../../test/testSetup'
|
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
|
||||||
|
import { createContributionLink } from '@/graphql/createContributionLink.js'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
@ -72,48 +73,70 @@ describe('ContributionLinkForm', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// describe('successfull submit', () => {
|
describe('successfull submit', () => {
|
||||||
// beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// mockAPIcall.mockResolvedValue({
|
apolloMutateMock.mockResolvedValue({
|
||||||
// data: {
|
data: {
|
||||||
// createContributionLink: {
|
createContributionLink: {
|
||||||
// link: 'https://localhost/redeem/CL-1a2345678',
|
link: 'https://localhost/redeem/CL-1a2345678',
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// })
|
})
|
||||||
// await wrapper.find('input.test-validFrom').setValue('2022-6-18')
|
await wrapper
|
||||||
// await wrapper.find('input.test-validTo').setValue('2022-7-18')
|
.findAllComponents({ name: 'BFormDatepicker' })
|
||||||
// await wrapper.find('input.test-name').setValue('test name')
|
.at(0)
|
||||||
// await wrapper.find('input.test-memo').setValue('test memo')
|
.vm.$emit('input', '2022-6-18')
|
||||||
// await wrapper.find('input.test-amount').setValue('100')
|
await wrapper
|
||||||
// await wrapper.find('form').trigger('submit')
|
.findAllComponents({ name: 'BFormDatepicker' })
|
||||||
// })
|
.at(1)
|
||||||
|
.vm.$emit('input', '2022-7-18')
|
||||||
|
await wrapper.find('input.test-name').setValue('test name')
|
||||||
|
await wrapper.find('textarea.test-memo').setValue('test memo')
|
||||||
|
await wrapper.find('input.test-amount').setValue('100')
|
||||||
|
await wrapper.find('form').trigger('submit')
|
||||||
|
})
|
||||||
|
|
||||||
// it('calls the API', () => {
|
it('calls the API', () => {
|
||||||
// expect(mockAPIcall).toHaveBeenCalledWith(
|
expect(apolloMutateMock).toHaveBeenCalledWith({
|
||||||
// expect.objectContaining({
|
mutation: createContributionLink,
|
||||||
// variables: {
|
variables: {
|
||||||
// link: 'https://localhost/redeem/CL-1a2345678',
|
validFrom: '2022-6-18',
|
||||||
// },
|
validTo: '2022-7-18',
|
||||||
// }),
|
name: 'test name',
|
||||||
// )
|
amount: '100',
|
||||||
// })
|
memo: 'test memo',
|
||||||
|
cycle: 'ONCE',
|
||||||
|
maxPerCycle: 1,
|
||||||
|
maxAmountPerMonth: '0',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// it('displays the new username', () => {
|
it('toasts a succes message', () => {
|
||||||
// expect(wrapper.find('div.display-username').text()).toEqual('@username')
|
expect(toastSuccessSpy).toBeCalledWith('https://localhost/redeem/CL-1a2345678')
|
||||||
// })
|
})
|
||||||
// })
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('send createContributionLink with error', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
apolloMutateMock.mockRejectedValue({ message: 'OUCH!' })
|
|
||||||
wrapper = Wrapper()
|
|
||||||
wrapper.vm.onSubmit()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('toasts an error message', () => {
|
describe('send createContributionLink with error', () => {
|
||||||
expect(toastErrorSpy).toBeCalledWith('contributionLink.noStartDate')
|
beforeEach(async () => {
|
||||||
|
apolloMutateMock.mockRejectedValue({ message: 'OUCH!' })
|
||||||
|
await wrapper
|
||||||
|
.findAllComponents({ name: 'BFormDatepicker' })
|
||||||
|
.at(0)
|
||||||
|
.vm.$emit('input', '2022-6-18')
|
||||||
|
await wrapper
|
||||||
|
.findAllComponents({ name: 'BFormDatepicker' })
|
||||||
|
.at(1)
|
||||||
|
.vm.$emit('input', '2022-7-18')
|
||||||
|
await wrapper.find('input.test-name').setValue('test name')
|
||||||
|
await wrapper.find('textarea.test-memo').setValue('test memo')
|
||||||
|
await wrapper.find('input.test-amount').setValue('100')
|
||||||
|
await wrapper.find('form').trigger('submit')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toasts an error message', () => {
|
||||||
|
expect(toastErrorSpy).toBeCalledWith('OUCH!')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="contribution-link-form">
|
<div class="contribution-link-form">
|
||||||
<div v-if="updateData" class="text-light bg-info p-3">
|
|
||||||
{{ updateData }}
|
|
||||||
</div>
|
|
||||||
<b-form class="m-5" @submit.prevent="onSubmit" ref="contributionLinkForm">
|
<b-form class="m-5" @submit.prevent="onSubmit" ref="contributionLinkForm">
|
||||||
<!-- Date -->
|
<!-- Date -->
|
||||||
<b-row>
|
<b-row>
|
||||||
@ -68,34 +65,32 @@
|
|||||||
class="test-amount"
|
class="test-amount"
|
||||||
></b-form-input>
|
></b-form-input>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
<b-collapse id="collapse-2">
|
<b-row class="mb-4">
|
||||||
<b-jumbotron>
|
<b-col>
|
||||||
<b-row class="mb-4">
|
<!-- Cycle -->
|
||||||
<b-col>
|
<label for="cycle">{{ $t('contributionLink.cycle') }}</label>
|
||||||
<!-- Cycle -->
|
<b-form-select
|
||||||
<label for="cycle">{{ $t('contributionLink.cycle') }}</label>
|
v-model="form.cycle"
|
||||||
<b-form-select
|
:options="cycle"
|
||||||
v-model="form.cycle"
|
class="mb-3"
|
||||||
:options="cycle"
|
size="lg"
|
||||||
:disabled="disabled"
|
></b-form-select>
|
||||||
class="mb-3"
|
</b-col>
|
||||||
size="lg"
|
<b-col>
|
||||||
></b-form-select>
|
<!-- maxPerCycle -->
|
||||||
</b-col>
|
<label for="maxPerCycle">{{ $t('contributionLink.maxPerCycle') }}</label>
|
||||||
<b-col>
|
<b-form-select
|
||||||
<!-- maxPerCycle -->
|
v-model="form.maxPerCycle"
|
||||||
<label for="maxPerCycle">{{ $t('contributionLink.maxPerCycle') }}</label>
|
:options="maxPerCycle"
|
||||||
<b-form-select
|
:disabled="disabled"
|
||||||
v-model="form.maxPerCycle"
|
class="mb-3"
|
||||||
:options="maxPerCycle"
|
size="lg"
|
||||||
:disabled="disabled"
|
></b-form-select>
|
||||||
class="mb-3"
|
</b-col>
|
||||||
size="lg"
|
</b-row>
|
||||||
></b-form-select>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
|
|
||||||
<!-- Max amount -->
|
<!-- Max amount -->
|
||||||
|
<!--
|
||||||
<b-form-group :label="$t('contributionLink.maximumAmount')">
|
<b-form-group :label="$t('contributionLink.maximumAmount')">
|
||||||
<b-form-input
|
<b-form-input
|
||||||
v-model="form.maxAmountPerMonth"
|
v-model="form.maxAmountPerMonth"
|
||||||
@ -105,8 +100,7 @@
|
|||||||
placeholder="0"
|
placeholder="0"
|
||||||
></b-form-input>
|
></b-form-input>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
</b-jumbotron>
|
-->
|
||||||
</b-collapse>
|
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<b-button type="submit" variant="primary">{{ $t('contributionLink.create') }}</b-button>
|
<b-button type="submit" variant="primary">{{ $t('contributionLink.create') }}</b-button>
|
||||||
<b-button type="reset" variant="danger" @click.prevent="onReset">
|
<b-button type="reset" variant="danger" @click.prevent="onReset">
|
||||||
@ -143,18 +137,18 @@ export default {
|
|||||||
min: new Date(),
|
min: new Date(),
|
||||||
cycle: [
|
cycle: [
|
||||||
{ value: 'ONCE', text: this.$t('contributionLink.options.cycle.once') },
|
{ value: 'ONCE', text: this.$t('contributionLink.options.cycle.once') },
|
||||||
{ value: 'hourly', text: this.$t('contributionLink.options.cycle.hourly') },
|
// { value: 'hourly', text: this.$t('contributionLink.options.cycle.hourly') },
|
||||||
{ value: 'daily', text: this.$t('contributionLink.options.cycle.daily') },
|
{ value: 'DAILY', text: this.$t('contributionLink.options.cycle.daily') },
|
||||||
{ value: 'weekly', text: this.$t('contributionLink.options.cycle.weekly') },
|
// { value: 'weekly', text: this.$t('contributionLink.options.cycle.weekly') },
|
||||||
{ value: 'monthly', text: this.$t('contributionLink.options.cycle.monthly') },
|
// { value: 'monthly', text: this.$t('contributionLink.options.cycle.monthly') },
|
||||||
{ value: 'yearly', text: this.$t('contributionLink.options.cycle.yearly') },
|
// { value: 'yearly', text: this.$t('contributionLink.options.cycle.yearly') },
|
||||||
],
|
],
|
||||||
maxPerCycle: [
|
maxPerCycle: [
|
||||||
{ value: '1', text: '1 x' },
|
{ value: '1', text: '1 x' },
|
||||||
{ value: '2', text: '2 x' },
|
// { value: '2', text: '2 x' },
|
||||||
{ value: '3', text: '3 x' },
|
// { value: '3', text: '3 x' },
|
||||||
{ value: '4', text: '4 x' },
|
// { value: '4', text: '4 x' },
|
||||||
{ value: '5', text: '5 x' },
|
// { value: '5', text: '5 x' },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -163,7 +157,6 @@ export default {
|
|||||||
if (this.form.validFrom === null)
|
if (this.form.validFrom === null)
|
||||||
return this.toastError(this.$t('contributionLink.noStartDate'))
|
return this.toastError(this.$t('contributionLink.noStartDate'))
|
||||||
if (this.form.validTo === null) return this.toastError(this.$t('contributionLink.noEndDate'))
|
if (this.form.validTo === null) return this.toastError(this.$t('contributionLink.noEndDate'))
|
||||||
// alert(JSON.stringify(this.form))
|
|
||||||
this.$apollo
|
this.$apollo
|
||||||
.mutate({
|
.mutate({
|
||||||
mutation: createContributionLink,
|
mutation: createContributionLink,
|
||||||
@ -182,6 +175,8 @@ export default {
|
|||||||
this.link = result.data.createContributionLink.link
|
this.link = result.data.createContributionLink.link
|
||||||
this.toastSuccess(this.link)
|
this.toastSuccess(this.link)
|
||||||
this.onReset()
|
this.onReset()
|
||||||
|
this.$root.$emit('bv::toggle::collapse', 'newContribution')
|
||||||
|
this.$emit('get-contribution-links')
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.toastError(error.message)
|
this.toastError(error.message)
|
||||||
@ -194,12 +189,8 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
updateData() {
|
|
||||||
return this.contributionLinkData
|
|
||||||
},
|
|
||||||
disabled() {
|
disabled() {
|
||||||
if (this.form.cycle === 'ONCE') return true
|
return true
|
||||||
return false
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ const mockAPIcall = jest.fn()
|
|||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
|
$d: jest.fn((d) => d),
|
||||||
$apollo: {
|
$apollo: {
|
||||||
mutate: mockAPIcall,
|
mutate: mockAPIcall,
|
||||||
},
|
},
|
||||||
@ -95,7 +96,7 @@ describe('ContributionLinkList', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('toasts a success message', () => {
|
it('toasts a success message', () => {
|
||||||
expect(toastSuccessSpy).toBeCalledWith('TODO: request message deleted ')
|
expect(toastSuccessSpy).toBeCalledWith('contributionLink.deleted')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="contribution-link-list">
|
<div class="contribution-link-list">
|
||||||
<b-table striped hover :items="items" :fields="fields">
|
<b-table striped hover :items="items" :fields="fields">
|
||||||
<template #cell(delete)>
|
<template #cell(delete)="data">
|
||||||
<b-button
|
<b-button
|
||||||
variant="danger"
|
variant="danger"
|
||||||
size="md"
|
size="md"
|
||||||
class="mr-2 test-delete-link"
|
class="mr-2 test-delete-link"
|
||||||
@click="deleteContributionLink"
|
@click="deleteContributionLink(data.item.id, data.item.name)"
|
||||||
>
|
>
|
||||||
<b-icon icon="trash" variant="light"></b-icon>
|
<b-icon icon="trash" variant="light"></b-icon>
|
||||||
</b-button>
|
</b-button>
|
||||||
@ -34,7 +34,7 @@
|
|||||||
<h6 class="mb-0">{{ modalData ? modalData.name : '' }}</h6>
|
<h6 class="mb-0">{{ modalData ? modalData.name : '' }}</h6>
|
||||||
</template>
|
</template>
|
||||||
<b-card-text>
|
<b-card-text>
|
||||||
{{ modalData }}
|
{{ modalData.memo ? modalData.memo : '' }}
|
||||||
<figure-qr-code :link="modalData ? modalData.link : ''" />
|
<figure-qr-code :link="modalData ? modalData.link : ''" />
|
||||||
</b-card-text>
|
</b-card-text>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@ -64,34 +64,56 @@ export default {
|
|||||||
'amount',
|
'amount',
|
||||||
{ key: 'cycle', label: this.$t('contributionLink.cycle') },
|
{ key: 'cycle', label: this.$t('contributionLink.cycle') },
|
||||||
{ key: 'maxPerCycle', label: this.$t('contributionLink.maxPerCycle') },
|
{ key: 'maxPerCycle', label: this.$t('contributionLink.maxPerCycle') },
|
||||||
{ key: 'validFrom', label: this.$t('contributionLink.validFrom') },
|
{
|
||||||
{ key: 'validTo', label: this.$t('contributionLink.validTo') },
|
key: 'validFrom',
|
||||||
|
label: this.$t('contributionLink.validFrom'),
|
||||||
|
formatter: (value, key, item) => {
|
||||||
|
if (value) {
|
||||||
|
return this.$d(new Date(value))
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'validTo',
|
||||||
|
label: this.$t('contributionLink.validTo'),
|
||||||
|
formatter: (value, key, item) => {
|
||||||
|
if (value) {
|
||||||
|
return this.$d(new Date(value))
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
'delete',
|
'delete',
|
||||||
'edit',
|
'edit',
|
||||||
'show',
|
'show',
|
||||||
],
|
],
|
||||||
modalData: null,
|
modalData: {},
|
||||||
modalDataLink: null,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
deleteContributionLink() {
|
deleteContributionLink(id, name) {
|
||||||
this.$bvModal.msgBoxConfirm(this.$t('contributionLink.deleteNow')).then(async (value) => {
|
this.$bvModal
|
||||||
if (value)
|
.msgBoxConfirm(this.$t('contributionLink.deleteNow', { name: name }))
|
||||||
await this.$apollo
|
.then(async (value) => {
|
||||||
.mutate({
|
if (value)
|
||||||
mutation: deleteContributionLink,
|
await this.$apollo
|
||||||
variables: {
|
.mutate({
|
||||||
id: this.id,
|
mutation: deleteContributionLink,
|
||||||
},
|
variables: {
|
||||||
})
|
id: id,
|
||||||
.then(() => {
|
},
|
||||||
this.toastSuccess('TODO: request message deleted ')
|
})
|
||||||
})
|
.then(() => {
|
||||||
.catch((err) => {
|
this.toastSuccess(this.$t('contributionLink.deleted'))
|
||||||
this.toastError(err.message)
|
this.$emit('get-contribution-links')
|
||||||
})
|
})
|
||||||
})
|
.catch((err) => {
|
||||||
|
this.toastError(err.message)
|
||||||
|
})
|
||||||
|
})
|
||||||
},
|
},
|
||||||
editContributionLink(row) {
|
editContributionLink(row) {
|
||||||
this.$emit('editContributionLinkData', row)
|
this.$emit('editContributionLinkData', row)
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
v-model="form.text"
|
v-model="form.text"
|
||||||
:placeholder="$t('contributionLink.memo')"
|
:placeholder="$t('contributionLink.memo')"
|
||||||
rows="3"
|
rows="3"
|
||||||
max-rows="6"
|
|
||||||
></b-form-textarea>
|
></b-form-textarea>
|
||||||
<b-row class="mt-4 mb-6">
|
<b-row class="mt-4 mb-6">
|
||||||
<b-col>
|
<b-col>
|
||||||
|
|||||||
@ -9,50 +9,120 @@ describe('ContributionMessagesListItem', () => {
|
|||||||
const mocks = {
|
const mocks = {
|
||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
$d: jest.fn((d) => d),
|
$d: jest.fn((d) => d),
|
||||||
$store: {
|
}
|
||||||
state: {
|
|
||||||
moderator: {
|
describe('if message author has moderator role', () => {
|
||||||
id: 107,
|
const propsData = {
|
||||||
},
|
contributionId: 42,
|
||||||
|
state: 'PENDING',
|
||||||
|
message: {
|
||||||
|
id: 111,
|
||||||
|
message: 'Lorem ipsum?',
|
||||||
|
createdAt: '2022-08-29T12:23:27.000Z',
|
||||||
|
updatedAt: null,
|
||||||
|
type: 'DIALOG',
|
||||||
|
userFirstName: 'Peter',
|
||||||
|
userLastName: 'Lustig',
|
||||||
|
userId: 107,
|
||||||
|
isModerator: true,
|
||||||
|
__typename: 'ContributionMessage',
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const propsData = {
|
const ModeratorItemWrapper = () => {
|
||||||
contributionId: 42,
|
return mount(ContributionMessagesListItem, {
|
||||||
state: 'PENDING0',
|
localVue,
|
||||||
message: {
|
mocks,
|
||||||
id: 111,
|
propsData,
|
||||||
message: 'asd asda sda sda',
|
})
|
||||||
createdAt: '2022-08-29T12:23:27.000Z',
|
}
|
||||||
updatedAt: null,
|
|
||||||
type: 'DIALOG',
|
|
||||||
userFirstName: 'Peter',
|
|
||||||
userLastName: 'Lustig',
|
|
||||||
userId: 107,
|
|
||||||
__typename: 'ContributionMessage',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const Wrapper = () => {
|
describe('mount', () => {
|
||||||
return mount(ContributionMessagesListItem, {
|
beforeAll(() => {
|
||||||
localVue,
|
wrapper = ModeratorItemWrapper()
|
||||||
mocks,
|
})
|
||||||
propsData,
|
|
||||||
|
it('has a DIV .text-right.is-moderator', () => {
|
||||||
|
expect(wrapper.find('div.text-right.is-moderator').exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has the complete user name', () => {
|
||||||
|
expect(wrapper.find('div.text-right.is-moderator > span:nth-child(2)').text()).toBe(
|
||||||
|
'Peter Lustig',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has the message creation date', () => {
|
||||||
|
expect(wrapper.find('div.text-right.is-moderator > span:nth-child(3)').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',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has the message', () => {
|
||||||
|
expect(wrapper.find('div.text-right.is-moderator > div:nth-child(5)').text()).toBe(
|
||||||
|
'Lorem ipsum?',
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
|
|
||||||
describe('mount', () => {
|
describe('if message author does not have moderator role', () => {
|
||||||
beforeEach(() => {
|
const propsData = {
|
||||||
wrapper = Wrapper()
|
contributionId: 42,
|
||||||
})
|
state: 'PENDING',
|
||||||
|
message: {
|
||||||
|
id: 113,
|
||||||
|
message: 'Asda sdad ad asdasd, das Ass das Das. ',
|
||||||
|
createdAt: '2022-08-29T12:25:34.000Z',
|
||||||
|
updatedAt: null,
|
||||||
|
type: 'DIALOG',
|
||||||
|
userFirstName: 'Bibi',
|
||||||
|
userLastName: 'Bloxberg',
|
||||||
|
userId: 108,
|
||||||
|
__typename: 'ContributionMessage',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
it('has a DIV .contribution-messages-list-item', () => {
|
const ItemWrapper = () => {
|
||||||
expect(wrapper.find('div.contribution-messages-list-item').exists()).toBe(true)
|
return mount(ContributionMessagesListItem, {
|
||||||
})
|
localVue,
|
||||||
|
mocks,
|
||||||
|
propsData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
it('props.message.default', () => {
|
describe('mount', () => {
|
||||||
expect(wrapper.vm.$options.props.message.default.call()).toEqual({})
|
beforeAll(() => {
|
||||||
|
wrapper = ItemWrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a DIV .text-left.is-not-moderator', () => {
|
||||||
|
expect(wrapper.find('div.text-left.is-not-moderator').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',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has the message creation date', () => {
|
||||||
|
expect(wrapper.find('div.is-not-moderator.text-left > span:nth-child(3)').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(
|
||||||
|
'Asda sdad ad asdasd, das Ass das Das.',
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,27 +1,44 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="contribution-messages-list-item">
|
<div class="contribution-messages-list-item">
|
||||||
<is-moderator v-if="message.isModerator" :message="message"></is-moderator>
|
<div v-if="message.isModerator" class="text-right is-moderator">
|
||||||
<is-not-moderator v-else :message="message"></is-not-moderator>
|
<b-avatar square :text="initialLetters" 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>
|
||||||
|
<div class="mt-2">{{ message.message }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-left is-not-moderator">
|
||||||
|
<b-avatar :text="initialLetters" 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>
|
||||||
|
<div class="mt-2">{{ message.message }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import IsModerator from '@/components/ContributionMessages/slots/IsModerator.vue'
|
|
||||||
import IsNotModerator from '@/components/ContributionMessages/slots/IsNotModerator.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ContributionMessagesListItem',
|
name: 'ContributionMessagesListItem',
|
||||||
components: {
|
|
||||||
IsModerator,
|
|
||||||
IsNotModerator,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
message: {
|
message: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
default() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</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); */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,49 +0,0 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
|
||||||
import IsModerator from './IsModerator.vue'
|
|
||||||
|
|
||||||
const localVue = global.localVue
|
|
||||||
|
|
||||||
describe('IsModerator', () => {
|
|
||||||
let wrapper
|
|
||||||
|
|
||||||
const mocks = {
|
|
||||||
$t: jest.fn((t) => t),
|
|
||||||
$d: jest.fn((d) => d),
|
|
||||||
}
|
|
||||||
|
|
||||||
const propsData = {
|
|
||||||
message: {
|
|
||||||
id: 111,
|
|
||||||
message: 'asd asda sda sda',
|
|
||||||
createdAt: '2022-08-29T12:23:27.000Z',
|
|
||||||
updatedAt: null,
|
|
||||||
type: 'DIALOG',
|
|
||||||
userFirstName: 'Peter',
|
|
||||||
userLastName: 'Lustig',
|
|
||||||
userId: 107,
|
|
||||||
__typename: 'ContributionMessage',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const Wrapper = () => {
|
|
||||||
return mount(IsModerator, {
|
|
||||||
localVue,
|
|
||||||
mocks,
|
|
||||||
propsData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('mount', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a DIV .slot-is-moderator', () => {
|
|
||||||
expect(wrapper.find('div.slot-is-moderator').exists()).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('props.message.default', () => {
|
|
||||||
expect(wrapper.vm.$options.props.message.default.call()).toEqual({})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="slot-is-moderator">
|
|
||||||
<div class="text-right">
|
|
||||||
<b-avatar square :text="initialLetters" 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>
|
|
||||||
<div class="mt-2">{{ message.message }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
message: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
initialLetters() {
|
|
||||||
return `${this.message.userFirstName[0]} ${this.message.userLastName[0]}`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.slot-is-moderator {
|
|
||||||
clear: both;
|
|
||||||
float: right;
|
|
||||||
width: 75%;
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
|
||||||
import IsNotModerator from './IsNotModerator.vue'
|
|
||||||
|
|
||||||
const localVue = global.localVue
|
|
||||||
|
|
||||||
describe('IsNotModerator', () => {
|
|
||||||
let wrapper
|
|
||||||
|
|
||||||
const mocks = {
|
|
||||||
$t: jest.fn((t) => t),
|
|
||||||
$d: jest.fn((d) => d),
|
|
||||||
}
|
|
||||||
|
|
||||||
const propsData = {
|
|
||||||
message: {
|
|
||||||
id: 113,
|
|
||||||
message: 'asda sdad ad asdasd ',
|
|
||||||
createdAt: '2022-08-29T12:25:34.000Z',
|
|
||||||
updatedAt: null,
|
|
||||||
type: 'DIALOG',
|
|
||||||
userFirstName: 'Bibi',
|
|
||||||
userLastName: 'Bloxberg',
|
|
||||||
userId: 108,
|
|
||||||
__typename: 'ContributionMessage',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const Wrapper = () => {
|
|
||||||
return mount(IsNotModerator, {
|
|
||||||
localVue,
|
|
||||||
mocks,
|
|
||||||
propsData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('mount', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a DIV .slot-is-not-moderator', () => {
|
|
||||||
expect(wrapper.find('div.slot-is-not-moderator').exists()).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('props.message.default', () => {
|
|
||||||
expect(wrapper.vm.$options.props.message.default.call()).toEqual({})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="slot-is-not-moderator">
|
|
||||||
<div>
|
|
||||||
<b-avatar :text="initialLetters" 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>
|
|
||||||
<div class="mt-2">{{ message.message }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
message: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
initialLetters() {
|
|
||||||
return `${this.message.userFirstName[0]} ${this.message.userLastName[0]}`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.slot-is-not-moderator {
|
|
||||||
clear: both;
|
|
||||||
width: 75%;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -6,30 +6,29 @@ const localVue = global.localVue
|
|||||||
|
|
||||||
const apolloQueryMock = jest.fn().mockResolvedValue({
|
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
creationTransactionList: [
|
creationTransactionList: {
|
||||||
{
|
contributionCount: 2,
|
||||||
id: 1,
|
contributionList: [
|
||||||
amount: 100,
|
{
|
||||||
balanceDate: 0,
|
id: 1,
|
||||||
creationDate: new Date(),
|
amount: 5.8,
|
||||||
memo: 'Testing',
|
createdAt: '2022-09-21T11:09:51.000Z',
|
||||||
linkedUser: {
|
confirmedAt: null,
|
||||||
firstName: 'Gradido',
|
contributionDate: '2022-08-01T00:00:00.000Z',
|
||||||
lastName: 'Akademie',
|
memo: 'für deine Hilfe, Fräulein Rottenmeier',
|
||||||
|
state: 'PENDING',
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
id: 2,
|
||||||
id: 2,
|
amount: '47',
|
||||||
amount: 200,
|
createdAt: '2022-09-21T11:09:28.000Z',
|
||||||
balanceDate: 0,
|
confirmedAt: '2022-09-21T11:09:28.000Z',
|
||||||
creationDate: new Date(),
|
contributionDate: '2022-08-01T00:00:00.000Z',
|
||||||
memo: 'Testing 2',
|
memo: 'für deine Hilfe, Frau Holle',
|
||||||
linkedUser: {
|
state: 'CONFIRMED',
|
||||||
firstName: 'Gradido',
|
|
||||||
lastName: 'Akademie',
|
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -43,7 +42,7 @@ const mocks = {
|
|||||||
|
|
||||||
const propsData = {
|
const propsData = {
|
||||||
userId: 1,
|
userId: 1,
|
||||||
fields: ['date', 'balance', 'name', 'memo', 'decay'],
|
fields: ['createdAt', 'contributionDate', 'confirmedAt', 'amount', 'memo'],
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('CreationTransactionList', () => {
|
describe('CreationTransactionList', () => {
|
||||||
@ -63,7 +62,7 @@ describe('CreationTransactionList', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
pageSize: 25,
|
pageSize: 10,
|
||||||
order: 'DESC',
|
order: 'DESC',
|
||||||
userId: 1,
|
userId: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,7 +1,44 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="component-creation-transaction-list">
|
<div class="component-creation-transaction-list">
|
||||||
<div class="h3">{{ $t('transactionlist.title') }}</div>
|
<div class="h3">{{ $t('transactionlist.title') }}</div>
|
||||||
<b-table striped hover :fields="fields" :items="items"></b-table>
|
<b-table striped hover :fields="fields" :items="items">
|
||||||
|
<template #cell(contributionDate)="data">
|
||||||
|
<div class="font-weight-bold">
|
||||||
|
{{ $d(new Date(data.item.contributionDate), 'month') }}
|
||||||
|
</div>
|
||||||
|
<div>{{ $d(new Date(data.item.contributionDate)) }}</div>
|
||||||
|
</template>
|
||||||
|
</b-table>
|
||||||
|
<div>
|
||||||
|
<b-pagination
|
||||||
|
pills
|
||||||
|
size="lg"
|
||||||
|
v-model="currentPage"
|
||||||
|
:per-page="perPage"
|
||||||
|
:total-rows="rows"
|
||||||
|
align="center"
|
||||||
|
:hide-ellipsis="true"
|
||||||
|
></b-pagination>
|
||||||
|
<b-button v-b-toggle.collapse-1 variant="light" size="sm">{{ $t('help.help') }}</b-button>
|
||||||
|
<b-collapse id="collapse-1" class="mt-2">
|
||||||
|
<div>
|
||||||
|
{{ $t('transactionlist.submitted') }} {{ $t('math.equals') }}
|
||||||
|
{{ $t('help.transactionlist.submitted') }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ $t('transactionlist.period') }} {{ $t('math.equals') }}
|
||||||
|
{{ $t('help.transactionlist.periods') }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ $t('transactionlist.confirmed') }} {{ $t('math.equals') }}
|
||||||
|
{{ $t('help.transactionlist.confirmed') }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ $t('transactionlist.state') }} {{ $t('math.equals') }}
|
||||||
|
{{ $t('help.transactionlist.state') }}
|
||||||
|
</div>
|
||||||
|
</b-collapse>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
@ -13,14 +50,37 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
items: [],
|
||||||
|
rows: 0,
|
||||||
|
currentPage: 1,
|
||||||
|
perPage: 10,
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
key: 'creationDate',
|
key: 'createdAt',
|
||||||
label: this.$t('transactionlist.date'),
|
label: this.$t('transactionlist.submitted'),
|
||||||
formatter: (value, key, item) => {
|
formatter: (value, key, item) => {
|
||||||
return this.$d(new Date(value))
|
return this.$d(new Date(value))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'contributionDate',
|
||||||
|
label: this.$t('transactionlist.period'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'confirmedAt',
|
||||||
|
label: this.$t('transactionlist.confirmed'),
|
||||||
|
formatter: (value, key, item) => {
|
||||||
|
if (value) {
|
||||||
|
return this.$d(new Date(value))
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'state',
|
||||||
|
label: this.$t('transactionlist.state'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'amount',
|
key: 'amount',
|
||||||
label: this.$t('transactionlist.amount'),
|
label: this.$t('transactionlist.amount'),
|
||||||
@ -28,23 +88,8 @@ export default {
|
|||||||
return `${value} GDD`
|
return `${value} GDD`
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'linkedUser',
|
|
||||||
label: this.$t('transactionlist.community'),
|
|
||||||
formatter: (value, key, item) => {
|
|
||||||
return `${value.firstName} ${value.lastName}`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ key: 'memo', label: this.$t('transactionlist.memo') },
|
{ key: 'memo', label: this.$t('transactionlist.memo') },
|
||||||
{
|
|
||||||
key: 'balanceDate',
|
|
||||||
label: this.$t('transactionlist.balanceDate'),
|
|
||||||
formatter: (value, key, item) => {
|
|
||||||
return this.$d(new Date(value))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
items: [],
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -53,14 +98,15 @@ export default {
|
|||||||
.query({
|
.query({
|
||||||
query: creationTransactionList,
|
query: creationTransactionList,
|
||||||
variables: {
|
variables: {
|
||||||
currentPage: 1,
|
currentPage: this.currentPage,
|
||||||
pageSize: 25,
|
pageSize: this.perPage,
|
||||||
order: 'DESC',
|
order: 'DESC',
|
||||||
userId: parseInt(this.userId),
|
userId: parseInt(this.userId),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
this.items = result.data.creationTransactionList
|
this.rows = result.data.creationTransactionList.contributionCount
|
||||||
|
this.items = result.data.creationTransactionList.contributionList
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.toastError(error.message)
|
this.toastError(error.message)
|
||||||
@ -70,5 +116,10 @@ export default {
|
|||||||
created() {
|
created() {
|
||||||
this.getTransactions()
|
this.getTransactions()
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
currentPage() {
|
||||||
|
this.getTransactions()
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -3,11 +3,15 @@ import NavBar from './NavBar.vue'
|
|||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const apolloMutateMock = jest.fn()
|
||||||
const storeDispatchMock = jest.fn()
|
const storeDispatchMock = jest.fn()
|
||||||
const routerPushMock = jest.fn()
|
const routerPushMock = jest.fn()
|
||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
|
$apollo: {
|
||||||
|
mutate: apolloMutateMock,
|
||||||
|
},
|
||||||
$store: {
|
$store: {
|
||||||
state: {
|
state: {
|
||||||
openCreations: 1,
|
openCreations: 1,
|
||||||
@ -69,5 +73,9 @@ describe('NavBar', () => {
|
|||||||
it('dispatches logout to store', () => {
|
it('dispatches logout to store', () => {
|
||||||
expect(storeDispatchMock).toBeCalledWith('logout')
|
expect(storeDispatchMock).toBeCalledWith('logout')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('has called logout mutation', () => {
|
||||||
|
expect(apolloMutateMock).toBeCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -28,14 +28,18 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import CONFIG from '../config'
|
import CONFIG from '../config'
|
||||||
|
import { logout } from '../graphql/logout'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'navbar',
|
name: 'navbar',
|
||||||
methods: {
|
methods: {
|
||||||
logout() {
|
async logout() {
|
||||||
window.location.assign(CONFIG.WALLET_URL)
|
window.location.assign(CONFIG.WALLET_URL)
|
||||||
// window.location = CONFIG.WALLET_URL
|
// window.location = CONFIG.WALLET_URL
|
||||||
this.$store.dispatch('logout')
|
this.$store.dispatch('logout')
|
||||||
|
await this.$apollo.mutate({
|
||||||
|
mutation: logout,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
wallet() {
|
wallet() {
|
||||||
window.location = CONFIG.WALLET_AUTH_URL.replace('{token}', this.$store.state.token)
|
window.location = CONFIG.WALLET_AUTH_URL.replace('{token}', this.$store.state.token)
|
||||||
|
|||||||
@ -8,14 +8,15 @@ export const creationTransactionList = gql`
|
|||||||
order: $order
|
order: $order
|
||||||
userId: $userId
|
userId: $userId
|
||||||
) {
|
) {
|
||||||
id
|
contributionCount
|
||||||
amount
|
contributionList {
|
||||||
balanceDate
|
id
|
||||||
creationDate
|
amount
|
||||||
memo
|
createdAt
|
||||||
linkedUser {
|
confirmedAt
|
||||||
firstName
|
contributionDate
|
||||||
lastName
|
memo
|
||||||
|
state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
admin/src/graphql/logout.js
Normal file
7
admin/src/graphql/logout.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export const logout = gql`
|
||||||
|
mutation {
|
||||||
|
logout
|
||||||
|
}
|
||||||
|
`
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import gql from 'graphql-tag'
|
|
||||||
|
|
||||||
export const showContributionLink = gql`
|
|
||||||
query ($id: Int!) {
|
|
||||||
showContributionLink {
|
|
||||||
id
|
|
||||||
validFrom
|
|
||||||
validTo
|
|
||||||
name
|
|
||||||
memo
|
|
||||||
amount
|
|
||||||
cycle
|
|
||||||
maxPerCycle
|
|
||||||
maxAmountPerMonth
|
|
||||||
code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
@ -7,8 +7,8 @@
|
|||||||
"contributionLinks": "Beitragslinks",
|
"contributionLinks": "Beitragslinks",
|
||||||
"create": "Anlegen",
|
"create": "Anlegen",
|
||||||
"cycle": "Zyklus",
|
"cycle": "Zyklus",
|
||||||
"deleteNow": "Automatische Creations wirklich löschen?",
|
"deleted": "Automatische Schöpfung gelöscht!",
|
||||||
"maximumAmount": "maximaler Betrag",
|
"deleteNow": "Automatische Creations '{name}' wirklich löschen?",
|
||||||
"maxPerCycle": "Wiederholungen",
|
"maxPerCycle": "Wiederholungen",
|
||||||
"memo": "Nachricht",
|
"memo": "Nachricht",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@ -20,11 +20,7 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"cycle": {
|
"cycle": {
|
||||||
"daily": "täglich",
|
"daily": "täglich",
|
||||||
"hourly": "stündlich",
|
"once": "einmalig"
|
||||||
"monthly": "monatlich",
|
|
||||||
"once": "einmalig",
|
|
||||||
"weekly": "wöchentlich",
|
|
||||||
"yearly": "jährlich"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"validFrom": "Startdatum",
|
"validFrom": "Startdatum",
|
||||||
@ -74,10 +70,20 @@
|
|||||||
"submit": "Senden"
|
"submit": "Senden"
|
||||||
},
|
},
|
||||||
"GDD": "GDD",
|
"GDD": "GDD",
|
||||||
|
"help": {
|
||||||
|
"help": "Hilfe",
|
||||||
|
"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]",
|
||||||
|
"submitted": "Wann wurde es vom Mitglied eingereicht"
|
||||||
|
}
|
||||||
|
},
|
||||||
"hide_details": "Details verbergen",
|
"hide_details": "Details verbergen",
|
||||||
"lastname": "Nachname",
|
"lastname": "Nachname",
|
||||||
"math": {
|
"math": {
|
||||||
"colon": ":",
|
"colon": ":",
|
||||||
|
"equals": "=",
|
||||||
"exclaim": "!",
|
"exclaim": "!",
|
||||||
"pipe": "|",
|
"pipe": "|",
|
||||||
"plus": "+"
|
"plus": "+"
|
||||||
@ -133,10 +139,11 @@
|
|||||||
},
|
},
|
||||||
"transactionlist": {
|
"transactionlist": {
|
||||||
"amount": "Betrag",
|
"amount": "Betrag",
|
||||||
"balanceDate": "Schöpfungsdatum",
|
"confirmed": "Bestätigt",
|
||||||
"community": "Gemeinschaft",
|
|
||||||
"date": "Datum",
|
|
||||||
"memo": "Nachricht",
|
"memo": "Nachricht",
|
||||||
|
"period": "Zeitraum",
|
||||||
|
"state": "Status",
|
||||||
|
"submitted": "Eingereicht",
|
||||||
"title": "Alle geschöpften Transaktionen für den Nutzer"
|
"title": "Alle geschöpften Transaktionen für den Nutzer"
|
||||||
},
|
},
|
||||||
"undelete_user": "Nutzer wiederherstellen",
|
"undelete_user": "Nutzer wiederherstellen",
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
"contributionLinks": "Contribution Links",
|
"contributionLinks": "Contribution Links",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
"cycle": "Cycle",
|
"cycle": "Cycle",
|
||||||
"deleteNow": "Do you really delete automatic creations?",
|
"deleted": "Automatic creation deleted!",
|
||||||
"maximumAmount": "Maximum amount",
|
"deleteNow": "Do you really delete automatic creations '{name}'?",
|
||||||
"maxPerCycle": "Repetition",
|
"maxPerCycle": "Repetition",
|
||||||
"memo": "Memo",
|
"memo": "Memo",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@ -20,11 +20,7 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"cycle": {
|
"cycle": {
|
||||||
"daily": "daily",
|
"daily": "daily",
|
||||||
"hourly": "hourly",
|
"once": "once"
|
||||||
"monthly": "monthly",
|
|
||||||
"once": "once",
|
|
||||||
"weekly": "weekly",
|
|
||||||
"yearly": "yearly"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"validFrom": "Start-date",
|
"validFrom": "Start-date",
|
||||||
@ -74,10 +70,20 @@
|
|||||||
"submit": "Send"
|
"submit": "Send"
|
||||||
},
|
},
|
||||||
"GDD": "GDD",
|
"GDD": "GDD",
|
||||||
|
"help": {
|
||||||
|
"help": "Help",
|
||||||
|
"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 = denied, CONFIRMED = confirmed]",
|
||||||
|
"submitted": "When was it submitted by the member"
|
||||||
|
}
|
||||||
|
},
|
||||||
"hide_details": "Hide details",
|
"hide_details": "Hide details",
|
||||||
"lastname": "Lastname",
|
"lastname": "Lastname",
|
||||||
"math": {
|
"math": {
|
||||||
"colon": ":",
|
"colon": ":",
|
||||||
|
"equals": "=",
|
||||||
"exclaim": "!",
|
"exclaim": "!",
|
||||||
"pipe": "|",
|
"pipe": "|",
|
||||||
"plus": "+"
|
"plus": "+"
|
||||||
@ -133,10 +139,11 @@
|
|||||||
},
|
},
|
||||||
"transactionlist": {
|
"transactionlist": {
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
"balanceDate": "Creation date",
|
"confirmed": "Confirmed",
|
||||||
"community": "Community",
|
|
||||||
"date": "Date",
|
|
||||||
"memo": "Message",
|
"memo": "Message",
|
||||||
|
"period": "Period",
|
||||||
|
"state": "State",
|
||||||
|
"submitted": "Submitted",
|
||||||
"title": "All creation-transactions for the user"
|
"title": "All creation-transactions for the user"
|
||||||
},
|
},
|
||||||
"undelete_user": "Undelete User",
|
"undelete_user": "Undelete User",
|
||||||
|
|||||||
@ -78,6 +78,7 @@ const storeCommitMock = jest.fn()
|
|||||||
const mocks = {
|
const mocks = {
|
||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
$n: jest.fn((n) => n),
|
$n: jest.fn((n) => n),
|
||||||
|
$d: jest.fn((d) => d),
|
||||||
$apollo: {
|
$apollo: {
|
||||||
query: apolloQueryMock,
|
query: apolloQueryMock,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -28,7 +28,11 @@
|
|||||||
</b-link>
|
</b-link>
|
||||||
</b-card-text>
|
</b-card-text>
|
||||||
</b-card>
|
</b-card>
|
||||||
<contribution-link :items="items" :count="count" />
|
<contribution-link
|
||||||
|
:items="items"
|
||||||
|
:count="count"
|
||||||
|
@get-contribution-links="getContributionLinks"
|
||||||
|
/>
|
||||||
<community-statistic class="mt-5" v-model="statistics" />
|
<community-statistic class="mt-5" v-model="statistics" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -5,41 +5,66 @@
|
|||||||
{
|
{
|
||||||
"type": "dateFile",
|
"type": "dateFile",
|
||||||
"filename": "../logs/backend/access.log",
|
"filename": "../logs/backend/access.log",
|
||||||
"pattern": "%d{ISO8601} %p %c %X{user} %f:%l %m",
|
"pattern": "yyyy-MM-dd",
|
||||||
|
"layout":
|
||||||
|
{
|
||||||
|
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||||
|
},
|
||||||
"keepFileExt" : true,
|
"keepFileExt" : true,
|
||||||
"fileNameSep" : "_"
|
"fileNameSep" : "_",
|
||||||
|
"numBackups" : 30
|
||||||
},
|
},
|
||||||
"apollo":
|
"apollo":
|
||||||
{
|
{
|
||||||
"type": "dateFile",
|
"type": "dateFile",
|
||||||
"filename": "../logs/backend/apollo.log",
|
"filename": "../logs/backend/apollo.log",
|
||||||
"pattern": "%d{ISO8601} %p %c %m",
|
"pattern": "yyyy-MM-dd",
|
||||||
|
"layout":
|
||||||
|
{
|
||||||
|
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||||
|
},
|
||||||
"keepFileExt" : true,
|
"keepFileExt" : true,
|
||||||
"fileNameSep" : "_"
|
"fileNameSep" : "_",
|
||||||
|
"numBackups" : 30
|
||||||
},
|
},
|
||||||
"backend":
|
"backend":
|
||||||
{
|
{
|
||||||
"type": "dateFile",
|
"type": "dateFile",
|
||||||
"filename": "../logs/backend/backend.log",
|
"filename": "../logs/backend/backend.log",
|
||||||
"pattern": "%d{ISO8601} %p %c %X{user} %f:%l %m",
|
"pattern": "yyyy-MM-dd",
|
||||||
|
"layout":
|
||||||
|
{
|
||||||
|
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||||
|
},
|
||||||
"keepFileExt" : true,
|
"keepFileExt" : true,
|
||||||
"fileNameSep" : "_"
|
"fileNameSep" : "_",
|
||||||
|
"numBackups" : 30
|
||||||
},
|
},
|
||||||
"klicktipp":
|
"klicktipp":
|
||||||
{
|
{
|
||||||
"type": "dateFile",
|
"type": "dateFile",
|
||||||
"filename": "../logs/backend/klicktipp.log",
|
"filename": "../logs/backend/klicktipp.log",
|
||||||
"pattern": "%d{ISO8601} %p %c %X{user} %f:%l %m",
|
"pattern": "yyyy-MM-dd",
|
||||||
|
"layout":
|
||||||
|
{
|
||||||
|
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||||
|
},
|
||||||
"keepFileExt" : true,
|
"keepFileExt" : true,
|
||||||
"fileNameSep" : "_"
|
"fileNameSep" : "_",
|
||||||
|
"numBackups" : 30
|
||||||
},
|
},
|
||||||
"errorFile":
|
"errorFile":
|
||||||
{
|
{
|
||||||
"type": "dateFile",
|
"type": "dateFile",
|
||||||
"filename": "../logs/backend/errors.log",
|
"filename": "../logs/backend/errors.log",
|
||||||
"pattern": "%d{ISO8601} %p %c %X{user} %f:%l %m",
|
"pattern": "yyyy-MM-dd",
|
||||||
|
"layout":
|
||||||
|
{
|
||||||
|
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||||
|
},
|
||||||
"keepFileExt" : true,
|
"keepFileExt" : true,
|
||||||
"fileNameSep" : "_"
|
"fileNameSep" : "_",
|
||||||
|
"numBackups" : 30
|
||||||
},
|
},
|
||||||
"errors":
|
"errors":
|
||||||
{
|
{
|
||||||
@ -52,7 +77,7 @@
|
|||||||
"type": "stdout",
|
"type": "stdout",
|
||||||
"layout":
|
"layout":
|
||||||
{
|
{
|
||||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c %X{user} %f:%l %m"
|
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"apolloOut":
|
"apolloOut":
|
||||||
@ -60,7 +85,7 @@
|
|||||||
"type": "stdout",
|
"type": "stdout",
|
||||||
"layout":
|
"layout":
|
||||||
{
|
{
|
||||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c %m"
|
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import { registerEnumType } from 'type-graphql'
|
import { registerEnumType } from 'type-graphql'
|
||||||
|
|
||||||
|
// lowercase values are not implemented yet
|
||||||
export enum ContributionCycleType {
|
export enum ContributionCycleType {
|
||||||
ONCE = 'once',
|
ONCE = 'ONCE',
|
||||||
HOUR = 'hour',
|
HOUR = 'hour',
|
||||||
TWO_HOURS = 'two_hours',
|
TWO_HOURS = 'two_hours',
|
||||||
FOUR_HOURS = 'four_hours',
|
FOUR_HOURS = 'four_hours',
|
||||||
EIGHT_HOURS = 'eight_hours',
|
EIGHT_HOURS = 'eight_hours',
|
||||||
HALF_DAY = 'half_day',
|
HALF_DAY = 'half_day',
|
||||||
DAY = 'day',
|
DAILY = 'DAILY',
|
||||||
TWO_DAYS = 'two_days',
|
TWO_DAYS = 'two_days',
|
||||||
THREE_DAYS = 'three_days',
|
THREE_DAYS = 'three_days',
|
||||||
FOUR_DAYS = 'four_days',
|
FOUR_DAYS = 'four_days',
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { User } from '@entity/User'
|
|||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class Contribution {
|
export class Contribution {
|
||||||
constructor(contribution: dbContribution, user: User) {
|
constructor(contribution: dbContribution, user?: User | null) {
|
||||||
this.id = contribution.id
|
this.id = contribution.id
|
||||||
this.firstName = user ? user.firstName : null
|
this.firstName = user ? user.firstName : null
|
||||||
this.lastName = user ? user.lastName : null
|
this.lastName = user ? user.lastName : null
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { peterLustig } from '@/seeds/users/peter-lustig'
|
|||||||
import { stephenHawking } from '@/seeds/users/stephen-hawking'
|
import { stephenHawking } from '@/seeds/users/stephen-hawking'
|
||||||
import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
|
import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
|
||||||
import {
|
import {
|
||||||
|
login,
|
||||||
setUserRole,
|
setUserRole,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
unDeleteUser,
|
unDeleteUser,
|
||||||
@ -28,7 +29,6 @@ import {
|
|||||||
} from '@/seeds/graphql/mutations'
|
} from '@/seeds/graphql/mutations'
|
||||||
import {
|
import {
|
||||||
listUnconfirmedContributions,
|
listUnconfirmedContributions,
|
||||||
login,
|
|
||||||
searchUsers,
|
searchUsers,
|
||||||
listTransactionLinksAdmin,
|
listTransactionLinksAdmin,
|
||||||
listContributionLinks,
|
listContributionLinks,
|
||||||
@ -98,8 +98,8 @@ describe('AdminResolver', () => {
|
|||||||
describe('without admin rights', () => {
|
describe('without admin rights', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
user = await userFactory(testEnv, bibiBloxberg)
|
user = await userFactory(testEnv, bibiBloxberg)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -123,8 +123,8 @@ describe('AdminResolver', () => {
|
|||||||
describe('with admin rights', () => {
|
describe('with admin rights', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
admin = await userFactory(testEnv, peterLustig)
|
admin = await userFactory(testEnv, peterLustig)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -251,8 +251,8 @@ describe('AdminResolver', () => {
|
|||||||
describe('without admin rights', () => {
|
describe('without admin rights', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
user = await userFactory(testEnv, bibiBloxberg)
|
user = await userFactory(testEnv, bibiBloxberg)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -276,8 +276,8 @@ describe('AdminResolver', () => {
|
|||||||
describe('with admin rights', () => {
|
describe('with admin rights', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
admin = await userFactory(testEnv, peterLustig)
|
admin = await userFactory(testEnv, peterLustig)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -359,8 +359,8 @@ describe('AdminResolver', () => {
|
|||||||
describe('without admin rights', () => {
|
describe('without admin rights', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
user = await userFactory(testEnv, bibiBloxberg)
|
user = await userFactory(testEnv, bibiBloxberg)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -384,8 +384,8 @@ describe('AdminResolver', () => {
|
|||||||
describe('with admin rights', () => {
|
describe('with admin rights', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
admin = await userFactory(testEnv, peterLustig)
|
admin = await userFactory(testEnv, peterLustig)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -471,8 +471,8 @@ describe('AdminResolver', () => {
|
|||||||
describe('without admin rights', () => {
|
describe('without admin rights', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
user = await userFactory(testEnv, bibiBloxberg)
|
user = await userFactory(testEnv, bibiBloxberg)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -516,8 +516,8 @@ describe('AdminResolver', () => {
|
|||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
admin = await userFactory(testEnv, peterLustig)
|
admin = await userFactory(testEnv, peterLustig)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -768,8 +768,8 @@ describe('AdminResolver', () => {
|
|||||||
describe('without admin rights', () => {
|
describe('without admin rights', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
user = await userFactory(testEnv, bibiBloxberg)
|
user = await userFactory(testEnv, bibiBloxberg)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -877,8 +877,8 @@ describe('AdminResolver', () => {
|
|||||||
describe('with admin rights', () => {
|
describe('with admin rights', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
admin = await userFactory(testEnv, peterLustig)
|
admin = await userFactory(testEnv, peterLustig)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1204,7 +1204,8 @@ describe('AdminResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('creation update is not valid', () => {
|
describe('creation update is not valid', () => {
|
||||||
it('throws an error', async () => {
|
// as this test has not clearly defined that date, it is a false positive
|
||||||
|
it.skip('throws an error', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
mutate({
|
mutate({
|
||||||
mutation: adminUpdateContribution,
|
mutation: adminUpdateContribution,
|
||||||
@ -1229,7 +1230,8 @@ describe('AdminResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('creation update is successful changing month', () => {
|
describe('creation update is successful changing month', () => {
|
||||||
it('returns update creation object', async () => {
|
// skipped as changing the month is currently disable
|
||||||
|
it.skip('returns update creation object', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
mutate({
|
mutate({
|
||||||
mutation: adminUpdateContribution,
|
mutation: adminUpdateContribution,
|
||||||
@ -1257,7 +1259,8 @@ describe('AdminResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('creation update is successful without changing month', () => {
|
describe('creation update is successful without changing month', () => {
|
||||||
it('returns update creation object', async () => {
|
// actually this mutation IS changing the month
|
||||||
|
it.skip('returns update creation object', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
mutate({
|
mutate({
|
||||||
mutation: adminUpdateContribution,
|
mutation: adminUpdateContribution,
|
||||||
@ -1301,10 +1304,10 @@ describe('AdminResolver', () => {
|
|||||||
lastName: 'Lustig',
|
lastName: 'Lustig',
|
||||||
email: 'peter@lustig.de',
|
email: 'peter@lustig.de',
|
||||||
date: expect.any(String),
|
date: expect.any(String),
|
||||||
memo: 'Das war leider zu Viel!',
|
memo: 'Herzlich Willkommen bei Gradido!',
|
||||||
amount: '200',
|
amount: '400',
|
||||||
moderator: admin.id,
|
moderator: admin.id,
|
||||||
creation: ['1000', '1000', '300'],
|
creation: ['1000', '600', '500'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
@ -1315,7 +1318,7 @@ describe('AdminResolver', () => {
|
|||||||
memo: 'Grundeinkommen',
|
memo: 'Grundeinkommen',
|
||||||
amount: '500',
|
amount: '500',
|
||||||
moderator: admin.id,
|
moderator: admin.id,
|
||||||
creation: ['1000', '1000', '300'],
|
creation: ['1000', '600', '500'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
@ -1590,8 +1593,8 @@ describe('AdminResolver', () => {
|
|||||||
describe('without admin rights', () => {
|
describe('without admin rights', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
user = await userFactory(testEnv, bibiBloxberg)
|
user = await userFactory(testEnv, bibiBloxberg)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1636,8 +1639,8 @@ describe('AdminResolver', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// admin: only now log in
|
// admin: only now log in
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1826,13 +1829,14 @@ describe('AdminResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('Contribution Links', () => {
|
describe('Contribution Links', () => {
|
||||||
|
const now = new Date()
|
||||||
const variables = {
|
const variables = {
|
||||||
amount: new Decimal(200),
|
amount: new Decimal(200),
|
||||||
name: 'Dokumenta 2022',
|
name: 'Dokumenta 2022',
|
||||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
||||||
cycle: 'once',
|
cycle: 'once',
|
||||||
validFrom: new Date(2022, 5, 18).toISOString(),
|
validFrom: new Date(2022, 5, 18).toISOString(),
|
||||||
validTo: new Date(2022, 7, 14).toISOString(),
|
validTo: new Date(now.getFullYear() + 1, 7, 14).toISOString(),
|
||||||
maxAmountPerMonth: new Decimal(200),
|
maxAmountPerMonth: new Decimal(200),
|
||||||
maxPerCycle: 1,
|
maxPerCycle: 1,
|
||||||
}
|
}
|
||||||
@ -1896,8 +1900,8 @@ describe('AdminResolver', () => {
|
|||||||
describe('without admin rights', () => {
|
describe('without admin rights', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
user = await userFactory(testEnv, bibiBloxberg)
|
user = await userFactory(testEnv, bibiBloxberg)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1970,8 +1974,8 @@ describe('AdminResolver', () => {
|
|||||||
describe('with admin rights', () => {
|
describe('with admin rights', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
user = await userFactory(testEnv, peterLustig)
|
user = await userFactory(testEnv, peterLustig)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -2014,7 +2018,7 @@ describe('AdminResolver', () => {
|
|||||||
name: 'Dokumenta 2022',
|
name: 'Dokumenta 2022',
|
||||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
||||||
validFrom: new Date('2022-06-18T00:00:00.000Z'),
|
validFrom: new Date('2022-06-18T00:00:00.000Z'),
|
||||||
validTo: new Date('2022-08-14T00:00:00.000Z'),
|
validTo: expect.any(Date),
|
||||||
cycle: 'once',
|
cycle: 'once',
|
||||||
maxPerCycle: 1,
|
maxPerCycle: 1,
|
||||||
totalMaxCountOfContribution: null,
|
totalMaxCountOfContribution: null,
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { AdminCreateContributions } from '@model/AdminCreateContributions'
|
|||||||
import { AdminUpdateContribution } from '@model/AdminUpdateContribution'
|
import { AdminUpdateContribution } from '@model/AdminUpdateContribution'
|
||||||
import { ContributionLink } from '@model/ContributionLink'
|
import { ContributionLink } from '@model/ContributionLink'
|
||||||
import { ContributionLinkList } from '@model/ContributionLinkList'
|
import { ContributionLinkList } from '@model/ContributionLinkList'
|
||||||
|
import { Contribution } from '@model/Contribution'
|
||||||
import { RIGHTS } from '@/auth/RIGHTS'
|
import { RIGHTS } from '@/auth/RIGHTS'
|
||||||
import { UserRepository } from '@repository/User'
|
import { UserRepository } from '@repository/User'
|
||||||
import AdminCreateContributionArgs from '@arg/AdminCreateContributionArgs'
|
import AdminCreateContributionArgs from '@arg/AdminCreateContributionArgs'
|
||||||
@ -23,12 +24,10 @@ import SearchUsersArgs from '@arg/SearchUsersArgs'
|
|||||||
import ContributionLinkArgs from '@arg/ContributionLinkArgs'
|
import ContributionLinkArgs from '@arg/ContributionLinkArgs'
|
||||||
import { Transaction as DbTransaction } from '@entity/Transaction'
|
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||||
import { Transaction } from '@model/Transaction'
|
|
||||||
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
|
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
|
||||||
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
||||||
import { TransactionRepository } from '@repository/Transaction'
|
|
||||||
import { calculateDecay } from '@/util/decay'
|
import { calculateDecay } from '@/util/decay'
|
||||||
import { Contribution } from '@entity/Contribution'
|
import { Contribution as DbContribution } from '@entity/Contribution'
|
||||||
import { hasElopageBuys } from '@/util/hasElopageBuys'
|
import { hasElopageBuys } from '@/util/hasElopageBuys'
|
||||||
import { User as dbUser } from '@entity/User'
|
import { User as dbUser } from '@entity/User'
|
||||||
import { User } from '@model/User'
|
import { User } from '@model/User'
|
||||||
@ -40,7 +39,6 @@ import { Decay } from '@model/Decay'
|
|||||||
import Paginated from '@arg/Paginated'
|
import Paginated from '@arg/Paginated'
|
||||||
import TransactionLinkFilters from '@arg/TransactionLinkFilters'
|
import TransactionLinkFilters from '@arg/TransactionLinkFilters'
|
||||||
import { Order } from '@enum/Order'
|
import { Order } from '@enum/Order'
|
||||||
import { communityUser } from '@/util/communityUser'
|
|
||||||
import { findUserByEmail, activationLink, printTimeDuration } from './UserResolver'
|
import { findUserByEmail, activationLink, printTimeDuration } from './UserResolver'
|
||||||
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
|
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
|
||||||
import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver'
|
import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver'
|
||||||
@ -66,6 +64,7 @@ import { ContributionMessageType } from '@enum/MessageType'
|
|||||||
import { ContributionMessage } from '@model/ContributionMessage'
|
import { ContributionMessage } from '@model/ContributionMessage'
|
||||||
import { sendContributionConfirmedEmail } from '@/mailer/sendContributionConfirmedEmail'
|
import { sendContributionConfirmedEmail } from '@/mailer/sendContributionConfirmedEmail'
|
||||||
import { sendAddedContributionMessageEmail } from '@/mailer/sendAddedContributionMessageEmail'
|
import { sendAddedContributionMessageEmail } from '@/mailer/sendAddedContributionMessageEmail'
|
||||||
|
import { ContributionListResult } from '../model/Contribution'
|
||||||
|
|
||||||
// const EMAIL_OPT_IN_REGISTER = 1
|
// const EMAIL_OPT_IN_REGISTER = 1
|
||||||
// const EMAIL_OPT_UNKNOWN = 3 // elopage?
|
// const EMAIL_OPT_UNKNOWN = 3 // elopage?
|
||||||
@ -248,7 +247,7 @@ export class AdminResolver {
|
|||||||
const creationDateObj = new Date(creationDate)
|
const creationDateObj = new Date(creationDate)
|
||||||
logger.trace('creationDateObj:', creationDateObj)
|
logger.trace('creationDateObj:', creationDateObj)
|
||||||
validateContribution(creations, amount, creationDateObj)
|
validateContribution(creations, amount, creationDateObj)
|
||||||
const contribution = Contribution.create()
|
const contribution = DbContribution.create()
|
||||||
contribution.userId = emailContact.userId
|
contribution.userId = emailContact.userId
|
||||||
contribution.amount = amount
|
contribution.amount = amount
|
||||||
contribution.createdAt = new Date()
|
contribution.createdAt = new Date()
|
||||||
@ -259,7 +258,7 @@ export class AdminResolver {
|
|||||||
contribution.contributionStatus = ContributionStatus.PENDING
|
contribution.contributionStatus = ContributionStatus.PENDING
|
||||||
|
|
||||||
logger.trace('contribution to save', contribution)
|
logger.trace('contribution to save', contribution)
|
||||||
await Contribution.save(contribution)
|
await DbContribution.save(contribution)
|
||||||
return getUserCreation(emailContact.userId)
|
return getUserCreation(emailContact.userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,7 +316,7 @@ export class AdminResolver {
|
|||||||
|
|
||||||
const moderator = getUser(context)
|
const moderator = getUser(context)
|
||||||
|
|
||||||
const contributionToUpdate = await Contribution.findOne({
|
const contributionToUpdate = await DbContribution.findOne({
|
||||||
where: { id, confirmedAt: IsNull() },
|
where: { id, confirmedAt: IsNull() },
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -340,6 +339,9 @@ export class AdminResolver {
|
|||||||
let creations = await getUserCreation(user.id)
|
let creations = await getUserCreation(user.id)
|
||||||
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
||||||
creations = updateCreations(creations, contributionToUpdate)
|
creations = updateCreations(creations, contributionToUpdate)
|
||||||
|
} else {
|
||||||
|
logger.error('Currently the month of the contribution cannot change.')
|
||||||
|
throw new Error('Currently the month of the contribution cannot change.')
|
||||||
}
|
}
|
||||||
|
|
||||||
// all possible cases not to be true are thrown in this function
|
// all possible cases not to be true are thrown in this function
|
||||||
@ -350,7 +352,7 @@ export class AdminResolver {
|
|||||||
contributionToUpdate.moderatorId = moderator.id
|
contributionToUpdate.moderatorId = moderator.id
|
||||||
contributionToUpdate.contributionStatus = ContributionStatus.PENDING
|
contributionToUpdate.contributionStatus = ContributionStatus.PENDING
|
||||||
|
|
||||||
await Contribution.save(contributionToUpdate)
|
await DbContribution.save(contributionToUpdate)
|
||||||
const result = new AdminUpdateContribution()
|
const result = new AdminUpdateContribution()
|
||||||
result.amount = amount
|
result.amount = amount
|
||||||
result.memo = contributionToUpdate.memo
|
result.memo = contributionToUpdate.memo
|
||||||
@ -367,7 +369,7 @@ export class AdminResolver {
|
|||||||
const contributions = await getConnection()
|
const contributions = await getConnection()
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
.select('c')
|
.select('c')
|
||||||
.from(Contribution, 'c')
|
.from(DbContribution, 'c')
|
||||||
.leftJoinAndSelect('c.messages', 'm')
|
.leftJoinAndSelect('c.messages', 'm')
|
||||||
.where({ confirmedAt: IsNull() })
|
.where({ confirmedAt: IsNull() })
|
||||||
.getMany()
|
.getMany()
|
||||||
@ -402,7 +404,7 @@ export class AdminResolver {
|
|||||||
@Arg('id', () => Int) id: number,
|
@Arg('id', () => Int) id: number,
|
||||||
@Ctx() context: Context,
|
@Ctx() context: Context,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const contribution = await Contribution.findOne(id)
|
const contribution = await DbContribution.findOne(id)
|
||||||
if (!contribution) {
|
if (!contribution) {
|
||||||
logger.error(`Contribution not found for given id: ${id}`)
|
logger.error(`Contribution not found for given id: ${id}`)
|
||||||
throw new Error('Contribution not found for given id.')
|
throw new Error('Contribution not found for given id.')
|
||||||
@ -427,7 +429,7 @@ export class AdminResolver {
|
|||||||
@Arg('id', () => Int) id: number,
|
@Arg('id', () => Int) id: number,
|
||||||
@Ctx() context: Context,
|
@Ctx() context: Context,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const contribution = await Contribution.findOne(id)
|
const contribution = await DbContribution.findOne(id)
|
||||||
if (!contribution) {
|
if (!contribution) {
|
||||||
logger.error(`Contribution not found for given id: ${id}`)
|
logger.error(`Contribution not found for given id: ${id}`)
|
||||||
throw new Error('Contribution not found to given id.')
|
throw new Error('Contribution not found to given id.')
|
||||||
@ -492,7 +494,7 @@ export class AdminResolver {
|
|||||||
contribution.confirmedBy = moderatorUser.id
|
contribution.confirmedBy = moderatorUser.id
|
||||||
contribution.transactionId = transaction.id
|
contribution.transactionId = transaction.id
|
||||||
contribution.contributionStatus = ContributionStatus.CONFIRMED
|
contribution.contributionStatus = ContributionStatus.CONFIRMED
|
||||||
await queryRunner.manager.update(Contribution, { id: contribution.id }, contribution)
|
await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution)
|
||||||
|
|
||||||
await queryRunner.commitTransaction()
|
await queryRunner.commitTransaction()
|
||||||
logger.info('creation commited successfuly.')
|
logger.info('creation commited successfuly.')
|
||||||
@ -517,24 +519,29 @@ export class AdminResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.CREATION_TRANSACTION_LIST])
|
@Authorized([RIGHTS.CREATION_TRANSACTION_LIST])
|
||||||
@Query(() => [Transaction])
|
@Query(() => ContributionListResult)
|
||||||
async creationTransactionList(
|
async creationTransactionList(
|
||||||
@Args()
|
@Args()
|
||||||
{ currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
|
{ currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
|
||||||
@Arg('userId', () => Int) userId: number,
|
@Arg('userId', () => Int) userId: number,
|
||||||
): Promise<Transaction[]> {
|
): Promise<ContributionListResult> {
|
||||||
const offset = (currentPage - 1) * pageSize
|
const offset = (currentPage - 1) * pageSize
|
||||||
const transactionRepository = getCustomRepository(TransactionRepository)
|
const [contributionResult, count] = await getConnection()
|
||||||
const [userTransactions] = await transactionRepository.findByUserPaged(
|
.createQueryBuilder()
|
||||||
userId,
|
.select('c')
|
||||||
pageSize,
|
.from(DbContribution, 'c')
|
||||||
offset,
|
.leftJoinAndSelect('c.user', 'u')
|
||||||
order,
|
.where(`user_id = ${userId}`)
|
||||||
true,
|
.limit(pageSize)
|
||||||
)
|
.offset(offset)
|
||||||
|
.orderBy('c.created_at', order)
|
||||||
|
.getManyAndCount()
|
||||||
|
|
||||||
const user = await dbUser.findOneOrFail({ id: userId })
|
return new ContributionListResult(
|
||||||
return userTransactions.map((t) => new Transaction(t, new User(user), communityUser))
|
count,
|
||||||
|
contributionResult.map((contribution) => new Contribution(contribution, contribution.user)),
|
||||||
|
)
|
||||||
|
// return userTransactions.map((t) => new Transaction(t, new User(user), communityUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.SEND_ACTIVATION_EMAIL])
|
@Authorized([RIGHTS.SEND_ACTIVATION_EMAIL])
|
||||||
@ -682,6 +689,7 @@ export class AdminResolver {
|
|||||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||||
): Promise<ContributionLinkList> {
|
): Promise<ContributionLinkList> {
|
||||||
const [links, count] = await DbContributionLink.findAndCount({
|
const [links, count] = await DbContributionLink.findAndCount({
|
||||||
|
where: [{ validTo: MoreThan(new Date()) }, { validTo: IsNull() }],
|
||||||
order: { createdAt: order },
|
order: { createdAt: order },
|
||||||
skip: (currentPage - 1) * pageSize,
|
skip: (currentPage - 1) * pageSize,
|
||||||
take: pageSize,
|
take: pageSize,
|
||||||
@ -755,7 +763,7 @@ export class AdminResolver {
|
|||||||
await queryRunner.startTransaction('REPEATABLE READ')
|
await queryRunner.startTransaction('REPEATABLE READ')
|
||||||
const contributionMessage = DbContributionMessage.create()
|
const contributionMessage = DbContributionMessage.create()
|
||||||
try {
|
try {
|
||||||
const contribution = await Contribution.findOne({
|
const contribution = await DbContribution.findOne({
|
||||||
where: { id: contributionId },
|
where: { id: contributionId },
|
||||||
relations: ['user'],
|
relations: ['user'],
|
||||||
})
|
})
|
||||||
@ -784,7 +792,7 @@ export class AdminResolver {
|
|||||||
contribution.contributionStatus === ContributionStatus.PENDING
|
contribution.contributionStatus === ContributionStatus.PENDING
|
||||||
) {
|
) {
|
||||||
contribution.contributionStatus = ContributionStatus.IN_PROGRESS
|
contribution.contributionStatus = ContributionStatus.IN_PROGRESS
|
||||||
await queryRunner.manager.update(Contribution, { id: contributionId }, contribution)
|
await queryRunner.manager.update(DbContribution, { id: contributionId }, contribution)
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendAddedContributionMessageEmail({
|
await sendAddedContributionMessageEmail({
|
||||||
|
|||||||
@ -7,8 +7,9 @@ import {
|
|||||||
adminCreateContributionMessage,
|
adminCreateContributionMessage,
|
||||||
createContribution,
|
createContribution,
|
||||||
createContributionMessage,
|
createContributionMessage,
|
||||||
|
login,
|
||||||
} from '@/seeds/graphql/mutations'
|
} from '@/seeds/graphql/mutations'
|
||||||
import { listContributionMessages, login } from '@/seeds/graphql/queries'
|
import { listContributionMessages } from '@/seeds/graphql/queries'
|
||||||
import { userFactory } from '@/seeds/factory/user'
|
import { userFactory } from '@/seeds/factory/user'
|
||||||
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||||
import { peterLustig } from '@/seeds/users/peter-lustig'
|
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||||
@ -21,14 +22,13 @@ jest.mock('@/mailer/sendAddedContributionMessageEmail', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let mutate: any, query: any, con: any
|
let mutate: any, con: any
|
||||||
let testEnv: any
|
let testEnv: any
|
||||||
let result: any
|
let result: any
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
testEnv = await testEnvironment()
|
testEnv = await testEnvironment()
|
||||||
mutate = testEnv.mutate
|
mutate = testEnv.mutate
|
||||||
query = testEnv.query
|
|
||||||
con = testEnv.con
|
con = testEnv.con
|
||||||
await cleanDB()
|
await cleanDB()
|
||||||
})
|
})
|
||||||
@ -59,8 +59,8 @@ describe('ContributionMessageResolver', () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await userFactory(testEnv, bibiBloxberg)
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
await userFactory(testEnv, peterLustig)
|
await userFactory(testEnv, peterLustig)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
result = await mutate({
|
result = await mutate({
|
||||||
@ -71,8 +71,8 @@ describe('ContributionMessageResolver', () => {
|
|||||||
creationDate: new Date().toString(),
|
creationDate: new Date().toString(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -103,8 +103,8 @@ describe('ContributionMessageResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('throws error when contribution.userId equals user.id', async () => {
|
it('throws error when contribution.userId equals user.id', async () => {
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
const result2 = await mutate({
|
const result2 = await mutate({
|
||||||
@ -195,8 +195,8 @@ describe('ContributionMessageResolver', () => {
|
|||||||
|
|
||||||
describe('authenticated', () => {
|
describe('authenticated', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -227,8 +227,8 @@ describe('ContributionMessageResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('throws error when other user tries to send createContributionMessage', async () => {
|
it('throws error when other user tries to send createContributionMessage', async () => {
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
await expect(
|
await expect(
|
||||||
@ -253,8 +253,8 @@ describe('ContributionMessageResolver', () => {
|
|||||||
|
|
||||||
describe('valid input', () => {
|
describe('valid input', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -304,8 +304,8 @@ describe('ContributionMessageResolver', () => {
|
|||||||
|
|
||||||
describe('authenticated', () => {
|
describe('authenticated', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -8,8 +8,9 @@ import {
|
|||||||
createContribution,
|
createContribution,
|
||||||
deleteContribution,
|
deleteContribution,
|
||||||
updateContribution,
|
updateContribution,
|
||||||
|
login,
|
||||||
} from '@/seeds/graphql/mutations'
|
} from '@/seeds/graphql/mutations'
|
||||||
import { listAllContributions, listContributions, login } from '@/seeds/graphql/queries'
|
import { listAllContributions, listContributions } from '@/seeds/graphql/queries'
|
||||||
import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
|
import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
|
||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
import { userFactory } from '@/seeds/factory/user'
|
import { userFactory } from '@/seeds/factory/user'
|
||||||
@ -54,8 +55,8 @@ describe('ContributionResolver', () => {
|
|||||||
describe('authenticated with valid user', () => {
|
describe('authenticated with valid user', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await userFactory(testEnv, bibiBloxberg)
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -197,8 +198,8 @@ describe('ContributionResolver', () => {
|
|||||||
const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de')
|
const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
await creationFactory(testEnv, bibisCreation!)
|
await creationFactory(testEnv, bibisCreation!)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
await mutate({
|
await mutate({
|
||||||
@ -310,8 +311,8 @@ describe('ContributionResolver', () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await userFactory(testEnv, peterLustig)
|
await userFactory(testEnv, peterLustig)
|
||||||
await userFactory(testEnv, bibiBloxberg)
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
result = await mutate({
|
result = await mutate({
|
||||||
@ -393,8 +394,8 @@ describe('ContributionResolver', () => {
|
|||||||
|
|
||||||
describe('wrong user tries to update the contribution', () => {
|
describe('wrong user tries to update the contribution', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -445,8 +446,8 @@ describe('ContributionResolver', () => {
|
|||||||
|
|
||||||
describe('update too much so that the limit is exceeded', () => {
|
describe('update too much so that the limit is exceeded', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -489,9 +490,7 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [new GraphQLError('Currently the month of the contribution cannot change.')],
|
||||||
new GraphQLError('No information for available creations for the given date'),
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -553,8 +552,8 @@ describe('ContributionResolver', () => {
|
|||||||
const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de')
|
const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
await creationFactory(testEnv, bibisCreation!)
|
await creationFactory(testEnv, bibisCreation!)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
await mutate({
|
await mutate({
|
||||||
@ -630,8 +629,8 @@ describe('ContributionResolver', () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await userFactory(testEnv, bibiBloxberg)
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
await userFactory(testEnv, peterLustig)
|
await userFactory(testEnv, peterLustig)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
result = await mutate({
|
result = await mutate({
|
||||||
@ -668,8 +667,8 @@ describe('ContributionResolver', () => {
|
|||||||
|
|
||||||
describe('other user sends a deleteContribtuion', () => {
|
describe('other user sends a deleteContribtuion', () => {
|
||||||
it('returns an error', async () => {
|
it('returns an error', async () => {
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
await expect(
|
await expect(
|
||||||
@ -702,8 +701,8 @@ describe('ContributionResolver', () => {
|
|||||||
|
|
||||||
describe('User deletes already confirmed contribution', () => {
|
describe('User deletes already confirmed contribution', () => {
|
||||||
it('throws an error', async () => {
|
it('throws an error', async () => {
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
await mutate({
|
await mutate({
|
||||||
@ -712,8 +711,8 @@ describe('ContributionResolver', () => {
|
|||||||
id: result.data.createContribution.id,
|
id: result.data.createContribution.id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
@ -164,6 +164,9 @@ export class ContributionResolver {
|
|||||||
let creations = await getUserCreation(user.id)
|
let creations = await getUserCreation(user.id)
|
||||||
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
||||||
creations = updateCreations(creations, contributionToUpdate)
|
creations = updateCreations(creations, contributionToUpdate)
|
||||||
|
} else {
|
||||||
|
logger.error('Currently the month of the contribution cannot change.')
|
||||||
|
throw new Error('Currently the month of the contribution cannot change.')
|
||||||
}
|
}
|
||||||
|
|
||||||
// all possible cases not to be true are thrown in this function
|
// all possible cases not to be true are thrown in this function
|
||||||
|
|||||||
@ -1,4 +1,118 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
import { transactionLinkCode } from './TransactionLinkResolver'
|
import { transactionLinkCode } from './TransactionLinkResolver'
|
||||||
|
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||||
|
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||||
|
import { cleanDB, testEnvironment } from '@test/helpers'
|
||||||
|
import { userFactory } from '@/seeds/factory/user'
|
||||||
|
import { login, createContributionLink, redeemTransactionLink } from '@/seeds/graphql/mutations'
|
||||||
|
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||||
|
import Decimal from 'decimal.js-light'
|
||||||
|
import { GraphQLError } from 'graphql'
|
||||||
|
|
||||||
|
let mutate: any, con: any
|
||||||
|
let testEnv: any
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
testEnv = await testEnvironment()
|
||||||
|
mutate = testEnv.mutate
|
||||||
|
con = testEnv.con
|
||||||
|
await cleanDB()
|
||||||
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
|
await userFactory(testEnv, peterLustig)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
await con.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('TransactionLinkResolver', () => {
|
||||||
|
describe('redeem daily Contribution Link', () => {
|
||||||
|
const now = new Date()
|
||||||
|
let contributionLink: DbContributionLink | undefined
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
await mutate({
|
||||||
|
mutation: createContributionLink,
|
||||||
|
variables: {
|
||||||
|
amount: new Decimal(5),
|
||||||
|
name: 'Daily Contribution Link',
|
||||||
|
memo: 'Thank you for contribute daily to the community',
|
||||||
|
cycle: 'DAILY',
|
||||||
|
validFrom: new Date(now.getFullYear(), 0, 1).toISOString(),
|
||||||
|
validTo: new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999).toISOString(),
|
||||||
|
maxAmountPerMonth: new Decimal(200),
|
||||||
|
maxPerCycle: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a daily contribution link in the database', async () => {
|
||||||
|
const cls = await DbContributionLink.find()
|
||||||
|
expect(cls).toHaveLength(1)
|
||||||
|
contributionLink = cls[0]
|
||||||
|
expect(contributionLink).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(Number),
|
||||||
|
name: 'Daily Contribution Link',
|
||||||
|
memo: 'Thank you for contribute daily to the community',
|
||||||
|
validFrom: new Date(now.getFullYear(), 0, 1),
|
||||||
|
validTo: new Date(now.getFullYear(), 11, 31, 23, 59, 59, 0),
|
||||||
|
cycle: 'DAILY',
|
||||||
|
maxPerCycle: 1,
|
||||||
|
totalMaxCountOfContribution: null,
|
||||||
|
maxAccountBalance: null,
|
||||||
|
minGapHours: null,
|
||||||
|
createdAt: expect.any(Date),
|
||||||
|
deletedAt: null,
|
||||||
|
code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
|
||||||
|
linkEnabled: true,
|
||||||
|
// amount: '200',
|
||||||
|
// maxAmountPerMonth: '200',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('allows the user to redeem the contribution link', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: redeemTransactionLink,
|
||||||
|
variables: {
|
||||||
|
code: 'CL-' + (contributionLink ? contributionLink.code : ''),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
|
redeemTransactionLink: true,
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not allow the user to redeem the contribution link a second time on the same day', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: redeemTransactionLink,
|
||||||
|
variables: {
|
||||||
|
code: 'CL-' + (contributionLink ? contributionLink.code : ''),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
errors: [
|
||||||
|
new GraphQLError(
|
||||||
|
'Creation from contribution link was not successful. Error: Contribution link already redeemed today',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('transactionLinkCode', () => {
|
describe('transactionLinkCode', () => {
|
||||||
const date = new Date()
|
const date = new Date()
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { backendLogger as logger } from '@/server/logger'
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
import { Context, getUser } from '@/server/context'
|
import { Context, getUser } from '@/server/context'
|
||||||
import { getConnection } from '@dbTools/typeorm'
|
import { getConnection, Between } from '@dbTools/typeorm'
|
||||||
import {
|
import {
|
||||||
Resolver,
|
Resolver,
|
||||||
Args,
|
Args,
|
||||||
@ -34,6 +34,7 @@ import { getUserCreation, validateContribution } from './util/creations'
|
|||||||
import { Decay } from '@model/Decay'
|
import { Decay } from '@model/Decay'
|
||||||
import Decimal from 'decimal.js-light'
|
import Decimal from 'decimal.js-light'
|
||||||
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||||
|
import { ContributionCycleType } from '@enum/ContributionCycleType'
|
||||||
|
|
||||||
const QueryLinkResult = createUnionType({
|
const QueryLinkResult = createUnionType({
|
||||||
name: 'QueryLinkResult', // the name of the GraphQL union
|
name: 'QueryLinkResult', // the name of the GraphQL union
|
||||||
@ -204,23 +205,55 @@ export class TransactionLinkResolver {
|
|||||||
throw new Error('Contribution link is depricated')
|
throw new Error('Contribution link is depricated')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (contributionLink.cycle !== 'ONCE') {
|
let alreadyRedeemed: DbContribution | undefined
|
||||||
logger.error('contribution link has unknown cycle', contributionLink.cycle)
|
switch (contributionLink.cycle) {
|
||||||
throw new Error('Contribution link has unknown cycle')
|
case ContributionCycleType.ONCE: {
|
||||||
}
|
alreadyRedeemed = await queryRunner.manager
|
||||||
// Test ONCE rule
|
.createQueryBuilder()
|
||||||
const alreadyRedeemed = await queryRunner.manager
|
.select('contribution')
|
||||||
.createQueryBuilder()
|
.from(DbContribution, 'contribution')
|
||||||
.select('contribution')
|
.where('contribution.contributionLinkId = :linkId AND contribution.userId = :id', {
|
||||||
.from(DbContribution, 'contribution')
|
linkId: contributionLink.id,
|
||||||
.where('contribution.contributionLinkId = :linkId AND contribution.userId = :id', {
|
id: user.id,
|
||||||
linkId: contributionLink.id,
|
})
|
||||||
id: user.id,
|
.getOne()
|
||||||
})
|
if (alreadyRedeemed) {
|
||||||
.getOne()
|
logger.error(
|
||||||
if (alreadyRedeemed) {
|
'contribution link with rule ONCE already redeemed by user with id',
|
||||||
logger.error('contribution link with rule ONCE already redeemed by user with id', user.id)
|
user.id,
|
||||||
throw new Error('Contribution link already redeemed')
|
)
|
||||||
|
throw new Error('Contribution link already redeemed')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ContributionCycleType.DAILY: {
|
||||||
|
const start = new Date()
|
||||||
|
start.setHours(0, 0, 0, 0)
|
||||||
|
const end = new Date()
|
||||||
|
end.setHours(23, 59, 59, 999)
|
||||||
|
alreadyRedeemed = await queryRunner.manager
|
||||||
|
.createQueryBuilder()
|
||||||
|
.select('contribution')
|
||||||
|
.from(DbContribution, 'contribution')
|
||||||
|
.where('contribution.contributionLinkId = :linkId AND contribution.userId = :id', {
|
||||||
|
linkId: contributionLink.id,
|
||||||
|
id: user.id,
|
||||||
|
contributionDate: Between(start, end),
|
||||||
|
})
|
||||||
|
.getOne()
|
||||||
|
if (alreadyRedeemed) {
|
||||||
|
logger.error(
|
||||||
|
'contribution link with rule DAILY already redeemed by user with id',
|
||||||
|
user.id,
|
||||||
|
)
|
||||||
|
throw new Error('Contribution link already redeemed today')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
logger.error('contribution link has unknown cycle', contributionLink.cycle)
|
||||||
|
throw new Error('Contribution link has unknown cycle')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const creations = await getUserCreation(user.id, false)
|
const creations = await getUserCreation(user.id, false)
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { testEnvironment, headerPushMock, resetToken, cleanDB } from '@test/help
|
|||||||
import { userFactory } from '@/seeds/factory/user'
|
import { userFactory } from '@/seeds/factory/user'
|
||||||
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||||
import {
|
import {
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
createUser,
|
createUser,
|
||||||
setPassword,
|
setPassword,
|
||||||
forgotPassword,
|
forgotPassword,
|
||||||
@ -12,7 +14,7 @@ import {
|
|||||||
createContribution,
|
createContribution,
|
||||||
confirmContribution,
|
confirmContribution,
|
||||||
} from '@/seeds/graphql/mutations'
|
} from '@/seeds/graphql/mutations'
|
||||||
import { login, logout, verifyLogin, queryOptIn, searchAdminUsers } from '@/seeds/graphql/queries'
|
import { verifyLogin, queryOptIn, searchAdminUsers } from '@/seeds/graphql/queries'
|
||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
import { User } from '@entity/User'
|
import { User } from '@entity/User'
|
||||||
import CONFIG from '@/config'
|
import CONFIG from '@/config'
|
||||||
@ -358,7 +360,7 @@ describe('UserResolver', () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await userFactory(testEnv, peterLustig)
|
await userFactory(testEnv, peterLustig)
|
||||||
await userFactory(testEnv, bobBaumeister)
|
await userFactory(testEnv, bobBaumeister)
|
||||||
await query({ query: login, variables: bobData })
|
await mutate({ mutation: login, variables: bobData })
|
||||||
|
|
||||||
// create contribution as user bob
|
// create contribution as user bob
|
||||||
contribution = await mutate({
|
contribution = await mutate({
|
||||||
@ -367,7 +369,7 @@ describe('UserResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// login as admin
|
// login as admin
|
||||||
await query({ query: login, variables: peterData })
|
await mutate({ mutation: login, variables: peterData })
|
||||||
|
|
||||||
// confirm the contribution
|
// confirm the contribution
|
||||||
contribution = await mutate({
|
contribution = await mutate({
|
||||||
@ -376,7 +378,7 @@ describe('UserResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// login as user bob
|
// login as user bob
|
||||||
bob = await query({ query: login, variables: bobData })
|
bob = await mutate({ mutation: login, variables: bobData })
|
||||||
|
|
||||||
// create transaction link
|
// create transaction link
|
||||||
await transactionLinkFactory(testEnv, {
|
await transactionLinkFactory(testEnv, {
|
||||||
@ -582,7 +584,7 @@ describe('UserResolver', () => {
|
|||||||
describe('no users in database', () => {
|
describe('no users in database', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
result = await query({ query: login, variables })
|
result = await mutate({ mutation: login, variables })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws an error', () => {
|
it('throws an error', () => {
|
||||||
@ -603,7 +605,7 @@ describe('UserResolver', () => {
|
|||||||
describe('user is in database and correct login data', () => {
|
describe('user is in database and correct login data', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await userFactory(testEnv, bibiBloxberg)
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
result = await query({ query: login, variables })
|
result = await mutate({ mutation: login, variables })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@ -640,7 +642,7 @@ describe('UserResolver', () => {
|
|||||||
describe('user is in database and wrong password', () => {
|
describe('user is in database and wrong password', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await userFactory(testEnv, bibiBloxberg)
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
result = await query({ query: login, variables: { ...variables, password: 'wrong' } })
|
result = await mutate({ mutation: login, variables: { ...variables, password: 'wrong' } })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@ -665,7 +667,7 @@ describe('UserResolver', () => {
|
|||||||
describe('unauthenticated', () => {
|
describe('unauthenticated', () => {
|
||||||
it('throws an error', async () => {
|
it('throws an error', async () => {
|
||||||
resetToken()
|
resetToken()
|
||||||
await expect(query({ query: logout })).resolves.toEqual(
|
await expect(mutate({ mutation: logout })).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('401 Unauthorized')],
|
errors: [new GraphQLError('401 Unauthorized')],
|
||||||
}),
|
}),
|
||||||
@ -681,7 +683,7 @@ describe('UserResolver', () => {
|
|||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await userFactory(testEnv, bibiBloxberg)
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
await query({ query: login, variables })
|
await mutate({ mutation: login, variables })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@ -689,7 +691,7 @@ describe('UserResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('returns true', async () => {
|
it('returns true', async () => {
|
||||||
await expect(query({ query: logout })).resolves.toEqual(
|
await expect(mutate({ mutation: logout })).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
data: { logout: 'true' },
|
data: { logout: 'true' },
|
||||||
errors: undefined,
|
errors: undefined,
|
||||||
@ -738,7 +740,7 @@ describe('UserResolver', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await query({ query: login, variables })
|
await mutate({ mutation: login, variables })
|
||||||
user = await User.find()
|
user = await User.find()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -929,8 +931,8 @@ describe('UserResolver', () => {
|
|||||||
describe('authenticated', () => {
|
describe('authenticated', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await userFactory(testEnv, bibiBloxberg)
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'bibi@bloxberg.de',
|
email: 'bibi@bloxberg.de',
|
||||||
password: 'Aa12345_',
|
password: 'Aa12345_',
|
||||||
@ -1061,8 +1063,8 @@ describe('UserResolver', () => {
|
|||||||
|
|
||||||
it('can login with new password', async () => {
|
it('can login with new password', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'bibi@bloxberg.de',
|
email: 'bibi@bloxberg.de',
|
||||||
password: 'Bb12345_',
|
password: 'Bb12345_',
|
||||||
@ -1081,8 +1083,8 @@ describe('UserResolver', () => {
|
|||||||
|
|
||||||
it('cannot login with old password', async () => {
|
it('cannot login with old password', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'bibi@bloxberg.de',
|
email: 'bibi@bloxberg.de',
|
||||||
password: 'Aa12345_',
|
password: 'Aa12345_',
|
||||||
@ -1119,8 +1121,8 @@ describe('UserResolver', () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await userFactory(testEnv, bibiBloxberg)
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
await userFactory(testEnv, peterLustig)
|
await userFactory(testEnv, peterLustig)
|
||||||
await query({
|
await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: {
|
variables: {
|
||||||
email: 'bibi@bloxberg.de',
|
email: 'bibi@bloxberg.de',
|
||||||
password: 'Aa12345_',
|
password: 'Aa12345_',
|
||||||
|
|||||||
@ -316,7 +316,7 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.LOGIN])
|
@Authorized([RIGHTS.LOGIN])
|
||||||
@Query(() => User)
|
@Mutation(() => User)
|
||||||
@UseMiddleware(klicktippNewsletterStateMiddleware)
|
@UseMiddleware(klicktippNewsletterStateMiddleware)
|
||||||
async login(
|
async login(
|
||||||
@Args() { email, password, publisherId }: UnsecureLoginArgs,
|
@Args() { email, password, publisherId }: UnsecureLoginArgs,
|
||||||
@ -351,7 +351,7 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
// add pubKey in logger-context for layout-pattern X{user} to print it in each logging message
|
// add pubKey in logger-context for layout-pattern X{user} to print it in each logging message
|
||||||
logger.addContext('user', dbUser.id)
|
logger.addContext('user', dbUser.id)
|
||||||
logger.debug('login credentials valid...')
|
logger.debug('validation of login credentials successful...')
|
||||||
|
|
||||||
const user = new User(dbUser, await getUserCreation(dbUser.id))
|
const user = new User(dbUser, await getUserCreation(dbUser.id))
|
||||||
logger.debug(`user= ${JSON.stringify(user, null, 2)}`)
|
logger.debug(`user= ${JSON.stringify(user, null, 2)}`)
|
||||||
@ -377,7 +377,7 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.LOGOUT])
|
@Authorized([RIGHTS.LOGOUT])
|
||||||
@Query(() => String)
|
@Mutation(() => String)
|
||||||
async logout(): Promise<boolean> {
|
async logout(): Promise<boolean> {
|
||||||
// TODO: We dont need this anymore, but might need this in the future in oder to invalidate a valid JWT-Token.
|
// TODO: We dont need this anymore, but might need this in the future in oder to invalidate a valid JWT-Token.
|
||||||
// Furthermore this hook can be useful for tracking user behaviour (did he logout or not? Warn him if he didn't on next login)
|
// Furthermore this hook can be useful for tracking user behaviour (did he logout or not? Warn him if he didn't on next login)
|
||||||
@ -396,6 +396,7 @@ export class UserResolver {
|
|||||||
@Args()
|
@Args()
|
||||||
{ email, firstName, lastName, language, publisherId, redeemCode = null }: CreateUserArgs,
|
{ email, firstName, lastName, language, publisherId, redeemCode = null }: CreateUserArgs,
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
|
logger.addContext('user', 'unknown')
|
||||||
logger.info(
|
logger.info(
|
||||||
`createUser(email=${email}, firstName=${firstName}, lastName=${lastName}, language=${language}, publisherId=${publisherId}, redeemCode =${redeemCode})`,
|
`createUser(email=${email}, firstName=${firstName}, lastName=${lastName}, language=${language}, publisherId=${publisherId}, redeemCode =${redeemCode})`,
|
||||||
)
|
)
|
||||||
@ -548,6 +549,7 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await queryRunner.commitTransaction()
|
await queryRunner.commitTransaction()
|
||||||
|
logger.addContext('user', dbUser.id)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`error during create user with ${e}`)
|
logger.error(`error during create user with ${e}`)
|
||||||
await queryRunner.rollbackTransaction()
|
await queryRunner.rollbackTransaction()
|
||||||
@ -571,6 +573,7 @@ export class UserResolver {
|
|||||||
@Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL])
|
@Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL])
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async forgotPassword(@Arg('email') email: string): Promise<boolean> {
|
async forgotPassword(@Arg('email') email: string): Promise<boolean> {
|
||||||
|
logger.addContext('user', 'unknown')
|
||||||
logger.info(`forgotPassword(${email})...`)
|
logger.info(`forgotPassword(${email})...`)
|
||||||
email = email.trim().toLowerCase()
|
email = email.trim().toLowerCase()
|
||||||
const user = await findUserByEmail(email).catch(() => {
|
const user = await findUserByEmail(email).catch(() => {
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||||
import { createContributionLink } from '@/seeds/graphql/mutations'
|
import { login, createContributionLink } from '@/seeds/graphql/mutations'
|
||||||
import { login } from '@/seeds/graphql/queries'
|
|
||||||
import { ContributionLink } from '@model/ContributionLink'
|
import { ContributionLink } from '@model/ContributionLink'
|
||||||
import { ContributionLinkInterface } from '@/seeds/contributionLink/ContributionLinkInterface'
|
import { ContributionLinkInterface } from '@/seeds/contributionLink/ContributionLinkInterface'
|
||||||
|
|
||||||
@ -8,12 +7,12 @@ export const contributionLinkFactory = async (
|
|||||||
client: ApolloServerTestClient,
|
client: ApolloServerTestClient,
|
||||||
contributionLink: ContributionLinkInterface,
|
contributionLink: ContributionLinkInterface,
|
||||||
): Promise<ContributionLink> => {
|
): Promise<ContributionLink> => {
|
||||||
const { mutate, query } = client
|
const { mutate } = client
|
||||||
|
|
||||||
// login as admin
|
// login as admin
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const user = await query({
|
const user = await mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
import { backendLogger as logger } from '@/server/logger'
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
import { adminCreateContribution, confirmContribution } from '@/seeds/graphql/mutations'
|
import { login, adminCreateContribution, confirmContribution } from '@/seeds/graphql/mutations'
|
||||||
import { login } from '@/seeds/graphql/queries'
|
|
||||||
import { CreationInterface } from '@/seeds/creation/CreationInterface'
|
import { CreationInterface } from '@/seeds/creation/CreationInterface'
|
||||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||||
import { Transaction } from '@entity/Transaction'
|
import { Transaction } from '@entity/Transaction'
|
||||||
@ -19,9 +18,9 @@ export const creationFactory = async (
|
|||||||
client: ApolloServerTestClient,
|
client: ApolloServerTestClient,
|
||||||
creation: CreationInterface,
|
creation: CreationInterface,
|
||||||
): Promise<Contribution | void> => {
|
): Promise<Contribution | void> => {
|
||||||
const { mutate, query } = client
|
const { mutate } = client
|
||||||
logger.trace('creationFactory...')
|
logger.trace('creationFactory...')
|
||||||
await query({ query: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' } })
|
await mutate({ mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' } })
|
||||||
logger.trace('creationFactory... after login')
|
logger.trace('creationFactory... after login')
|
||||||
// TODO it would be nice to have this mutation return the id
|
// TODO it would be nice to have this mutation return the id
|
||||||
await mutate({ mutation: adminCreateContribution, variables: { ...creation } })
|
await mutate({ mutation: adminCreateContribution, variables: { ...creation } })
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||||
import { createTransactionLink } from '@/seeds/graphql/mutations'
|
import { login, createTransactionLink } from '@/seeds/graphql/mutations'
|
||||||
import { login } from '@/seeds/graphql/queries'
|
|
||||||
import { TransactionLinkInterface } from '@/seeds/transactionLink/TransactionLinkInterface'
|
import { TransactionLinkInterface } from '@/seeds/transactionLink/TransactionLinkInterface'
|
||||||
import { transactionLinkExpireDate } from '@/graphql/resolver/TransactionLinkResolver'
|
import { transactionLinkExpireDate } from '@/graphql/resolver/TransactionLinkResolver'
|
||||||
import { TransactionLink } from '@entity/TransactionLink'
|
import { TransactionLink } from '@entity/TransactionLink'
|
||||||
@ -9,10 +8,13 @@ export const transactionLinkFactory = async (
|
|||||||
client: ApolloServerTestClient,
|
client: ApolloServerTestClient,
|
||||||
transactionLink: TransactionLinkInterface,
|
transactionLink: TransactionLinkInterface,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const { mutate, query } = client
|
const { mutate } = client
|
||||||
|
|
||||||
// login
|
// login
|
||||||
await query({ query: login, variables: { email: transactionLink.email, password: 'Aa12345_' } })
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: transactionLink.email, password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
|
||||||
const variables = {
|
const variables = {
|
||||||
amount: transactionLink.amount,
|
amount: transactionLink.amount,
|
||||||
|
|||||||
@ -289,3 +289,33 @@ export const adminCreateContributionMessage = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const redeemTransactionLink = gql`
|
||||||
|
mutation ($code: String!) {
|
||||||
|
redeemTransactionLink(code: $code)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const login = gql`
|
||||||
|
mutation ($email: String!, $password: String!, $publisherId: Int) {
|
||||||
|
login(email: $email, password: $password, publisherId: $publisherId) {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
language
|
||||||
|
klickTipp {
|
||||||
|
newsletterState
|
||||||
|
}
|
||||||
|
hasElopage
|
||||||
|
publisherId
|
||||||
|
isAdmin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const logout = gql`
|
||||||
|
mutation {
|
||||||
|
logout
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|||||||
@ -1,23 +1,5 @@
|
|||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
export const login = gql`
|
|
||||||
query ($email: String!, $password: String!, $publisherId: Int) {
|
|
||||||
login(email: $email, password: $password, publisherId: $publisherId) {
|
|
||||||
id
|
|
||||||
email
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
language
|
|
||||||
klickTipp {
|
|
||||||
newsletterState
|
|
||||||
}
|
|
||||||
hasElopage
|
|
||||||
publisherId
|
|
||||||
isAdmin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const verifyLogin = gql`
|
export const verifyLogin = gql`
|
||||||
query {
|
query {
|
||||||
verifyLogin {
|
verifyLogin {
|
||||||
@ -35,12 +17,6 @@ export const verifyLogin = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const logout = gql`
|
|
||||||
query {
|
|
||||||
logout
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const queryOptIn = gql`
|
export const queryOptIn = gql`
|
||||||
query ($optIn: String!) {
|
query ($optIn: String!) {
|
||||||
queryOptIn(optIn: $optIn)
|
queryOptIn(optIn: $optIn)
|
||||||
|
|||||||
@ -35,6 +35,7 @@ const createServer = async (
|
|||||||
context: any = serverContext,
|
context: any = serverContext,
|
||||||
logger: Logger = apolloLogger,
|
logger: Logger = apolloLogger,
|
||||||
): Promise<ServerDef> => {
|
): Promise<ServerDef> => {
|
||||||
|
logger.addContext('user', 'unknown')
|
||||||
logger.debug('createServer...')
|
logger.debug('createServer...')
|
||||||
|
|
||||||
// open mysql connection
|
// open mysql connection
|
||||||
|
|||||||
@ -1668,9 +1668,9 @@ camelcase@^6.2.0:
|
|||||||
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
|
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001264:
|
caniuse-lite@^1.0.30001264:
|
||||||
version "1.0.30001325"
|
version "1.0.30001418"
|
||||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz"
|
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz"
|
||||||
integrity sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ==
|
integrity sha512-oIs7+JL3K9JRQ3jPZjlH6qyYDp+nBTCais7hjh0s+fuBwufc7uZ7hPYMXrDOJhV360KGMTcczMRObk0/iMqZRg==
|
||||||
|
|
||||||
chalk@^2.0.0:
|
chalk@^2.0.0:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
|
|||||||
@ -14,8 +14,8 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
\`type\` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
|
\`type\` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
\`user_id\` int(10) unsigned NOT NULL,
|
\`user_id\` int(10) unsigned NOT NULL,
|
||||||
\`email\` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL UNIQUE,
|
\`email\` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL UNIQUE,
|
||||||
\`email_verification_code\` bigint(20) unsigned NOT NULL UNIQUE,
|
\`email_verification_code\` bigint(20) unsigned DEFAULT NULL UNIQUE,
|
||||||
\`email_opt_in_type_id\` int NOT NULL,
|
\`email_opt_in_type_id\` int DEFAULT NULL,
|
||||||
\`email_resend_count\` int DEFAULT '0',
|
\`email_resend_count\` int DEFAULT '0',
|
||||||
\`email_checked\` tinyint(4) NOT NULL DEFAULT 0,
|
\`email_checked\` tinyint(4) NOT NULL DEFAULT 0,
|
||||||
\`phone\` varchar(255) COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
|
\`phone\` varchar(255) COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
|
||||||
@ -41,47 +41,13 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
|
|
||||||
// merge values from login_email_opt_in table with users.email in new user_contacts table
|
// merge values from login_email_opt_in table with users.email in new user_contacts table
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO user_contacts
|
INSERT INTO user_contacts
|
||||||
(type, user_id, email, email_verification_code, email_opt_in_type_id, email_resend_count, email_checked, created_at, updated_at, deleted_at)
|
(type, user_id, email, email_verification_code, email_opt_in_type_id, email_resend_count, email_checked, created_at, updated_at, deleted_at)
|
||||||
SELECT
|
SELECT 'EMAIL', users.id, users.email, optin.verification_code, optin.email_opt_in_type_id, optin.resend_count, users.email_checked, users.created, null, users.deletedAt
|
||||||
'EMAIL',
|
FROM users LEFT JOIN
|
||||||
u.id as user_id,
|
(SELECT le.id, le.user_id, le.verification_code, le.email_opt_in_type_id, le.resend_count, le.created, le.updated,
|
||||||
u.email,
|
ROW_NUMBER() OVER (PARTITION BY le.user_id ORDER BY le.created DESC) AS row_num
|
||||||
e.verification_code as email_verification_code,
|
FROM login_email_opt_in as le) AS optin ON users.id = optin.user_id AND row_num = 1;`)
|
||||||
e.email_opt_in_type_id,
|
|
||||||
e.resend_count as email_resend_count,
|
|
||||||
u.email_checked,
|
|
||||||
e.created as created_at,
|
|
||||||
e.updated as updated_at,
|
|
||||||
u.deletedAt as deleted_at\
|
|
||||||
FROM
|
|
||||||
users as u,
|
|
||||||
login_email_opt_in as e
|
|
||||||
WHERE
|
|
||||||
u.id = e.user_id AND
|
|
||||||
e.id in (
|
|
||||||
WITH opt_in AS (
|
|
||||||
SELECT
|
|
||||||
le.id, le.user_id, le.created, le.updated, ROW_NUMBER() OVER (PARTITION BY le.user_id ORDER BY le.created DESC) AS row_num
|
|
||||||
FROM
|
|
||||||
login_email_opt_in as le
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
opt_in.id
|
|
||||||
FROM
|
|
||||||
opt_in
|
|
||||||
WHERE
|
|
||||||
row_num = 1);`)
|
|
||||||
/*
|
|
||||||
// SELECT
|
|
||||||
// le.id
|
|
||||||
// FROM
|
|
||||||
// login_email_opt_in as le
|
|
||||||
// WHERE
|
|
||||||
// le.user_id = u.id
|
|
||||||
// ORDER BY
|
|
||||||
// le.updated DESC, le.created DESC LIMIT 1);`)
|
|
||||||
*/
|
|
||||||
|
|
||||||
// insert in users table the email_id of the new created email-contacts
|
// insert in users table the email_id of the new created email-contacts
|
||||||
const contacts = await queryFn(`SELECT c.id, c.user_id FROM user_contacts as c`)
|
const contacts = await queryFn(`SELECT c.id, c.user_id FROM user_contacts as c`)
|
||||||
@ -113,11 +79,13 @@ export async function downgrade(queryFn: (query: string, values?: any[]) => Prom
|
|||||||
)
|
)
|
||||||
|
|
||||||
// reconstruct the previous email back from contacts to users table
|
// reconstruct the previous email back from contacts to users table
|
||||||
const contacts = await queryFn(`SELECT c.id, c.email, c.user_id FROM user_contacts as c`)
|
const contacts = await queryFn(
|
||||||
|
`SELECT c.id, c.email, c.user_id, c.email_checked FROM user_contacts as c`,
|
||||||
|
)
|
||||||
for (const id in contacts) {
|
for (const id in contacts) {
|
||||||
const contact = contacts[id]
|
const contact = contacts[id]
|
||||||
await queryFn(
|
await queryFn(
|
||||||
`UPDATE users SET email = "${contact.email}" WHERE id = "${contact.user_id}" and email_id = "${contact.id}"`,
|
`UPDATE users SET email = "${contact.email}", email_checked="${contact.email_checked}" WHERE id = "${contact.user_id}" and email_id = "${contact.id}"`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
await queryFn('ALTER TABLE users MODIFY COLUMN email varchar(255) NOT NULL UNIQUE;')
|
await queryFn('ALTER TABLE users MODIFY COLUMN email varchar(255) NOT NULL UNIQUE;')
|
||||||
|
|||||||
@ -4,6 +4,12 @@
|
|||||||
# How to do this is described in detail in [setup.md](./setup.md)
|
# How to do this is described in detail in [setup.md](./setup.md)
|
||||||
|
|
||||||
# Find current directory & configure paths
|
# Find current directory & configure paths
|
||||||
|
## For manualy use in terminal
|
||||||
|
## set -o allexport
|
||||||
|
## SCRIPT_DIR=$(pwd)
|
||||||
|
## PROJECT_ROOT=$SCRIPT_DIR/../..
|
||||||
|
## set +o allexport
|
||||||
|
# Use here in script
|
||||||
set -o allexport
|
set -o allexport
|
||||||
SCRIPT_PATH=$(realpath $0)
|
SCRIPT_PATH=$(realpath $0)
|
||||||
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
|
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
|
||||||
@ -90,7 +96,7 @@ sudo certbot
|
|||||||
# Install logrotate
|
# Install logrotate
|
||||||
sudo apt-get install -y logrotate
|
sudo apt-get install -y logrotate
|
||||||
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_DIR/logrotate/gradido.conf.template > $SCRIPT_DIR/logrotate/gradido.conf
|
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_DIR/logrotate/gradido.conf.template > $SCRIPT_DIR/logrotate/gradido.conf
|
||||||
sudo mv $SCRIPT_DIR/logrotate/gradido.conf /etc/logrotate.d/gradido.conf
|
sudo cp $SCRIPT_DIR/logrotate/gradido.conf.template /etc/logrotate.d/gradido.conf
|
||||||
sudo chown root:root /etc/logrotate.d/gradido.conf
|
sudo chown root:root /etc/logrotate.d/gradido.conf
|
||||||
|
|
||||||
# Install mysql autobackup
|
# Install mysql autobackup
|
||||||
@ -137,4 +143,4 @@ envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/admin/.env.te
|
|||||||
# daily job: 0 4 * * * find /tmp -name "yarn--*" -ctime +1 -exec rm -r {} \; > /dev/null
|
# daily job: 0 4 * * * find /tmp -name "yarn--*" -ctime +1 -exec rm -r {} \; > /dev/null
|
||||||
# Start gradido
|
# Start gradido
|
||||||
# Note: on first startup some errors will occur - nothing serious
|
# Note: on first startup some errors will occur - nothing serious
|
||||||
./start.sh
|
./start.sh
|
||||||
|
|||||||
@ -1,107 +1,233 @@
|
|||||||
# Setup script to setup the server be ready to run gradido
|
|
||||||
# This assums you have root access via ssh to your cleanly setup server
|
|
||||||
# Furthermore this assumes you have debian (11 64bit) running
|
|
||||||
|
|
||||||
# Check your (Sub-)Domain with your Provider.
|
# Instructions To Run `Gradido` On Your Server
|
||||||
# In this document gddhost.tld refers to your chosen domain
|
|
||||||
|
|
||||||
> ssh root@gddhost.tld
|
We split setting up `Gradido` on your server into three steps:
|
||||||
|
|
||||||
# change root default shell
|
- [Preparing your server](#command-list-to-setup-your-server-be-ready-to-install-gradido)
|
||||||
> chsh -s /bin/bash
|
- [Installing `Gradido`](#use-commands-in-installsh-manually-in-your-shell-for-now)
|
||||||
# Create user `gradido`
|
- [Crone-Job for `Gradido`](#define-cronjob-to-compensate-yarn-output-in-tmp)
|
||||||
> useradd -d /home/gradido -m gradido
|
|
||||||
> passwd gradido
|
|
||||||
>> enter new password twice
|
|
||||||
|
|
||||||
# Gives the user priviledges - this might be omitted in order to harden security
|
## Command List To Setup Your Server Be Ready To Install `Gradido`
|
||||||
# Care: This will require another administering user if you don't want root access.
|
|
||||||
# Since this setup expects the user running the software be the same as the administering user,
|
|
||||||
# you have to adjust the instructions according to that scenario.
|
|
||||||
# You might lock yourself out, if done wrong.
|
|
||||||
> usermod -a -G sudo gradido
|
|
||||||
|
|
||||||
# change gradido default shell
|
We assume you have root access via ssh to your cleanly setup server.
|
||||||
> chsh -s /bin/bash gradido
|
Furthermore we assume you have debian (11 64bit) running.
|
||||||
# Install sudo
|
|
||||||
> apt-get install sudo
|
|
||||||
# switch to the new user
|
|
||||||
> su gradido
|
|
||||||
|
|
||||||
# Register first ssh key for user `gradido`
|
Check your (Sub-)Domain with your Provider.
|
||||||
> mkdir ~/.ssh
|
In this document `gddhost.tld` refers to your chosen domain.
|
||||||
> chmod 700 ~/.ssh
|
|
||||||
> nano ~/.ssh/authorized_keys
|
|
||||||
>> insert public key
|
|
||||||
>> ctrl + x
|
|
||||||
>> save
|
|
||||||
|
|
||||||
# Test authentication via SSH
|
### SSH into your server
|
||||||
> ssh -i /path/to/privKey gradido@gddhost.tld
|
|
||||||
>> This should log you in and allow you to use sudo commands, which will require the user's password
|
|
||||||
|
|
||||||
# Disable password authentication & root login
|
```bash
|
||||||
> cd /etc/ssh
|
ssh root@gddhost.tld
|
||||||
> sudo cp sshd_config sshd_config.org
|
```
|
||||||
> sudo nano sshd_config
|
|
||||||
>> change `PermitRootLogin yes` to `PermitRootLogin no`
|
|
||||||
>> change `#PasswordAuthentication yes` to `PasswordAuthentication no`
|
|
||||||
>> change `UsePAM yes` to `UsePAM no`
|
|
||||||
>> ctrl + x
|
|
||||||
>> save
|
|
||||||
> sudo /etc/init.d/ssh restart
|
|
||||||
|
|
||||||
# Test SSH Access only, no root ssh access
|
### Change root default shell
|
||||||
> ssh gradido@gddhost.tld
|
|
||||||
>> Will result in in either a password request for your key or the message `Permission denied (publickey)`
|
|
||||||
> ssh -i /path/to/privKey root@gddhost.tld
|
|
||||||
>> Will result in `Permission denied (publickey)`
|
|
||||||
> ssh -i /path/to/privKey gradido@gddhost.tld
|
|
||||||
>> Will succeed after entering the correct keys password (if any)
|
|
||||||
|
|
||||||
# update system
|
```bash
|
||||||
> sudo apt-get update
|
chsh -s /bin/bash
|
||||||
> sudo apt-get upgrade
|
```
|
||||||
|
|
||||||
# Install security tools
|
### Create user `gradido`
|
||||||
## ufw
|
|
||||||
> sudo apt-get install ufw
|
|
||||||
> sudo ufw allow http
|
|
||||||
> sudo ufw allow https
|
|
||||||
> sudo ufw allow ssh
|
|
||||||
> sudo ufw enable
|
|
||||||
|
|
||||||
## fail2ban
|
```bash
|
||||||
> sudo apt-get install -y fail2ban
|
$ useradd -d /home/gradido -m gradido
|
||||||
> sudo /etc/init.d/fail2ban restart
|
$ passwd gradido
|
||||||
|
# enter new password twice
|
||||||
|
```
|
||||||
|
|
||||||
# Install gradido
|
### Give the user priviledges
|
||||||
> sudo apt-get install -y git
|
|
||||||
> cd ~
|
|
||||||
> git clone https://github.com/gradido/gradido.git
|
|
||||||
|
|
||||||
# Timezone
|
This might be omitted in order to harden security.
|
||||||
# Note: This is needed - since there is Summer-Time included in the default server Setup - UTC is REQUIRED for production data
|
|
||||||
> sudo timedatectl set-timezone UTC
|
|
||||||
# > sudo timedatectl set-ntp on
|
|
||||||
# > sudo apt purge ntp
|
|
||||||
# > sudo systemctl start systemd-timesyncd
|
|
||||||
# >> timedatectl to verify
|
|
||||||
|
|
||||||
# Adjust .env
|
***!!! Attention !!!***
|
||||||
# NOTE ';' can not be part of any value
|
|
||||||
# The Github Secret is Created on Github in Settimgs -> Webhooks
|
- Care: This will require another administering user if you don't want root access.
|
||||||
> cd gradido/deployment/bare_metal
|
- Since this setup expects the user running the software be the same as the administering user,
|
||||||
> cp .env.dist .env
|
- you have to adjust the instructions according to that scenario.
|
||||||
> nano .env
|
- you might lock yourself out, if done wrong.
|
||||||
>> Adjust values accordingly
|
|
||||||
# Define cronjob to compensate yarn output in /tmp
|
#### Add the new user `gradido` to `sudo` group
|
||||||
> yarn creates output in /tmp directory, which must be deleted regularly and will be done per cronjob
|
|
||||||
> on stage1 a hourly job is necessary by setting the following job in the crontab for the gradido user
|
```bash
|
||||||
> crontab -e opens the crontab in edit-mode and insert the following entry:
|
usermod -a -G sudo gradido
|
||||||
> "0 * * * * find /tmp -name "yarn--*" -cmin +60 -exec rm -r {} \; > /dev/null"
|
```
|
||||||
> on stage2 a daily job is necessary by setting the following job in the crontab for the gradido user
|
|
||||||
> crontab -e opens the crontab in edit-mode and insert the following entry:
|
### Change gradido default shell
|
||||||
> "0 4 * * * find /tmp -name "yarn--*" -ctime +1 -exec rm -r {} \; > /dev/null"
|
|
||||||
# TODO the install.sh is not yet ready to run directly - consider to use it as pattern to do it manually
|
```bash
|
||||||
> ./install.sh
|
chsh -s /bin/bash gradido
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install sudo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apt-get install sudo
|
||||||
|
```
|
||||||
|
|
||||||
|
### Switch to the new user
|
||||||
|
|
||||||
|
```bash
|
||||||
|
su gradido
|
||||||
|
```
|
||||||
|
|
||||||
|
### Register first ssh key for user `gradido`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mkdir ~/.ssh
|
||||||
|
$ chmod 700 ~/.ssh
|
||||||
|
$ nano ~/.ssh/authorized_keys
|
||||||
|
# insert public key
|
||||||
|
# ctrl + x
|
||||||
|
# save
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test authentication via SSH
|
||||||
|
|
||||||
|
If you logout from the server you can test authentication:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ssh -i /path/to/privKey gradido@gddhost.tld
|
||||||
|
# This should log you in and allow you to use sudo commands, which will require the user's password
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disable password authentication and root login
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cd /etc/ssh
|
||||||
|
$ sudo cp sshd_config sshd_config.org
|
||||||
|
$ sudo nano sshd_config
|
||||||
|
# change 'PermitRootLogin yes' to `PermitRootLogin no`
|
||||||
|
# change 'PasswordAuthentication yes' to 'PasswordAuthentication no'
|
||||||
|
# change 'UsePAM yes' to 'UsePAM no'
|
||||||
|
# ctrl + x
|
||||||
|
# save
|
||||||
|
$ sudo /etc/init.d/ssh restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test SSH Access only, no root ssh access
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ssh gradido@gddhost.tld
|
||||||
|
# Will result in in either a passphrase request for your key or the message 'Permission denied (publickey)'
|
||||||
|
$ ssh -i /path/to/privKey root@gddhost.tld
|
||||||
|
# Will result in 'Permission denied (publickey)'
|
||||||
|
$ ssh -i /path/to/privKey gradido@gddhost.tld
|
||||||
|
# Will succeed after entering the correct keys passphrase (if any)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update system
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install security tools
|
||||||
|
|
||||||
|
#### Install: `ufw`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install ufw
|
||||||
|
sudo ufw allow http
|
||||||
|
sudo ufw allow https
|
||||||
|
sudo ufw allow ssh
|
||||||
|
sudo ufw enable
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Install: `fail2ban`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install -y fail2ban
|
||||||
|
sudo /etc/init.d/fail2ban restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install `Gradido` code
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install -y git
|
||||||
|
cd ~
|
||||||
|
git clone https://github.com/gradido/gradido.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### Timezone
|
||||||
|
|
||||||
|
*Note: This is needed - since there is Summer-Time included in the default server Setup - UTC is REQUIRED for production data.*
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo timedatectl set-timezone UTC
|
||||||
|
sudo timedatectl set-ntp on
|
||||||
|
sudo apt purge ntp
|
||||||
|
sudo systemctl start systemd-timesyncd
|
||||||
|
# timedatectl to verify
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adjust the values in `.env`
|
||||||
|
|
||||||
|
***!!! Attention !!!***
|
||||||
|
|
||||||
|
*Don't forget this step!
|
||||||
|
All your following installations in `install.sh` will fail!*
|
||||||
|
|
||||||
|
*Notes:*
|
||||||
|
|
||||||
|
- *`;` cannot be part of any value!*
|
||||||
|
- *The GitHub secret is created on GitHub in Settings -> Webhooks.*
|
||||||
|
|
||||||
|
#### Create `.env` and set values
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cd gradido/deployment/bare_metal
|
||||||
|
$ cp .env.dist .env
|
||||||
|
$ nano .env
|
||||||
|
# adjust values accordingly
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use Commands In `install.sh` Manually In Your Shell For Now
|
||||||
|
|
||||||
|
The script `install.sh` is not yet ready to run directly.
|
||||||
|
Use it as pattern to do all steps manually in your terminal shell.
|
||||||
|
|
||||||
|
*TODO: Bring the `install.sh` script to run in the shell.*
|
||||||
|
|
||||||
|
***!!! Attention !!!***
|
||||||
|
|
||||||
|
- *Commands in `install.sh`:*
|
||||||
|
- *The commands for setting the paths in the used env variables are not working directly in the terminal, consider the out commented commands for this purpose.*
|
||||||
|
|
||||||
|
Follow the commands in `./install.sh` as installation pattern.
|
||||||
|
|
||||||
|
## Define Cronjob To Compensate Yarn Output In `/tmp`
|
||||||
|
|
||||||
|
`yarn` creates output in `/tmp` directory, which must be deleted regularly and will be done per Cron-Job.
|
||||||
|
|
||||||
|
### On `stage1`
|
||||||
|
|
||||||
|
An hourly job is necessary on `stage1` by setting the following job in the `crontab` for the `gradido` user.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
crontab -e
|
||||||
|
```
|
||||||
|
|
||||||
|
This opens the crontab in edit-mode and insert the following entry:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
0 * * * * find /tmp -name "yarn--*" -cmin +60 -exec rm -r {} \; > /dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
### On `stage2`
|
||||||
|
|
||||||
|
A daily job is necessary on `stage2` by setting the following job in the `crontab` for the `gradido` user.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
crontab -e
|
||||||
|
```
|
||||||
|
|
||||||
|
This opens the `crontab` in edit-mode and insert the following entry:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
0 4 * * * find /tmp -name "yarn--*" -ctime +1 -exec rm -r {} \; > /dev/null
|
||||||
|
```
|
||||||
|
|||||||
@ -62,13 +62,13 @@ The business events will be stored in database in the new table `EventProtocol`.
|
|||||||
|
|
||||||
The following table lists for each event type the mapping between old and new key, the mandatory attributes, which have to be initialized at event occurence and to be written in the database event protocol table:
|
The following table lists for each event type the mapping between old and new key, the mandatory attributes, which have to be initialized at event occurence and to be written in the database event protocol table:
|
||||||
|
|
||||||
| EventType - old key | EventType - new key | id | type | createdAt | userID | XuserID | XCommunityID | transactionID | contribID | amount |
|
| EventKey | EventType | id | type | createdAt | userID | XuserID | XCommunityID | transactionID | contribID | amount |
|
||||||
| :-------------------------------- | :------------------------------------- | :-: | :--: | :-------: | :----: | :-----: | :----------: | :-----------: | :-------: | :----: |
|
| :-------------------------------- | :------------------------------------- | :-: | :--: | :-------: | :----: | :-----: | :----------: | :-----------: | :-------: | :----: |
|
||||||
| BASIC | BasicEvent | x | x | x | | | | | | |
|
| BASIC | BasicEvent | x | x | x | | | | | | |
|
||||||
| VISIT_GRADIDO | VisitGradidoEvent | x | x | x | | | | | | |
|
| VISIT_GRADIDO | VisitGradidoEvent | x | x | x | | | | | | |
|
||||||
| REGISTER | RegisterEvent | x | x | x | x | | | | | |
|
| REGISTER | RegisterEvent | x | x | x | x | | | | | |
|
||||||
| LOGIN | LoginEvent | x | x | x | x | | | | | |
|
| LOGIN | LoginEvent | x | x | x | x | | | | | |
|
||||||
| | VerifyRedeemEvent | | | | | | | | | |
|
| VERIFY_REDEEM | VerifyRedeemEvent | x | x | x | x | | | (x) | (x) | |
|
||||||
| REDEEM_REGISTER | RedeemRegisterEvent | x | x | x | x | | | (x) | (x) | |
|
| REDEEM_REGISTER | RedeemRegisterEvent | x | x | x | x | | | (x) | (x) | |
|
||||||
| REDEEM_LOGIN | RedeemLoginEvent | x | x | x | x | | | (x) | (x) | |
|
| REDEEM_LOGIN | RedeemLoginEvent | x | x | x | x | | | (x) | (x) | |
|
||||||
| ACTIVATE_ACCOUNT | ActivateAccountEvent | x | x | x | x | | | | | |
|
| ACTIVATE_ACCOUNT | ActivateAccountEvent | x | x | x | x | | | | | |
|
||||||
@ -82,20 +82,20 @@ The following table lists for each event type the mapping between old and new ke
|
|||||||
| TRANSACTION_SEND_REDEEM | TransactionLinkRedeemEvent | x | x | x | x | x | x | x | | x |
|
| TRANSACTION_SEND_REDEEM | TransactionLinkRedeemEvent | x | x | x | x | x | x | x | | x |
|
||||||
| CONTRIBUTION_CREATE | ContributionCreateEvent | x | x | x | x | | | | x | x |
|
| CONTRIBUTION_CREATE | ContributionCreateEvent | x | x | x | x | | | | x | x |
|
||||||
| CONTRIBUTION_CONFIRM | ContributionConfirmEvent | x | x | x | x | x | x | | x | x |
|
| CONTRIBUTION_CONFIRM | ContributionConfirmEvent | x | x | x | x | x | x | | x | x |
|
||||||
| | ContributionDenyEvent | x | x | x | x | x | x | | x | x |
|
| CONTRIBUTION_DENY | ContributionDenyEvent | x | x | x | x | x | x | | x | x |
|
||||||
| CONTRIBUTION_LINK_DEFINE | ContributionLinkDefineEvent | x | x | x | x | | | | | x |
|
| CONTRIBUTION_LINK_DEFINE | ContributionLinkDefineEvent | x | x | x | x | | | | | x |
|
||||||
| CONTRIBUTION_LINK_ACTIVATE_REDEEM | ContributionLinkRedeemEvent | x | x | x | x | | | | x | x |
|
| CONTRIBUTION_LINK_ACTIVATE_REDEEM | ContributionLinkRedeemEvent | x | x | x | x | | | | x | x |
|
||||||
| | UserCreateContributionMessageEvent | x | x | x | x | | | | x | x |
|
| USER_CREATES_CONTRIBUTION_MESSAGE | UserCreateContributionMessageEvent | x | x | x | x | | | | x | x |
|
||||||
| | AdminCreateContributionMessageEvent | x | x | x | x | | | | x | x |
|
| ADMIN_CREATES_CONTRIBUTION_MESSAGE | AdminCreateContributionMessageEvent | x | x | x | x | | | | x | x |
|
||||||
| | LogoutEvent | x | x | x | x | | | | x | x |
|
| LOGOUT | LogoutEvent | x | x | x | x | | | | | |
|
||||||
| SEND_CONFIRMATION_EMAIL | SendConfirmEmailEvent | x | x | x | x | | | | | |
|
| SEND_CONFIRMATION_EMAIL | SendConfirmEmailEvent | x | x | x | x | | | | | |
|
||||||
| | SendAccountMultiRegistrationEmailEvent | x | x | x | x | | | | | |
|
| SEND_ACCOUNT_MULTIREGISTRATION_EMAIL | SendAccountMultiRegistrationEmailEvent | x | x | x | x | | | | | |
|
||||||
| | SendForgotPasswordEmailEvent | x | x | x | x | | | | | |
|
| SEND_FORGOT_PASSWORD_EMAIL | SendForgotPasswordEmailEvent | x | x | x | x | | | | | |
|
||||||
| | SendTransactionSendEmailEvent | x | x | x | x | x | x | x | | x |
|
| SEND_TRANSACTION_SEND_EMAIL | SendTransactionSendEmailEvent | x | x | x | x | x | x | x | | x |
|
||||||
| | SendTransactionReceiveEmailEvent | x | x | x | x | x | x | x | | x |
|
| SEND_TRANSACTION_RECEIVE_EMAIL | SendTransactionReceiveEmailEvent | x | x | x | x | x | x | x | | x |
|
||||||
| | SendAddedContributionEmailEvent | x | x | x | x | | | | x | x |
|
| SEND_ADDED_CONTRIBUTION_EMAIL | SendAddedContributionEmailEvent | x | x | x | x | | | | x | x |
|
||||||
| | SendContributionConfirmEmailEvent | x | x | x | x | | | | x | x |
|
| SEND_CONTRIBUTION_CONFIRM_EMAIL | SendContributionConfirmEmailEvent | x | x | x | x | | | | x | x |
|
||||||
| | SendTransactionLinkRedeemEmailEvent | x | x | x | x | x | x | x | | x |
|
| SEND_TRANSACTION_LINK_REDEEM_EMAIL | SendTransactionLinkRedeemEmailEvent | x | x | x | x | x | x | x | | x |
|
||||||
| TRANSACTION_REPEATE_REDEEM | - | | | | | | | | | |
|
| TRANSACTION_REPEATE_REDEEM | - | | | | | | | | | |
|
||||||
| TRANSACTION_RECEIVE_REDEEM | - | | | | | | | | | |
|
| TRANSACTION_RECEIVE_REDEEM | - | | | | | | | | | |
|
||||||
|
|
||||||
|
|||||||
@ -1,24 +1,73 @@
|
|||||||
# Gradido End-to-End Testing with [Cypress](https://www.cypress.io/) (CI-ready via Docker)
|
# Gradido End-to-End Testing with [Cypress](https://www.cypress.io/) (CI-ready via Docker)
|
||||||
|
|
||||||
|
A setup to show-case Cypress as an end-to-end testing tool for Gradido running in a Docker container.
|
||||||
|
The tests are organized in feature files written in Gherkin syntax.
|
||||||
|
|
||||||
|
|
||||||
|
## Features under test
|
||||||
|
|
||||||
|
So far these features are initially tested
|
||||||
|
- [User authentication](https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.feature)
|
||||||
|
- [User profile - change password](https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/UserProfile.ChangePassword.feature)
|
||||||
|
- [User registration]((https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/User.Registration.feature)) (WIP)
|
||||||
|
|
||||||
A sample setup to show-case Cypress as an end-to-end testing tool for Gradido running in a Docker container.
|
|
||||||
Here we have a simple UI-based happy path login test running against the DEV system.
|
|
||||||
|
|
||||||
## Precondition
|
## Precondition
|
||||||
Since dependencies and configurations for Github Actions integration is not set up yet, please run in root directory
|
|
||||||
|
Before running the tests, change to the repo's root directory (gradido).
|
||||||
|
|
||||||
|
### Boot up the system under test
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose up
|
docker-compose up
|
||||||
```
|
```
|
||||||
|
|
||||||
to boot up the DEV system, before running the test.
|
### Seed the database
|
||||||
|
|
||||||
|
The database has to be seeded upfront to every test run.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# change to the backend directory
|
||||||
|
cd /path/to/gradido/gradido/backend
|
||||||
|
|
||||||
|
# install all dependencies
|
||||||
|
yarn
|
||||||
|
|
||||||
|
# seed the database (everytime before running the tests)
|
||||||
|
yarn seed
|
||||||
|
```
|
||||||
|
|
||||||
## Execute the test
|
## Execute the test
|
||||||
|
|
||||||
|
This setup will be integrated in the Gradido Github Actions to automatically support the CI/CD process.
|
||||||
|
For now the test setup can only be used locally in two modes.
|
||||||
|
|
||||||
|
### Run Cypress directly from the code
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# change to the tests directory
|
||||||
|
cd /path/to/gradido/e2e-tests/cypress/tests
|
||||||
|
|
||||||
|
# install all dependencies
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
# a) run the tests on command line
|
||||||
|
yarn cypress run
|
||||||
|
|
||||||
|
# b) open the Cypress GUI to run the tests in interactive mode
|
||||||
|
yarn cypress open
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Run Cyprss from a separate Docker container
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# change to the cypress directory
|
||||||
|
cd /path/to/gradido/e2e-tests/cypress/
|
||||||
|
|
||||||
# build a Docker image from the Dockerfile
|
# build a Docker image from the Dockerfile
|
||||||
docker build -t gradido_e2e-tests-cypress .
|
docker build -t gradido_e2e-tests-cypress .
|
||||||
|
|
||||||
# run the Docker container and execute the given tests
|
# run the Docker image and execute the given tests
|
||||||
docker run -it --network=host gradido_e2e-tests-cypress yarn run cypress-e2e-tests
|
docker run -it --network=host gradido_e2e-tests-cypress yarn cypress-e2e
|
||||||
```
|
```
|
||||||
|
|||||||
@ -32,6 +32,7 @@ export default defineConfig({
|
|||||||
excludeSpecPattern: "*.js",
|
excludeSpecPattern: "*.js",
|
||||||
baseUrl: "http://localhost:3000",
|
baseUrl: "http://localhost:3000",
|
||||||
chromeWebSecurity: false,
|
chromeWebSecurity: false,
|
||||||
|
defaultCommandTimeout: 10000,
|
||||||
supportFile: "cypress/support/index.ts",
|
supportFile: "cypress/support/index.ts",
|
||||||
viewportHeight: 720,
|
viewportHeight: 720,
|
||||||
viewportWidth: 1280,
|
viewportWidth: 1280,
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
Feature: User registration
|
||||||
|
As a user
|
||||||
|
I want to register to create an account
|
||||||
|
|
||||||
|
@skip
|
||||||
|
Scenario: Register successfully
|
||||||
|
Given the browser navigates to page "/register"
|
||||||
|
When the user fills name and email "Regina" "Register" "regina@register.com"
|
||||||
|
And the user agrees to the privacy policy
|
||||||
|
And the user submits the registration form
|
||||||
|
Then the user can use a provided activation link
|
||||||
|
And the user can set a password "Aa12345_"
|
||||||
|
And the user can login with the credentials "regina@register.com" "Aa12345_"
|
||||||
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
export class LoginPage {
|
export class LoginPage {
|
||||||
// selectors
|
// selectors
|
||||||
emailInput = "#Email-input-field";
|
emailInput = "input[type=email]";
|
||||||
passwordInput = "#Password-input-field";
|
passwordInput = "input[type=password]";
|
||||||
submitBtn = "[type=submit]";
|
submitBtn = "[type=submit]";
|
||||||
emailHint = "#vee_Email";
|
emailHint = "#vee_Email";
|
||||||
passwordHint = "#vee_Password";
|
passwordHint = "#vee_Password";
|
||||||
|
|||||||
@ -4,8 +4,8 @@ export class ProfilePage {
|
|||||||
// selectors
|
// selectors
|
||||||
openChangePassword = "[data-test=open-password-change-form]";
|
openChangePassword = "[data-test=open-password-change-form]";
|
||||||
oldPasswordInput = "#password-input-field";
|
oldPasswordInput = "#password-input-field";
|
||||||
newPasswordInput = "#New-password-input-field";
|
newPasswordInput = "#new-password-input-field";
|
||||||
newPasswordRepeatInput = "#Repeat-new-password-input-field";
|
newPasswordRepeatInput = "#repeat-new-password-input-field";
|
||||||
submitNewPasswordBtn = "[data-test=submit-new-password-btn]";
|
submitNewPasswordBtn = "[data-test=submit-new-password-btn]";
|
||||||
|
|
||||||
goto() {
|
goto() {
|
||||||
@ -19,12 +19,12 @@ export class ProfilePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enterNewPassword(password: string) {
|
enterNewPassword(password: string) {
|
||||||
cy.get(this.newPasswordInput).clear().type(password);
|
cy.get(this.newPasswordInput).find("input").clear().type(password);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
enterRepeatPassword(password: string) {
|
enterRepeatPassword(password: string) {
|
||||||
cy.get(this.newPasswordRepeatInput).clear().type(password);
|
cy.get(this.newPasswordRepeatInput).find("input").clear().type(password);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,42 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
export class RegistrationPage {
|
||||||
|
// selectors
|
||||||
|
firstnameInput = "#registerFirstname";
|
||||||
|
lastnameInput = "#registerLastname";
|
||||||
|
emailInput = "#Email-input-field";
|
||||||
|
checkbox = "#registerCheckbox";
|
||||||
|
submitBtn = "[type=submit]";
|
||||||
|
|
||||||
|
RegistrationThanxHeadline = ".test-message-headline";
|
||||||
|
RegistrationThanxText = ".test-message-subtitle";
|
||||||
|
|
||||||
|
goto() {
|
||||||
|
cy.visit("/register");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
enterFirstname(firstname: string) {
|
||||||
|
cy.get(this.firstnameInput).clear().type(firstname);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
enterLastname(lastname: string) {
|
||||||
|
cy.get(this.lastnameInput).clear().type(lastname);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
enterEmail(email: string) {
|
||||||
|
cy.get(this.emailInput).clear().type(email);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPrivacyCheckbox() {
|
||||||
|
cy.get(this.checkbox).click({ force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
submitRegistrationPage() {
|
||||||
|
cy.get(this.submitBtn).should("be.enabled");
|
||||||
|
cy.get(this.submitBtn).click();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
export class Toasts {
|
export class Toasts {
|
||||||
// selectors
|
// selectors
|
||||||
|
toastSlot = ".b-toaster-slot";
|
||||||
|
toastTypeSuccess = ".b-toast-success";
|
||||||
|
toastTypeError = ".b-toast-danger";
|
||||||
toastTitle = ".gdd-toaster-title";
|
toastTitle = ".gdd-toaster-title";
|
||||||
toastMessage = ".gdd-toaster-body";
|
toastMessage = ".gdd-toaster-body";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,11 +25,11 @@ Then("the user is logged in with username {string}", (username: string) => {
|
|||||||
|
|
||||||
Then("the user cannot login", () => {
|
Then("the user cannot login", () => {
|
||||||
const toast = new Toasts();
|
const toast = new Toasts();
|
||||||
cy.get(toast.toastTitle).should("contain.text", "Error!");
|
cy.get(toast.toastSlot).within(() => {
|
||||||
cy.get(toast.toastMessage).should(
|
cy.get(toast.toastTypeError);
|
||||||
"contain.text",
|
cy.get(toast.toastTitle).should("be.visible");
|
||||||
"No user with this credentials."
|
cy.get(toast.toastMessage).should("be.visible");
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@ -24,9 +24,9 @@ And("the user submits the password form", () => {
|
|||||||
|
|
||||||
When("the user is presented a {string} message", (type: string) => {
|
When("the user is presented a {string} message", (type: string) => {
|
||||||
const toast = new Toasts();
|
const toast = new Toasts();
|
||||||
cy.get(toast.toastTitle).should("contain.text", "Success");
|
cy.get(toast.toastSlot).within(() => {
|
||||||
cy.get(toast.toastMessage).should(
|
cy.get(toast.toastTypeSuccess);
|
||||||
"contain.text",
|
cy.get(toast.toastTitle).should("be.visible");
|
||||||
"Your password has been changed."
|
cy.get(toast.toastMessage).should("be.visible");
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"cypress": "cypress run",
|
"cypress-e2e": "cypress run",
|
||||||
"lint": "eslint --max-warnings=0 --ext .js,.ts ."
|
"lint": "eslint --max-warnings=0 --ext .js,.ts ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
v-model="form.text"
|
v-model="form.text"
|
||||||
:placeholder="$t('form.memo')"
|
:placeholder="$t('form.memo')"
|
||||||
rows="3"
|
rows="3"
|
||||||
max-rows="6"
|
|
||||||
></b-form-textarea>
|
></b-form-textarea>
|
||||||
<b-row class="mt-4 mb-6">
|
<b-row class="mt-4 mb-6">
|
||||||
<b-col>
|
<b-col>
|
||||||
|
|||||||
@ -1,29 +1,29 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ContributionMessagesList from './ContributionMessagesList.vue'
|
import ContributionMessagesList from './ContributionMessagesList.vue'
|
||||||
|
import ContributionMessagesListItem from './ContributionMessagesListItem.vue'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$t: jest.fn((t) => t),
|
||||||
|
$d: jest.fn((d) => d),
|
||||||
|
$store: {
|
||||||
|
state: {
|
||||||
|
firstName: 'Peter',
|
||||||
|
lastName: 'Lustig',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
describe('ContributionMessagesList', () => {
|
describe('ContributionMessagesList', () => {
|
||||||
let wrapper
|
|
||||||
|
|
||||||
const mocks = {
|
|
||||||
$t: jest.fn((t) => t),
|
|
||||||
$d: jest.fn((d) => d),
|
|
||||||
$store: {
|
|
||||||
state: {
|
|
||||||
firstName: 'Peter',
|
|
||||||
lastName: 'Lustig',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const propsData = {
|
const propsData = {
|
||||||
contributionId: 42,
|
contributionId: 42,
|
||||||
state: 'PENDING0',
|
state: 'PENDING',
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
id: 111,
|
id: 111,
|
||||||
message: 'asd asda sda sda',
|
message: 'Lorem ipsum?',
|
||||||
createdAt: '2022-08-29T12:23:27.000Z',
|
createdAt: '2022-08-29T12:23:27.000Z',
|
||||||
updatedAt: null,
|
updatedAt: null,
|
||||||
type: 'DIALOG',
|
type: 'DIALOG',
|
||||||
@ -32,10 +32,21 @@ describe('ContributionMessagesList', () => {
|
|||||||
userId: 107,
|
userId: 107,
|
||||||
__typename: 'ContributionMessage',
|
__typename: 'ContributionMessage',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 113,
|
||||||
|
message: 'Asda sdad ad asdasd, das Ass das Das. ',
|
||||||
|
createdAt: '2022-08-29T12:25:34.000Z',
|
||||||
|
updatedAt: null,
|
||||||
|
type: 'DIALOG',
|
||||||
|
userFirstName: 'Bibi',
|
||||||
|
userLastName: 'Bloxberg',
|
||||||
|
userId: 108,
|
||||||
|
__typename: 'ContributionMessage',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
const Wrapper = () => {
|
const ListWrapper = () => {
|
||||||
return mount(ContributionMessagesList, {
|
return mount(ContributionMessagesList, {
|
||||||
localVue,
|
localVue,
|
||||||
mocks,
|
mocks,
|
||||||
@ -45,11 +56,123 @@ describe('ContributionMessagesList', () => {
|
|||||||
|
|
||||||
describe('mount', () => {
|
describe('mount', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = Wrapper()
|
wrapper = ListWrapper()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a DIV .contribution-messages-list-item', () => {
|
it('has two DIV .contribution-messages-list-item elements', () => {
|
||||||
expect(wrapper.find('div.contribution-messages-list-item').exists()).toBe(true)
|
expect(wrapper.findAll('div.contribution-messages-list-item').length).toBe(2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('ContributionMessagesListItem', () => {
|
||||||
|
describe('if message author has moderator role', () => {
|
||||||
|
const propsData = {
|
||||||
|
message: {
|
||||||
|
id: 113,
|
||||||
|
message: 'Asda sdad ad asdasd, das Ass das Das. ',
|
||||||
|
createdAt: '2022-08-29T12:25:34.000Z',
|
||||||
|
updatedAt: null,
|
||||||
|
type: 'DIALOG',
|
||||||
|
userFirstName: 'Bibi',
|
||||||
|
userLastName: 'Bloxberg',
|
||||||
|
userId: 108,
|
||||||
|
__typename: 'ContributionMessage',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItemWrapper = () => {
|
||||||
|
return mount(ContributionMessagesListItem, {
|
||||||
|
localVue,
|
||||||
|
mocks,
|
||||||
|
propsData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
wrapper = ItemWrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a DIV .is-moderator.text-left', () => {
|
||||||
|
expect(wrapper.find('div.is-moderator.text-left').exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has the complete user name', () => {
|
||||||
|
expect(wrapper.find('div.is-moderator.text-left > span:nth-child(2)').text()).toBe(
|
||||||
|
'Bibi Bloxberg',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has the message creation date', () => {
|
||||||
|
expect(wrapper.find('div.is-moderator.text-left > span:nth-child(3)').text()).toMatch(
|
||||||
|
'Mon Aug 29 2022 12:25:34 GMT+0000',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has the moderator label', () => {
|
||||||
|
expect(wrapper.find('div.is-moderator.text-left > small:nth-child(4)').text()).toBe(
|
||||||
|
'community.moderator',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has the message', () => {
|
||||||
|
expect(wrapper.find('div.is-moderator.text-left > div:nth-child(5)').text()).toBe(
|
||||||
|
'Asda sdad ad asdasd, das Ass das Das.',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('if message author does not have moderator role', () => {
|
||||||
|
const propsData = {
|
||||||
|
message: {
|
||||||
|
id: 111,
|
||||||
|
message: 'Lorem ipsum?',
|
||||||
|
createdAt: '2022-08-29T12:23:27.000Z',
|
||||||
|
updatedAt: null,
|
||||||
|
type: 'DIALOG',
|
||||||
|
userFirstName: 'Peter',
|
||||||
|
userLastName: 'Lustig',
|
||||||
|
userId: 107,
|
||||||
|
__typename: 'ContributionMessage',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModeratorItemWrapper = () => {
|
||||||
|
return mount(ContributionMessagesListItem, {
|
||||||
|
localVue,
|
||||||
|
mocks,
|
||||||
|
propsData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
wrapper = ModeratorItemWrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a DIV .is-not-moderator.text-right', () => {
|
||||||
|
expect(wrapper.find('div.is-not-moderator.text-right').exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has the complete user name', () => {
|
||||||
|
expect(wrapper.find('div.is-not-moderator.text-right > span:nth-child(2)').text()).toBe(
|
||||||
|
'Peter Lustig',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has the message creation date', () => {
|
||||||
|
expect(wrapper.find('div.is-not-moderator.text-right > span:nth-child(3)').text()).toMatch(
|
||||||
|
'Mon Aug 29 2022 12:23:27 GMT+0000',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has the message', () => {
|
||||||
|
expect(wrapper.find('div.is-not-moderator.text-right > div:nth-child(4)').text()).toBe(
|
||||||
|
'Lorem ipsum?',
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,26 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="contribution-messages-list-item">
|
<div class="contribution-messages-list-item">
|
||||||
<is-not-moderator v-if="isNotModerator" :message="message"></is-not-moderator>
|
<div v-if="isNotModerator" class="is-not-moderator text-right">
|
||||||
<is-moderator v-else :message="message"></is-moderator>
|
<b-avatar :text="initialLetters" 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>
|
||||||
|
<div class="mt-2">{{ message.message }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="is-moderator text-left">
|
||||||
|
<b-avatar square :text="initialLetters" 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('community.moderator') }}</small>
|
||||||
|
<div class="mt-2">{{ message.message }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
import IsModerator from '@/components/ContributionMessages/slots/IsModerator.vue'
|
|
||||||
import IsNotModerator from '@/components/ContributionMessages/slots/IsNotModerator.vue'
|
|
||||||
|
|
||||||
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'ContributionMessagesListItem',
|
name: 'ContributionMessagesListItem',
|
||||||
components: {
|
|
||||||
IsModerator,
|
|
||||||
IsNotModerator,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
message: {
|
message: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
default() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -36,3 +38,19 @@ export default {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.is-not-moderator {
|
||||||
|
float: right;
|
||||||
|
/* background-color: rgb(261, 204, 221); */
|
||||||
|
width: 75%;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.is-moderator {
|
||||||
|
clear: both;
|
||||||
|
/* background-color: rgb(255, 255, 128); */
|
||||||
|
width: 75%;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,49 +0,0 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
|
||||||
import IsModerator from './IsModerator.vue'
|
|
||||||
|
|
||||||
const localVue = global.localVue
|
|
||||||
|
|
||||||
describe('IsModerator', () => {
|
|
||||||
let wrapper
|
|
||||||
|
|
||||||
const mocks = {
|
|
||||||
$t: jest.fn((t) => t),
|
|
||||||
$d: jest.fn((d) => d),
|
|
||||||
}
|
|
||||||
|
|
||||||
const propsData = {
|
|
||||||
message: {
|
|
||||||
id: 111,
|
|
||||||
message: 'asd asda sda sda',
|
|
||||||
createdAt: '2022-08-29T12:23:27.000Z',
|
|
||||||
updatedAt: null,
|
|
||||||
type: 'DIALOG',
|
|
||||||
userFirstName: 'Peter',
|
|
||||||
userLastName: 'Lustig',
|
|
||||||
userId: 107,
|
|
||||||
__typename: 'ContributionMessage',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const Wrapper = () => {
|
|
||||||
return mount(IsModerator, {
|
|
||||||
localVue,
|
|
||||||
mocks,
|
|
||||||
propsData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('mount', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a DIV .slot-is-moderator', () => {
|
|
||||||
expect(wrapper.find('div.slot-is-moderator').exists()).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('props.message.default', () => {
|
|
||||||
expect(wrapper.vm.$options.props.message.default.call()).toEqual({})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="slot-is-moderator">
|
|
||||||
<b-avatar square :text="initialLetters" 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('community.moderator') }}</small>
|
|
||||||
<div class="mt-2">{{ message.message }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
message: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
initialLetters() {
|
|
||||||
return `${this.message.userFirstName[0]} ${this.message.userLastName[0]}`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.slot-is-moderator {
|
|
||||||
clear: both;
|
|
||||||
/* background-color: rgb(255, 242, 227); */
|
|
||||||
width: 75%;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
|
||||||
import IsNotModerator from './IsNotModerator.vue'
|
|
||||||
|
|
||||||
const localVue = global.localVue
|
|
||||||
|
|
||||||
describe('IsNotModerator', () => {
|
|
||||||
let wrapper
|
|
||||||
|
|
||||||
const mocks = {
|
|
||||||
$t: jest.fn((t) => t),
|
|
||||||
$d: jest.fn((d) => d),
|
|
||||||
}
|
|
||||||
|
|
||||||
const propsData = {
|
|
||||||
message: {
|
|
||||||
id: 113,
|
|
||||||
message: 'asda sdad ad asdasd ',
|
|
||||||
createdAt: '2022-08-29T12:25:34.000Z',
|
|
||||||
updatedAt: null,
|
|
||||||
type: 'DIALOG',
|
|
||||||
userFirstName: 'Bibi',
|
|
||||||
userLastName: 'Bloxberg',
|
|
||||||
userId: 108,
|
|
||||||
__typename: 'ContributionMessage',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const Wrapper = () => {
|
|
||||||
return mount(IsNotModerator, {
|
|
||||||
localVue,
|
|
||||||
mocks,
|
|
||||||
propsData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('mount', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a DIV .slot-is-not-moderator', () => {
|
|
||||||
expect(wrapper.find('div.slot-is-not-moderator').exists()).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('props.message.default', () => {
|
|
||||||
expect(wrapper.vm.$options.props.message.default.call()).toEqual({})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="slot-is-not-moderator">
|
|
||||||
<div class="text-right">
|
|
||||||
<b-avatar :text="initialLetters" 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>
|
|
||||||
<div class="mt-2">{{ message.message }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
message: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
initialLetters() {
|
|
||||||
return `${this.message.userFirstName[0]} ${this.message.userLastName[0]}`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.slot-is-not-moderator {
|
|
||||||
float: right;
|
|
||||||
width: 75%;
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -42,7 +42,6 @@
|
|||||||
id="contribution-memo"
|
id="contribution-memo"
|
||||||
v-model="form.memo"
|
v-model="form.memo"
|
||||||
rows="3"
|
rows="3"
|
||||||
max-rows="6"
|
|
||||||
:placeholder="$t('contribution.yourActivity')"
|
:placeholder="$t('contribution.yourActivity')"
|
||||||
required
|
required
|
||||||
></b-form-textarea>
|
></b-form-textarea>
|
||||||
|
|||||||
@ -46,7 +46,7 @@
|
|||||||
<label class="input-1 mt-4" for="input-1">{{ $t('form.recipient') }}</label>
|
<label class="input-1 mt-4" for="input-1">{{ $t('form.recipient') }}</label>
|
||||||
<b-input-group
|
<b-input-group
|
||||||
id="input-group-1"
|
id="input-group-1"
|
||||||
class="border border-default"
|
class="border border-default border-radius"
|
||||||
description="We'll never share your email with anyone else."
|
description="We'll never share your email with anyone else."
|
||||||
size="lg"
|
size="lg"
|
||||||
>
|
>
|
||||||
@ -81,7 +81,11 @@
|
|||||||
v-slot="{ errors, valid }"
|
v-slot="{ errors, valid }"
|
||||||
>
|
>
|
||||||
<label class="input-2" for="input-2">{{ $t('form.amount') }}</label>
|
<label class="input-2" for="input-2">{{ $t('form.amount') }}</label>
|
||||||
<b-input-group id="input-group-2" class="border border-default" size="lg">
|
<b-input-group
|
||||||
|
id="input-group-2"
|
||||||
|
class="border border-default border-radius"
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
<b-input-group-prepend class="p-2 d-none d-md-block">
|
<b-input-group-prepend class="p-2 d-none d-md-block">
|
||||||
<div class="m-1 mt-2">{{ $t('GDD') }}</div>
|
<div class="m-1 mt-2">{{ $t('GDD') }}</div>
|
||||||
</b-input-group-prepend>
|
</b-input-group-prepend>
|
||||||
@ -115,7 +119,7 @@
|
|||||||
v-slot="{ errors }"
|
v-slot="{ errors }"
|
||||||
>
|
>
|
||||||
<label class="input-3" for="input-3">{{ $t('form.message') }}</label>
|
<label class="input-3" for="input-3">{{ $t('form.message') }}</label>
|
||||||
<b-input-group id="input-group-3" class="border border-default">
|
<b-input-group id="input-group-3" class="border border-default border-radius">
|
||||||
<b-input-group-prepend class="d-none d-md-block">
|
<b-input-group-prepend class="d-none d-md-block">
|
||||||
<b-icon icon="chat-right-text" class="display-4 m-3 mt-4"></b-icon>
|
<b-icon icon="chat-right-text" class="display-4 m-3 mt-4"></b-icon>
|
||||||
</b-input-group-prepend>
|
</b-input-group-prepend>
|
||||||
@ -237,4 +241,7 @@ span.errors {
|
|||||||
#input-3:focus {
|
#input-3:focus {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.border-radius {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -29,6 +29,7 @@
|
|||||||
required: true,
|
required: true,
|
||||||
samePassword: value.password,
|
samePassword: value.password,
|
||||||
}"
|
}"
|
||||||
|
id="repeat-new-password-input-field"
|
||||||
:label="register ? $t('form.passwordRepeat') : $t('form.password_new_repeat')"
|
:label="register ? $t('form.passwordRepeat') : $t('form.password_new_repeat')"
|
||||||
:immediate="true"
|
:immediate="true"
|
||||||
:name="createId(register ? $t('form.passwordRepeat') : $t('form.password_new_repeat'))"
|
:name="createId(register ? $t('form.passwordRepeat') : $t('form.password_new_repeat'))"
|
||||||
|
|||||||
@ -136,3 +136,27 @@ export const createContributionMessage = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const login = gql`
|
||||||
|
mutation($email: String!, $password: String!, $publisherId: Int) {
|
||||||
|
login(email: $email, password: $password, publisherId: $publisherId) {
|
||||||
|
email
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
language
|
||||||
|
klickTipp {
|
||||||
|
newsletterState
|
||||||
|
}
|
||||||
|
hasElopage
|
||||||
|
publisherId
|
||||||
|
isAdmin
|
||||||
|
creation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const logout = gql`
|
||||||
|
mutation {
|
||||||
|
logout
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|||||||
@ -1,23 +1,5 @@
|
|||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
export const login = gql`
|
|
||||||
query($email: String!, $password: String!, $publisherId: Int) {
|
|
||||||
login(email: $email, password: $password, publisherId: $publisherId) {
|
|
||||||
email
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
language
|
|
||||||
klickTipp {
|
|
||||||
newsletterState
|
|
||||||
}
|
|
||||||
hasElopage
|
|
||||||
publisherId
|
|
||||||
isAdmin
|
|
||||||
creation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const verifyLogin = gql`
|
export const verifyLogin = gql`
|
||||||
query {
|
query {
|
||||||
verifyLogin {
|
verifyLogin {
|
||||||
@ -36,12 +18,6 @@ export const verifyLogin = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const logout = gql`
|
|
||||||
query {
|
|
||||||
logout
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const transactionsQuery = gql`
|
export const transactionsQuery = gql`
|
||||||
query($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) {
|
query($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) {
|
||||||
transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
|
transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
|
||||||
|
|||||||
@ -18,6 +18,7 @@ const apolloMock = jest.fn().mockResolvedValue({
|
|||||||
logout: 'success',
|
logout: 'success',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
const apolloQueryMock = jest.fn()
|
||||||
|
|
||||||
describe('DashboardLayout', () => {
|
describe('DashboardLayout', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
@ -40,7 +41,8 @@ describe('DashboardLayout', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
$apollo: {
|
$apollo: {
|
||||||
query: apolloMock,
|
mutate: apolloMock,
|
||||||
|
query: apolloQueryMock,
|
||||||
},
|
},
|
||||||
$store: {
|
$store: {
|
||||||
state: {
|
state: {
|
||||||
@ -142,7 +144,7 @@ describe('DashboardLayout', () => {
|
|||||||
|
|
||||||
describe('update transactions', () => {
|
describe('update transactions', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
apolloMock.mockResolvedValue({
|
apolloQueryMock.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
transactionList: {
|
transactionList: {
|
||||||
balance: {
|
balance: {
|
||||||
@ -163,7 +165,7 @@ describe('DashboardLayout', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('calls the API', () => {
|
it('calls the API', () => {
|
||||||
expect(apolloMock).toBeCalledWith(
|
expect(apolloQueryMock).toBeCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
currentPage: 2,
|
currentPage: 2,
|
||||||
@ -201,7 +203,7 @@ describe('DashboardLayout', () => {
|
|||||||
|
|
||||||
describe('update transactions returns error', () => {
|
describe('update transactions returns error', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
apolloMock.mockRejectedValue({
|
apolloQueryMock.mockRejectedValue({
|
||||||
message: 'Ouch!',
|
message: 'Ouch!',
|
||||||
})
|
})
|
||||||
await wrapper
|
await wrapper
|
||||||
|
|||||||
@ -41,7 +41,8 @@
|
|||||||
import Navbar from '@/components/Menu/Navbar.vue'
|
import Navbar from '@/components/Menu/Navbar.vue'
|
||||||
import Sidebar from '@/components/Menu/Sidebar.vue'
|
import Sidebar from '@/components/Menu/Sidebar.vue'
|
||||||
import SessionLogoutTimeout from '@/components/SessionLogoutTimeout.vue'
|
import SessionLogoutTimeout from '@/components/SessionLogoutTimeout.vue'
|
||||||
import { logout, transactionsQuery } from '@/graphql/queries'
|
import { transactionsQuery } from '@/graphql/queries'
|
||||||
|
import { logout } from '@/graphql/mutations'
|
||||||
import ContentFooter from '@/components/ContentFooter.vue'
|
import ContentFooter from '@/components/ContentFooter.vue'
|
||||||
import { FadeTransition } from 'vue2-transitions'
|
import { FadeTransition } from 'vue2-transitions'
|
||||||
import CONFIG from '@/config'
|
import CONFIG from '@/config'
|
||||||
@ -75,8 +76,8 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
async logout() {
|
async logout() {
|
||||||
this.$apollo
|
this.$apollo
|
||||||
.query({
|
.mutate({
|
||||||
query: logout,
|
mutation: logout,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$store.dispatch('logout')
|
this.$store.dispatch('logout')
|
||||||
|
|||||||
@ -24,7 +24,8 @@
|
|||||||
"moderator": "Moderator",
|
"moderator": "Moderator",
|
||||||
"moderators": "Moderatoren",
|
"moderators": "Moderatoren",
|
||||||
"myContributions": "Meine Beiträge zum Gemeinwohl",
|
"myContributions": "Meine Beiträge zum Gemeinwohl",
|
||||||
"openContributionLinks": "öffentliche Beitrags-Linkliste",
|
"noOpenContributionLinkText": "Zur Zeit gibt es keine automatischen Schöpfungen.",
|
||||||
|
"openContributionLinks": "Öffentliche Beitrags-Linkliste",
|
||||||
"openContributionLinkText": "Folgende {count} automatische Schöpfungen werden zur Zeit durch die Gemeinschaft „{name}“ bereitgestellt.",
|
"openContributionLinkText": "Folgende {count} automatische Schöpfungen werden zur Zeit durch die Gemeinschaft „{name}“ bereitgestellt.",
|
||||||
"other-communities": "Weitere Gemeinschaften",
|
"other-communities": "Weitere Gemeinschaften",
|
||||||
"submitContribution": "Beitrag einreichen",
|
"submitContribution": "Beitrag einreichen",
|
||||||
|
|||||||
@ -24,7 +24,8 @@
|
|||||||
"moderator": "Moderator",
|
"moderator": "Moderator",
|
||||||
"moderators": "Moderators",
|
"moderators": "Moderators",
|
||||||
"myContributions": "My contributions to the common good",
|
"myContributions": "My contributions to the common good",
|
||||||
"openContributionLinks": "open Contribution links list",
|
"noOpenContributionLinkText": "Currently there are no automatic creations.",
|
||||||
|
"openContributionLinks": "Open contribution-link list",
|
||||||
"openContributionLinkText": "The following {count} automatic creations are currently provided by the \"{name}\" community.",
|
"openContributionLinkText": "The following {count} automatic creations are currently provided by the \"{name}\" community.",
|
||||||
"other-communities": "Other communities",
|
"other-communities": "Other communities",
|
||||||
"submitContribution": "Submit contribution",
|
"submitContribution": "Submit contribution",
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
<hr />
|
<hr />
|
||||||
<b-container>
|
<b-container>
|
||||||
<div class="h3">{{ $t('community.openContributionLinks') }}</div>
|
<div class="h3">{{ $t('community.openContributionLinks') }}</div>
|
||||||
<small>
|
<small v-if="count > 0">
|
||||||
{{
|
{{
|
||||||
$t('community.openContributionLinkText', {
|
$t('community.openContributionLinkText', {
|
||||||
name: CONFIG.COMMUNITY_NAME,
|
name: CONFIG.COMMUNITY_NAME,
|
||||||
@ -22,6 +22,9 @@
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</small>
|
</small>
|
||||||
|
<small v-else>
|
||||||
|
{{ $t('community.noOpenContributionLinkText') }}
|
||||||
|
</small>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="item in itemsContributionLinks" v-bind:key="item.id">
|
<li v-for="item in itemsContributionLinks" v-bind:key="item.id">
|
||||||
<div>{{ item.name }}</div>
|
<div>{{ item.name }}</div>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import Login from './Login'
|
|||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
const apolloQueryMock = jest.fn()
|
const apolloMutateMock = jest.fn()
|
||||||
const mockStoreDispach = jest.fn()
|
const mockStoreDispach = jest.fn()
|
||||||
const mockStoreCommit = jest.fn()
|
const mockStoreCommit = jest.fn()
|
||||||
const mockRouterPush = jest.fn()
|
const mockRouterPush = jest.fn()
|
||||||
@ -41,7 +41,7 @@ describe('Login', () => {
|
|||||||
params: {},
|
params: {},
|
||||||
},
|
},
|
||||||
$apollo: {
|
$apollo: {
|
||||||
query: apolloQueryMock,
|
mutate: apolloMutateMock,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ describe('Login', () => {
|
|||||||
await wrapper.find('input[placeholder="Email"]').setValue('user@example.org')
|
await wrapper.find('input[placeholder="Email"]').setValue('user@example.org')
|
||||||
await wrapper.find('input[placeholder="form.password"]').setValue('1234')
|
await wrapper.find('input[placeholder="form.password"]').setValue('1234')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
apolloQueryMock.mockResolvedValue({
|
apolloMutateMock.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
login: 'token',
|
login: 'token',
|
||||||
},
|
},
|
||||||
@ -123,7 +123,7 @@ describe('Login', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('calls the API with the given data', () => {
|
it('calls the API with the given data', () => {
|
||||||
expect(apolloQueryMock).toBeCalledWith(
|
expect(apolloMutateMock).toBeCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
@ -175,7 +175,7 @@ describe('Login', () => {
|
|||||||
|
|
||||||
describe('login fails', () => {
|
describe('login fails', () => {
|
||||||
const createError = async (errorMessage) => {
|
const createError = async (errorMessage) => {
|
||||||
apolloQueryMock.mockRejectedValue({
|
apolloMutateMock.mockRejectedValue({
|
||||||
message: errorMessage,
|
message: errorMessage,
|
||||||
})
|
})
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
import InputPassword from '@/components/Inputs/InputPassword'
|
import InputPassword from '@/components/Inputs/InputPassword'
|
||||||
import InputEmail from '@/components/Inputs/InputEmail'
|
import InputEmail from '@/components/Inputs/InputEmail'
|
||||||
import Message from '@/components/Message/Message'
|
import Message from '@/components/Message/Message'
|
||||||
import { login } from '@/graphql/queries'
|
import { login } from '@/graphql/mutations'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
@ -71,14 +71,13 @@ export default {
|
|||||||
container: this.$refs.submitButton,
|
container: this.$refs.submitButton,
|
||||||
})
|
})
|
||||||
this.$apollo
|
this.$apollo
|
||||||
.query({
|
.mutate({
|
||||||
query: login,
|
mutation: login,
|
||||||
variables: {
|
variables: {
|
||||||
email: this.form.email,
|
email: this.form.email,
|
||||||
password: this.form.password,
|
password: this.form.password,
|
||||||
publisherId: this.$store.state.publisherId,
|
publisherId: this.$store.state.publisherId,
|
||||||
},
|
},
|
||||||
fetchPolicy: 'network-only',
|
|
||||||
})
|
})
|
||||||
.then(async (result) => {
|
.then(async (result) => {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -43,6 +43,7 @@ const mocks = {
|
|||||||
$store: {
|
$store: {
|
||||||
state: {
|
state: {
|
||||||
token: null,
|
token: null,
|
||||||
|
tokenTime: null,
|
||||||
email: 'bibi@bloxberg.de',
|
email: 'bibi@bloxberg.de',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -68,7 +69,7 @@ describe('TransactionLink', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('mount', () => {
|
describe('mount', () => {
|
||||||
beforeEach(() => {
|
beforeAll(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
})
|
})
|
||||||
@ -214,112 +215,159 @@ describe('TransactionLink', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('token in store and own link', () => {
|
describe('token in store', () => {
|
||||||
beforeEach(() => {
|
beforeAll(() => {
|
||||||
mocks.$store.state.token = 'token'
|
mocks.$store.state.token = 'token'
|
||||||
apolloQueryMock.mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
queryTransactionLink: {
|
|
||||||
__typename: 'TransactionLink',
|
|
||||||
id: 92,
|
|
||||||
amount: '22',
|
|
||||||
memo: 'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ',
|
|
||||||
createdAt: '2022-03-17T16:10:28.000Z',
|
|
||||||
validUntil: transactionLinkValidExpireDate(),
|
|
||||||
redeemedAt: null,
|
|
||||||
deletedAt: null,
|
|
||||||
user: { firstName: 'Bibi', publisherId: 0, email: 'bibi@bloxberg.de' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a RedeemSelfCreator component', () => {
|
describe('sufficient token time in store', () => {
|
||||||
expect(wrapper.findComponent({ name: 'RedeemSelfCreator' }).exists()).toBe(true)
|
beforeAll(() => {
|
||||||
})
|
mocks.$store.state.tokenTime = Math.floor(Date.now() / 1000) + 20
|
||||||
|
|
||||||
it('has a no redeem text', () => {
|
|
||||||
expect(wrapper.findComponent({ name: 'RedeemSelfCreator' }).text()).toContain(
|
|
||||||
'gdd_per_link.no-redeem',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it.skip('has a link to transaction page', () => {
|
|
||||||
expect(wrapper.find('a[target="/transactions"]').exists()).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('valid link', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
mocks.$store.state.token = 'token'
|
|
||||||
apolloQueryMock.mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
queryTransactionLink: {
|
|
||||||
__typename: 'TransactionLink',
|
|
||||||
id: 92,
|
|
||||||
amount: '22',
|
|
||||||
memo: 'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ',
|
|
||||||
createdAt: '2022-03-17T16:10:28.000Z',
|
|
||||||
validUntil: transactionLinkValidExpireDate(),
|
|
||||||
redeemedAt: null,
|
|
||||||
deletedAt: null,
|
|
||||||
user: { firstName: 'Peter', publisherId: 0, email: 'peter@listig.de' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a RedeemValid component', () => {
|
|
||||||
expect(wrapper.findComponent({ name: 'RedeemValid' }).exists()).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a button with redeem text', () => {
|
|
||||||
expect(wrapper.findComponent({ name: 'RedeemValid' }).find('button').text()).toBe(
|
|
||||||
'gdd_per_link.redeem',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('redeem link with success', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
apolloMutateMock.mockResolvedValue()
|
|
||||||
await wrapper.findComponent({ name: 'RedeemValid' }).find('button').trigger('click')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calls the API', () => {
|
describe('own link', () => {
|
||||||
expect(apolloMutateMock).toBeCalledWith(
|
beforeAll(() => {
|
||||||
expect.objectContaining({
|
apolloQueryMock.mockResolvedValue({
|
||||||
mutation: redeemTransactionLink,
|
data: {
|
||||||
variables: {
|
queryTransactionLink: {
|
||||||
code: 'some-code',
|
__typename: 'TransactionLink',
|
||||||
|
id: 92,
|
||||||
|
amount: '22',
|
||||||
|
memo:
|
||||||
|
'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ',
|
||||||
|
createdAt: '2022-03-17T16:10:28.000Z',
|
||||||
|
validUntil: transactionLinkValidExpireDate(),
|
||||||
|
redeemedAt: null,
|
||||||
|
deletedAt: null,
|
||||||
|
user: { firstName: 'Bibi', publisherId: 0, email: 'bibi@bloxberg.de' },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}),
|
})
|
||||||
)
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a RedeemSelfCreator component', () => {
|
||||||
|
expect(wrapper.findComponent({ name: 'RedeemSelfCreator' }).exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a no redeem text', () => {
|
||||||
|
expect(wrapper.findComponent({ name: 'RedeemSelfCreator' }).text()).toContain(
|
||||||
|
'gdd_per_link.no-redeem',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.skip('has a link to transaction page', () => {
|
||||||
|
expect(wrapper.find('a[target="/transactions"]').exists()).toBe(true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('toasts a success message', () => {
|
describe('valid link', () => {
|
||||||
expect(mocks.$t).toBeCalledWith('gdd_per_link.redeem')
|
beforeAll(() => {
|
||||||
expect(toastSuccessSpy).toBeCalledWith('gdd_per_link.redeemed; ')
|
apolloQueryMock.mockResolvedValue({
|
||||||
})
|
data: {
|
||||||
|
queryTransactionLink: {
|
||||||
|
__typename: 'TransactionLink',
|
||||||
|
id: 92,
|
||||||
|
amount: '22',
|
||||||
|
memo:
|
||||||
|
'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ',
|
||||||
|
createdAt: '2022-03-17T16:10:28.000Z',
|
||||||
|
validUntil: transactionLinkValidExpireDate(),
|
||||||
|
redeemedAt: null,
|
||||||
|
deletedAt: null,
|
||||||
|
user: { firstName: 'Peter', publisherId: 0, email: 'peter@listig.de' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
it('pushes the route to overview', () => {
|
it('has a RedeemValid component', () => {
|
||||||
expect(routerPushMock).toBeCalledWith('/overview')
|
expect(wrapper.findComponent({ name: 'RedeemValid' }).exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a button with redeem text', () => {
|
||||||
|
expect(wrapper.findComponent({ name: 'RedeemValid' }).find('button').text()).toBe(
|
||||||
|
'gdd_per_link.redeem',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('redeem link with success', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
apolloMutateMock.mockResolvedValue()
|
||||||
|
await wrapper.findComponent({ name: 'RedeemValid' }).find('button').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the API', () => {
|
||||||
|
expect(apolloMutateMock).toBeCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
mutation: redeemTransactionLink,
|
||||||
|
variables: {
|
||||||
|
code: 'some-code',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toasts a success message', () => {
|
||||||
|
expect(mocks.$t).toBeCalledWith('gdd_per_link.redeem')
|
||||||
|
expect(toastSuccessSpy).toBeCalledWith('gdd_per_link.redeemed; ')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('pushes the route to overview', () => {
|
||||||
|
expect(routerPushMock).toBeCalledWith('/overview')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('redeem link with error', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
apolloMutateMock.mockRejectedValue({ message: 'Oh Noo!' })
|
||||||
|
await wrapper.findComponent({ name: 'RedeemValid' }).find('button').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toasts an error message', () => {
|
||||||
|
expect(toastErrorSpy).toBeCalledWith('Oh Noo!')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('pushes the route to overview', () => {
|
||||||
|
expect(routerPushMock).toBeCalledWith('/overview')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('redeem link with error', () => {
|
describe('no sufficient token time in store', () => {
|
||||||
beforeEach(async () => {
|
beforeAll(() => {
|
||||||
apolloMutateMock.mockRejectedValue({ message: 'Oh Noo!' })
|
mocks.$store.state.tokenTime = 1665125185
|
||||||
await wrapper.findComponent({ name: 'RedeemValid' }).find('button').trigger('click')
|
apolloQueryMock.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
queryTransactionLink: {
|
||||||
|
__typename: 'TransactionLink',
|
||||||
|
id: 92,
|
||||||
|
amount: '22',
|
||||||
|
memo:
|
||||||
|
'Abrakadabra drei, vier, fünf, sechs, hier steht jetzt ein Memotext! Hex hex ',
|
||||||
|
createdAt: '2022-03-17T16:10:28.000Z',
|
||||||
|
validUntil: transactionLinkValidExpireDate(),
|
||||||
|
redeemedAt: null,
|
||||||
|
deletedAt: null,
|
||||||
|
user: { firstName: 'Bibi', publisherId: 0, email: 'bibi@bloxberg.de' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
wrapper = Wrapper()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('toasts an error message', () => {
|
it('has a RedeemLoggedOut component', () => {
|
||||||
expect(toastErrorSpy).toBeCalledWith('Oh Noo!')
|
expect(wrapper.findComponent({ name: 'RedeemLoggedOut' }).exists()).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('pushes the route to overview', () => {
|
it('has a link to register with code', () => {
|
||||||
expect(routerPushMock).toBeCalledWith('/overview')
|
expect(wrapper.find('a[href="/register/some-code"]').exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a link to login with code', () => {
|
||||||
|
expect(wrapper.find('a[href="/login/some-code"]').exists()).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -103,6 +103,12 @@ export default {
|
|||||||
isContributionLink() {
|
isContributionLink() {
|
||||||
return this.$route.params.code.search(/^CL-/) === 0
|
return this.$route.params.code.search(/^CL-/) === 0
|
||||||
},
|
},
|
||||||
|
tokenExpiresInSeconds() {
|
||||||
|
const remainingSecs = Math.floor(
|
||||||
|
(new Date(this.$store.state.tokenTime * 1000).getTime() - new Date().getTime()) / 1000,
|
||||||
|
)
|
||||||
|
return remainingSecs <= 0 ? 0 : remainingSecs
|
||||||
|
},
|
||||||
itemType() {
|
itemType() {
|
||||||
// link is deleted: at, from
|
// link is deleted: at, from
|
||||||
if (this.linkData.deletedAt) {
|
if (this.linkData.deletedAt) {
|
||||||
@ -130,7 +136,9 @@ export default {
|
|||||||
return `TEXT`
|
return `TEXT`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.$store.state.token) {
|
if (this.$store.state.token && this.$store.state.tokenTime) {
|
||||||
|
if (this.tokenExpiresInSeconds < 5) return `LOGGED_OUT`
|
||||||
|
|
||||||
// logged in, nicht berechtigt einzulösen, eigener link
|
// logged in, nicht berechtigt einzulösen, eigener link
|
||||||
if (this.linkData.user && this.$store.state.email === this.linkData.user.email) {
|
if (this.linkData.user && this.$store.state.email === this.linkData.user.email) {
|
||||||
return `SELF_CREATOR`
|
return `SELF_CREATOR`
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user