mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into admin_pending_creation
This commit is contained in:
commit
2f92aec460
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: 51
|
||||
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 }}
|
||||
|
||||
##############################################################################
|
||||
|
||||
3
admin/.env.dist
Normal file
3
admin/.env.dist
Normal file
@ -0,0 +1,3 @@
|
||||
GRAPHQL_URI=http://localhost:4000/graphql
|
||||
WALLET_AUTH_URL=http://localhost/vue/authenticate?token=$1
|
||||
DEBUG_DISABLE_AUTH=false
|
||||
@ -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',
|
||||
|
||||
@ -1,43 +1,28 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import App from './App'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const storeCommitMock = jest.fn()
|
||||
const stubs = {
|
||||
RouterView: true,
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
$store: {
|
||||
commit: storeCommitMock,
|
||||
state: {
|
||||
token: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const localStorageMock = (() => {
|
||||
let store = {}
|
||||
|
||||
return {
|
||||
getItem: (key) => {
|
||||
return store[key] || null
|
||||
},
|
||||
setItem: (key, value) => {
|
||||
store[key] = value.toString()
|
||||
},
|
||||
removeItem: (key) => {
|
||||
delete store[key]
|
||||
},
|
||||
clear: () => {
|
||||
store = {}
|
||||
},
|
||||
}
|
||||
})()
|
||||
|
||||
describe('App', () => {
|
||||
let wrapper
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(App, { localVue, mocks })
|
||||
return shallowMount(App, { localVue, stubs, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
describe('shallowMount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
@ -46,23 +31,4 @@ describe('App', () => {
|
||||
expect(wrapper.find('div#app').exists()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('window localStorage is undefined', () => {
|
||||
it('does not commit a token to the store', () => {
|
||||
expect(storeCommitMock).not.toBeCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('with token in local storage', () => {
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: localStorageMock,
|
||||
})
|
||||
window.localStorage.setItem('vuex', JSON.stringify({ token: 1234 }))
|
||||
})
|
||||
|
||||
it.skip('commits the token to the store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('token', 1234)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,19 +1,15 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<nav-bar class="wrapper-nav" />
|
||||
<router-view class="wrapper p-3"></router-view>
|
||||
<foo-ter />
|
||||
<default-layout v-if="$store.state.token" />
|
||||
<router-view v-else></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavBar from '@/components/NavBar.vue'
|
||||
import FooTer from '@/components/Footer.vue'
|
||||
import defaultLayout from '@/layouts/defaultLayout.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
NavBar,
|
||||
FooTer,
|
||||
},
|
||||
name: 'app',
|
||||
components: { defaultLayout },
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -146,7 +146,7 @@ export default {
|
||||
required: false,
|
||||
},
|
||||
creation: {
|
||||
type: Object,
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
itemsMassCreation: {
|
||||
@ -198,9 +198,10 @@ export default {
|
||||
this.text = this.creationUserData.text
|
||||
break
|
||||
case 'range':
|
||||
this.value = this.creationUserData.creation_gdd
|
||||
this.value = this.creationUserData.creationGdd
|
||||
break
|
||||
default:
|
||||
// TODO: Toast
|
||||
alert("I don't know such values")
|
||||
}
|
||||
},
|
||||
@ -262,9 +263,11 @@ export default {
|
||||
// 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.creationUserData.datum = this.radioSelected.long
|
||||
this.creationUserData.creation_gdd = this.value
|
||||
this.creationUserData.text = this.text
|
||||
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 + '')
|
||||
|
||||
@ -16,15 +16,43 @@
|
||||
>
|
||||
| {{ $store.state.openCreations }} offene Schöpfungen
|
||||
</b-nav-item>
|
||||
<b-nav-item @click="wallet">Wallet</b-nav-item>
|
||||
<b-nav-item @click="logout">Logout</b-nav-item>
|
||||
<!-- <b-nav-item v-show="open < 1" to="/creation-confirm">| keine offene Schöpfungen</b-nav-item> -->
|
||||
</b-navbar-nav>
|
||||
</b-collapse>
|
||||
<b-navbar-brand href="http://localhost:3000/vue/login">Profilbereich</b-navbar-brand>
|
||||
</b-navbar>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import CONFIG from '../config'
|
||||
|
||||
export default {
|
||||
name: 'navbar',
|
||||
methods: {
|
||||
logout() {
|
||||
// TODO
|
||||
// this.$emit('logout')
|
||||
/* this.$apollo
|
||||
.query({
|
||||
query: logout,
|
||||
})
|
||||
.then(() => {
|
||||
this.$store.dispatch('logout')
|
||||
this.$router.push('/logout')
|
||||
})
|
||||
.catch(() => {
|
||||
this.$store.dispatch('logout')
|
||||
if (this.$router.currentRoute.path !== '/logout') this.$router.push('/logout')
|
||||
})
|
||||
*/
|
||||
this.$store.dispatch('logout')
|
||||
this.$router.push('/logout')
|
||||
},
|
||||
wallet() {
|
||||
window.location = CONFIG.WALLET_AUTH_URL.replace('$1', this.$store.state.token)
|
||||
this.$store.dispatch('logout') // logout without redirect
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -10,7 +10,7 @@ describe('UserTable', () => {
|
||||
type: 'Type',
|
||||
itemsUser: [],
|
||||
fieldsTable: [],
|
||||
creation: {},
|
||||
creation: [],
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
|
||||
@ -67,6 +67,7 @@
|
||||
:creation="row.item.creation"
|
||||
:item="row.item"
|
||||
:creationUserData="creationData"
|
||||
@update-creation-data="updateCreationData"
|
||||
/>
|
||||
|
||||
<b-button size="sm" @click="row.toggleDetails">
|
||||
@ -139,7 +140,7 @@ export default {
|
||||
default: '',
|
||||
},
|
||||
creation: {
|
||||
type: Object,
|
||||
type: Array,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
@ -226,6 +227,11 @@ export default {
|
||||
}
|
||||
row.toggleDetails()
|
||||
},
|
||||
updateCreationData(data) {
|
||||
this.creationData = {
|
||||
...data,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -17,8 +17,13 @@ const environment = {
|
||||
PRODUCTION: process.env.NODE_ENV === 'production' || false,
|
||||
}
|
||||
|
||||
const server = {
|
||||
const endpoints = {
|
||||
GRAPHQL_URI: process.env.GRAPHQL_URI || 'http://localhost:4000/graphql',
|
||||
WALLET_AUTH_URL: process.env.WALLET_AUTH_URL || 'http://localhost/vue/authenticate?token=$1',
|
||||
}
|
||||
|
||||
const debug = {
|
||||
DEBUG_DISABLE_AUTH: process.env.DEBUG_DISABLE_AUTH === 'true' || false,
|
||||
}
|
||||
|
||||
const options = {}
|
||||
@ -26,8 +31,9 @@ const options = {}
|
||||
const CONFIG = {
|
||||
...version,
|
||||
...environment,
|
||||
...server,
|
||||
...endpoints,
|
||||
...options,
|
||||
...debug,
|
||||
}
|
||||
|
||||
export default CONFIG
|
||||
|
||||
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(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
19
admin/src/layouts/defaultLayout.vue
Normal file
19
admin/src/layouts/defaultLayout.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<nav-bar class="wrapper-nav" />
|
||||
<router-view class="wrapper p-3"></router-view>
|
||||
<content-footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavBar from '@/components/NavBar.vue'
|
||||
import ContentFooter from '@/components/ContentFooter.vue'
|
||||
export default {
|
||||
name: 'defaultLayout',
|
||||
components: {
|
||||
NavBar,
|
||||
ContentFooter,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
59
admin/src/pages/Creation.spec.js
Normal file
59
admin/src/pages/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')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -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"
|
||||
@ -47,7 +49,7 @@ import UserTable from '../components/UserTable.vue'
|
||||
import { searchUsers } from '../graphql/searchUsers'
|
||||
|
||||
export default {
|
||||
name: 'overview',
|
||||
name: 'Creation',
|
||||
components: {
|
||||
CreationFormular,
|
||||
UserTable,
|
||||
@ -57,7 +59,6 @@ export default {
|
||||
showArrays: false,
|
||||
Searchfields: [
|
||||
{ key: 'bookmark', label: 'merken' },
|
||||
|
||||
{ key: 'firstName', label: 'Firstname' },
|
||||
{ key: 'lastName', label: 'Lastname' },
|
||||
{ key: 'creation', label: 'Creation' },
|
||||
@ -77,11 +78,11 @@ export default {
|
||||
creation: [null, null, null],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getUsers()
|
||||
async created() {
|
||||
await this.getUsers()
|
||||
},
|
||||
methods: {
|
||||
getUsers() {
|
||||
async getUsers() {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: searchUsers,
|
||||
@ -93,7 +94,7 @@ export default {
|
||||
this.itemsList = result.data.searchUsers.map((user) => {
|
||||
return {
|
||||
...user,
|
||||
// showDetails: true,
|
||||
showDetails: false,
|
||||
}
|
||||
})
|
||||
})
|
||||
53
admin/src/pages/CreationConfirm.spec.js
Normal file
53
admin/src/pages/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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -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!
|
||||
@ -17,7 +17,7 @@
|
||||
import UserTable from '../components/UserTable.vue'
|
||||
|
||||
export default {
|
||||
name: 'creation_confirm',
|
||||
name: 'CreationConfirm',
|
||||
components: {
|
||||
UserTable,
|
||||
},
|
||||
59
admin/src/pages/UserSearch.spec.js
Normal file
59
admin/src/pages/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,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="user-search">
|
||||
<label>Usersuche</label>
|
||||
<b-input
|
||||
type="text"
|
||||
@ -21,7 +21,7 @@ import UserTable from '../components/UserTable.vue'
|
||||
import { searchUsers } from '../graphql/searchUsers'
|
||||
|
||||
export default {
|
||||
name: 'overview',
|
||||
name: 'UserSearch',
|
||||
components: {
|
||||
UserTable,
|
||||
},
|
||||
@ -1,7 +1,25 @@
|
||||
import CONFIG from '../config'
|
||||
|
||||
const addNavigationGuards = (router, store) => {
|
||||
// store token on `authenticate`
|
||||
router.beforeEach((to, from, next) => {
|
||||
// handle authentication
|
||||
if (to.meta.requiresAuth && !store.state.token) {
|
||||
if (to.path === '/authenticate' && to.query && to.query.token) {
|
||||
// TODO verify user to get user data
|
||||
store.commit('token', to.query.token)
|
||||
next({ path: '/' })
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
// protect all routes but `not-found`
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (
|
||||
!CONFIG.DEBUG_DISABLE_AUTH && // we did not disabled the auth module for debug purposes
|
||||
!store.state.token && // we do not have a token
|
||||
to.path !== '/not-found' && // we are not on `not-found`
|
||||
to.path !== '/logout' // we are not on `logout`
|
||||
) {
|
||||
next({ path: '/not-found' })
|
||||
} else {
|
||||
next()
|
||||
|
||||
64
admin/src/router/guards.test.js
Normal file
64
admin/src/router/guards.test.js
Normal file
@ -0,0 +1,64 @@
|
||||
import addNavigationGuards from './guards'
|
||||
import router from './router'
|
||||
|
||||
const storeCommitMock = jest.fn()
|
||||
|
||||
const store = {
|
||||
commit: storeCommitMock,
|
||||
state: {
|
||||
token: null,
|
||||
},
|
||||
}
|
||||
|
||||
addNavigationGuards(router, store)
|
||||
|
||||
describe('navigation guards', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('authenticate', () => {
|
||||
const navGuard = router.beforeHooks[0]
|
||||
const next = jest.fn()
|
||||
|
||||
describe('with valid token', () => {
|
||||
it('commits the token to the store', async () => {
|
||||
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
|
||||
})
|
||||
|
||||
it('redirects to /', async () => {
|
||||
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||
expect(next).toBeCalledWith({ path: '/' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('without valid token', () => {
|
||||
it('does not commit the token to the store', async () => {
|
||||
navGuard({ path: '/authenticate' }, {}, next)
|
||||
expect(storeCommitMock).not.toBeCalledWith()
|
||||
})
|
||||
|
||||
it('calls next withou arguments', async () => {
|
||||
navGuard({ path: '/authenticate' }, {}, next)
|
||||
expect(next).toBeCalledWith()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('protect all routes', () => {
|
||||
const navGuard = router.beforeHooks[1]
|
||||
const next = jest.fn()
|
||||
|
||||
it('redirects no not found with no token in store ', () => {
|
||||
navGuard({ path: '/' }, {}, next)
|
||||
expect(next).toBeCalledWith({ path: '/not-found' })
|
||||
})
|
||||
|
||||
it('does not redirect when token in store', () => {
|
||||
store.state.token = 'valid token'
|
||||
navGuard({ path: '/' }, {}, next)
|
||||
expect(next).toBeCalledWith()
|
||||
})
|
||||
})
|
||||
})
|
||||
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 seven routes defined', () => {
|
||||
expect(routes).toHaveLength(7)
|
||||
})
|
||||
|
||||
it('has "/overview" as default', async () => {
|
||||
const component = await routes.find((r) => r.path === '/').component()
|
||||
expect(component.default.name).toBe('overview')
|
||||
})
|
||||
|
||||
describe('logout', () => {
|
||||
it('loads the "NotFoundPage" component', async () => {
|
||||
const component = await routes.find((r) => r.path === '/logout').component()
|
||||
expect(component.default.name).toBe('not-found')
|
||||
})
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,38 +1,27 @@
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
component: () => import('@/views/Overview.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
path: '/authenticate',
|
||||
},
|
||||
{
|
||||
path: '/overview',
|
||||
component: () => import('@/views/Overview.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
path: '/',
|
||||
component: () => import('@/pages/Overview.vue'),
|
||||
},
|
||||
{
|
||||
// TODO: Implement a "You are logged out"-Page
|
||||
path: '/logout',
|
||||
component: () => import('@/components/NotFoundPage.vue'),
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
component: () => import('@/views/UserSearch.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
component: () => import('@/pages/UserSearch.vue'),
|
||||
},
|
||||
{
|
||||
path: '/creation',
|
||||
component: () => import('@/views/Creation.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
component: () => import('@/pages/Creation.vue'),
|
||||
},
|
||||
{
|
||||
path: '/creation-confirm',
|
||||
component: () => import('@/views/CreationConfirm.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
component: () => import('@/pages/CreationConfirm.vue'),
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import Vuex from 'vuex'
|
||||
import Vue from 'vue'
|
||||
import createPersistedState from 'vuex-persistedstate'
|
||||
import CONFIG from '../config'
|
||||
|
||||
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
|
||||
@ -19,6 +20,13 @@ export const mutations = {
|
||||
},
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
logout: ({ commit, state }) => {
|
||||
commit('token', null)
|
||||
window.localStorage.clear()
|
||||
},
|
||||
}
|
||||
|
||||
const store = new Vuex.Store({
|
||||
plugins: [
|
||||
createPersistedState({
|
||||
@ -26,12 +34,13 @@ const store = new Vuex.Store({
|
||||
}),
|
||||
],
|
||||
state: {
|
||||
token: 'some-valid-token',
|
||||
token: CONFIG.DEBUG_DISABLE_AUTH ? 'validToken' : null,
|
||||
moderator: 'Dertest Moderator',
|
||||
openCreations: 0,
|
||||
},
|
||||
// Syncronous mutation of the state
|
||||
mutations,
|
||||
actions,
|
||||
})
|
||||
|
||||
export default store
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import { mutations } from './store'
|
||||
import store, { mutations, actions } from './store'
|
||||
|
||||
const { token } = mutations
|
||||
const { token, openCreationsPlus, openCreationsMinus, resetOpenCreations } = mutations
|
||||
const { logout } = actions
|
||||
|
||||
const CONFIG = {
|
||||
DEBUG_DISABLE_AUTH: true,
|
||||
}
|
||||
|
||||
describe('Vuex store', () => {
|
||||
describe('mutations', () => {
|
||||
@ -11,5 +16,68 @@ describe('Vuex store', () => {
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('actions', () => {
|
||||
describe('logout', () => {
|
||||
const windowStorageMock = jest.fn()
|
||||
const commit = jest.fn()
|
||||
const state = {}
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
window.localStorage.clear = windowStorageMock
|
||||
})
|
||||
|
||||
it('deletes the token in store', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toBeCalledWith('token', null)
|
||||
})
|
||||
|
||||
it.skip('clears the window local storage', () => {
|
||||
expect(windowStorageMock).toBeCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('state', () => {
|
||||
describe('authentication enabled', () => {
|
||||
it('has no token', () => {
|
||||
expect(store.state.token).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('authentication enabled', () => {
|
||||
beforeEach(() => {
|
||||
CONFIG.DEBUG_DISABLE_AUTH = false
|
||||
})
|
||||
|
||||
it.skip('has a token', () => {
|
||||
expect(store.state.token).toBe('validToken')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
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'
|
||||
@ -8,6 +8,7 @@ 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) => {
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -55,9 +55,21 @@ const email = {
|
||||
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
|
||||
|
||||
@ -20,6 +20,7 @@ export class User {
|
||||
this.pubkey = json.public_hex
|
||||
this.language = json.language
|
||||
this.publisherId = json.publisher_id
|
||||
this.isAdmin = json.isAdmin
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +49,7 @@ export class User {
|
||||
@Field(() => number)
|
||||
created: number
|
||||
|
||||
@Field(() => Boolean)
|
||||
@Field(() =>>> Boolean)
|
||||
emailChecked: boolean
|
||||
|
||||
@Field(() => Boolean)
|
||||
@ -71,6 +72,9 @@ export class User {
|
||||
@Field(() => Int, { nullable: true })
|
||||
publisherId?: number
|
||||
|
||||
@Field(() => Boolean)
|
||||
isAdmin: boolean
|
||||
|
||||
@Field(() => Boolean)
|
||||
coinanimation: boolean
|
||||
|
||||
|
||||
@ -194,6 +194,69 @@ const SecretKeyCryptographyDecrypt = (encryptedMessage: Buffer, encryptionKey: B
|
||||
|
||||
@Resolver()
|
||||
export class UserResolver {
|
||||
/*
|
||||
@Authorized()
|
||||
@Query(() => User)
|
||||
async verifyLogin(@Ctx() context: any): Promise<User> {
|
||||
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||
loginUser = loginUserRepository.findByPubkeyHex()
|
||||
const user = new User(result.data.user)
|
||||
|
||||
this.email = json.email
|
||||
this.firstName = json.first_name
|
||||
this.lastName = json.last_name
|
||||
this.username = json.username
|
||||
this.description = json.description
|
||||
this.pubkey = json.public_hex
|
||||
this.language = json.language
|
||||
this.publisherId = json.publisher_id
|
||||
this.isAdmin = json.isAdmin
|
||||
|
||||
const userSettingRepository = getCustomRepository(UserSettingRepository)
|
||||
const coinanimation = await userSettingRepository
|
||||
.readBoolean(userEntity.id, Setting.COIN_ANIMATION)
|
||||
.catch((error) => {
|
||||
throw new Error(error)
|
||||
})
|
||||
user.coinanimation = coinanimation
|
||||
user.isAdmin = true // TODO implement
|
||||
return user
|
||||
}
|
||||
*/
|
||||
|
||||
@Authorized()
|
||||
@Query(() => User)
|
||||
@UseMiddleware(klicktippNewsletterStateMiddleware)
|
||||
async verifyLogin(@Ctx() context: any): Promise<User> {
|
||||
// TODO refactor and do not have duplicate code with login(see below)
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
const userEntity = await userRepository.findByPubkeyHex(context.pubKey)
|
||||
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||
const loginUser = await loginUserRepository.findByEmail(userEntity.email)
|
||||
const user = new User()
|
||||
user.email = userEntity.email
|
||||
user.firstName = userEntity.firstName
|
||||
user.lastName = userEntity.lastName
|
||||
user.username = userEntity.username
|
||||
user.description = loginUser.description
|
||||
user.pubkey = userEntity.pubkey.toString('hex')
|
||||
user.language = loginUser.language
|
||||
|
||||
// Elopage Status & Stored PublisherId
|
||||
user.hasElopage = await this.hasElopage(context)
|
||||
|
||||
// coinAnimation
|
||||
const userSettingRepository = getCustomRepository(UserSettingRepository)
|
||||
const coinanimation = await userSettingRepository
|
||||
.readBoolean(userEntity.id, Setting.COIN_ANIMATION)
|
||||
.catch((error) => {
|
||||
throw new Error(error)
|
||||
})
|
||||
user.coinanimation = coinanimation
|
||||
user.isAdmin = true // TODO implement
|
||||
return user
|
||||
}
|
||||
|
||||
@Query(() => User)
|
||||
@UseMiddleware(klicktippNewsletterStateMiddleware)
|
||||
async login(
|
||||
@ -266,6 +329,7 @@ export class UserResolver {
|
||||
throw new Error(error)
|
||||
})
|
||||
user.coinanimation = coinanimation
|
||||
user.isAdmin = true // TODO implement
|
||||
|
||||
context.setHeaders.push({
|
||||
key: 'token',
|
||||
@ -303,22 +367,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
|
||||
@ -336,11 +401,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()
|
||||
@ -349,13 +416,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()
|
||||
@ -367,21 +436,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
|
||||
|
||||
@ -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(),
|
||||
|
||||
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==
|
||||
|
||||
@ -1,166 +1,166 @@
|
||||
version: "3.4"
|
||||
|
||||
services:
|
||||
########################################################
|
||||
# FRONTEND #############################################
|
||||
########################################################
|
||||
frontend:
|
||||
image: gradido/frontend:development
|
||||
build:
|
||||
target: development
|
||||
environment:
|
||||
- NODE_ENV="development"
|
||||
# - DEBUG=true
|
||||
volumes:
|
||||
# This makes sure the docker container has its own node modules.
|
||||
# Therefore it is possible to have a different node version on the host machine
|
||||
- frontend_node_modules:/app/node_modules
|
||||
# bind the local folder to the docker to allow live reload
|
||||
- ./frontend:/app
|
||||
|
||||
########################################################
|
||||
# ADMIN INTERFACE ######################################
|
||||
########################################################
|
||||
admin:
|
||||
image: gradido/admin:development
|
||||
build:
|
||||
target: development
|
||||
environment:
|
||||
- NODE_ENV="development"
|
||||
# - DEBUG=true
|
||||
volumes:
|
||||
# This makes sure the docker container has its own node modules.
|
||||
# Therefore it is possible to have a different node version on the host machine
|
||||
- admin_node_modules:/app/node_modules
|
||||
# bind the local folder to the docker to allow live reload
|
||||
- ./admin:/app
|
||||
|
||||
########################################################
|
||||
# BACKEND ##############################################
|
||||
########################################################
|
||||
backend:
|
||||
image: gradido/backend:development
|
||||
build:
|
||||
target: development
|
||||
networks:
|
||||
- external-net
|
||||
- internal-net
|
||||
environment:
|
||||
- NODE_ENV="development"
|
||||
volumes:
|
||||
# This makes sure the docker container has its own node modules.
|
||||
# Therefore it is possible to have a different node version on the host machine
|
||||
- backend_node_modules:/app/node_modules
|
||||
- backend_database_node_modules:/database/node_modules
|
||||
- backend_database_build:/database/build
|
||||
# bind the local folder to the docker to allow live reload
|
||||
- ./backend:/app
|
||||
- ./database:/database
|
||||
|
||||
########################################################
|
||||
# DATABASE ##############################################
|
||||
########################################################
|
||||
database:
|
||||
# we always run on production here since else the service lingers
|
||||
# feel free to change this behaviour if it seems useful
|
||||
# Due to problems with the volume caching the built files
|
||||
# we changed this to test build. This keeps the service running.
|
||||
image: gradido/database:test_up
|
||||
build:
|
||||
target: test_up
|
||||
#networks:
|
||||
# - external-net
|
||||
# - internal-net
|
||||
environment:
|
||||
- NODE_ENV="development"
|
||||
volumes:
|
||||
# This makes sure the docker container has its own node modules.
|
||||
# Therefore it is possible to have a different node version on the host machine
|
||||
- database_node_modules:/app/node_modules
|
||||
- database_build:/app/build
|
||||
# bind the local folder to the docker to allow live reload
|
||||
- ./database:/app
|
||||
|
||||
#########################################################
|
||||
## LOGIN SERVER #########################################
|
||||
#########################################################
|
||||
login-server:
|
||||
build:
|
||||
dockerfile: Dockerfiles/ubuntu/Dockerfile.debug
|
||||
networks:
|
||||
- external-net
|
||||
- internal-net
|
||||
security_opt:
|
||||
- seccomp:unconfined
|
||||
cap_add:
|
||||
- SYS_PTRACE
|
||||
volumes:
|
||||
- ./logs:/var/log/grd_login
|
||||
- ./login_server/src:/code/src
|
||||
- ./login_server/dependencies:/code/dependencies
|
||||
- ./login_server/scripts:/code/scripts
|
||||
- ./configs/login_server:/etc/grd_login
|
||||
- login_build_ubuntu_3.1:/code/build
|
||||
|
||||
|
||||
#########################################################
|
||||
## COMMUNITY SERVER (cakephp with php-fpm) ##############
|
||||
#########################################################
|
||||
community-server:
|
||||
build:
|
||||
context: .
|
||||
target: community_server
|
||||
dockerfile: ./community_server/Dockerfile
|
||||
depends_on:
|
||||
- mariadb
|
||||
networks:
|
||||
- internal-net
|
||||
- external-net
|
||||
volumes:
|
||||
- ./community_server/config/php-fpm/php-ini-overrides.ini:/etc/php/7.4/fpm/conf.d/99-overrides.ini
|
||||
- ./community_server/src:/var/www/cakephp/src
|
||||
|
||||
#########################################################
|
||||
## MARIADB ##############################################
|
||||
#########################################################
|
||||
mariadb:
|
||||
networks:
|
||||
- internal-net
|
||||
- external-net
|
||||
|
||||
#########################################################
|
||||
## NGINX ################################################
|
||||
#########################################################
|
||||
nginx:
|
||||
depends_on:
|
||||
- frontend
|
||||
- community-server
|
||||
- login-server
|
||||
volumes:
|
||||
- ./logs/nginx:/var/log/nginx
|
||||
|
||||
#########################################################
|
||||
## PHPMYADMIN ###########################################
|
||||
#########################################################
|
||||
phpmyadmin:
|
||||
image: phpmyadmin
|
||||
environment:
|
||||
- PMA_ARBITRARY=1
|
||||
#restart: always
|
||||
ports:
|
||||
- 8074:80
|
||||
networks:
|
||||
- internal-net
|
||||
- external-net
|
||||
volumes:
|
||||
- /sessions
|
||||
|
||||
volumes:
|
||||
frontend_node_modules:
|
||||
admin_node_modules:
|
||||
backend_node_modules:
|
||||
backend_database_node_modules:
|
||||
backend_database_build:
|
||||
database_node_modules:
|
||||
database_build:
|
||||
version: "3.4"
|
||||
|
||||
services:
|
||||
########################################################
|
||||
# FRONTEND #############################################
|
||||
########################################################
|
||||
frontend:
|
||||
image: gradido/frontend:development
|
||||
build:
|
||||
target: development
|
||||
environment:
|
||||
- NODE_ENV="development"
|
||||
# - DEBUG=true
|
||||
volumes:
|
||||
# This makes sure the docker container has its own node modules.
|
||||
# Therefore it is possible to have a different node version on the host machine
|
||||
- frontend_node_modules:/app/node_modules
|
||||
# bind the local folder to the docker to allow live reload
|
||||
- ./frontend:/app
|
||||
|
||||
########################################################
|
||||
# ADMIN INTERFACE ######################################
|
||||
########################################################
|
||||
admin:
|
||||
image: gradido/admin:development
|
||||
build:
|
||||
target: development
|
||||
environment:
|
||||
- NODE_ENV="development"
|
||||
# - DEBUG=true
|
||||
volumes:
|
||||
# This makes sure the docker container has its own node modules.
|
||||
# Therefore it is possible to have a different node version on the host machine
|
||||
- admin_node_modules:/app/node_modules
|
||||
# bind the local folder to the docker to allow live reload
|
||||
- ./admin:/app
|
||||
|
||||
########################################################
|
||||
# BACKEND ##############################################
|
||||
########################################################
|
||||
backend:
|
||||
image: gradido/backend:development
|
||||
build:
|
||||
target: development
|
||||
networks:
|
||||
- external-net
|
||||
- internal-net
|
||||
environment:
|
||||
- NODE_ENV="development"
|
||||
volumes:
|
||||
# This makes sure the docker container has its own node modules.
|
||||
# Therefore it is possible to have a different node version on the host machine
|
||||
- backend_node_modules:/app/node_modules
|
||||
- backend_database_node_modules:/database/node_modules
|
||||
- backend_database_build:/database/build
|
||||
# bind the local folder to the docker to allow live reload
|
||||
- ./backend:/app
|
||||
- ./database:/database
|
||||
|
||||
########################################################
|
||||
# DATABASE ##############################################
|
||||
########################################################
|
||||
database:
|
||||
# we always run on production here since else the service lingers
|
||||
# feel free to change this behaviour if it seems useful
|
||||
# Due to problems with the volume caching the built files
|
||||
# we changed this to test build. This keeps the service running.
|
||||
image: gradido/database:test_up
|
||||
build:
|
||||
target: test_up
|
||||
#networks:
|
||||
# - external-net
|
||||
# - internal-net
|
||||
environment:
|
||||
- NODE_ENV="development"
|
||||
volumes:
|
||||
# This makes sure the docker container has its own node modules.
|
||||
# Therefore it is possible to have a different node version on the host machine
|
||||
- database_node_modules:/app/node_modules
|
||||
- database_build:/app/build
|
||||
# bind the local folder to the docker to allow live reload
|
||||
- ./database:/app
|
||||
|
||||
#########################################################
|
||||
## LOGIN SERVER #########################################
|
||||
#########################################################
|
||||
login-server:
|
||||
build:
|
||||
dockerfile: Dockerfiles/ubuntu/Dockerfile.debug
|
||||
networks:
|
||||
- external-net
|
||||
- internal-net
|
||||
security_opt:
|
||||
- seccomp:unconfined
|
||||
cap_add:
|
||||
- SYS_PTRACE
|
||||
volumes:
|
||||
- ./logs:/var/log/grd_login
|
||||
- ./login_server/src:/code/src
|
||||
- ./login_server/dependencies:/code/dependencies
|
||||
- ./login_server/scripts:/code/scripts
|
||||
- ./configs/login_server:/etc/grd_login
|
||||
- login_build_ubuntu_3.1:/code/build
|
||||
|
||||
|
||||
#########################################################
|
||||
## COMMUNITY SERVER (cakephp with php-fpm) ##############
|
||||
#########################################################
|
||||
community-server:
|
||||
build:
|
||||
context: .
|
||||
target: community_server
|
||||
dockerfile: ./community_server/Dockerfile
|
||||
depends_on:
|
||||
- mariadb
|
||||
networks:
|
||||
- internal-net
|
||||
- external-net
|
||||
volumes:
|
||||
- ./community_server/config/php-fpm/php-ini-overrides.ini:/etc/php/7.4/fpm/conf.d/99-overrides.ini
|
||||
- ./community_server/src:/var/www/cakephp/src
|
||||
|
||||
#########################################################
|
||||
## MARIADB ##############################################
|
||||
#########################################################
|
||||
mariadb:
|
||||
networks:
|
||||
- internal-net
|
||||
- external-net
|
||||
|
||||
#########################################################
|
||||
## NGINX ################################################
|
||||
#########################################################
|
||||
nginx:
|
||||
depends_on:
|
||||
- frontend
|
||||
- community-server
|
||||
- login-server
|
||||
volumes:
|
||||
- ./logs/nginx:/var/log/nginx
|
||||
|
||||
#########################################################
|
||||
## PHPMYADMIN ###########################################
|
||||
#########################################################
|
||||
phpmyadmin:
|
||||
image: phpmyadmin
|
||||
environment:
|
||||
- PMA_ARBITRARY=1
|
||||
#restart: always
|
||||
ports:
|
||||
- 8074:80
|
||||
networks:
|
||||
- internal-net
|
||||
- external-net
|
||||
volumes:
|
||||
- /sessions
|
||||
|
||||
volumes:
|
||||
frontend_node_modules:
|
||||
admin_node_modules:
|
||||
backend_node_modules:
|
||||
backend_database_node_modules:
|
||||
backend_database_build:
|
||||
database_node_modules:
|
||||
database_build:
|
||||
login_build_ubuntu_3.1:
|
||||
@ -1,3 +1,4 @@
|
||||
GRAPHQL_URI=http://localhost:4000/graphql
|
||||
DEFAULT_PUBLISHER_ID=2896
|
||||
//BUILD_COMMIT=0000000
|
||||
#BUILD_COMMIT=0000000
|
||||
ADMIN_AUTH_URL=http://localhost/admin/authenticate?token=$1
|
||||
@ -1,21 +0,0 @@
|
||||
DEV README von Alex
|
||||
|
||||
default Page:
|
||||
´´´
|
||||
<template>
|
||||
<div>default</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'default',
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {},
|
||||
watch: {},
|
||||
}
|
||||
</script>
|
||||
|
||||
´´´
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
<!--
|
||||
IMPORTANT: Please use the following link to create a new issue:
|
||||
|
||||
https://www.gradido.net/new-issue/bootstrap-vue-gradido-wallet
|
||||
|
||||
**If your issue was not created using the app above, it will be closed immediately.**
|
||||
-->
|
||||
|
||||
|
||||
@ -385,4 +385,13 @@ TODO: Update GDT-Server um paging und Zugriff auf alle Einträge zu erhalten, op
|
||||
GET https://staging.gradido.net/state-balances/ajaxGdtTransactions
|
||||
Liefert wenn alles in Ordnung ist:
|
||||
|
||||
wenn nicht type 7 dann "amount" in euro ansonsten in GDT
|
||||
wenn nicht type 7 dann "amount" in euro ansonsten in GDT
|
||||
|
||||
## Additional Software
|
||||
|
||||
For `yarn locales` you will need `jq` to use it.
|
||||
You can install it (on arch) via
|
||||
|
||||
```
|
||||
sudo pacman -S jq
|
||||
```
|
||||
@ -3,6 +3,8 @@ import SideBar from './SideBar'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const storeDispatchMock = jest.fn()
|
||||
|
||||
describe('SideBar', () => {
|
||||
let wrapper
|
||||
|
||||
@ -23,7 +25,7 @@ describe('SideBar', () => {
|
||||
lastName: 'example',
|
||||
hasElopage: false,
|
||||
},
|
||||
commit: jest.fn(),
|
||||
dispatch: storeDispatchMock,
|
||||
},
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
@ -154,6 +156,42 @@ describe('SideBar', () => {
|
||||
expect(wrapper.emitted('logout')).toEqual([[]])
|
||||
})
|
||||
})
|
||||
|
||||
describe('admin-area', () => {
|
||||
it('is not visible when not an admin', () => {
|
||||
expect(wrapper.findAll('li').at(1).text()).not.toBe('admin_area')
|
||||
})
|
||||
|
||||
describe('logged in as admin', () => {
|
||||
const assignLocationSpy = jest.fn()
|
||||
beforeEach(async () => {
|
||||
mocks.$store.state.isAdmin = true
|
||||
mocks.$store.state.token = 'valid-token'
|
||||
window.location.assign = assignLocationSpy
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('is visible', () => {
|
||||
expect(wrapper.findAll('li').at(1).text()).toBe('admin_area')
|
||||
})
|
||||
|
||||
describe('click on admin area', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findAll('li').at(1).find('a').trigger('click')
|
||||
})
|
||||
|
||||
it('opens a new window when clicked', () => {
|
||||
expect(assignLocationSpy).toHaveBeenCalledWith(
|
||||
'http://localhost/admin/authenticate?token=valid-token',
|
||||
)
|
||||
})
|
||||
|
||||
it('dispatches logout to store', () => {
|
||||
expect(storeDispatchMock).toHaveBeenCalledWith('logout')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -45,11 +45,20 @@
|
||||
<slot name="links"></slot>
|
||||
</ul>
|
||||
<hr class="my-2" />
|
||||
|
||||
<ul class="navbar-nav ml-3">
|
||||
<li class="nav-item">
|
||||
<a :href="getElopageLink()" class="nav-link" target="_blank">
|
||||
{{ $t('members_area') }}
|
||||
<b-badge v-if="!this.$store.state.hasElopage" pill variant="danger">!</b-badge>
|
||||
<b-badge v-if="!$store.state.hasElopage" pill variant="danger">!</b-badge>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="navbar-nav ml-3" v-if="$store.state.isAdmin">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link pointer" @click="admin">
|
||||
{{ $t('admin_area') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@ -112,6 +121,10 @@ export default {
|
||||
logout() {
|
||||
this.$emit('logout')
|
||||
},
|
||||
admin() {
|
||||
window.location.assign(CONFIG.ADMIN_AUTH_URL.replace('$1', this.$store.state.token))
|
||||
this.$store.dispatch('logout') // logout without redirect
|
||||
},
|
||||
getElopageLink() {
|
||||
const pId = this.$store.state.publisherId
|
||||
? this.$store.state.publisherId
|
||||
|
||||
@ -18,8 +18,9 @@ const environment = {
|
||||
DEFAULT_PUBLISHER_ID: process.env.DEFAULT_PUBLISHER_ID || 2896,
|
||||
}
|
||||
|
||||
const server = {
|
||||
const endpoints = {
|
||||
GRAPHQL_URI: process.env.GRAPHQL_URI || 'http://localhost:4000/graphql',
|
||||
ADMIN_AUTH_URL: process.env.ADMIN_AUTH_URL || 'http://localhost/admin/authenticate?token=$1',
|
||||
}
|
||||
|
||||
const options = {}
|
||||
@ -27,7 +28,7 @@ const options = {}
|
||||
const CONFIG = {
|
||||
...version,
|
||||
...environment,
|
||||
...server,
|
||||
...endpoints,
|
||||
...options,
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +15,27 @@ export const login = gql`
|
||||
}
|
||||
hasElopage
|
||||
publisherId
|
||||
isAdmin
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const verifyLogin = gql`
|
||||
query {
|
||||
verifyLogin {
|
||||
email
|
||||
username
|
||||
firstName
|
||||
lastName
|
||||
language
|
||||
description
|
||||
coinanimation
|
||||
klickTipp {
|
||||
newsletterState
|
||||
}
|
||||
hasElopage
|
||||
publisherId
|
||||
isAdmin
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"admin_area": "Adminbereich",
|
||||
"back": "Zurück",
|
||||
"community": {
|
||||
"choose-another-community": "Eine andere Gemeinschaft auswählen",
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"admin_area": "Admin's area",
|
||||
"back": "Back",
|
||||
"community": {
|
||||
"choose-another-community": "Choose another community",
|
||||
|
||||
@ -51,7 +51,7 @@ Vue.config.productionTip = false
|
||||
|
||||
loadAllRules(i18n)
|
||||
|
||||
addNavigationGuards(router, store)
|
||||
addNavigationGuards(router, store, apolloProvider.defaultClient)
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
|
||||
@ -1,12 +1,34 @@
|
||||
const addNavigationGuards = (router, store) => {
|
||||
import { verifyLogin } from '../graphql/queries'
|
||||
|
||||
const addNavigationGuards = (router, store, apollo) => {
|
||||
// handle publisherId
|
||||
router.beforeEach((to, from, next) => {
|
||||
// handle publisherId
|
||||
const publisherId = to.query.pid
|
||||
if (publisherId) {
|
||||
store.commit('publisherId', publisherId)
|
||||
delete to.query.pid
|
||||
}
|
||||
// handle authentication
|
||||
next()
|
||||
})
|
||||
|
||||
// store token on authenticate
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
if (to.path === '/authenticate' && to.query.token) {
|
||||
// TODO verify user in order to get user data
|
||||
store.commit('token', to.query.token)
|
||||
const result = await apollo.query({
|
||||
query: verifyLogin,
|
||||
fetchPolicy: 'network-only',
|
||||
})
|
||||
store.dispatch('login', result.data.verifyLogin)
|
||||
next({ path: '/overview' })
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
// handle authentication
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.meta.requiresAuth && !store.state.token) {
|
||||
next({ path: '/login' })
|
||||
} else {
|
||||
|
||||
@ -30,7 +30,7 @@ describe('navigation guards', () => {
|
||||
})
|
||||
|
||||
describe('authorization', () => {
|
||||
const navGuard = router.beforeHooks[0]
|
||||
const navGuard = router.beforeHooks[2]
|
||||
const next = jest.fn()
|
||||
|
||||
it('redirects to login when not authorized', () => {
|
||||
|
||||
@ -49,8 +49,8 @@ describe('router', () => {
|
||||
expect(routes.find((r) => r.path === '/').redirect()).toEqual({ path: '/login' })
|
||||
})
|
||||
|
||||
it('has fourteen routes defined', () => {
|
||||
expect(routes).toHaveLength(14)
|
||||
it('has fifteen routes defined', () => {
|
||||
expect(routes).toHaveLength(15)
|
||||
})
|
||||
|
||||
describe('overview', () => {
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import NotFound from '@/views/NotFoundPage.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/authenticate',
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
redirect: (to) => {
|
||||
|
||||
@ -34,6 +34,9 @@ export const mutations = {
|
||||
if (isNaN(pubId)) pubId = null
|
||||
state.publisherId = pubId
|
||||
},
|
||||
isAdmin: (state, isAdmin) => {
|
||||
state.isAdmin = !!isAdmin
|
||||
},
|
||||
community: (state, community) => {
|
||||
state.community = community
|
||||
},
|
||||
@ -57,6 +60,7 @@ export const actions = {
|
||||
commit('newsletterState', data.klickTipp.newsletterState)
|
||||
commit('hasElopage', data.hasElopage)
|
||||
commit('publisherId', data.publisherId)
|
||||
commit('isAdmin', data.isAdmin)
|
||||
},
|
||||
logout: ({ commit, state }) => {
|
||||
commit('token', null)
|
||||
@ -69,6 +73,7 @@ export const actions = {
|
||||
commit('newsletterState', null)
|
||||
commit('hasElopage', false)
|
||||
commit('publisherId', null)
|
||||
commit('isAdmin', false)
|
||||
localStorage.clear()
|
||||
},
|
||||
}
|
||||
@ -87,6 +92,7 @@ export const store = new Vuex.Store({
|
||||
username: '',
|
||||
description: '',
|
||||
token: null,
|
||||
isAdmin: false,
|
||||
coinanimation: true,
|
||||
newsletterState: null,
|
||||
community: {
|
||||
|
||||
@ -148,11 +148,12 @@ describe('Vuex store', () => {
|
||||
},
|
||||
hasElopage: false,
|
||||
publisherId: 1234,
|
||||
isAdmin: true,
|
||||
}
|
||||
|
||||
it('calls ten commits', () => {
|
||||
it('calls eleven commits', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenCalledTimes(10)
|
||||
expect(commit).toHaveBeenCalledTimes(11)
|
||||
})
|
||||
|
||||
it('commits email', () => {
|
||||
@ -204,15 +205,20 @@ describe('Vuex store', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(10, 'publisherId', 1234)
|
||||
})
|
||||
|
||||
it('commits isAdmin', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(11, 'isAdmin', true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('logout', () => {
|
||||
const commit = jest.fn()
|
||||
const state = {}
|
||||
|
||||
it('calls ten commits', () => {
|
||||
it('calls eleven commits', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenCalledTimes(10)
|
||||
expect(commit).toHaveBeenCalledTimes(11)
|
||||
})
|
||||
|
||||
it('commits token', () => {
|
||||
@ -265,6 +271,11 @@ describe('Vuex store', () => {
|
||||
expect(commit).toHaveBeenNthCalledWith(10, 'publisherId', null)
|
||||
})
|
||||
|
||||
it('commits isAdmin', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(11, 'isAdmin', false)
|
||||
})
|
||||
|
||||
// how to get this working?
|
||||
it.skip('calls localStorage.clear()', () => {
|
||||
const clearStorageMock = jest.fn()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user