mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into onboarding_process
This commit is contained in:
commit
f58f61fd41
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@ -380,7 +380,7 @@ jobs:
|
|||||||
##########################################################################
|
##########################################################################
|
||||||
- name: frontend | Unit tests
|
- name: frontend | Unit tests
|
||||||
run: |
|
run: |
|
||||||
docker run -v ~/coverage:/app/coverage --rm gradido/frontend:test yarn run test
|
docker run --env NODE_ENV=test -v ~/coverage:/app/coverage --rm gradido/frontend:test yarn run test
|
||||||
cp -r ~/coverage ./coverage
|
cp -r ~/coverage ./coverage
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# COVERAGE REPORT FRONTEND ###############################################
|
# COVERAGE REPORT FRONTEND ###############################################
|
||||||
@ -399,7 +399,7 @@ jobs:
|
|||||||
report_name: Coverage Frontend
|
report_name: Coverage Frontend
|
||||||
type: lcov
|
type: lcov
|
||||||
result_path: ./coverage/lcov.info
|
result_path: ./coverage/lcov.info
|
||||||
min_coverage: 86
|
min_coverage: 90
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
@ -441,7 +441,7 @@ jobs:
|
|||||||
report_name: Coverage Admin Interface
|
report_name: Coverage Admin Interface
|
||||||
type: lcov
|
type: lcov
|
||||||
result_path: ./coverage/lcov.info
|
result_path: ./coverage/lcov.info
|
||||||
min_coverage: 49
|
min_coverage: 69
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
@ -657,4 +657,4 @@ jobs:
|
|||||||
- name: database | up
|
- name: database | up
|
||||||
run: docker-compose -f docker-compose.yml run -T database yarn up
|
run: docker-compose -f docker-compose.yml run -T database yarn up
|
||||||
- name: database | reset
|
- name: database | reset
|
||||||
run: docker-compose -f docker-compose.yml run -T database yarn reset
|
run: docker-compose -f docker-compose.yml run -T database yarn reset
|
||||||
|
|||||||
16
README.md
16
README.md
@ -69,6 +69,22 @@ We are currently restructuring the service to reduce dependencies and unify busi
|
|||||||
|
|
||||||
Once you have `docker-compose` up and running, you can open [http://localhost/vue](http://localhost/vue) and create yourself a new wallet account.
|
Once you have `docker-compose` up and running, you can open [http://localhost/vue](http://localhost/vue) and create yourself a new wallet account.
|
||||||
|
|
||||||
|
## How to release
|
||||||
|
|
||||||
|
A release is tagged on Github by its version number and published as github release. This is done automatically when a new version is defined in the [package.json](./package.json) and merged into master - furthermore we set all our sub-package-versions to the same version as the main package.json version to make version management as simple as possible.
|
||||||
|
Each release is accompanied with release notes automatically generated from the git log which is available as [CHANGELOG.md](./CHANGELOG.md).
|
||||||
|
|
||||||
|
To generate the Changelog and set a new Version you should use the following commands in the main folder
|
||||||
|
```bash
|
||||||
|
git fetch --all
|
||||||
|
yarn release
|
||||||
|
```
|
||||||
|
|
||||||
|
The first command `git fetch --all` will make sure you have all tags previously defined which is required to generate a correct changelog. The second command `yarn release` will execute the changelog tool and set version numbers in the main package and sub-packages. It is required to do `yarn install` before you can use this command.
|
||||||
|
After generating a new version you should commit the changes. This will be the CHANGELOG.md and several package.json files. This commit will be omitted in the changelog.
|
||||||
|
|
||||||
|
Note: The Changelog will be regenerated with all tags on release on the external builder tool, but will not be checked in there. The Changelog on the github release will therefore always be correct, on the repo it might be incorrect due to missing tags when executing the `yarn release` command.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
| Problem | Issue | Solution | Description |
|
| Problem | Issue | Solution | Description |
|
||||||
|
|||||||
@ -43,6 +43,7 @@
|
|||||||
"vue-jest": "^3.0.7",
|
"vue-jest": "^3.0.7",
|
||||||
"vue-moment": "^4.1.0",
|
"vue-moment": "^4.1.0",
|
||||||
"vue-router": "^3.5.3",
|
"vue-router": "^3.5.3",
|
||||||
|
"vue-toasted": "^1.1.28",
|
||||||
"vuex": "^3.6.2",
|
"vuex": "^3.6.2",
|
||||||
"vuex-persistedstate": "^4.1.0"
|
"vuex-persistedstate": "^4.1.0"
|
||||||
},
|
},
|
||||||
|
|||||||
BIN
admin/public/img/brand/gradido_logo_w.png
Normal file
BIN
admin/public/img/brand/gradido_logo_w.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
BIN
admin/public/img/brand/green.png
Normal file
BIN
admin/public/img/brand/green.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@ -3,6 +3,23 @@ import CreationFormular from './CreationFormular.vue'
|
|||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const apolloMock = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
verifyLogin: {
|
||||||
|
name: 'success',
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const apolloMutateMock = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
createPendingCreation: [0, 0, 0],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const stateCommitMock = jest.fn()
|
||||||
|
const toastedErrorMock = jest.fn()
|
||||||
|
const toastedSuccessMock = jest.fn()
|
||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
$moment: jest.fn(() => {
|
$moment: jest.fn(() => {
|
||||||
return {
|
return {
|
||||||
@ -14,11 +31,27 @@ const mocks = {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
$apollo: {
|
||||||
|
query: apolloMock,
|
||||||
|
mutate: apolloMutateMock,
|
||||||
|
},
|
||||||
|
$store: {
|
||||||
|
commit: stateCommitMock,
|
||||||
|
state: {
|
||||||
|
moderator: {
|
||||||
|
id: 0,
|
||||||
|
name: 'test moderator',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
$toasted: {
|
||||||
|
error: toastedErrorMock,
|
||||||
|
success: toastedSuccessMock,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const propsData = {
|
const propsData = {
|
||||||
type: '',
|
type: '',
|
||||||
item: {},
|
|
||||||
creation: [],
|
creation: [],
|
||||||
itemsMassCreation: {},
|
itemsMassCreation: {},
|
||||||
}
|
}
|
||||||
@ -39,6 +72,24 @@ describe('CreationFormular', () => {
|
|||||||
expect(wrapper.find('.component-creation-formular').exists()).toBeTruthy()
|
expect(wrapper.find('.component-creation-formular').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('server sends back moderator data', () => {
|
||||||
|
it('called store commit with mocked data', () => {
|
||||||
|
expect(stateCommitMock).toBeCalledWith('moderator', { name: 'success', id: 0 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('server throws error for moderator data call', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
apolloMock.mockRejectedValueOnce({ message: 'Ouch!' })
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has called store commit with fake data', () => {
|
||||||
|
expect(stateCommitMock).toBeCalledWith('moderator', { id: 0, name: 'Test Moderator' })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('radio buttons to selcet month', () => {
|
describe('radio buttons to selcet month', () => {
|
||||||
it('has three radio buttons', () => {
|
it('has three radio buttons', () => {
|
||||||
expect(wrapper.findAll('input[type="radio"]').length).toBe(3)
|
expect(wrapper.findAll('input[type="radio"]').length).toBe(3)
|
||||||
@ -92,6 +143,8 @@ describe('CreationFormular', () => {
|
|||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
||||||
await wrapper.setData({ rangeMin: 180 })
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
|
await wrapper.setData({ text: 'Test create coins' })
|
||||||
|
await wrapper.setData({ value: 90 })
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('first radio button', () => {
|
describe('first radio button', () => {
|
||||||
@ -106,6 +159,66 @@ describe('CreationFormular', () => {
|
|||||||
it('sets rangeMax to 200', () => {
|
it('sets rangeMax to 200', () => {
|
||||||
expect(wrapper.vm.rangeMax).toBe(200)
|
expect(wrapper.vm.rangeMax).toBe(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('sendForm', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.find('.test-submit').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends ... to apollo', () => {
|
||||||
|
expect(apolloMutateMock).toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('sendForm', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
apolloMutateMock.mockRejectedValueOnce({ message: 'Ouch!' })
|
||||||
|
await wrapper.find('.test-submit').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends ... to apollo', () => {
|
||||||
|
expect(toastedErrorMock).toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Negativ value', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
||||||
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
|
await wrapper.setData({ value: -20 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has no submit button', async () => {
|
||||||
|
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Empty text', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
||||||
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
|
await wrapper.setData({ text: '' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has no submit button', async () => {
|
||||||
|
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Text length less than 10', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
||||||
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
|
await wrapper.setData({ text: 'Try this' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has no submit button', async () => {
|
||||||
|
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('second radio button', () => {
|
describe('second radio button', () => {
|
||||||
@ -120,6 +233,55 @@ describe('CreationFormular', () => {
|
|||||||
it('sets rangeMax to 400', () => {
|
it('sets rangeMax to 400', () => {
|
||||||
expect(wrapper.vm.rangeMax).toBe(400)
|
expect(wrapper.vm.rangeMax).toBe(400)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('sendForm', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.find('.test-submit').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends ... to apollo', () => {
|
||||||
|
expect(apolloMutateMock).toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Negativ value', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
||||||
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
|
await wrapper.setData({ value: -20 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has no submit button', async () => {
|
||||||
|
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Empty text', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
||||||
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
|
await wrapper.setData({ text: '' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has no submit button', async () => {
|
||||||
|
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Text length less than 10', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
||||||
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
|
await wrapper.setData({ text: 'Try this' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has no submit button', async () => {
|
||||||
|
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('third radio button', () => {
|
describe('third radio button', () => {
|
||||||
@ -134,6 +296,63 @@ describe('CreationFormular', () => {
|
|||||||
it('sets rangeMax to 400', () => {
|
it('sets rangeMax to 400', () => {
|
||||||
expect(wrapper.vm.rangeMax).toBe(600)
|
expect(wrapper.vm.rangeMax).toBe(600)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('sendForm', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.find('.test-submit').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends mutation to apollo', () => {
|
||||||
|
expect(apolloMutateMock).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toast success message', () => {
|
||||||
|
expect(toastedSuccessMock).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('store commit openCreationPlus', () => {
|
||||||
|
expect(stateCommitMock).toBeCalledWith('openCreationsPlus', 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Negativ value', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
||||||
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
|
await wrapper.setData({ value: -20 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has no submit button', async () => {
|
||||||
|
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Empty text', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
||||||
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
|
await wrapper.setData({ text: '' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has no submit button', async () => {
|
||||||
|
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Text length less than 10', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
||||||
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
|
await wrapper.setData({ text: 'Try this' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has no submit button', async () => {
|
||||||
|
expect(await wrapper.find('.test-submit').attributes('disabled')).toBe('disabled')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,81 +1,75 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="component-creation-formular">
|
<div class="component-creation-formular">
|
||||||
<div>
|
<div class="shadow p-3 mb-5 bg-white rounded">
|
||||||
<h3>
|
|
||||||
{{
|
|
||||||
this.type === 'singleCreation'
|
|
||||||
? 'Einzelschöpfung für ' + item.firstName + ' ' + item.lastName + ''
|
|
||||||
: 'Mehrfachschöpfung für ' + Object.keys(this.itemsMassCreation).length + ' Mitglieder'
|
|
||||||
}}
|
|
||||||
{{ 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
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-show="this.type === 'singleCreation' || Object.keys(this.itemsMassCreation).length > 0"
|
|
||||||
class="shadow p-3 mb-5 bg-white rounded"
|
|
||||||
>
|
|
||||||
<b-form ref="creationForm">
|
<b-form ref="creationForm">
|
||||||
<b-row class="m-4">
|
<b-row class="m-4">
|
||||||
<label>Monat Auswählen</label>
|
<label>Monat Auswählen</label>
|
||||||
<b-col class="text-left">
|
<b-col class="text-left">
|
||||||
<b-form-radio
|
<b-form-radio
|
||||||
|
id="beforeLastMonth"
|
||||||
v-model="radioSelected"
|
v-model="radioSelected"
|
||||||
:value="beforeLastMonth"
|
:value="beforeLastMonth"
|
||||||
|
:disabled="creation[0] === 0"
|
||||||
size="lg"
|
size="lg"
|
||||||
@change="updateRadioSelected(beforeLastMonth, 0, creation[0])"
|
@change="updateRadioSelected(beforeLastMonth, 0, creation[0])"
|
||||||
>
|
>
|
||||||
{{ beforeLastMonth.short }} {{ creation[0] != null ? creation[0] + ' GDD' : '' }}
|
<label for="beforeLastMonth">
|
||||||
|
{{ beforeLastMonth.short }} {{ creation[0] != null ? creation[0] + ' GDD' : '' }}
|
||||||
|
</label>
|
||||||
</b-form-radio>
|
</b-form-radio>
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col>
|
<b-col>
|
||||||
<b-form-radio
|
<b-form-radio
|
||||||
|
id="lastMonth"
|
||||||
v-model="radioSelected"
|
v-model="radioSelected"
|
||||||
:value="lastMonth"
|
:value="lastMonth"
|
||||||
|
:disabled="creation[1] === 0"
|
||||||
size="lg"
|
size="lg"
|
||||||
@change="updateRadioSelected(lastMonth, 1, creation[1])"
|
@change="updateRadioSelected(lastMonth, 1, creation[1])"
|
||||||
>
|
>
|
||||||
{{ lastMonth.short }} {{ creation[1] != null ? creation[1] + ' GDD' : '' }}
|
<label for="lastMonth">
|
||||||
|
{{ lastMonth.short }} {{ creation[1] != null ? creation[1] + ' GDD' : '' }}
|
||||||
|
</label>
|
||||||
</b-form-radio>
|
</b-form-radio>
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col class="text-right">
|
<b-col class="text-right">
|
||||||
<b-form-radio
|
<b-form-radio
|
||||||
|
id="currentMonth"
|
||||||
v-model="radioSelected"
|
v-model="radioSelected"
|
||||||
:value="currentMonth"
|
:value="currentMonth"
|
||||||
|
:disabled="creation[2] === 0"
|
||||||
size="lg"
|
size="lg"
|
||||||
@change="updateRadioSelected(currentMonth, 2, creation[2])"
|
@change="updateRadioSelected(currentMonth, 2, creation[2])"
|
||||||
>
|
>
|
||||||
{{ currentMonth.short }} {{ creation[2] != null ? creation[2] + ' GDD' : '' }}
|
<label for="currentMonth">
|
||||||
|
{{ currentMonth.short }} {{ creation[2] != null ? creation[2] + ' GDD' : '' }}
|
||||||
|
</label>
|
||||||
</b-form-radio>
|
</b-form-radio>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
|
|
||||||
<b-row class="m-4">
|
<b-row class="m-4" v-show="createdIndex != null">
|
||||||
<label>Betrag Auswählen</label>
|
<label>Betrag Auswählen</label>
|
||||||
<b-input-group>
|
<div>
|
||||||
<template #append>
|
<b-input-group prepend="GDD" append=".00">
|
||||||
<b-input-group-text><strong class="text-danger">GDD</strong></b-input-group-text>
|
<b-form-input
|
||||||
</template>
|
type="number"
|
||||||
<b-form-input
|
v-model="value"
|
||||||
type="number"
|
:min="rangeMin"
|
||||||
v-model="value"
|
:max="rangeMax"
|
||||||
:min="rangeMin"
|
></b-form-input>
|
||||||
:max="rangeMax"
|
</b-input-group>
|
||||||
></b-form-input>
|
|
||||||
</b-input-group>
|
|
||||||
|
|
||||||
<b-input
|
<b-input-group prepend="0" :append="String(rangeMax)" class="mt-3">
|
||||||
id="range-2"
|
<b-form-input
|
||||||
class="mt-2"
|
type="range"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
type="range"
|
:min="rangeMin"
|
||||||
:min="rangeMin"
|
:max="rangeMax"
|
||||||
:max="rangeMax"
|
step="10"
|
||||||
step="10"
|
></b-form-input>
|
||||||
@load="checkFormForUpdate('range')"
|
</b-input-group>
|
||||||
></b-input>
|
</div>
|
||||||
</b-row>
|
</b-row>
|
||||||
<b-row class="m-4">
|
<b-row class="m-4">
|
||||||
<label>Text eintragen</label>
|
<label>Text eintragen</label>
|
||||||
@ -85,7 +79,6 @@
|
|||||||
v-model="text"
|
v-model="text"
|
||||||
:state="text.length >= 10"
|
:state="text.length >= 10"
|
||||||
placeholder="Mindestens 10 Zeichen eingeben"
|
placeholder="Mindestens 10 Zeichen eingeben"
|
||||||
@load="checkFormForUpdate('text')"
|
|
||||||
rows="3"
|
rows="3"
|
||||||
></b-form-textarea>
|
></b-form-textarea>
|
||||||
</div>
|
</div>
|
||||||
@ -102,6 +95,7 @@
|
|||||||
v-if="pagetype === 'PageCreationConfirm'"
|
v-if="pagetype === 'PageCreationConfirm'"
|
||||||
type="button"
|
type="button"
|
||||||
variant="success"
|
variant="success"
|
||||||
|
class="test-submit"
|
||||||
@click="submitCreation"
|
@click="submitCreation"
|
||||||
:disabled="radioSelected === '' || value <= 0 || text.length < 10"
|
:disabled="radioSelected === '' || value <= 0 || text.length < 10"
|
||||||
>
|
>
|
||||||
@ -112,6 +106,7 @@
|
|||||||
v-else
|
v-else
|
||||||
type="button"
|
type="button"
|
||||||
variant="success"
|
variant="success"
|
||||||
|
class="test-submit"
|
||||||
@click="submitCreation"
|
@click="submitCreation"
|
||||||
:disabled="radioSelected === '' || value <= 0 || text.length < 10"
|
:disabled="radioSelected === '' || value <= 0 || text.length < 10"
|
||||||
>
|
>
|
||||||
@ -125,6 +120,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import { verifyLogin } from '../graphql/verifyLogin'
|
||||||
|
import { createPendingCreation } from '../graphql/createPendingCreation'
|
||||||
export default {
|
export default {
|
||||||
name: 'CreationFormular',
|
name: 'CreationFormular',
|
||||||
props: {
|
props: {
|
||||||
@ -140,151 +137,143 @@ export default {
|
|||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
|
default() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default() {
|
||||||
|
return []
|
||||||
|
},
|
||||||
},
|
},
|
||||||
creationUserData: {
|
creationUserData: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
|
default() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
creation: {
|
creation: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
itemsMassCreation: {
|
|
||||||
type: Object,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
radioSelected: '',
|
radioSelected: '',
|
||||||
text: '',
|
text: !this.creationUserData.memo ? '' : this.creationUserData.memo,
|
||||||
value: 0,
|
value: !this.creationUserData.amount ? 0 : this.creationUserData.amount,
|
||||||
rangeMin: 0,
|
rangeMin: 0,
|
||||||
rangeMax: 1000,
|
rangeMax: 1000,
|
||||||
currentMonth: {
|
currentMonth: {
|
||||||
short: this.$moment().format('MMMM'),
|
short: this.$moment().format('MMMM'),
|
||||||
long: this.$moment().format('DD/MM/YYYY'),
|
long: this.$moment().format('YYYY-MM-DD'),
|
||||||
},
|
},
|
||||||
lastMonth: {
|
lastMonth: {
|
||||||
short: this.$moment().subtract(1, 'month').format('MMMM'),
|
short: this.$moment().subtract(1, 'month').format('MMMM'),
|
||||||
long: this.$moment().subtract(1, 'month').format('DD/MM/YYYY'),
|
long: this.$moment().subtract(1, 'month').format('YYYY-MM') + '-01',
|
||||||
},
|
},
|
||||||
beforeLastMonth: {
|
beforeLastMonth: {
|
||||||
short: this.$moment().subtract(2, 'month').format('MMMM'),
|
short: this.$moment().subtract(2, 'month').format('MMMM'),
|
||||||
long: this.$moment().subtract(2, 'month').format('DD/MM/YYYY'),
|
long: this.$moment().subtract(2, 'month').format('YYYY-MM') + '-01',
|
||||||
},
|
},
|
||||||
submitObj: null,
|
submitObj: null,
|
||||||
isdisabled: true,
|
isdisabled: true,
|
||||||
|
createdIndex: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
// Auswählen eines Zeitraumes
|
// Auswählen eines Zeitraumes
|
||||||
updateRadioSelected(name, index, openCreation) {
|
updateRadioSelected(name, index, openCreation) {
|
||||||
|
this.createdIndex = index
|
||||||
// Wenn Mehrfachschöpfung
|
// Wenn Mehrfachschöpfung
|
||||||
if (this.type === 'massCreation') {
|
if (this.type === 'massCreation') {
|
||||||
// An Creation.vue emitten und radioSelectedMass aktualisieren
|
// An Creation.vue emitten und radioSelectedMass aktualisieren
|
||||||
this.$emit('update-radio-selected', [name, index])
|
this.$emit('update-radio-selected', [name, index])
|
||||||
}
|
} else if (this.type === 'singleCreation') {
|
||||||
// Wenn Einzelschöpfung
|
|
||||||
if (this.type === 'singleCreation') {
|
|
||||||
this.rangeMin = 0
|
this.rangeMin = 0
|
||||||
// Der maximale offene Betrag an GDD die für ein User noch geschöpft werden kann
|
// Der maximale offene Betrag an GDD die für ein User noch geschöpft werden kann
|
||||||
this.rangeMax = openCreation
|
this.rangeMax = openCreation
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
checkFormForUpdate(input) {
|
|
||||||
switch (input) {
|
|
||||||
case 'text':
|
|
||||||
this.text = this.creationUserData.text
|
|
||||||
break
|
|
||||||
case 'range':
|
|
||||||
this.value = this.creationUserData.creationGdd
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
// TODO: Toast
|
|
||||||
alert("I don't know such values")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
submitCreation() {
|
submitCreation() {
|
||||||
// Formular Prüfen ob ein Zeitraum ausgewählt wurde. Ansonsten abbrechen und Hinweis anzeigen
|
|
||||||
if (this.radioSelected === '') {
|
|
||||||
return alert('Bitte wähle einen Zeitraum!')
|
|
||||||
}
|
|
||||||
// Formular Prüfen ob der GDD Betrag grösser 0 ist. Ansonsten abbrechen und Hinweis anzeigen
|
|
||||||
if (this.value === 0) {
|
|
||||||
return alert('Bitte gib einen GDD Betrag an!')
|
|
||||||
}
|
|
||||||
// Formular Prüfen ob der Text vorhanden ist. Ansonsten abbrechen und Hinweis anzeigen
|
|
||||||
if (this.text === '') {
|
|
||||||
return alert('Bitte gib einen Text ein!')
|
|
||||||
}
|
|
||||||
// Formular Prüfen ob der Text länger als 10 Zeichen hat. Ansonsten abbrechen und Hinweis anzeigen
|
|
||||||
if (this.text.length < 10) {
|
|
||||||
return alert('Bitte gib einen Text ein der länger als 10 Zeichen ist!')
|
|
||||||
}
|
|
||||||
if (this.type === 'massCreation') {
|
if (this.type === 'massCreation') {
|
||||||
// Die anzahl der Mitglieder aus der Mehrfachschöpfung
|
// Die anzahl der Mitglieder aus der Mehrfachschöpfung
|
||||||
const i = Object.keys(this.itemsMassCreation).length
|
const i = Object.keys(this.itemsMassCreation).length
|
||||||
// hinweis das eine Mehrfachschöpfung ausgeführt wird an (Anzahl der MItgleider an die geschöpft wird)
|
// hinweis das eine Mehrfachschöpfung ausgeführt wird an (Anzahl der MItgleider an die geschöpft wird)
|
||||||
alert('SUBMIT CREATION => ' + this.type + ' >> für VIELE ' + i + ' Mitglieder')
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('SUBMIT CREATION => ' + this.type + ' >> für VIELE ' + i + ' Mitglieder')
|
||||||
this.submitObj = [
|
this.submitObj = [
|
||||||
{
|
{
|
||||||
item: this.itemsMassCreation,
|
item: this.itemsMassCreation,
|
||||||
datum: this.radioSelected,
|
email: this.item.email,
|
||||||
|
creationDate: this.radioSelected.long,
|
||||||
amount: this.value,
|
amount: this.value,
|
||||||
text: this.text,
|
memo: this.text,
|
||||||
moderator: this.$store.state.moderator,
|
moderator: this.$store.state.moderator.id,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
alert('MehrfachSCHÖPFUNG ABSENDEN FÜR >> ' + i + ' Mitglieder')
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('MehrfachSCHÖPFUNG ABSENDEN FÜR >> ' + i + ' Mitglieder')
|
||||||
|
|
||||||
// $store - offene Schöpfungen hochzählen
|
// $store - offene Schöpfungen hochzählen
|
||||||
this.$store.commit('openCreationsPlus', i)
|
this.$store.commit('openCreationsPlus', i)
|
||||||
|
|
||||||
// lösche alle Mitglieder aus der MehrfachSchöpfungsListe nach dem alle Mehrfachschpfungen zum bestätigen gesendet wurden.
|
// lösche alle Mitglieder aus der MehrfachSchöpfungsListe nach dem alle Mehrfachschpfungen zum bestätigen gesendet wurden.
|
||||||
this.$emit('remove-all-bookmark')
|
this.$emit('remove-all-bookmark')
|
||||||
}
|
} else if (this.type === 'singleCreation') {
|
||||||
|
this.submitObj = {
|
||||||
if (this.type === 'singleCreation') {
|
email: this.item.email,
|
||||||
// hinweis das eine einzelne schöpfung ausgeführt wird an (Vorname)
|
creationDate: this.radioSelected.long,
|
||||||
alert('SUBMIT CREATION => ' + this.type + ' >> für ' + this.item.firstName + '')
|
amount: Number(this.value),
|
||||||
// erstellen eines Arrays (submitObj) mit allen Daten
|
memo: this.text,
|
||||||
this.submitObj = [
|
moderator: Number(this.$store.state.moderator.id),
|
||||||
{
|
|
||||||
item: this.item,
|
|
||||||
datum: this.radioSelected.long,
|
|
||||||
amount: this.value,
|
|
||||||
text: this.text,
|
|
||||||
moderator: this.$store.state.moderator,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
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
|
this.$apollo
|
||||||
alert(JSON.stringify(this.submitObj))
|
.mutate({
|
||||||
// das submitObj zurücksetzen
|
mutation: createPendingCreation,
|
||||||
this.submitObj = null
|
variables: this.submitObj,
|
||||||
// das creation Formular reseten
|
})
|
||||||
this.$refs.creationForm.reset()
|
.then((result) => {
|
||||||
// Den geschöpften Wert auf o setzen
|
this.$emit('update-user-data', this.item, result.data.createPendingCreation)
|
||||||
this.value = 0
|
this.$toasted.success(
|
||||||
|
`Offene Schöpfung (${this.value} GDD) für ${this.item.email} wurde gespeichert und liegen zur Bestätigung bereit`,
|
||||||
|
)
|
||||||
|
this.$store.commit('openCreationsPlus', 1)
|
||||||
|
this.submitObj = null
|
||||||
|
this.createdIndex = null
|
||||||
|
// das creation Formular reseten
|
||||||
|
this.$refs.creationForm.reset()
|
||||||
|
// Den geschöpften Wert auf o setzen
|
||||||
|
this.value = 0
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toasted.error(error.message)
|
||||||
|
this.submitObj = null
|
||||||
|
// das creation Formular reseten
|
||||||
|
this.$refs.creationForm.reset()
|
||||||
|
// Den geschöpften Wert auf o setzen
|
||||||
|
this.value = 0
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
searchModeratorData() {
|
||||||
|
this.$apollo
|
||||||
|
.query({ query: verifyLogin })
|
||||||
|
.then((result) => {
|
||||||
|
this.$store.commit('moderator', result.data.verifyLogin)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.$store.commit('moderator', { id: 0, name: 'Test Moderator' })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.searchModeratorData()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
191
admin/src/components/EditCreationFormular.spec.js
Normal file
191
admin/src/components/EditCreationFormular.spec.js
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import EditCreationFormular from './EditCreationFormular.vue'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const apolloMutateMock = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
updatePendingCreation: {
|
||||||
|
creation: [0, 0, 0],
|
||||||
|
date: new Date(),
|
||||||
|
memo: 'qwertzuiopasdfghjkl',
|
||||||
|
moderator: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const stateCommitMock = jest.fn()
|
||||||
|
const toastedErrorMock = jest.fn()
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$moment: jest.fn(() => {
|
||||||
|
return {
|
||||||
|
format: jest.fn((m) => m),
|
||||||
|
subtract: jest.fn(() => {
|
||||||
|
return {
|
||||||
|
format: jest.fn((m) => m),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
$apollo: {
|
||||||
|
mutate: apolloMutateMock,
|
||||||
|
},
|
||||||
|
$store: {
|
||||||
|
state: {
|
||||||
|
moderator: {
|
||||||
|
id: 0,
|
||||||
|
name: 'test moderator',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
commit: stateCommitMock,
|
||||||
|
},
|
||||||
|
$toasted: {
|
||||||
|
error: toastedErrorMock,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsData = {
|
||||||
|
type: '',
|
||||||
|
creation: [],
|
||||||
|
itemsMassCreation: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('EditCreationFormular', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(EditCreationFormular, { localVue, mocks, propsData })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a DIV element with the class.component-edit-creation-formular', () => {
|
||||||
|
expect(wrapper.find('.component-edit-creation-formular').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('radio buttons to selcet month', () => {
|
||||||
|
it('has three radio buttons', () => {
|
||||||
|
expect(wrapper.findAll('input[type="radio"]').length).toBe(3)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with single creation', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
||||||
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
|
await wrapper.setData({ text: 'Test create coins' })
|
||||||
|
await wrapper.setData({ value: 90 })
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('first radio button', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.findAll('input[type="radio"]').at(0).setChecked()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets rangeMin to 0', () => {
|
||||||
|
expect(wrapper.vm.rangeMin).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets rangeMax to 200', () => {
|
||||||
|
expect(wrapper.vm.rangeMax).toBe(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('sendForm', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.find('.test-submit').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends ... to apollo', () => {
|
||||||
|
expect(apolloMutateMock).toBeCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
variables: {
|
||||||
|
amount: 90,
|
||||||
|
creationDate: 'YYYY-MM-01',
|
||||||
|
email: undefined,
|
||||||
|
id: undefined,
|
||||||
|
memo: 'Test create coins',
|
||||||
|
moderator: 0,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('second radio button', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.findAll('input[type="radio"]').at(1).setChecked()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets rangeMin to 0', () => {
|
||||||
|
expect(wrapper.vm.rangeMin).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets rangeMax to 400', () => {
|
||||||
|
expect(wrapper.vm.rangeMax).toBe(400)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('sendForm', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.find('.test-submit').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends ... to apollo', () => {
|
||||||
|
expect(apolloMutateMock).toBeCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
variables: {
|
||||||
|
amount: 90,
|
||||||
|
creationDate: 'YYYY-MM-01',
|
||||||
|
email: undefined,
|
||||||
|
id: undefined,
|
||||||
|
memo: 'Test create coins',
|
||||||
|
moderator: 0,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('third radio button', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.findAll('input[type="radio"]').at(2).setChecked()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets rangeMin to 0', () => {
|
||||||
|
expect(wrapper.vm.rangeMin).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets rangeMax to 400', () => {
|
||||||
|
expect(wrapper.vm.rangeMax).toBe(600)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('sendForm', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.find('.test-submit').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends ... to apollo', () => {
|
||||||
|
expect(apolloMutateMock).toBeCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
variables: {
|
||||||
|
amount: 90,
|
||||||
|
creationDate: 'YYYY-MM-DD',
|
||||||
|
email: undefined,
|
||||||
|
id: undefined,
|
||||||
|
memo: 'Test create coins',
|
||||||
|
moderator: 0,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
273
admin/src/components/EditCreationFormular.vue
Normal file
273
admin/src/components/EditCreationFormular.vue
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
<template>
|
||||||
|
<div class="component-edit-creation-formular">
|
||||||
|
<div class="shadow p-3 mb-5 bg-white rounded">
|
||||||
|
<b-form ref="updateCreationForm">
|
||||||
|
<b-row class="m-4">
|
||||||
|
<label>Monat Auswählen</label>
|
||||||
|
<b-col class="text-left">
|
||||||
|
<b-form-radio
|
||||||
|
id="beforeLastMonth"
|
||||||
|
v-model="radioSelected"
|
||||||
|
:value="beforeLastMonth"
|
||||||
|
:disabled="selectedOpenCreationAmount[0] === 0"
|
||||||
|
size="lg"
|
||||||
|
@change="updateRadioSelected(beforeLastMonth, 0, selectedOpenCreationAmount[0])"
|
||||||
|
>
|
||||||
|
<label for="beforeLastMonth">
|
||||||
|
{{ beforeLastMonth.short }}
|
||||||
|
{{
|
||||||
|
selectedOpenCreationAmount[0] != null
|
||||||
|
? selectedOpenCreationAmount[0] + ' GDD'
|
||||||
|
: ''
|
||||||
|
}}
|
||||||
|
</label>
|
||||||
|
</b-form-radio>
|
||||||
|
</b-col>
|
||||||
|
<b-col>
|
||||||
|
<b-form-radio
|
||||||
|
id="lastMonth"
|
||||||
|
v-model="radioSelected"
|
||||||
|
:value="lastMonth"
|
||||||
|
:disabled="selectedOpenCreationAmount[1] === 0"
|
||||||
|
size="lg"
|
||||||
|
@change="updateRadioSelected(lastMonth, 1, selectedOpenCreationAmount[1])"
|
||||||
|
>
|
||||||
|
<label for="lastMonth">
|
||||||
|
{{ lastMonth.short }}
|
||||||
|
{{
|
||||||
|
selectedOpenCreationAmount[1] != null
|
||||||
|
? selectedOpenCreationAmount[1] + ' GDD'
|
||||||
|
: ''
|
||||||
|
}}
|
||||||
|
</label>
|
||||||
|
</b-form-radio>
|
||||||
|
</b-col>
|
||||||
|
<b-col class="text-right">
|
||||||
|
<b-form-radio
|
||||||
|
id="currentMonth"
|
||||||
|
v-model="radioSelected"
|
||||||
|
:value="currentMonth"
|
||||||
|
:disabled="selectedOpenCreationAmount[2] === 0"
|
||||||
|
size="lg"
|
||||||
|
@change="updateRadioSelected(currentMonth, 2, selectedOpenCreationAmount[2])"
|
||||||
|
>
|
||||||
|
<label for="currentMonth">
|
||||||
|
{{ currentMonth.short }}
|
||||||
|
{{
|
||||||
|
selectedOpenCreationAmount[2] != null
|
||||||
|
? selectedOpenCreationAmount[2] + ' GDD'
|
||||||
|
: ''
|
||||||
|
}}
|
||||||
|
</label>
|
||||||
|
</b-form-radio>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
|
||||||
|
<b-row class="m-4">
|
||||||
|
<label>Betrag Auswählen</label>
|
||||||
|
<div>
|
||||||
|
<b-input-group prepend="GDD" append=".00">
|
||||||
|
<b-form-input
|
||||||
|
type="number"
|
||||||
|
v-model="value"
|
||||||
|
:min="rangeMin"
|
||||||
|
:max="rangeMax"
|
||||||
|
></b-form-input>
|
||||||
|
</b-input-group>
|
||||||
|
|
||||||
|
<b-input-group prepend="0" :append="String(rangeMax)" class="mt-3">
|
||||||
|
<b-form-input
|
||||||
|
type="range"
|
||||||
|
v-model="value"
|
||||||
|
:min="rangeMin"
|
||||||
|
:max="rangeMax"
|
||||||
|
step="10"
|
||||||
|
></b-form-input>
|
||||||
|
</b-input-group>
|
||||||
|
</div>
|
||||||
|
</b-row>
|
||||||
|
<b-row class="m-4">
|
||||||
|
<label>Text eintragen</label>
|
||||||
|
<div>
|
||||||
|
<b-form-textarea
|
||||||
|
id="textarea-state"
|
||||||
|
v-model="text"
|
||||||
|
:state="text.length >= 10"
|
||||||
|
placeholder="Mindestens 10 Zeichen eingeben"
|
||||||
|
rows="3"
|
||||||
|
></b-form-textarea>
|
||||||
|
</div>
|
||||||
|
</b-row>
|
||||||
|
<b-row class="m-4">
|
||||||
|
<b-col class="text-center">
|
||||||
|
<b-button type="reset" variant="danger" @click="$refs.updateCreationForm.reset()">
|
||||||
|
zurücksetzen
|
||||||
|
</b-button>
|
||||||
|
</b-col>
|
||||||
|
<b-col class="text-center">
|
||||||
|
<div class="text-right">
|
||||||
|
<b-button
|
||||||
|
type="button"
|
||||||
|
variant="success"
|
||||||
|
class="test-submit"
|
||||||
|
@click="submitCreation"
|
||||||
|
:disabled="radioSelected === '' || value <= 0 || text.length < 10"
|
||||||
|
>
|
||||||
|
Update Schöpfung ({{ type }},{{ pagetype }})
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { updatePendingCreation } from '../graphql/updatePendingCreation'
|
||||||
|
export default {
|
||||||
|
name: 'EditCreationFormular',
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
pagetype: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default() {
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
creationUserData: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
creation: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
radioSelected: '',
|
||||||
|
text: !this.creationUserData.memo ? '' : this.creationUserData.memo,
|
||||||
|
value: !this.creationUserData.amount ? 0 : this.creationUserData.amount,
|
||||||
|
rangeMin: 0,
|
||||||
|
rangeMax: 1000,
|
||||||
|
currentMonth: {
|
||||||
|
short: this.$moment().format('MMMM'),
|
||||||
|
long: this.$moment().format('YYYY-MM-DD'),
|
||||||
|
},
|
||||||
|
lastMonth: {
|
||||||
|
short: this.$moment().subtract(1, 'month').format('MMMM'),
|
||||||
|
long: this.$moment().subtract(1, 'month').format('YYYY-MM') + '-01',
|
||||||
|
},
|
||||||
|
beforeLastMonth: {
|
||||||
|
short: this.$moment().subtract(2, 'month').format('MMMM'),
|
||||||
|
long: this.$moment().subtract(2, 'month').format('YYYY-MM') + '-01',
|
||||||
|
},
|
||||||
|
submitObj: null,
|
||||||
|
isdisabled: true,
|
||||||
|
createdIndex: null,
|
||||||
|
selectedOpenCreationAmount: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
updateRadioSelected(name, index, openCreation) {
|
||||||
|
this.createdIndex = index
|
||||||
|
this.rangeMin = 0
|
||||||
|
this.rangeMax = this.creation[index]
|
||||||
|
},
|
||||||
|
submitCreation() {
|
||||||
|
this.submitObj = {
|
||||||
|
id: this.item.id,
|
||||||
|
email: this.item.email,
|
||||||
|
creationDate: this.radioSelected.long,
|
||||||
|
amount: Number(this.value),
|
||||||
|
memo: this.text,
|
||||||
|
moderator: Number(this.$store.state.moderator.id),
|
||||||
|
}
|
||||||
|
|
||||||
|
// hinweis das eine ein einzelne Schöpfung abgesendet wird an (email)
|
||||||
|
this.$apollo
|
||||||
|
.mutate({
|
||||||
|
mutation: updatePendingCreation,
|
||||||
|
variables: this.submitObj,
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
this.$emit('update-user-data', this.item, result.data.updatePendingCreation.creation)
|
||||||
|
this.$emit('update-creation-data', {
|
||||||
|
amount: Number(result.data.updatePendingCreation.amount),
|
||||||
|
date: result.data.updatePendingCreation.date,
|
||||||
|
memo: result.data.updatePendingCreation.memo,
|
||||||
|
moderator: Number(result.data.updatePendingCreation.moderator),
|
||||||
|
row: this.row,
|
||||||
|
})
|
||||||
|
this.$toasted.success(
|
||||||
|
`Offene schöpfung (${this.value} GDD) für ${this.item.email} wurde geändert, liegt zur Bestätigung bereit`,
|
||||||
|
)
|
||||||
|
this.submitObj = null
|
||||||
|
this.createdIndex = null
|
||||||
|
// das creation Formular reseten
|
||||||
|
this.$refs.updateCreationForm.reset()
|
||||||
|
// Den geschöpften Wert auf o setzen
|
||||||
|
this.value = 0
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toasted.error(error.message)
|
||||||
|
this.submitObj = null
|
||||||
|
// das creation Formular reseten
|
||||||
|
this.$refs.updateCreationForm.reset()
|
||||||
|
// Den geschöpften Wert auf o setzen
|
||||||
|
this.value = 0
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.pagetype === 'PageCreationConfirm' && this.creationUserData.date) {
|
||||||
|
switch (this.$moment(this.creationUserData.date).format('MMMM')) {
|
||||||
|
case this.currentMonth.short:
|
||||||
|
this.createdIndex = 2
|
||||||
|
this.radioSelected = this.currentMonth
|
||||||
|
break
|
||||||
|
case this.lastMonth.short:
|
||||||
|
this.createdIndex = 1
|
||||||
|
this.radioSelected = this.lastMonth
|
||||||
|
break
|
||||||
|
case this.beforeLastMonth.short:
|
||||||
|
this.createdIndex = 0
|
||||||
|
this.radioSelected = this.beforeLastMonth
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error('Something went wrong')
|
||||||
|
}
|
||||||
|
this.selectedOpenCreationAmount[this.createdIndex] =
|
||||||
|
this.creation[this.createdIndex] + this.creationUserData.amount
|
||||||
|
this.rangeMax = this.selectedOpenCreationAmount[this.createdIndex]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -3,11 +3,19 @@ import NavBar from './NavBar.vue'
|
|||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const storeDispatchMock = jest.fn()
|
||||||
|
const routerPushMock = jest.fn()
|
||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
$store: {
|
$store: {
|
||||||
state: {
|
state: {
|
||||||
openCreations: 1,
|
openCreations: 1,
|
||||||
|
token: 'valid-token',
|
||||||
},
|
},
|
||||||
|
dispatch: storeDispatchMock,
|
||||||
|
},
|
||||||
|
$router: {
|
||||||
|
push: routerPushMock,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,4 +35,34 @@ describe('NavBar', () => {
|
|||||||
expect(wrapper.find('.component-nabvar').exists()).toBeTruthy()
|
expect(wrapper.find('.component-nabvar').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('wallet', () => {
|
||||||
|
const assignLocationSpy = jest.fn()
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.findAll('a').at(5).trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it.skip('changes widnow location to wallet', () => {
|
||||||
|
expect(assignLocationSpy).toBeCalledWith('valid-token')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dispatches logout to store', () => {
|
||||||
|
expect(storeDispatchMock).toBeCalledWith('logout')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('logout', () => {
|
||||||
|
// const assignLocationSpy = jest.fn()
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.findAll('a').at(6).trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('redirects to /logout', () => {
|
||||||
|
expect(routerPushMock).toBeCalledWith('/logout')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dispatches logout to store', () => {
|
||||||
|
expect(storeDispatchMock).toBeCalledWith('logout')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="component-nabvar">
|
<div class="component-nabvar">
|
||||||
<b-navbar toggleable="sm" type="dark" variant="success">
|
<b-navbar toggleable="sm" type="dark" variant="success">
|
||||||
<b-navbar-brand to="/">Adminbereich</b-navbar-brand>
|
<b-navbar-brand to="/">
|
||||||
|
<img src="img/brand/green.png" class="navbar-brand-img" alt="..." />
|
||||||
|
</b-navbar-brand>
|
||||||
|
|
||||||
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
|
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
|
||||||
|
|
||||||
<b-collapse id="nav-collapse" is-nav>
|
<b-collapse id="nav-collapse" is-nav>
|
||||||
<b-navbar-nav>
|
<b-navbar-nav>
|
||||||
|
<b-nav-item to="/">Übersicht |</b-nav-item>
|
||||||
<b-nav-item to="/user">Usersuche |</b-nav-item>
|
<b-nav-item to="/user">Usersuche |</b-nav-item>
|
||||||
<b-nav-item to="/creation">Mehrfachschöpfung</b-nav-item>
|
<b-nav-item to="/creation">Mehrfachschöpfung</b-nav-item>
|
||||||
<b-nav-item
|
<b-nav-item
|
||||||
@ -31,21 +34,6 @@ export default {
|
|||||||
name: 'navbar',
|
name: 'navbar',
|
||||||
methods: {
|
methods: {
|
||||||
logout() {
|
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.$store.dispatch('logout')
|
||||||
this.$router.push('/logout')
|
this.$router.push('/logout')
|
||||||
},
|
},
|
||||||
@ -56,3 +44,9 @@ export default {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.navbar-brand-img {
|
||||||
|
height: 2rem;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -14,11 +14,11 @@
|
|||||||
{{ overlayText.text2 }}
|
{{ overlayText.text2 }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<b-button size="lg" variant="danger" class="m-3" @click="overlayCancel">
|
<b-button size="md" variant="danger" class="m-3" @click="overlayCancel">
|
||||||
{{ overlayText.button_cancel }}
|
{{ overlayText.button_cancel }}
|
||||||
</b-button>
|
</b-button>
|
||||||
<b-button
|
<b-button
|
||||||
size="lg"
|
size="md"
|
||||||
variant="success"
|
variant="success"
|
||||||
class="m-3 text-right"
|
class="m-3 text-right"
|
||||||
@click="overlayOK(overlayBookmarkType, overlayItem)"
|
@click="overlayOK(overlayBookmarkType, overlayItem)"
|
||||||
@ -39,7 +39,7 @@
|
|||||||
<template #cell(edit_creation)="row">
|
<template #cell(edit_creation)="row">
|
||||||
<b-button
|
<b-button
|
||||||
variant="info"
|
variant="info"
|
||||||
size="lg"
|
size="md"
|
||||||
@click="editCreationUserTable(row, row.item)"
|
@click="editCreationUserTable(row, row.item)"
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
>
|
>
|
||||||
@ -49,7 +49,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #cell(show_details)="row">
|
<template #cell(show_details)="row">
|
||||||
<b-button variant="info" size="lg" @click="row.toggleDetails" class="mr-2">
|
<b-button variant="info" size="md" @click="row.toggleDetails" class="mr-2">
|
||||||
<b-icon v-if="row.detailsShowing" icon="eye-slash-fill" aria-label="Help"></b-icon>
|
<b-icon v-if="row.detailsShowing" icon="eye-slash-fill" aria-label="Help"></b-icon>
|
||||||
<b-icon v-else icon="eye-fill" aria-label="Help"></b-icon>
|
<b-icon v-else icon="eye-fill" aria-label="Help"></b-icon>
|
||||||
</b-button>
|
</b-button>
|
||||||
@ -60,14 +60,27 @@
|
|||||||
<b-row class="mb-2">
|
<b-row class="mb-2">
|
||||||
<b-col></b-col>
|
<b-col></b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
|
{{ type }}
|
||||||
<creation-formular
|
<creation-formular
|
||||||
|
v-if="type === 'PageUserSearch'"
|
||||||
type="singleCreation"
|
type="singleCreation"
|
||||||
:pagetype="type"
|
:pagetype="type"
|
||||||
:creation="row.item.creation"
|
:creation="row.item.creation"
|
||||||
:item="row.item"
|
:item="row.item"
|
||||||
:creationUserData="creationData"
|
:creationUserData="creationUserData"
|
||||||
@update-creation-data="updateCreationData"
|
@update-creation-data="updateCreationData"
|
||||||
|
@update-user-data="updateUserData"
|
||||||
|
/>
|
||||||
|
<edit-creation-formular
|
||||||
|
v-else
|
||||||
|
type="singleCreation"
|
||||||
|
:pagetype="type"
|
||||||
|
:creation="row.item.creation"
|
||||||
|
:item="row.item"
|
||||||
|
:row="row"
|
||||||
|
:creationUserData="creationUserData"
|
||||||
|
@update-creation-data="updateCreationData"
|
||||||
|
@update-user-data="updateUserData"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<b-button size="sm" @click="row.toggleDetails">
|
<b-button size="sm" @click="row.toggleDetails">
|
||||||
@ -93,7 +106,7 @@
|
|||||||
<b-button
|
<b-button
|
||||||
variant="danger"
|
variant="danger"
|
||||||
v-show="type === 'UserListMassCreation' || type === 'PageCreationConfirm'"
|
v-show="type === 'UserListMassCreation' || type === 'PageCreationConfirm'"
|
||||||
size="lg"
|
size="md"
|
||||||
@click="overlayShow('remove', row.item)"
|
@click="overlayShow('remove', row.item)"
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
>
|
>
|
||||||
@ -105,7 +118,7 @@
|
|||||||
<b-button
|
<b-button
|
||||||
variant="success"
|
variant="success"
|
||||||
v-show="type === 'PageCreationConfirm'"
|
v-show="type === 'PageCreationConfirm'"
|
||||||
size="lg"
|
size="md"
|
||||||
@click="overlayShow('confirm', row.item)"
|
@click="overlayShow('confirm', row.item)"
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
>
|
>
|
||||||
@ -118,6 +131,8 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import CreationFormular from '../components/CreationFormular.vue'
|
import CreationFormular from '../components/CreationFormular.vue'
|
||||||
|
import EditCreationFormular from '../components/EditCreationFormular.vue'
|
||||||
|
import { confirmPendingCreation } from '../graphql/confirmPendingCreation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'UserTable',
|
name: 'UserTable',
|
||||||
@ -146,10 +161,11 @@ export default {
|
|||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
CreationFormular,
|
CreationFormular,
|
||||||
|
EditCreationFormular,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
creationData: {},
|
creationUserData: {},
|
||||||
overlay: false,
|
overlay: false,
|
||||||
overlayBookmarkType: '',
|
overlayBookmarkType: '',
|
||||||
overlayItem: [],
|
overlayItem: [],
|
||||||
@ -213,24 +229,38 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
bookmarkConfirm(item) {
|
bookmarkConfirm(item) {
|
||||||
alert('die schöpfung bestätigen und abschließen')
|
this.$apollo
|
||||||
alert(JSON.stringify(item))
|
.mutate({
|
||||||
this.$emit('remove-confirm-result', item, 'remove')
|
mutation: confirmPendingCreation,
|
||||||
|
variables: {
|
||||||
|
id: item.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.$emit('remove-confirm-result', item, 'remove')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toasted.error(error.message)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
editCreationUserTable(row, rowItem) {
|
editCreationUserTable(row, rowItem) {
|
||||||
alert('editCreationUserTable')
|
|
||||||
if (!row.detailsShowing) {
|
if (!row.detailsShowing) {
|
||||||
alert('offen edit loslegen')
|
this.creationUserData = rowItem
|
||||||
// this.item = rowItem
|
} else {
|
||||||
this.creationData = rowItem
|
this.creationUserData = {}
|
||||||
// alert(this.creationData)
|
|
||||||
}
|
}
|
||||||
row.toggleDetails()
|
row.toggleDetails()
|
||||||
},
|
},
|
||||||
updateCreationData(data) {
|
updateCreationData(data) {
|
||||||
this.creationData = {
|
this.creationUserData.amount = data.amount
|
||||||
...data,
|
this.creationUserData.date = data.date
|
||||||
}
|
this.creationUserData.memo = data.memo
|
||||||
|
this.creationUserData.moderator = data.moderator
|
||||||
|
|
||||||
|
data.row.toggleDetails()
|
||||||
|
},
|
||||||
|
updateUserData(rowItem, newCreation) {
|
||||||
|
rowItem.creation = newCreation
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
7
admin/src/graphql/confirmPendingCreation.js
Normal file
7
admin/src/graphql/confirmPendingCreation.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export const confirmPendingCreation = gql`
|
||||||
|
mutation ($id: Float!) {
|
||||||
|
confirmPendingCreation(id: $id)
|
||||||
|
}
|
||||||
|
`
|
||||||
19
admin/src/graphql/createPendingCreation.js
Normal file
19
admin/src/graphql/createPendingCreation.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export const createPendingCreation = gql`
|
||||||
|
mutation (
|
||||||
|
$email: String!
|
||||||
|
$amount: Int!
|
||||||
|
$memo: String!
|
||||||
|
$creationDate: String!
|
||||||
|
$moderator: Int!
|
||||||
|
) {
|
||||||
|
createPendingCreation(
|
||||||
|
email: $email
|
||||||
|
amount: $amount
|
||||||
|
memo: $memo
|
||||||
|
creationDate: $creationDate
|
||||||
|
moderator: $moderator
|
||||||
|
)
|
||||||
|
}
|
||||||
|
`
|
||||||
7
admin/src/graphql/deletePendingCreation.js
Normal file
7
admin/src/graphql/deletePendingCreation.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export const deletePendingCreation = gql`
|
||||||
|
mutation ($id: Float!) {
|
||||||
|
deletePendingCreation(id: $id)
|
||||||
|
}
|
||||||
|
`
|
||||||
17
admin/src/graphql/getPendingCreations.js
Normal file
17
admin/src/graphql/getPendingCreations.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export const getPendingCreations = gql`
|
||||||
|
query {
|
||||||
|
getPendingCreations {
|
||||||
|
id
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
email
|
||||||
|
amount
|
||||||
|
memo
|
||||||
|
date
|
||||||
|
moderator
|
||||||
|
creation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
27
admin/src/graphql/updatePendingCreation.js
Normal file
27
admin/src/graphql/updatePendingCreation.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export const updatePendingCreation = gql`
|
||||||
|
mutation (
|
||||||
|
$id: Int!
|
||||||
|
$email: String!
|
||||||
|
$amount: Int!
|
||||||
|
$memo: String!
|
||||||
|
$creationDate: String!
|
||||||
|
$moderator: Int!
|
||||||
|
) {
|
||||||
|
updatePendingCreation(
|
||||||
|
id: $id
|
||||||
|
email: $email
|
||||||
|
amount: $amount
|
||||||
|
memo: $memo
|
||||||
|
creationDate: $creationDate
|
||||||
|
moderator: $moderator
|
||||||
|
) {
|
||||||
|
amount
|
||||||
|
date
|
||||||
|
memo
|
||||||
|
creation
|
||||||
|
moderator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
12
admin/src/graphql/verifyLogin.js
Normal file
12
admin/src/graphql/verifyLogin.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export const verifyLogin = gql`
|
||||||
|
query {
|
||||||
|
verifyLogin {
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
isAdmin
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
@ -11,48 +11,16 @@ import addNavigationGuards from './router/guards'
|
|||||||
|
|
||||||
import i18n from './i18n'
|
import i18n from './i18n'
|
||||||
|
|
||||||
import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
|
|
||||||
import VueApollo from 'vue-apollo'
|
import VueApollo from 'vue-apollo'
|
||||||
|
|
||||||
import CONFIG from './config'
|
|
||||||
|
|
||||||
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
||||||
import 'bootstrap/dist/css/bootstrap.css'
|
import 'bootstrap/dist/css/bootstrap.css'
|
||||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||||
|
|
||||||
import moment from 'vue-moment'
|
import moment from 'vue-moment'
|
||||||
|
import Toasted from 'vue-toasted'
|
||||||
|
|
||||||
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
|
import { apolloProvider } from './plugins/apolloProvider'
|
||||||
|
|
||||||
const authLink = new ApolloLink((operation, forward) => {
|
|
||||||
const token = store.state.token
|
|
||||||
|
|
||||||
operation.setContext({
|
|
||||||
headers: {
|
|
||||||
Authorization: token && token.length > 0 ? `Bearer ${token}` : '',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return forward(operation).map((response) => {
|
|
||||||
if (response.errors && response.errors[0].message === '403.13 - Client certificate revoked') {
|
|
||||||
response.errors[0].message = i18n.t('error.session-expired')
|
|
||||||
store.dispatch('logout', null)
|
|
||||||
if (router.currentRoute.path !== '/logout') router.push('/logout')
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
const newToken = operation.getContext().response.headers.get('token')
|
|
||||||
if (newToken) store.commit('token', newToken)
|
|
||||||
return response
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const apolloClient = new ApolloClient({
|
|
||||||
link: authLink.concat(httpLink),
|
|
||||||
cache: new InMemoryCache(),
|
|
||||||
})
|
|
||||||
|
|
||||||
const apolloProvider = new VueApollo({
|
|
||||||
defaultClient: apolloClient,
|
|
||||||
})
|
|
||||||
|
|
||||||
Vue.use(BootstrapVue)
|
Vue.use(BootstrapVue)
|
||||||
|
|
||||||
@ -62,7 +30,19 @@ Vue.use(moment)
|
|||||||
|
|
||||||
Vue.use(VueApollo)
|
Vue.use(VueApollo)
|
||||||
|
|
||||||
addNavigationGuards(router, store)
|
Vue.use(Toasted, {
|
||||||
|
position: 'top-center',
|
||||||
|
duration: 5000,
|
||||||
|
fullWidth: true,
|
||||||
|
action: {
|
||||||
|
text: 'x',
|
||||||
|
onClick: (e, toastObject) => {
|
||||||
|
toastObject.goAway(0)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
addNavigationGuards(router, store, apolloProvider.defaultClient)
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
moment,
|
moment,
|
||||||
|
|||||||
@ -3,18 +3,21 @@ import './main'
|
|||||||
import CONFIG from './config'
|
import CONFIG from './config'
|
||||||
|
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import VueApollo from 'vue-apollo'
|
||||||
import VueI18n from 'vue-i18n'
|
import i18n from './i18n'
|
||||||
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
||||||
import moment from 'vue-moment'
|
import moment from 'vue-moment'
|
||||||
|
import store from './store/store'
|
||||||
|
import router from './router/router'
|
||||||
|
|
||||||
jest.mock('vue')
|
jest.mock('vue')
|
||||||
|
jest.mock('vue-apollo')
|
||||||
jest.mock('vuex')
|
jest.mock('vuex')
|
||||||
jest.mock('vue-i18n')
|
jest.mock('vue-i18n')
|
||||||
jest.mock('vue-moment')
|
jest.mock('vue-moment')
|
||||||
|
jest.mock('./store/store')
|
||||||
const storeMock = jest.fn()
|
jest.mock('./i18n')
|
||||||
Vuex.Store = storeMock
|
jest.mock('./router/router')
|
||||||
|
|
||||||
jest.mock('apollo-boost', () => {
|
jest.mock('apollo-boost', () => {
|
||||||
return {
|
return {
|
||||||
@ -55,27 +58,47 @@ describe('main', () => {
|
|||||||
expect(InMemoryCache).toBeCalled()
|
expect(InMemoryCache).toBeCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('calls the VueApollo', () => {
|
||||||
|
expect(VueApollo).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
it('calls Vue', () => {
|
it('calls Vue', () => {
|
||||||
expect(Vue).toBeCalled()
|
expect(Vue).toBeCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calls VueI18n', () => {
|
it('calls i18n', () => {
|
||||||
expect(VueI18n).toBeCalled()
|
expect(Vue).toBeCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
i18n,
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('calls BootstrapVue', () => {
|
it('calls BootstrapVue', () => {
|
||||||
expect(BootstrapVue).toBeCalled()
|
expect(Vue.use).toBeCalledWith(BootstrapVue)
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('calls IconsPlugin', () => {
|
it('calls IconsPlugin', () => {
|
||||||
expect(IconsPlugin).toBeCalled()
|
expect(Vue.use).toBeCalledWith(IconsPlugin)
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('calls Moment', () => {
|
it('calls Moment', () => {
|
||||||
expect(moment).toBeCalled()
|
expect(Vue.use).toBeCalledWith(moment)
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('creates a store', () => {
|
it('creates a store', () => {
|
||||||
expect(storeMock).toBeCalled()
|
expect(Vue).toBeCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
store,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a router', () => {
|
||||||
|
expect(Vue).toBeCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
router,
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -21,24 +21,26 @@
|
|||||||
</b-col>
|
</b-col>
|
||||||
<b-col cols="12" lg="7" class="shadow p-3 mb-5 rounded bg-info">
|
<b-col cols="12" lg="7" class="shadow p-3 mb-5 rounded bg-info">
|
||||||
<user-table
|
<user-table
|
||||||
v-if="massCreation.length > 0"
|
v-show="itemsMassCreation.length > 0"
|
||||||
class="shadow p-3 mb-5 bg-white rounded"
|
class="shadow p-3 mb-5 bg-white rounded"
|
||||||
type="UserListMassCreation"
|
type="UserListMassCreation"
|
||||||
:itemsUser="massCreation"
|
:itemsUser="itemsMassCreation"
|
||||||
:fieldsTable="fields"
|
:fieldsTable="fields"
|
||||||
:criteria="null"
|
:criteria="null"
|
||||||
:creation="creation"
|
:creation="creation"
|
||||||
@update-item="updateItem"
|
@update-item="updateItem"
|
||||||
/>
|
/>
|
||||||
|
<div v-if="itemsMassCreation.length === 0">
|
||||||
|
Bitte wähle ein oder Mehrere Mitglieder aus für die du Schöpfen möchtest
|
||||||
|
</div>
|
||||||
<creation-formular
|
<creation-formular
|
||||||
v-if="massCreation.length > 0"
|
v-else
|
||||||
type="massCreation"
|
type="massCreation"
|
||||||
:creation="creation"
|
:creation="creation"
|
||||||
:itemsMassCreation="massCreation"
|
:items="itemsMassCreation"
|
||||||
@update-radio-selected="updateRadioSelected"
|
|
||||||
@remove-all-bookmark="removeAllBookmark"
|
@remove-all-bookmark="removeAllBookmark"
|
||||||
/>
|
/>
|
||||||
|
{{ itemsMassCreation }}
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
</div>
|
</div>
|
||||||
@ -72,7 +74,7 @@ export default {
|
|||||||
{ key: 'bookmark', label: 'löschen' },
|
{ key: 'bookmark', label: 'löschen' },
|
||||||
],
|
],
|
||||||
itemsList: [],
|
itemsList: [],
|
||||||
massCreation: [],
|
itemsMassCreation: [],
|
||||||
radioSelectedMass: '',
|
radioSelectedMass: '',
|
||||||
criteria: '',
|
criteria: '',
|
||||||
creation: [null, null, null],
|
creation: [null, null, null],
|
||||||
@ -111,12 +113,12 @@ export default {
|
|||||||
findArr = this.itemsList.find((arr) => arr.id === e.id)
|
findArr = this.itemsList.find((arr) => arr.id === e.id)
|
||||||
index = this.itemsList.indexOf(findArr)
|
index = this.itemsList.indexOf(findArr)
|
||||||
this.itemsList.splice(index, 1)
|
this.itemsList.splice(index, 1)
|
||||||
this.massCreation.push(e)
|
this.itemsMassCreation.push(e)
|
||||||
break
|
break
|
||||||
case 'remove':
|
case 'remove':
|
||||||
findArr = this.massCreation.find((arr) => arr.id === e.id)
|
findArr = this.itemsMassCreation.find((arr) => arr.id === e.id)
|
||||||
index = this.massCreation.indexOf(findArr)
|
index = this.itemsMassCreation.indexOf(findArr)
|
||||||
this.massCreation.splice(index, 1)
|
this.itemsMassCreation.splice(index, 1)
|
||||||
this.itemsList.push(e)
|
this.itemsList.push(e)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
@ -124,19 +126,19 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateRadioSelected(obj) {
|
// updateRadioSelected(obj) {
|
||||||
this.radioSelectedMass = obj[0]
|
// this.radioSelectedMass = obj[0]
|
||||||
},
|
// },
|
||||||
|
|
||||||
removeAllBookmark() {
|
removeAllBookmark() {
|
||||||
alert('remove all bookmarks')
|
alert('remove all bookmarks')
|
||||||
const index = 0
|
const index = 0
|
||||||
let i = 0
|
let i = 0
|
||||||
|
|
||||||
for (i; i < this.massCreation.length; i++) {
|
for (i; i < this.itemsMassCreation.length; i++) {
|
||||||
this.itemsList.push(this.massCreation[i])
|
this.itemsList.push(this.itemsMassCreation[i])
|
||||||
}
|
}
|
||||||
this.massCreation.splice(index, this.massCreation.length)
|
this.itemsMassCreation.splice(index, this.itemsMassCreation.length)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,58 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import CreationConfirm from './CreationConfirm.vue'
|
import CreationConfirm from './CreationConfirm.vue'
|
||||||
|
import { deletePendingCreation } from '../graphql/deletePendingCreation'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
const storeCommitMock = jest.fn()
|
const storeCommitMock = jest.fn()
|
||||||
|
const toastedErrorMock = jest.fn()
|
||||||
|
const toastedSuccessMock = jest.fn()
|
||||||
|
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
getPendingCreations: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
firstName: 'Bibi',
|
||||||
|
lastName: 'Bloxberg',
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
amount: 500,
|
||||||
|
memo: 'Danke für alles',
|
||||||
|
date: new Date(),
|
||||||
|
moderator: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
firstName: 'Räuber',
|
||||||
|
lastName: 'Hotzenplotz',
|
||||||
|
email: 'raeuber@hotzenplotz.de',
|
||||||
|
amount: 1000000,
|
||||||
|
memo: 'Gut Ergatert',
|
||||||
|
date: new Date(),
|
||||||
|
moderator: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const apolloMutateMock = jest.fn().mockResolvedValue({})
|
||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
$store: {
|
$store: {
|
||||||
commit: storeCommitMock,
|
commit: storeCommitMock,
|
||||||
},
|
},
|
||||||
|
$apollo: {
|
||||||
|
query: apolloQueryMock,
|
||||||
|
mutate: apolloMutateMock,
|
||||||
|
},
|
||||||
|
$toasted: {
|
||||||
|
error: toastedErrorMock,
|
||||||
|
success: toastedSuccessMock,
|
||||||
|
},
|
||||||
|
$moment: jest.fn((value) => {
|
||||||
|
return {
|
||||||
|
format: jest.fn((format) => value),
|
||||||
|
}
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('CreationConfirm', () => {
|
describe('CreationConfirm', () => {
|
||||||
@ -32,22 +76,85 @@ describe('CreationConfirm', () => {
|
|||||||
it('commits resetOpenCreations to store', () => {
|
it('commits resetOpenCreations to store', () => {
|
||||||
expect(storeCommitMock).toBeCalledWith('resetOpenCreations')
|
expect(storeCommitMock).toBeCalledWith('resetOpenCreations')
|
||||||
})
|
})
|
||||||
|
it('commits setOpenCreations to store', () => {
|
||||||
it('commits openCreationsPlus to store', () => {
|
expect(storeCommitMock).toBeCalledWith('setOpenCreations', 2)
|
||||||
expect(storeCommitMock).toBeCalledWith('openCreationsPlus', 5)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('confirm creation', () => {
|
describe('confirm creation delete with success', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
apolloQueryMock.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
getPendingCreations: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
firstName: 'Bibi',
|
||||||
|
lastName: 'Bloxberg',
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
amount: 500,
|
||||||
|
memo: 'Danke für alles',
|
||||||
|
date: new Date(),
|
||||||
|
moderator: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
firstName: 'Räuber',
|
||||||
|
lastName: 'Hotzenplotz',
|
||||||
|
email: 'raeuber@hotzenplotz.de',
|
||||||
|
amount: 1000000,
|
||||||
|
memo: 'Gut Ergatert',
|
||||||
|
date: new Date(),
|
||||||
|
moderator: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
await wrapper
|
await wrapper
|
||||||
.findComponent({ name: 'UserTable' })
|
.findComponent({ name: 'UserTable' })
|
||||||
.vm.$emit('remove-confirm-result', 1, 'remove')
|
.vm.$emit('remove-confirm-result', { id: 1 }, 'remove')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the deletePendingCreation mutation', () => {
|
||||||
|
expect(apolloMutateMock).toBeCalledWith({
|
||||||
|
mutation: deletePendingCreation,
|
||||||
|
variables: { id: 1 },
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('commits openCreationsMinus to store', () => {
|
it('commits openCreationsMinus to store', () => {
|
||||||
expect(storeCommitMock).toBeCalledWith('openCreationsMinus', 1)
|
expect(storeCommitMock).toBeCalledWith('openCreationsMinus', 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('toasts a success message', () => {
|
||||||
|
expect(toastedSuccessMock).toBeCalledWith('Pending Creation has been deleted')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('confirm creation delete with error', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
apolloMutateMock.mockRejectedValue({ message: 'Ouchhh!' })
|
||||||
|
await wrapper
|
||||||
|
.findComponent({ name: 'UserTable' })
|
||||||
|
.vm.$emit('remove-confirm-result', { id: 1 }, 'remove')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toasts an error message', () => {
|
||||||
|
expect(toastedErrorMock).toBeCalledWith('Ouchhh!')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('server response is error', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
apolloQueryMock.mockRejectedValue({
|
||||||
|
message: 'Ouch!',
|
||||||
|
})
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toast an error message', () => {
|
||||||
|
expect(toastedErrorMock).toBeCalledWith('Ouch!')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,9 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="creation-confirm">
|
<div class="creation-confirm">
|
||||||
<small class="bg-danger text-light p-1">
|
|
||||||
Die anzahl der offene Schöpfungen stimmen nicht! Diese wird bei absenden im $store
|
|
||||||
hochgezählt. Die Liste die hier angezeigt wird ist SIMULIERT!
|
|
||||||
</small>
|
|
||||||
<user-table
|
<user-table
|
||||||
class="mt-4"
|
class="mt-4"
|
||||||
type="PageCreationConfirm"
|
type="PageCreationConfirm"
|
||||||
@ -15,6 +11,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import UserTable from '../components/UserTable.vue'
|
import UserTable from '../components/UserTable.vue'
|
||||||
|
import { getPendingCreations } from '../graphql/getPendingCreations'
|
||||||
|
import { deletePendingCreation } from '../graphql/deletePendingCreation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CreationConfirm',
|
name: 'CreationConfirm',
|
||||||
@ -30,120 +28,68 @@ export default {
|
|||||||
{ key: 'firstName', label: 'Vorname' },
|
{ key: 'firstName', label: 'Vorname' },
|
||||||
{ key: 'lastName', label: 'Nachname' },
|
{ key: 'lastName', label: 'Nachname' },
|
||||||
{
|
{
|
||||||
key: 'creation_gdd',
|
key: 'amount',
|
||||||
label: 'Schöpfung',
|
label: 'Schöpfung',
|
||||||
formatter: (value) => {
|
formatter: (value) => {
|
||||||
return value + ' GDD'
|
return value + ' GDD'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ key: 'text', label: 'Text' },
|
{ key: 'memo', label: 'Text' },
|
||||||
{
|
{
|
||||||
key: 'creation_date',
|
key: 'date',
|
||||||
label: 'Datum',
|
label: 'Datum',
|
||||||
formatter: (value) => {
|
formatter: (value) => {
|
||||||
return value.long
|
return this.$moment(value).format('ll')
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ key: 'creation_moderator', label: 'Moderator' },
|
{ key: 'moderator', label: 'Moderator' },
|
||||||
{ key: 'edit_creation', label: 'ändern' },
|
{ key: 'edit_creation', label: 'ändern' },
|
||||||
{ key: 'confirm', label: 'speichern' },
|
{ key: 'confirm', label: 'speichern' },
|
||||||
],
|
],
|
||||||
confirmResult: [
|
confirmResult: [],
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
email: 'dickerson@web.de',
|
|
||||||
firstName: 'Dickerson',
|
|
||||||
lastName: 'Macdonald',
|
|
||||||
creation: '[450,200,700]',
|
|
||||||
creation_gdd: '1000',
|
|
||||||
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam ',
|
|
||||||
|
|
||||||
creation_date: {
|
|
||||||
short: 'November',
|
|
||||||
long: '22/11/2021',
|
|
||||||
},
|
|
||||||
creation_moderator: 'Manuela Gast',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
email: 'larsen@woob.de',
|
|
||||||
firstName: 'Larsen',
|
|
||||||
lastName: 'Shaw',
|
|
||||||
creation: '[300,200,1000]',
|
|
||||||
creation_gdd: '1000',
|
|
||||||
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam ',
|
|
||||||
|
|
||||||
creation_date: {
|
|
||||||
short: 'November',
|
|
||||||
long: '03/11/2021',
|
|
||||||
},
|
|
||||||
creation_moderator: 'Manuela Gast',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
email: 'geneva@tete.de',
|
|
||||||
firstName: 'Geneva',
|
|
||||||
lastName: 'Wilson',
|
|
||||||
creation: '[350,200,900]',
|
|
||||||
creation_gdd: '1000',
|
|
||||||
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam',
|
|
||||||
creation_date: {
|
|
||||||
short: 'September',
|
|
||||||
long: '27/09/2021',
|
|
||||||
},
|
|
||||||
creation_moderator: 'Manuela Gast',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
email: 'viewrter@asdfvb.com',
|
|
||||||
firstName: 'Soledare',
|
|
||||||
lastName: 'Takker',
|
|
||||||
creation: '[100,400,800]',
|
|
||||||
creation_gdd: '500',
|
|
||||||
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo ',
|
|
||||||
creation_date: {
|
|
||||||
short: 'Oktober',
|
|
||||||
long: '12/10/2021',
|
|
||||||
},
|
|
||||||
creation_moderator: 'Evelyn Roller',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
email: 'dickerson@web.de',
|
|
||||||
firstName: 'Dickerson',
|
|
||||||
lastName: 'Macdonald',
|
|
||||||
creation: '[100,400,800]',
|
|
||||||
creation_gdd: '200',
|
|
||||||
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At',
|
|
||||||
creation_date: {
|
|
||||||
short: 'September',
|
|
||||||
long: '05/09/2021',
|
|
||||||
},
|
|
||||||
creation_moderator: 'Manuela Gast',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
removeConfirmResult(e, event) {
|
removeConfirmResult(e, event) {
|
||||||
if (event === 'remove') {
|
if (event === 'remove') {
|
||||||
let index = 0
|
let index = 0
|
||||||
let findArr = {}
|
const findArr = this.confirmResult.find((arr) => arr.id === e.id)
|
||||||
|
this.$apollo
|
||||||
findArr = this.confirmResult.find((arr) => arr.id === e.id)
|
.mutate({
|
||||||
|
mutation: deletePendingCreation,
|
||||||
index = this.confirmResult.indexOf(findArr)
|
variables: {
|
||||||
|
id: findArr.id,
|
||||||
this.confirmResult.splice(index, 1)
|
},
|
||||||
|
})
|
||||||
this.$store.commit('openCreationsMinus', 1)
|
.then((result) => {
|
||||||
|
index = this.confirmResult.indexOf(findArr)
|
||||||
|
this.confirmResult.splice(index, 1)
|
||||||
|
this.$store.commit('openCreationsMinus', 1)
|
||||||
|
this.$toasted.success('Pending Creation has been deleted')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toasted.error(error.message)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getPendingCreations() {
|
||||||
|
this.$apollo
|
||||||
|
.query({
|
||||||
|
query: getPendingCreations,
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
this.$store.commit('resetOpenCreations')
|
||||||
|
this.confirmResult = result.data.getPendingCreations.reverse()
|
||||||
|
this.$store.commit('setOpenCreations', result.data.getPendingCreations.length)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toasted.error(error.message)
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
created() {
|
async created() {
|
||||||
this.$store.commit('resetOpenCreations')
|
await this.getPendingCreations()
|
||||||
this.$store.commit('openCreationsPlus', Object.keys(this.confirmResult).length)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
74
admin/src/pages/Overview.spec.js
Normal file
74
admin/src/pages/Overview.spec.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import Overview from './Overview.vue'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
getPendingCreations: [
|
||||||
|
{
|
||||||
|
pending: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pending: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pending: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const storeCommitMock = jest.fn()
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$apollo: {
|
||||||
|
query: apolloQueryMock,
|
||||||
|
},
|
||||||
|
$store: {
|
||||||
|
commit: storeCommitMock,
|
||||||
|
state: {
|
||||||
|
openCreations: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Overview', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(Overview, { localVue, mocks })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls getPendingCreations', () => {
|
||||||
|
expect(apolloQueryMock).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('commts three pending creations to store', () => {
|
||||||
|
expect(storeCommitMock).toBeCalledWith('setOpenCreations', 3)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with open creations', () => {
|
||||||
|
it('renders a link to confirm creations', () => {
|
||||||
|
expect(wrapper.find('a[href="creation-confirm"]').text()).toContain('2')
|
||||||
|
expect(wrapper.find('a[href="creation-confirm"]').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('without open creations', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.$store.state.openCreations = 0
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders a link to confirm creations', () => {
|
||||||
|
expect(wrapper.find('a[href="creation-confirm"]').text()).toContain('0')
|
||||||
|
expect(wrapper.find('a[href="creation-confirm"]').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="admin-overview">
|
||||||
<b-card
|
<b-card
|
||||||
v-show="$store.state.openCreations > 0"
|
v-show="$store.state.openCreations > 0"
|
||||||
border-variant="primary"
|
border-variant="primary"
|
||||||
@ -29,26 +29,6 @@
|
|||||||
</b-card-text>
|
</b-card-text>
|
||||||
</b-card>
|
</b-card>
|
||||||
<br />
|
<br />
|
||||||
<b-row>
|
|
||||||
<b-col>
|
|
||||||
<b-card border-variant="info" header="offene Registrierung" align="center">
|
|
||||||
<b-card-text>Unbestätigte E-mail Registrierung</b-card-text>
|
|
||||||
</b-card>
|
|
||||||
</b-col>
|
|
||||||
<b-col>
|
|
||||||
<b-card border-variant="info" header="geschöpfte Stunden" align="center">
|
|
||||||
<b-card-text>Wievile Stunden können noch von Mitgliedern geschöpft werden?</b-card-text>
|
|
||||||
</b-card>
|
|
||||||
</b-col>
|
|
||||||
<b-col>
|
|
||||||
<b-card border-variant="info" header="Gemeinschafts Konto" align="center">
|
|
||||||
<b-card-text>
|
|
||||||
Für jedes Mitglied kann für das Gemeinschaftskonto geschöpft werden. Pro Monat 1000 x
|
|
||||||
Mitglieder
|
|
||||||
</b-card-text>
|
|
||||||
</b-card>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<hr />
|
<hr />
|
||||||
<br />
|
<br />
|
||||||
<b-list-group>
|
<b-list-group>
|
||||||
@ -57,26 +37,40 @@
|
|||||||
</b-list-group-item>
|
</b-list-group-item>
|
||||||
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
||||||
Mitglieder
|
Mitglieder
|
||||||
<b-badge class="bg-success" pill>14</b-badge>
|
<b-badge class="bg-success" pill>2400</b-badge>
|
||||||
</b-list-group-item>
|
</b-list-group-item>
|
||||||
|
|
||||||
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
||||||
aktive Mitglieder
|
aktive Mitglieder
|
||||||
<b-badge class="bg-primary" pill>12</b-badge>
|
<b-badge class="bg-primary" pill>2201</b-badge>
|
||||||
</b-list-group-item>
|
</b-list-group-item>
|
||||||
|
|
||||||
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
||||||
nicht bestätigte Mitglieder
|
nicht bestätigte Mitglieder
|
||||||
<b-badge class="bg-warning text-dark" pill>2</b-badge>
|
<b-badge class="bg-warning text-dark" pill>120</b-badge>
|
||||||
</b-list-group-item>
|
</b-list-group-item>
|
||||||
</b-list-group>
|
</b-list-group>
|
||||||
<b-button @click="$store.commit('resetOpenCreations')">
|
|
||||||
lösche alle offenen Test Schöpfungen
|
|
||||||
</b-button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import { getPendingCreations } from '../graphql/getPendingCreations'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'overview',
|
name: 'overview',
|
||||||
|
methods: {
|
||||||
|
async getPendingCreations() {
|
||||||
|
this.$apollo
|
||||||
|
.query({
|
||||||
|
query: getPendingCreations,
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
this.$store.commit('setOpenCreations', result.data.getPendingCreations.length)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getPendingCreations()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -32,7 +32,13 @@ export default {
|
|||||||
{ key: 'email', label: 'Email' },
|
{ key: 'email', label: 'Email' },
|
||||||
{ key: 'firstName', label: 'Firstname' },
|
{ key: 'firstName', label: 'Firstname' },
|
||||||
{ key: 'lastName', label: 'Lastname' },
|
{ key: 'lastName', label: 'Lastname' },
|
||||||
{ key: 'creation', label: 'Creation' },
|
{
|
||||||
|
key: 'creation',
|
||||||
|
label: 'Creation',
|
||||||
|
formatter: (value, key, item) => {
|
||||||
|
return String(value)
|
||||||
|
},
|
||||||
|
},
|
||||||
{ key: 'show_details', label: 'Details' },
|
{ key: 'show_details', label: 'Details' },
|
||||||
],
|
],
|
||||||
searchResult: [],
|
searchResult: [],
|
||||||
|
|||||||
37
admin/src/plugins/apolloProvider.js
Normal file
37
admin/src/plugins/apolloProvider.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
|
||||||
|
import VueApollo from 'vue-apollo'
|
||||||
|
import CONFIG from '../config'
|
||||||
|
import store from '../store/store'
|
||||||
|
import router from '../router/router'
|
||||||
|
import i18n from '../i18n'
|
||||||
|
|
||||||
|
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
|
||||||
|
|
||||||
|
const authLink = new ApolloLink((operation, forward) => {
|
||||||
|
const token = store.state.token
|
||||||
|
operation.setContext({
|
||||||
|
headers: {
|
||||||
|
Authorization: token && token.length > 0 ? `Bearer ${token}` : '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return forward(operation).map((response) => {
|
||||||
|
if (response.errors && response.errors[0].message === '403.13 - Client certificate revoked') {
|
||||||
|
response.errors[0].message = i18n.t('error.session-expired')
|
||||||
|
store.dispatch('logout', null)
|
||||||
|
if (router.currentRoute.path !== '/logout') router.push('/logout')
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
const newToken = operation.getContext().response.headers.get('token')
|
||||||
|
if (newToken) store.commit('token', newToken)
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const apolloClient = new ApolloClient({
|
||||||
|
link: authLink.concat(httpLink),
|
||||||
|
cache: new InMemoryCache(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const apolloProvider = new VueApollo({
|
||||||
|
defaultClient: apolloClient,
|
||||||
|
})
|
||||||
178
admin/src/plugins/apolloProvider.test.js
Normal file
178
admin/src/plugins/apolloProvider.test.js
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import { ApolloClient, ApolloLink, HttpLink } from 'apollo-boost'
|
||||||
|
import './apolloProvider'
|
||||||
|
import CONFIG from '../config'
|
||||||
|
|
||||||
|
import VueApollo from 'vue-apollo'
|
||||||
|
import store from '../store/store'
|
||||||
|
import router from '../router/router'
|
||||||
|
import i18n from '../i18n'
|
||||||
|
|
||||||
|
jest.mock('vue-apollo')
|
||||||
|
jest.mock('../store/store')
|
||||||
|
jest.mock('../router/router')
|
||||||
|
jest.mock('../i18n')
|
||||||
|
|
||||||
|
jest.mock('apollo-boost', () => {
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
ApolloClient: jest.fn(),
|
||||||
|
ApolloLink: jest.fn(() => {
|
||||||
|
return { concat: jest.fn() }
|
||||||
|
}),
|
||||||
|
InMemoryCache: jest.fn(),
|
||||||
|
HttpLink: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apolloProvider', () => {
|
||||||
|
it('calls the HttpLink', () => {
|
||||||
|
expect(HttpLink).toBeCalledWith({ uri: CONFIG.GRAPHQL_URI })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the ApolloLink', () => {
|
||||||
|
expect(ApolloLink).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the ApolloClient', () => {
|
||||||
|
expect(ApolloClient).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the VueApollo', () => {
|
||||||
|
expect(VueApollo).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('ApolloLink', () => {
|
||||||
|
// mock store
|
||||||
|
const storeDispatchMock = jest.fn()
|
||||||
|
const storeCommitMock = jest.fn()
|
||||||
|
store.state = {
|
||||||
|
token: 'some-token',
|
||||||
|
}
|
||||||
|
store.dispatch = storeDispatchMock
|
||||||
|
store.commit = storeCommitMock
|
||||||
|
|
||||||
|
// mock i18n.t
|
||||||
|
i18n.t = jest.fn((t) => t)
|
||||||
|
|
||||||
|
// mock apllo response
|
||||||
|
const responseMock = {
|
||||||
|
errors: [{ message: '403.13 - Client certificate revoked' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
// mock router
|
||||||
|
const routerPushMock = jest.fn()
|
||||||
|
router.push = routerPushMock
|
||||||
|
router.currentRoute = {
|
||||||
|
path: '/overview',
|
||||||
|
}
|
||||||
|
|
||||||
|
// mock context
|
||||||
|
const setContextMock = jest.fn()
|
||||||
|
const getContextMock = jest.fn(() => {
|
||||||
|
return {
|
||||||
|
response: {
|
||||||
|
headers: {
|
||||||
|
get: jest.fn(() => 'another-token'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// mock apollo link function params
|
||||||
|
const operationMock = {
|
||||||
|
setContext: setContextMock,
|
||||||
|
getContext: getContextMock,
|
||||||
|
}
|
||||||
|
|
||||||
|
const forwardMock = jest.fn(() => {
|
||||||
|
return [responseMock]
|
||||||
|
})
|
||||||
|
|
||||||
|
// get apollo link callback
|
||||||
|
const middleware = ApolloLink.mock.calls[0][0]
|
||||||
|
|
||||||
|
describe('with token in store', () => {
|
||||||
|
it('sets authorization header with token', () => {
|
||||||
|
// run the apollo link callback with mocked params
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
expect(setContextMock).toBeCalledWith({
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer some-token',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('without token in store', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.state.token = null
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets authorization header empty', () => {
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
expect(setContextMock).toBeCalledWith({
|
||||||
|
headers: {
|
||||||
|
Authorization: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apollo response is 403.13', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// run the apollo link callback with mocked params
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dispatches logout', () => {
|
||||||
|
expect(storeDispatchMock).toBeCalledWith('logout', null)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('current route is not logout', () => {
|
||||||
|
it('redirects to logout', () => {
|
||||||
|
expect(routerPushMock).toBeCalledWith('/logout')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('current route is logout', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
router.currentRoute.path = '/logout'
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not redirect to logout', () => {
|
||||||
|
expect(routerPushMock).not.toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apollo response is with new token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
delete responseMock.errors
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('commits new token to store', () => {
|
||||||
|
expect(storeCommitMock).toBeCalledWith('token', 'another-token')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apollo response is without new token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
getContextMock.mockReturnValue({
|
||||||
|
response: {
|
||||||
|
headers: {
|
||||||
|
get: jest.fn(() => null),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not commit token to store', () => {
|
||||||
|
expect(storeCommitMock).not.toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,12 +1,28 @@
|
|||||||
|
import { verifyLogin } from '../graphql/verifyLogin'
|
||||||
import CONFIG from '../config'
|
import CONFIG from '../config'
|
||||||
|
|
||||||
const addNavigationGuards = (router, store) => {
|
const addNavigationGuards = (router, store, apollo) => {
|
||||||
// store token on `authenticate`
|
// store token on `authenticate`
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
if (to.path === '/authenticate' && to.query && to.query.token) {
|
if (to.path === '/authenticate' && to.query && to.query.token) {
|
||||||
// TODO verify user to get user data
|
|
||||||
store.commit('token', to.query.token)
|
store.commit('token', to.query.token)
|
||||||
next({ path: '/' })
|
await apollo
|
||||||
|
.query({
|
||||||
|
query: verifyLogin,
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
const moderator = result.data.verifyLogin
|
||||||
|
if (moderator.isAdmin) {
|
||||||
|
store.commit('moderator', moderator)
|
||||||
|
next({ path: '/' })
|
||||||
|
} else {
|
||||||
|
next({ path: '/not-found' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
next({ path: '/not-found' })
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
@ -16,7 +32,9 @@ const addNavigationGuards = (router, store) => {
|
|||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
if (
|
if (
|
||||||
!CONFIG.DEBUG_DISABLE_AUTH && // we did not disabled the auth module for debug purposes
|
!CONFIG.DEBUG_DISABLE_AUTH && // we did not disabled the auth module for debug purposes
|
||||||
!store.state.token && // we do not have a token
|
(!store.state.token || // we do not have a token
|
||||||
|
!store.state.moderator || // no moderator set in store
|
||||||
|
!store.state.moderator.isAdmin) && // user is no admin
|
||||||
to.path !== '/not-found' && // we are not on `not-found`
|
to.path !== '/not-found' && // we are not on `not-found`
|
||||||
to.path !== '/logout' // we are not on `logout`
|
to.path !== '/logout' // we are not on `logout`
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -2,6 +2,13 @@ import addNavigationGuards from './guards'
|
|||||||
import router from './router'
|
import router from './router'
|
||||||
|
|
||||||
const storeCommitMock = jest.fn()
|
const storeCommitMock = jest.fn()
|
||||||
|
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
verifyLogin: {
|
||||||
|
isAdmin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const store = {
|
const store = {
|
||||||
commit: storeCommitMock,
|
commit: storeCommitMock,
|
||||||
@ -10,7 +17,11 @@ const store = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
addNavigationGuards(router, store)
|
const apollo = {
|
||||||
|
query: apolloQueryMock,
|
||||||
|
}
|
||||||
|
|
||||||
|
addNavigationGuards(router, store, apollo)
|
||||||
|
|
||||||
describe('navigation guards', () => {
|
describe('navigation guards', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -21,18 +32,70 @@ describe('navigation guards', () => {
|
|||||||
const navGuard = router.beforeHooks[0]
|
const navGuard = router.beforeHooks[0]
|
||||||
const next = jest.fn()
|
const next = jest.fn()
|
||||||
|
|
||||||
describe('with valid token', () => {
|
describe('with valid token and as admin', () => {
|
||||||
it('commits the token to the store', async () => {
|
beforeEach(() => {
|
||||||
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('commits the token to the store', async () => {
|
||||||
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
|
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('commits the moderator to the store', () => {
|
||||||
|
expect(storeCommitMock).toBeCalledWith('moderator', { isAdmin: true })
|
||||||
|
})
|
||||||
|
|
||||||
it('redirects to /', async () => {
|
it('redirects to /', async () => {
|
||||||
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
|
||||||
expect(next).toBeCalledWith({ path: '/' })
|
expect(next).toBeCalledWith({ path: '/' })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('with valid token and not as admin', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
apolloQueryMock.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
verifyLogin: {
|
||||||
|
isAdmin: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('commits the token to the store', async () => {
|
||||||
|
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not commit the moderator to the store', () => {
|
||||||
|
expect(storeCommitMock).not.toBeCalledWith('moderator', { isAdmin: false })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('redirects to /not-found', async () => {
|
||||||
|
expect(next).toBeCalledWith({ path: '/not-found' })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with valid token and server error on verification', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
apolloQueryMock.mockRejectedValue({
|
||||||
|
message: 'Ouch!',
|
||||||
|
})
|
||||||
|
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('commits the token to the store', async () => {
|
||||||
|
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not commit the moderator to the store', () => {
|
||||||
|
expect(storeCommitMock).not.toBeCalledWith('moderator', { isAdmin: false })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('redirects to /not-found', async () => {
|
||||||
|
expect(next).toBeCalledWith({ path: '/not-found' })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('without valid token', () => {
|
describe('without valid token', () => {
|
||||||
it('does not commit the token to the store', async () => {
|
it('does not commit the token to the store', async () => {
|
||||||
navGuard({ path: '/authenticate' }, {}, next)
|
navGuard({ path: '/authenticate' }, {}, next)
|
||||||
@ -55,9 +118,16 @@ describe('navigation guards', () => {
|
|||||||
expect(next).toBeCalledWith({ path: '/not-found' })
|
expect(next).toBeCalledWith({ path: '/not-found' })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not redirect when token in store', () => {
|
it('redirects to not found with token in store and not moderator', () => {
|
||||||
store.state.token = 'valid token'
|
store.state.token = 'valid token'
|
||||||
navGuard({ path: '/' }, {}, next)
|
navGuard({ path: '/' }, {}, next)
|
||||||
|
expect(next).toBeCalledWith({ path: '/not-found' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not redirect with token in store and as moderator', () => {
|
||||||
|
store.state.token = 'valid token'
|
||||||
|
store.state.moderator = { isAdmin: true }
|
||||||
|
navGuard({ path: '/' }, {}, next)
|
||||||
expect(next).toBeCalledWith()
|
expect(next).toBeCalledWith()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -18,11 +18,18 @@ export const mutations = {
|
|||||||
token: (state, token) => {
|
token: (state, token) => {
|
||||||
state.token = token
|
state.token = token
|
||||||
},
|
},
|
||||||
|
setOpenCreations: (state, openCreations) => {
|
||||||
|
state.openCreations = openCreations
|
||||||
|
},
|
||||||
|
moderator: (state, moderator) => {
|
||||||
|
state.moderator = moderator
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
logout: ({ commit, state }) => {
|
logout: ({ commit, state }) => {
|
||||||
commit('token', null)
|
commit('token', null)
|
||||||
|
commit('moderator', null)
|
||||||
window.localStorage.clear()
|
window.localStorage.clear()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -35,7 +42,7 @@ const store = new Vuex.Store({
|
|||||||
],
|
],
|
||||||
state: {
|
state: {
|
||||||
token: CONFIG.DEBUG_DISABLE_AUTH ? 'validToken' : null,
|
token: CONFIG.DEBUG_DISABLE_AUTH ? 'validToken' : null,
|
||||||
moderator: 'Dertest Moderator',
|
moderator: null,
|
||||||
openCreations: 0,
|
openCreations: 0,
|
||||||
},
|
},
|
||||||
// Syncronous mutation of the state
|
// Syncronous mutation of the state
|
||||||
|
|||||||
@ -1,11 +1,19 @@
|
|||||||
import store, { mutations, actions } from './store'
|
import store, { mutations, actions } from './store'
|
||||||
|
import CONFIG from '../config'
|
||||||
|
|
||||||
const { token, openCreationsPlus, openCreationsMinus, resetOpenCreations } = mutations
|
jest.mock('../config')
|
||||||
|
|
||||||
|
const {
|
||||||
|
token,
|
||||||
|
openCreationsPlus,
|
||||||
|
openCreationsMinus,
|
||||||
|
resetOpenCreations,
|
||||||
|
setOpenCreations,
|
||||||
|
moderator,
|
||||||
|
} = mutations
|
||||||
const { logout } = actions
|
const { logout } = actions
|
||||||
|
|
||||||
const CONFIG = {
|
CONFIG.DEBUG_DISABLE_AUTH = true
|
||||||
DEBUG_DISABLE_AUTH: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Vuex store', () => {
|
describe('Vuex store', () => {
|
||||||
describe('mutations', () => {
|
describe('mutations', () => {
|
||||||
@ -40,6 +48,22 @@ describe('Vuex store', () => {
|
|||||||
expect(state.openCreations).toEqual(0)
|
expect(state.openCreations).toEqual(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('moderator', () => {
|
||||||
|
it('sets the moderator object in state', () => {
|
||||||
|
const state = { moderator: null }
|
||||||
|
moderator(state, { id: 1 })
|
||||||
|
expect(state.moderator).toEqual({ id: 1 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('setOpenCreations', () => {
|
||||||
|
it('sets the open creations to given value', () => {
|
||||||
|
const state = { openCreations: 24 }
|
||||||
|
setOpenCreations(state, 12)
|
||||||
|
expect(state.openCreations).toEqual(12)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('actions', () => {
|
describe('actions', () => {
|
||||||
@ -57,6 +81,11 @@ describe('Vuex store', () => {
|
|||||||
expect(commit).toBeCalledWith('token', null)
|
expect(commit).toBeCalledWith('token', null)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('deletes the moderator in store', () => {
|
||||||
|
logout({ commit, state })
|
||||||
|
expect(commit).toBeCalledWith('moderator', null)
|
||||||
|
})
|
||||||
|
|
||||||
it.skip('clears the window local storage', () => {
|
it.skip('clears the window local storage', () => {
|
||||||
expect(windowStorageMock).toBeCalled()
|
expect(windowStorageMock).toBeCalled()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -12524,6 +12524,11 @@ vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0:
|
|||||||
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
||||||
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
|
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
|
||||||
|
|
||||||
|
vue-toasted@^1.1.28:
|
||||||
|
version "1.1.28"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-toasted/-/vue-toasted-1.1.28.tgz#dbabb83acc89f7a9e8765815e491d79f0dc65c26"
|
||||||
|
integrity sha512-UUzr5LX51UbbiROSGZ49GOgSzFxaMHK6L00JV8fir/CYNJCpIIvNZ5YmS4Qc8Y2+Z/4VVYRpeQL2UO0G800Raw==
|
||||||
|
|
||||||
vue@^2.6.11:
|
vue@^2.6.11:
|
||||||
version "2.6.14"
|
version "2.6.14"
|
||||||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
|
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
|
||||||
|
|||||||
@ -2,8 +2,6 @@ PORT=4000
|
|||||||
JWT_SECRET=secret123
|
JWT_SECRET=secret123
|
||||||
JWT_EXPIRES_IN=10m
|
JWT_EXPIRES_IN=10m
|
||||||
GRAPHIQL=false
|
GRAPHIQL=false
|
||||||
LOGIN_API_URL=http://login-server:1201/
|
|
||||||
COMMUNITY_API_URL=http://nginx/api/
|
|
||||||
GDT_API_URL=https://gdt.gradido.net
|
GDT_API_URL=https://gdt.gradido.net
|
||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
DB_PORT=3306
|
DB_PORT=3306
|
||||||
|
|||||||
@ -29,6 +29,7 @@
|
|||||||
"jest": "^27.2.4",
|
"jest": "^27.2.4",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"module-alias": "^2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
|
"moment": "^2.29.1",
|
||||||
"mysql2": "^2.3.0",
|
"mysql2": "^2.3.0",
|
||||||
"nodemailer": "^6.6.5",
|
"nodemailer": "^6.6.5",
|
||||||
"random-bigint": "^0.0.1",
|
"random-bigint": "^0.0.1",
|
||||||
|
|||||||
@ -4,10 +4,8 @@ export const INALIENABLE_RIGHTS = [
|
|||||||
RIGHTS.LOGIN,
|
RIGHTS.LOGIN,
|
||||||
RIGHTS.GET_COMMUNITY_INFO,
|
RIGHTS.GET_COMMUNITY_INFO,
|
||||||
RIGHTS.COMMUNITIES,
|
RIGHTS.COMMUNITIES,
|
||||||
RIGHTS.LOGIN_VIA_EMAIL_VERIFICATION_CODE,
|
|
||||||
RIGHTS.CREATE_USER,
|
RIGHTS.CREATE_USER,
|
||||||
RIGHTS.SEND_RESET_PASSWORD_EMAIL,
|
RIGHTS.SEND_RESET_PASSWORD_EMAIL,
|
||||||
RIGHTS.RESET_PASSWORD,
|
RIGHTS.SET_PASSWORD,
|
||||||
RIGHTS.CHECK_USERNAME,
|
RIGHTS.CHECK_USERNAME,
|
||||||
RIGHTS.CHECK_EMAIL,
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -12,14 +12,12 @@ export enum RIGHTS {
|
|||||||
SUBSCRIBE_NEWSLETTER = 'SUBSCRIBE_NEWSLETTER',
|
SUBSCRIBE_NEWSLETTER = 'SUBSCRIBE_NEWSLETTER',
|
||||||
TRANSACTION_LIST = 'TRANSACTION_LIST',
|
TRANSACTION_LIST = 'TRANSACTION_LIST',
|
||||||
SEND_COINS = 'SEND_COINS',
|
SEND_COINS = 'SEND_COINS',
|
||||||
LOGIN_VIA_EMAIL_VERIFICATION_CODE = 'LOGIN_VIA_EMAIL_VERIFICATION_CODE',
|
|
||||||
LOGOUT = 'LOGOUT',
|
LOGOUT = 'LOGOUT',
|
||||||
CREATE_USER = 'CREATE_USER',
|
CREATE_USER = 'CREATE_USER',
|
||||||
SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL',
|
SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL',
|
||||||
RESET_PASSWORD = 'RESET_PASSWORD',
|
SET_PASSWORD = 'SET_PASSWORD',
|
||||||
UPDATE_USER_INFOS = 'UPDATE_USER_INFOS',
|
UPDATE_USER_INFOS = 'UPDATE_USER_INFOS',
|
||||||
CHECK_USERNAME = 'CHECK_USERNAME',
|
CHECK_USERNAME = 'CHECK_USERNAME',
|
||||||
CHECK_EMAIL = 'CHECK_EMAIL',
|
|
||||||
HAS_ELOPAGE = 'HAS_ELOPAGE',
|
HAS_ELOPAGE = 'HAS_ELOPAGE',
|
||||||
// Admin
|
// Admin
|
||||||
SEARCH_USERS = 'SEARCH_USERS',
|
SEARCH_USERS = 'SEARCH_USERS',
|
||||||
|
|||||||
@ -8,8 +8,6 @@ const server = {
|
|||||||
JWT_SECRET: process.env.JWT_SECRET || 'secret123',
|
JWT_SECRET: process.env.JWT_SECRET || 'secret123',
|
||||||
JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '10m',
|
JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '10m',
|
||||||
GRAPHIQL: process.env.GRAPHIQL === 'true' || false,
|
GRAPHIQL: process.env.GRAPHIQL === 'true' || false,
|
||||||
LOGIN_API_URL: process.env.LOGIN_API_URL || 'http://login-server:1201/',
|
|
||||||
COMMUNITY_API_URL: process.env.COMMUNITY_API_URL || 'http://nginx/api/',
|
|
||||||
GDT_API_URL: process.env.GDT_API_URL || 'https://gdt.gradido.net',
|
GDT_API_URL: process.env.GDT_API_URL || 'https://gdt.gradido.net',
|
||||||
PRODUCTION: process.env.NODE_ENV === 'production' || false,
|
PRODUCTION: process.env.NODE_ENV === 'production' || false,
|
||||||
}
|
}
|
||||||
@ -53,6 +51,7 @@ const email = {
|
|||||||
EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '587',
|
EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '587',
|
||||||
EMAIL_LINK_VERIFICATION:
|
EMAIL_LINK_VERIFICATION:
|
||||||
process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/vue/checkEmail/$1',
|
process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/vue/checkEmail/$1',
|
||||||
|
EMAIL_LINK_SETPASSWORD: process.env.EMAIL_LINK_SETPASSWORD || 'http://localhost/vue/reset/$1',
|
||||||
}
|
}
|
||||||
|
|
||||||
const webhook = {
|
const webhook = {
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
import { ArgsType, Field } from 'type-graphql'
|
|
||||||
|
|
||||||
@ArgsType()
|
|
||||||
export default class ChangePasswordArgs {
|
|
||||||
@Field(() => Number)
|
|
||||||
sessionId: number
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
email: string
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
password: string
|
|
||||||
}
|
|
||||||
19
backend/src/graphql/arg/CreatePendingCreationArgs.ts
Normal file
19
backend/src/graphql/arg/CreatePendingCreationArgs.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { ArgsType, Field, Int } from 'type-graphql'
|
||||||
|
|
||||||
|
@ArgsType()
|
||||||
|
export default class CreatePendingCreationArgs {
|
||||||
|
@Field(() => String)
|
||||||
|
email: string
|
||||||
|
|
||||||
|
@Field(() => Int)
|
||||||
|
amount: number
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
memo: string
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
creationDate: string
|
||||||
|
|
||||||
|
@Field(() => Int)
|
||||||
|
moderator: number
|
||||||
|
}
|
||||||
22
backend/src/graphql/arg/UpdatePendingCreationArgs.ts
Normal file
22
backend/src/graphql/arg/UpdatePendingCreationArgs.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ArgsType, Field, Int } from 'type-graphql'
|
||||||
|
|
||||||
|
@ArgsType()
|
||||||
|
export default class CreatePendingCreationArgs {
|
||||||
|
@Field(() => Int)
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
email: string
|
||||||
|
|
||||||
|
@Field(() => Int)
|
||||||
|
amount: number
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
memo: string
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
creationDate: string
|
||||||
|
|
||||||
|
@Field(() => Int)
|
||||||
|
moderator: number
|
||||||
|
}
|
||||||
@ -1,29 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
||||||
import { ObjectType, Field } from 'type-graphql'
|
|
||||||
|
|
||||||
@ObjectType()
|
|
||||||
export class CheckEmailResponse {
|
|
||||||
constructor(json: any) {
|
|
||||||
this.sessionId = json.session_id
|
|
||||||
this.email = json.user.email
|
|
||||||
this.language = json.user.language
|
|
||||||
this.firstName = json.user.first_name
|
|
||||||
this.lastName = json.user.last_name
|
|
||||||
}
|
|
||||||
|
|
||||||
@Field(() => Number)
|
|
||||||
sessionId: number
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
email: string
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
firstName: string
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
lastName: string
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
language: string
|
|
||||||
}
|
|
||||||
0
backend/src/graphql/model/CreatePendingCreation.ts
Normal file
0
backend/src/graphql/model/CreatePendingCreation.ts
Normal file
@ -1,17 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
||||||
import { ObjectType, Field } from 'type-graphql'
|
|
||||||
|
|
||||||
@ObjectType()
|
|
||||||
export class LoginViaVerificationCode {
|
|
||||||
constructor(json: any) {
|
|
||||||
this.sessionId = json.session_id
|
|
||||||
this.email = json.user.email
|
|
||||||
}
|
|
||||||
|
|
||||||
@Field(() => Number)
|
|
||||||
sessionId: number
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
email: string
|
|
||||||
}
|
|
||||||
34
backend/src/graphql/model/PendingCreation.ts
Normal file
34
backend/src/graphql/model/PendingCreation.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { ObjectType, Field, Int } from 'type-graphql'
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class PendingCreation {
|
||||||
|
@Field(() => String)
|
||||||
|
firstName: string
|
||||||
|
|
||||||
|
@Field(() => Int)
|
||||||
|
id?: number
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
lastName: string
|
||||||
|
|
||||||
|
@Field(() => Number)
|
||||||
|
userId: number
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
email: string
|
||||||
|
|
||||||
|
@Field(() => Date)
|
||||||
|
date: Date
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
memo: string
|
||||||
|
|
||||||
|
@Field(() => Number)
|
||||||
|
amount: number
|
||||||
|
|
||||||
|
@Field(() => Number)
|
||||||
|
moderator: number
|
||||||
|
|
||||||
|
@Field(() => [Number])
|
||||||
|
creation: number[]
|
||||||
|
}
|
||||||
@ -1,17 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
||||||
import { ObjectType, Field } from 'type-graphql'
|
|
||||||
|
|
||||||
@ObjectType()
|
|
||||||
export class SendPasswordResetEmailResponse {
|
|
||||||
constructor(json: any) {
|
|
||||||
this.state = json.state
|
|
||||||
this.msg = json.msg
|
|
||||||
}
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
state: string
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
msg?: string
|
|
||||||
}
|
|
||||||
19
backend/src/graphql/model/UpdatePendingCreation.ts
Normal file
19
backend/src/graphql/model/UpdatePendingCreation.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { ObjectType, Field } from 'type-graphql'
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class UpdatePendingCreation {
|
||||||
|
@Field(() => Date)
|
||||||
|
date: Date
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
memo: string
|
||||||
|
|
||||||
|
@Field(() => Number)
|
||||||
|
amount: number
|
||||||
|
|
||||||
|
@Field(() => Number)
|
||||||
|
moderator: number
|
||||||
|
|
||||||
|
@Field(() => [Number])
|
||||||
|
creation: number[]
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ export class User {
|
|||||||
*/
|
*/
|
||||||
constructor(json?: any) {
|
constructor(json?: any) {
|
||||||
if (json) {
|
if (json) {
|
||||||
|
this.id = json.id
|
||||||
this.email = json.email
|
this.email = json.email
|
||||||
this.firstName = json.first_name
|
this.firstName = json.first_name
|
||||||
this.lastName = json.last_name
|
this.lastName = json.last_name
|
||||||
@ -24,6 +25,9 @@ export class User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Field(() => Number)
|
||||||
|
id: number
|
||||||
|
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
email: string
|
email: string
|
||||||
|
|
||||||
|
|||||||
@ -1,28 +1,326 @@
|
|||||||
import { Resolver, Query, Arg, Authorized } from 'type-graphql'
|
import { Resolver, Query, Arg, Args, Authorized, Mutation } from 'type-graphql'
|
||||||
import { getCustomRepository } from 'typeorm'
|
import { getCustomRepository, Raw } from 'typeorm'
|
||||||
import { UserAdmin } from '../model/UserAdmin'
|
import { UserAdmin } from '../model/UserAdmin'
|
||||||
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
import { PendingCreation } from '../model/PendingCreation'
|
||||||
|
import { UpdatePendingCreation } from '../model/UpdatePendingCreation'
|
||||||
import { RIGHTS } from '../../auth/RIGHTS'
|
import { RIGHTS } from '../../auth/RIGHTS'
|
||||||
|
import { TransactionRepository } from '../../typeorm/repository/Transaction'
|
||||||
|
import { TransactionCreationRepository } from '../../typeorm/repository/TransactionCreation'
|
||||||
|
import { PendingCreationRepository } from '../../typeorm/repository/PendingCreation'
|
||||||
|
import { UserRepository } from '../../typeorm/repository/User'
|
||||||
|
import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs'
|
||||||
|
import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs'
|
||||||
|
import moment from 'moment'
|
||||||
|
import { Transaction } from '@entity/Transaction'
|
||||||
|
import { TransactionCreation } from '@entity/TransactionCreation'
|
||||||
|
import { UserTransaction } from '@entity/UserTransaction'
|
||||||
|
import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction'
|
||||||
|
import { BalanceRepository } from '../../typeorm/repository/Balance'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class AdminResolver {
|
export class AdminResolver {
|
||||||
@Authorized([RIGHTS.SEARCH_USERS])
|
@Authorized([RIGHTS.SEARCH_USERS])
|
||||||
@Query(() => [UserAdmin])
|
@Query(() => [UserAdmin])
|
||||||
async searchUsers(@Arg('searchText') searchText: string): Promise<UserAdmin[]> {
|
async searchUsers(@Arg('searchText') searchText: string): Promise<UserAdmin[]> {
|
||||||
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
const userRepository = getCustomRepository(UserRepository)
|
||||||
const loginUsers = await loginUserRepository.findBySearchCriteria(searchText)
|
const users = await userRepository.findBySearchCriteria(searchText)
|
||||||
const users = loginUsers.map((loginUser) => {
|
const adminUsers = await Promise.all(
|
||||||
const user = new UserAdmin()
|
users.map(async (user) => {
|
||||||
user.firstName = loginUser.firstName
|
const adminUser = new UserAdmin()
|
||||||
user.lastName = loginUser.lastName
|
adminUser.firstName = user.firstName
|
||||||
user.email = loginUser.email
|
adminUser.lastName = user.lastName
|
||||||
user.creation = [
|
adminUser.email = user.email
|
||||||
(Math.floor(Math.random() * 50) + 1) * 20,
|
adminUser.creation = await getUserCreations(user.id)
|
||||||
(Math.floor(Math.random() * 50) + 1) * 20,
|
return adminUser
|
||||||
(Math.floor(Math.random() * 50) + 1) * 20,
|
}),
|
||||||
]
|
)
|
||||||
return user
|
return adminUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
@Authorized([RIGHTS.SEARCH_USERS])
|
||||||
|
@Mutation(() => [Number])
|
||||||
|
async createPendingCreation(
|
||||||
|
@Args() { email, amount, memo, creationDate, moderator }: CreatePendingCreationArgs,
|
||||||
|
): Promise<number[]> {
|
||||||
|
const userRepository = getCustomRepository(UserRepository)
|
||||||
|
const user = await userRepository.findByEmail(email)
|
||||||
|
|
||||||
|
const creations = await getUserCreations(user.id)
|
||||||
|
const creationDateObj = new Date(creationDate)
|
||||||
|
if (isCreationValid(creations, amount, creationDateObj)) {
|
||||||
|
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
||||||
|
const loginPendingTaskAdmin = pendingCreationRepository.create()
|
||||||
|
loginPendingTaskAdmin.userId = user.id
|
||||||
|
loginPendingTaskAdmin.amount = BigInt(amount * 10000)
|
||||||
|
loginPendingTaskAdmin.created = new Date()
|
||||||
|
loginPendingTaskAdmin.date = creationDateObj
|
||||||
|
loginPendingTaskAdmin.memo = memo
|
||||||
|
loginPendingTaskAdmin.moderator = moderator
|
||||||
|
|
||||||
|
pendingCreationRepository.save(loginPendingTaskAdmin)
|
||||||
|
}
|
||||||
|
return await getUserCreations(user.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Authorized([RIGHTS.SEARCH_USERS])
|
||||||
|
@Mutation(() => UpdatePendingCreation)
|
||||||
|
async updatePendingCreation(
|
||||||
|
@Args() { id, email, amount, memo, creationDate, moderator }: UpdatePendingCreationArgs,
|
||||||
|
): Promise<UpdatePendingCreation> {
|
||||||
|
const userRepository = getCustomRepository(UserRepository)
|
||||||
|
const user = await userRepository.findByEmail(email)
|
||||||
|
|
||||||
|
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
||||||
|
const updatedCreation = await pendingCreationRepository.findOneOrFail({ id })
|
||||||
|
|
||||||
|
if (updatedCreation.userId !== user.id)
|
||||||
|
throw new Error('user of the pending creation and send user does not correspond')
|
||||||
|
|
||||||
|
updatedCreation.amount = BigInt(amount * 10000)
|
||||||
|
updatedCreation.memo = memo
|
||||||
|
updatedCreation.date = new Date(creationDate)
|
||||||
|
updatedCreation.moderator = moderator
|
||||||
|
|
||||||
|
await pendingCreationRepository.save(updatedCreation)
|
||||||
|
const result = new UpdatePendingCreation()
|
||||||
|
result.amount = parseInt(amount.toString())
|
||||||
|
result.memo = updatedCreation.memo
|
||||||
|
result.date = updatedCreation.date
|
||||||
|
result.moderator = updatedCreation.moderator
|
||||||
|
result.creation = await getUserCreations(user.id)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
// const creations = await getUserCreations(user.id)
|
||||||
|
// const creationDateObj = new Date(creationDate)
|
||||||
|
// if (isCreationValid(creations, amount, creationDateObj)) {
|
||||||
|
// const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
||||||
|
// const loginPendingTaskAdmin = pendingCreationRepository.create()
|
||||||
|
// loginPendingTaskAdmin.userId = user.id
|
||||||
|
// loginPendingTaskAdmin.amount = BigInt(amount * 10000)
|
||||||
|
// loginPendingTaskAdmin.created = new Date()
|
||||||
|
// loginPendingTaskAdmin.date = creationDateObj
|
||||||
|
// loginPendingTaskAdmin.memo = memo
|
||||||
|
// loginPendingTaskAdmin.moderator = moderator
|
||||||
|
//
|
||||||
|
// pendingCreationRepository.save(loginPendingTaskAdmin)
|
||||||
|
// }
|
||||||
|
// return await getUserCreations(user.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query(() => [PendingCreation])
|
||||||
|
async getPendingCreations(): Promise<PendingCreation[]> {
|
||||||
|
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
||||||
|
const pendingCreations = await pendingCreationRepository.find()
|
||||||
|
|
||||||
|
const pendingCreationsPromise = await Promise.all(
|
||||||
|
pendingCreations.map(async (pendingCreation) => {
|
||||||
|
const userRepository = getCustomRepository(UserRepository)
|
||||||
|
const user = await userRepository.findOneOrFail({ id: pendingCreation.userId })
|
||||||
|
|
||||||
|
const parsedAmount = Number(parseInt(pendingCreation.amount.toString()) / 10000)
|
||||||
|
// pendingCreation.amount = parsedAmount
|
||||||
|
const newPendingCreation = {
|
||||||
|
...pendingCreation,
|
||||||
|
amount: parsedAmount,
|
||||||
|
firstName: user.firstName,
|
||||||
|
lastName: user.lastName,
|
||||||
|
email: user.email,
|
||||||
|
creation: await getUserCreations(user.id),
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPendingCreation
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
return pendingCreationsPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => Boolean)
|
||||||
|
async deletePendingCreation(@Arg('id') id: number): Promise<boolean> {
|
||||||
|
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
||||||
|
const entity = await pendingCreationRepository.findOneOrFail(id)
|
||||||
|
const res = await pendingCreationRepository.delete(entity)
|
||||||
|
return !!res
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => Boolean)
|
||||||
|
async confirmPendingCreation(@Arg('id') id: number): Promise<boolean> {
|
||||||
|
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
||||||
|
const pendingCreation = await pendingCreationRepository.findOneOrFail(id)
|
||||||
|
|
||||||
|
const transactionRepository = getCustomRepository(TransactionRepository)
|
||||||
|
let transaction = new Transaction()
|
||||||
|
transaction.transactionTypeId = 1
|
||||||
|
transaction.memo = pendingCreation.memo
|
||||||
|
transaction.received = new Date()
|
||||||
|
transaction.blockchainTypeId = 1
|
||||||
|
transaction = await transactionRepository.save(transaction)
|
||||||
|
if (!transaction) throw new Error('Could not create transaction')
|
||||||
|
|
||||||
|
const transactionCreationRepository = getCustomRepository(TransactionCreationRepository)
|
||||||
|
let transactionCreation = new TransactionCreation()
|
||||||
|
transactionCreation.transactionId = transaction.id
|
||||||
|
transactionCreation.userId = pendingCreation.userId
|
||||||
|
transactionCreation.amount = parseInt(pendingCreation.amount.toString())
|
||||||
|
transactionCreation.targetDate = pendingCreation.date
|
||||||
|
transactionCreation = await transactionCreationRepository.save(transactionCreation)
|
||||||
|
if (!transactionCreation) throw new Error('Could not create transactionCreation')
|
||||||
|
|
||||||
|
const userTransactionRepository = getCustomRepository(UserTransactionRepository)
|
||||||
|
const lastUserTransaction = await userTransactionRepository.findLastForUser(
|
||||||
|
pendingCreation.userId,
|
||||||
|
)
|
||||||
|
let newBalance = 0
|
||||||
|
if (!lastUserTransaction) {
|
||||||
|
newBalance = 0
|
||||||
|
} else {
|
||||||
|
newBalance = lastUserTransaction.balance
|
||||||
|
}
|
||||||
|
newBalance = Number(newBalance) + Number(parseInt(pendingCreation.amount.toString()))
|
||||||
|
|
||||||
|
const newUserTransaction = new UserTransaction()
|
||||||
|
newUserTransaction.userId = pendingCreation.userId
|
||||||
|
newUserTransaction.transactionId = transaction.id
|
||||||
|
newUserTransaction.transactionTypeId = transaction.transactionTypeId
|
||||||
|
newUserTransaction.balance = Number(newBalance)
|
||||||
|
newUserTransaction.balanceDate = transaction.received
|
||||||
|
|
||||||
|
await userTransactionRepository.save(newUserTransaction).catch((error) => {
|
||||||
|
throw new Error('Error saving user transaction: ' + error)
|
||||||
})
|
})
|
||||||
return users
|
|
||||||
|
const balanceRepository = getCustomRepository(BalanceRepository)
|
||||||
|
let userBalance = await balanceRepository.findByUser(pendingCreation.userId)
|
||||||
|
|
||||||
|
if (!userBalance) userBalance = balanceRepository.create()
|
||||||
|
userBalance.userId = pendingCreation.userId
|
||||||
|
userBalance.amount = Number(newBalance)
|
||||||
|
userBalance.modified = new Date()
|
||||||
|
userBalance.recordDate = userBalance.recordDate ? userBalance.recordDate : new Date()
|
||||||
|
await balanceRepository.save(userBalance)
|
||||||
|
await pendingCreationRepository.delete(pendingCreation)
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getUserCreations(id: number): Promise<number[]> {
|
||||||
|
const dateNextMonth = moment().add(1, 'month').format('YYYY-MM') + '-01'
|
||||||
|
const dateMonth = moment().format('YYYY-MM') + '-01'
|
||||||
|
const dateLastMonth = moment().subtract(1, 'month').format('YYYY-MM') + '-01'
|
||||||
|
const dateBeforeLastMonth = moment().subtract(2, 'month').format('YYYY-MM') + '-01'
|
||||||
|
|
||||||
|
const transactionCreationRepository = getCustomRepository(TransactionCreationRepository)
|
||||||
|
const createdAmountBeforeLastMonth = await transactionCreationRepository
|
||||||
|
.createQueryBuilder('transaction_creations')
|
||||||
|
.select('SUM(transaction_creations.amount)', 'sum')
|
||||||
|
.where('transaction_creations.state_user_id = :id', { id })
|
||||||
|
.andWhere({
|
||||||
|
targetDate: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
|
||||||
|
date: dateBeforeLastMonth,
|
||||||
|
enddate: dateLastMonth,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.getRawOne()
|
||||||
|
|
||||||
|
const createdAmountLastMonth = await transactionCreationRepository
|
||||||
|
.createQueryBuilder('transaction_creations')
|
||||||
|
.select('SUM(transaction_creations.amount)', 'sum')
|
||||||
|
.where('transaction_creations.state_user_id = :id', { id })
|
||||||
|
.andWhere({
|
||||||
|
targetDate: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
|
||||||
|
date: dateLastMonth,
|
||||||
|
enddate: dateMonth,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.getRawOne()
|
||||||
|
|
||||||
|
const createdAmountMonth = await transactionCreationRepository
|
||||||
|
.createQueryBuilder('transaction_creations')
|
||||||
|
.select('SUM(transaction_creations.amount)', 'sum')
|
||||||
|
.where('transaction_creations.state_user_id = :id', { id })
|
||||||
|
.andWhere({
|
||||||
|
targetDate: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
|
||||||
|
date: dateMonth,
|
||||||
|
enddate: dateNextMonth,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.getRawOne()
|
||||||
|
|
||||||
|
const pendingCreationRepository = getCustomRepository(PendingCreationRepository)
|
||||||
|
const pendingAmountMounth = await pendingCreationRepository
|
||||||
|
.createQueryBuilder('login_pending_tasks_admin')
|
||||||
|
.select('SUM(login_pending_tasks_admin.amount)', 'sum')
|
||||||
|
.where('login_pending_tasks_admin.userId = :id', { id })
|
||||||
|
.andWhere({
|
||||||
|
date: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
|
||||||
|
date: dateMonth,
|
||||||
|
enddate: dateNextMonth,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.getRawOne()
|
||||||
|
|
||||||
|
const pendingAmountLastMounth = await pendingCreationRepository
|
||||||
|
.createQueryBuilder('login_pending_tasks_admin')
|
||||||
|
.select('SUM(login_pending_tasks_admin.amount)', 'sum')
|
||||||
|
.where('login_pending_tasks_admin.userId = :id', { id })
|
||||||
|
.andWhere({
|
||||||
|
date: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
|
||||||
|
date: dateLastMonth,
|
||||||
|
enddate: dateMonth,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.getRawOne()
|
||||||
|
|
||||||
|
const pendingAmountBeforeLastMounth = await pendingCreationRepository
|
||||||
|
.createQueryBuilder('login_pending_tasks_admin')
|
||||||
|
.select('SUM(login_pending_tasks_admin.amount)', 'sum')
|
||||||
|
.where('login_pending_tasks_admin.userId = :id', { id })
|
||||||
|
.andWhere({
|
||||||
|
date: Raw((alias) => `${alias} >= :date and ${alias} < :enddate`, {
|
||||||
|
date: dateBeforeLastMonth,
|
||||||
|
enddate: dateLastMonth,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.getRawOne()
|
||||||
|
|
||||||
|
// COUNT amount from 2 tables
|
||||||
|
const usedCreationBeforeLastMonth =
|
||||||
|
(Number(createdAmountBeforeLastMonth.sum) + Number(pendingAmountBeforeLastMounth.sum)) / 10000
|
||||||
|
const usedCreationLastMonth =
|
||||||
|
(Number(createdAmountLastMonth.sum) + Number(pendingAmountLastMounth.sum)) / 10000
|
||||||
|
const usedCreationMonth =
|
||||||
|
(Number(createdAmountMonth.sum) + Number(pendingAmountMounth.sum)) / 10000
|
||||||
|
return [
|
||||||
|
1000 - usedCreationBeforeLastMonth,
|
||||||
|
1000 - usedCreationLastMonth,
|
||||||
|
1000 - usedCreationMonth,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCreationValid(creations: number[], amount: number, creationDate: Date) {
|
||||||
|
const dateMonth = moment().format('YYYY-MM')
|
||||||
|
const dateLastMonth = moment().subtract(1, 'month').format('YYYY-MM')
|
||||||
|
const dateBeforeLastMonth = moment().subtract(2, 'month').format('YYYY-MM')
|
||||||
|
const creationDateMonth = moment(creationDate).format('YYYY-MM')
|
||||||
|
|
||||||
|
let openCreation
|
||||||
|
switch (creationDateMonth) {
|
||||||
|
case dateMonth:
|
||||||
|
openCreation = creations[2]
|
||||||
|
break
|
||||||
|
case dateLastMonth:
|
||||||
|
openCreation = creations[1]
|
||||||
|
break
|
||||||
|
case dateBeforeLastMonth:
|
||||||
|
openCreation = creations[0]
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error('CreationDate is not in last three months')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openCreation < amount) {
|
||||||
|
throw new Error(`Open creation (${openCreation}) is less than amount (${amount})`)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@ -3,24 +3,16 @@
|
|||||||
|
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql'
|
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql'
|
||||||
import { getConnection, getCustomRepository } from 'typeorm'
|
import { getConnection, getCustomRepository, getRepository } from 'typeorm'
|
||||||
import CONFIG from '../../config'
|
import CONFIG from '../../config'
|
||||||
import { LoginViaVerificationCode } from '../model/LoginViaVerificationCode'
|
|
||||||
import { SendPasswordResetEmailResponse } from '../model/SendPasswordResetEmailResponse'
|
|
||||||
import { User } from '../model/User'
|
import { User } from '../model/User'
|
||||||
import { User as DbUser } from '@entity/User'
|
import { User as DbUser } from '@entity/User'
|
||||||
import { encode } from '../../auth/JWT'
|
import { encode } from '../../auth/JWT'
|
||||||
import ChangePasswordArgs from '../arg/ChangePasswordArgs'
|
|
||||||
import CheckUsernameArgs from '../arg/CheckUsernameArgs'
|
import CheckUsernameArgs from '../arg/CheckUsernameArgs'
|
||||||
import CreateUserArgs from '../arg/CreateUserArgs'
|
import CreateUserArgs from '../arg/CreateUserArgs'
|
||||||
import UnsecureLoginArgs from '../arg/UnsecureLoginArgs'
|
import UnsecureLoginArgs from '../arg/UnsecureLoginArgs'
|
||||||
import UpdateUserInfosArgs from '../arg/UpdateUserInfosArgs'
|
import UpdateUserInfosArgs from '../arg/UpdateUserInfosArgs'
|
||||||
import { apiPost, apiGet } from '../../apis/HttpRequest'
|
import { klicktippNewsletterStateMiddleware } from '../../middleware/klicktippMiddleware'
|
||||||
import {
|
|
||||||
klicktippRegistrationMiddleware,
|
|
||||||
klicktippNewsletterStateMiddleware,
|
|
||||||
} from '../../middleware/klicktippMiddleware'
|
|
||||||
import { CheckEmailResponse } from '../model/CheckEmailResponse'
|
|
||||||
import { UserSettingRepository } from '../../typeorm/repository/UserSettingRepository'
|
import { UserSettingRepository } from '../../typeorm/repository/UserSettingRepository'
|
||||||
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
||||||
import { Setting } from '../enum/Setting'
|
import { Setting } from '../enum/Setting'
|
||||||
@ -30,10 +22,14 @@ import { LoginUserBackup } from '@entity/LoginUserBackup'
|
|||||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||||
import { sendEMail } from '../../util/sendEMail'
|
import { sendEMail } from '../../util/sendEMail'
|
||||||
import { LoginElopageBuysRepository } from '../../typeorm/repository/LoginElopageBuys'
|
import { LoginElopageBuysRepository } from '../../typeorm/repository/LoginElopageBuys'
|
||||||
|
import { signIn } from '../../apis/KlicktippController'
|
||||||
import { RIGHTS } from '../../auth/RIGHTS'
|
import { RIGHTS } from '../../auth/RIGHTS'
|
||||||
import { ServerUserRepository } from '../../typeorm/repository/ServerUser'
|
import { ServerUserRepository } from '../../typeorm/repository/ServerUser'
|
||||||
import { ROLE_ADMIN } from '../../auth/ROLES'
|
import { ROLE_ADMIN } from '../../auth/ROLES'
|
||||||
|
|
||||||
|
const EMAIL_OPT_IN_RESET_PASSWORD = 2
|
||||||
|
const EMAIL_OPT_IN_REGISTER = 1
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const sodium = require('sodium-native')
|
const sodium = require('sodium-native')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
@ -58,50 +54,8 @@ const PassphraseGenerate = (): string[] => {
|
|||||||
result.push(WORDS[sodium.randombytes_random() % 2048])
|
result.push(WORDS[sodium.randombytes_random() % 2048])
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
/*
|
|
||||||
return [
|
|
||||||
'behind',
|
|
||||||
'salmon',
|
|
||||||
'fluid',
|
|
||||||
'orphan',
|
|
||||||
'frost',
|
|
||||||
'elder',
|
|
||||||
'amateur',
|
|
||||||
'always',
|
|
||||||
'panel',
|
|
||||||
'palm',
|
|
||||||
'leopard',
|
|
||||||
'essay',
|
|
||||||
'punch',
|
|
||||||
'title',
|
|
||||||
'fun',
|
|
||||||
'annual',
|
|
||||||
'page',
|
|
||||||
'hundred',
|
|
||||||
'journey',
|
|
||||||
'select',
|
|
||||||
'figure',
|
|
||||||
'tunnel',
|
|
||||||
'casual',
|
|
||||||
'bar',
|
|
||||||
]
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Test results:
|
|
||||||
INSERT INTO `login_users` (`id`, `email`, `first_name`, `last_name`, `username`, `description`, `password`, `pubkey`, `privkey`, `email_hash`, `created`, `email_checked`, `passphrase_shown`, `language`, `disabled`, `group_id`, `publisher_id`) VALUES
|
|
||||||
// old
|
|
||||||
(1, 'peter@lustig.de', 'peter', 'lustig', '', '', 4747956395458240931, 0x8c75edd507f470e5378f927489374694d68f3d155523f1c4402c36affd35a7ed, 0xb0e310655726b088631ccfd31ad6470ee50115c161dde8559572fa90657270ff13dc1200b2d3ea90dfbe92f3a4475ee4d9cee4989e39736a0870c33284bc73a8ae690e6da89f241a121eb3b500c22885, 0x9f700e6f6ec351a140b674c0edd4479509697b023bd8bee8826915ef6c2af036, '2021-11-03 20:05:04', 0, 0, 'de', 0, 1, 0);
|
|
||||||
// new
|
|
||||||
(2, 'peter@lustig.de', 'peter', 'lustig', '', '', 4747956395458240931, 0x8c75edd507f470e5378f927489374694d68f3d155523f1c4402c36affd35a7ed, 0xb0e310655726b088631ccfd31ad6470ee50115c161dde8559572fa90657270ff13dc1200b2d3ea90dfbe92f3a4475ee4d9cee4989e39736a0870c33284bc73a8ae690e6da89f241a121eb3b500c22885, 0x9f700e6f6ec351a140b674c0edd4479509697b023bd8bee8826915ef6c2af036, '2021-11-03 20:22:15', 0, 0, 'de', 0, 1, 0);
|
|
||||||
INSERT INTO `login_user_backups` (`id`, `user_id`, `passphrase`, `mnemonic_type`) VALUES
|
|
||||||
// old
|
|
||||||
(1, 1, 'behind salmon fluid orphan frost elder amateur always panel palm leopard essay punch title fun annual page hundred journey select figure tunnel casual bar ', 2);
|
|
||||||
// new
|
|
||||||
(2, 2, 'behind salmon fluid orphan frost elder amateur always panel palm leopard essay punch title fun annual page hundred journey select figure tunnel casual bar ', 2);
|
|
||||||
*/
|
|
||||||
|
|
||||||
const KeyPairEd25519Create = (passphrase: string[]): Buffer[] => {
|
const KeyPairEd25519Create = (passphrase: string[]): Buffer[] => {
|
||||||
if (!passphrase.length || passphrase.length < PHRASE_WORD_COUNT) {
|
if (!passphrase.length || passphrase.length < PHRASE_WORD_COUNT) {
|
||||||
throw new Error('passphrase empty or to short')
|
throw new Error('passphrase empty or to short')
|
||||||
@ -207,6 +161,7 @@ export class UserResolver {
|
|||||||
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||||
const loginUser = await loginUserRepository.findByEmail(userEntity.email)
|
const loginUser = await loginUserRepository.findByEmail(userEntity.email)
|
||||||
const user = new User()
|
const user = new User()
|
||||||
|
user.id = userEntity.id
|
||||||
user.email = userEntity.email
|
user.email = userEntity.email
|
||||||
user.firstName = userEntity.firstName
|
user.firstName = userEntity.firstName
|
||||||
user.lastName = userEntity.lastName
|
user.lastName = userEntity.lastName
|
||||||
@ -239,13 +194,21 @@ export class UserResolver {
|
|||||||
@Ctx() context: any,
|
@Ctx() context: any,
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
email = email.trim().toLowerCase()
|
email = email.trim().toLowerCase()
|
||||||
// const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
|
|
||||||
// UnsecureLogin
|
|
||||||
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||||
const loginUser = await loginUserRepository.findByEmail(email).catch(() => {
|
const loginUser = await loginUserRepository.findByEmail(email).catch(() => {
|
||||||
throw new Error('No user with this credentials')
|
throw new Error('No user with this credentials')
|
||||||
})
|
})
|
||||||
if (!loginUser.emailChecked) throw new Error('user email not validated')
|
if (!loginUser.emailChecked) {
|
||||||
|
throw new Error('User email not validated')
|
||||||
|
}
|
||||||
|
if (loginUser.password === BigInt(0)) {
|
||||||
|
// TODO we want to catch this on the frontend and ask the user to check his emails or resend code
|
||||||
|
throw new Error('User has no password set yet')
|
||||||
|
}
|
||||||
|
if (!loginUser.pubKey || !loginUser.privKey) {
|
||||||
|
// TODO we want to catch this on the frontend and ask the user to check his emails or resend code
|
||||||
|
throw new Error('User has no private or publicKey')
|
||||||
|
}
|
||||||
const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
||||||
const loginUserPassword = BigInt(loginUser.password.toString())
|
const loginUserPassword = BigInt(loginUser.password.toString())
|
||||||
if (loginUserPassword !== passwordHash[0].readBigUInt64LE()) {
|
if (loginUserPassword !== passwordHash[0].readBigUInt64LE()) {
|
||||||
@ -276,6 +239,7 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const user = new User()
|
const user = new User()
|
||||||
|
user.id = userEntity.id
|
||||||
user.email = email
|
user.email = email
|
||||||
user.firstName = loginUser.firstName
|
user.firstName = loginUser.firstName
|
||||||
user.lastName = loginUser.lastName
|
user.lastName = loginUser.lastName
|
||||||
@ -318,22 +282,6 @@ export class UserResolver {
|
|||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.LOGIN_VIA_EMAIL_VERIFICATION_CODE])
|
|
||||||
@Query(() => LoginViaVerificationCode)
|
|
||||||
async loginViaEmailVerificationCode(
|
|
||||||
@Arg('optin') optin: string,
|
|
||||||
): Promise<LoginViaVerificationCode> {
|
|
||||||
// I cannot use number as type here.
|
|
||||||
// The value received is not the same as sent by the query
|
|
||||||
const result = await apiGet(
|
|
||||||
CONFIG.LOGIN_API_URL + 'loginViaEmailVerificationCode?emailVerificationCode=' + optin,
|
|
||||||
)
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.data)
|
|
||||||
}
|
|
||||||
return new LoginViaVerificationCode(result.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Authorized([RIGHTS.LOGOUT])
|
@Authorized([RIGHTS.LOGOUT])
|
||||||
@Query(() => String)
|
@Query(() => String)
|
||||||
async logout(): Promise<boolean> {
|
async logout(): Promise<boolean> {
|
||||||
@ -348,7 +296,7 @@ export class UserResolver {
|
|||||||
@Authorized([RIGHTS.CREATE_USER])
|
@Authorized([RIGHTS.CREATE_USER])
|
||||||
@Mutation(() => String)
|
@Mutation(() => String)
|
||||||
async createUser(
|
async createUser(
|
||||||
@Args() { email, firstName, lastName, password, language, publisherId }: CreateUserArgs,
|
@Args() { email, firstName, lastName, language, publisherId }: CreateUserArgs,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
|
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
|
||||||
// default int publisher_id = 0;
|
// default int publisher_id = 0;
|
||||||
@ -358,13 +306,6 @@ export class UserResolver {
|
|||||||
language = DEFAULT_LANGUAGE
|
language = DEFAULT_LANGUAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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!',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate username
|
// Validate username
|
||||||
// TODO: never true
|
// TODO: never true
|
||||||
const username = ''
|
const username = ''
|
||||||
@ -382,10 +323,10 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const passphrase = PassphraseGenerate()
|
const passphrase = PassphraseGenerate()
|
||||||
const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
// const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
||||||
const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
// const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
||||||
|
// const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
||||||
const emailHash = getEmailHash(email)
|
const emailHash = getEmailHash(email)
|
||||||
const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
|
||||||
|
|
||||||
// Table: login_users
|
// Table: login_users
|
||||||
const loginUser = new LoginUser()
|
const loginUser = new LoginUser()
|
||||||
@ -394,13 +335,13 @@ export class UserResolver {
|
|||||||
loginUser.lastName = lastName
|
loginUser.lastName = lastName
|
||||||
loginUser.username = username
|
loginUser.username = username
|
||||||
loginUser.description = ''
|
loginUser.description = ''
|
||||||
loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
// loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
||||||
loginUser.emailHash = emailHash
|
loginUser.emailHash = emailHash
|
||||||
loginUser.language = language
|
loginUser.language = language
|
||||||
loginUser.groupId = 1
|
loginUser.groupId = 1
|
||||||
loginUser.publisherId = publisherId
|
loginUser.publisherId = publisherId
|
||||||
loginUser.pubKey = keyPair[0]
|
// loginUser.pubKey = keyPair[0]
|
||||||
loginUser.privKey = encryptedPrivkey
|
// loginUser.privKey = encryptedPrivkey
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
await queryRunner.connect()
|
await queryRunner.connect()
|
||||||
@ -426,11 +367,13 @@ export class UserResolver {
|
|||||||
|
|
||||||
// Table: state_users
|
// Table: state_users
|
||||||
const dbUser = new DbUser()
|
const dbUser = new DbUser()
|
||||||
dbUser.pubkey = keyPair[0]
|
|
||||||
dbUser.email = email
|
dbUser.email = email
|
||||||
dbUser.firstName = firstName
|
dbUser.firstName = firstName
|
||||||
dbUser.lastName = lastName
|
dbUser.lastName = lastName
|
||||||
dbUser.username = username
|
dbUser.username = username
|
||||||
|
// TODO this field has no null allowed unlike the loginServer table
|
||||||
|
dbUser.pubkey = Buffer.alloc(32, 0) // default to 0000...
|
||||||
|
// dbUser.pubkey = keyPair[0]
|
||||||
|
|
||||||
await queryRunner.manager.save(dbUser).catch((er) => {
|
await queryRunner.manager.save(dbUser).catch((er) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@ -439,10 +382,11 @@ export class UserResolver {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Store EmailOptIn in DB
|
// Store EmailOptIn in DB
|
||||||
|
// TODO: this has duplicate code with sendResetPasswordEmail
|
||||||
const emailOptIn = new LoginEmailOptIn()
|
const emailOptIn = new LoginEmailOptIn()
|
||||||
emailOptIn.userId = loginUserId
|
emailOptIn.userId = loginUserId
|
||||||
emailOptIn.verificationCode = random(64)
|
emailOptIn.verificationCode = random(64)
|
||||||
emailOptIn.emailOptInTypeId = 2
|
emailOptIn.emailOptInTypeId = EMAIL_OPT_IN_REGISTER
|
||||||
|
|
||||||
await queryRunner.manager.save(emailOptIn).catch((error) => {
|
await queryRunner.manager.save(emailOptIn).catch((error) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@ -487,38 +431,172 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL])
|
@Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL])
|
||||||
@Query(() => SendPasswordResetEmailResponse)
|
@Query(() => Boolean)
|
||||||
async sendResetPasswordEmail(
|
async sendResetPasswordEmail(@Arg('email') email: string): Promise<boolean> {
|
||||||
@Arg('email') email: string,
|
// TODO: this has duplicate code with createUser
|
||||||
): Promise<SendPasswordResetEmailResponse> {
|
// TODO: Moriz: I think we do not need this variable.
|
||||||
const payload = {
|
let emailAlreadySend = false
|
||||||
email,
|
|
||||||
email_text: 7,
|
const loginUserRepository = await getCustomRepository(LoginUserRepository)
|
||||||
email_verification_code_type: 'resetPassword',
|
const loginUser = await loginUserRepository.findOneOrFail({ email })
|
||||||
|
|
||||||
|
const loginEmailOptInRepository = await getRepository(LoginEmailOptIn)
|
||||||
|
let optInCode = await loginEmailOptInRepository.findOne({
|
||||||
|
userId: loginUser.id,
|
||||||
|
emailOptInTypeId: EMAIL_OPT_IN_RESET_PASSWORD,
|
||||||
|
})
|
||||||
|
if (optInCode) {
|
||||||
|
emailAlreadySend = true
|
||||||
|
} else {
|
||||||
|
optInCode = new LoginEmailOptIn()
|
||||||
|
optInCode.verificationCode = random(64)
|
||||||
|
optInCode.userId = loginUser.id
|
||||||
|
optInCode.emailOptInTypeId = EMAIL_OPT_IN_RESET_PASSWORD
|
||||||
|
await loginEmailOptInRepository.save(optInCode)
|
||||||
}
|
}
|
||||||
const response = await apiPost(CONFIG.LOGIN_API_URL + 'sendEmail', payload)
|
|
||||||
if (!response.success) {
|
const link = CONFIG.EMAIL_LINK_SETPASSWORD.replace(
|
||||||
throw new Error(response.data)
|
/\$1/g,
|
||||||
|
optInCode.verificationCode.toString(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (emailAlreadySend) {
|
||||||
|
const timeElapsed = Date.now() - new Date(optInCode.updatedAt).getTime()
|
||||||
|
if (timeElapsed <= 10 * 60 * 1000) {
|
||||||
|
throw new Error('email already sent less than 10 minutes before')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new SendPasswordResetEmailResponse(response.data)
|
|
||||||
|
const emailSent = await sendEMail({
|
||||||
|
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
|
||||||
|
to: `${loginUser.firstName} ${loginUser.lastName} <${email}>`,
|
||||||
|
subject: 'Gradido: Reset Password',
|
||||||
|
text: `Hallo ${loginUser.firstName} ${loginUser.lastName},
|
||||||
|
|
||||||
|
Du oder jemand anderes hat für dieses Konto ein Zurücksetzen des Passworts angefordert.
|
||||||
|
Wenn du es warst, klicke bitte auf den Link: ${link}
|
||||||
|
oder kopiere den obigen Link in Dein Browserfenster.
|
||||||
|
|
||||||
|
Mit freundlichen Grüßen,
|
||||||
|
dein Gradido-Team`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// In case EMails are disabled log the activation link for the user
|
||||||
|
if (!emailSent) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Reset password link: ${link}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.RESET_PASSWORD])
|
@Authorized([RIGHTS.SET_PASSWORD])
|
||||||
@Mutation(() => String)
|
@Mutation(() => Boolean)
|
||||||
async resetPassword(
|
async setPassword(
|
||||||
@Args()
|
@Arg('code') code: string,
|
||||||
{ sessionId, email, password }: ChangePasswordArgs,
|
@Arg('password') password: string,
|
||||||
): Promise<string> {
|
): Promise<boolean> {
|
||||||
const payload = {
|
// Validate Password
|
||||||
session_id: sessionId,
|
if (!isPassword(password)) {
|
||||||
email,
|
throw new Error(
|
||||||
password,
|
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
const result = await apiPost(CONFIG.LOGIN_API_URL + 'resetPassword', payload)
|
|
||||||
if (!result.success) {
|
// Load code
|
||||||
throw new Error(result.data)
|
const loginEmailOptInRepository = await getRepository(LoginEmailOptIn)
|
||||||
|
const optInCode = await loginEmailOptInRepository
|
||||||
|
.findOneOrFail({ verificationCode: code })
|
||||||
|
.catch(() => {
|
||||||
|
throw new Error('Could not login with emailVerificationCode')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Code is only valid for 10minutes
|
||||||
|
const timeElapsed = Date.now() - new Date(optInCode.updatedAt).getTime()
|
||||||
|
if (timeElapsed > 10 * 60 * 1000) {
|
||||||
|
throw new Error('Code is older than 10 minutes')
|
||||||
}
|
}
|
||||||
return 'success'
|
|
||||||
|
// load loginUser
|
||||||
|
const loginUserRepository = await getCustomRepository(LoginUserRepository)
|
||||||
|
const loginUser = await loginUserRepository
|
||||||
|
.findOneOrFail({ id: optInCode.userId })
|
||||||
|
.catch(() => {
|
||||||
|
throw new Error('Could not find corresponding Login User')
|
||||||
|
})
|
||||||
|
|
||||||
|
// load user
|
||||||
|
const dbUserRepository = await getCustomRepository(UserRepository)
|
||||||
|
const dbUser = await dbUserRepository.findOneOrFail({ email: loginUser.email }).catch(() => {
|
||||||
|
throw new Error('Could not find corresponding User')
|
||||||
|
})
|
||||||
|
|
||||||
|
const loginUserBackupRepository = await getRepository(LoginUserBackup)
|
||||||
|
const loginUserBackup = await loginUserBackupRepository
|
||||||
|
.findOneOrFail({ userId: loginUser.id })
|
||||||
|
.catch(() => {
|
||||||
|
throw new Error('Could not find corresponding BackupUser')
|
||||||
|
})
|
||||||
|
|
||||||
|
const passphrase = loginUserBackup.passphrase.slice(0, -1).split(' ')
|
||||||
|
if (passphrase.length < PHRASE_WORD_COUNT) {
|
||||||
|
// TODO if this can happen we cannot recover from that
|
||||||
|
throw new Error('Could not load a correct passphrase')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate EMail
|
||||||
|
loginUser.emailChecked = true
|
||||||
|
|
||||||
|
// Update Password
|
||||||
|
const passwordHash = SecretKeyCryptographyCreateKey(loginUser.email, password) // return short and long hash
|
||||||
|
const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
||||||
|
const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
||||||
|
loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
||||||
|
loginUser.pubKey = keyPair[0]
|
||||||
|
loginUser.privKey = encryptedPrivkey
|
||||||
|
dbUser.pubkey = keyPair[0]
|
||||||
|
|
||||||
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
|
await queryRunner.connect()
|
||||||
|
await queryRunner.startTransaction('READ UNCOMMITTED')
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Save loginUser
|
||||||
|
await queryRunner.manager.save(loginUser).catch((error) => {
|
||||||
|
throw new Error('error saving loginUser: ' + error)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Save user
|
||||||
|
await queryRunner.manager.save(dbUser).catch((error) => {
|
||||||
|
throw new Error('error saving user: ' + error)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete Code
|
||||||
|
await queryRunner.manager.remove(optInCode).catch((error) => {
|
||||||
|
throw new Error('error deleting code: ' + error)
|
||||||
|
})
|
||||||
|
|
||||||
|
await queryRunner.commitTransaction()
|
||||||
|
} catch (e) {
|
||||||
|
await queryRunner.rollbackTransaction()
|
||||||
|
throw e
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign into Klicktipp
|
||||||
|
// TODO do we always signUp the user? How to handle things with old users?
|
||||||
|
if (optInCode.emailOptInTypeId === EMAIL_OPT_IN_REGISTER) {
|
||||||
|
try {
|
||||||
|
await signIn(loginUser.email, loginUser.language, loginUser.firstName, loginUser.lastName)
|
||||||
|
} catch {
|
||||||
|
// TODO is this a problem?
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Could not subscribe to klicktipp')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.UPDATE_USER_INFOS])
|
@Authorized([RIGHTS.UPDATE_USER_INFOS])
|
||||||
@ -654,19 +732,6 @@ export class UserResolver {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.CHECK_EMAIL])
|
|
||||||
@Query(() => CheckEmailResponse)
|
|
||||||
@UseMiddleware(klicktippRegistrationMiddleware)
|
|
||||||
async checkEmail(@Arg('optin') optin: string): Promise<CheckEmailResponse> {
|
|
||||||
const result = await apiGet(
|
|
||||||
CONFIG.LOGIN_API_URL + 'loginViaEmailVerificationCode?emailVerificationCode=' + optin,
|
|
||||||
)
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.data)
|
|
||||||
}
|
|
||||||
return new CheckEmailResponse(result.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Authorized([RIGHTS.HAS_ELOPAGE])
|
@Authorized([RIGHTS.HAS_ELOPAGE])
|
||||||
@Query(() => Boolean)
|
@Query(() => Boolean)
|
||||||
async hasElopage(@Ctx() context: any): Promise<boolean> {
|
async hasElopage(@Ctx() context: any): Promise<boolean> {
|
||||||
|
|||||||
@ -1,20 +1,20 @@
|
|||||||
import { MiddlewareFn } from 'type-graphql'
|
import { MiddlewareFn } from 'type-graphql'
|
||||||
import { signIn, getKlickTippUser } from '../apis/KlicktippController'
|
import { /* signIn, */ getKlickTippUser } from '../apis/KlicktippController'
|
||||||
import { KlickTipp } from '../graphql/model/KlickTipp'
|
import { KlickTipp } from '../graphql/model/KlickTipp'
|
||||||
import CONFIG from '../config/index'
|
import CONFIG from '../config/index'
|
||||||
|
|
||||||
export const klicktippRegistrationMiddleware: MiddlewareFn = async (
|
// export const klicktippRegistrationMiddleware: MiddlewareFn = async (
|
||||||
// Only for demo
|
// // Only for demo
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
// /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||||
{ root, args, context, info },
|
// { root, args, context, info },
|
||||||
next,
|
// next,
|
||||||
) => {
|
// ) => {
|
||||||
// Do Something here before resolver is called
|
// // Do Something here before resolver is called
|
||||||
const result = await next()
|
// const result = await next()
|
||||||
// Do Something here after resolver is completed
|
// // Do Something here after resolver is completed
|
||||||
await signIn(result.email, result.language, result.firstName, result.lastName)
|
// await signIn(result.email, result.language, result.firstName, result.lastName)
|
||||||
return result
|
// return result
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const klicktippNewsletterStateMiddleware: MiddlewareFn = async (
|
export const klicktippNewsletterStateMiddleware: MiddlewareFn = async (
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||||
|
|||||||
@ -29,7 +29,7 @@ import { elopageWebhook } from '../webhook/elopage'
|
|||||||
// TODO implement
|
// TODO implement
|
||||||
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
|
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
|
||||||
|
|
||||||
const DB_VERSION = '0004-login_server_data'
|
const DB_VERSION = '0005-admin_tables'
|
||||||
|
|
||||||
const createServer = async (context: any = serverContext): Promise<any> => {
|
const createServer = async (context: any = serverContext): Promise<any> => {
|
||||||
// open mysql connection
|
// open mysql connection
|
||||||
|
|||||||
5
backend/src/typeorm/repository/PendingCreation.ts
Normal file
5
backend/src/typeorm/repository/PendingCreation.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { EntityRepository, Repository } from 'typeorm'
|
||||||
|
import { LoginPendingTasksAdmin } from '@entity/LoginPendingTasksAdmin'
|
||||||
|
|
||||||
|
@EntityRepository(LoginPendingTasksAdmin)
|
||||||
|
export class PendingCreationRepository extends Repository<LoginPendingTasksAdmin> {}
|
||||||
5
backend/src/typeorm/repository/TransactionCreation.ts
Normal file
5
backend/src/typeorm/repository/TransactionCreation.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { EntityRepository, Repository } from 'typeorm'
|
||||||
|
import { TransactionCreation } from '@entity/TransactionCreation'
|
||||||
|
|
||||||
|
@EntityRepository(TransactionCreation)
|
||||||
|
export class TransactionCreationRepository extends Repository<TransactionCreation> {}
|
||||||
@ -30,4 +30,17 @@ export class UserRepository extends Repository<User> {
|
|||||||
})
|
})
|
||||||
return usersIndiced
|
return usersIndiced
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findBySearchCriteria(searchCriteria: string): Promise<User[]> {
|
||||||
|
return await this.createQueryBuilder('user')
|
||||||
|
.where(
|
||||||
|
'user.firstName like :name or user.lastName like :lastName or user.email like :email',
|
||||||
|
{
|
||||||
|
name: `%${searchCriteria}%`,
|
||||||
|
lastName: `%${searchCriteria}%`,
|
||||||
|
email: `%${searchCriteria}%`,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.getMany()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
/*
|
/*
|
||||||
Elopage Webhook
|
Elopage Webhook
|
||||||
|
|
||||||
|
|||||||
@ -4139,6 +4139,11 @@ module-alias@^2.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0"
|
resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0"
|
||||||
integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==
|
integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==
|
||||||
|
|
||||||
|
moment@^2.29.1:
|
||||||
|
version "2.29.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||||
|
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
|||||||
@ -39,6 +39,7 @@ yarn seed
|
|||||||
## Seeded Users
|
## Seeded Users
|
||||||
|
|
||||||
| email | password | admin |
|
| email | password | admin |
|
||||||
|
|------------------------|------------|---------|
|
||||||
| peter@lustig.de | `Aa12345_` | `true` |
|
| peter@lustig.de | `Aa12345_` | `true` |
|
||||||
| bibi@bloxberg.de | `Aa12345_` | `false` |
|
| bibi@bloxberg.de | `Aa12345_` | `false` |
|
||||||
| raeuber@hotzenplotz.de | `Aa12345_` | `false` |
|
| raeuber@hotzenplotz.de | `Aa12345_` | `false` |
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, JoinColumn, OneToOne } from 'typeorm'
|
||||||
|
import { User } from '../User'
|
||||||
|
|
||||||
@Entity('state_balances')
|
@Entity('state_balances')
|
||||||
export class Balance extends BaseEntity {
|
export class Balance extends BaseEntity {
|
||||||
@ -16,4 +17,8 @@ export class Balance extends BaseEntity {
|
|||||||
|
|
||||||
@Column({ type: 'bigint' })
|
@Column({ type: 'bigint' })
|
||||||
amount: number
|
amount: number
|
||||||
|
|
||||||
|
@OneToOne(() => User, { nullable: false })
|
||||||
|
@JoinColumn({ name: 'state_user_id' })
|
||||||
|
user: User
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,4 @@
|
|||||||
import {
|
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm'
|
||||||
BaseEntity,
|
|
||||||
Entity,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Column,
|
|
||||||
Timestamp,
|
|
||||||
OneToOne,
|
|
||||||
JoinColumn,
|
|
||||||
} from 'typeorm'
|
|
||||||
import { Transaction } from './Transaction'
|
import { Transaction } from './Transaction'
|
||||||
|
|
||||||
@Entity('transaction_creations')
|
@Entity('transaction_creations')
|
||||||
@ -24,7 +16,7 @@ export class TransactionCreation extends BaseEntity {
|
|||||||
amount: number
|
amount: number
|
||||||
|
|
||||||
@Column({ name: 'target_date', type: 'timestamp' })
|
@Column({ name: 'target_date', type: 'timestamp' })
|
||||||
targetDate: Timestamp
|
targetDate: Date
|
||||||
|
|
||||||
@OneToOne(() => Transaction)
|
@OneToOne(() => Transaction)
|
||||||
@JoinColumn({ name: 'transaction_id' })
|
@JoinColumn({ name: 'transaction_id' })
|
||||||
|
|||||||
21
database/entity/0001-init_db/TransactionSignature.ts
Normal file
21
database/entity/0001-init_db/TransactionSignature.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm'
|
||||||
|
import { Transaction } from './Transaction'
|
||||||
|
|
||||||
|
@Entity('transaction_signatures')
|
||||||
|
export class TransactionSignature extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({ name: 'transaction_id' })
|
||||||
|
transactionId: number
|
||||||
|
|
||||||
|
@Column({ type: 'binary', length: 64 })
|
||||||
|
signature: Buffer
|
||||||
|
|
||||||
|
@Column({ type: 'binary', length: 32 })
|
||||||
|
pubkey: Buffer
|
||||||
|
|
||||||
|
@ManyToOne(() => Transaction)
|
||||||
|
@JoinColumn({ name: 'transaction_id' })
|
||||||
|
transaction: Transaction
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm'
|
||||||
|
import { Balance } from '../Balance'
|
||||||
|
|
||||||
// Moriz: I do not like the idea of having two user tables
|
// Moriz: I do not like the idea of having two user tables
|
||||||
@Entity('state_users')
|
@Entity('state_users')
|
||||||
@ -29,4 +30,7 @@ export class User extends BaseEntity {
|
|||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
disabled: boolean
|
disabled: boolean
|
||||||
|
|
||||||
|
@OneToOne(() => Balance, (balance) => balance.user)
|
||||||
|
balance: Balance
|
||||||
}
|
}
|
||||||
|
|||||||
25
database/entity/0005-admin_tables/LoginPendingTasksAdmin.ts
Normal file
25
database/entity/0005-admin_tables/LoginPendingTasksAdmin.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
|
||||||
|
|
||||||
|
@Entity('login_pending_tasks_admin')
|
||||||
|
export class LoginPendingTasksAdmin extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({ unsigned: true, nullable: false })
|
||||||
|
userId: number
|
||||||
|
|
||||||
|
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
created: Date
|
||||||
|
|
||||||
|
@Column({ type: 'datetime', nullable: false })
|
||||||
|
date: Date
|
||||||
|
|
||||||
|
@Column({ length: 256, nullable: true, default: null })
|
||||||
|
memo: string
|
||||||
|
|
||||||
|
@Column({ type: 'bigint', nullable: false })
|
||||||
|
amount: BigInt
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
moderator: number
|
||||||
|
}
|
||||||
1
database/entity/LoginPendingTasksAdmin.ts
Normal file
1
database/entity/LoginPendingTasksAdmin.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { LoginPendingTasksAdmin } from './0005-admin_tables/LoginPendingTasksAdmin'
|
||||||
1
database/entity/TransactionSignature.ts
Normal file
1
database/entity/TransactionSignature.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { TransactionSignature } from './0001-init_db/TransactionSignature'
|
||||||
@ -8,10 +8,12 @@ import { Migration } from './Migration'
|
|||||||
import { ServerUser } from './ServerUser'
|
import { ServerUser } from './ServerUser'
|
||||||
import { Transaction } from './Transaction'
|
import { Transaction } from './Transaction'
|
||||||
import { TransactionCreation } from './TransactionCreation'
|
import { TransactionCreation } from './TransactionCreation'
|
||||||
|
import { TransactionSignature } from './TransactionSignature'
|
||||||
import { TransactionSendCoin } from './TransactionSendCoin'
|
import { TransactionSendCoin } from './TransactionSendCoin'
|
||||||
import { User } from './User'
|
import { User } from './User'
|
||||||
import { UserSetting } from './UserSetting'
|
import { UserSetting } from './UserSetting'
|
||||||
import { UserTransaction } from './UserTransaction'
|
import { UserTransaction } from './UserTransaction'
|
||||||
|
import { LoginPendingTasksAdmin } from './LoginPendingTasksAdmin'
|
||||||
|
|
||||||
export const entities = [
|
export const entities = [
|
||||||
Balance,
|
Balance,
|
||||||
@ -24,8 +26,10 @@ export const entities = [
|
|||||||
ServerUser,
|
ServerUser,
|
||||||
Transaction,
|
Transaction,
|
||||||
TransactionCreation,
|
TransactionCreation,
|
||||||
|
TransactionSignature,
|
||||||
TransactionSendCoin,
|
TransactionSendCoin,
|
||||||
User,
|
User,
|
||||||
UserSetting,
|
UserSetting,
|
||||||
UserTransaction,
|
UserTransaction,
|
||||||
|
LoginPendingTasksAdmin,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
CREATE TABLE \`user_setting\` (
|
CREATE TABLE IF NOT EXISTS \`user_setting\` (
|
||||||
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
\`userId\` int(11) NOT NULL,
|
\`userId\` int(11) NOT NULL,
|
||||||
\`key\` varchar(255) NOT NULL,
|
\`key\` varchar(255) NOT NULL,
|
||||||
@ -25,5 +25,5 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
|
|
||||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
// write downgrade logic as parameter of queryFn
|
// write downgrade logic as parameter of queryFn
|
||||||
await queryFn(`DROP TABLE \`user_setting\`;`)
|
await queryFn(`DROP TABLE IF EXISTS \`user_setting\`;`)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
CREATE TABLE \`login_app_access_tokens\` (
|
CREATE TABLE IF NOT EXISTS \`login_app_access_tokens\` (
|
||||||
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
\`user_id\` int NOT NULL,
|
\`user_id\` int NOT NULL,
|
||||||
\`access_code\` bigint unsigned NOT NULL,
|
\`access_code\` bigint unsigned NOT NULL,
|
||||||
@ -25,7 +25,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
CREATE TABLE \`login_elopage_buys\` (
|
CREATE TABLE IF NOT EXISTS \`login_elopage_buys\` (
|
||||||
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
\`elopage_user_id\` int DEFAULT NULL,
|
\`elopage_user_id\` int DEFAULT NULL,
|
||||||
\`affiliate_program_id\` int NOT NULL,
|
\`affiliate_program_id\` int NOT NULL,
|
||||||
@ -42,7 +42,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
CREATE TABLE \`login_email_opt_in_types\` (
|
CREATE TABLE IF NOT EXISTS \`login_email_opt_in_types\` (
|
||||||
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
\`name\` varchar(255) NOT NULL,
|
\`name\` varchar(255) NOT NULL,
|
||||||
\`description\` varchar(255) NOT NULL,
|
\`description\` varchar(255) NOT NULL,
|
||||||
@ -50,7 +50,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
CREATE TABLE \`login_email_opt_in\` (
|
CREATE TABLE IF NOT EXISTS \`login_email_opt_in\` (
|
||||||
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
\`user_id\` int NOT NULL,
|
\`user_id\` int NOT NULL,
|
||||||
\`verification_code\` bigint unsigned NOT NULL,
|
\`verification_code\` bigint unsigned NOT NULL,
|
||||||
@ -63,7 +63,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
CREATE TABLE \`login_groups\` (
|
CREATE TABLE IF NOT EXISTS \`login_groups\` (
|
||||||
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
\`alias\` varchar(190) NOT NULL,
|
\`alias\` varchar(190) NOT NULL,
|
||||||
\`name\` varchar(255) NOT NULL,
|
\`name\` varchar(255) NOT NULL,
|
||||||
@ -76,7 +76,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
CREATE TABLE \`login_pending_tasks\` (
|
CREATE TABLE IF NOT EXISTS \`login_pending_tasks\` (
|
||||||
\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT,
|
\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
\`user_id\` int UNSIGNED DEFAULT 0,
|
\`user_id\` int UNSIGNED DEFAULT 0,
|
||||||
\`request\` varbinary(2048) NOT NULL,
|
\`request\` varbinary(2048) NOT NULL,
|
||||||
@ -91,7 +91,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
CREATE TABLE \`login_roles\` (
|
CREATE TABLE IF NOT EXISTS \`login_roles\` (
|
||||||
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
\`name\` varchar(255) NOT NULL,
|
\`name\` varchar(255) NOT NULL,
|
||||||
\`description\` varchar(255) NOT NULL,
|
\`description\` varchar(255) NOT NULL,
|
||||||
@ -100,7 +100,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
CREATE TABLE \`login_user_backups\` (
|
CREATE TABLE IF NOT EXISTS \`login_user_backups\` (
|
||||||
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
\`user_id\` int NOT NULL,
|
\`user_id\` int NOT NULL,
|
||||||
\`passphrase\` text NOT NULL,
|
\`passphrase\` text NOT NULL,
|
||||||
@ -109,7 +109,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
CREATE TABLE \`login_user_roles\` (
|
CREATE TABLE IF NOT EXISTS \`login_user_roles\` (
|
||||||
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
\`user_id\` int NOT NULL,
|
\`user_id\` int NOT NULL,
|
||||||
\`role_id\` int NOT NULL,
|
\`role_id\` int NOT NULL,
|
||||||
@ -117,7 +117,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
CREATE TABLE \`login_users\` (
|
CREATE TABLE IF NOT EXISTS \`login_users\` (
|
||||||
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
\`email\` varchar(191) NOT NULL,
|
\`email\` varchar(191) NOT NULL,
|
||||||
\`first_name\` varchar(150) NOT NULL,
|
\`first_name\` varchar(150) NOT NULL,
|
||||||
@ -143,14 +143,14 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
|
|
||||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
// write downgrade logic as parameter of queryFn
|
// write downgrade logic as parameter of queryFn
|
||||||
await queryFn(`DROP TABLE \`login_app_access_tokens\`;`)
|
await queryFn(`DROP TABLE IF EXISTS \`login_app_access_tokens\`;`)
|
||||||
await queryFn(`DROP TABLE \`login_elopage_buys\`;`)
|
await queryFn(`DROP TABLE IF EXISTS \`login_elopage_buys\`;`)
|
||||||
await queryFn(`DROP TABLE \`login_email_opt_in_types\`;`)
|
await queryFn(`DROP TABLE IF EXISTS \`login_email_opt_in_types\`;`)
|
||||||
await queryFn(`DROP TABLE \`login_email_opt_in\`;`)
|
await queryFn(`DROP TABLE IF EXISTS \`login_email_opt_in\`;`)
|
||||||
await queryFn(`DROP TABLE \`login_groups\`;`)
|
await queryFn(`DROP TABLE IF EXISTS \`login_groups\`;`)
|
||||||
await queryFn(`DROP TABLE \`login_pending_tasks\`;`)
|
await queryFn(`DROP TABLE IF EXISTS \`login_pending_tasks\`;`)
|
||||||
await queryFn(`DROP TABLE \`login_roles\`;`)
|
await queryFn(`DROP TABLE IF EXISTS \`login_roles\`;`)
|
||||||
await queryFn(`DROP TABLE \`login_user_backups\`;`)
|
await queryFn(`DROP TABLE IF EXISTS \`login_user_backups\`;`)
|
||||||
await queryFn(`DROP TABLE \`login_user_roles\`;`)
|
await queryFn(`DROP TABLE IF EXISTS \`login_user_roles\`;`)
|
||||||
await queryFn(`DROP TABLE \`login_users\`;`)
|
await queryFn(`DROP TABLE IF EXISTS \`login_users\`;`)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,34 +25,34 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
}
|
}
|
||||||
|
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_app_access_tokens\` SELECT * FROM ${LOGIN_SERVER_DB}.\`app_access_tokens\`;
|
INSERT IGNORE INTO \`login_app_access_tokens\` SELECT * FROM ${LOGIN_SERVER_DB}.\`app_access_tokens\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_elopage_buys\` SELECT * FROM ${LOGIN_SERVER_DB}.\`elopage_buys\`;
|
INSERT IGNORE INTO \`login_elopage_buys\` SELECT * FROM ${LOGIN_SERVER_DB}.\`elopage_buys\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_email_opt_in_types\` SELECT * FROM ${LOGIN_SERVER_DB}.\`email_opt_in_types\`;
|
INSERT IGNORE INTO \`login_email_opt_in_types\` SELECT * FROM ${LOGIN_SERVER_DB}.\`email_opt_in_types\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_email_opt_in\` SELECT * FROM ${LOGIN_SERVER_DB}.\`email_opt_in\`;
|
INSERT IGNORE INTO \`login_email_opt_in\` SELECT * FROM ${LOGIN_SERVER_DB}.\`email_opt_in\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_groups\` SELECT * FROM ${LOGIN_SERVER_DB}.\`groups\`;
|
INSERT IGNORE INTO \`login_groups\` SELECT * FROM ${LOGIN_SERVER_DB}.\`groups\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_pending_tasks\` SELECT * FROM ${LOGIN_SERVER_DB}.\`pending_tasks\`;
|
INSERT IGNORE INTO \`login_pending_tasks\` SELECT * FROM ${LOGIN_SERVER_DB}.\`pending_tasks\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_roles\` SELECT * FROM ${LOGIN_SERVER_DB}.\`roles\`;
|
INSERT IGNORE INTO \`login_roles\` SELECT * FROM ${LOGIN_SERVER_DB}.\`roles\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_user_backups\` SELECT * FROM ${LOGIN_SERVER_DB}.\`user_backups\`;
|
INSERT IGNORE INTO \`login_user_backups\` SELECT * FROM ${LOGIN_SERVER_DB}.\`user_backups\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_user_roles\` SELECT * FROM ${LOGIN_SERVER_DB}.\`user_roles\`;
|
INSERT IGNORE INTO \`login_user_roles\` SELECT * FROM ${LOGIN_SERVER_DB}.\`user_roles\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_users\` SELECT * FROM ${LOGIN_SERVER_DB}.\`users\`;
|
INSERT IGNORE INTO \`login_users\` SELECT * FROM ${LOGIN_SERVER_DB}.\`users\`;
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// TODO clarify if we need this on non docker environment?
|
// TODO clarify if we need this on non docker environment?
|
||||||
@ -63,15 +63,5 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
// write downgrade logic as parameter of queryFn
|
// TODO NO EMPTY FUNCTION
|
||||||
await queryFn(`DELETE FROM \`login_app_access_tokens\`;`)
|
|
||||||
await queryFn(`DELETE FROM \`login_elopage_buys\`;`)
|
|
||||||
await queryFn(`DELETE FROM \`login_email_opt_in_types\`;`)
|
|
||||||
await queryFn(`DELETE FROM \`login_email_opt_in\`;`)
|
|
||||||
await queryFn(`DELETE FROM \`login_groups\`;`)
|
|
||||||
await queryFn(`DELETE FROM \`login_pending_tasks\`;`)
|
|
||||||
await queryFn(`DELETE FROM \`login_roles\`;`)
|
|
||||||
await queryFn(`DELETE FROM \`login_user_backups\`;`)
|
|
||||||
await queryFn(`DELETE FROM \`login_user_roles\`;`)
|
|
||||||
await queryFn(`DELETE FROM \`login_users\`;`)
|
|
||||||
}
|
}
|
||||||
|
|||||||
29
database/migrations/0005-admin_tables.ts
Normal file
29
database/migrations/0005-admin_tables.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/* MIGRATION FOR ADMIN INTERFACE
|
||||||
|
*
|
||||||
|
* This migration is special since it takes into account that
|
||||||
|
* the database can be setup already but also may not be.
|
||||||
|
* Therefore you will find all `CREATE TABLE` statements with
|
||||||
|
* a `IF NOT EXISTS`, all `INSERT` with an `IGNORE` and in the
|
||||||
|
* downgrade function all `DROP TABLE` with a `IF EXISTS`.
|
||||||
|
* This ensures compatibility for existing or non-existing
|
||||||
|
* databases.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
await queryFn(`
|
||||||
|
CREATE TABLE IF NOT EXISTS \`login_pending_tasks_admin\` (
|
||||||
|
\`id\` int UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
\`userId\` int UNSIGNED DEFAULT 0,
|
||||||
|
\`created\` datetime NOT NULL,
|
||||||
|
\`date\` datetime NOT NULL,
|
||||||
|
\`memo\` text DEFAULT NULL,
|
||||||
|
\`amount\` bigint(20) NOT NULL,
|
||||||
|
\`moderator\` int UNSIGNED DEFAULT 0,
|
||||||
|
PRIMARY KEY (\`id\`)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
await queryFn(`DROP TABLE IF EXISTS \`login_pending_tasks_admin\`;`)
|
||||||
|
}
|
||||||
18
database/src/factories/balance.factory.ts
Normal file
18
database/src/factories/balance.factory.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import Faker from 'faker'
|
||||||
|
import { define } from 'typeorm-seeding'
|
||||||
|
import { Balance } from '../../entity/Balance'
|
||||||
|
import { BalanceContext } from '../interface/TransactionContext'
|
||||||
|
|
||||||
|
define(Balance, (faker: typeof Faker, context?: BalanceContext) => {
|
||||||
|
if (!context || !context.user) {
|
||||||
|
throw new Error('Balance: No user present!')
|
||||||
|
}
|
||||||
|
|
||||||
|
const balance = new Balance()
|
||||||
|
balance.modified = context.modified ? context.modified : faker.date.recent()
|
||||||
|
balance.recordDate = context.recordDate ? context.recordDate : faker.date.recent()
|
||||||
|
balance.amount = context.amount ? context.amount : 10000000
|
||||||
|
balance.user = context.user
|
||||||
|
|
||||||
|
return balance
|
||||||
|
})
|
||||||
18
database/src/factories/transaction-creation.factory.ts
Normal file
18
database/src/factories/transaction-creation.factory.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import Faker from 'faker'
|
||||||
|
import { define } from 'typeorm-seeding'
|
||||||
|
import { TransactionCreation } from '../../entity/TransactionCreation'
|
||||||
|
import { TransactionCreationContext } from '../interface/TransactionContext'
|
||||||
|
|
||||||
|
define(TransactionCreation, (faker: typeof Faker, context?: TransactionCreationContext) => {
|
||||||
|
if (!context || !context.userId || !context.transaction) {
|
||||||
|
throw new Error('TransactionCreation: No userId and/or transaction present!')
|
||||||
|
}
|
||||||
|
|
||||||
|
const transactionCreation = new TransactionCreation()
|
||||||
|
transactionCreation.userId = context.userId
|
||||||
|
transactionCreation.amount = context.amount ? context.amount : 100000
|
||||||
|
transactionCreation.targetDate = context.targetDate ? context.targetDate : new Date()
|
||||||
|
transactionCreation.transaction = context.transaction
|
||||||
|
|
||||||
|
return transactionCreation
|
||||||
|
})
|
||||||
18
database/src/factories/transaction-signature.factory.ts
Normal file
18
database/src/factories/transaction-signature.factory.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import Faker from 'faker'
|
||||||
|
import { define } from 'typeorm-seeding'
|
||||||
|
import { TransactionSignature } from '../../entity/TransactionSignature'
|
||||||
|
import { TransactionSignatureContext } from '../interface/TransactionContext'
|
||||||
|
import { randomBytes } from 'crypto'
|
||||||
|
|
||||||
|
define(TransactionSignature, (faker: typeof Faker, context?: TransactionSignatureContext) => {
|
||||||
|
if (!context || !context.transaction) {
|
||||||
|
throw new Error('TransactionSignature: No transaction present!')
|
||||||
|
}
|
||||||
|
|
||||||
|
const transactionSignature = new TransactionSignature()
|
||||||
|
transactionSignature.signature = context.signature ? context.signature : randomBytes(64)
|
||||||
|
transactionSignature.pubkey = context.pubkey ? context.pubkey : randomBytes(32)
|
||||||
|
transactionSignature.transaction = context.transaction
|
||||||
|
|
||||||
|
return transactionSignature
|
||||||
|
})
|
||||||
20
database/src/factories/transaction.factory.ts
Normal file
20
database/src/factories/transaction.factory.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import Faker from 'faker'
|
||||||
|
import { define } from 'typeorm-seeding'
|
||||||
|
import { Transaction } from '../../entity/Transaction'
|
||||||
|
import { TransactionContext } from '../interface/TransactionContext'
|
||||||
|
import { randomBytes } from 'crypto'
|
||||||
|
|
||||||
|
define(Transaction, (faker: typeof Faker, context?: TransactionContext) => {
|
||||||
|
if (!context) context = {}
|
||||||
|
|
||||||
|
const transaction = new Transaction()
|
||||||
|
transaction.transactionTypeId = context.transactionTypeId ? context.transactionTypeId : 2
|
||||||
|
transaction.txHash = context.txHash ? context.txHash : randomBytes(48)
|
||||||
|
transaction.memo = context.memo || context.memo === '' ? context.memo : faker.lorem.sentence()
|
||||||
|
transaction.received = context.received ? context.received : new Date()
|
||||||
|
transaction.blockchainTypeId = context.blockchainTypeId ? context.blockchainTypeId : 1
|
||||||
|
if (context.transactionSendCoin) transaction.transactionSendCoin = context.transactionSendCoin
|
||||||
|
if (context.transactionCreation) transaction.transactionCreation = context.transactionCreation
|
||||||
|
|
||||||
|
return transaction
|
||||||
|
})
|
||||||
19
database/src/factories/user-transaction.factory.ts
Normal file
19
database/src/factories/user-transaction.factory.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Faker from 'faker'
|
||||||
|
import { define } from 'typeorm-seeding'
|
||||||
|
import { UserTransaction } from '../../entity/UserTransaction'
|
||||||
|
import { UserTransactionContext } from '../interface/TransactionContext'
|
||||||
|
|
||||||
|
define(UserTransaction, (faker: typeof Faker, context?: UserTransactionContext) => {
|
||||||
|
if (!context || !context.userId || !context.transactionId) {
|
||||||
|
throw new Error('UserTransaction: No userId and/or transactionId present!')
|
||||||
|
}
|
||||||
|
|
||||||
|
const userTransaction = new UserTransaction()
|
||||||
|
userTransaction.userId = context.userId
|
||||||
|
userTransaction.transactionId = context.transactionId
|
||||||
|
userTransaction.transactionTypeId = context.transactionTypeId ? context.transactionTypeId : 1
|
||||||
|
userTransaction.balance = context.balance ? context.balance : 100000
|
||||||
|
userTransaction.balanceDate = context.balanceDate ? context.balanceDate : new Date()
|
||||||
|
|
||||||
|
return userTransaction
|
||||||
|
})
|
||||||
@ -9,6 +9,7 @@ import { CreatePeterLustigSeed } from './seeds/users/peter-lustig.admin.seed'
|
|||||||
import { CreateBibiBloxbergSeed } from './seeds/users/bibi-bloxberg.seed'
|
import { CreateBibiBloxbergSeed } from './seeds/users/bibi-bloxberg.seed'
|
||||||
import { CreateRaeuberHotzenplotzSeed } from './seeds/users/raeuber-hotzenplotz.seed'
|
import { CreateRaeuberHotzenplotzSeed } from './seeds/users/raeuber-hotzenplotz.seed'
|
||||||
import { CreateBobBaumeisterSeed } from './seeds/users/bob-baumeister.seed'
|
import { CreateBobBaumeisterSeed } from './seeds/users/bob-baumeister.seed'
|
||||||
|
import { DecayStartBlockSeed } from './seeds/decay-start-block.seed'
|
||||||
|
|
||||||
const run = async (command: string) => {
|
const run = async (command: string) => {
|
||||||
// Database actions not supported by our migration library
|
// Database actions not supported by our migration library
|
||||||
@ -59,6 +60,7 @@ const run = async (command: string) => {
|
|||||||
root: process.cwd(),
|
root: process.cwd(),
|
||||||
configName: 'ormconfig.js',
|
configName: 'ormconfig.js',
|
||||||
})
|
})
|
||||||
|
await runSeeder(DecayStartBlockSeed)
|
||||||
await runSeeder(CreatePeterLustigSeed)
|
await runSeeder(CreatePeterLustigSeed)
|
||||||
await runSeeder(CreateBibiBloxbergSeed)
|
await runSeeder(CreateBibiBloxbergSeed)
|
||||||
await runSeeder(CreateRaeuberHotzenplotzSeed)
|
await runSeeder(CreateRaeuberHotzenplotzSeed)
|
||||||
|
|||||||
52
database/src/interface/TransactionContext.ts
Normal file
52
database/src/interface/TransactionContext.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Transaction } from '../../entity/Transaction'
|
||||||
|
import { TransactionSendCoin } from '../../entity/TransactionSendCoin'
|
||||||
|
import { TransactionCreation } from '../../entity/TransactionCreation'
|
||||||
|
import { User } from '../../entity/User'
|
||||||
|
|
||||||
|
export interface TransactionContext {
|
||||||
|
transactionTypeId?: number
|
||||||
|
txHash?: Buffer
|
||||||
|
memo?: string
|
||||||
|
received?: Date
|
||||||
|
blockchainTypeId?: number
|
||||||
|
transactionSendCoin?: TransactionSendCoin
|
||||||
|
transactionCreation?: TransactionCreation
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BalanceContext {
|
||||||
|
modified?: Date
|
||||||
|
recordDate?: Date
|
||||||
|
amount?: number
|
||||||
|
user?: User
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransactionSendCoinContext {
|
||||||
|
senderPublic?: Buffer
|
||||||
|
userId?: number
|
||||||
|
recipiantPublic?: Buffer
|
||||||
|
recipiantUserId?: number
|
||||||
|
amount?: number
|
||||||
|
senderFinalBalance?: number
|
||||||
|
transaction?: Transaction
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransactionCreationContext {
|
||||||
|
userId?: number
|
||||||
|
amount?: number
|
||||||
|
targetDate?: Date
|
||||||
|
transaction?: Transaction
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserTransactionContext {
|
||||||
|
userId?: number
|
||||||
|
transactionId?: number
|
||||||
|
transactionTypeId?: number
|
||||||
|
balance?: number
|
||||||
|
balanceDate?: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransactionSignatureContext {
|
||||||
|
signature?: Buffer
|
||||||
|
pubkey?: Buffer
|
||||||
|
transaction?: Transaction
|
||||||
|
}
|
||||||
@ -27,4 +27,14 @@ export interface UserInterface {
|
|||||||
modified?: Date
|
modified?: Date
|
||||||
// flag for admin
|
// flag for admin
|
||||||
isAdmin?: boolean
|
isAdmin?: boolean
|
||||||
|
// flag for balance (creation of 1000 GDD)
|
||||||
|
addBalance?: boolean
|
||||||
|
// balance
|
||||||
|
balanceModified?: Date
|
||||||
|
recordDate?: Date
|
||||||
|
targetDate?: Date
|
||||||
|
amount?: number
|
||||||
|
creationTxHash?: Buffer
|
||||||
|
signature?: Buffer
|
||||||
|
signaturePubkey?: Buffer
|
||||||
}
|
}
|
||||||
|
|||||||
17
database/src/seeds/decay-start-block.seed.ts
Normal file
17
database/src/seeds/decay-start-block.seed.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Factory, Seeder } from 'typeorm-seeding'
|
||||||
|
import { Transaction } from '../../entity/Transaction'
|
||||||
|
|
||||||
|
export class DecayStartBlockSeed implements Seeder {
|
||||||
|
public async run(factory: Factory): Promise<void> {
|
||||||
|
await factory(Transaction)({
|
||||||
|
transactionTypeId: 9,
|
||||||
|
txHash: Buffer.from(
|
||||||
|
'9c9c4152b8a4cfbac287eee18d2d262e9de756fae726fc0ca36b788564973fff00000000000000000000000000000000',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
memo: '',
|
||||||
|
received: new Date('2021-11-30T09:13:26'),
|
||||||
|
blockchainTypeId: 1,
|
||||||
|
}).create()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,16 +5,28 @@ import {
|
|||||||
ServerUserContext,
|
ServerUserContext,
|
||||||
LoginUserRolesContext,
|
LoginUserRolesContext,
|
||||||
} from '../../interface/UserContext'
|
} from '../../interface/UserContext'
|
||||||
|
import {
|
||||||
|
BalanceContext,
|
||||||
|
TransactionContext,
|
||||||
|
TransactionCreationContext,
|
||||||
|
UserTransactionContext,
|
||||||
|
TransactionSignatureContext,
|
||||||
|
} from '../../interface/TransactionContext'
|
||||||
import { UserInterface } from '../../interface/UserInterface'
|
import { UserInterface } from '../../interface/UserInterface'
|
||||||
import { User } from '../../../entity/User'
|
import { User } from '../../../entity/User'
|
||||||
import { LoginUser } from '../../../entity/LoginUser'
|
import { LoginUser } from '../../../entity/LoginUser'
|
||||||
import { LoginUserBackup } from '../../../entity/LoginUserBackup'
|
import { LoginUserBackup } from '../../../entity/LoginUserBackup'
|
||||||
import { ServerUser } from '../../../entity/ServerUser'
|
import { ServerUser } from '../../../entity/ServerUser'
|
||||||
import { LoginUserRoles } from '../../../entity/LoginUserRoles'
|
import { LoginUserRoles } from '../../../entity/LoginUserRoles'
|
||||||
|
import { Balance } from '../../../entity/Balance'
|
||||||
|
import { Transaction } from '../../../entity/Transaction'
|
||||||
|
import { TransactionSignature } from '../../../entity/TransactionSignature'
|
||||||
|
import { UserTransaction } from '../../../entity/UserTransaction'
|
||||||
|
import { TransactionCreation } from '../../../entity/TransactionCreation'
|
||||||
import { Factory } from 'typeorm-seeding'
|
import { Factory } from 'typeorm-seeding'
|
||||||
|
|
||||||
export const userSeeder = async (factory: Factory, userData: UserInterface): Promise<void> => {
|
export const userSeeder = async (factory: Factory, userData: UserInterface): Promise<void> => {
|
||||||
await factory(User)(createUserContext(userData)).create()
|
const user = await factory(User)(createUserContext(userData)).create()
|
||||||
const loginUser = await factory(LoginUser)(createLoginUserContext(userData)).create()
|
const loginUser = await factory(LoginUser)(createLoginUserContext(userData)).create()
|
||||||
await factory(LoginUserBackup)(createLoginUserBackupContext(userData, loginUser)).create()
|
await factory(LoginUserBackup)(createLoginUserBackupContext(userData, loginUser)).create()
|
||||||
|
|
||||||
@ -25,9 +37,26 @@ export const userSeeder = async (factory: Factory, userData: UserInterface): Pro
|
|||||||
// It works with LoginRoles empty!!
|
// It works with LoginRoles empty!!
|
||||||
await factory(LoginUserRoles)(createLoginUserRolesContext(loginUser)).create()
|
await factory(LoginUserRoles)(createLoginUserRolesContext(loginUser)).create()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userData.addBalance) {
|
||||||
|
// create some GDD for the user
|
||||||
|
await factory(Balance)(createBalanceContext(userData, user)).create()
|
||||||
|
const transaction = await factory(Transaction)(
|
||||||
|
createTransactionContext(userData, 1, 'Herzlich Willkommen bei Gradido!'),
|
||||||
|
).create()
|
||||||
|
await factory(TransactionCreation)(
|
||||||
|
createTransactionCreationContext(userData, user, transaction),
|
||||||
|
).create()
|
||||||
|
await factory(UserTransaction)(
|
||||||
|
createUserTransactionContext(userData, user, transaction),
|
||||||
|
).create()
|
||||||
|
await factory(TransactionSignature)(
|
||||||
|
createTransactionSignatureContext(userData, transaction),
|
||||||
|
).create()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createUserContext = (context: UserInterface): UserContext => {
|
const createUserContext = (context: UserInterface): UserContext => {
|
||||||
return {
|
return {
|
||||||
pubkey: context.pubKey,
|
pubkey: context.pubKey,
|
||||||
email: context.email,
|
email: context.email,
|
||||||
@ -38,7 +67,7 @@ export const createUserContext = (context: UserInterface): UserContext => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createLoginUserContext = (context: UserInterface): LoginUserContext => {
|
const createLoginUserContext = (context: UserInterface): LoginUserContext => {
|
||||||
return {
|
return {
|
||||||
email: context.email,
|
email: context.email,
|
||||||
firstName: context.firstName,
|
firstName: context.firstName,
|
||||||
@ -59,7 +88,7 @@ export const createLoginUserContext = (context: UserInterface): LoginUserContext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createLoginUserBackupContext = (
|
const createLoginUserBackupContext = (
|
||||||
context: UserInterface,
|
context: UserInterface,
|
||||||
loginUser: LoginUser,
|
loginUser: LoginUser,
|
||||||
): LoginUserBackupContext => {
|
): LoginUserBackupContext => {
|
||||||
@ -70,7 +99,7 @@ export const createLoginUserBackupContext = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createServerUserContext = (context: UserInterface): ServerUserContext => {
|
const createServerUserContext = (context: UserInterface): ServerUserContext => {
|
||||||
return {
|
return {
|
||||||
role: context.role,
|
role: context.role,
|
||||||
username: context.username,
|
username: context.username,
|
||||||
@ -83,9 +112,69 @@ export const createServerUserContext = (context: UserInterface): ServerUserConte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createLoginUserRolesContext = (loginUser: LoginUser): LoginUserRolesContext => {
|
const createLoginUserRolesContext = (loginUser: LoginUser): LoginUserRolesContext => {
|
||||||
return {
|
return {
|
||||||
userId: loginUser.id,
|
userId: loginUser.id,
|
||||||
roleId: 1,
|
roleId: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createBalanceContext = (context: UserInterface, user: User): BalanceContext => {
|
||||||
|
return {
|
||||||
|
modified: context.balanceModified,
|
||||||
|
recordDate: context.recordDate,
|
||||||
|
amount: context.amount,
|
||||||
|
user,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTransactionContext = (
|
||||||
|
context: UserInterface,
|
||||||
|
type: number,
|
||||||
|
memo: string,
|
||||||
|
): TransactionContext => {
|
||||||
|
return {
|
||||||
|
transactionTypeId: type,
|
||||||
|
txHash: context.creationTxHash,
|
||||||
|
memo,
|
||||||
|
received: context.recordDate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTransactionCreationContext = (
|
||||||
|
context: UserInterface,
|
||||||
|
user: User,
|
||||||
|
transaction: Transaction,
|
||||||
|
): TransactionCreationContext => {
|
||||||
|
return {
|
||||||
|
userId: user.id,
|
||||||
|
amount: context.amount,
|
||||||
|
targetDate: context.targetDate,
|
||||||
|
transaction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createUserTransactionContext = (
|
||||||
|
context: UserInterface,
|
||||||
|
user: User,
|
||||||
|
transaction: Transaction,
|
||||||
|
): UserTransactionContext => {
|
||||||
|
return {
|
||||||
|
userId: user.id,
|
||||||
|
transactionId: transaction.id,
|
||||||
|
transactionTypeId: transaction.transactionTypeId,
|
||||||
|
balance: context.amount,
|
||||||
|
balanceDate: context.recordDate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTransactionSignatureContext = (
|
||||||
|
context: UserInterface,
|
||||||
|
transaction: Transaction,
|
||||||
|
): TransactionSignatureContext => {
|
||||||
|
return {
|
||||||
|
signature: context.signature,
|
||||||
|
pubkey: context.signaturePubkey,
|
||||||
|
transaction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -22,4 +22,21 @@ export const bibiBloxberg = {
|
|||||||
'knife normal level all hurdle crucial color avoid warrior stadium road bachelor affair topple hawk pottery right afford immune two ceiling budget glance hour ',
|
'knife normal level all hurdle crucial color avoid warrior stadium road bachelor affair topple hawk pottery right afford immune two ceiling budget glance hour ',
|
||||||
mnemonicType: 2,
|
mnemonicType: 2,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
|
addBalance: true,
|
||||||
|
balanceModified: new Date('2021-11-30T10:37:11'),
|
||||||
|
recordDate: new Date('2021-11-30T10:37:11'),
|
||||||
|
targetDate: new Date('2021-08-01 00:00:00'),
|
||||||
|
amount: 10000000,
|
||||||
|
creationTxHash: Buffer.from(
|
||||||
|
'51103dc0fc2ca5d5d75a9557a1e899304e5406cfdb1328d8df6414d527b0118100000000000000000000000000000000',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
signature: Buffer.from(
|
||||||
|
'2a2c71f3e41adc060bbc3086577e2d57d24eeeb0a7727339c3f85aad813808f601d7e1df56a26e0929d2e67fc054fca429ccfa283ed2782185c7f009fe008f0c',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
signaturePubkey: Buffer.from(
|
||||||
|
'7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,4 +22,21 @@ export const bobBaumeister = {
|
|||||||
'detail master source effort unable waste tilt flush domain orchard art truck hint barrel response gate impose peanut secret merry three uncle wink resource ',
|
'detail master source effort unable waste tilt flush domain orchard art truck hint barrel response gate impose peanut secret merry three uncle wink resource ',
|
||||||
mnemonicType: 2,
|
mnemonicType: 2,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
|
addBalance: true,
|
||||||
|
balanceModified: new Date('2021-11-30T10:37:14'),
|
||||||
|
recordDate: new Date('2021-11-30T10:37:14'),
|
||||||
|
targetDate: new Date('2021-08-01 00:00:00'),
|
||||||
|
amount: 10000000,
|
||||||
|
creationTxHash: Buffer.from(
|
||||||
|
'be095dc87acb94987e71168fee8ecbf50ecb43a180b1006e75d573b35725c69c00000000000000000000000000000000',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
signature: Buffer.from(
|
||||||
|
'1fbd6b9a3d359923b2501557f3bc79fa7e428127c8090fb16bc490b4d87870ab142b3817ddd902d22f0b26472a483233784a0e460c0622661752a13978903905',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
signaturePubkey: Buffer.from(
|
||||||
|
'7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,4 +22,21 @@ export const raeuberHotzenplotz = {
|
|||||||
'gospel trip tenant mouse spider skill auto curious man video chief response same little over expire drum display fancy clinic keen throw urge basket ',
|
'gospel trip tenant mouse spider skill auto curious man video chief response same little over expire drum display fancy clinic keen throw urge basket ',
|
||||||
mnemonicType: 2,
|
mnemonicType: 2,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
|
addBalance: true,
|
||||||
|
balanceModified: new Date('2021-11-30T10:37:13'),
|
||||||
|
recordDate: new Date('2021-11-30T10:37:13'),
|
||||||
|
targetDate: new Date('2021-08-01 00:00:00'),
|
||||||
|
amount: 10000000,
|
||||||
|
creationTxHash: Buffer.from(
|
||||||
|
'23ba44fd84deb59b9f32969ad0cb18bfa4588be1bdb99c396888506474c16c1900000000000000000000000000000000',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
signature: Buffer.from(
|
||||||
|
'756d3da061687c575d1dbc5073908f646aa5f498b0927b217c83b48af471450e571dfe8421fb8e1f1ebd1104526b7e7c6fa78684e2da59c8f7f5a8dc3d9e5b0b',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
signaturePubkey: Buffer.from(
|
||||||
|
'7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,22 @@
|
|||||||
module.exports = {
|
module.exports = function (api) {
|
||||||
presets: ['@babel/preset-env'],
|
api.cache(true)
|
||||||
plugins: [
|
|
||||||
|
const presets = ['@babel/preset-env']
|
||||||
|
const plugins = [
|
||||||
[
|
[
|
||||||
'component',
|
'component',
|
||||||
{
|
{
|
||||||
styleLibraryName: 'theme-chalk',
|
styleLibraryName: 'theme-chalk',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
]
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
plugins.push('transform-require-context')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
presets,
|
||||||
|
plugins,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,4 +22,5 @@ module.exports = {
|
|||||||
testMatch: ['**/?(*.)+(spec|test).js?(x)'],
|
testMatch: ['**/?(*.)+(spec|test).js?(x)'],
|
||||||
// snapshotSerializers: ['jest-serializer-vue'],
|
// snapshotSerializers: ['jest-serializer-vue'],
|
||||||
transformIgnorePatterns: ['<rootDir>/node_modules/(?!vee-validate/dist/rules)'],
|
transformIgnorePatterns: ['<rootDir>/node_modules/(?!vee-validate/dist/rules)'],
|
||||||
|
testEnvironment: 'jest-environment-jsdom-sixteen',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,7 @@
|
|||||||
"apollo-boost": "^0.4.9",
|
"apollo-boost": "^0.4.9",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"babel-core": "^7.0.0-bridge.0",
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
"babel-jest": "^26.6.3",
|
"babel-jest": "^27.3.1",
|
||||||
"babel-plugin-require-context-hook": "^1.0.0",
|
|
||||||
"babel-preset-vue": "^2.0.2",
|
"babel-preset-vue": "^2.0.2",
|
||||||
"bootstrap": "4.3.1",
|
"bootstrap": "4.3.1",
|
||||||
"bootstrap-vue": "^2.5.0",
|
"bootstrap-vue": "^2.5.0",
|
||||||
@ -51,6 +50,7 @@
|
|||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"jest-canvas-mock": "^2.3.1",
|
"jest-canvas-mock": "^2.3.1",
|
||||||
|
"jest-environment-jsdom-sixteen": "^2.0.0",
|
||||||
"nouislider": "^12.1.0",
|
"nouislider": "^12.1.0",
|
||||||
"particles-bg-vue": "1.2.3",
|
"particles-bg-vue": "1.2.3",
|
||||||
"perfect-scrollbar": "^1.3.0",
|
"perfect-scrollbar": "^1.3.0",
|
||||||
@ -88,6 +88,7 @@
|
|||||||
"@vue/eslint-config-prettier": "^4.0.1",
|
"@vue/eslint-config-prettier": "^4.0.1",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-plugin-component": "^1.1.0",
|
"babel-plugin-component": "^1.1.0",
|
||||||
|
"babel-plugin-transform-require-context": "^0.1.1",
|
||||||
"dotenv-webpack": "^7.0.3",
|
"dotenv-webpack": "^7.0.3",
|
||||||
"node-sass": "^6.0.1",
|
"node-sass": "^6.0.1",
|
||||||
"sass-loader": "^10",
|
"sass-loader": "^10",
|
||||||
|
|||||||
@ -23,6 +23,7 @@
|
|||||||
variant="outline-light"
|
variant="outline-light"
|
||||||
@click="toggleShowPassword"
|
@click="toggleShowPassword"
|
||||||
class="border-left-0 rounded-right"
|
class="border-left-0 rounded-right"
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<b-icon :icon="showPassword ? 'eye' : 'eye-slash'" />
|
<b-icon :icon="showPassword ? 'eye' : 'eye-slash'" />
|
||||||
</b-button>
|
</b-button>
|
||||||
|
|||||||
@ -167,7 +167,10 @@ describe('SideBar', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mocks.$store.state.isAdmin = true
|
mocks.$store.state.isAdmin = true
|
||||||
mocks.$store.state.token = 'valid-token'
|
mocks.$store.state.token = 'valid-token'
|
||||||
window.location.assign = assignLocationSpy
|
delete window.location
|
||||||
|
window.location = {
|
||||||
|
assign: assignLocationSpy,
|
||||||
|
}
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -12,9 +12,9 @@ export const unsubscribeNewsletter = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const resetPassword = gql`
|
export const setPassword = gql`
|
||||||
mutation($sessionId: Float!, $email: String!, $password: String!) {
|
mutation($code: String!, $password: String!) {
|
||||||
resetPassword(sessionId: $sessionId, email: $email, password: $password)
|
setPassword(code: $code, password: $password)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -42,12 +42,11 @@ export const updateUserInfos = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const registerUser = gql`
|
export const createUser = gql`
|
||||||
mutation(
|
mutation(
|
||||||
$firstName: String!
|
$firstName: String!
|
||||||
$lastName: String!
|
$lastName: String!
|
||||||
$email: String!
|
$email: String!
|
||||||
$password: String!
|
|
||||||
$language: String!
|
$language: String!
|
||||||
$publisherId: Int
|
$publisherId: Int
|
||||||
) {
|
) {
|
||||||
@ -55,7 +54,6 @@ export const registerUser = gql`
|
|||||||
email: $email
|
email: $email
|
||||||
firstName: $firstName
|
firstName: $firstName
|
||||||
lastName: $lastName
|
lastName: $lastName
|
||||||
password: $password
|
|
||||||
language: $language
|
language: $language
|
||||||
publisherId: $publisherId
|
publisherId: $publisherId
|
||||||
)
|
)
|
||||||
|
|||||||
@ -46,15 +46,6 @@ export const logout = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const loginViaEmailVerificationCode = gql`
|
|
||||||
query($optin: String!) {
|
|
||||||
loginViaEmailVerificationCode(optin: $optin) {
|
|
||||||
sessionId
|
|
||||||
email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const transactionsQuery = gql`
|
export const transactionsQuery = gql`
|
||||||
query($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) {
|
query($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) {
|
||||||
transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
|
transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
|
||||||
@ -88,9 +79,7 @@ export const transactionsQuery = gql`
|
|||||||
|
|
||||||
export const sendResetPasswordEmail = gql`
|
export const sendResetPasswordEmail = gql`
|
||||||
query($email: String!) {
|
query($email: String!) {
|
||||||
sendResetPasswordEmail(email: $email) {
|
sendResetPasswordEmail(email: $email)
|
||||||
state
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -118,15 +107,6 @@ export const listGDTEntriesQuery = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const checkEmailQuery = gql`
|
|
||||||
query($optin: String!) {
|
|
||||||
checkEmail(optin: $optin) {
|
|
||||||
email
|
|
||||||
sessionId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const communityInfo = gql`
|
export const communityInfo = gql`
|
||||||
query {
|
query {
|
||||||
getCommunityInfo {
|
getCommunityInfo {
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"no-account": "Leider konnten wir keinen Account finden mit diesen Daten!",
|
"no-account": "Leider konnten wir keinen Account finden mit diesen Daten!",
|
||||||
"no-email-verify": "Die Email wurde noch nicht bestätigt, bitte überprüfe deine Emails und klicke auf den Aktivierungslink!",
|
"no-email-verify": "Die Email wurde noch nicht bestätigt, bitte überprüfe deine Emails und klicke auf den Aktivierungslink!",
|
||||||
"session-expired": "Sitzung abgelaufen!"
|
"session-expired": "Die Sitzung wurde aus Sicherheitsgründen beendet."
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"amount": "Betrag",
|
"amount": "Betrag",
|
||||||
@ -114,6 +114,11 @@
|
|||||||
"message": "hallo gradido !!",
|
"message": "hallo gradido !!",
|
||||||
"overview": "Übersicht",
|
"overview": "Übersicht",
|
||||||
"privacy_policy": "Datenschutzerklärung",
|
"privacy_policy": "Datenschutzerklärung",
|
||||||
|
"publisher": {
|
||||||
|
"infoNoRegister": "Dies ist für die Registrieung nicht nötig!",
|
||||||
|
"infoText": "Trage hier die ID des Herausgebers ein. Wenn du keine ID hast dann bitte leer lassen.",
|
||||||
|
"publisherId": "PublisherID"
|
||||||
|
},
|
||||||
"send": "Senden",
|
"send": "Senden",
|
||||||
"settings": {
|
"settings": {
|
||||||
"coinanimation": {
|
"coinanimation": {
|
||||||
@ -140,12 +145,17 @@
|
|||||||
"password": {
|
"password": {
|
||||||
"change-password": "Passwort ändern",
|
"change-password": "Passwort ändern",
|
||||||
"forgot_pwd": "Passwort vergessen?",
|
"forgot_pwd": "Passwort vergessen?",
|
||||||
|
"not-authenticated": "Leider konnten wir dich nicht authentifizieren. Bitte wende dich an den Support.",
|
||||||
|
"resend_subtitle": "Dein Aktivierungslink ist abgelaufen, Du kannst hier ein neuen anfordern.",
|
||||||
"reset": "Passwort zurücksetzen",
|
"reset": "Passwort zurücksetzen",
|
||||||
"reset-password": {
|
"reset-password": {
|
||||||
"not-authenticated": "Leider konnten wir dich nicht authentifizieren. Bitte wende dich an den Support.",
|
|
||||||
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünftig in der Gradido-App anmelden kannst."
|
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünftig in der Gradido-App anmelden kannst."
|
||||||
},
|
},
|
||||||
"send_now": "Jetzt senden",
|
"send_now": "Jetzt senden",
|
||||||
|
"set": "Passwort festlegen",
|
||||||
|
"set-password": {
|
||||||
|
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünftig in der Gradido-App anmelden kannst."
|
||||||
|
},
|
||||||
"subtitle": "Wenn du dein Passwort vergessen hast, kannst du es hier zurücksetzen."
|
"subtitle": "Wenn du dein Passwort vergessen hast, kannst du es hier zurücksetzen."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
"error": "Error",
|
"error": "Error",
|
||||||
"no-account": "Unfortunately we could not find an account to the given data!",
|
"no-account": "Unfortunately we could not find an account to the given data!",
|
||||||
"no-email-verify": "Your email is not activated yet, please check your emails and click the activation link!",
|
"no-email-verify": "Your email is not activated yet, please check your emails and click the activation link!",
|
||||||
"session-expired": "The session expired"
|
"session-expired": "The session was closed for security reasons."
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
@ -114,6 +114,11 @@
|
|||||||
"message": "hello gradido !!",
|
"message": "hello gradido !!",
|
||||||
"overview": "Overview",
|
"overview": "Overview",
|
||||||
"privacy_policy": "Privacy policy",
|
"privacy_policy": "Privacy policy",
|
||||||
|
"publisher": {
|
||||||
|
"infoNoRegister": "This is not necessary for registration!",
|
||||||
|
"infoText": "Enter the ID of the publisher here. If you do not have an ID, please leave it blank.",
|
||||||
|
"publisherId": "PublisherID"
|
||||||
|
},
|
||||||
"send": "Send",
|
"send": "Send",
|
||||||
"settings": {
|
"settings": {
|
||||||
"coinanimation": {
|
"coinanimation": {
|
||||||
@ -140,12 +145,17 @@
|
|||||||
"password": {
|
"password": {
|
||||||
"change-password": "Change password",
|
"change-password": "Change password",
|
||||||
"forgot_pwd": "Forgot password?",
|
"forgot_pwd": "Forgot password?",
|
||||||
|
"not-authenticated": "Unfortunately we could not authenticate you. Please contact the support.",
|
||||||
|
"resend_subtitle": "Your activation link is expired, here you can order a new one.",
|
||||||
"reset": "Reset password",
|
"reset": "Reset password",
|
||||||
"reset-password": {
|
"reset-password": {
|
||||||
"not-authenticated": "Unfortunately we could not authenticate you. Please contact the support.",
|
|
||||||
"text": "Now you can save a new password to login to the Gradido-App in the future."
|
"text": "Now you can save a new password to login to the Gradido-App in the future."
|
||||||
},
|
},
|
||||||
"send_now": "Send now",
|
"send_now": "Send now",
|
||||||
|
"set": "Set password",
|
||||||
|
"set-password": {
|
||||||
|
"text": "Now you can save a new password to login to the Gradido-App in the future."
|
||||||
|
},
|
||||||
"subtitle": "If you have forgotten your password, you can reset it here."
|
"subtitle": "If you have forgotten your password, you can reset it here."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,9 +3,6 @@ import DashboardPlugin from './plugins/dashboard-plugin'
|
|||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import i18n from './i18n.js'
|
import i18n from './i18n.js'
|
||||||
import { loadAllRules } from './validation-rules'
|
import { loadAllRules } from './validation-rules'
|
||||||
import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
|
|
||||||
import VueApollo from 'vue-apollo'
|
|
||||||
import CONFIG from './config'
|
|
||||||
|
|
||||||
import addNavigationGuards from './routes/guards'
|
import addNavigationGuards from './routes/guards'
|
||||||
|
|
||||||
@ -13,42 +10,22 @@ import { store } from './store/store'
|
|||||||
|
|
||||||
import router from './routes/router'
|
import router from './routes/router'
|
||||||
|
|
||||||
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
|
import { apolloProvider } from './plugins/apolloProvider'
|
||||||
|
|
||||||
const authLink = new ApolloLink((operation, forward) => {
|
|
||||||
const token = store.state.token
|
|
||||||
operation.setContext({
|
|
||||||
headers: {
|
|
||||||
Authorization: token && token.length > 0 ? `Bearer ${token}` : '',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return forward(operation).map((response) => {
|
|
||||||
if (response.errors && response.errors[0].message === '403.13 - Client certificate revoked') {
|
|
||||||
response.errors[0].message = i18n.t('error.session-expired')
|
|
||||||
store.dispatch('logout', null)
|
|
||||||
if (router.currentRoute.path !== '/login') router.push('/login')
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
const newToken = operation.getContext().response.headers.get('token')
|
|
||||||
if (newToken) store.commit('token', newToken)
|
|
||||||
return response
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const apolloClient = new ApolloClient({
|
|
||||||
link: authLink.concat(httpLink),
|
|
||||||
cache: new InMemoryCache(),
|
|
||||||
uri: CONFIG.GRAPHQL_URI,
|
|
||||||
})
|
|
||||||
|
|
||||||
const apolloProvider = new VueApollo({
|
|
||||||
defaultClient: apolloClient,
|
|
||||||
})
|
|
||||||
|
|
||||||
// plugin setup
|
// plugin setup
|
||||||
Vue.use(DashboardPlugin)
|
Vue.use(DashboardPlugin)
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
Vue.toasted.register(
|
||||||
|
'error',
|
||||||
|
(payload) => {
|
||||||
|
return payload.replace(/^GraphQL error: /, '')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'error',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
loadAllRules(i18n)
|
loadAllRules(i18n)
|
||||||
|
|
||||||
addNavigationGuards(router, store, apolloProvider.defaultClient)
|
addNavigationGuards(router, store, apolloProvider.defaultClient)
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export const getCommunityInfoMixin = {
|
|||||||
return result.data.getCommunityInfo
|
return result.data.getCommunityInfo
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toasted.error(error.message)
|
this.$toasted.global.error(error.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
37
frontend/src/plugins/apolloProvider.js
Normal file
37
frontend/src/plugins/apolloProvider.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
|
||||||
|
import VueApollo from 'vue-apollo'
|
||||||
|
import CONFIG from '../config'
|
||||||
|
import { store } from '../store/store'
|
||||||
|
import router from '../routes/router'
|
||||||
|
import i18n from '../i18n'
|
||||||
|
|
||||||
|
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
|
||||||
|
|
||||||
|
const authLink = new ApolloLink((operation, forward) => {
|
||||||
|
const token = store.state.token
|
||||||
|
operation.setContext({
|
||||||
|
headers: {
|
||||||
|
Authorization: token && token.length > 0 ? `Bearer ${token}` : '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return forward(operation).map((response) => {
|
||||||
|
if (response.errors && response.errors[0].message === '403.13 - Client certificate revoked') {
|
||||||
|
response.errors[0].message = i18n.t('error.session-expired')
|
||||||
|
store.dispatch('logout', null)
|
||||||
|
if (router.currentRoute.path !== '/login') router.push('/login')
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
const newToken = operation.getContext().response.headers.get('token')
|
||||||
|
if (newToken) store.commit('token', newToken)
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const apolloClient = new ApolloClient({
|
||||||
|
link: authLink.concat(httpLink),
|
||||||
|
cache: new InMemoryCache(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const apolloProvider = new VueApollo({
|
||||||
|
defaultClient: apolloClient,
|
||||||
|
})
|
||||||
178
frontend/src/plugins/apolloProvider.test.js
Normal file
178
frontend/src/plugins/apolloProvider.test.js
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import { ApolloClient, ApolloLink, HttpLink } from 'apollo-boost'
|
||||||
|
import './apolloProvider'
|
||||||
|
import CONFIG from '../config'
|
||||||
|
|
||||||
|
import VueApollo from 'vue-apollo'
|
||||||
|
import { store } from '../store/store.js'
|
||||||
|
import router from '../routes/router'
|
||||||
|
import i18n from '../i18n'
|
||||||
|
|
||||||
|
jest.mock('vue-apollo')
|
||||||
|
jest.mock('../store/store')
|
||||||
|
jest.mock('../routes/router')
|
||||||
|
jest.mock('../i18n')
|
||||||
|
|
||||||
|
jest.mock('apollo-boost', () => {
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
ApolloClient: jest.fn(),
|
||||||
|
ApolloLink: jest.fn(() => {
|
||||||
|
return { concat: jest.fn() }
|
||||||
|
}),
|
||||||
|
InMemoryCache: jest.fn(),
|
||||||
|
HttpLink: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apolloProvider', () => {
|
||||||
|
it('calls the HttpLink', () => {
|
||||||
|
expect(HttpLink).toBeCalledWith({ uri: CONFIG.GRAPHQL_URI })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the ApolloLink', () => {
|
||||||
|
expect(ApolloLink).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the ApolloClient', () => {
|
||||||
|
expect(ApolloClient).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the VueApollo', () => {
|
||||||
|
expect(VueApollo).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('ApolloLink', () => {
|
||||||
|
// mock store
|
||||||
|
const storeDispatchMock = jest.fn()
|
||||||
|
const storeCommitMock = jest.fn()
|
||||||
|
store.state = {
|
||||||
|
token: 'some-token',
|
||||||
|
}
|
||||||
|
store.dispatch = storeDispatchMock
|
||||||
|
store.commit = storeCommitMock
|
||||||
|
|
||||||
|
// mock i18n.t
|
||||||
|
i18n.t = jest.fn((t) => t)
|
||||||
|
|
||||||
|
// mock apllo response
|
||||||
|
const responseMock = {
|
||||||
|
errors: [{ message: '403.13 - Client certificate revoked' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
// mock router
|
||||||
|
const routerPushMock = jest.fn()
|
||||||
|
router.push = routerPushMock
|
||||||
|
router.currentRoute = {
|
||||||
|
path: '/overview',
|
||||||
|
}
|
||||||
|
|
||||||
|
// mock context
|
||||||
|
const setContextMock = jest.fn()
|
||||||
|
const getContextMock = jest.fn(() => {
|
||||||
|
return {
|
||||||
|
response: {
|
||||||
|
headers: {
|
||||||
|
get: jest.fn(() => 'another-token'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// mock apollo link function params
|
||||||
|
const operationMock = {
|
||||||
|
setContext: setContextMock,
|
||||||
|
getContext: getContextMock,
|
||||||
|
}
|
||||||
|
|
||||||
|
const forwardMock = jest.fn(() => {
|
||||||
|
return [responseMock]
|
||||||
|
})
|
||||||
|
|
||||||
|
// get apollo link callback
|
||||||
|
const middleware = ApolloLink.mock.calls[0][0]
|
||||||
|
|
||||||
|
describe('with token in store', () => {
|
||||||
|
it('sets authorization header with token', () => {
|
||||||
|
// run the apollo link callback with mocked params
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
expect(setContextMock).toBeCalledWith({
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer some-token',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('without token in store', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.state.token = null
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets authorization header empty', () => {
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
expect(setContextMock).toBeCalledWith({
|
||||||
|
headers: {
|
||||||
|
Authorization: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apollo response is 403.13', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// run the apollo link callback with mocked params
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dispatches logout', () => {
|
||||||
|
expect(storeDispatchMock).toBeCalledWith('logout', null)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('current route is not login', () => {
|
||||||
|
it('redirects to logout', () => {
|
||||||
|
expect(routerPushMock).toBeCalledWith('/login')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('current route is login', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
router.currentRoute.path = '/login'
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not redirect to login', () => {
|
||||||
|
expect(routerPushMock).not.toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apollo response is with new token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
delete responseMock.errors
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('commits new token to store', () => {
|
||||||
|
expect(storeCommitMock).toBeCalledWith('token', 'another-token')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apollo response is without new token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
getContextMock.mockReturnValue({
|
||||||
|
response: {
|
||||||
|
headers: {
|
||||||
|
get: jest.fn(() => null),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not commit token to store', () => {
|
||||||
|
expect(storeCommitMock).not.toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -4,8 +4,11 @@ import Vue from 'vue'
|
|||||||
import GlobalComponents from './globalComponents'
|
import GlobalComponents from './globalComponents'
|
||||||
import GlobalDirectives from './globalDirectives'
|
import GlobalDirectives from './globalDirectives'
|
||||||
|
|
||||||
|
import Toasted from 'vue-toasted'
|
||||||
|
|
||||||
jest.mock('./globalComponents')
|
jest.mock('./globalComponents')
|
||||||
jest.mock('./globalDirectives')
|
jest.mock('./globalDirectives')
|
||||||
|
jest.mock('vue-toasted')
|
||||||
|
|
||||||
jest.mock('vue')
|
jest.mock('vue')
|
||||||
|
|
||||||
@ -22,4 +25,21 @@ describe('dashboard plugin', () => {
|
|||||||
it('installs the global directives', () => {
|
it('installs the global directives', () => {
|
||||||
expect(vueUseMock).toBeCalledWith(GlobalDirectives)
|
expect(vueUseMock).toBeCalledWith(GlobalDirectives)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('vue toasted', () => {
|
||||||
|
const toastedAction = vueUseMock.mock.calls[11][1].action.onClick
|
||||||
|
const goAwayMock = jest.fn()
|
||||||
|
const toastObject = {
|
||||||
|
goAway: goAwayMock,
|
||||||
|
}
|
||||||
|
|
||||||
|
it('installs vue toasted', () => {
|
||||||
|
expect(vueUseMock).toBeCalledWith(Toasted, expect.anything())
|
||||||
|
})
|
||||||
|
|
||||||
|
it('onClick calls goAway(0)', () => {
|
||||||
|
toastedAction({}, toastObject)
|
||||||
|
expect(goAwayMock).toBeCalledWith(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -15,12 +15,19 @@ const addNavigationGuards = (router, store, apollo) => {
|
|||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
if (to.path === '/authenticate' && to.query.token) {
|
if (to.path === '/authenticate' && to.query.token) {
|
||||||
store.commit('token', to.query.token)
|
store.commit('token', to.query.token)
|
||||||
const result = await apollo.query({
|
await apollo
|
||||||
query: verifyLogin,
|
.query({
|
||||||
fetchPolicy: 'network-only',
|
query: verifyLogin,
|
||||||
})
|
fetchPolicy: 'network-only',
|
||||||
store.dispatch('login', result.data.verifyLogin)
|
})
|
||||||
next({ path: '/overview' })
|
.then((result) => {
|
||||||
|
store.dispatch('login', result.data.verifyLogin)
|
||||||
|
next({ path: '/overview' })
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
store.dispatch('logout')
|
||||||
|
next()
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,15 +2,28 @@ import addNavigationGuards from './guards'
|
|||||||
import router from './router'
|
import router from './router'
|
||||||
|
|
||||||
const storeCommitMock = jest.fn()
|
const storeCommitMock = jest.fn()
|
||||||
|
const storeDispatchMock = jest.fn()
|
||||||
|
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
verifyLogin: {
|
||||||
|
firstName: 'Peter',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const store = {
|
const store = {
|
||||||
commit: storeCommitMock,
|
commit: storeCommitMock,
|
||||||
state: {
|
state: {
|
||||||
token: null,
|
token: null,
|
||||||
},
|
},
|
||||||
|
dispatch: storeDispatchMock,
|
||||||
}
|
}
|
||||||
|
|
||||||
addNavigationGuards(router, store)
|
const apollo = {
|
||||||
|
query: apolloQueryMock,
|
||||||
|
}
|
||||||
|
|
||||||
|
addNavigationGuards(router, store, apollo)
|
||||||
|
|
||||||
describe('navigation guards', () => {
|
describe('navigation guards', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -29,6 +42,46 @@ describe('navigation guards', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('authenticate', () => {
|
||||||
|
const navGuard = router.beforeHooks[1]
|
||||||
|
const next = jest.fn()
|
||||||
|
|
||||||
|
describe('with valid token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('commts the token to the store', () => {
|
||||||
|
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls verifyLogin', () => {
|
||||||
|
expect(apolloQueryMock).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('commits login to the store', () => {
|
||||||
|
expect(storeDispatchMock).toBeCalledWith('login', { firstName: 'Peter' })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with valid token and server error', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
apolloQueryMock.mockRejectedValue({
|
||||||
|
message: 'Ouch!',
|
||||||
|
})
|
||||||
|
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dispatches logout to store', () => {
|
||||||
|
expect(storeDispatchMock).toBeCalledWith('logout')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls next', () => {
|
||||||
|
expect(next).toBeCalledWith()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('authorization', () => {
|
describe('authorization', () => {
|
||||||
const navGuard = router.beforeHooks[2]
|
const navGuard = router.beforeHooks[2]
|
||||||
const next = jest.fn()
|
const next = jest.fn()
|
||||||
|
|||||||
@ -49,8 +49,8 @@ describe('router', () => {
|
|||||||
expect(routes.find((r) => r.path === '/').redirect()).toEqual({ path: '/login' })
|
expect(routes.find((r) => r.path === '/').redirect()).toEqual({ path: '/login' })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has fifteen routes defined', () => {
|
it('has sixteen routes defined', () => {
|
||||||
expect(routes).toHaveLength(15)
|
expect(routes).toHaveLength(16)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('overview', () => {
|
describe('overview', () => {
|
||||||
@ -143,6 +143,13 @@ describe('router', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('password with param comingFrom', () => {
|
||||||
|
it('loads the "Password" component', async () => {
|
||||||
|
const component = await routes.find((r) => r.path === '/password/:comingFrom').component()
|
||||||
|
expect(component.default.name).toBe('password')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('register-community', () => {
|
describe('register-community', () => {
|
||||||
it('loads the "registerCommunity" component', async () => {
|
it('loads the "registerCommunity" component', async () => {
|
||||||
const component = await routes.find((r) => r.path === '/register-community').component()
|
const component = await routes.find((r) => r.path === '/register-community').component()
|
||||||
@ -167,7 +174,7 @@ describe('router', () => {
|
|||||||
describe('checkEmail', () => {
|
describe('checkEmail', () => {
|
||||||
it('loads the "CheckEmail" component', async () => {
|
it('loads the "CheckEmail" component', async () => {
|
||||||
const component = await routes.find((r) => r.path === '/checkEmail/:optin').component()
|
const component = await routes.find((r) => r.path === '/checkEmail/:optin').component()
|
||||||
expect(component.default.name).toBe('CheckEmail')
|
expect(component.default.name).toBe('ResetPassword')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user