Merge branch 'master' into seed-transactions

This commit is contained in:
Moriz Wahl 2021-12-07 10:54:06 +01:00
commit 911e0cdf2a
44 changed files with 1754 additions and 346 deletions

View File

@ -399,7 +399,7 @@ jobs:
report_name: Coverage Frontend report_name: Coverage Frontend
type: lcov type: lcov
result_path: ./coverage/lcov.info result_path: ./coverage/lcov.info
min_coverage: 86 min_coverage: 87
token: ${{ github.token }} token: ${{ github.token }}
############################################################################## ##############################################################################
@ -441,7 +441,7 @@ jobs:
report_name: Coverage Admin Interface report_name: Coverage Admin Interface
type: lcov type: lcov
result_path: ./coverage/lcov.info result_path: ./coverage/lcov.info
min_coverage: 50 min_coverage: 60
token: ${{ github.token }} token: ${{ github.token }}
############################################################################## ##############################################################################

View File

@ -69,6 +69,22 @@ We are currently restructuring the service to reduce dependencies and unify busi
Once you have `docker-compose` up and running, you can open [http://localhost/vue](http://localhost/vue) and create yourself a new wallet account. Once you have `docker-compose` up and running, you can open [http://localhost/vue](http://localhost/vue) and create yourself a new wallet account.
## How to release
A release is tagged on Github by its version number and published as github release. This is done automatically when a new version is defined in the [package.json](./package.json) and merged into master - furthermore we set all our sub-package-versions to the same version as the main package.json version to make version management as simple as possible.
Each release is accompanied with release notes automatically generated from the git log which is available as [CHANGELOG.md](./CHANGELOG.md).
To generate the Changelog and set a new Version you should use the following commands in the main folder
```bash
git fetch --all
yarn release
```
The first command `git fetch --all` will make sure you have all tags previously defined which is required to generate a correct changelog. The second command `yarn release` will execute the changelog tool and set version numbers in the main package and sub-packages. It is required to do `yarn install` before you can use this command.
After generating a new version you should commit the changes. This will be the CHANGELOG.md and several package.json files. This commit will be omitted in the changelog.
Note: The Changelog will be regenerated with all tags on release on the external builder tool, but will not be checked in there. The Changelog on the github release will therefore always be correct, on the repo it might be incorrect due to missing tags when executing the `yarn release` command.
## Troubleshooting ## Troubleshooting
| Problem | Issue | Solution | Description | | Problem | Issue | Solution | Description |

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

@ -11,7 +11,14 @@ const apolloMock = jest.fn().mockResolvedValue({
}, },
}, },
}) })
const apolloMutateMock = jest.fn().mockResolvedValue({
data: {
createPendingCreation: [0, 0, 0],
},
})
const stateCommitMock = jest.fn() const stateCommitMock = jest.fn()
const toastedErrorMock = jest.fn()
const toastedSuccessMock = jest.fn()
const mocks = { const mocks = {
$moment: jest.fn(() => { $moment: jest.fn(() => {
@ -26,15 +33,25 @@ const mocks = {
}), }),
$apollo: { $apollo: {
query: apolloMock, query: apolloMock,
mutate: apolloMutateMock,
}, },
$store: { $store: {
commit: stateCommitMock, commit: stateCommitMock,
state: {
moderator: {
id: 0,
name: 'test moderator',
},
},
},
$toasted: {
error: toastedErrorMock,
success: toastedSuccessMock,
}, },
} }
const propsData = { const propsData = {
type: '', type: '',
item: {},
creation: [], creation: [],
itemsMassCreation: {}, itemsMassCreation: {},
} }
@ -64,9 +81,10 @@ describe('CreationFormular', () => {
describe('server throws error for moderator data call', () => { describe('server throws error for moderator data call', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks() jest.clearAllMocks()
apolloMock.mockRejectedValue({ message: 'Ouch!' }) apolloMock.mockRejectedValueOnce({ message: 'Ouch!' })
wrapper = Wrapper() wrapper = Wrapper()
}) })
it('has called store commit with fake data', () => { it('has called store commit with fake data', () => {
expect(stateCommitMock).toBeCalledWith('moderator', { id: 0, name: 'Test Moderator' }) expect(stateCommitMock).toBeCalledWith('moderator', { id: 0, name: 'Test Moderator' })
}) })
@ -125,6 +143,8 @@ describe('CreationFormular', () => {
jest.clearAllMocks() jest.clearAllMocks()
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] }) await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
await wrapper.setData({ rangeMin: 180 }) await wrapper.setData({ rangeMin: 180 })
await wrapper.setData({ text: 'Test create coins' })
await wrapper.setData({ value: 90 })
}) })
describe('first radio button', () => { describe('first radio button', () => {
@ -139,6 +159,66 @@ describe('CreationFormular', () => {
it('sets rangeMax to 200', () => { it('sets rangeMax to 200', () => {
expect(wrapper.vm.rangeMax).toBe(200) expect(wrapper.vm.rangeMax).toBe(200)
}) })
describe('sendForm', () => {
beforeEach(async () => {
await wrapper.find('.test-submit').trigger('click')
})
it('sends ... to apollo', () => {
expect(apolloMutateMock).toBeCalled()
})
})
describe('sendForm', () => {
beforeEach(async () => {
apolloMutateMock.mockRejectedValueOnce({ message: 'Ouch!' })
await wrapper.find('.test-submit').trigger('click')
})
it('sends ... to apollo', () => {
expect(toastedErrorMock).toBeCalled()
})
})
describe('Negativ value', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
await wrapper.setData({ rangeMin: 180 })
await wrapper.setData({ value: -20 })
})
it('has no submit button', async () => {
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
})
})
describe('Empty text', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
await wrapper.setData({ rangeMin: 180 })
await wrapper.setData({ text: '' })
})
it('has no submit button', async () => {
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
})
})
describe('Text length less than 10', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
await wrapper.setData({ rangeMin: 180 })
await wrapper.setData({ text: 'Try this' })
})
it('has no submit button', async () => {
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
})
})
}) })
describe('second radio button', () => { describe('second radio button', () => {
@ -153,6 +233,55 @@ describe('CreationFormular', () => {
it('sets rangeMax to 400', () => { it('sets rangeMax to 400', () => {
expect(wrapper.vm.rangeMax).toBe(400) expect(wrapper.vm.rangeMax).toBe(400)
}) })
describe('sendForm', () => {
beforeEach(async () => {
await wrapper.find('.test-submit').trigger('click')
})
it('sends ... to apollo', () => {
expect(apolloMutateMock).toBeCalled()
})
})
describe('Negativ value', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
await wrapper.setData({ rangeMin: 180 })
await wrapper.setData({ value: -20 })
})
it('has no submit button', async () => {
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
})
})
describe('Empty text', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
await wrapper.setData({ rangeMin: 180 })
await wrapper.setData({ text: '' })
})
it('has no submit button', async () => {
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
})
})
describe('Text length less than 10', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
await wrapper.setData({ rangeMin: 180 })
await wrapper.setData({ text: 'Try this' })
})
it('has no submit button', async () => {
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
})
})
}) })
describe('third radio button', () => { describe('third radio button', () => {
@ -167,6 +296,63 @@ describe('CreationFormular', () => {
it('sets rangeMax to 400', () => { it('sets rangeMax to 400', () => {
expect(wrapper.vm.rangeMax).toBe(600) expect(wrapper.vm.rangeMax).toBe(600)
}) })
describe('sendForm', () => {
beforeEach(async () => {
await wrapper.find('.test-submit').trigger('click')
})
it('sends mutation to apollo', () => {
expect(apolloMutateMock).toBeCalled()
})
it('toast success message', () => {
expect(toastedSuccessMock).toBeCalled()
})
it('store commit openCreationPlus', () => {
expect(stateCommitMock).toBeCalledWith('openCreationsPlus', 1)
})
})
describe('Negativ value', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
await wrapper.setData({ rangeMin: 180 })
await wrapper.setData({ value: -20 })
})
it('has no submit button', async () => {
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
})
})
describe('Empty text', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
await wrapper.setData({ rangeMin: 180 })
await wrapper.setData({ text: '' })
})
it('has no submit button', async () => {
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
})
})
describe('Text length less than 10', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
await wrapper.setData({ rangeMin: 180 })
await wrapper.setData({ text: 'Try this' })
})
it('has no submit button', async () => {
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
})
})
}) })
}) })
}) })

View File

@ -1,60 +1,54 @@
<template> <template>
<div class="component-creation-formular"> <div class="component-creation-formular">
<div> <div class="shadow p-3 mb-5 bg-white rounded">
<h3>
{{
this.type === 'singleCreation'
? 'Einzelschöpfung für ' + item.firstName + ' ' + item.lastName + ''
: 'Mehrfachschöpfung für ' + Object.keys(this.itemsMassCreation).length + ' Mitglieder'
}}
</h3>
<div v-show="this.type === 'massCreation' && Object.keys(this.itemsMassCreation).length <= 0">
Bitte wähle ein oder Mehrere Mitglieder aus für die du Schöpfen möchtest
</div>
</div>
<div
v-show="this.type === 'singleCreation' || Object.keys(this.itemsMassCreation).length > 0"
class="shadow p-3 mb-5 bg-white rounded"
>
<b-form ref="creationForm"> <b-form ref="creationForm">
<b-row class="m-4"> <b-row class="m-4">
<label>Monat Auswählen</label> <label>Monat Auswählen</label>
<b-col class="text-left"> <b-col class="text-left">
<b-form-radio <b-form-radio
id="beforeLastMonth"
v-model="radioSelected" v-model="radioSelected"
:value="beforeLastMonth" :value="beforeLastMonth"
:disabled="creation[0] === 0" :disabled="creation[0] === 0"
size="lg" size="lg"
@change="updateRadioSelected(beforeLastMonth, 0, creation[0])" @change="updateRadioSelected(beforeLastMonth, 0, creation[0])"
> >
<label for="beforeLastMonth">
{{ beforeLastMonth.short }} {{ creation[0] != null ? creation[0] + ' GDD' : '' }} {{ beforeLastMonth.short }} {{ creation[0] != null ? creation[0] + ' GDD' : '' }}
</label>
</b-form-radio> </b-form-radio>
</b-col> </b-col>
<b-col> <b-col>
<b-form-radio <b-form-radio
id="lastMonth"
v-model="radioSelected" v-model="radioSelected"
:value="lastMonth" :value="lastMonth"
:disabled="creation[1] === 0" :disabled="creation[1] === 0"
size="lg" size="lg"
@change="updateRadioSelected(lastMonth, 1, creation[1])" @change="updateRadioSelected(lastMonth, 1, creation[1])"
> >
<label for="lastMonth">
{{ lastMonth.short }} {{ creation[1] != null ? creation[1] + ' GDD' : '' }} {{ lastMonth.short }} {{ creation[1] != null ? creation[1] + ' GDD' : '' }}
</label>
</b-form-radio> </b-form-radio>
</b-col> </b-col>
<b-col class="text-right"> <b-col class="text-right">
<b-form-radio <b-form-radio
id="currentMonth"
v-model="radioSelected" v-model="radioSelected"
:value="currentMonth" :value="currentMonth"
:disabled="creation[2] === 0" :disabled="creation[2] === 0"
size="lg" size="lg"
@change="updateRadioSelected(currentMonth, 2, creation[2])" @change="updateRadioSelected(currentMonth, 2, creation[2])"
> >
<label for="currentMonth">
{{ currentMonth.short }} {{ creation[2] != null ? creation[2] + ' GDD' : '' }} {{ currentMonth.short }} {{ creation[2] != null ? creation[2] + ' GDD' : '' }}
</label>
</b-form-radio> </b-form-radio>
</b-col> </b-col>
</b-row> </b-row>
<b-row class="m-4" v-show="createdIndex"> <b-row class="m-4" v-show="createdIndex != null">
<label>Betrag Auswählen</label> <label>Betrag Auswählen</label>
<div> <div>
<b-input-group prepend="GDD" append=".00"> <b-input-group prepend="GDD" append=".00">
@ -73,7 +67,6 @@
:min="rangeMin" :min="rangeMin"
:max="rangeMax" :max="rangeMax"
step="10" step="10"
@load="checkFormForUpdate('range')"
></b-form-input> ></b-form-input>
</b-input-group> </b-input-group>
</div> </div>
@ -86,7 +79,6 @@
v-model="text" v-model="text"
:state="text.length >= 10" :state="text.length >= 10"
placeholder="Mindestens 10 Zeichen eingeben" placeholder="Mindestens 10 Zeichen eingeben"
@load="checkFormForUpdate('text')"
rows="3" rows="3"
></b-form-textarea> ></b-form-textarea>
</div> </div>
@ -103,6 +95,7 @@
v-if="pagetype === 'PageCreationConfirm'" v-if="pagetype === 'PageCreationConfirm'"
type="button" type="button"
variant="success" variant="success"
class="test-submit"
@click="submitCreation" @click="submitCreation"
:disabled="radioSelected === '' || value <= 0 || text.length < 10" :disabled="radioSelected === '' || value <= 0 || text.length < 10"
> >
@ -113,6 +106,7 @@
v-else v-else
type="button" type="button"
variant="success" variant="success"
class="test-submit"
@click="submitCreation" @click="submitCreation"
:disabled="radioSelected === '' || value <= 0 || text.length < 10" :disabled="radioSelected === '' || value <= 0 || text.length < 10"
> >
@ -143,25 +137,34 @@ export default {
item: { item: {
type: Object, type: Object,
required: false, required: false,
default() {
return {}
},
},
items: {
type: Array,
required: false,
default() {
return []
},
}, },
creationUserData: { creationUserData: {
type: Object, type: Object,
required: false, required: false,
default() {
return {}
},
}, },
creation: { creation: {
type: Array, type: Array,
required: true, required: true,
}, },
itemsMassCreation: {
type: Object,
required: false,
},
}, },
data() { data() {
return { return {
radioSelected: '', radioSelected: '',
text: '', text: !this.creationUserData.memo ? '' : this.creationUserData.memo,
value: 0, value: !this.creationUserData.amount ? 0 : this.creationUserData.amount,
rangeMin: 0, rangeMin: 0,
rangeMax: 1000, rangeMax: 1000,
currentMonth: { currentMonth: {
@ -181,6 +184,7 @@ export default {
createdIndex: null, createdIndex: null,
} }
}, },
methods: { methods: {
// Auswählen eines Zeitraumes // Auswählen eines Zeitraumes
updateRadioSelected(name, index, openCreation) { updateRadioSelected(name, index, openCreation) {
@ -189,49 +193,19 @@ export default {
if (this.type === 'massCreation') { if (this.type === 'massCreation') {
// An Creation.vue emitten und radioSelectedMass aktualisieren // An Creation.vue emitten und radioSelectedMass aktualisieren
this.$emit('update-radio-selected', [name, index]) this.$emit('update-radio-selected', [name, index])
} } else if (this.type === 'singleCreation') {
// Wenn Einzelschöpfung
if (this.type === 'singleCreation') {
this.rangeMin = 0 this.rangeMin = 0
// Der maximale offene Betrag an GDD die für ein User noch geschöpft werden kann // Der maximale offene Betrag an GDD die für ein User noch geschöpft werden kann
this.rangeMax = openCreation this.rangeMax = openCreation
} }
}, },
checkFormForUpdate(input) {
switch (input) {
case 'text':
this.text = this.creationUserData.text
break
case 'range':
this.value = this.creationUserData.creationGdd
break
default:
// TODO: Toast
alert("I don't know such values")
}
},
submitCreation() { submitCreation() {
// Formular Prüfen ob ein Zeitraum ausgewählt wurde. Ansonsten abbrechen und Hinweis anzeigen
if (this.radioSelected === '') {
return alert('Bitte wähle einen Zeitraum!')
}
// Formular Prüfen ob der GDD Betrag grösser 0 ist. Ansonsten abbrechen und Hinweis anzeigen
if (this.value === 0) {
return alert('Bitte gib einen GDD Betrag an!')
}
// Formular Prüfen ob der Text vorhanden ist. Ansonsten abbrechen und Hinweis anzeigen
if (this.text === '') {
return alert('Bitte gib einen Text ein!')
}
// Formular Prüfen ob der Text länger als 10 Zeichen hat. Ansonsten abbrechen und Hinweis anzeigen
if (this.text.length < 10) {
return alert('Bitte gib einen Text ein der länger als 10 Zeichen ist!')
}
if (this.type === 'massCreation') { if (this.type === 'massCreation') {
// Die anzahl der Mitglieder aus der Mehrfachschöpfung // Die anzahl der Mitglieder aus der Mehrfachschöpfung
const i = Object.keys(this.itemsMassCreation).length const i = Object.keys(this.itemsMassCreation).length
// hinweis das eine Mehrfachschöpfung ausgeführt wird an (Anzahl der MItgleider an die geschöpft wird) // hinweis das eine Mehrfachschöpfung ausgeführt wird an (Anzahl der MItgleider an die geschöpft wird)
alert('SUBMIT CREATION => ' + this.type + ' >> für VIELE ' + i + ' Mitglieder') // eslint-disable-next-line no-console
console.log('SUBMIT CREATION => ' + this.type + ' >> für VIELE ' + i + ' Mitglieder')
this.submitObj = [ this.submitObj = [
{ {
item: this.itemsMassCreation, item: this.itemsMassCreation,
@ -242,16 +216,15 @@ export default {
moderator: this.$store.state.moderator.id, moderator: this.$store.state.moderator.id,
}, },
] ]
alert('MehrfachSCHÖPFUNG ABSENDEN FÜR >> ' + i + ' Mitglieder') // eslint-disable-next-line no-console
console.log('MehrfachSCHÖPFUNG ABSENDEN FÜR >> ' + i + ' Mitglieder')
// $store - offene Schöpfungen hochzählen // $store - offene Schöpfungen hochzählen
this.$store.commit('openCreationsPlus', i) this.$store.commit('openCreationsPlus', i)
// lösche alle Mitglieder aus der MehrfachSchöpfungsListe nach dem alle Mehrfachschpfungen zum bestätigen gesendet wurden. // lösche alle Mitglieder aus der MehrfachSchöpfungsListe nach dem alle Mehrfachschpfungen zum bestätigen gesendet wurden.
this.$emit('remove-all-bookmark') this.$emit('remove-all-bookmark')
} } else if (this.type === 'singleCreation') {
if (this.type === 'singleCreation') {
this.submitObj = { this.submitObj = {
email: this.item.email, email: this.item.email,
creationDate: this.radioSelected.long, creationDate: this.radioSelected.long,
@ -260,16 +233,6 @@ export default {
moderator: Number(this.$store.state.moderator.id), moderator: Number(this.$store.state.moderator.id),
} }
if (this.pagetype === 'PageCreationConfirm') {
// hinweis das eine ein einzelne Schöpfung abgesendet wird an (email)
alert('UPDATE EINZEL SCHÖPFUNG ABSENDEN FÜR >> ')
// umschreiben, update eine bestehende Schöpfung eine
this.$emit('update-creation-data', {
datum: this.radioSelected.long,
creationGdd: this.value,
text: this.text,
})
} else {
this.$apollo this.$apollo
.mutate({ .mutate({
mutation: createPendingCreation, mutation: createPendingCreation,
@ -278,7 +241,7 @@ export default {
.then((result) => { .then((result) => {
this.$emit('update-user-data', this.item, result.data.createPendingCreation) this.$emit('update-user-data', this.item, result.data.createPendingCreation)
this.$toasted.success( this.$toasted.success(
`Offene schöpfung (${this.value} GDD) für ${this.item.email} wurde gespeichert, liegen zur bestätigung bereit`, `Offene Schöpfung (${this.value} GDD) für ${this.item.email} wurde gespeichert und liegen zur Bestätigung bereit`,
) )
this.$store.commit('openCreationsPlus', 1) this.$store.commit('openCreationsPlus', 1)
this.submitObj = null this.submitObj = null
@ -297,7 +260,6 @@ export default {
this.value = 0 this.value = 0
}) })
} }
}
}, },
searchModeratorData() { searchModeratorData() {
this.$apollo this.$apollo

View File

@ -0,0 +1,191 @@
import { mount } from '@vue/test-utils'
import EditCreationFormular from './EditCreationFormular.vue'
const localVue = global.localVue
const apolloMutateMock = jest.fn().mockResolvedValue({
data: {
updatePendingCreation: {
creation: [0, 0, 0],
date: new Date(),
memo: 'qwertzuiopasdfghjkl',
moderator: 0,
},
},
})
const stateCommitMock = jest.fn()
const toastedErrorMock = jest.fn()
const mocks = {
$moment: jest.fn(() => {
return {
format: jest.fn((m) => m),
subtract: jest.fn(() => {
return {
format: jest.fn((m) => m),
}
}),
}
}),
$apollo: {
mutate: apolloMutateMock,
},
$store: {
state: {
moderator: {
id: 0,
name: 'test moderator',
},
},
commit: stateCommitMock,
},
$toasted: {
error: toastedErrorMock,
},
}
const propsData = {
type: '',
creation: [],
itemsMassCreation: {},
}
describe('EditCreationFormular', () => {
let wrapper
const Wrapper = () => {
return mount(EditCreationFormular, { localVue, mocks, propsData })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('has a DIV element with the class.component-edit-creation-formular', () => {
expect(wrapper.find('.component-edit-creation-formular').exists()).toBeTruthy()
})
describe('radio buttons to selcet month', () => {
it('has three radio buttons', () => {
expect(wrapper.findAll('input[type="radio"]').length).toBe(3)
})
describe('with single creation', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
await wrapper.setData({ rangeMin: 180 })
await wrapper.setData({ text: 'Test create coins' })
await wrapper.setData({ value: 90 })
})
describe('first radio button', () => {
beforeEach(async () => {
await wrapper.findAll('input[type="radio"]').at(0).setChecked()
})
it('sets rangeMin to 0', () => {
expect(wrapper.vm.rangeMin).toBe(0)
})
it('sets rangeMax to 200', () => {
expect(wrapper.vm.rangeMax).toBe(200)
})
describe('sendForm', () => {
beforeEach(async () => {
await wrapper.find('.test-submit').trigger('click')
})
it('sends ... to apollo', () => {
expect(apolloMutateMock).toBeCalledWith(
expect.objectContaining({
variables: {
amount: 90,
creationDate: 'YYYY-MM-01',
email: undefined,
id: undefined,
memo: 'Test create coins',
moderator: 0,
},
}),
)
})
})
})
describe('second radio button', () => {
beforeEach(async () => {
await wrapper.findAll('input[type="radio"]').at(1).setChecked()
})
it('sets rangeMin to 0', () => {
expect(wrapper.vm.rangeMin).toBe(0)
})
it('sets rangeMax to 400', () => {
expect(wrapper.vm.rangeMax).toBe(400)
})
describe('sendForm', () => {
beforeEach(async () => {
await wrapper.find('.test-submit').trigger('click')
})
it('sends ... to apollo', () => {
expect(apolloMutateMock).toBeCalledWith(
expect.objectContaining({
variables: {
amount: 90,
creationDate: 'YYYY-MM-01',
email: undefined,
id: undefined,
memo: 'Test create coins',
moderator: 0,
},
}),
)
})
})
})
describe('third radio button', () => {
beforeEach(async () => {
await wrapper.findAll('input[type="radio"]').at(2).setChecked()
})
it('sets rangeMin to 0', () => {
expect(wrapper.vm.rangeMin).toBe(0)
})
it('sets rangeMax to 400', () => {
expect(wrapper.vm.rangeMax).toBe(600)
})
describe('sendForm', () => {
beforeEach(async () => {
await wrapper.find('.test-submit').trigger('click')
})
it('sends ... to apollo', () => {
expect(apolloMutateMock).toBeCalledWith(
expect.objectContaining({
variables: {
amount: 90,
creationDate: 'YYYY-MM-DD',
email: undefined,
id: undefined,
memo: 'Test create coins',
moderator: 0,
},
}),
)
})
})
})
})
})
})
})

View File

@ -0,0 +1,273 @@
<template>
<div class="component-edit-creation-formular">
<div class="shadow p-3 mb-5 bg-white rounded">
<b-form ref="updateCreationForm">
<b-row class="m-4">
<label>Monat Auswählen</label>
<b-col class="text-left">
<b-form-radio
id="beforeLastMonth"
v-model="radioSelected"
:value="beforeLastMonth"
:disabled="selectedOpenCreationAmount[0] === 0"
size="lg"
@change="updateRadioSelected(beforeLastMonth, 0, selectedOpenCreationAmount[0])"
>
<label for="beforeLastMonth">
{{ beforeLastMonth.short }}
{{
selectedOpenCreationAmount[0] != null
? selectedOpenCreationAmount[0] + ' GDD'
: ''
}}
</label>
</b-form-radio>
</b-col>
<b-col>
<b-form-radio
id="lastMonth"
v-model="radioSelected"
:value="lastMonth"
:disabled="selectedOpenCreationAmount[1] === 0"
size="lg"
@change="updateRadioSelected(lastMonth, 1, selectedOpenCreationAmount[1])"
>
<label for="lastMonth">
{{ lastMonth.short }}
{{
selectedOpenCreationAmount[1] != null
? selectedOpenCreationAmount[1] + ' GDD'
: ''
}}
</label>
</b-form-radio>
</b-col>
<b-col class="text-right">
<b-form-radio
id="currentMonth"
v-model="radioSelected"
:value="currentMonth"
:disabled="selectedOpenCreationAmount[2] === 0"
size="lg"
@change="updateRadioSelected(currentMonth, 2, selectedOpenCreationAmount[2])"
>
<label for="currentMonth">
{{ currentMonth.short }}
{{
selectedOpenCreationAmount[2] != null
? selectedOpenCreationAmount[2] + ' GDD'
: ''
}}
</label>
</b-form-radio>
</b-col>
</b-row>
<b-row class="m-4">
<label>Betrag Auswählen</label>
<div>
<b-input-group prepend="GDD" append=".00">
<b-form-input
type="number"
v-model="value"
:min="rangeMin"
:max="rangeMax"
></b-form-input>
</b-input-group>
<b-input-group prepend="0" :append="String(rangeMax)" class="mt-3">
<b-form-input
type="range"
v-model="value"
:min="rangeMin"
:max="rangeMax"
step="10"
></b-form-input>
</b-input-group>
</div>
</b-row>
<b-row class="m-4">
<label>Text eintragen</label>
<div>
<b-form-textarea
id="textarea-state"
v-model="text"
:state="text.length >= 10"
placeholder="Mindestens 10 Zeichen eingeben"
rows="3"
></b-form-textarea>
</div>
</b-row>
<b-row class="m-4">
<b-col class="text-center">
<b-button type="reset" variant="danger" @click="$refs.updateCreationForm.reset()">
zurücksetzen
</b-button>
</b-col>
<b-col class="text-center">
<div class="text-right">
<b-button
type="button"
variant="success"
class="test-submit"
@click="submitCreation"
:disabled="radioSelected === '' || value <= 0 || text.length < 10"
>
Update Schöpfung ({{ type }},{{ pagetype }})
</b-button>
</div>
</b-col>
</b-row>
</b-form>
</div>
</div>
</template>
<script>
import { updatePendingCreation } from '../graphql/updatePendingCreation'
export default {
name: 'EditCreationFormular',
props: {
type: {
type: String,
required: false,
},
pagetype: {
type: String,
required: false,
default: '',
},
item: {
type: Object,
required: false,
default() {
return {}
},
},
items: {
type: Array,
required: false,
default() {
return []
},
},
row: {
type: Object,
required: false,
default() {
return {}
},
},
creationUserData: {
type: Object,
required: false,
default() {
return {}
},
},
creation: {
type: Array,
required: true,
},
},
data() {
return {
radioSelected: '',
text: !this.creationUserData.memo ? '' : this.creationUserData.memo,
value: !this.creationUserData.amount ? 0 : this.creationUserData.amount,
rangeMin: 0,
rangeMax: 1000,
currentMonth: {
short: this.$moment().format('MMMM'),
long: this.$moment().format('YYYY-MM-DD'),
},
lastMonth: {
short: this.$moment().subtract(1, 'month').format('MMMM'),
long: this.$moment().subtract(1, 'month').format('YYYY-MM') + '-01',
},
beforeLastMonth: {
short: this.$moment().subtract(2, 'month').format('MMMM'),
long: this.$moment().subtract(2, 'month').format('YYYY-MM') + '-01',
},
submitObj: null,
isdisabled: true,
createdIndex: null,
selectedOpenCreationAmount: {},
}
},
methods: {
updateRadioSelected(name, index, openCreation) {
this.createdIndex = index
this.rangeMin = 0
this.rangeMax = this.creation[index]
},
submitCreation() {
this.submitObj = {
id: this.item.id,
email: this.item.email,
creationDate: this.radioSelected.long,
amount: Number(this.value),
memo: this.text,
moderator: Number(this.$store.state.moderator.id),
}
// hinweis das eine ein einzelne Schöpfung abgesendet wird an (email)
this.$apollo
.mutate({
mutation: updatePendingCreation,
variables: this.submitObj,
})
.then((result) => {
this.$emit('update-user-data', this.item, result.data.updatePendingCreation.creation)
this.$emit('update-creation-data', {
amount: Number(result.data.updatePendingCreation.amount),
date: result.data.updatePendingCreation.date,
memo: result.data.updatePendingCreation.memo,
moderator: Number(result.data.updatePendingCreation.moderator),
row: this.row,
})
this.$toasted.success(
`Offene schöpfung (${this.value} GDD) für ${this.item.email} wurde geändert, liegt zur Bestätigung bereit`,
)
this.submitObj = null
this.createdIndex = null
// das creation Formular reseten
this.$refs.updateCreationForm.reset()
// Den geschöpften Wert auf o setzen
this.value = 0
})
.catch((error) => {
this.$toasted.error(error.message)
this.submitObj = null
// das creation Formular reseten
this.$refs.updateCreationForm.reset()
// Den geschöpften Wert auf o setzen
this.value = 0
})
},
},
created() {
if (this.pagetype === 'PageCreationConfirm' && this.creationUserData.date) {
switch (this.$moment(this.creationUserData.date).format('MMMM')) {
case this.currentMonth.short:
this.createdIndex = 2
this.radioSelected = this.currentMonth
break
case this.lastMonth.short:
this.createdIndex = 1
this.radioSelected = this.lastMonth
break
case this.beforeLastMonth.short:
this.createdIndex = 0
this.radioSelected = this.beforeLastMonth
break
default:
throw new Error('Something went wrong')
}
this.selectedOpenCreationAmount[this.createdIndex] =
this.creation[this.createdIndex] + this.creationUserData.amount
this.rangeMax = this.selectedOpenCreationAmount[this.createdIndex]
}
},
}
</script>

View File

@ -47,5 +47,6 @@ export default {
<style> <style>
.navbar-brand-img { .navbar-brand-img {
height: 2rem; height: 2rem;
padding-left: 10px;
} }
</style> </style>

View File

@ -60,13 +60,25 @@
<b-row class="mb-2"> <b-row class="mb-2">
<b-col></b-col> <b-col></b-col>
</b-row> </b-row>
{{ type }}
<creation-formular <creation-formular
v-if="type === 'PageUserSearch'"
type="singleCreation" type="singleCreation"
:pagetype="type" :pagetype="type"
:creation="row.item.creation" :creation="row.item.creation"
:item="row.item" :item="row.item"
:creationUserData="creationData" :creationUserData="creationUserData"
@update-creation-data="updateCreationData"
@update-user-data="updateUserData"
/>
<edit-creation-formular
v-else
type="singleCreation"
:pagetype="type"
:creation="row.item.creation"
:item="row.item"
:row="row"
:creationUserData="creationUserData"
@update-creation-data="updateCreationData" @update-creation-data="updateCreationData"
@update-user-data="updateUserData" @update-user-data="updateUserData"
/> />
@ -119,6 +131,8 @@
<script> <script>
import CreationFormular from '../components/CreationFormular.vue' import CreationFormular from '../components/CreationFormular.vue'
import EditCreationFormular from '../components/EditCreationFormular.vue'
import { confirmPendingCreation } from '../graphql/confirmPendingCreation'
export default { export default {
name: 'UserTable', name: 'UserTable',
@ -147,10 +161,11 @@ export default {
}, },
components: { components: {
CreationFormular, CreationFormular,
EditCreationFormular,
}, },
data() { data() {
return { return {
creationData: {}, creationUserData: {},
overlay: false, overlay: false,
overlayBookmarkType: '', overlayBookmarkType: '',
overlayItem: [], overlayItem: [],
@ -214,24 +229,35 @@ export default {
} }
}, },
bookmarkConfirm(item) { bookmarkConfirm(item) {
alert('die schöpfung bestätigen und abschließen') this.$apollo
alert(JSON.stringify(item)) .mutate({
mutation: confirmPendingCreation,
variables: {
id: item.id,
},
})
.then(() => {
this.$emit('remove-confirm-result', item, 'remove') this.$emit('remove-confirm-result', item, 'remove')
})
.catch((error) => {
this.$toasted.error(error.message)
})
}, },
editCreationUserTable(row, rowItem) { editCreationUserTable(row, rowItem) {
alert('editCreationUserTable')
if (!row.detailsShowing) { if (!row.detailsShowing) {
alert('offen edit loslegen') this.creationUserData = rowItem
// this.item = rowItem } else {
this.creationData = rowItem this.creationUserData = {}
// alert(this.creationData)
} }
row.toggleDetails() row.toggleDetails()
}, },
updateCreationData(data) { updateCreationData(data) {
this.creationData = { this.creationUserData.amount = data.amount
...data, this.creationUserData.date = data.date
} this.creationUserData.memo = data.memo
this.creationUserData.moderator = data.moderator
data.row.toggleDetails()
}, },
updateUserData(rowItem, newCreation) { updateUserData(rowItem, newCreation) {
rowItem.creation = newCreation rowItem.creation = newCreation

View File

@ -0,0 +1,7 @@
import gql from 'graphql-tag'
export const confirmPendingCreation = gql`
mutation ($id: Float!) {
confirmPendingCreation(id: $id)
}
`

View File

@ -0,0 +1,7 @@
import gql from 'graphql-tag'
export const deletePendingCreation = gql`
mutation ($id: Float!) {
deletePendingCreation(id: $id)
}
`

View File

@ -0,0 +1,17 @@
import gql from 'graphql-tag'
export const getPendingCreations = gql`
query {
getPendingCreations {
id
firstName
lastName
email
amount
memo
date
moderator
creation
}
}
`

View File

@ -0,0 +1,27 @@
import gql from 'graphql-tag'
export const updatePendingCreation = gql`
mutation (
$id: Int!
$email: String!
$amount: Int!
$memo: String!
$creationDate: String!
$moderator: Int!
) {
updatePendingCreation(
id: $id
email: $email
amount: $amount
memo: $memo
creationDate: $creationDate
moderator: $moderator
) {
amount
date
memo
creation
moderator
}
}
`

View File

@ -5,6 +5,7 @@ export const verifyLogin = gql`
verifyLogin { verifyLogin {
firstName firstName
lastName lastName
isAdmin
id id
} }
} }

View File

@ -75,7 +75,7 @@ Vue.use(Toasted, {
}, },
}) })
addNavigationGuards(router, store) addNavigationGuards(router, store, apolloProvider.defaultClient)
new Vue({ new Vue({
moment, moment,

View File

@ -4,19 +4,20 @@ import CONFIG from './config'
import Vue from 'vue' import Vue from 'vue'
import VueApollo from 'vue-apollo' import VueApollo from 'vue-apollo'
import Vuex from 'vuex' import i18n from './i18n'
import VueI18n from 'vue-i18n'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue' import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import moment from 'vue-moment' import moment from 'vue-moment'
import store from './store/store'
import router from './router/router'
jest.mock('vue') jest.mock('vue')
jest.mock('vue-apollo') jest.mock('vue-apollo')
jest.mock('vuex') jest.mock('vuex')
jest.mock('vue-i18n') jest.mock('vue-i18n')
jest.mock('vue-moment') jest.mock('vue-moment')
jest.mock('./store/store')
const storeMock = jest.fn() jest.mock('./i18n')
Vuex.Store = storeMock jest.mock('./router/router')
jest.mock('apollo-boost', () => { jest.mock('apollo-boost', () => {
return { return {
@ -65,8 +66,12 @@ describe('main', () => {
expect(Vue).toBeCalled() expect(Vue).toBeCalled()
}) })
it('calls VueI18n', () => { it('calls i18n', () => {
expect(VueI18n).toBeCalled() expect(Vue).toBeCalledWith(
expect.objectContaining({
i18n,
}),
)
}) })
it('calls BootstrapVue', () => { it('calls BootstrapVue', () => {
@ -81,7 +86,92 @@ describe('main', () => {
expect(Vue.use).toBeCalledWith(moment) expect(Vue.use).toBeCalledWith(moment)
}) })
it.skip('creates a store', () => { it('creates a store', () => {
expect(storeMock).toBeCalled() expect(Vue).toBeCalledWith(
expect.objectContaining({
store,
}),
)
})
it('creates a router', () => {
expect(Vue).toBeCalledWith(
expect.objectContaining({
router,
}),
)
})
describe('ApolloLink', () => {
// mock store
const storeDispatchMock = jest.fn()
store.state = {
token: 'some-token',
}
store.dispatch = storeDispatchMock
// mock i18n.t
i18n.t = jest.fn((t) => t)
// mock apllo response
const responseMock = {
errors: [{ message: '403.13 - Client certificate revoked' }],
}
// mock router
const routerPushMock = jest.fn()
router.push = routerPushMock
router.currentRoute = {
path: '/overview',
}
// mock context
const setContextMock = jest.fn()
const getContextMock = jest.fn(() => {
return {
response: {
headers: {
get: jest.fn(),
},
},
}
})
// mock apollo link function params
const operationMock = {
setContext: setContextMock,
getContext: getContextMock,
}
const forwardMock = jest.fn(() => {
return [responseMock]
})
// get apollo link callback
const middleware = ApolloLink.mock.calls[0][0]
beforeEach(() => {
jest.clearAllMocks()
// run the callback with mocked params
middleware(operationMock, forwardMock)
})
it('sets authorization header', () => {
expect(setContextMock).toBeCalledWith({
headers: {
Authorization: 'Bearer some-token',
},
})
})
describe('apollo response is 403.13', () => {
it.skip('dispatches logout', () => {
expect(storeDispatchMock).toBeCalledWith('logout', null)
})
it.skip('redirects to logout', () => {
expect(routerPushMock).toBeCalledWith('/logout')
})
})
}) })
}) })

View File

@ -21,24 +21,26 @@
</b-col> </b-col>
<b-col cols="12" lg="7" class="shadow p-3 mb-5 rounded bg-info"> <b-col cols="12" lg="7" class="shadow p-3 mb-5 rounded bg-info">
<user-table <user-table
v-if="massCreation.length > 0" v-show="itemsMassCreation.length > 0"
class="shadow p-3 mb-5 bg-white rounded" class="shadow p-3 mb-5 bg-white rounded"
type="UserListMassCreation" type="UserListMassCreation"
:itemsUser="massCreation" :itemsUser="itemsMassCreation"
:fieldsTable="fields" :fieldsTable="fields"
:criteria="null" :criteria="null"
:creation="creation" :creation="creation"
@update-item="updateItem" @update-item="updateItem"
/> />
<div v-if="itemsMassCreation.length === 0">
Bitte wähle ein oder Mehrere Mitglieder aus für die du Schöpfen möchtest
</div>
<creation-formular <creation-formular
v-if="massCreation.length > 0" v-else
type="massCreation" type="massCreation"
:creation="creation" :creation="creation"
:itemsMassCreation="massCreation" :items="itemsMassCreation"
@update-radio-selected="updateRadioSelected"
@remove-all-bookmark="removeAllBookmark" @remove-all-bookmark="removeAllBookmark"
/> />
{{ itemsMassCreation }}
</b-col> </b-col>
</b-row> </b-row>
</div> </div>
@ -72,7 +74,7 @@ export default {
{ key: 'bookmark', label: 'löschen' }, { key: 'bookmark', label: 'löschen' },
], ],
itemsList: [], itemsList: [],
massCreation: [], itemsMassCreation: [],
radioSelectedMass: '', radioSelectedMass: '',
criteria: '', criteria: '',
creation: [null, null, null], creation: [null, null, null],
@ -111,12 +113,12 @@ export default {
findArr = this.itemsList.find((arr) => arr.id === e.id) findArr = this.itemsList.find((arr) => arr.id === e.id)
index = this.itemsList.indexOf(findArr) index = this.itemsList.indexOf(findArr)
this.itemsList.splice(index, 1) this.itemsList.splice(index, 1)
this.massCreation.push(e) this.itemsMassCreation.push(e)
break break
case 'remove': case 'remove':
findArr = this.massCreation.find((arr) => arr.id === e.id) findArr = this.itemsMassCreation.find((arr) => arr.id === e.id)
index = this.massCreation.indexOf(findArr) index = this.itemsMassCreation.indexOf(findArr)
this.massCreation.splice(index, 1) this.itemsMassCreation.splice(index, 1)
this.itemsList.push(e) this.itemsList.push(e)
break break
default: default:
@ -124,19 +126,19 @@ export default {
} }
}, },
updateRadioSelected(obj) { // updateRadioSelected(obj) {
this.radioSelectedMass = obj[0] // this.radioSelectedMass = obj[0]
}, // },
removeAllBookmark() { removeAllBookmark() {
alert('remove all bookmarks') alert('remove all bookmarks')
const index = 0 const index = 0
let i = 0 let i = 0
for (i; i < this.massCreation.length; i++) { for (i; i < this.itemsMassCreation.length; i++) {
this.itemsList.push(this.massCreation[i]) this.itemsList.push(this.itemsMassCreation[i])
} }
this.massCreation.splice(index, this.massCreation.length) this.itemsMassCreation.splice(index, this.itemsMassCreation.length)
}, },
}, },
} }

View File

@ -1,14 +1,58 @@
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import CreationConfirm from './CreationConfirm.vue' import CreationConfirm from './CreationConfirm.vue'
import { deletePendingCreation } from '../graphql/deletePendingCreation'
const localVue = global.localVue const localVue = global.localVue
const storeCommitMock = jest.fn() const storeCommitMock = jest.fn()
const toastedErrorMock = jest.fn()
const toastedSuccessMock = jest.fn()
const apolloQueryMock = jest.fn().mockResolvedValue({
data: {
getPendingCreations: [
{
id: 1,
firstName: 'Bibi',
lastName: 'Bloxberg',
email: 'bibi@bloxberg.de',
amount: 500,
memo: 'Danke für alles',
date: new Date(),
moderator: 0,
},
{
id: 2,
firstName: 'Räuber',
lastName: 'Hotzenplotz',
email: 'raeuber@hotzenplotz.de',
amount: 1000000,
memo: 'Gut Ergatert',
date: new Date(),
moderator: 0,
},
],
},
})
const apolloMutateMock = jest.fn().mockResolvedValue({})
const mocks = { const mocks = {
$store: { $store: {
commit: storeCommitMock, commit: storeCommitMock,
}, },
$apollo: {
query: apolloQueryMock,
mutate: apolloMutateMock,
},
$toasted: {
error: toastedErrorMock,
success: toastedSuccessMock,
},
$moment: jest.fn((value) => {
return {
format: jest.fn((format) => value),
}
}),
} }
describe('CreationConfirm', () => { describe('CreationConfirm', () => {
@ -32,22 +76,85 @@ describe('CreationConfirm', () => {
it('commits resetOpenCreations to store', () => { it('commits resetOpenCreations to store', () => {
expect(storeCommitMock).toBeCalledWith('resetOpenCreations') expect(storeCommitMock).toBeCalledWith('resetOpenCreations')
}) })
it('commits setOpenCreations to store', () => {
it('commits openCreationsPlus to store', () => { expect(storeCommitMock).toBeCalledWith('setOpenCreations', 2)
expect(storeCommitMock).toBeCalledWith('openCreationsPlus', 5)
}) })
}) })
describe('confirm creation', () => { describe('confirm creation delete with success', () => {
beforeEach(async () => { beforeEach(async () => {
apolloQueryMock.mockResolvedValue({
data: {
getPendingCreations: [
{
id: 1,
firstName: 'Bibi',
lastName: 'Bloxberg',
email: 'bibi@bloxberg.de',
amount: 500,
memo: 'Danke für alles',
date: new Date(),
moderator: 0,
},
{
id: 2,
firstName: 'Räuber',
lastName: 'Hotzenplotz',
email: 'raeuber@hotzenplotz.de',
amount: 1000000,
memo: 'Gut Ergatert',
date: new Date(),
moderator: 0,
},
],
},
})
await wrapper await wrapper
.findComponent({ name: 'UserTable' }) .findComponent({ name: 'UserTable' })
.vm.$emit('remove-confirm-result', 1, 'remove') .vm.$emit('remove-confirm-result', { id: 1 }, 'remove')
})
it('calls the deletePendingCreation mutation', () => {
expect(apolloMutateMock).toBeCalledWith({
mutation: deletePendingCreation,
variables: { id: 1 },
})
}) })
it('commits openCreationsMinus to store', () => { it('commits openCreationsMinus to store', () => {
expect(storeCommitMock).toBeCalledWith('openCreationsMinus', 1) expect(storeCommitMock).toBeCalledWith('openCreationsMinus', 1)
}) })
it('toasts a success message', () => {
expect(toastedSuccessMock).toBeCalledWith('Pending Creation has been deleted')
})
})
describe('confirm creation delete with error', () => {
beforeEach(async () => {
apolloMutateMock.mockRejectedValue({ message: 'Ouchhh!' })
await wrapper
.findComponent({ name: 'UserTable' })
.vm.$emit('remove-confirm-result', { id: 1 }, 'remove')
})
it('toasts an error message', () => {
expect(toastedErrorMock).toBeCalledWith('Ouchhh!')
})
})
describe('server response is error', () => {
beforeEach(() => {
jest.clearAllMocks()
apolloQueryMock.mockRejectedValue({
message: 'Ouch!',
})
wrapper = Wrapper()
})
it('toast an error message', () => {
expect(toastedErrorMock).toBeCalledWith('Ouch!')
})
}) })
}) })
}) })

View File

@ -1,9 +1,5 @@
<template> <template>
<div class="creation-confirm"> <div class="creation-confirm">
<small class="bg-danger text-light p-1">
Die anzahl der offene Schöpfungen stimmen nicht! Diese wird bei absenden im $store
hochgezählt. Die Liste die hier angezeigt wird ist SIMULIERT!
</small>
<user-table <user-table
class="mt-4" class="mt-4"
type="PageCreationConfirm" type="PageCreationConfirm"
@ -15,6 +11,8 @@
</template> </template>
<script> <script>
import UserTable from '../components/UserTable.vue' import UserTable from '../components/UserTable.vue'
import { getPendingCreations } from '../graphql/getPendingCreations'
import { deletePendingCreation } from '../graphql/deletePendingCreation'
export default { export default {
name: 'CreationConfirm', name: 'CreationConfirm',
@ -30,120 +28,67 @@ export default {
{ key: 'firstName', label: 'Vorname' }, { key: 'firstName', label: 'Vorname' },
{ key: 'lastName', label: 'Nachname' }, { key: 'lastName', label: 'Nachname' },
{ {
key: 'creation_gdd', key: 'amount',
label: 'Schöpfung', label: 'Schöpfung',
formatter: (value) => { formatter: (value) => {
return value + ' GDD' return value + ' GDD'
}, },
}, },
{ key: 'text', label: 'Text' }, { key: 'memo', label: 'Text' },
{ {
key: 'creation_date', key: 'date',
label: 'Datum', label: 'Datum',
formatter: (value) => { formatter: (value) => {
return value.long return this.$moment(value).format('ll')
}, },
}, },
{ key: 'creation_moderator', label: 'Moderator' }, { key: 'moderator', label: 'Moderator' },
{ key: 'edit_creation', label: 'ändern' }, { key: 'edit_creation', label: 'ändern' },
{ key: 'confirm', label: 'speichern' }, { key: 'confirm', label: 'speichern' },
], ],
confirmResult: [ confirmResult: [],
{
id: 1,
email: 'dickerson@web.de',
firstName: 'Dickerson',
lastName: 'Macdonald',
creation: '[450,200,700]',
creation_gdd: '1000',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam ',
creation_date: {
short: 'November',
long: '22/11/2021',
},
creation_moderator: 'Manuela Gast',
},
{
id: 2,
email: 'larsen@woob.de',
firstName: 'Larsen',
lastName: 'Shaw',
creation: '[300,200,1000]',
creation_gdd: '1000',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam ',
creation_date: {
short: 'November',
long: '03/11/2021',
},
creation_moderator: 'Manuela Gast',
},
{
id: 3,
email: 'geneva@tete.de',
firstName: 'Geneva',
lastName: 'Wilson',
creation: '[350,200,900]',
creation_gdd: '1000',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam',
creation_date: {
short: 'September',
long: '27/09/2021',
},
creation_moderator: 'Manuela Gast',
},
{
id: 4,
email: 'viewrter@asdfvb.com',
firstName: 'Soledare',
lastName: 'Takker',
creation: '[100,400,800]',
creation_gdd: '500',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo ',
creation_date: {
short: 'Oktober',
long: '12/10/2021',
},
creation_moderator: 'Evelyn Roller',
},
{
id: 5,
email: 'dickerson@web.de',
firstName: 'Dickerson',
lastName: 'Macdonald',
creation: '[100,400,800]',
creation_gdd: '200',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At',
creation_date: {
short: 'September',
long: '05/09/2021',
},
creation_moderator: 'Manuela Gast',
},
],
} }
}, },
methods: { methods: {
removeConfirmResult(e, event) { removeConfirmResult(e, event) {
if (event === 'remove') { if (event === 'remove') {
let index = 0 let index = 0
let findArr = {} const findArr = this.confirmResult.find((arr) => arr.id === e.id)
this.$apollo
findArr = this.confirmResult.find((arr) => arr.id === e.id) .mutate({
mutation: deletePendingCreation,
variables: {
id: findArr.id,
},
})
.then((result) => {
index = this.confirmResult.indexOf(findArr) index = this.confirmResult.indexOf(findArr)
this.confirmResult.splice(index, 1) this.confirmResult.splice(index, 1)
this.$store.commit('openCreationsMinus', 1) this.$store.commit('openCreationsMinus', 1)
this.$toasted.success('Pending Creation has been deleted')
})
.catch((error) => {
this.$toasted.error(error.message)
})
} }
}, },
}, getPendingCreations() {
created() { this.$apollo
.query({
query: getPendingCreations,
})
.then((result) => {
this.$store.commit('resetOpenCreations') this.$store.commit('resetOpenCreations')
this.$store.commit('openCreationsPlus', Object.keys(this.confirmResult).length) this.confirmResult = result.data.getPendingCreations.reverse()
this.$store.commit('setOpenCreations', result.data.getPendingCreations.length)
})
.catch((error) => {
this.$toasted.error(error.message)
})
},
},
async created() {
await this.getPendingCreations()
}, },
} }
</script> </script>

View File

@ -0,0 +1,74 @@
import { mount } from '@vue/test-utils'
import Overview from './Overview.vue'
const localVue = global.localVue
const apolloQueryMock = jest.fn().mockResolvedValue({
data: {
getPendingCreations: [
{
pending: true,
},
{
pending: true,
},
{
pending: true,
},
],
},
})
const storeCommitMock = jest.fn()
const mocks = {
$apollo: {
query: apolloQueryMock,
},
$store: {
commit: storeCommitMock,
state: {
openCreations: 2,
},
},
}
describe('Overview', () => {
let wrapper
const Wrapper = () => {
return mount(Overview, { localVue, mocks })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('calls getPendingCreations', () => {
expect(apolloQueryMock).toBeCalled()
})
it('commts three pending creations to store', () => {
expect(storeCommitMock).toBeCalledWith('setOpenCreations', 3)
})
describe('with open creations', () => {
it('renders a link to confirm creations', () => {
expect(wrapper.find('a[href="creation-confirm"]').text()).toContain('2')
expect(wrapper.find('a[href="creation-confirm"]').exists()).toBeTruthy()
})
})
describe('without open creations', () => {
beforeEach(() => {
mocks.$store.state.openCreations = 0
})
it('renders a link to confirm creations', () => {
expect(wrapper.find('a[href="creation-confirm"]').text()).toContain('0')
expect(wrapper.find('a[href="creation-confirm"]').exists()).toBeTruthy()
})
})
})
})

View File

@ -1,5 +1,5 @@
<template> <template>
<div> <div class="admin-overview">
<b-card <b-card
v-show="$store.state.openCreations > 0" v-show="$store.state.openCreations > 0"
border-variant="primary" border-variant="primary"
@ -29,26 +29,6 @@
</b-card-text> </b-card-text>
</b-card> </b-card>
<br /> <br />
<b-row>
<b-col>
<b-card border-variant="info" header="offene Registrierung" align="center">
<b-card-text>Unbestätigte E-mail Registrierung</b-card-text>
</b-card>
</b-col>
<b-col>
<b-card border-variant="info" header="geschöpfte Stunden" align="center">
<b-card-text>Wievile Stunden können noch von Mitgliedern geschöpft werden?</b-card-text>
</b-card>
</b-col>
<b-col>
<b-card border-variant="info" header="Gemeinschafts Konto" align="center">
<b-card-text>
Für jedes Mitglied kann für das Gemeinschaftskonto geschöpft werden. Pro Monat 1000 x
Mitglieder
</b-card-text>
</b-card>
</b-col>
</b-row>
<hr /> <hr />
<br /> <br />
<b-list-group> <b-list-group>
@ -57,26 +37,39 @@
</b-list-group-item> </b-list-group-item>
<b-list-group-item class="d-flex justify-content-between align-items-center"> <b-list-group-item class="d-flex justify-content-between align-items-center">
Mitglieder Mitglieder
<b-badge class="bg-success" pill>14</b-badge> <b-badge class="bg-success" pill>2400</b-badge>
</b-list-group-item> </b-list-group-item>
<b-list-group-item class="d-flex justify-content-between align-items-center"> <b-list-group-item class="d-flex justify-content-between align-items-center">
aktive Mitglieder aktive Mitglieder
<b-badge class="bg-primary" pill>12</b-badge> <b-badge class="bg-primary" pill>2201</b-badge>
</b-list-group-item> </b-list-group-item>
<b-list-group-item class="d-flex justify-content-between align-items-center"> <b-list-group-item class="d-flex justify-content-between align-items-center">
nicht bestätigte Mitglieder nicht bestätigte Mitglieder
<b-badge class="bg-warning text-dark" pill>2</b-badge> <b-badge class="bg-warning text-dark" pill>120</b-badge>
</b-list-group-item> </b-list-group-item>
</b-list-group> </b-list-group>
<b-button @click="$store.commit('resetOpenCreations')">
lösche alle offenen Test Schöpfungen
</b-button>
</div> </div>
</template> </template>
<script> <script>
import { getPendingCreations } from '../graphql/getPendingCreations'
export default { export default {
name: 'overview', name: 'overview',
methods: {
async getPendingCreations() {
this.$apollo
.query({
query: getPendingCreations,
})
.then((result) => {
this.$store.commit('setOpenCreations', result.data.getPendingCreations.length)
})
},
},
created() {
this.getPendingCreations()
},
} }
</script> </script>

View File

@ -1,12 +1,28 @@
import { verifyLogin } from '../graphql/verifyLogin'
import CONFIG from '../config' import CONFIG from '../config'
const addNavigationGuards = (router, store) => { const addNavigationGuards = (router, store, apollo) => {
// store token on `authenticate` // store token on `authenticate`
router.beforeEach((to, from, next) => { router.beforeEach(async (to, from, next) => {
if (to.path === '/authenticate' && to.query && to.query.token) { if (to.path === '/authenticate' && to.query && to.query.token) {
// TODO verify user to get user data
store.commit('token', to.query.token) store.commit('token', to.query.token)
await apollo
.query({
query: verifyLogin,
fetchPolicy: 'network-only',
})
.then((result) => {
const moderator = result.data.verifyLogin
if (moderator.isAdmin) {
store.commit('moderator', moderator)
next({ path: '/' }) next({ path: '/' })
} else {
next({ path: '/not-found' })
}
})
.catch(() => {
next({ path: '/not-found' })
})
} else { } else {
next() next()
} }
@ -16,7 +32,9 @@ const addNavigationGuards = (router, store) => {
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
if ( if (
!CONFIG.DEBUG_DISABLE_AUTH && // we did not disabled the auth module for debug purposes !CONFIG.DEBUG_DISABLE_AUTH && // we did not disabled the auth module for debug purposes
!store.state.token && // we do not have a token (!store.state.token || // we do not have a token
!store.state.moderator || // no moderator set in store
!store.state.moderator.isAdmin) && // user is no admin
to.path !== '/not-found' && // we are not on `not-found` to.path !== '/not-found' && // we are not on `not-found`
to.path !== '/logout' // we are not on `logout` to.path !== '/logout' // we are not on `logout`
) { ) {

View File

@ -2,6 +2,13 @@ import addNavigationGuards from './guards'
import router from './router' import router from './router'
const storeCommitMock = jest.fn() const storeCommitMock = jest.fn()
const apolloQueryMock = jest.fn().mockResolvedValue({
data: {
verifyLogin: {
isAdmin: true,
},
},
})
const store = { const store = {
commit: storeCommitMock, commit: storeCommitMock,
@ -10,7 +17,11 @@ const store = {
}, },
} }
addNavigationGuards(router, store) const apollo = {
query: apolloQueryMock,
}
addNavigationGuards(router, store, apollo)
describe('navigation guards', () => { describe('navigation guards', () => {
beforeEach(() => { beforeEach(() => {
@ -21,18 +32,70 @@ describe('navigation guards', () => {
const navGuard = router.beforeHooks[0] const navGuard = router.beforeHooks[0]
const next = jest.fn() const next = jest.fn()
describe('with valid token', () => { describe('with valid token and as admin', () => {
it('commits the token to the store', async () => { beforeEach(() => {
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next) navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
})
it('commits the token to the store', async () => {
expect(storeCommitMock).toBeCalledWith('token', 'valid-token') expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
}) })
it('commits the moderator to the store', () => {
expect(storeCommitMock).toBeCalledWith('moderator', { isAdmin: true })
})
it('redirects to /', async () => { it('redirects to /', async () => {
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
expect(next).toBeCalledWith({ path: '/' }) expect(next).toBeCalledWith({ path: '/' })
}) })
}) })
describe('with valid token and not as admin', () => {
beforeEach(() => {
apolloQueryMock.mockResolvedValue({
data: {
verifyLogin: {
isAdmin: false,
},
},
})
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
})
it('commits the token to the store', async () => {
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
})
it('does not commit the moderator to the store', () => {
expect(storeCommitMock).not.toBeCalledWith('moderator', { isAdmin: false })
})
it('redirects to /not-found', async () => {
expect(next).toBeCalledWith({ path: '/not-found' })
})
})
describe('with valid token and server error on verification', () => {
beforeEach(() => {
apolloQueryMock.mockRejectedValue({
message: 'Ouch!',
})
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
})
it('commits the token to the store', async () => {
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
})
it('does not commit the moderator to the store', () => {
expect(storeCommitMock).not.toBeCalledWith('moderator', { isAdmin: false })
})
it('redirects to /not-found', async () => {
expect(next).toBeCalledWith({ path: '/not-found' })
})
})
describe('without valid token', () => { describe('without valid token', () => {
it('does not commit the token to the store', async () => { it('does not commit the token to the store', async () => {
navGuard({ path: '/authenticate' }, {}, next) navGuard({ path: '/authenticate' }, {}, next)
@ -55,9 +118,16 @@ describe('navigation guards', () => {
expect(next).toBeCalledWith({ path: '/not-found' }) expect(next).toBeCalledWith({ path: '/not-found' })
}) })
it('does not redirect when token in store', () => { it('redirects to not found with token in store and not moderator', () => {
store.state.token = 'valid token' store.state.token = 'valid token'
navGuard({ path: '/' }, {}, next) navGuard({ path: '/' }, {}, next)
expect(next).toBeCalledWith({ path: '/not-found' })
})
it('does not redirect with token in store and as moderator', () => {
store.state.token = 'valid token'
store.state.moderator = { isAdmin: true }
navGuard({ path: '/' }, {}, next)
expect(next).toBeCalledWith() expect(next).toBeCalledWith()
}) })
}) })

View File

@ -18,6 +18,9 @@ export const mutations = {
token: (state, token) => { token: (state, token) => {
state.token = token state.token = token
}, },
setOpenCreations: (state, openCreations) => {
state.openCreations = openCreations
},
moderator: (state, moderator) => { moderator: (state, moderator) => {
state.moderator = moderator state.moderator = moderator
}, },
@ -26,6 +29,7 @@ export const mutations = {
export const actions = { export const actions = {
logout: ({ commit, state }) => { logout: ({ commit, state }) => {
commit('token', null) commit('token', null)
commit('moderator', null)
window.localStorage.clear() window.localStorage.clear()
}, },
} }
@ -38,7 +42,7 @@ const store = new Vuex.Store({
], ],
state: { state: {
token: CONFIG.DEBUG_DISABLE_AUTH ? 'validToken' : null, token: CONFIG.DEBUG_DISABLE_AUTH ? 'validToken' : null,
moderator: { name: 'Dertest Moderator', id: 0 }, moderator: null,
openCreations: 0, openCreations: 0,
}, },
// Syncronous mutation of the state // Syncronous mutation of the state

View File

@ -1,11 +1,19 @@
import store, { mutations, actions } from './store' import store, { mutations, actions } from './store'
import CONFIG from '../config'
const { token, openCreationsPlus, openCreationsMinus, resetOpenCreations } = mutations jest.mock('../config')
const {
token,
openCreationsPlus,
openCreationsMinus,
resetOpenCreations,
setOpenCreations,
moderator,
} = mutations
const { logout } = actions const { logout } = actions
const CONFIG = { CONFIG.DEBUG_DISABLE_AUTH = true
DEBUG_DISABLE_AUTH: true,
}
describe('Vuex store', () => { describe('Vuex store', () => {
describe('mutations', () => { describe('mutations', () => {
@ -40,6 +48,22 @@ describe('Vuex store', () => {
expect(state.openCreations).toEqual(0) expect(state.openCreations).toEqual(0)
}) })
}) })
describe('moderator', () => {
it('sets the moderator object in state', () => {
const state = { moderator: null }
moderator(state, { id: 1 })
expect(state.moderator).toEqual({ id: 1 })
})
})
describe('setOpenCreations', () => {
it('sets the open creations to given value', () => {
const state = { openCreations: 24 }
setOpenCreations(state, 12)
expect(state.openCreations).toEqual(12)
})
})
}) })
describe('actions', () => { describe('actions', () => {
@ -57,6 +81,11 @@ describe('Vuex store', () => {
expect(commit).toBeCalledWith('token', null) expect(commit).toBeCalledWith('token', null)
}) })
it('deletes the moderator in store', () => {
logout({ commit, state })
expect(commit).toBeCalledWith('moderator', null)
})
it.skip('clears the window local storage', () => { it.skip('clears the window local storage', () => {
expect(windowStorageMock).toBeCalled() expect(windowStorageMock).toBeCalled()
}) })

View File

@ -0,0 +1,22 @@
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType()
export default class CreatePendingCreationArgs {
@Field(() => Int)
id: number
@Field(() => String)
email: string
@Field(() => Int)
amount: number
@Field(() => String)
memo: string
@Field(() => String)
creationDate: string
@Field(() => Int)
moderator: number
}

View File

@ -0,0 +1,34 @@
import { ObjectType, Field, Int } from 'type-graphql'
@ObjectType()
export class PendingCreation {
@Field(() => String)
firstName: string
@Field(() => Int)
id?: number
@Field(() => String)
lastName: string
@Field(() => Number)
userId: number
@Field(() => String)
email: string
@Field(() => Date)
date: Date
@Field(() => String)
memo: string
@Field(() => Number)
amount: number
@Field(() => Number)
moderator: number
@Field(() => [Number])
creation: number[]
}

View File

@ -0,0 +1,19 @@
import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class UpdatePendingCreation {
@Field(() => Date)
date: Date
@Field(() => String)
memo: string
@Field(() => Number)
amount: number
@Field(() => Number)
moderator: number
@Field(() => [Number])
creation: number[]
}

View File

@ -1,34 +1,43 @@
import { Resolver, Query, Arg, Args, Authorized, Mutation } from 'type-graphql' import { Resolver, Query, Arg, Args, Authorized, Mutation } from 'type-graphql'
import { getCustomRepository, Raw } from 'typeorm' import { getCustomRepository, Raw } from 'typeorm'
import { UserAdmin } from '../model/UserAdmin' import { UserAdmin } from '../model/UserAdmin'
import { LoginUserRepository } from '../../typeorm/repository/LoginUser' import { PendingCreation } from '../model/PendingCreation'
import { UpdatePendingCreation } from '../model/UpdatePendingCreation'
import { RIGHTS } from '../../auth/RIGHTS' import { RIGHTS } from '../../auth/RIGHTS'
import { TransactionRepository } from '../../typeorm/repository/Transaction'
import { TransactionCreationRepository } from '../../typeorm/repository/TransactionCreation' import { TransactionCreationRepository } from '../../typeorm/repository/TransactionCreation'
import { PendingCreationRepository } from '../../typeorm/repository/PendingCreation' import { PendingCreationRepository } from '../../typeorm/repository/PendingCreation'
import { UserRepository } from '../../typeorm/repository/User' import { UserRepository } from '../../typeorm/repository/User'
import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs' import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs'
import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs'
import moment from 'moment' import moment from 'moment'
import { Transaction } from '@entity/Transaction'
import { TransactionCreation } from '@entity/TransactionCreation'
import { UserTransaction } from '@entity/UserTransaction'
import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction'
import { BalanceRepository } from '../../typeorm/repository/Balance'
@Resolver() @Resolver()
export class AdminResolver { export class AdminResolver {
@Authorized([RIGHTS.SEARCH_USERS]) @Authorized([RIGHTS.SEARCH_USERS])
@Query(() => [UserAdmin]) @Query(() => [UserAdmin])
async searchUsers(@Arg('searchText') searchText: string): Promise<UserAdmin[]> { async searchUsers(@Arg('searchText') searchText: string): Promise<UserAdmin[]> {
const loginUserRepository = getCustomRepository(LoginUserRepository) const userRepository = getCustomRepository(UserRepository)
const loginUsers = await loginUserRepository.findBySearchCriteria(searchText) const users = await userRepository.findBySearchCriteria(searchText)
const users = await Promise.all( const adminUsers = await Promise.all(
loginUsers.map(async (loginUser) => { users.map(async (user) => {
const user = new UserAdmin() const adminUser = new UserAdmin()
user.firstName = loginUser.firstName adminUser.firstName = user.firstName
user.lastName = loginUser.lastName adminUser.lastName = user.lastName
user.email = loginUser.email adminUser.email = user.email
user.creation = await getUserCreations(loginUser.id) adminUser.creation = await getUserCreations(user.id)
return user return adminUser
}), }),
) )
return users return adminUsers
} }
@Authorized([RIGHTS.SEARCH_USERS])
@Mutation(() => [Number]) @Mutation(() => [Number])
async createPendingCreation( async createPendingCreation(
@Args() { email, amount, memo, creationDate, moderator }: CreatePendingCreationArgs, @Args() { email, amount, memo, creationDate, moderator }: CreatePendingCreationArgs,
@ -52,6 +61,147 @@ export class AdminResolver {
} }
return await getUserCreations(user.id) return await getUserCreations(user.id)
} }
// @Authorized([RIGHTS.SEARCH_USERS])
@Mutation(() => UpdatePendingCreation)
async updatePendingCreation(
@Args() { id, email, amount, memo, creationDate, moderator }: UpdatePendingCreationArgs,
): Promise<UpdatePendingCreation> {
const userRepository = getCustomRepository(UserRepository)
const user = await userRepository.findByEmail(email)
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
const updatedCreation = await pendingCreationRepository.findOneOrFail({ id })
if (updatedCreation.userId !== user.id)
throw new Error('user of the pending creation and send user does not correspond')
updatedCreation.amount = BigInt(amount * 10000)
updatedCreation.memo = memo
updatedCreation.date = new Date(creationDate)
updatedCreation.moderator = moderator
await pendingCreationRepository.save(updatedCreation)
const result = new UpdatePendingCreation()
result.amount = parseInt(updatedCreation.amount.toString())
result.memo = updatedCreation.memo
result.date = updatedCreation.date
result.moderator = updatedCreation.moderator
result.creation = await getUserCreations(user.id)
return result
// const creations = await getUserCreations(user.id)
// const creationDateObj = new Date(creationDate)
// if (isCreationValid(creations, amount, creationDateObj)) {
// const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
// const loginPendingTaskAdmin = pendingCreationRepository.create()
// loginPendingTaskAdmin.userId = user.id
// loginPendingTaskAdmin.amount = BigInt(amount * 10000)
// loginPendingTaskAdmin.created = new Date()
// loginPendingTaskAdmin.date = creationDateObj
// loginPendingTaskAdmin.memo = memo
// loginPendingTaskAdmin.moderator = moderator
//
// pendingCreationRepository.save(loginPendingTaskAdmin)
// }
// return await getUserCreations(user.id)
}
@Query(() => [PendingCreation])
async getPendingCreations(): Promise<PendingCreation[]> {
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
const pendingCreations = await pendingCreationRepository.find()
const pendingCreationsPromise = await Promise.all(
pendingCreations.map(async (pendingCreation) => {
const userRepository = getCustomRepository(UserRepository)
const user = await userRepository.findOneOrFail({ id: pendingCreation.userId })
const parsedAmount = Number(parseInt(pendingCreation.amount.toString()) / 10000)
// pendingCreation.amount = parsedAmount
const newPendingCreation = {
...pendingCreation,
amount: parsedAmount,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
creation: await getUserCreations(user.id),
}
return newPendingCreation
}),
)
return pendingCreationsPromise
}
@Mutation(() => Boolean)
async deletePendingCreation(@Arg('id') id: number): Promise<boolean> {
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
const entity = await pendingCreationRepository.findOneOrFail(id)
const res = await pendingCreationRepository.delete(entity)
return !!res
}
@Mutation(() => Boolean)
async confirmPendingCreation(@Arg('id') id: number): Promise<boolean> {
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
const pendingCreation = await pendingCreationRepository.findOneOrFail(id)
const transactionRepository = getCustomRepository(TransactionRepository)
let transaction = new Transaction()
transaction.transactionTypeId = 1
transaction.memo = pendingCreation.memo
transaction.received = new Date()
transaction.blockchainTypeId = 1
transaction = await transactionRepository.save(transaction)
if (!transaction) throw new Error('Could not create transaction')
const transactionCreationRepository = getCustomRepository(TransactionCreationRepository)
let transactionCreation = new TransactionCreation()
transactionCreation.transactionId = transaction.id
transactionCreation.userId = pendingCreation.userId
transactionCreation.amount = parseInt(pendingCreation.amount.toString())
transactionCreation.targetDate = pendingCreation.date
transactionCreation = await transactionCreationRepository.save(transactionCreation)
if (!transactionCreation) throw new Error('Could not create transactionCreation')
const userTransactionRepository = getCustomRepository(UserTransactionRepository)
const lastUserTransaction = await userTransactionRepository.findLastForUser(
pendingCreation.userId,
)
let newBalance = 0
if (!lastUserTransaction) {
newBalance = 0
} else {
newBalance = lastUserTransaction.balance
}
newBalance = Number(newBalance) + Number(parseInt(pendingCreation.amount.toString()) / 10000)
const newUserTransaction = new UserTransaction()
newUserTransaction.userId = pendingCreation.userId
newUserTransaction.transactionId = transaction.id
newUserTransaction.transactionTypeId = transaction.transactionTypeId
newUserTransaction.balance = Number(newBalance)
newUserTransaction.balanceDate = transaction.received
await userTransactionRepository.save(newUserTransaction).catch((error) => {
throw new Error('Error saving user transaction: ' + error)
})
const balanceRepository = getCustomRepository(BalanceRepository)
let userBalance = await balanceRepository.findByUser(pendingCreation.userId)
if (!userBalance) userBalance = balanceRepository.create()
userBalance.userId = pendingCreation.userId
userBalance.amount = Number(newBalance * 10000)
userBalance.modified = new Date()
userBalance.recordDate = userBalance.recordDate ? userBalance.recordDate : new Date()
await balanceRepository.save(userBalance)
await pendingCreationRepository.delete(pendingCreation)
return true
}
} }
async function getUserCreations(id: number): Promise<number[]> { async function getUserCreations(id: number): Promise<number[]> {

View File

@ -428,7 +428,7 @@ async function addUserTransaction(
if (lastUserTransaction) { if (lastUserTransaction) {
newBalance += Number( newBalance += Number(
await calculateDecay( await calculateDecay(
Number(lastUserTransaction.balance), Number(lastUserTransaction.balance * 10000),
lastUserTransaction.balanceDate, lastUserTransaction.balanceDate,
transaction.received, transaction.received,
).catch(() => { ).catch(() => {

View File

@ -30,4 +30,17 @@ export class UserRepository extends Repository<User> {
}) })
return usersIndiced return usersIndiced
} }
async findBySearchCriteria(searchCriteria: string): Promise<User[]> {
return await this.createQueryBuilder('user')
.where(
'user.firstName like :name or user.lastName like :lastName or user.email like :email',
{
name: `%${searchCriteria}%`,
lastName: `%${searchCriteria}%`,
email: `%${searchCriteria}%`,
},
)
.getMany()
}
} }

View File

@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* /*
Elopage Webhook Elopage Webhook

View File

@ -14,7 +14,7 @@
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) { export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(` await queryFn(`
CREATE TABLE \`user_setting\` ( CREATE TABLE IF NOT EXISTS \`user_setting\` (
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT, \`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
\`userId\` int(11) NOT NULL, \`userId\` int(11) NOT NULL,
\`key\` varchar(255) NOT NULL, \`key\` varchar(255) NOT NULL,
@ -25,5 +25,5 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) { export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
// write downgrade logic as parameter of queryFn // write downgrade logic as parameter of queryFn
await queryFn(`DROP TABLE \`user_setting\`;`) await queryFn(`DROP TABLE IF EXISTS \`user_setting\`;`)
} }

View File

@ -14,7 +14,7 @@
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) { export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(` await queryFn(`
CREATE TABLE \`login_app_access_tokens\` ( CREATE TABLE IF NOT EXISTS \`login_app_access_tokens\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT, \`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`user_id\` int NOT NULL, \`user_id\` int NOT NULL,
\`access_code\` bigint unsigned NOT NULL, \`access_code\` bigint unsigned NOT NULL,
@ -25,7 +25,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`) `)
await queryFn(` await queryFn(`
CREATE TABLE \`login_elopage_buys\` ( CREATE TABLE IF NOT EXISTS \`login_elopage_buys\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT, \`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`elopage_user_id\` int DEFAULT NULL, \`elopage_user_id\` int DEFAULT NULL,
\`affiliate_program_id\` int NOT NULL, \`affiliate_program_id\` int NOT NULL,
@ -42,7 +42,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`) `)
await queryFn(` await queryFn(`
CREATE TABLE \`login_email_opt_in_types\` ( CREATE TABLE IF NOT EXISTS \`login_email_opt_in_types\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT, \`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`name\` varchar(255) NOT NULL, \`name\` varchar(255) NOT NULL,
\`description\` varchar(255) NOT NULL, \`description\` varchar(255) NOT NULL,
@ -50,7 +50,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`) `)
await queryFn(` await queryFn(`
CREATE TABLE \`login_email_opt_in\` ( CREATE TABLE IF NOT EXISTS \`login_email_opt_in\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT, \`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`user_id\` int NOT NULL, \`user_id\` int NOT NULL,
\`verification_code\` bigint unsigned NOT NULL, \`verification_code\` bigint unsigned NOT NULL,
@ -63,7 +63,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`) `)
await queryFn(` await queryFn(`
CREATE TABLE \`login_groups\` ( CREATE TABLE IF NOT EXISTS \`login_groups\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT, \`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`alias\` varchar(190) NOT NULL, \`alias\` varchar(190) NOT NULL,
\`name\` varchar(255) NOT NULL, \`name\` varchar(255) NOT NULL,
@ -76,7 +76,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`) `)
await queryFn(` await queryFn(`
CREATE TABLE \`login_pending_tasks\` ( CREATE TABLE IF NOT EXISTS \`login_pending_tasks\` (
\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT, \`id\` int UNSIGNED NOT NULL AUTO_INCREMENT,
\`user_id\` int UNSIGNED DEFAULT 0, \`user_id\` int UNSIGNED DEFAULT 0,
\`request\` varbinary(2048) NOT NULL, \`request\` varbinary(2048) NOT NULL,
@ -91,7 +91,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4;
`) `)
await queryFn(` await queryFn(`
CREATE TABLE \`login_roles\` ( CREATE TABLE IF NOT EXISTS \`login_roles\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT, \`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`name\` varchar(255) NOT NULL, \`name\` varchar(255) NOT NULL,
\`description\` varchar(255) NOT NULL, \`description\` varchar(255) NOT NULL,
@ -100,7 +100,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`) `)
await queryFn(` await queryFn(`
CREATE TABLE \`login_user_backups\` ( CREATE TABLE IF NOT EXISTS \`login_user_backups\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT, \`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`user_id\` int NOT NULL, \`user_id\` int NOT NULL,
\`passphrase\` text NOT NULL, \`passphrase\` text NOT NULL,
@ -109,7 +109,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`) `)
await queryFn(` await queryFn(`
CREATE TABLE \`login_user_roles\` ( CREATE TABLE IF NOT EXISTS \`login_user_roles\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT, \`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`user_id\` int NOT NULL, \`user_id\` int NOT NULL,
\`role_id\` int NOT NULL, \`role_id\` int NOT NULL,
@ -117,7 +117,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`) `)
await queryFn(` await queryFn(`
CREATE TABLE \`login_users\` ( CREATE TABLE IF NOT EXISTS \`login_users\` (
\`id\` int unsigned NOT NULL AUTO_INCREMENT, \`id\` int unsigned NOT NULL AUTO_INCREMENT,
\`email\` varchar(191) NOT NULL, \`email\` varchar(191) NOT NULL,
\`first_name\` varchar(150) NOT NULL, \`first_name\` varchar(150) NOT NULL,
@ -143,14 +143,14 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) { export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
// write downgrade logic as parameter of queryFn // write downgrade logic as parameter of queryFn
await queryFn(`DROP TABLE \`login_app_access_tokens\`;`) await queryFn(`DROP TABLE IF EXISTS \`login_app_access_tokens\`;`)
await queryFn(`DROP TABLE \`login_elopage_buys\`;`) await queryFn(`DROP TABLE IF EXISTS \`login_elopage_buys\`;`)
await queryFn(`DROP TABLE \`login_email_opt_in_types\`;`) await queryFn(`DROP TABLE IF EXISTS \`login_email_opt_in_types\`;`)
await queryFn(`DROP TABLE \`login_email_opt_in\`;`) await queryFn(`DROP TABLE IF EXISTS \`login_email_opt_in\`;`)
await queryFn(`DROP TABLE \`login_groups\`;`) await queryFn(`DROP TABLE IF EXISTS \`login_groups\`;`)
await queryFn(`DROP TABLE \`login_pending_tasks\`;`) await queryFn(`DROP TABLE IF EXISTS \`login_pending_tasks\`;`)
await queryFn(`DROP TABLE \`login_roles\`;`) await queryFn(`DROP TABLE IF EXISTS \`login_roles\`;`)
await queryFn(`DROP TABLE \`login_user_backups\`;`) await queryFn(`DROP TABLE IF EXISTS \`login_user_backups\`;`)
await queryFn(`DROP TABLE \`login_user_roles\`;`) await queryFn(`DROP TABLE IF EXISTS \`login_user_roles\`;`)
await queryFn(`DROP TABLE \`login_users\`;`) await queryFn(`DROP TABLE IF EXISTS \`login_users\`;`)
} }

View File

@ -63,15 +63,5 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
} }
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) { export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
// write downgrade logic as parameter of queryFn // TODO NO EMPTY FUNCTION
await queryFn(`DELETE FROM \`login_app_access_tokens\`;`)
await queryFn(`DELETE FROM \`login_elopage_buys\`;`)
await queryFn(`DELETE FROM \`login_email_opt_in_types\`;`)
await queryFn(`DELETE FROM \`login_email_opt_in\`;`)
await queryFn(`DELETE FROM \`login_groups\`;`)
await queryFn(`DELETE FROM \`login_pending_tasks\`;`)
await queryFn(`DELETE FROM \`login_roles\`;`)
await queryFn(`DELETE FROM \`login_user_backups\`;`)
await queryFn(`DELETE FROM \`login_user_roles\`;`)
await queryFn(`DELETE FROM \`login_users\`;`)
} }

View File

@ -11,7 +11,7 @@
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) { export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(` await queryFn(`
CREATE TABLE \`login_pending_tasks_admin\` ( CREATE TABLE IF NOT EXISTS \`login_pending_tasks_admin\` (
\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT, \`id\` int UNSIGNED NOT NULL AUTO_INCREMENT,
\`userId\` int UNSIGNED DEFAULT 0, \`userId\` int UNSIGNED DEFAULT 0,
\`created\` datetime NOT NULL, \`created\` datetime NOT NULL,
@ -25,5 +25,5 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
} }
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) { export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`DROP TABLE \`login_pending_tasks_admin\`;`) await queryFn(`DROP TABLE IF EXISTS \`login_pending_tasks_admin\`;`)
} }

View File

@ -114,6 +114,11 @@
"message": "hallo gradido !!", "message": "hallo gradido !!",
"overview": "Übersicht", "overview": "Übersicht",
"privacy_policy": "Datenschutzerklärung", "privacy_policy": "Datenschutzerklärung",
"publisher": {
"infoNoRegister": "Dies ist für die Registrieung nicht nötig!",
"infoText": "Trage hier die ID des Herausgebers ein. Wenn du keine ID hast dann bitte leer lassen.",
"publisherId": "PublisherID"
},
"send": "Senden", "send": "Senden",
"settings": { "settings": {
"coinanimation": { "coinanimation": {

View File

@ -114,6 +114,11 @@
"message": "hello gradido !!", "message": "hello gradido !!",
"overview": "Overview", "overview": "Overview",
"privacy_policy": "Privacy policy", "privacy_policy": "Privacy policy",
"publisher": {
"infoNoRegister": "This is not necessary for registration!",
"infoText": "Enter the ID of the publisher here. If you do not have an ID, please leave it blank.",
"publisherId": "PublisherID"
},
"send": "Send", "send": "Send",
"settings": { "settings": {
"coinanimation": { "coinanimation": {

View File

@ -15,12 +15,19 @@ const addNavigationGuards = (router, store, apollo) => {
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
if (to.path === '/authenticate' && to.query.token) { if (to.path === '/authenticate' && to.query.token) {
store.commit('token', to.query.token) store.commit('token', to.query.token)
const result = await apollo.query({ await apollo
.query({
query: verifyLogin, query: verifyLogin,
fetchPolicy: 'network-only', fetchPolicy: 'network-only',
}) })
.then((result) => {
store.dispatch('login', result.data.verifyLogin) store.dispatch('login', result.data.verifyLogin)
next({ path: '/overview' }) next({ path: '/overview' })
})
.catch(() => {
store.dispatch('logout')
next()
})
} else { } else {
next() next()
} }

View File

@ -2,15 +2,28 @@ import addNavigationGuards from './guards'
import router from './router' import router from './router'
const storeCommitMock = jest.fn() const storeCommitMock = jest.fn()
const storeDispatchMock = jest.fn()
const apolloQueryMock = jest.fn().mockResolvedValue({
data: {
verifyLogin: {
firstName: 'Peter',
},
},
})
const store = { const store = {
commit: storeCommitMock, commit: storeCommitMock,
state: { state: {
token: null, token: null,
}, },
dispatch: storeDispatchMock,
} }
addNavigationGuards(router, store) const apollo = {
query: apolloQueryMock,
}
addNavigationGuards(router, store, apollo)
describe('navigation guards', () => { describe('navigation guards', () => {
beforeEach(() => { beforeEach(() => {
@ -29,6 +42,46 @@ describe('navigation guards', () => {
}) })
}) })
describe('authenticate', () => {
const navGuard = router.beforeHooks[1]
const next = jest.fn()
describe('with valid token', () => {
beforeEach(() => {
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
})
it('commts the token to the store', () => {
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
})
it('calls verifyLogin', () => {
expect(apolloQueryMock).toBeCalled()
})
it('commits login to the store', () => {
expect(storeDispatchMock).toBeCalledWith('login', { firstName: 'Peter' })
})
})
describe('with valid token and server error', () => {
beforeEach(() => {
apolloQueryMock.mockRejectedValue({
message: 'Ouch!',
})
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
})
it('dispatches logout to store', () => {
expect(storeDispatchMock).toBeCalledWith('logout')
})
it('calls next', () => {
expect(next).toBeCalledWith()
})
})
})
describe('authorization', () => { describe('authorization', () => {
const navGuard = router.beforeHooks[2] const navGuard = router.beforeHooks[2]
const next = jest.fn() const next = jest.fn()

View File

@ -11,6 +11,7 @@ const {
coinanimation, coinanimation,
newsletterState, newsletterState,
publisherId, publisherId,
isAdmin,
community, community,
hasElopage, hasElopage,
} = mutations } = mutations
@ -104,6 +105,14 @@ describe('Vuex store', () => {
}) })
}) })
describe('isAdmin', () => {
it('sets the state of isAdmin', () => {
const state = { isAdmin: null }
isAdmin(state, true)
expect(state.isAdmin).toEqual(true)
})
})
describe('community', () => { describe('community', () => {
it('sets the state of community', () => { it('sets the state of community', () => {
const state = {} const state = {}

View File

@ -170,6 +170,11 @@ describe('Register', () => {
expect(wrapper.find('#registerCheckbox').exists()).toBeTruthy() expect(wrapper.find('#registerCheckbox').exists()).toBeTruthy()
}) })
it('has PublisherId input fields', () => {
wrapper.find('.publisherCollaps').trigger('click')
expect(wrapper.find('#publisherid').exists()).toBe(true)
})
it('has disabled submit button when not completely filled', () => { it('has disabled submit button when not completely filled', () => {
expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBe('disabled') expect(wrapper.find('button[type="submit"]').attributes('disabled')).toBe('disabled')
}) })
@ -221,6 +226,11 @@ describe('Register', () => {
wrapper.find('input[name="form.password"]').setValue('Aa123456_') wrapper.find('input[name="form.password"]').setValue('Aa123456_')
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456_') wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456_')
wrapper.find('.language-switch-select').findAll('option').at(1).setSelected() wrapper.find('.language-switch-select').findAll('option').at(1).setSelected()
wrapper.find('#publisherid').setValue('12345')
})
it('commits publisherId to store', () => {
expect(mockStoreCommit).toBeCalledWith('publisherId', 12345)
}) })
it('has enabled submit button when completely filled', () => { it('has enabled submit button when completely filled', () => {

View File

@ -121,8 +121,44 @@
{{ messageError }} {{ messageError }}
</span> </span>
</b-alert> </b-alert>
<b-row v-b-toggle:my-collapse class="text-muted shadow-sm p-3 publisherCollaps">
<b-col>
{{ $t('publisher.publisherId') }} : {{ $store.state.publisherId }}
</b-col>
<b-col class="text-right">
<b-icon icon="chevron-down" aria-hidden="true"></b-icon>
</b-col>
</b-row>
<b-row>
<b-col>
<b-collapse id="my-collapse" class="">
<b-input-group class="shadow-sm p-2 bg-white rounded">
<b-input-group-prepend is-text>
<b-icon icon="person-fill"></b-icon>
</b-input-group-prepend>
<b-form-input
id="publisherid"
type="text"
placeholder="Publisher ID"
v-model="publisherId"
@input="commitStore(publisherId)"
></b-form-input>
</b-input-group>
<div
v-b-toggle:my-collapse
class="text-center mt-1 shadow-lg p-3 mb-5 rounded"
>
{{ $t('publisher.infoText') }}
<span class="text-dark">{{ $t('publisher.infoNoRegister') }}</span>
<div class="text-center"> <div class="text-center">
<b-icon icon="chevron-up" aria-hidden="true"></b-icon>
</div>
</div>
</b-collapse>
</b-col>
</b-row>
<div class="text-center mt-5">
<div class="text-center"> <div class="text-center">
<router-link class="test-button-back" to="/login"> <router-link class="test-button-back" to="/login">
<b-button variant="outline-secondary" class="mr-4"> <b-button variant="outline-secondary" class="mr-4">
@ -185,6 +221,7 @@ export default {
showError: false, showError: false,
messageError: '', messageError: '',
register: true, register: true,
publisherId: this.$store.state.publisherId,
} }
}, },
methods: { methods: {
@ -197,6 +234,9 @@ export default {
getValidationState({ dirty, validated, valid = null }) { getValidationState({ dirty, validated, valid = null }) {
return dirty || validated ? valid : null return dirty || validated ? valid : null
}, },
commitStore(val) {
this.$store.commit('publisherId', val)
},
async onSubmit() { async onSubmit() {
this.$apollo this.$apollo
.mutate({ .mutate({

View File

@ -6,6 +6,7 @@ PROJECT_DIR="${SCRIPT_DIR}/../"
FRONTEND_DIR="${PROJECT_DIR}/frontend/" FRONTEND_DIR="${PROJECT_DIR}/frontend/"
BACKEND_DIR="${PROJECT_DIR}/backend/" BACKEND_DIR="${PROJECT_DIR}/backend/"
DATABASE_DIR="${PROJECT_DIR}/database/" DATABASE_DIR="${PROJECT_DIR}/database/"
ADMIN_DIR="${PROJECT_DIR}/admin/"
# navigate to project directory # navigate to project directory
cd ${PROJECT_DIR} cd ${PROJECT_DIR}
@ -23,6 +24,8 @@ cd ${BACKEND_DIR}
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION} yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
cd ${DATABASE_DIR} cd ${DATABASE_DIR}
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION} yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
cd ${ADMIN_DIR}
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
# generate changelog # generate changelog
cd ${PROJECT_DIR} cd ${PROJECT_DIR}