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
|
||||
type: lcov
|
||||
result_path: ./coverage/lcov.info
|
||||
min_coverage: 65
|
||||
min_coverage: 47
|
||||
token: ${{ github.token }}
|
||||
|
||||
##############################################################################
|
||||
@ -491,7 +491,7 @@ jobs:
|
||||
report_name: Coverage Backend
|
||||
type: lcov
|
||||
result_path: ./backend/coverage/lcov.info
|
||||
min_coverage: 38
|
||||
min_coverage: 37
|
||||
token: ${{ github.token }}
|
||||
|
||||
##############################################################################
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
module.exports = {
|
||||
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: [
|
||||
'js',
|
||||
// 'jsx',
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "26.6.3",
|
||||
"moment": "^2.29.1",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
"stats-webpack-plugin": "^0.7.0",
|
||||
"vue": "^2.6.11",
|
||||
"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>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'ContentFooter',
|
||||
}
|
||||
</script>
|
||||
@ -19,7 +19,7 @@ const mocks = {
|
||||
const propsData = {
|
||||
type: '',
|
||||
item: {},
|
||||
creation: {},
|
||||
creation: [],
|
||||
itemsMassCreation: {},
|
||||
}
|
||||
|
||||
@ -38,5 +38,104 @@ describe('CreationFormular', () => {
|
||||
it('has a DIV element with the class.component-creation-formular', () => {
|
||||
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>
|
||||
{{
|
||||
this.type === 'singleCreation'
|
||||
? 'Einzelschöpfung für ' + item.first_name + ' ' + item.last_name + ''
|
||||
: 'Massenschöpfung für ' + Object.keys(this.itemsMassCreation).length + ' Mitglieder'
|
||||
? 'Einzelschöpfung für ' + item.firstName + ' ' + item.lastName + ''
|
||||
: 'Mehrfachschöpfung für ' + Object.keys(this.itemsMassCreation).length + ' Mitglieder'
|
||||
}}
|
||||
{{ item }}
|
||||
</h3>
|
||||
<div v-show="this.type === 'massCreation' && Object.keys(this.itemsMassCreation).length <= 0">
|
||||
Bitte wähle ein oder Mehrere Mitglieder aus für die du Schöpfen möchtest
|
||||
@ -26,7 +27,7 @@
|
||||
size="lg"
|
||||
@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-col>
|
||||
<b-col>
|
||||
@ -36,7 +37,7 @@
|
||||
size="lg"
|
||||
@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-col>
|
||||
<b-col class="text-right">
|
||||
@ -46,7 +47,7 @@
|
||||
size="lg"
|
||||
@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-col>
|
||||
</b-row>
|
||||
@ -73,6 +74,7 @@
|
||||
:min="rangeMin"
|
||||
:max="rangeMax"
|
||||
step="10"
|
||||
@load="checkFormForUpdate('range')"
|
||||
></b-input>
|
||||
</b-row>
|
||||
<b-row class="m-4">
|
||||
@ -83,6 +85,7 @@
|
||||
v-model="text"
|
||||
:state="text.length >= 10"
|
||||
placeholder="Mindestens 10 Zeichen eingeben"
|
||||
@load="checkFormForUpdate('text')"
|
||||
rows="3"
|
||||
></b-form-textarea>
|
||||
</div>
|
||||
@ -96,6 +99,17 @@
|
||||
<b-col class="text-center">
|
||||
<div class="text-right">
|
||||
<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"
|
||||
variant="success"
|
||||
@click="submitCreation"
|
||||
@ -116,14 +130,23 @@ export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: false,
|
||||
},
|
||||
pagetype: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
item: {
|
||||
type: Object,
|
||||
required: false,
|
||||
},
|
||||
creation: {
|
||||
creationUserData: {
|
||||
type: Object,
|
||||
required: false,
|
||||
},
|
||||
creation: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
itemsMassCreation: {
|
||||
@ -138,9 +161,18 @@ export default {
|
||||
value: 0,
|
||||
rangeMin: 0,
|
||||
rangeMax: 1000,
|
||||
currentMonth: this.$moment().format('MMMM'),
|
||||
lastMonth: this.$moment().subtract(1, 'month').format('MMMM'),
|
||||
beforeLastMonth: this.$moment().subtract(2, 'month').format('MMMM'),
|
||||
currentMonth: {
|
||||
short: this.$moment().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,
|
||||
isdisabled: true,
|
||||
}
|
||||
@ -148,7 +180,7 @@ export default {
|
||||
methods: {
|
||||
// Auswählen eines Zeitraumes
|
||||
updateRadioSelected(name, index, openCreation) {
|
||||
// Wenn Massenschöpfung
|
||||
// Wenn Mehrfachschöpfung
|
||||
if (this.type === 'massCreation') {
|
||||
// An Creation.vue emitten und radioSelectedMass aktualisieren
|
||||
this.$emit('update-radio-selected', [name, index])
|
||||
@ -160,6 +192,19 @@ export default {
|
||||
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() {
|
||||
// Formular Prüfen ob ein Zeitraum ausgewählt wurde. Ansonsten abbrechen und Hinweis anzeigen
|
||||
if (this.radioSelected === '') {
|
||||
@ -178,9 +223,9 @@ export default {
|
||||
return alert('Bitte gib einen Text ein der länger als 10 Zeichen ist!')
|
||||
}
|
||||
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
|
||||
// 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')
|
||||
this.submitObj = [
|
||||
{
|
||||
@ -191,32 +236,44 @@ export default {
|
||||
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
|
||||
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')
|
||||
}
|
||||
|
||||
if (this.type === 'singleCreation') {
|
||||
// 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
|
||||
this.submitObj = [
|
||||
{
|
||||
item: this.item,
|
||||
datum: this.radioSelected,
|
||||
datum: this.radioSelected.long,
|
||||
amount: this.value,
|
||||
text: this.text,
|
||||
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 + '')
|
||||
// $store - offene Schöpfungen hochzählen
|
||||
this.$store.commit('openCreationsPlus', 1)
|
||||
|
||||
if (this.pagetype === 'PageCreationConfirm') {
|
||||
// hinweis das eine ein einzelne Schöpfung abgesendet wird an (email)
|
||||
alert('UPDATE EINZEL SCHÖPFUNG ABSENDEN FÜR >> ')
|
||||
// umschreiben, update eine bestehende Schöpfung eine
|
||||
this.$emit('update-creation-data', {
|
||||
datum: this.radioSelected.long,
|
||||
creationGdd: this.value,
|
||||
text: this.text,
|
||||
})
|
||||
} else {
|
||||
// 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
|
||||
|
||||
@ -15,7 +15,7 @@ describe('NavBar', () => {
|
||||
let wrapper
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(NavBar, { localVue, mocks })
|
||||
return mount(NavBar, { mocks, localVue })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
<b-collapse id="nav-collapse" is-nav>
|
||||
<b-navbar-nav>
|
||||
<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
|
||||
v-show="$store.state.openCreations > 0"
|
||||
class="h5 bg-danger"
|
||||
|
||||
@ -10,7 +10,7 @@ describe('UserTable', () => {
|
||||
type: 'Type',
|
||||
itemsUser: [],
|
||||
fieldsTable: [],
|
||||
creation: {},
|
||||
creation: [],
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
|
||||
@ -37,7 +37,12 @@
|
||||
stacked="md"
|
||||
>
|
||||
<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-else icon="pencil-square" aria-label="Help"></b-icon>
|
||||
</b-button>
|
||||
@ -46,7 +51,7 @@
|
||||
<template #cell(show_details)="row">
|
||||
<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-else icon="eye-slash-fill" aria-label="Help"></b-icon>
|
||||
<b-icon v-else icon="eye-fill" aria-label="Help"></b-icon>
|
||||
</b-button>
|
||||
</template>
|
||||
|
||||
@ -58,8 +63,11 @@
|
||||
|
||||
<creation-formular
|
||||
type="singleCreation"
|
||||
:creation="getCreationInMonths(row.item.creation)"
|
||||
:pagetype="type"
|
||||
:creation="row.item.creation"
|
||||
:item="row.item"
|
||||
:creationUserData="creationData"
|
||||
@update-creation-data="updateCreationData"
|
||||
/>
|
||||
|
||||
<b-button size="sm" @click="row.toggleDetails">
|
||||
@ -67,7 +75,7 @@
|
||||
:icon="type === 'PageCreationConfirm' ? 'x' : 'eye-slash-fill'"
|
||||
aria-label="Help"
|
||||
></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-card>
|
||||
</template>
|
||||
@ -132,7 +140,7 @@ export default {
|
||||
default: '',
|
||||
},
|
||||
creation: {
|
||||
type: Object,
|
||||
type: Array,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
@ -141,6 +149,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
creationData: {},
|
||||
overlay: false,
|
||||
overlayBookmarkType: '',
|
||||
overlayItem: [],
|
||||
@ -200,16 +209,28 @@ export default {
|
||||
}
|
||||
|
||||
if (this.type === 'PageCreationConfirm') {
|
||||
this.$emit('update-confirm-result', item, 'remove')
|
||||
this.$emit('remove-confirm-result', item, 'remove')
|
||||
}
|
||||
},
|
||||
bookmarkConfirm(item) {
|
||||
alert('die schöpfung bestätigen und abschließen')
|
||||
alert(JSON.stringify(item))
|
||||
this.$emit('update-confirm-result', item, 'remove')
|
||||
this.$emit('remove-confirm-result', item, 'remove')
|
||||
},
|
||||
getCreationInMonths(creation) {
|
||||
return creation.split(',')
|
||||
editCreationUserTable(row, rowItem) {
|
||||
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 App from './App.vue'
|
||||
|
||||
// without this async calls are not working
|
||||
import 'regenerator-runtime'
|
||||
|
||||
import store from './store/store'
|
||||
|
||||
import router from './router/router'
|
||||
@ -22,7 +25,8 @@ import moment from 'vue-moment'
|
||||
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
|
||||
|
||||
const authLink = new ApolloLink((operation, forward) => {
|
||||
const token = '' // store.state.token
|
||||
const token = store.state.token
|
||||
|
||||
operation.setContext({
|
||||
headers: {
|
||||
Authorization: token && token.length > 0 ? `Bearer ${token}` : '',
|
||||
@ -52,10 +56,13 @@ const apolloProvider = new VueApollo({
|
||||
})
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
Vue.use(IconsPlugin)
|
||||
|
||||
Vue.use(moment)
|
||||
|
||||
Vue.use(VueApollo)
|
||||
|
||||
addNavigationGuards(router, store)
|
||||
|
||||
new Vue({
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="creation">
|
||||
<b-row>
|
||||
<b-col cols="12" lg="5">
|
||||
<label>Usersuche</label>
|
||||
@ -10,6 +10,7 @@
|
||||
placeholder="User suche"
|
||||
></b-input>
|
||||
<user-table
|
||||
v-if="itemsList.length > 0"
|
||||
type="UserListSearch"
|
||||
:itemsUser="itemsList"
|
||||
:fieldsTable="Searchfields"
|
||||
@ -20,7 +21,7 @@
|
||||
</b-col>
|
||||
<b-col cols="12" lg="7" class="shadow p-3 mb-5 rounded bg-info">
|
||||
<user-table
|
||||
v-show="Object.keys(this.massCreation).length > 0"
|
||||
v-if="massCreation.length > 0"
|
||||
class="shadow p-3 mb-5 bg-white rounded"
|
||||
type="UserListMassCreation"
|
||||
:itemsUser="massCreation"
|
||||
@ -31,6 +32,7 @@
|
||||
/>
|
||||
|
||||
<creation-formular
|
||||
v-if="massCreation.length > 0"
|
||||
type="massCreation"
|
||||
:creation="creation"
|
||||
:itemsMassCreation="massCreation"
|
||||
@ -44,9 +46,10 @@
|
||||
<script>
|
||||
import CreationFormular from '../components/CreationFormular.vue'
|
||||
import UserTable from '../components/UserTable.vue'
|
||||
import { searchUsers } from '../graphql/searchUsers'
|
||||
|
||||
export default {
|
||||
name: 'overview',
|
||||
name: 'Creation',
|
||||
components: {
|
||||
CreationFormular,
|
||||
UserTable,
|
||||
@ -56,60 +59,49 @@ export default {
|
||||
showArrays: false,
|
||||
Searchfields: [
|
||||
{ key: 'bookmark', label: 'merken' },
|
||||
|
||||
{ key: 'first_name', label: 'Firstname' },
|
||||
{ key: 'last_name', label: 'Lastname' },
|
||||
{ key: 'firstName', label: 'Firstname' },
|
||||
{ key: 'lastName', label: 'Lastname' },
|
||||
{ key: 'creation', label: 'Creation' },
|
||||
{ key: 'email', label: 'Email' },
|
||||
],
|
||||
fields: [
|
||||
{ key: 'email', label: 'Email' },
|
||||
{ key: 'first_name', label: 'Firstname' },
|
||||
{ key: 'last_name', label: 'Lastname' },
|
||||
{ key: 'firstName', label: 'Firstname' },
|
||||
{ key: 'lastName', label: 'Lastname' },
|
||||
{ key: 'creation', label: 'Creation' },
|
||||
{ key: 'bookmark', label: 'löschen' },
|
||||
],
|
||||
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',
|
||||
},
|
||||
],
|
||||
itemsList: this.searchResult,
|
||||
itemsList: [],
|
||||
massCreation: [],
|
||||
radioSelectedMass: '',
|
||||
criteria: '',
|
||||
creation: [null, null, null],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.itemsList = this.searchResult
|
||||
async created() {
|
||||
await this.getUsers()
|
||||
},
|
||||
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) {
|
||||
let index = 0
|
||||
let findArr = {}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="creation-confirm">
|
||||
<small class="bg-danger text-light p-1">
|
||||
Die anzahl der offene Schöpfungen stimmen nicht! Diese wird bei absenden im $store
|
||||
hochgezählt. Die Liste die hier angezeigt wird ist SIMULIERT!
|
||||
@ -8,9 +8,8 @@
|
||||
class="mt-4"
|
||||
type="PageCreationConfirm"
|
||||
:itemsUser="confirmResult"
|
||||
:creation="creation"
|
||||
:fieldsTable="fields"
|
||||
@update-confirm-result="updateConfirmResult"
|
||||
@remove-confirm-result="removeConfirmResult"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@ -18,7 +17,7 @@
|
||||
import UserTable from '../components/UserTable.vue'
|
||||
|
||||
export default {
|
||||
name: 'creation_confirm',
|
||||
name: 'CreationConfirm',
|
||||
components: {
|
||||
UserTable,
|
||||
},
|
||||
@ -28,11 +27,23 @@ export default {
|
||||
fields: [
|
||||
{ key: 'bookmark', label: 'löschen' },
|
||||
{ key: 'email', label: 'Email' },
|
||||
{ key: 'first_name', label: 'Vorname' },
|
||||
{ key: 'last_name', label: 'Nachname' },
|
||||
{ key: 'creation_gdd', label: 'GDD' },
|
||||
{ key: 'firstName', label: 'Vorname' },
|
||||
{ key: 'lastName', label: 'Nachname' },
|
||||
{
|
||||
key: 'creation_gdd',
|
||||
label: 'Schöpfung',
|
||||
formatter: (value) => {
|
||||
return value + ' GDD'
|
||||
},
|
||||
},
|
||||
{ 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: 'edit_creation', label: 'ändern' },
|
||||
{ key: 'confirm', label: 'speichern' },
|
||||
@ -41,67 +52,81 @@ export default {
|
||||
{
|
||||
id: 1,
|
||||
email: 'dickerson@web.de',
|
||||
first_name: 'Dickerson',
|
||||
last_name: 'Macdonald',
|
||||
creation: '450,200,700',
|
||||
firstName: 'Dickerson',
|
||||
lastName: 'Macdonald',
|
||||
creation: '[450,200,700]',
|
||||
creation_gdd: '1000',
|
||||
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam ',
|
||||
|
||||
creation_date: '01/11/2021',
|
||||
creation_date: {
|
||||
short: 'November',
|
||||
long: '22/11/2021',
|
||||
},
|
||||
creation_moderator: 'Manuela Gast',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
email: 'larsen@woob.de',
|
||||
first_name: 'Larsen',
|
||||
last_name: 'Shaw',
|
||||
creation: '300,200,1000',
|
||||
firstName: 'Larsen',
|
||||
lastName: 'Shaw',
|
||||
creation: '[300,200,1000]',
|
||||
creation_gdd: '1000',
|
||||
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam ',
|
||||
|
||||
creation_date: '01/11/2021',
|
||||
creation_date: {
|
||||
short: 'November',
|
||||
long: '03/11/2021',
|
||||
},
|
||||
creation_moderator: 'Manuela Gast',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
email: 'geneva@tete.de',
|
||||
first_name: 'Geneva',
|
||||
last_name: 'Wilson',
|
||||
creation: '350,200,900',
|
||||
firstName: 'Geneva',
|
||||
lastName: 'Wilson',
|
||||
creation: '[350,200,900]',
|
||||
creation_gdd: '1000',
|
||||
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam',
|
||||
creation_date: '01/11/2021',
|
||||
creation_date: {
|
||||
short: 'September',
|
||||
long: '27/09/2021',
|
||||
},
|
||||
creation_moderator: 'Manuela Gast',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
email: 'viewrter@asdfvb.com',
|
||||
first_name: 'Soledare',
|
||||
last_name: 'Takker',
|
||||
firstName: 'Soledare',
|
||||
lastName: 'Takker',
|
||||
creation: '[100,400,800]',
|
||||
creation_gdd: '500',
|
||||
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo ',
|
||||
|
||||
creation_date: '01/10/2021',
|
||||
creation_date: {
|
||||
short: 'Oktober',
|
||||
long: '12/10/2021',
|
||||
},
|
||||
creation_moderator: 'Evelyn Roller',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
email: 'dickerson@web.de',
|
||||
first_name: 'Dickerson',
|
||||
last_name: 'Macdonald',
|
||||
creation: '100,400,800',
|
||||
firstName: 'Dickerson',
|
||||
lastName: 'Macdonald',
|
||||
creation: '[100,400,800]',
|
||||
creation_gdd: '200',
|
||||
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At',
|
||||
creation_date: '01/09/2021',
|
||||
creation_date: {
|
||||
short: 'September',
|
||||
long: '05/09/2021',
|
||||
},
|
||||
creation_moderator: 'Manuela Gast',
|
||||
},
|
||||
],
|
||||
creation: [null, null, null],
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateConfirmResult(e, event) {
|
||||
removeConfirmResult(e, event) {
|
||||
if (event === 'remove') {
|
||||
let index = 0
|
||||
let findArr = {}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="user-search">
|
||||
<label>Usersuche</label>
|
||||
<b-input
|
||||
type="text"
|
||||
v-model="criteria"
|
||||
class="shadow p-3 mb-5 bg-white rounded"
|
||||
placeholder="User suche"
|
||||
@input="getUsers"
|
||||
></b-input>
|
||||
<user-table
|
||||
type="PageUserSearch"
|
||||
@ -17,9 +18,10 @@
|
||||
</template>
|
||||
<script>
|
||||
import UserTable from '../components/UserTable.vue'
|
||||
import { searchUsers } from '../graphql/searchUsers'
|
||||
|
||||
export default {
|
||||
name: 'overview',
|
||||
name: 'UserSearch',
|
||||
components: {
|
||||
UserTable,
|
||||
},
|
||||
@ -28,46 +30,41 @@ export default {
|
||||
showArrays: false,
|
||||
fields: [
|
||||
{ key: 'email', label: 'Email' },
|
||||
{ key: 'first_name', label: 'Firstname' },
|
||||
{ key: 'last_name', label: 'Lastname' },
|
||||
{ key: 'firstName', label: 'Firstname' },
|
||||
{ key: 'lastName', label: 'Lastname' },
|
||||
{ key: 'creation', label: 'Creation' },
|
||||
{ key: 'show_details', label: 'Details' },
|
||||
],
|
||||
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',
|
||||
},
|
||||
],
|
||||
searchResult: [],
|
||||
massCreation: [],
|
||||
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>
|
||||
|
||||
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 = {
|
||||
openCreationsPlus: (state, i) => {
|
||||
state.openCreations = state.openCreations + i
|
||||
state.openCreations += i
|
||||
},
|
||||
openCreationsMinus: (state, i) => {
|
||||
state.openCreations = state.openCreations - i
|
||||
state.openCreations -= i
|
||||
},
|
||||
resetOpenCreations: (state) => {
|
||||
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 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.use(BootstrapVue)
|
||||
global.localVue.use(IconsPlugin)
|
||||
|
||||
// throw errors for vue warnings to force the programmers to take care about warnings
|
||||
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"
|
||||
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
|
||||
|
||||
regenerator-runtime@^0.13.4:
|
||||
regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.9:
|
||||
version "0.13.9"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
|
||||
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
|
||||
|
||||
@ -30,4 +30,6 @@ COMMUNITY_URL=
|
||||
COMMUNITY_REGISTER_URL=
|
||||
COMMUNITY_DESCRIPTION=
|
||||
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-testing": "^2.25.2",
|
||||
"axios": "^0.21.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"class-validator": "^0.13.1",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^10.0.0",
|
||||
|
||||
@ -51,14 +51,25 @@ const email = {
|
||||
EMAIL_PASSWORD: process.env.EMAIL_PASSWORD || 'xxx',
|
||||
EMAIL_SMTP_URL: process.env.EMAIL_SMTP_URL || 'gmail.com',
|
||||
EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '587',
|
||||
|
||||
EMAIL_LINK_VERIFICATION:
|
||||
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
|
||||
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
|
||||
|
||||
@ -12,10 +12,7 @@ export default class CreateUserArgs {
|
||||
lastName: string
|
||||
|
||||
@Field(() => String)
|
||||
password: string
|
||||
|
||||
@Field(() => String)
|
||||
language: string
|
||||
language?: string // Will default to DEFAULT_LANGUAGE
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
publisherId: number
|
||||
|
||||
@ -2,9 +2,6 @@
|
||||
|
||||
import { AuthChecker } from 'type-graphql'
|
||||
|
||||
import CONFIG from '../../config'
|
||||
import { apiGet } from '../../apis/HttpRequest'
|
||||
|
||||
import decode from '../../jwt/decode'
|
||||
import encode from '../../jwt/encode'
|
||||
|
||||
@ -13,7 +10,7 @@ const isAuthorized: AuthChecker<any> = async (
|
||||
) => {
|
||||
if (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) })
|
||||
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()
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
throw e
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
// 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
|
||||
// index for the transaction which requires a chain without gaps
|
||||
@ -627,6 +624,9 @@ export class TransactionResolver {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('problems with reset auto increment: %o', error)
|
||||
})
|
||||
throw e
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
// send notification email
|
||||
// TODO: translate
|
||||
|
||||
@ -22,14 +22,14 @@ import {
|
||||
} from '../../middleware/klicktippMiddleware'
|
||||
import { CheckEmailResponse } from '../model/CheckEmailResponse'
|
||||
import { UserSettingRepository } from '../../typeorm/repository/UserSettingRepository'
|
||||
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
||||
import { Setting } from '../enum/Setting'
|
||||
import { UserRepository } from '../../typeorm/repository/User'
|
||||
import { LoginUser } from '@entity/LoginUser'
|
||||
import { LoginElopageBuys } from '@entity/LoginElopageBuys'
|
||||
import { LoginUserBackup } from '@entity/LoginUserBackup'
|
||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||
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
|
||||
const sodium = require('sodium-native')
|
||||
@ -231,33 +231,33 @@ export class UserResolver {
|
||||
@Ctx() context: any,
|
||||
): Promise<User> {
|
||||
email = email.trim().toLowerCase()
|
||||
const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
|
||||
|
||||
// if there is no user, throw an authentication error
|
||||
if (!result.success) {
|
||||
throw new Error(result.data)
|
||||
}
|
||||
|
||||
context.setHeaders.push({
|
||||
key: 'token',
|
||||
value: encode(result.data.user.public_hex),
|
||||
// const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
|
||||
// UnsecureLogin
|
||||
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||
const loginUser = await loginUserRepository.findByEmail(email).catch(() => {
|
||||
throw new Error('No user with this credentials')
|
||||
})
|
||||
const user = new User(result.data.user)
|
||||
// Hack: Database Field is not validated properly and not nullable
|
||||
if (user.publisherId === 0) {
|
||||
user.publisherId = undefined
|
||||
if (!loginUser.emailChecked) throw new Error('user email not validated')
|
||||
const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
||||
const loginUserPassword = BigInt(loginUser.password.toString())
|
||||
if (loginUserPassword !== passwordHash[0].readBigUInt64LE()) {
|
||||
throw new Error('No user with this credentials')
|
||||
}
|
||||
user.hasElopage = result.data.hasElopage
|
||||
// read additional settings from settings table
|
||||
// TODO: If user has no pubKey Create it again and update user.
|
||||
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
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.firstName = user.firstName
|
||||
userEntity.lastName = user.lastName
|
||||
userEntity.username = user.username
|
||||
userEntity.email = user.email
|
||||
userEntity.pubkey = Buffer.from(user.pubkey, 'hex')
|
||||
userEntity.firstName = loginUser.firstName
|
||||
userEntity.lastName = loginUser.lastName
|
||||
userEntity.username = loginUser.username
|
||||
userEntity.email = loginUser.email
|
||||
userEntity.pubkey = loginUser.pubKey
|
||||
|
||||
userRepository.save(userEntity).catch(() => {
|
||||
throw new Error('error by save userEntity')
|
||||
@ -267,16 +267,28 @@ export class UserResolver {
|
||||
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) {
|
||||
user.publisherId = publisherId
|
||||
|
||||
// TODO: Check if we can use updateUserInfos
|
||||
// await this.updateUserInfos({ publisherId }, { pubKey: loginUser.pubKey })
|
||||
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||
const loginUser = await loginUserRepository.findOneOrFail({ email: userEntity.email })
|
||||
loginUser.publisherId = publisherId
|
||||
loginUserRepository.save(loginUser)
|
||||
}
|
||||
|
||||
// coinAnimation
|
||||
const userSettingRepository = getCustomRepository(UserSettingRepository)
|
||||
const coinanimation = await userSettingRepository
|
||||
.readBoolean(userEntity.id, Setting.COIN_ANIMATION)
|
||||
@ -285,6 +297,12 @@ export class UserResolver {
|
||||
})
|
||||
user.coinanimation = coinanimation
|
||||
user.isAdmin = true // TODO implement
|
||||
|
||||
context.setHeaders.push({
|
||||
key: 'token',
|
||||
value: encode(loginUser.pubKey),
|
||||
})
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
@ -316,22 +334,23 @@ export class UserResolver {
|
||||
|
||||
@Mutation(() => String)
|
||||
async createUser(
|
||||
@Args() { email, firstName, lastName, password, language, publisherId }: CreateUserArgs,
|
||||
@Args() { email, firstName, lastName, language, publisherId }: CreateUserArgs,
|
||||
): Promise<string> {
|
||||
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
|
||||
// default int publisher_id = 0;
|
||||
|
||||
// Validate Language (no throw)
|
||||
if (!isLanguage(language)) {
|
||||
if (!language || !isLanguage(language)) {
|
||||
language = DEFAULT_LANGUAGE
|
||||
}
|
||||
|
||||
// TODO: Register process
|
||||
// Validate Password
|
||||
if (!isPassword(password)) {
|
||||
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!',
|
||||
)
|
||||
}
|
||||
// if (!isPassword(password)) {
|
||||
// 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!',
|
||||
// )
|
||||
// }
|
||||
|
||||
// Validate username
|
||||
// TODO: never true
|
||||
@ -349,11 +368,13 @@ export class UserResolver {
|
||||
throw new Error(`User already exists.`)
|
||||
}
|
||||
|
||||
const passphrase = PassphraseGenerate()
|
||||
const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
||||
const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
||||
// TODO: Register process
|
||||
// const passphrase = PassphraseGenerate()
|
||||
// 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 encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
||||
|
||||
// Table: login_users
|
||||
const loginUser = new LoginUser()
|
||||
@ -362,13 +383,15 @@ export class UserResolver {
|
||||
loginUser.lastName = lastName
|
||||
loginUser.username = username
|
||||
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.language = language
|
||||
loginUser.groupId = 1
|
||||
loginUser.publisherId = publisherId
|
||||
loginUser.pubKey = keyPair[0]
|
||||
loginUser.privKey = encryptedPrivkey
|
||||
// TODO: Register process
|
||||
// loginUser.pubKey = keyPair[0]
|
||||
// loginUser.privKey = encryptedPrivkey
|
||||
|
||||
const queryRunner = getConnection().createQueryRunner()
|
||||
await queryRunner.connect()
|
||||
@ -380,21 +403,24 @@ export class UserResolver {
|
||||
throw new Error('insert user failed')
|
||||
})
|
||||
|
||||
// TODO: Register process
|
||||
// Table: login_user_backups
|
||||
const loginUserBackup = new LoginUserBackup()
|
||||
loginUserBackup.userId = loginUserId
|
||||
loginUserBackup.passphrase = passphrase.join(' ') + ' ' // login server saves trailing space
|
||||
loginUserBackup.mnemonicType = 2 // ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER;
|
||||
// const loginUserBackup = new LoginUserBackup()
|
||||
// loginUserBackup.userId = loginUserId
|
||||
// loginUserBackup.passphrase = passphrase.join(' ') + ' ' // login server saves trailing space
|
||||
// loginUserBackup.mnemonicType = 2 // ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER;
|
||||
|
||||
await queryRunner.manager.save(loginUserBackup).catch((error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('insert LoginUserBackup failed', error)
|
||||
throw new Error('insert user backup failed')
|
||||
})
|
||||
// TODO: Register process
|
||||
// await queryRunner.manager.save(loginUserBackup).catch((error) => {
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.log('insert LoginUserBackup failed', error)
|
||||
// throw new Error('insert user backup failed')
|
||||
// })
|
||||
|
||||
// Table: state_users
|
||||
const dbUser = new DbUser()
|
||||
dbUser.pubkey = keyPair[0]
|
||||
// TODO: Register process
|
||||
// dbUser.pubkey = keyPair[0]
|
||||
dbUser.email = email
|
||||
dbUser.firstName = firstName
|
||||
dbUser.lastName = lastName
|
||||
@ -568,7 +594,7 @@ export class UserResolver {
|
||||
await queryRunner.startTransaction('READ UNCOMMITTED')
|
||||
|
||||
try {
|
||||
if (coinanimation) {
|
||||
if (coinanimation !== null && coinanimation !== undefined) {
|
||||
queryRunner.manager
|
||||
.getCustomRepository(UserSettingRepository)
|
||||
.setOrUpdate(userEntity.id, Setting.COIN_ANIMATION, coinanimation.toString())
|
||||
@ -640,7 +666,8 @@ export class UserResolver {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import 'module-alias/register'
|
||||
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import express from 'express'
|
||||
import bodyParser from 'body-parser'
|
||||
|
||||
// database
|
||||
import connection from '../typeorm/connection'
|
||||
@ -22,6 +23,9 @@ import CONFIG from '../config'
|
||||
// graphql
|
||||
import schema from '../graphql/schema'
|
||||
|
||||
// webhooks
|
||||
import { elopageWebhook } from '../webhook/elopage'
|
||||
|
||||
// TODO implement
|
||||
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
|
||||
|
||||
@ -50,6 +54,12 @@ const createServer = async (context: any = serverContext): Promise<any> => {
|
||||
// cors
|
||||
app.use(cors)
|
||||
|
||||
// bodyparser
|
||||
app.use(bodyParser.json())
|
||||
|
||||
// Elopage Webhook
|
||||
app.post('/hook/elopage/' + CONFIG.WEBHOOK_ELOPAGE_SECRET, elopageWebhook)
|
||||
|
||||
// Apollo Server
|
||||
const apollo = new ApolloServer({
|
||||
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'
|
||||
|
||||
@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()
|
||||
}
|
||||
|
||||
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[]> {
|
||||
if (!userIds.length) return []
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
||||
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
|
||||
build:
|
||||
target: development
|
||||
networks:
|
||||
- external-net
|
||||
environment:
|
||||
- NODE_ENV="development"
|
||||
# - DEBUG=true
|
||||
@ -27,8 +25,6 @@ services:
|
||||
image: gradido/admin:development
|
||||
build:
|
||||
target: development
|
||||
networks:
|
||||
- external-net
|
||||
environment:
|
||||
- NODE_ENV="development"
|
||||
# - DEBUG=true
|
||||
|
||||
@ -15,6 +15,7 @@ services:
|
||||
context: ./frontend
|
||||
target: production
|
||||
networks:
|
||||
- external-net
|
||||
- internal-net
|
||||
ports:
|
||||
- 3000:3000
|
||||
@ -39,6 +40,7 @@ services:
|
||||
context: ./admin
|
||||
target: production
|
||||
networks:
|
||||
- external-net
|
||||
- internal-net
|
||||
ports:
|
||||
- 8080:8080
|
||||
|
||||
@ -47,6 +47,7 @@
|
||||
"change-password": "Fehler beim Ändern des Passworts",
|
||||
"error": "Fehler",
|
||||
"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!"
|
||||
},
|
||||
"form": {
|
||||
@ -181,9 +182,12 @@
|
||||
"uppercase": "Ein Großbuchstabe erforderlich."
|
||||
},
|
||||
"thx": {
|
||||
"activateEmail": "Deine Email wurde noch nicht aktiviert, bitte überprüfe deine Email und Klicke den Aktivierungslink!",
|
||||
"checkEmail": "Deine Email würde erfolgreich verifiziert.",
|
||||
"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.",
|
||||
"title": "Danke!"
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@
|
||||
"change-password": "Error while changing password",
|
||||
"error": "Error",
|
||||
"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"
|
||||
},
|
||||
"form": {
|
||||
@ -181,9 +182,12 @@
|
||||
"uppercase": "One uppercase letter required."
|
||||
},
|
||||
"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.",
|
||||
"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.",
|
||||
"title": "Thank you!"
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ const routes = [
|
||||
path: '/thx/:comingFrom',
|
||||
component: () => import('../views/Pages/thx.vue'),
|
||||
beforeEnter: (to, from, next) => {
|
||||
const validFrom = ['password', 'reset', 'register']
|
||||
const validFrom = ['password', 'reset', 'register', 'login']
|
||||
if (!validFrom.includes(from.path.split('/')[1])) {
|
||||
next({ path: '/login' })
|
||||
} else {
|
||||
|
||||
@ -104,9 +104,14 @@ export default {
|
||||
this.$router.push('/overview')
|
||||
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()
|
||||
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 LanguageSwitchSelect from '../../components/LanguageSwitchSelect.vue'
|
||||
import { registerUser } from '../../graphql/mutations'
|
||||
import { localeChanged } from 'vee-validate'
|
||||
import { getCommunityInfoMixin } from '../../mixins/getCommunityInfo'
|
||||
|
||||
export default {
|
||||
@ -189,6 +190,9 @@ export default {
|
||||
methods: {
|
||||
updateLanguage(e) {
|
||||
this.language = e
|
||||
this.$store.commit('language', this.language)
|
||||
this.$i18n.locale = this.language
|
||||
localeChanged(this.language)
|
||||
},
|
||||
getValidationState({ dirty, validated, valid = null }) {
|
||||
return dirty || validated ? valid : null
|
||||
|
||||
@ -4,10 +4,12 @@
|
||||
<div class="header py-7 py-lg-8 pt-lg-9">
|
||||
<b-container>
|
||||
<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>
|
||||
<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>
|
||||
</b-container>
|
||||
</div>
|
||||
@ -17,25 +19,33 @@
|
||||
<script>
|
||||
const textFields = {
|
||||
password: {
|
||||
headline: 'site.thx.title',
|
||||
subtitle: 'site.thx.email',
|
||||
button: 'login',
|
||||
linkTo: '/login',
|
||||
},
|
||||
reset: {
|
||||
headline: 'site.thx.title',
|
||||
subtitle: 'site.thx.reset',
|
||||
button: 'login',
|
||||
linkTo: '/login',
|
||||
},
|
||||
register: {
|
||||
headline: 'site.thx.title',
|
||||
subtitle: 'site.thx.register',
|
||||
button: 'site.login.signin',
|
||||
linkTo: '/overview',
|
||||
},
|
||||
checkEmail: {
|
||||
headline: 'site.thx.title',
|
||||
subtitle: 'site.thx.checkEmail',
|
||||
button: 'login',
|
||||
linkTo: '/login',
|
||||
},
|
||||
login: {
|
||||
headline: 'site.thx.errorTitle',
|
||||
subtitle: 'site.thx.activateEmail',
|
||||
},
|
||||
}
|
||||
|
||||
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");
|
||||
case USER_NO_PRIVATE_KEY:
|
||||
case USER_COMPLETE:
|
||||
case USER_EMAIL_NOT_ACTIVATED:
|
||||
result->set("state", "success");
|
||||
result->set("user", session->getNewUser()->getJson());
|
||||
result->set("session_id", session->getHandle());
|
||||
@ -158,6 +157,10 @@ Poco::JSON::Object* JsonUnsecureLogin::handle(Poco::Dynamic::Var params)
|
||||
AWAIT(hasElopageTask)
|
||||
result->set("hasElopage", hasElopageTask->hasElopage());
|
||||
return result;
|
||||
case USER_EMAIL_NOT_ACTIVATED:
|
||||
result->set("state", "processing");
|
||||
result->set("msg", "user email not validated");
|
||||
break;
|
||||
default:
|
||||
result->set("state", "error");
|
||||
result->set("msg", "unknown user state");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user