mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into login_admin_interface
This commit is contained in:
commit
ac7eb303b4
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -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: 65
|
min_coverage: 47
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
@ -491,7 +491,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: 38
|
min_coverage: 37
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
verbose: true,
|
verbose: true,
|
||||||
collectCoverageFrom: ['src/**/*.{js,vue}', '!**/node_modules/**', '!**/?(*.)+(spec|test).js?(x)'],
|
collectCoverageFrom: [
|
||||||
|
'src/**/*.{js,vue}',
|
||||||
|
'!**/node_modules/**',
|
||||||
|
'!src/assets/**',
|
||||||
|
'!**/?(*.)+(spec|test).js?(x)',
|
||||||
|
],
|
||||||
moduleFileExtensions: [
|
moduleFileExtensions: [
|
||||||
'js',
|
'js',
|
||||||
// 'jsx',
|
// 'jsx',
|
||||||
|
|||||||
@ -35,6 +35,7 @@
|
|||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "26.6.3",
|
"jest": "26.6.3",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
|
"regenerator-runtime": "^0.13.9",
|
||||||
"stats-webpack-plugin": "^0.7.0",
|
"stats-webpack-plugin": "^0.7.0",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-apollo": "^3.0.8",
|
"vue-apollo": "^3.0.8",
|
||||||
|
|||||||
26
admin/src/App.spec.js
Normal file
26
admin/src/App.spec.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { shallowMount } from '@vue/test-utils'
|
||||||
|
import App from './App'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const stubs = {
|
||||||
|
RouterView: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('App', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
const Wrapper = () => {
|
||||||
|
return shallowMount(App, { localVue, stubs })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('shallowMount', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a div with id "app"', () => {
|
||||||
|
expect(wrapper.find('div#app').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -8,3 +8,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ContentFooter',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -19,7 +19,7 @@ const mocks = {
|
|||||||
const propsData = {
|
const propsData = {
|
||||||
type: '',
|
type: '',
|
||||||
item: {},
|
item: {},
|
||||||
creation: {},
|
creation: [],
|
||||||
itemsMassCreation: {},
|
itemsMassCreation: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,5 +38,104 @@ describe('CreationFormular', () => {
|
|||||||
it('has a DIV element with the class.component-creation-formular', () => {
|
it('has a DIV element with the class.component-creation-formular', () => {
|
||||||
expect(wrapper.find('.component-creation-formular').exists()).toBeTruthy()
|
expect(wrapper.find('.component-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 mass creation', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setProps({ type: 'massCreation' })
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('first radio button', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.findAll('input[type="radio"]').at(0).setChecked()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits update-radio-selected with index 0', () => {
|
||||||
|
expect(wrapper.emitted()['update-radio-selected']).toEqual([
|
||||||
|
[expect.arrayContaining([0])],
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('second radio button', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.findAll('input[type="radio"]').at(1).setChecked()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits update-radio-selected with index 1', () => {
|
||||||
|
expect(wrapper.emitted()['update-radio-selected']).toEqual([
|
||||||
|
[expect.arrayContaining([1])],
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('third radio button', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.findAll('input[type="radio"]').at(2).setChecked()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits update-radio-selected with index 2', () => {
|
||||||
|
expect(wrapper.emitted()['update-radio-selected']).toEqual([
|
||||||
|
[expect.arrayContaining([2])],
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with single creation', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
||||||
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
|
})
|
||||||
|
|
||||||
|
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('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('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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -4,9 +4,10 @@
|
|||||||
<h3>
|
<h3>
|
||||||
{{
|
{{
|
||||||
this.type === 'singleCreation'
|
this.type === 'singleCreation'
|
||||||
? 'Einzelschöpfung für ' + item.first_name + ' ' + item.last_name + ''
|
? 'Einzelschöpfung für ' + item.firstName + ' ' + item.lastName + ''
|
||||||
: 'Massenschöpfung für ' + Object.keys(this.itemsMassCreation).length + ' Mitglieder'
|
: 'Mehrfachschöpfung für ' + Object.keys(this.itemsMassCreation).length + ' Mitglieder'
|
||||||
}}
|
}}
|
||||||
|
{{ item }}
|
||||||
</h3>
|
</h3>
|
||||||
<div v-show="this.type === 'massCreation' && Object.keys(this.itemsMassCreation).length <= 0">
|
<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
|
Bitte wähle ein oder Mehrere Mitglieder aus für die du Schöpfen möchtest
|
||||||
@ -26,7 +27,7 @@
|
|||||||
size="lg"
|
size="lg"
|
||||||
@change="updateRadioSelected(beforeLastMonth, 0, creation[0])"
|
@change="updateRadioSelected(beforeLastMonth, 0, creation[0])"
|
||||||
>
|
>
|
||||||
{{ beforeLastMonth }} {{ creation[0] != null ? creation[0] + ' GDD' : '' }}
|
{{ beforeLastMonth.short }} {{ creation[0] != null ? creation[0] + ' GDD' : '' }}
|
||||||
</b-form-radio>
|
</b-form-radio>
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col>
|
<b-col>
|
||||||
@ -36,7 +37,7 @@
|
|||||||
size="lg"
|
size="lg"
|
||||||
@change="updateRadioSelected(lastMonth, 1, creation[1])"
|
@change="updateRadioSelected(lastMonth, 1, creation[1])"
|
||||||
>
|
>
|
||||||
{{ lastMonth }} {{ creation[1] != null ? creation[1] + ' GDD' : '' }}
|
{{ lastMonth.short }} {{ creation[1] != null ? creation[1] + ' GDD' : '' }}
|
||||||
</b-form-radio>
|
</b-form-radio>
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col class="text-right">
|
<b-col class="text-right">
|
||||||
@ -46,7 +47,7 @@
|
|||||||
size="lg"
|
size="lg"
|
||||||
@change="updateRadioSelected(currentMonth, 2, creation[2])"
|
@change="updateRadioSelected(currentMonth, 2, creation[2])"
|
||||||
>
|
>
|
||||||
{{ currentMonth }} {{ creation[2] != null ? creation[2] + ' GDD' : '' }}
|
{{ currentMonth.short }} {{ creation[2] != null ? creation[2] + ' GDD' : '' }}
|
||||||
</b-form-radio>
|
</b-form-radio>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
@ -73,6 +74,7 @@
|
|||||||
:min="rangeMin"
|
:min="rangeMin"
|
||||||
:max="rangeMax"
|
:max="rangeMax"
|
||||||
step="10"
|
step="10"
|
||||||
|
@load="checkFormForUpdate('range')"
|
||||||
></b-input>
|
></b-input>
|
||||||
</b-row>
|
</b-row>
|
||||||
<b-row class="m-4">
|
<b-row class="m-4">
|
||||||
@ -83,6 +85,7 @@
|
|||||||
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>
|
||||||
@ -96,6 +99,17 @@
|
|||||||
<b-col class="text-center">
|
<b-col class="text-center">
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<b-button
|
<b-button
|
||||||
|
v-if="pagetype === 'PageCreationConfirm'"
|
||||||
|
type="button"
|
||||||
|
variant="success"
|
||||||
|
@click="submitCreation"
|
||||||
|
:disabled="radioSelected === '' || value <= 0 || text.length < 10"
|
||||||
|
>
|
||||||
|
Update Schöpfung ({{ type }},{{ pagetype }})
|
||||||
|
</b-button>
|
||||||
|
|
||||||
|
<b-button
|
||||||
|
v-else
|
||||||
type="button"
|
type="button"
|
||||||
variant="success"
|
variant="success"
|
||||||
@click="submitCreation"
|
@click="submitCreation"
|
||||||
@ -116,14 +130,23 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: false,
|
||||||
|
},
|
||||||
|
pagetype: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
creation: {
|
creationUserData: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
creation: {
|
||||||
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
itemsMassCreation: {
|
itemsMassCreation: {
|
||||||
@ -138,9 +161,18 @@ export default {
|
|||||||
value: 0,
|
value: 0,
|
||||||
rangeMin: 0,
|
rangeMin: 0,
|
||||||
rangeMax: 1000,
|
rangeMax: 1000,
|
||||||
currentMonth: this.$moment().format('MMMM'),
|
currentMonth: {
|
||||||
lastMonth: this.$moment().subtract(1, 'month').format('MMMM'),
|
short: this.$moment().format('MMMM'),
|
||||||
beforeLastMonth: this.$moment().subtract(2, 'month').format('MMMM'),
|
long: this.$moment().format('DD/MM/YYYY'),
|
||||||
|
},
|
||||||
|
lastMonth: {
|
||||||
|
short: this.$moment().subtract(1, 'month').format('MMMM'),
|
||||||
|
long: this.$moment().subtract(1, 'month').format('DD/MM/YYYY'),
|
||||||
|
},
|
||||||
|
beforeLastMonth: {
|
||||||
|
short: this.$moment().subtract(2, 'month').format('MMMM'),
|
||||||
|
long: this.$moment().subtract(2, 'month').format('DD/MM/YYYY'),
|
||||||
|
},
|
||||||
submitObj: null,
|
submitObj: null,
|
||||||
isdisabled: true,
|
isdisabled: true,
|
||||||
}
|
}
|
||||||
@ -148,7 +180,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
// Auswählen eines Zeitraumes
|
// Auswählen eines Zeitraumes
|
||||||
updateRadioSelected(name, index, openCreation) {
|
updateRadioSelected(name, index, openCreation) {
|
||||||
// Wenn Massenschöpfung
|
// Wenn Mehrfachschöpfung
|
||||||
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])
|
||||||
@ -160,6 +192,19 @@ export default {
|
|||||||
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
|
// Formular Prüfen ob ein Zeitraum ausgewählt wurde. Ansonsten abbrechen und Hinweis anzeigen
|
||||||
if (this.radioSelected === '') {
|
if (this.radioSelected === '') {
|
||||||
@ -178,9 +223,9 @@ export default {
|
|||||||
return alert('Bitte gib einen Text ein der länger als 10 Zeichen ist!')
|
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 Massenschö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 Massenschö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')
|
alert('SUBMIT CREATION => ' + this.type + ' >> für VIELE ' + i + ' Mitglieder')
|
||||||
this.submitObj = [
|
this.submitObj = [
|
||||||
{
|
{
|
||||||
@ -191,32 +236,44 @@ export default {
|
|||||||
moderator: this.$store.state.moderator,
|
moderator: this.$store.state.moderator,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
alert('MassenSCHÖPFUNG ABSENDEN FÜR >> ' + i + ' Mitglieder')
|
alert('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 MassenSchöpfungsListe nach dem alle Massenschpfungen 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')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.type === 'singleCreation') {
|
if (this.type === 'singleCreation') {
|
||||||
// hinweis das eine einzelne schöpfung ausgeführt wird an (Vorname)
|
// hinweis das eine einzelne schöpfung ausgeführt wird an (Vorname)
|
||||||
alert('SUBMIT CREATION => ' + this.type + ' >> für ' + this.item.first_name + '')
|
alert('SUBMIT CREATION => ' + this.type + ' >> für ' + this.item.firstName + '')
|
||||||
// erstellen eines Arrays (submitObj) mit allen Daten
|
// erstellen eines Arrays (submitObj) mit allen Daten
|
||||||
this.submitObj = [
|
this.submitObj = [
|
||||||
{
|
{
|
||||||
item: this.item,
|
item: this.item,
|
||||||
datum: this.radioSelected,
|
datum: this.radioSelected.long,
|
||||||
amount: this.value,
|
amount: this.value,
|
||||||
text: this.text,
|
text: this.text,
|
||||||
moderator: this.$store.state.moderator,
|
moderator: this.$store.state.moderator,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
// hinweis das eine ein einzelne Schöpfung abgesendet wird an (email)
|
|
||||||
alert('EINZEL SCHÖPFUNG ABSENDEN FÜR >> ' + this.item.first_name + '')
|
if (this.pagetype === 'PageCreationConfirm') {
|
||||||
// $store - offene Schöpfungen hochzählen
|
// hinweis das eine ein einzelne Schöpfung abgesendet wird an (email)
|
||||||
this.$store.commit('openCreationsPlus', 1)
|
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 {
|
||||||
|
// hinweis das eine ein einzelne Schöpfung abgesendet wird an (email)
|
||||||
|
alert('EINZEL SCHÖPFUNG ABSENDEN FÜR >> ' + this.item.firstName + '')
|
||||||
|
// $store - offene Schöpfungen hochzählen
|
||||||
|
this.$store.commit('openCreationsPlus', 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// das absendeergebniss im string ansehen
|
// das absendeergebniss im string ansehen
|
||||||
|
|||||||
@ -15,7 +15,7 @@ describe('NavBar', () => {
|
|||||||
let wrapper
|
let wrapper
|
||||||
|
|
||||||
const Wrapper = () => {
|
const Wrapper = () => {
|
||||||
return mount(NavBar, { localVue, mocks })
|
return mount(NavBar, { mocks, localVue })
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('mount', () => {
|
describe('mount', () => {
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
<b-collapse id="nav-collapse" is-nav>
|
<b-collapse id="nav-collapse" is-nav>
|
||||||
<b-navbar-nav>
|
<b-navbar-nav>
|
||||||
<b-nav-item to="/user">Usersuche |</b-nav-item>
|
<b-nav-item to="/user">Usersuche |</b-nav-item>
|
||||||
<b-nav-item to="/creation">Massenschöpfung</b-nav-item>
|
<b-nav-item to="/creation">Mehrfachschöpfung</b-nav-item>
|
||||||
<b-nav-item
|
<b-nav-item
|
||||||
v-show="$store.state.openCreations > 0"
|
v-show="$store.state.openCreations > 0"
|
||||||
class="h5 bg-danger"
|
class="h5 bg-danger"
|
||||||
|
|||||||
@ -10,7 +10,7 @@ describe('UserTable', () => {
|
|||||||
type: 'Type',
|
type: 'Type',
|
||||||
itemsUser: [],
|
itemsUser: [],
|
||||||
fieldsTable: [],
|
fieldsTable: [],
|
||||||
creation: {},
|
creation: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
const Wrapper = () => {
|
const Wrapper = () => {
|
||||||
|
|||||||
@ -37,7 +37,12 @@
|
|||||||
stacked="md"
|
stacked="md"
|
||||||
>
|
>
|
||||||
<template #cell(edit_creation)="row">
|
<template #cell(edit_creation)="row">
|
||||||
<b-button variant="info" size="lg" @click="row.toggleDetails" class="mr-2">
|
<b-button
|
||||||
|
variant="info"
|
||||||
|
size="lg"
|
||||||
|
@click="editCreationUserTable(row, row.item)"
|
||||||
|
class="mr-2"
|
||||||
|
>
|
||||||
<b-icon v-if="row.detailsShowing" icon="x" aria-label="Help"></b-icon>
|
<b-icon v-if="row.detailsShowing" icon="x" aria-label="Help"></b-icon>
|
||||||
<b-icon v-else icon="pencil-square" aria-label="Help"></b-icon>
|
<b-icon v-else icon="pencil-square" aria-label="Help"></b-icon>
|
||||||
</b-button>
|
</b-button>
|
||||||
@ -46,7 +51,7 @@
|
|||||||
<template #cell(show_details)="row">
|
<template #cell(show_details)="row">
|
||||||
<b-button variant="info" size="lg" @click="row.toggleDetails" class="mr-2">
|
<b-button variant="info" size="lg" @click="row.toggleDetails" class="mr-2">
|
||||||
<b-icon v-if="row.detailsShowing" icon="eye-slash-fill" aria-label="Help"></b-icon>
|
<b-icon v-if="row.detailsShowing" icon="eye-slash-fill" aria-label="Help"></b-icon>
|
||||||
<b-icon v-else icon="eye-slash-fill" aria-label="Help"></b-icon>
|
<b-icon v-else icon="eye-fill" aria-label="Help"></b-icon>
|
||||||
</b-button>
|
</b-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -58,8 +63,11 @@
|
|||||||
|
|
||||||
<creation-formular
|
<creation-formular
|
||||||
type="singleCreation"
|
type="singleCreation"
|
||||||
:creation="getCreationInMonths(row.item.creation)"
|
:pagetype="type"
|
||||||
|
:creation="row.item.creation"
|
||||||
:item="row.item"
|
:item="row.item"
|
||||||
|
:creationUserData="creationData"
|
||||||
|
@update-creation-data="updateCreationData"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<b-button size="sm" @click="row.toggleDetails">
|
<b-button size="sm" @click="row.toggleDetails">
|
||||||
@ -67,7 +75,7 @@
|
|||||||
:icon="type === 'PageCreationConfirm' ? 'x' : 'eye-slash-fill'"
|
:icon="type === 'PageCreationConfirm' ? 'x' : 'eye-slash-fill'"
|
||||||
aria-label="Help"
|
aria-label="Help"
|
||||||
></b-icon>
|
></b-icon>
|
||||||
Details verbergen von {{ row.item.first_name }} {{ row.item.last_name }}
|
Details verbergen von {{ row.item.firstName }} {{ row.item.lastName }}
|
||||||
</b-button>
|
</b-button>
|
||||||
</b-card>
|
</b-card>
|
||||||
</template>
|
</template>
|
||||||
@ -132,7 +140,7 @@ export default {
|
|||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
creation: {
|
creation: {
|
||||||
type: Object,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -141,6 +149,7 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
creationData: {},
|
||||||
overlay: false,
|
overlay: false,
|
||||||
overlayBookmarkType: '',
|
overlayBookmarkType: '',
|
||||||
overlayItem: [],
|
overlayItem: [],
|
||||||
@ -200,16 +209,28 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.type === 'PageCreationConfirm') {
|
if (this.type === 'PageCreationConfirm') {
|
||||||
this.$emit('update-confirm-result', item, 'remove')
|
this.$emit('remove-confirm-result', item, 'remove')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bookmarkConfirm(item) {
|
bookmarkConfirm(item) {
|
||||||
alert('die schöpfung bestätigen und abschließen')
|
alert('die schöpfung bestätigen und abschließen')
|
||||||
alert(JSON.stringify(item))
|
alert(JSON.stringify(item))
|
||||||
this.$emit('update-confirm-result', item, 'remove')
|
this.$emit('remove-confirm-result', item, 'remove')
|
||||||
},
|
},
|
||||||
getCreationInMonths(creation) {
|
editCreationUserTable(row, rowItem) {
|
||||||
return creation.split(',')
|
alert('editCreationUserTable')
|
||||||
|
if (!row.detailsShowing) {
|
||||||
|
alert('offen edit loslegen')
|
||||||
|
// this.item = rowItem
|
||||||
|
this.creationData = rowItem
|
||||||
|
// alert(this.creationData)
|
||||||
|
}
|
||||||
|
row.toggleDetails()
|
||||||
|
},
|
||||||
|
updateCreationData(data) {
|
||||||
|
this.creationData = {
|
||||||
|
...data,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
12
admin/src/graphql/searchUsers.js
Normal file
12
admin/src/graphql/searchUsers.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export const searchUsers = gql`
|
||||||
|
query ($searchText: String!) {
|
||||||
|
searchUsers(searchText: $searchText) {
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
email
|
||||||
|
creation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
30
admin/src/i18n.test.js
Normal file
30
admin/src/i18n.test.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import i18n from './i18n'
|
||||||
|
import VueI18n from 'vue-i18n'
|
||||||
|
|
||||||
|
jest.mock('vue-i18n')
|
||||||
|
|
||||||
|
describe('i18n', () => {
|
||||||
|
it('calls i18n with locale en', () => {
|
||||||
|
expect(VueI18n).toBeCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
locale: 'en',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls i18n with fallback locale en', () => {
|
||||||
|
expect(VueI18n).toBeCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a _t function', () => {
|
||||||
|
expect(i18n).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
_t: expect.anything(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,6 +1,9 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
|
||||||
|
// without this async calls are not working
|
||||||
|
import 'regenerator-runtime'
|
||||||
|
|
||||||
import store from './store/store'
|
import store from './store/store'
|
||||||
|
|
||||||
import router from './router/router'
|
import router from './router/router'
|
||||||
@ -22,7 +25,8 @@ import moment from 'vue-moment'
|
|||||||
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
|
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
|
||||||
|
|
||||||
const authLink = new ApolloLink((operation, forward) => {
|
const authLink = new ApolloLink((operation, forward) => {
|
||||||
const token = '' // store.state.token
|
const token = store.state.token
|
||||||
|
|
||||||
operation.setContext({
|
operation.setContext({
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: token && token.length > 0 ? `Bearer ${token}` : '',
|
Authorization: token && token.length > 0 ? `Bearer ${token}` : '',
|
||||||
@ -52,10 +56,13 @@ const apolloProvider = new VueApollo({
|
|||||||
})
|
})
|
||||||
|
|
||||||
Vue.use(BootstrapVue)
|
Vue.use(BootstrapVue)
|
||||||
|
|
||||||
Vue.use(IconsPlugin)
|
Vue.use(IconsPlugin)
|
||||||
|
|
||||||
Vue.use(moment)
|
Vue.use(moment)
|
||||||
|
|
||||||
|
Vue.use(VueApollo)
|
||||||
|
|
||||||
addNavigationGuards(router, store)
|
addNavigationGuards(router, store)
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="creation">
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-col cols="12" lg="5">
|
<b-col cols="12" lg="5">
|
||||||
<label>Usersuche</label>
|
<label>Usersuche</label>
|
||||||
@ -10,6 +10,7 @@
|
|||||||
placeholder="User suche"
|
placeholder="User suche"
|
||||||
></b-input>
|
></b-input>
|
||||||
<user-table
|
<user-table
|
||||||
|
v-if="itemsList.length > 0"
|
||||||
type="UserListSearch"
|
type="UserListSearch"
|
||||||
:itemsUser="itemsList"
|
:itemsUser="itemsList"
|
||||||
:fieldsTable="Searchfields"
|
:fieldsTable="Searchfields"
|
||||||
@ -20,7 +21,7 @@
|
|||||||
</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-show="Object.keys(this.massCreation).length > 0"
|
v-if="massCreation.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="massCreation"
|
||||||
@ -31,6 +32,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<creation-formular
|
<creation-formular
|
||||||
|
v-if="massCreation.length > 0"
|
||||||
type="massCreation"
|
type="massCreation"
|
||||||
:creation="creation"
|
:creation="creation"
|
||||||
:itemsMassCreation="massCreation"
|
:itemsMassCreation="massCreation"
|
||||||
@ -44,9 +46,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import CreationFormular from '../components/CreationFormular.vue'
|
import CreationFormular from '../components/CreationFormular.vue'
|
||||||
import UserTable from '../components/UserTable.vue'
|
import UserTable from '../components/UserTable.vue'
|
||||||
|
import { searchUsers } from '../graphql/searchUsers'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'overview',
|
name: 'Creation',
|
||||||
components: {
|
components: {
|
||||||
CreationFormular,
|
CreationFormular,
|
||||||
UserTable,
|
UserTable,
|
||||||
@ -56,60 +59,49 @@ export default {
|
|||||||
showArrays: false,
|
showArrays: false,
|
||||||
Searchfields: [
|
Searchfields: [
|
||||||
{ key: 'bookmark', label: 'merken' },
|
{ key: 'bookmark', label: 'merken' },
|
||||||
|
{ key: 'firstName', label: 'Firstname' },
|
||||||
{ key: 'first_name', label: 'Firstname' },
|
{ key: 'lastName', label: 'Lastname' },
|
||||||
{ key: 'last_name', label: 'Lastname' },
|
|
||||||
{ key: 'creation', label: 'Creation' },
|
{ key: 'creation', label: 'Creation' },
|
||||||
{ key: 'email', label: 'Email' },
|
{ key: 'email', label: 'Email' },
|
||||||
],
|
],
|
||||||
fields: [
|
fields: [
|
||||||
{ key: 'email', label: 'Email' },
|
{ key: 'email', label: 'Email' },
|
||||||
{ key: 'first_name', label: 'Firstname' },
|
{ key: 'firstName', label: 'Firstname' },
|
||||||
{ key: 'last_name', label: 'Lastname' },
|
{ key: 'lastName', label: 'Lastname' },
|
||||||
{ key: 'creation', label: 'Creation' },
|
{ key: 'creation', label: 'Creation' },
|
||||||
{ key: 'bookmark', label: 'löschen' },
|
{ key: 'bookmark', label: 'löschen' },
|
||||||
],
|
],
|
||||||
searchResult: [
|
itemsList: [],
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
email: 'dickerson@web.de',
|
|
||||||
first_name: 'Dickerson',
|
|
||||||
last_name: 'Macdonald',
|
|
||||||
creation: '450,200,700',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
email: 'larsen@woob.de',
|
|
||||||
first_name: 'Larsen',
|
|
||||||
last_name: 'Shaw',
|
|
||||||
creation: '300,200,1000',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
email: 'geneva@tete.de',
|
|
||||||
first_name: 'Geneva',
|
|
||||||
last_name: 'Wilson',
|
|
||||||
creation: '350,200,900',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
email: 'viewrter@asdfvb.com',
|
|
||||||
first_name: 'Soledare',
|
|
||||||
last_name: 'Takker',
|
|
||||||
creation: '100,400,800',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
itemsList: this.searchResult,
|
|
||||||
massCreation: [],
|
massCreation: [],
|
||||||
radioSelectedMass: '',
|
radioSelectedMass: '',
|
||||||
criteria: '',
|
criteria: '',
|
||||||
creation: [null, null, null],
|
creation: [null, null, null],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
async created() {
|
||||||
this.itemsList = this.searchResult
|
await this.getUsers()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async getUsers() {
|
||||||
|
this.$apollo
|
||||||
|
.query({
|
||||||
|
query: searchUsers,
|
||||||
|
variables: {
|
||||||
|
searchText: this.criteria,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
this.itemsList = result.data.searchUsers.map((user) => {
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
showDetails: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toasted.error(error.message)
|
||||||
|
})
|
||||||
|
},
|
||||||
updateItem(e, event) {
|
updateItem(e, event) {
|
||||||
let index = 0
|
let index = 0
|
||||||
let findArr = {}
|
let findArr = {}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="creation-confirm">
|
||||||
<small class="bg-danger text-light p-1">
|
<small class="bg-danger text-light p-1">
|
||||||
Die anzahl der offene Schöpfungen stimmen nicht! Diese wird bei absenden im $store
|
Die anzahl der offene Schöpfungen stimmen nicht! Diese wird bei absenden im $store
|
||||||
hochgezählt. Die Liste die hier angezeigt wird ist SIMULIERT!
|
hochgezählt. Die Liste die hier angezeigt wird ist SIMULIERT!
|
||||||
@ -8,9 +8,8 @@
|
|||||||
class="mt-4"
|
class="mt-4"
|
||||||
type="PageCreationConfirm"
|
type="PageCreationConfirm"
|
||||||
:itemsUser="confirmResult"
|
:itemsUser="confirmResult"
|
||||||
:creation="creation"
|
|
||||||
:fieldsTable="fields"
|
:fieldsTable="fields"
|
||||||
@update-confirm-result="updateConfirmResult"
|
@remove-confirm-result="removeConfirmResult"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -18,7 +17,7 @@
|
|||||||
import UserTable from '../components/UserTable.vue'
|
import UserTable from '../components/UserTable.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'creation_confirm',
|
name: 'CreationConfirm',
|
||||||
components: {
|
components: {
|
||||||
UserTable,
|
UserTable,
|
||||||
},
|
},
|
||||||
@ -28,11 +27,23 @@ export default {
|
|||||||
fields: [
|
fields: [
|
||||||
{ key: 'bookmark', label: 'löschen' },
|
{ key: 'bookmark', label: 'löschen' },
|
||||||
{ key: 'email', label: 'Email' },
|
{ key: 'email', label: 'Email' },
|
||||||
{ key: 'first_name', label: 'Vorname' },
|
{ key: 'firstName', label: 'Vorname' },
|
||||||
{ key: 'last_name', label: 'Nachname' },
|
{ key: 'lastName', label: 'Nachname' },
|
||||||
{ key: 'creation_gdd', label: 'GDD' },
|
{
|
||||||
|
key: 'creation_gdd',
|
||||||
|
label: 'Schöpfung',
|
||||||
|
formatter: (value) => {
|
||||||
|
return value + ' GDD'
|
||||||
|
},
|
||||||
|
},
|
||||||
{ key: 'text', label: 'Text' },
|
{ key: 'text', label: 'Text' },
|
||||||
{ key: 'creation_date', label: 'Datum' },
|
{
|
||||||
|
key: 'creation_date',
|
||||||
|
label: 'Datum',
|
||||||
|
formatter: (value) => {
|
||||||
|
return value.long
|
||||||
|
},
|
||||||
|
},
|
||||||
{ key: 'creation_moderator', label: 'Moderator' },
|
{ key: 'creation_moderator', label: 'Moderator' },
|
||||||
{ key: 'edit_creation', label: 'ändern' },
|
{ key: 'edit_creation', label: 'ändern' },
|
||||||
{ key: 'confirm', label: 'speichern' },
|
{ key: 'confirm', label: 'speichern' },
|
||||||
@ -41,67 +52,81 @@ export default {
|
|||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
email: 'dickerson@web.de',
|
email: 'dickerson@web.de',
|
||||||
first_name: 'Dickerson',
|
firstName: 'Dickerson',
|
||||||
last_name: 'Macdonald',
|
lastName: 'Macdonald',
|
||||||
creation: '450,200,700',
|
creation: '[450,200,700]',
|
||||||
creation_gdd: '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 ',
|
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam ',
|
||||||
|
|
||||||
creation_date: '01/11/2021',
|
creation_date: {
|
||||||
|
short: 'November',
|
||||||
|
long: '22/11/2021',
|
||||||
|
},
|
||||||
creation_moderator: 'Manuela Gast',
|
creation_moderator: 'Manuela Gast',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
email: 'larsen@woob.de',
|
email: 'larsen@woob.de',
|
||||||
first_name: 'Larsen',
|
firstName: 'Larsen',
|
||||||
last_name: 'Shaw',
|
lastName: 'Shaw',
|
||||||
creation: '300,200,1000',
|
creation: '[300,200,1000]',
|
||||||
creation_gdd: '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 ',
|
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam ',
|
||||||
|
|
||||||
creation_date: '01/11/2021',
|
creation_date: {
|
||||||
|
short: 'November',
|
||||||
|
long: '03/11/2021',
|
||||||
|
},
|
||||||
creation_moderator: 'Manuela Gast',
|
creation_moderator: 'Manuela Gast',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
email: 'geneva@tete.de',
|
email: 'geneva@tete.de',
|
||||||
first_name: 'Geneva',
|
firstName: 'Geneva',
|
||||||
last_name: 'Wilson',
|
lastName: 'Wilson',
|
||||||
creation: '350,200,900',
|
creation: '[350,200,900]',
|
||||||
creation_gdd: '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',
|
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam',
|
||||||
creation_date: '01/11/2021',
|
creation_date: {
|
||||||
|
short: 'September',
|
||||||
|
long: '27/09/2021',
|
||||||
|
},
|
||||||
creation_moderator: 'Manuela Gast',
|
creation_moderator: 'Manuela Gast',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
email: 'viewrter@asdfvb.com',
|
email: 'viewrter@asdfvb.com',
|
||||||
first_name: 'Soledare',
|
firstName: 'Soledare',
|
||||||
last_name: 'Takker',
|
lastName: 'Takker',
|
||||||
|
creation: '[100,400,800]',
|
||||||
creation_gdd: '500',
|
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 ',
|
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: {
|
||||||
creation_date: '01/10/2021',
|
short: 'Oktober',
|
||||||
|
long: '12/10/2021',
|
||||||
|
},
|
||||||
creation_moderator: 'Evelyn Roller',
|
creation_moderator: 'Evelyn Roller',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
email: 'dickerson@web.de',
|
email: 'dickerson@web.de',
|
||||||
first_name: 'Dickerson',
|
firstName: 'Dickerson',
|
||||||
last_name: 'Macdonald',
|
lastName: 'Macdonald',
|
||||||
creation: '100,400,800',
|
creation: '[100,400,800]',
|
||||||
creation_gdd: '200',
|
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',
|
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: '01/09/2021',
|
creation_date: {
|
||||||
|
short: 'September',
|
||||||
|
long: '05/09/2021',
|
||||||
|
},
|
||||||
creation_moderator: 'Manuela Gast',
|
creation_moderator: 'Manuela Gast',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
creation: [null, null, null],
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
updateConfirmResult(e, event) {
|
removeConfirmResult(e, event) {
|
||||||
if (event === 'remove') {
|
if (event === 'remove') {
|
||||||
let index = 0
|
let index = 0
|
||||||
let findArr = {}
|
let findArr = {}
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="user-search">
|
||||||
<label>Usersuche</label>
|
<label>Usersuche</label>
|
||||||
<b-input
|
<b-input
|
||||||
type="text"
|
type="text"
|
||||||
v-model="criteria"
|
v-model="criteria"
|
||||||
class="shadow p-3 mb-5 bg-white rounded"
|
class="shadow p-3 mb-5 bg-white rounded"
|
||||||
placeholder="User suche"
|
placeholder="User suche"
|
||||||
|
@input="getUsers"
|
||||||
></b-input>
|
></b-input>
|
||||||
<user-table
|
<user-table
|
||||||
type="PageUserSearch"
|
type="PageUserSearch"
|
||||||
@ -17,9 +18,10 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import UserTable from '../components/UserTable.vue'
|
import UserTable from '../components/UserTable.vue'
|
||||||
|
import { searchUsers } from '../graphql/searchUsers'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'overview',
|
name: 'UserSearch',
|
||||||
components: {
|
components: {
|
||||||
UserTable,
|
UserTable,
|
||||||
},
|
},
|
||||||
@ -28,46 +30,41 @@ export default {
|
|||||||
showArrays: false,
|
showArrays: false,
|
||||||
fields: [
|
fields: [
|
||||||
{ key: 'email', label: 'Email' },
|
{ key: 'email', label: 'Email' },
|
||||||
{ key: 'first_name', label: 'Firstname' },
|
{ key: 'firstName', label: 'Firstname' },
|
||||||
{ key: 'last_name', label: 'Lastname' },
|
{ key: 'lastName', label: 'Lastname' },
|
||||||
{ key: 'creation', label: 'Creation' },
|
{ key: 'creation', label: 'Creation' },
|
||||||
{ key: 'show_details', label: 'Details' },
|
{ key: 'show_details', label: 'Details' },
|
||||||
],
|
],
|
||||||
searchResult: [
|
searchResult: [],
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
email: 'dickerson@web.de',
|
|
||||||
first_name: 'Dickerson',
|
|
||||||
last_name: 'Macdonald',
|
|
||||||
creation: '450,200,700',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
email: 'larsen@woob.de',
|
|
||||||
first_name: 'Larsen',
|
|
||||||
last_name: 'Shaw',
|
|
||||||
creation: '300,200,1000',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
email: 'geneva@tete.de',
|
|
||||||
first_name: 'Geneva',
|
|
||||||
last_name: 'Wilson',
|
|
||||||
creation: '350,200,900',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
email: 'viewrter@asdfvb.com',
|
|
||||||
first_name: 'Soledare',
|
|
||||||
last_name: 'Takker',
|
|
||||||
creation: '100,400,800',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
massCreation: [],
|
massCreation: [],
|
||||||
criteria: '',
|
criteria: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {},
|
methods: {
|
||||||
|
getUsers() {
|
||||||
|
this.$apollo
|
||||||
|
.query({
|
||||||
|
query: searchUsers,
|
||||||
|
variables: {
|
||||||
|
searchText: this.criteria,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
this.searchResult = result.data.searchUsers.map((user) => {
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
// showDetails: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toasted.error(error.message)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getUsers()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
92
admin/src/router/router.test.js
Normal file
92
admin/src/router/router.test.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import router from './router'
|
||||||
|
|
||||||
|
describe('router', () => {
|
||||||
|
describe('options', () => {
|
||||||
|
const { options } = router
|
||||||
|
const { scrollBehavior, routes } = options
|
||||||
|
|
||||||
|
it('has "/admin" as base', () => {
|
||||||
|
expect(options).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
base: '/admin',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has "active" as linkActiveClass', () => {
|
||||||
|
expect(options).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
linkActiveClass: 'active',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has "history" as mode', () => {
|
||||||
|
expect(options).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
mode: 'history',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('scroll behavior', () => {
|
||||||
|
it('returns save position when given', () => {
|
||||||
|
expect(scrollBehavior({}, {}, 'given')).toBe('given')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns selector when hash is given', () => {
|
||||||
|
expect(scrollBehavior({ hash: '#to' }, {})).toEqual({ selector: '#to' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns top left coordinates as default', () => {
|
||||||
|
expect(scrollBehavior({}, {})).toEqual({ x: 0, y: 0 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('routes', () => {
|
||||||
|
it('has "/overview" as default', async () => {
|
||||||
|
const component = await routes.find((r) => r.path === '/').component()
|
||||||
|
expect(component.default.name).toBe('overview')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has fourteen routes defined', () => {
|
||||||
|
expect(routes).toHaveLength(6)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('overview', () => {
|
||||||
|
it('loads the "Overview" component', async () => {
|
||||||
|
const component = await routes.find((r) => r.path === '/overview').component()
|
||||||
|
expect(component.default.name).toBe('overview')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('user', () => {
|
||||||
|
it('loads the "UserSearch" component', async () => {
|
||||||
|
const component = await routes.find((r) => r.path === '/user').component()
|
||||||
|
expect(component.default.name).toBe('UserSearch')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('creation', () => {
|
||||||
|
it('loads the "Creation" component', async () => {
|
||||||
|
const component = await routes.find((r) => r.path === '/creation').component()
|
||||||
|
expect(component.default.name).toBe('Creation')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('creation-confirm', () => {
|
||||||
|
it('loads the "CreationConfirm" component', async () => {
|
||||||
|
const component = await routes.find((r) => r.path === '/creation-confirm').component()
|
||||||
|
expect(component.default.name).toBe('CreationConfirm')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('not found page', () => {
|
||||||
|
it('renders the "NotFound" component', async () => {
|
||||||
|
const component = await routes.find((r) => r.path === '*').component()
|
||||||
|
expect(component.default.name).toEqual('not-found')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -7,10 +7,10 @@ Vue.use(Vuex)
|
|||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
openCreationsPlus: (state, i) => {
|
openCreationsPlus: (state, i) => {
|
||||||
state.openCreations = state.openCreations + i
|
state.openCreations += i
|
||||||
},
|
},
|
||||||
openCreationsMinus: (state, i) => {
|
openCreationsMinus: (state, i) => {
|
||||||
state.openCreations = state.openCreations - i
|
state.openCreations -= i
|
||||||
},
|
},
|
||||||
resetOpenCreations: (state) => {
|
resetOpenCreations: (state) => {
|
||||||
state.openCreations = 0
|
state.openCreations = 0
|
||||||
|
|||||||
39
admin/src/store/store.test.js
Normal file
39
admin/src/store/store.test.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { mutations } from './store'
|
||||||
|
|
||||||
|
const { token, openCreationsPlus, openCreationsMinus, resetOpenCreations } = mutations
|
||||||
|
|
||||||
|
describe('Vuex store', () => {
|
||||||
|
describe('mutations', () => {
|
||||||
|
describe('token', () => {
|
||||||
|
it('sets the state of token', () => {
|
||||||
|
const state = { token: null }
|
||||||
|
token(state, '1234')
|
||||||
|
expect(state.token).toEqual('1234')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('openCreationsPlus', () => {
|
||||||
|
it('increases the open creations by a given number', () => {
|
||||||
|
const state = { openCreations: 0 }
|
||||||
|
openCreationsPlus(state, 12)
|
||||||
|
expect(state.openCreations).toEqual(12)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('openCreationsMinus', () => {
|
||||||
|
it('decreases the open creations by a given number', () => {
|
||||||
|
const state = { openCreations: 12 }
|
||||||
|
openCreationsMinus(state, 2)
|
||||||
|
expect(state.openCreations).toEqual(10)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('resetOpenCreations', () => {
|
||||||
|
it('sets the open creations to 0', () => {
|
||||||
|
const state = { openCreations: 24 }
|
||||||
|
resetOpenCreations(state)
|
||||||
|
expect(state.openCreations).toEqual(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
59
admin/src/views/Creation.spec.js
Normal file
59
admin/src/views/Creation.spec.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import Creation from './Creation.vue'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
searchUsers: [
|
||||||
|
{
|
||||||
|
firstName: 'Bibi',
|
||||||
|
lastName: 'Bloxberg',
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
creation: [200, 400, 600],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const toastErrorMock = jest.fn()
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$apollo: {
|
||||||
|
query: apolloQueryMock,
|
||||||
|
},
|
||||||
|
$toasted: {
|
||||||
|
error: toastErrorMock,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Creation', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(Creation, { localVue, mocks })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a DIV element with the class.creation', () => {
|
||||||
|
expect(wrapper.find('div.creation').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apollo returns error', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
apolloQueryMock.mockRejectedValue({
|
||||||
|
message: 'Ouch',
|
||||||
|
})
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toasts an error message', () => {
|
||||||
|
expect(toastErrorMock).toBeCalledWith('Ouch')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
53
admin/src/views/CreationConfirm.spec.js
Normal file
53
admin/src/views/CreationConfirm.spec.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import CreationConfirm from './CreationConfirm.vue'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const storeCommitMock = jest.fn()
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$store: {
|
||||||
|
commit: storeCommitMock,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('CreationConfirm', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(CreationConfirm, { localVue, mocks })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a DIV element with the class.creation-confirm', () => {
|
||||||
|
expect(wrapper.find('div.creation-confirm').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('store', () => {
|
||||||
|
it('commits resetOpenCreations to store', () => {
|
||||||
|
expect(storeCommitMock).toBeCalledWith('resetOpenCreations')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('commits openCreationsPlus to store', () => {
|
||||||
|
expect(storeCommitMock).toBeCalledWith('openCreationsPlus', 5)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('confirm creation', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper
|
||||||
|
.findComponent({ name: 'UserTable' })
|
||||||
|
.vm.$emit('remove-confirm-result', 1, 'remove')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('commits openCreationsMinus to store', () => {
|
||||||
|
expect(storeCommitMock).toBeCalledWith('openCreationsMinus', 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
59
admin/src/views/UserSearch.spec.js
Normal file
59
admin/src/views/UserSearch.spec.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import UserSearch from './UserSearch.vue'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
searchUsers: [
|
||||||
|
{
|
||||||
|
firstName: 'Bibi',
|
||||||
|
lastName: 'Bloxberg',
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
creation: [200, 400, 600],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const toastErrorMock = jest.fn()
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$apollo: {
|
||||||
|
query: apolloQueryMock,
|
||||||
|
},
|
||||||
|
$toasted: {
|
||||||
|
error: toastErrorMock,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('UserSearch', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(UserSearch, { localVue, mocks })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a DIV element with the class.user-search', () => {
|
||||||
|
expect(wrapper.find('div.user-search').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apollo returns error', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
apolloQueryMock.mockRejectedValue({
|
||||||
|
message: 'Ouch',
|
||||||
|
})
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toasts an error message', () => {
|
||||||
|
expect(toastErrorMock).toBeCalledWith('Ouch')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,10 +1,14 @@
|
|||||||
import { createLocalVue } from '@vue/test-utils'
|
import { createLocalVue } from '@vue/test-utils'
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import { BootstrapVue } from 'bootstrap-vue'
|
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
||||||
|
|
||||||
|
// without this async calls are not working
|
||||||
|
import 'regenerator-runtime'
|
||||||
|
|
||||||
global.localVue = createLocalVue()
|
global.localVue = createLocalVue()
|
||||||
|
|
||||||
global.localVue.use(BootstrapVue)
|
global.localVue.use(BootstrapVue)
|
||||||
|
global.localVue.use(IconsPlugin)
|
||||||
|
|
||||||
// throw errors for vue warnings to force the programmers to take care about warnings
|
// throw errors for vue warnings to force the programmers to take care about warnings
|
||||||
Vue.config.warnHandler = (w) => {
|
Vue.config.warnHandler = (w) => {
|
||||||
|
|||||||
@ -10623,7 +10623,7 @@ regenerator-runtime@^0.11.0:
|
|||||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
||||||
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
|
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
|
||||||
|
|
||||||
regenerator-runtime@^0.13.4:
|
regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.9:
|
||||||
version "0.13.9"
|
version "0.13.9"
|
||||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
|
||||||
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
|
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
|
||||||
|
|||||||
@ -30,4 +30,6 @@ COMMUNITY_URL=
|
|||||||
COMMUNITY_REGISTER_URL=
|
COMMUNITY_REGISTER_URL=
|
||||||
COMMUNITY_DESCRIPTION=
|
COMMUNITY_DESCRIPTION=
|
||||||
LOGIN_APP_SECRET=21ffbbc616fe
|
LOGIN_APP_SECRET=21ffbbc616fe
|
||||||
LOGIN_SERVER_KEY=a51ef8ac7ef1abf162fb7a65261acd7a
|
LOGIN_SERVER_KEY=a51ef8ac7ef1abf162fb7a65261acd7a
|
||||||
|
|
||||||
|
WEBHOOK_ELOPAGE_SECRET=secret
|
||||||
@ -20,6 +20,7 @@
|
|||||||
"apollo-server-express": "^2.25.2",
|
"apollo-server-express": "^2.25.2",
|
||||||
"apollo-server-testing": "^2.25.2",
|
"apollo-server-testing": "^2.25.2",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
|
"body-parser": "^1.19.0",
|
||||||
"class-validator": "^0.13.1",
|
"class-validator": "^0.13.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
|
|||||||
@ -51,14 +51,25 @@ const email = {
|
|||||||
EMAIL_PASSWORD: process.env.EMAIL_PASSWORD || 'xxx',
|
EMAIL_PASSWORD: process.env.EMAIL_PASSWORD || 'xxx',
|
||||||
EMAIL_SMTP_URL: process.env.EMAIL_SMTP_URL || 'gmail.com',
|
EMAIL_SMTP_URL: process.env.EMAIL_SMTP_URL || 'gmail.com',
|
||||||
EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '587',
|
EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '587',
|
||||||
|
|
||||||
EMAIL_LINK_VERIFICATION:
|
EMAIL_LINK_VERIFICATION:
|
||||||
process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/vue/checkEmail/$1',
|
process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/vue/checkEmail/$1',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const webhook = {
|
||||||
|
WEBHOOK_ELOPAGE_SECRET: process.env.WEBHOOK_ELOPAGE_SECRET || 'secret',
|
||||||
|
}
|
||||||
|
|
||||||
// This is needed by graphql-directive-auth
|
// This is needed by graphql-directive-auth
|
||||||
process.env.APP_SECRET = server.JWT_SECRET
|
process.env.APP_SECRET = server.JWT_SECRET
|
||||||
|
|
||||||
const CONFIG = { ...server, ...database, ...klicktipp, ...community, ...email, ...loginServer }
|
const CONFIG = {
|
||||||
|
...server,
|
||||||
|
...database,
|
||||||
|
...klicktipp,
|
||||||
|
...community,
|
||||||
|
...email,
|
||||||
|
...loginServer,
|
||||||
|
...webhook,
|
||||||
|
}
|
||||||
|
|
||||||
export default CONFIG
|
export default CONFIG
|
||||||
|
|||||||
@ -12,10 +12,7 @@ export default class CreateUserArgs {
|
|||||||
lastName: string
|
lastName: string
|
||||||
|
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
password: string
|
language?: string // Will default to DEFAULT_LANGUAGE
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
language: string
|
|
||||||
|
|
||||||
@Field(() => Int, { nullable: true })
|
@Field(() => Int, { nullable: true })
|
||||||
publisherId: number
|
publisherId: number
|
||||||
|
|||||||
@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
import { AuthChecker } from 'type-graphql'
|
import { AuthChecker } from 'type-graphql'
|
||||||
|
|
||||||
import CONFIG from '../../config'
|
|
||||||
import { apiGet } from '../../apis/HttpRequest'
|
|
||||||
|
|
||||||
import decode from '../../jwt/decode'
|
import decode from '../../jwt/decode'
|
||||||
import encode from '../../jwt/encode'
|
import encode from '../../jwt/encode'
|
||||||
|
|
||||||
@ -13,7 +10,7 @@ const isAuthorized: AuthChecker<any> = async (
|
|||||||
) => {
|
) => {
|
||||||
if (context.token) {
|
if (context.token) {
|
||||||
const decoded = decode(context.token)
|
const decoded = decode(context.token)
|
||||||
context.pubKey = decoded.pubKey
|
context.pubKey = Buffer.from(decoded.pubKey).toString('hex')
|
||||||
context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) })
|
context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) })
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
16
backend/src/graphql/model/UserAdmin.ts
Normal file
16
backend/src/graphql/model/UserAdmin.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { ObjectType, Field } from 'type-graphql'
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class UserAdmin {
|
||||||
|
@Field(() => String)
|
||||||
|
email: string
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
firstName: string
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
lastName: string
|
||||||
|
|
||||||
|
@Field(() => [Number])
|
||||||
|
creation: number[]
|
||||||
|
}
|
||||||
26
backend/src/graphql/resolver/AdminResolver.ts
Normal file
26
backend/src/graphql/resolver/AdminResolver.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Resolver, Query, Arg } from 'type-graphql'
|
||||||
|
import { getCustomRepository } from 'typeorm'
|
||||||
|
import { UserAdmin } from '../model/UserAdmin'
|
||||||
|
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
||||||
|
|
||||||
|
@Resolver()
|
||||||
|
export class AdminResolver {
|
||||||
|
@Query(() => [UserAdmin])
|
||||||
|
async searchUsers(@Arg('searchText') searchText: string): Promise<UserAdmin[]> {
|
||||||
|
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||||
|
const loginUsers = await loginUserRepository.findBySearchCriteria(searchText)
|
||||||
|
const users = loginUsers.map((loginUser) => {
|
||||||
|
const user = new UserAdmin()
|
||||||
|
user.firstName = loginUser.firstName
|
||||||
|
user.lastName = loginUser.lastName
|
||||||
|
user.email = loginUser.email
|
||||||
|
user.creation = [
|
||||||
|
(Math.floor(Math.random() * 50) + 1) * 20,
|
||||||
|
(Math.floor(Math.random() * 50) + 1) * 20,
|
||||||
|
(Math.floor(Math.random() * 50) + 1) * 20,
|
||||||
|
]
|
||||||
|
return user
|
||||||
|
})
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -613,9 +613,6 @@ export class TransactionResolver {
|
|||||||
await queryRunner.commitTransaction()
|
await queryRunner.commitTransaction()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await queryRunner.rollbackTransaction()
|
await queryRunner.rollbackTransaction()
|
||||||
throw e
|
|
||||||
} finally {
|
|
||||||
await queryRunner.release()
|
|
||||||
// TODO: This is broken code - we should never correct an autoincrement index in production
|
// TODO: This is broken code - we should never correct an autoincrement index in production
|
||||||
// according to dario it is required tho to properly work. The index of the table is used as
|
// according to dario it is required tho to properly work. The index of the table is used as
|
||||||
// index for the transaction which requires a chain without gaps
|
// index for the transaction which requires a chain without gaps
|
||||||
@ -627,6 +624,9 @@ export class TransactionResolver {
|
|||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('problems with reset auto increment: %o', error)
|
console.log('problems with reset auto increment: %o', error)
|
||||||
})
|
})
|
||||||
|
throw e
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release()
|
||||||
}
|
}
|
||||||
// send notification email
|
// send notification email
|
||||||
// TODO: translate
|
// TODO: translate
|
||||||
|
|||||||
@ -22,14 +22,14 @@ import {
|
|||||||
} from '../../middleware/klicktippMiddleware'
|
} from '../../middleware/klicktippMiddleware'
|
||||||
import { CheckEmailResponse } from '../model/CheckEmailResponse'
|
import { CheckEmailResponse } from '../model/CheckEmailResponse'
|
||||||
import { UserSettingRepository } from '../../typeorm/repository/UserSettingRepository'
|
import { UserSettingRepository } from '../../typeorm/repository/UserSettingRepository'
|
||||||
|
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
||||||
import { Setting } from '../enum/Setting'
|
import { Setting } from '../enum/Setting'
|
||||||
import { UserRepository } from '../../typeorm/repository/User'
|
import { UserRepository } from '../../typeorm/repository/User'
|
||||||
import { LoginUser } from '@entity/LoginUser'
|
import { LoginUser } from '@entity/LoginUser'
|
||||||
import { LoginElopageBuys } from '@entity/LoginElopageBuys'
|
|
||||||
import { LoginUserBackup } from '@entity/LoginUserBackup'
|
import { LoginUserBackup } from '@entity/LoginUserBackup'
|
||||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||||
import { sendEMail } from '../../util/sendEMail'
|
import { sendEMail } from '../../util/sendEMail'
|
||||||
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
import { LoginElopageBuysRepository } from '../../typeorm/repository/LoginElopageBuys'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const sodium = require('sodium-native')
|
const sodium = require('sodium-native')
|
||||||
@ -231,33 +231,33 @@ export class UserResolver {
|
|||||||
@Ctx() context: any,
|
@Ctx() context: any,
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
email = email.trim().toLowerCase()
|
email = email.trim().toLowerCase()
|
||||||
const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
|
// const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
|
||||||
|
// UnsecureLogin
|
||||||
// if there is no user, throw an authentication error
|
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||||
if (!result.success) {
|
const loginUser = await loginUserRepository.findByEmail(email).catch(() => {
|
||||||
throw new Error(result.data)
|
throw new Error('No user with this credentials')
|
||||||
}
|
|
||||||
|
|
||||||
context.setHeaders.push({
|
|
||||||
key: 'token',
|
|
||||||
value: encode(result.data.user.public_hex),
|
|
||||||
})
|
})
|
||||||
const user = new User(result.data.user)
|
if (!loginUser.emailChecked) throw new Error('user email not validated')
|
||||||
// Hack: Database Field is not validated properly and not nullable
|
const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
||||||
if (user.publisherId === 0) {
|
const loginUserPassword = BigInt(loginUser.password.toString())
|
||||||
user.publisherId = undefined
|
if (loginUserPassword !== passwordHash[0].readBigUInt64LE()) {
|
||||||
|
throw new Error('No user with this credentials')
|
||||||
}
|
}
|
||||||
user.hasElopage = result.data.hasElopage
|
// TODO: If user has no pubKey Create it again and update user.
|
||||||
// read additional settings from settings table
|
|
||||||
const userRepository = getCustomRepository(UserRepository)
|
const userRepository = getCustomRepository(UserRepository)
|
||||||
let userEntity: void | DbUser
|
let userEntity: void | DbUser
|
||||||
userEntity = await userRepository.findByPubkeyHex(user.pubkey).catch(() => {
|
const loginUserPubKey = loginUser.pubKey
|
||||||
|
const loginUserPubKeyString = loginUserPubKey.toString('hex')
|
||||||
|
userEntity = await userRepository.findByPubkeyHex(loginUserPubKeyString).catch(() => {
|
||||||
|
// User not stored in state_users
|
||||||
|
// TODO: Check with production data - email is unique which can cause problems
|
||||||
userEntity = new DbUser()
|
userEntity = new DbUser()
|
||||||
userEntity.firstName = user.firstName
|
userEntity.firstName = loginUser.firstName
|
||||||
userEntity.lastName = user.lastName
|
userEntity.lastName = loginUser.lastName
|
||||||
userEntity.username = user.username
|
userEntity.username = loginUser.username
|
||||||
userEntity.email = user.email
|
userEntity.email = loginUser.email
|
||||||
userEntity.pubkey = Buffer.from(user.pubkey, 'hex')
|
userEntity.pubkey = loginUser.pubKey
|
||||||
|
|
||||||
userRepository.save(userEntity).catch(() => {
|
userRepository.save(userEntity).catch(() => {
|
||||||
throw new Error('error by save userEntity')
|
throw new Error('error by save userEntity')
|
||||||
@ -267,16 +267,28 @@ export class UserResolver {
|
|||||||
throw new Error('error with cannot happen')
|
throw new Error('error with cannot happen')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save publisherId if Elopage is not yet registered
|
const user = new User()
|
||||||
|
user.email = email
|
||||||
|
user.firstName = loginUser.firstName
|
||||||
|
user.lastName = loginUser.lastName
|
||||||
|
user.username = loginUser.username
|
||||||
|
user.description = loginUser.description
|
||||||
|
user.pubkey = loginUserPubKeyString
|
||||||
|
user.language = loginUser.language
|
||||||
|
|
||||||
|
// Elopage Status & Stored PublisherId
|
||||||
|
user.hasElopage = await this.hasElopage({ pubKey: loginUserPubKeyString })
|
||||||
if (!user.hasElopage && publisherId) {
|
if (!user.hasElopage && publisherId) {
|
||||||
user.publisherId = publisherId
|
user.publisherId = publisherId
|
||||||
|
// TODO: Check if we can use updateUserInfos
|
||||||
|
// await this.updateUserInfos({ publisherId }, { pubKey: loginUser.pubKey })
|
||||||
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||||
const loginUser = await loginUserRepository.findOneOrFail({ email: userEntity.email })
|
const loginUser = await loginUserRepository.findOneOrFail({ email: userEntity.email })
|
||||||
loginUser.publisherId = publisherId
|
loginUser.publisherId = publisherId
|
||||||
loginUserRepository.save(loginUser)
|
loginUserRepository.save(loginUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// coinAnimation
|
||||||
const userSettingRepository = getCustomRepository(UserSettingRepository)
|
const userSettingRepository = getCustomRepository(UserSettingRepository)
|
||||||
const coinanimation = await userSettingRepository
|
const coinanimation = await userSettingRepository
|
||||||
.readBoolean(userEntity.id, Setting.COIN_ANIMATION)
|
.readBoolean(userEntity.id, Setting.COIN_ANIMATION)
|
||||||
@ -285,6 +297,12 @@ export class UserResolver {
|
|||||||
})
|
})
|
||||||
user.coinanimation = coinanimation
|
user.coinanimation = coinanimation
|
||||||
user.isAdmin = true // TODO implement
|
user.isAdmin = true // TODO implement
|
||||||
|
|
||||||
|
context.setHeaders.push({
|
||||||
|
key: 'token',
|
||||||
|
value: encode(loginUser.pubKey),
|
||||||
|
})
|
||||||
|
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,22 +334,23 @@ export class UserResolver {
|
|||||||
|
|
||||||
@Mutation(() => String)
|
@Mutation(() => String)
|
||||||
async createUser(
|
async createUser(
|
||||||
@Args() { email, firstName, lastName, password, language, publisherId }: CreateUserArgs,
|
@Args() { email, firstName, lastName, language, publisherId }: CreateUserArgs,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
|
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
|
||||||
// default int publisher_id = 0;
|
// default int publisher_id = 0;
|
||||||
|
|
||||||
// Validate Language (no throw)
|
// Validate Language (no throw)
|
||||||
if (!isLanguage(language)) {
|
if (!language || !isLanguage(language)) {
|
||||||
language = DEFAULT_LANGUAGE
|
language = DEFAULT_LANGUAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Register process
|
||||||
// Validate Password
|
// Validate Password
|
||||||
if (!isPassword(password)) {
|
// if (!isPassword(password)) {
|
||||||
throw new Error(
|
// throw new Error(
|
||||||
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
// 'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Validate username
|
// Validate username
|
||||||
// TODO: never true
|
// TODO: never true
|
||||||
@ -349,11 +368,13 @@ export class UserResolver {
|
|||||||
throw new Error(`User already exists.`)
|
throw new Error(`User already exists.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const passphrase = PassphraseGenerate()
|
// TODO: Register process
|
||||||
const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
// const passphrase = PassphraseGenerate()
|
||||||
const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
// const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
||||||
|
// const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
||||||
|
// const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
||||||
|
|
||||||
const emailHash = getEmailHash(email)
|
const emailHash = getEmailHash(email)
|
||||||
const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
|
||||||
|
|
||||||
// Table: login_users
|
// Table: login_users
|
||||||
const loginUser = new LoginUser()
|
const loginUser = new LoginUser()
|
||||||
@ -362,13 +383,15 @@ export class UserResolver {
|
|||||||
loginUser.lastName = lastName
|
loginUser.lastName = lastName
|
||||||
loginUser.username = username
|
loginUser.username = username
|
||||||
loginUser.description = ''
|
loginUser.description = ''
|
||||||
loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
// TODO: Register process
|
||||||
|
// loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
||||||
loginUser.emailHash = emailHash
|
loginUser.emailHash = emailHash
|
||||||
loginUser.language = language
|
loginUser.language = language
|
||||||
loginUser.groupId = 1
|
loginUser.groupId = 1
|
||||||
loginUser.publisherId = publisherId
|
loginUser.publisherId = publisherId
|
||||||
loginUser.pubKey = keyPair[0]
|
// TODO: Register process
|
||||||
loginUser.privKey = encryptedPrivkey
|
// loginUser.pubKey = keyPair[0]
|
||||||
|
// loginUser.privKey = encryptedPrivkey
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
await queryRunner.connect()
|
await queryRunner.connect()
|
||||||
@ -380,21 +403,24 @@ export class UserResolver {
|
|||||||
throw new Error('insert user failed')
|
throw new Error('insert user failed')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TODO: Register process
|
||||||
// Table: login_user_backups
|
// Table: login_user_backups
|
||||||
const loginUserBackup = new LoginUserBackup()
|
// const loginUserBackup = new LoginUserBackup()
|
||||||
loginUserBackup.userId = loginUserId
|
// loginUserBackup.userId = loginUserId
|
||||||
loginUserBackup.passphrase = passphrase.join(' ') + ' ' // login server saves trailing space
|
// loginUserBackup.passphrase = passphrase.join(' ') + ' ' // login server saves trailing space
|
||||||
loginUserBackup.mnemonicType = 2 // ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER;
|
// loginUserBackup.mnemonicType = 2 // ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER;
|
||||||
|
|
||||||
await queryRunner.manager.save(loginUserBackup).catch((error) => {
|
// TODO: Register process
|
||||||
// eslint-disable-next-line no-console
|
// await queryRunner.manager.save(loginUserBackup).catch((error) => {
|
||||||
console.log('insert LoginUserBackup failed', error)
|
// // eslint-disable-next-line no-console
|
||||||
throw new Error('insert user backup failed')
|
// console.log('insert LoginUserBackup failed', error)
|
||||||
})
|
// throw new Error('insert user backup failed')
|
||||||
|
// })
|
||||||
|
|
||||||
// Table: state_users
|
// Table: state_users
|
||||||
const dbUser = new DbUser()
|
const dbUser = new DbUser()
|
||||||
dbUser.pubkey = keyPair[0]
|
// TODO: Register process
|
||||||
|
// dbUser.pubkey = keyPair[0]
|
||||||
dbUser.email = email
|
dbUser.email = email
|
||||||
dbUser.firstName = firstName
|
dbUser.firstName = firstName
|
||||||
dbUser.lastName = lastName
|
dbUser.lastName = lastName
|
||||||
@ -568,7 +594,7 @@ export class UserResolver {
|
|||||||
await queryRunner.startTransaction('READ UNCOMMITTED')
|
await queryRunner.startTransaction('READ UNCOMMITTED')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (coinanimation) {
|
if (coinanimation !== null && coinanimation !== undefined) {
|
||||||
queryRunner.manager
|
queryRunner.manager
|
||||||
.getCustomRepository(UserSettingRepository)
|
.getCustomRepository(UserSettingRepository)
|
||||||
.setOrUpdate(userEntity.id, Setting.COIN_ANIMATION, coinanimation.toString())
|
.setOrUpdate(userEntity.id, Setting.COIN_ANIMATION, coinanimation.toString())
|
||||||
@ -640,7 +666,8 @@ export class UserResolver {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const elopageBuyCount = await LoginElopageBuys.count({ payerEmail: userEntity.email })
|
const loginElopageBuysRepository = getCustomRepository(LoginElopageBuysRepository)
|
||||||
|
const elopageBuyCount = await loginElopageBuysRepository.count({ payerEmail: userEntity.email })
|
||||||
return elopageBuyCount > 0
|
return elopageBuyCount > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import 'module-alias/register'
|
|||||||
|
|
||||||
import { ApolloServer } from 'apollo-server-express'
|
import { ApolloServer } from 'apollo-server-express'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
|
import bodyParser from 'body-parser'
|
||||||
|
|
||||||
// database
|
// database
|
||||||
import connection from '../typeorm/connection'
|
import connection from '../typeorm/connection'
|
||||||
@ -22,6 +23,9 @@ import CONFIG from '../config'
|
|||||||
// graphql
|
// graphql
|
||||||
import schema from '../graphql/schema'
|
import schema from '../graphql/schema'
|
||||||
|
|
||||||
|
// webhooks
|
||||||
|
import { elopageWebhook } from '../webhook/elopage'
|
||||||
|
|
||||||
// TODO implement
|
// TODO implement
|
||||||
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
|
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
|
||||||
|
|
||||||
@ -50,6 +54,12 @@ const createServer = async (context: any = serverContext): Promise<any> => {
|
|||||||
// cors
|
// cors
|
||||||
app.use(cors)
|
app.use(cors)
|
||||||
|
|
||||||
|
// bodyparser
|
||||||
|
app.use(bodyParser.json())
|
||||||
|
|
||||||
|
// Elopage Webhook
|
||||||
|
app.post('/hook/elopage/' + CONFIG.WEBHOOK_ELOPAGE_SECRET, elopageWebhook)
|
||||||
|
|
||||||
// Apollo Server
|
// Apollo Server
|
||||||
const apollo = new ApolloServer({
|
const apollo = new ApolloServer({
|
||||||
schema: await schema(),
|
schema: await schema(),
|
||||||
|
|||||||
5
backend/src/typeorm/repository/LoginElopageBuys.ts
Normal file
5
backend/src/typeorm/repository/LoginElopageBuys.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { EntityRepository, Repository } from 'typeorm'
|
||||||
|
import { LoginElopageBuys } from '@entity/LoginElopageBuys'
|
||||||
|
|
||||||
|
@EntityRepository(LoginElopageBuys)
|
||||||
|
export class LoginElopageBuysRepository extends Repository<LoginElopageBuys> {}
|
||||||
@ -2,4 +2,23 @@ import { EntityRepository, Repository } from 'typeorm'
|
|||||||
import { LoginUser } from '@entity/LoginUser'
|
import { LoginUser } from '@entity/LoginUser'
|
||||||
|
|
||||||
@EntityRepository(LoginUser)
|
@EntityRepository(LoginUser)
|
||||||
export class LoginUserRepository extends Repository<LoginUser> {}
|
export class LoginUserRepository extends Repository<LoginUser> {
|
||||||
|
async findByEmail(email: string): Promise<LoginUser> {
|
||||||
|
return this.createQueryBuilder('loginUser')
|
||||||
|
.where('loginUser.email = :email', { email })
|
||||||
|
.getOneOrFail()
|
||||||
|
}
|
||||||
|
|
||||||
|
async findBySearchCriteria(searchCriteria: string): Promise<LoginUser[]> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -9,6 +9,15 @@ export class UserRepository extends Repository<User> {
|
|||||||
.getOneOrFail()
|
.getOneOrFail()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findByPubkeyHexBuffer(pubkeyHexBuffer: Buffer): Promise<User> {
|
||||||
|
const pubKeyString = pubkeyHexBuffer.toString('hex')
|
||||||
|
return await this.findByPubkeyHex(pubKeyString)
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByEmail(email: string): Promise<User> {
|
||||||
|
return this.createQueryBuilder('user').where('user.email = :email', { email }).getOneOrFail()
|
||||||
|
}
|
||||||
|
|
||||||
async getUsersIndiced(userIds: number[]): Promise<User[]> {
|
async getUsersIndiced(userIds: number[]): Promise<User[]> {
|
||||||
if (!userIds.length) return []
|
if (!userIds.length) return []
|
||||||
const users = await this.createQueryBuilder('user')
|
const users = await this.createQueryBuilder('user')
|
||||||
|
|||||||
154
backend/src/webhook/elopage.ts
Normal file
154
backend/src/webhook/elopage.ts
Normal file
File diff suppressed because one or more lines are too long
@ -1552,7 +1552,7 @@ binary-extensions@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||||
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
||||||
|
|
||||||
body-parser@1.19.0, body-parser@^1.18.3:
|
body-parser@1.19.0, body-parser@^1.18.3, body-parser@^1.19.0:
|
||||||
version "1.19.0"
|
version "1.19.0"
|
||||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
||||||
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
|
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
|
||||||
|
|||||||
31
database/entity/ServerUser.ts
Normal file
31
database/entity/ServerUser.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
||||||
|
|
||||||
|
@Entity('server_users')
|
||||||
|
export class ServerUser extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({ length: 50 })
|
||||||
|
username: string
|
||||||
|
|
||||||
|
@Column({ type: 'bigint', unsigned: true })
|
||||||
|
password: BigInt
|
||||||
|
|
||||||
|
@Column({ length: 50, unique: true })
|
||||||
|
email: string
|
||||||
|
|
||||||
|
@Column({ length: 20, default: 'admin' })
|
||||||
|
role: string
|
||||||
|
|
||||||
|
@Column({ default: 0 })
|
||||||
|
activated: number
|
||||||
|
|
||||||
|
@Column({ name: 'last_login', default: null, nullable: true })
|
||||||
|
lastLogin: Date
|
||||||
|
|
||||||
|
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
created: Date
|
||||||
|
|
||||||
|
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
modified: Date
|
||||||
|
}
|
||||||
@ -8,8 +8,6 @@ services:
|
|||||||
image: gradido/frontend:development
|
image: gradido/frontend:development
|
||||||
build:
|
build:
|
||||||
target: development
|
target: development
|
||||||
networks:
|
|
||||||
- external-net
|
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV="development"
|
- NODE_ENV="development"
|
||||||
# - DEBUG=true
|
# - DEBUG=true
|
||||||
@ -27,8 +25,6 @@ services:
|
|||||||
image: gradido/admin:development
|
image: gradido/admin:development
|
||||||
build:
|
build:
|
||||||
target: development
|
target: development
|
||||||
networks:
|
|
||||||
- external-net
|
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV="development"
|
- NODE_ENV="development"
|
||||||
# - DEBUG=true
|
# - DEBUG=true
|
||||||
|
|||||||
@ -15,6 +15,7 @@ services:
|
|||||||
context: ./frontend
|
context: ./frontend
|
||||||
target: production
|
target: production
|
||||||
networks:
|
networks:
|
||||||
|
- external-net
|
||||||
- internal-net
|
- internal-net
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
@ -39,6 +40,7 @@ services:
|
|||||||
context: ./admin
|
context: ./admin
|
||||||
target: production
|
target: production
|
||||||
networks:
|
networks:
|
||||||
|
- external-net
|
||||||
- internal-net
|
- internal-net
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
|
|||||||
@ -47,6 +47,7 @@
|
|||||||
"change-password": "Fehler beim Ändern des Passworts",
|
"change-password": "Fehler beim Ändern des Passworts",
|
||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"no-account": "Leider konnten wir keinen Account finden mit diesen Daten!",
|
"no-account": "Leider konnten wir keinen Account finden mit diesen Daten!",
|
||||||
|
"no-email-verify": "Die Email wurde noch nicht bestätigt, bitte überprüfe deine Emails und klicke auf den Aktivierungslink!",
|
||||||
"session-expired": "Sitzung abgelaufen!"
|
"session-expired": "Sitzung abgelaufen!"
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
@ -181,9 +182,12 @@
|
|||||||
"uppercase": "Ein Großbuchstabe erforderlich."
|
"uppercase": "Ein Großbuchstabe erforderlich."
|
||||||
},
|
},
|
||||||
"thx": {
|
"thx": {
|
||||||
|
"activateEmail": "Deine Email wurde noch nicht aktiviert, bitte überprüfe deine Email und Klicke den Aktivierungslink!",
|
||||||
"checkEmail": "Deine Email würde erfolgreich verifiziert.",
|
"checkEmail": "Deine Email würde erfolgreich verifiziert.",
|
||||||
"email": "Wir haben dir eine eMail gesendet.",
|
"email": "Wir haben dir eine eMail gesendet.",
|
||||||
"register": "Du bist jetzt registriert.",
|
"emailActivated": "Danke dass Du deine Email bestätigt hast.",
|
||||||
|
"errorTitle": "Achtung!",
|
||||||
|
"register": "Du bist jetzt registriert, bitte überprüfe deine Emails und klicke auf den Aktivierungslink.",
|
||||||
"reset": "Dein Passwort wurde geändert.",
|
"reset": "Dein Passwort wurde geändert.",
|
||||||
"title": "Danke!"
|
"title": "Danke!"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,6 +47,7 @@
|
|||||||
"change-password": "Error while changing password",
|
"change-password": "Error while changing password",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"no-account": "Unfortunately we could not find an account to the given data!",
|
"no-account": "Unfortunately we could not find an account to the given data!",
|
||||||
|
"no-email-verify": "Your email is not activated yet, please check your emails and click the activation link!",
|
||||||
"session-expired": "The session expired"
|
"session-expired": "The session expired"
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
@ -181,9 +182,12 @@
|
|||||||
"uppercase": "One uppercase letter required."
|
"uppercase": "One uppercase letter required."
|
||||||
},
|
},
|
||||||
"thx": {
|
"thx": {
|
||||||
|
"activateEmail": "Your email has not been activated yet, please check your emails and click the activation link!",
|
||||||
"checkEmail": "Your email has been successfully verified.",
|
"checkEmail": "Your email has been successfully verified.",
|
||||||
"email": "We have sent you an email.",
|
"email": "We have sent you an email.",
|
||||||
"register": "You are registred now.",
|
"emailActivated": "Thank you your email has been activated.",
|
||||||
|
"errorTitle": "Attention!",
|
||||||
|
"register": "You are registered now, please check your emails and click the activation link.",
|
||||||
"reset": "Your password has been changed.",
|
"reset": "Your password has been changed.",
|
||||||
"title": "Thank you!"
|
"title": "Thank you!"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,7 +50,7 @@ const routes = [
|
|||||||
path: '/thx/:comingFrom',
|
path: '/thx/:comingFrom',
|
||||||
component: () => import('../views/Pages/thx.vue'),
|
component: () => import('../views/Pages/thx.vue'),
|
||||||
beforeEnter: (to, from, next) => {
|
beforeEnter: (to, from, next) => {
|
||||||
const validFrom = ['password', 'reset', 'register']
|
const validFrom = ['password', 'reset', 'register', 'login']
|
||||||
if (!validFrom.includes(from.path.split('/')[1])) {
|
if (!validFrom.includes(from.path.split('/')[1])) {
|
||||||
next({ path: '/login' })
|
next({ path: '/login' })
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -104,9 +104,14 @@ export default {
|
|||||||
this.$router.push('/overview')
|
this.$router.push('/overview')
|
||||||
loader.hide()
|
loader.hide()
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((error) => {
|
||||||
|
if (!error.message.includes('user email not validated')) {
|
||||||
|
this.$toasted.error(this.$t('error.no-account'))
|
||||||
|
} else {
|
||||||
|
// : this.$t('error.no-email-verify')
|
||||||
|
this.$router.push('/thx/login')
|
||||||
|
}
|
||||||
loader.hide()
|
loader.hide()
|
||||||
this.$toasted.error(this.$t('error.no-account'))
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -161,6 +161,7 @@ import InputEmail from '../../components/Inputs/InputEmail.vue'
|
|||||||
import InputPasswordConfirmation from '../../components/Inputs/InputPasswordConfirmation.vue'
|
import InputPasswordConfirmation from '../../components/Inputs/InputPasswordConfirmation.vue'
|
||||||
import LanguageSwitchSelect from '../../components/LanguageSwitchSelect.vue'
|
import LanguageSwitchSelect from '../../components/LanguageSwitchSelect.vue'
|
||||||
import { registerUser } from '../../graphql/mutations'
|
import { registerUser } from '../../graphql/mutations'
|
||||||
|
import { localeChanged } from 'vee-validate'
|
||||||
import { getCommunityInfoMixin } from '../../mixins/getCommunityInfo'
|
import { getCommunityInfoMixin } from '../../mixins/getCommunityInfo'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -189,6 +190,9 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
updateLanguage(e) {
|
updateLanguage(e) {
|
||||||
this.language = e
|
this.language = e
|
||||||
|
this.$store.commit('language', this.language)
|
||||||
|
this.$i18n.locale = this.language
|
||||||
|
localeChanged(this.language)
|
||||||
},
|
},
|
||||||
getValidationState({ dirty, validated, valid = null }) {
|
getValidationState({ dirty, validated, valid = null }) {
|
||||||
return dirty || validated ? valid : null
|
return dirty || validated ? valid : null
|
||||||
|
|||||||
@ -4,10 +4,12 @@
|
|||||||
<div class="header py-7 py-lg-8 pt-lg-9">
|
<div class="header py-7 py-lg-8 pt-lg-9">
|
||||||
<b-container>
|
<b-container>
|
||||||
<div class="header-body text-center mb-7">
|
<div class="header-body text-center mb-7">
|
||||||
<p class="h1">{{ $t('site.thx.title') }}</p>
|
<p class="h1">{{ $t(displaySetup.headline) }}</p>
|
||||||
<p class="h4">{{ $t(displaySetup.subtitle) }}</p>
|
<p class="h4">{{ $t(displaySetup.subtitle) }}</p>
|
||||||
<hr />
|
<hr />
|
||||||
<b-button :to="displaySetup.linkTo">{{ $t(displaySetup.button) }}</b-button>
|
<b-button v-if="displaySetup.linkTo" :to="displaySetup.linkTo">
|
||||||
|
{{ $t(displaySetup.button) }}
|
||||||
|
</b-button>
|
||||||
</div>
|
</div>
|
||||||
</b-container>
|
</b-container>
|
||||||
</div>
|
</div>
|
||||||
@ -17,25 +19,33 @@
|
|||||||
<script>
|
<script>
|
||||||
const textFields = {
|
const textFields = {
|
||||||
password: {
|
password: {
|
||||||
|
headline: 'site.thx.title',
|
||||||
subtitle: 'site.thx.email',
|
subtitle: 'site.thx.email',
|
||||||
button: 'login',
|
button: 'login',
|
||||||
linkTo: '/login',
|
linkTo: '/login',
|
||||||
},
|
},
|
||||||
reset: {
|
reset: {
|
||||||
|
headline: 'site.thx.title',
|
||||||
subtitle: 'site.thx.reset',
|
subtitle: 'site.thx.reset',
|
||||||
button: 'login',
|
button: 'login',
|
||||||
linkTo: '/login',
|
linkTo: '/login',
|
||||||
},
|
},
|
||||||
register: {
|
register: {
|
||||||
|
headline: 'site.thx.title',
|
||||||
subtitle: 'site.thx.register',
|
subtitle: 'site.thx.register',
|
||||||
button: 'site.login.signin',
|
button: 'site.login.signin',
|
||||||
linkTo: '/overview',
|
linkTo: '/overview',
|
||||||
},
|
},
|
||||||
checkEmail: {
|
checkEmail: {
|
||||||
|
headline: 'site.thx.title',
|
||||||
subtitle: 'site.thx.checkEmail',
|
subtitle: 'site.thx.checkEmail',
|
||||||
button: 'login',
|
button: 'login',
|
||||||
linkTo: '/login',
|
linkTo: '/login',
|
||||||
},
|
},
|
||||||
|
login: {
|
||||||
|
headline: 'site.thx.errorTitle',
|
||||||
|
subtitle: 'site.thx.activateEmail',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@ -147,7 +147,6 @@ Poco::JSON::Object* JsonUnsecureLogin::handle(Poco::Dynamic::Var params)
|
|||||||
infos.add("set user.group_id to default group_id = 1");
|
infos.add("set user.group_id to default group_id = 1");
|
||||||
case USER_NO_PRIVATE_KEY:
|
case USER_NO_PRIVATE_KEY:
|
||||||
case USER_COMPLETE:
|
case USER_COMPLETE:
|
||||||
case USER_EMAIL_NOT_ACTIVATED:
|
|
||||||
result->set("state", "success");
|
result->set("state", "success");
|
||||||
result->set("user", session->getNewUser()->getJson());
|
result->set("user", session->getNewUser()->getJson());
|
||||||
result->set("session_id", session->getHandle());
|
result->set("session_id", session->getHandle());
|
||||||
@ -158,6 +157,10 @@ Poco::JSON::Object* JsonUnsecureLogin::handle(Poco::Dynamic::Var params)
|
|||||||
AWAIT(hasElopageTask)
|
AWAIT(hasElopageTask)
|
||||||
result->set("hasElopage", hasElopageTask->hasElopage());
|
result->set("hasElopage", hasElopageTask->hasElopage());
|
||||||
return result;
|
return result;
|
||||||
|
case USER_EMAIL_NOT_ACTIVATED:
|
||||||
|
result->set("state", "processing");
|
||||||
|
result->set("msg", "user email not validated");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
result->set("state", "error");
|
result->set("state", "error");
|
||||||
result->set("msg", "unknown user state");
|
result->set("msg", "unknown user state");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user