mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge pull request #1202 from gradido/1197-admin-interface-created-transactions-list
1197 admin interface created transactions list
This commit is contained in:
commit
0b9225da09
75
admin/src/components/ConfirmRegisterMailFormular.spec.js
Normal file
75
admin/src/components/ConfirmRegisterMailFormular.spec.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import ConfirmRegisterMailFormular from './ConfirmRegisterMailFormular.vue'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const apolloMutateMock = jest.fn().mockResolvedValue()
|
||||||
|
const toastSuccessMock = jest.fn()
|
||||||
|
const toastErrorMock = jest.fn()
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$apollo: {
|
||||||
|
mutate: apolloMutateMock,
|
||||||
|
},
|
||||||
|
$toasted: {
|
||||||
|
success: toastSuccessMock,
|
||||||
|
error: toastErrorMock,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsData = {
|
||||||
|
email: 'bob@baumeister.de',
|
||||||
|
dateLastSend: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ConfirmRegisterMailFormular', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(ConfirmRegisterMailFormular, { localVue, mocks, propsData })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a DIV element with the class.component-confirm-register-mail', () => {
|
||||||
|
expect(wrapper.find('.component-confirm-register-mail').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('send register mail with success', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper.find('button.test-button').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the API with email', () => {
|
||||||
|
expect(apolloMutateMock).toBeCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
variables: { email: 'bob@baumeister.de' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toasts a success message', () => {
|
||||||
|
expect(toastSuccessMock).toBeCalledWith(
|
||||||
|
'Erfolgreich senden der Confirmation Link an die E-Mail des Users! bob@baumeister.de',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('send register mail with error', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
apolloMutateMock.mockRejectedValue({ message: 'OUCH!' })
|
||||||
|
wrapper = Wrapper()
|
||||||
|
wrapper.find('button.test-button').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toasts an error message', () => {
|
||||||
|
expect(toastErrorMock).toBeCalledWith(
|
||||||
|
'Fehler beim senden des confirmation link an den Benutzer: OUCH!',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
62
admin/src/components/ConfirmRegisterMailFormular.vue
Normal file
62
admin/src/components/ConfirmRegisterMailFormular.vue
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<div class="component-confirm-register-mail">
|
||||||
|
<div class="shadow p-3 mb-5 bg-white rounded">
|
||||||
|
<div class="h5">
|
||||||
|
Die letzte Email wurde am
|
||||||
|
<b>{{ dateLastSend }} Uhr</b>
|
||||||
|
an das Mitglied ({{ email }}) gesendet.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Using components -->
|
||||||
|
<b-input-group prepend="Email bestätigen, wiederholt senden an:" class="mt-3">
|
||||||
|
<b-form-input readonly :value="email"></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button variant="outline-success" class="test-button" @click="sendRegisterMail">
|
||||||
|
Registrierungs-Email bestätigen, jetzt senden
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
</b-input-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { sendActivationEmail } from '../graphql/sendActivationEmail'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ConfirmRegisterMail',
|
||||||
|
props: {
|
||||||
|
email: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
dateLastSend: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sendRegisterMail() {
|
||||||
|
this.$apollo
|
||||||
|
.mutate({
|
||||||
|
mutation: sendActivationEmail,
|
||||||
|
variables: {
|
||||||
|
email: this.email,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.$toasted.success(
|
||||||
|
'Erfolgreich senden der Confirmation Link an die E-Mail des Users! ' + this.email,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toasted.error(
|
||||||
|
'Fehler beim senden des confirmation link an den Benutzer: ' + error.message,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.input-group-text {
|
||||||
|
background-color: rgb(255, 252, 205);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -176,8 +176,8 @@ describe('CreationFormular', () => {
|
|||||||
await wrapper.find('.test-submit').trigger('click')
|
await wrapper.find('.test-submit').trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sends ... to apollo', () => {
|
it('toasts an error message', () => {
|
||||||
expect(toastedErrorMock).toBeCalled()
|
expect(toastedErrorMock).toBeCalledWith('Ouch!')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="component-creation-formular">
|
<div class="component-creation-formular">
|
||||||
|
CREATION FORMULAR
|
||||||
<div class="shadow p-3 mb-5 bg-white rounded">
|
<div 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">
|
||||||
@ -204,8 +205,6 @@ export default {
|
|||||||
// 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)
|
||||||
// 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,
|
||||||
@ -216,8 +215,6 @@ export default {
|
|||||||
moderator: this.$store.state.moderator.id,
|
moderator: this.$store.state.moderator.id,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
// 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)
|
||||||
|
|||||||
115
admin/src/components/CreationTransactionListFormular.spec.js
Normal file
115
admin/src/components/CreationTransactionListFormular.spec.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import CreationTransactionListFormular from './CreationTransactionListFormular.vue'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
transactionList: {
|
||||||
|
transactions: [
|
||||||
|
{
|
||||||
|
type: 'created',
|
||||||
|
balance: 100,
|
||||||
|
decayStart: 0,
|
||||||
|
decayEnd: 0,
|
||||||
|
decayDuration: 0,
|
||||||
|
memo: 'Testing',
|
||||||
|
transactionId: 1,
|
||||||
|
name: 'Bibi',
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
date: new Date(),
|
||||||
|
decay: {
|
||||||
|
balance: 0.01,
|
||||||
|
decayStart: 0,
|
||||||
|
decayEnd: 0,
|
||||||
|
decayDuration: 0,
|
||||||
|
decayStartBlock: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'created',
|
||||||
|
balance: 200,
|
||||||
|
decayStart: 0,
|
||||||
|
decayEnd: 0,
|
||||||
|
decayDuration: 0,
|
||||||
|
memo: 'Testing 2',
|
||||||
|
transactionId: 2,
|
||||||
|
name: 'Bibi',
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
date: new Date(),
|
||||||
|
decay: {
|
||||||
|
balance: 0.01,
|
||||||
|
decayStart: 0,
|
||||||
|
decayEnd: 0,
|
||||||
|
decayDuration: 0,
|
||||||
|
decayStartBlock: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const toastedErrorMock = jest.fn()
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$apollo: {
|
||||||
|
query: apolloQueryMock,
|
||||||
|
},
|
||||||
|
$toasted: {
|
||||||
|
global: {
|
||||||
|
error: toastedErrorMock,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsData = {
|
||||||
|
userId: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('CreationTransactionListFormular', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(CreationTransactionListFormular, { localVue, mocks, propsData })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends query to Apollo when created', () => {
|
||||||
|
expect(apolloQueryMock).toBeCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
variables: {
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 25,
|
||||||
|
order: 'DESC',
|
||||||
|
onlyCreations: true,
|
||||||
|
userId: 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has two values for the transaction', () => {
|
||||||
|
expect(wrapper.find('tbody').findAll('tr').length).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('query transaction with error', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
apolloQueryMock.mockRejectedValue({ message: 'OUCH!' })
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the API', () => {
|
||||||
|
expect(apolloQueryMock).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toast error', () => {
|
||||||
|
expect(toastedErrorMock).toBeCalledWith('OUCH!')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
44
admin/src/components/CreationTransactionListFormular.vue
Normal file
44
admin/src/components/CreationTransactionListFormular.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div class="component-creation-transaction-list">
|
||||||
|
Alle Geschöpften Transaktionen für den User
|
||||||
|
<b-table striped hover :items="items"></b-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { transactionList } from '../graphql/transactionList'
|
||||||
|
export default {
|
||||||
|
name: 'CreationTransactionList',
|
||||||
|
props: {
|
||||||
|
userId: { type: Number, required: true },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
items: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getTransactions() {
|
||||||
|
this.$apollo
|
||||||
|
.query({
|
||||||
|
query: transactionList,
|
||||||
|
variables: {
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 25,
|
||||||
|
order: 'DESC',
|
||||||
|
onlyCreations: true,
|
||||||
|
userId: parseInt(this.userId),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
this.items = result.data.transactionList.transactions
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toasted.global.error(error.message)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getTransactions()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -46,9 +46,12 @@ const mocks = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const propsData = {
|
const propsData = {
|
||||||
type: '',
|
creation: [200, 400, 600],
|
||||||
creation: [],
|
creationUserData: {
|
||||||
itemsMassCreation: {},
|
memo: 'Test schöpfung 1',
|
||||||
|
amount: 100,
|
||||||
|
date: '2021-12-01',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('EditCreationFormular', () => {
|
describe('EditCreationFormular', () => {
|
||||||
@ -67,7 +70,7 @@ describe('EditCreationFormular', () => {
|
|||||||
expect(wrapper.find('.component-edit-creation-formular').exists()).toBeTruthy()
|
expect(wrapper.find('.component-edit-creation-formular').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('radio buttons to selcet month', () => {
|
describe('radio buttons to select 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)
|
||||||
})
|
})
|
||||||
@ -75,7 +78,7 @@ describe('EditCreationFormular', () => {
|
|||||||
describe('with single creation', () => {
|
describe('with single creation', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
await wrapper.setProps({ creation: [200, 400, 600] })
|
||||||
await wrapper.setData({ rangeMin: 180 })
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
await wrapper.setData({ text: 'Test create coins' })
|
await wrapper.setData({ text: 'Test create coins' })
|
||||||
await wrapper.setData({ value: 90 })
|
await wrapper.setData({ value: 90 })
|
||||||
@ -113,6 +116,26 @@ describe('EditCreationFormular', () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('sendForm with error', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
apolloMutateMock.mockRejectedValue({
|
||||||
|
message: 'Ouch!',
|
||||||
|
})
|
||||||
|
wrapper = Wrapper()
|
||||||
|
await wrapper.setProps({ type: 'singleCreation', creation: [200, 400, 600] })
|
||||||
|
await wrapper.setData({ text: 'Test create coins' })
|
||||||
|
await wrapper.setData({ value: 90 })
|
||||||
|
await wrapper.findAll('input[type="radio"]').at(0).setChecked()
|
||||||
|
await wrapper.setData({ rangeMin: 100 })
|
||||||
|
await wrapper.find('.test-submit').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toast error message', () => {
|
||||||
|
expect(toastedErrorMock).toBeCalledWith('Ouch!')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -148,23 +171,44 @@ describe('EditCreationFormular', () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('sendForm with error', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
apolloMutateMock.mockRejectedValue({
|
||||||
|
message: 'Ouch!',
|
||||||
|
})
|
||||||
|
wrapper = Wrapper()
|
||||||
|
await wrapper.setProps({ creation: [200, 400, 600] })
|
||||||
|
await wrapper.setData({ text: 'Test create coins' })
|
||||||
|
await wrapper.setData({ value: 100 })
|
||||||
|
await wrapper.findAll('input[type="radio"]').at(1).setChecked()
|
||||||
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
|
await wrapper.find('.test-submit').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toast error message', () => {
|
||||||
|
expect(toastedErrorMock).toBeCalledWith('Ouch!')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('third radio button', () => {
|
describe('third radio button', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
await wrapper.findAll('input[type="radio"]').at(2).setChecked()
|
await wrapper.findAll('input[type="radio"]').at(2).setChecked()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sets rangeMin to 0', () => {
|
it('sets rangeMin to 180', () => {
|
||||||
expect(wrapper.vm.rangeMin).toBe(0)
|
expect(wrapper.vm.rangeMin).toBe(180)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sets rangeMax to 400', () => {
|
it('sets rangeMax to 700', () => {
|
||||||
expect(wrapper.vm.rangeMax).toBe(600)
|
expect(wrapper.vm.rangeMax).toBe(700)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('sendForm', () => {
|
describe('sendForm with success', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await wrapper.find('.test-submit').trigger('click')
|
await wrapper.find('.test-submit').trigger('click')
|
||||||
})
|
})
|
||||||
@ -184,6 +228,26 @@ describe('EditCreationFormular', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('sendForm with error', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
apolloMutateMock.mockRejectedValue({
|
||||||
|
message: 'Ouch!',
|
||||||
|
})
|
||||||
|
wrapper = Wrapper()
|
||||||
|
await wrapper.setProps({ creation: [200, 400, 600] })
|
||||||
|
await wrapper.setData({ text: 'Test create coins' })
|
||||||
|
await wrapper.setData({ value: 90 })
|
||||||
|
await wrapper.findAll('input[type="radio"]').at(2).setChecked()
|
||||||
|
await wrapper.setData({ rangeMin: 180 })
|
||||||
|
await wrapper.find('.test-submit').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toast error message', () => {
|
||||||
|
expect(toastedErrorMock).toBeCalledWith('Ouch!')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -113,7 +113,7 @@
|
|||||||
@click="submitCreation"
|
@click="submitCreation"
|
||||||
:disabled="radioSelected === '' || value <= 0 || text.length < 10"
|
:disabled="radioSelected === '' || value <= 0 || text.length < 10"
|
||||||
>
|
>
|
||||||
Update Schöpfung ({{ type }},{{ pagetype }})
|
Update Schöpfung
|
||||||
</b-button>
|
</b-button>
|
||||||
</div>
|
</div>
|
||||||
</b-col>
|
</b-col>
|
||||||
@ -127,15 +127,6 @@ import { updatePendingCreation } from '../graphql/updatePendingCreation'
|
|||||||
export default {
|
export default {
|
||||||
name: 'EditCreationFormular',
|
name: 'EditCreationFormular',
|
||||||
props: {
|
props: {
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
pagetype: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@ -143,13 +134,6 @@ export default {
|
|||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
items: {
|
|
||||||
type: Array,
|
|
||||||
required: false,
|
|
||||||
default() {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
},
|
|
||||||
row: {
|
row: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: false,
|
||||||
@ -247,7 +231,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if (this.pagetype === 'PageCreationConfirm' && this.creationUserData.date) {
|
if (this.creationUserData.date) {
|
||||||
switch (this.$moment(this.creationUserData.date).format('MMMM')) {
|
switch (this.$moment(this.creationUserData.date).format('MMMM')) {
|
||||||
case this.currentMonth.short:
|
case this.currentMonth.short:
|
||||||
this.createdIndex = 2
|
this.createdIndex = 2
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="component-nabvar">
|
<div class="component-nabvar">
|
||||||
<b-navbar toggleable="sm" type="dark" variant="success">
|
<b-navbar toggleable="md" type="dark" variant="success" class="p-3">
|
||||||
<b-navbar-brand to="/">
|
<b-navbar-brand to="/">
|
||||||
<img src="img/brand/green.png" class="navbar-brand-img" alt="..." />
|
<img src="img/brand/green.png" class="navbar-brand-img" alt="..." />
|
||||||
</b-navbar-brand>
|
</b-navbar-brand>
|
||||||
@ -9,19 +9,18 @@
|
|||||||
|
|
||||||
<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="/">Ü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
|
||||||
v-show="$store.state.openCreations > 0"
|
v-show="$store.state.openCreations > 0"
|
||||||
class="h5 bg-danger"
|
class="bg-color-creation p-1"
|
||||||
to="/creation-confirm"
|
to="/creation-confirm"
|
||||||
>
|
>
|
||||||
| {{ $store.state.openCreations }} offene Schöpfungen
|
{{ $store.state.openCreations }} offene Schöpfungen
|
||||||
</b-nav-item>
|
</b-nav-item>
|
||||||
<b-nav-item @click="wallet">Wallet</b-nav-item>
|
<b-nav-item @click="wallet">Wallet</b-nav-item>
|
||||||
<b-nav-item @click="logout">Logout</b-nav-item>
|
<b-nav-item @click="logout">Logout</b-nav-item>
|
||||||
<!-- <b-nav-item v-show="open < 1" to="/creation-confirm">| keine offene Schöpfungen</b-nav-item> -->
|
|
||||||
</b-navbar-nav>
|
</b-navbar-nav>
|
||||||
</b-collapse>
|
</b-collapse>
|
||||||
</b-navbar>
|
</b-navbar>
|
||||||
@ -49,4 +48,7 @@ export default {
|
|||||||
height: 2rem;
|
height: 2rem;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
.bg-color-creation {
|
||||||
|
background-color: #cf1010dc;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
27
admin/src/components/RowDetails.vue
Normal file
27
admin/src/components/RowDetails.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
<b-card class="shadow-lg pl-3 pr-3 mb-5 bg-white rounded">
|
||||||
|
<b-row class="mb-2">
|
||||||
|
<b-col></b-col>
|
||||||
|
</b-row>
|
||||||
|
<slot :name="slotName" />
|
||||||
|
<b-button size="sm" @click="$emit('row-toogle-details', row, index)">
|
||||||
|
<b-icon
|
||||||
|
:icon="type === 'PageCreationConfirm' ? 'x' : 'eye-slash-fill'"
|
||||||
|
aria-label="Help"
|
||||||
|
></b-icon>
|
||||||
|
Details verbergen von {{ row.item.firstName }} {{ row.item.lastName }}
|
||||||
|
</b-button>
|
||||||
|
</b-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'RowDetails',
|
||||||
|
props: {
|
||||||
|
row: { required: true, type: Object },
|
||||||
|
slotName: { requried: true, type: String },
|
||||||
|
type: { requried: true, type: String },
|
||||||
|
index: { requried: true, type: Number },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -37,30 +37,47 @@
|
|||||||
stacked="md"
|
stacked="md"
|
||||||
>
|
>
|
||||||
<template #cell(edit_creation)="row">
|
<template #cell(edit_creation)="row">
|
||||||
<b-button
|
<b-button variant="info" size="md" @click="rowToogleDetails(row, 0)" class="mr-2">
|
||||||
variant="info"
|
<b-icon :icon="row.detailsShowing ? 'x' : 'pencil-square'" aria-label="Help"></b-icon>
|
||||||
size="md"
|
|
||||||
@click="editCreationUserTable(row, row.item)"
|
|
||||||
class="mr-2"
|
|
||||||
>
|
|
||||||
<b-icon v-if="row.detailsShowing" icon="x" aria-label="Help"></b-icon>
|
|
||||||
<b-icon v-else icon="pencil-square" aria-label="Help"></b-icon>
|
|
||||||
</b-button>
|
</b-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #cell(show_details)="row">
|
<template #cell(show_details)="row">
|
||||||
<b-button variant="info" size="md" @click="row.toggleDetails" class="mr-2">
|
<b-button variant="info" size="md" @click="rowToogleDetails(row, 0)" class="mr-2">
|
||||||
<b-icon v-if="row.detailsShowing" icon="eye-slash-fill" aria-label="Help"></b-icon>
|
<b-icon :icon="row.detailsShowing ? 'eye-slash-fill' : 'eye-fill'"></b-icon>
|
||||||
<b-icon v-else icon="eye-fill" aria-label="Help"></b-icon>
|
</b-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(confirm_mail)="row">
|
||||||
|
<b-button
|
||||||
|
:variant="row.item.emailChecked ? 'success' : 'danger'"
|
||||||
|
size="md"
|
||||||
|
@click="rowToogleDetails(row, 1)"
|
||||||
|
class="mr-2"
|
||||||
|
>
|
||||||
|
<b-icon
|
||||||
|
:icon="row.item.emailChecked ? 'envelope-open' : 'envelope'"
|
||||||
|
aria-label="Help"
|
||||||
|
></b-icon>
|
||||||
|
</b-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(transactions_list)="row">
|
||||||
|
<b-button variant="warning" size="md" @click="rowToogleDetails(row, 2)" class="mr-2">
|
||||||
|
<b-icon icon="list"></b-icon>
|
||||||
</b-button>
|
</b-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #row-details="row">
|
<template #row-details="row">
|
||||||
<b-card class="shadow-lg p-3 mb-5 bg-white rounded">
|
<row-details
|
||||||
<b-row class="mb-2">
|
:row="row"
|
||||||
<b-col></b-col>
|
:type="type"
|
||||||
</b-row>
|
:slotName="slotName"
|
||||||
{{ type }}
|
:index="slotIndex"
|
||||||
|
@row-toogle-details="rowToogleDetails"
|
||||||
|
>
|
||||||
|
<template #show-creation>
|
||||||
|
<div>
|
||||||
<creation-formular
|
<creation-formular
|
||||||
v-if="type === 'PageUserSearch'"
|
v-if="type === 'PageUserSearch'"
|
||||||
type="singleCreation"
|
type="singleCreation"
|
||||||
@ -82,17 +99,19 @@
|
|||||||
@update-creation-data="updateCreationData"
|
@update-creation-data="updateCreationData"
|
||||||
@update-user-data="updateUserData"
|
@update-user-data="updateUserData"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<b-button size="sm" @click="row.toggleDetails">
|
</template>
|
||||||
<b-icon
|
<template #show-register-mail>
|
||||||
:icon="type === 'PageCreationConfirm' ? 'x' : 'eye-slash-fill'"
|
<confirm-register-mail-formular
|
||||||
aria-label="Help"
|
:email="row.item.email"
|
||||||
></b-icon>
|
:dateLastSend="$moment().subtract(1, 'month').format('dddd, DD.MMMM.YYYY HH:mm'),"
|
||||||
Details verbergen von {{ row.item.firstName }} {{ row.item.lastName }}
|
/>
|
||||||
</b-button>
|
</template>
|
||||||
</b-card>
|
<template #show-transaction-list>
|
||||||
|
<creation-transaction-list-formular :userId="row.item.userId" />
|
||||||
|
</template>
|
||||||
|
</row-details>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #cell(bookmark)="row">
|
<template #cell(bookmark)="row">
|
||||||
<b-button
|
<b-button
|
||||||
variant="warning"
|
variant="warning"
|
||||||
@ -132,8 +151,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import CreationFormular from '../components/CreationFormular.vue'
|
import CreationFormular from '../components/CreationFormular.vue'
|
||||||
import EditCreationFormular from '../components/EditCreationFormular.vue'
|
import EditCreationFormular from '../components/EditCreationFormular.vue'
|
||||||
|
import ConfirmRegisterMailFormular from '../components/ConfirmRegisterMailFormular.vue'
|
||||||
|
import CreationTransactionListFormular from '../components/CreationTransactionListFormular.vue'
|
||||||
|
import RowDetails from '../components/RowDetails.vue'
|
||||||
|
|
||||||
import { confirmPendingCreation } from '../graphql/confirmPendingCreation'
|
import { confirmPendingCreation } from '../graphql/confirmPendingCreation'
|
||||||
|
|
||||||
|
const slotNames = ['show-creation', 'show-register-mail', 'show-transaction-list']
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'UserTable',
|
name: 'UserTable',
|
||||||
props: {
|
props: {
|
||||||
@ -162,9 +187,15 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
CreationFormular,
|
CreationFormular,
|
||||||
EditCreationFormular,
|
EditCreationFormular,
|
||||||
|
ConfirmRegisterMailFormular,
|
||||||
|
CreationTransactionListFormular,
|
||||||
|
RowDetails,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
showCreationFormular: null,
|
||||||
|
showConfirmRegisterMailFormular: null,
|
||||||
|
showCreationTransactionListFormular: null,
|
||||||
creationUserData: {},
|
creationUserData: {},
|
||||||
overlay: false,
|
overlay: false,
|
||||||
overlayBookmarkType: '',
|
overlayBookmarkType: '',
|
||||||
@ -178,9 +209,35 @@ export default {
|
|||||||
button_cancel: 'Cancel',
|
button_cancel: 'Cancel',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
slotIndex: 0,
|
||||||
|
openRow: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
rowToogleDetails(row, index) {
|
||||||
|
if (this.openRow) {
|
||||||
|
if (this.openRow.index === row.index) {
|
||||||
|
if (index === this.slotIndex) {
|
||||||
|
row.toggleDetails()
|
||||||
|
this.openRow = null
|
||||||
|
} else {
|
||||||
|
this.slotIndex = index
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.openRow.toggleDetails()
|
||||||
|
row.toggleDetails()
|
||||||
|
this.slotIndex = index
|
||||||
|
this.openRow = row
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
row.toggleDetails()
|
||||||
|
this.slotIndex = index
|
||||||
|
this.openRow = row
|
||||||
|
if (this.type === 'PageCreationConfirm') {
|
||||||
|
this.creationUserData = row.item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
overlayShow(bookmarkType, item) {
|
overlayShow(bookmarkType, item) {
|
||||||
this.overlay = true
|
this.overlay = true
|
||||||
this.overlayBookmarkType = bookmarkType
|
this.overlayBookmarkType = bookmarkType
|
||||||
@ -243,14 +300,6 @@ export default {
|
|||||||
this.$toasted.error(error.message)
|
this.$toasted.error(error.message)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
editCreationUserTable(row, rowItem) {
|
|
||||||
if (!row.detailsShowing) {
|
|
||||||
this.creationUserData = rowItem
|
|
||||||
} else {
|
|
||||||
this.creationUserData = {}
|
|
||||||
}
|
|
||||||
row.toggleDetails()
|
|
||||||
},
|
|
||||||
updateCreationData(data) {
|
updateCreationData(data) {
|
||||||
this.creationUserData.amount = data.amount
|
this.creationUserData.amount = data.amount
|
||||||
this.creationUserData.date = data.date
|
this.creationUserData.date = data.date
|
||||||
@ -263,6 +312,11 @@ export default {
|
|||||||
rowItem.creation = newCreation
|
rowItem.creation = newCreation
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
slotName() {
|
||||||
|
return slotNames[this.slotIndex]
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@ -3,10 +3,12 @@ import gql from 'graphql-tag'
|
|||||||
export const searchUsers = gql`
|
export const searchUsers = gql`
|
||||||
query ($searchText: String!) {
|
query ($searchText: String!) {
|
||||||
searchUsers(searchText: $searchText) {
|
searchUsers(searchText: $searchText) {
|
||||||
|
userId
|
||||||
firstName
|
firstName
|
||||||
lastName
|
lastName
|
||||||
email
|
email
|
||||||
creation
|
creation
|
||||||
|
emailChecked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
7
admin/src/graphql/sendActivationEmail.js
Normal file
7
admin/src/graphql/sendActivationEmail.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export const sendActivationEmail = gql`
|
||||||
|
mutation ($email: String!) {
|
||||||
|
sendActivationEmail(email: $email)
|
||||||
|
}
|
||||||
|
`
|
||||||
44
admin/src/graphql/transactionList.js
Normal file
44
admin/src/graphql/transactionList.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export const transactionList = gql`
|
||||||
|
query (
|
||||||
|
$currentPage: Int = 1
|
||||||
|
$pageSize: Int = 25
|
||||||
|
$order: Order = DESC
|
||||||
|
$onlyCreations: Boolean = false
|
||||||
|
$userId: Int = null
|
||||||
|
) {
|
||||||
|
transactionList(
|
||||||
|
currentPage: $currentPage
|
||||||
|
pageSize: $pageSize
|
||||||
|
order: $order
|
||||||
|
onlyCreations: $onlyCreations
|
||||||
|
userId: $userId
|
||||||
|
) {
|
||||||
|
gdtSum
|
||||||
|
count
|
||||||
|
balance
|
||||||
|
decay
|
||||||
|
decayDate
|
||||||
|
transactions {
|
||||||
|
type
|
||||||
|
balance
|
||||||
|
decayStart
|
||||||
|
decayEnd
|
||||||
|
decayDuration
|
||||||
|
memo
|
||||||
|
transactionId
|
||||||
|
name
|
||||||
|
email
|
||||||
|
date
|
||||||
|
decay {
|
||||||
|
balance
|
||||||
|
decayStart
|
||||||
|
decayEnd
|
||||||
|
decayDuration
|
||||||
|
decayStartBlock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
@ -40,7 +40,6 @@
|
|||||||
:items="itemsMassCreation"
|
:items="itemsMassCreation"
|
||||||
@remove-all-bookmark="removeAllBookmark"
|
@remove-all-bookmark="removeAllBookmark"
|
||||||
/>
|
/>
|
||||||
{{ itemsMassCreation }}
|
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -28,28 +28,6 @@
|
|||||||
</b-link>
|
</b-link>
|
||||||
</b-card-text>
|
</b-card-text>
|
||||||
</b-card>
|
</b-card>
|
||||||
<br />
|
|
||||||
<hr />
|
|
||||||
<br />
|
|
||||||
<b-list-group>
|
|
||||||
<b-list-group-item class="bg-secondary text-light" href="user">
|
|
||||||
zur Usersuche
|
|
||||||
</b-list-group-item>
|
|
||||||
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
|
||||||
Mitglieder
|
|
||||||
<b-badge class="bg-success" pill>2400</b-badge>
|
|
||||||
</b-list-group-item>
|
|
||||||
|
|
||||||
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
|
||||||
aktive Mitglieder
|
|
||||||
<b-badge class="bg-primary" pill>2201</b-badge>
|
|
||||||
</b-list-group-item>
|
|
||||||
|
|
||||||
<b-list-group-item class="d-flex justify-content-between align-items-center">
|
|
||||||
nicht bestätigte Mitglieder
|
|
||||||
<b-badge class="bg-warning text-dark" pill>120</b-badge>
|
|
||||||
</b-list-group-item>
|
|
||||||
</b-list-group>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -11,6 +11,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
|||||||
lastName: 'Bloxberg',
|
lastName: 'Bloxberg',
|
||||||
email: 'bibi@bloxberg.de',
|
email: 'bibi@bloxberg.de',
|
||||||
creation: [200, 400, 600],
|
creation: [200, 400, 600],
|
||||||
|
emailChecked: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -43,6 +44,16 @@ describe('UserSearch', () => {
|
|||||||
expect(wrapper.find('div.user-search').exists()).toBeTruthy()
|
expect(wrapper.find('div.user-search').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('unconfirmed emails', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.find('button.btn-block').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('filters the users by unconfirmed emails', () => {
|
||||||
|
expect(wrapper.vm.searchResult).toHaveLength(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('apollo returns error', () => {
|
describe('apollo returns error', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
apolloQueryMock.mockRejectedValue({
|
apolloQueryMock.mockRejectedValue({
|
||||||
|
|||||||
@ -4,16 +4,23 @@
|
|||||||
<b-input
|
<b-input
|
||||||
type="text"
|
type="text"
|
||||||
v-model="criteria"
|
v-model="criteria"
|
||||||
class="shadow p-3 mb-5 bg-white rounded"
|
class="shadow p-3 mb-3 bg-white rounded"
|
||||||
placeholder="User suche"
|
placeholder="User suche"
|
||||||
@input="getUsers"
|
@input="getUsers"
|
||||||
></b-input>
|
></b-input>
|
||||||
|
|
||||||
<user-table
|
<user-table
|
||||||
type="PageUserSearch"
|
type="PageUserSearch"
|
||||||
:itemsUser="searchResult"
|
:itemsUser="searchResult"
|
||||||
:fieldsTable="fields"
|
:fieldsTable="fields"
|
||||||
:criteria="criteria"
|
:criteria="criteria"
|
||||||
/>
|
/>
|
||||||
|
<div>
|
||||||
|
<b-button block variant="danger" @click="unconfirmedRegisterMails">
|
||||||
|
<b-icon icon="envelope" variant="light"></b-icon>
|
||||||
|
Anzeigen aller nicht registrierten E-Mails.
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
@ -40,6 +47,8 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ key: 'show_details', label: 'Details' },
|
{ key: 'show_details', label: 'Details' },
|
||||||
|
{ key: 'confirm_mail', label: 'Mail' },
|
||||||
|
{ key: 'transactions_list', label: 'Transaction' },
|
||||||
],
|
],
|
||||||
searchResult: [],
|
searchResult: [],
|
||||||
massCreation: [],
|
massCreation: [],
|
||||||
@ -48,6 +57,11 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
unconfirmedRegisterMails() {
|
||||||
|
this.searchResult = this.searchResult.filter((user) => {
|
||||||
|
return user.emailChecked
|
||||||
|
})
|
||||||
|
},
|
||||||
getUsers() {
|
getUsers() {
|
||||||
this.$apollo
|
this.$apollo
|
||||||
.query({
|
.query({
|
||||||
@ -57,12 +71,7 @@ export default {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
this.searchResult = result.data.searchUsers.map((user) => {
|
this.searchResult = result.data.searchUsers
|
||||||
return {
|
|
||||||
...user,
|
|
||||||
// showDetails: true,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toasted.error(error.message)
|
this.$toasted.error(error.message)
|
||||||
|
|||||||
@ -15,6 +15,9 @@ DB_DATABASE=gradido_community
|
|||||||
#EMAIL_PASSWORD=
|
#EMAIL_PASSWORD=
|
||||||
#EMAIL_SMTP_URL=
|
#EMAIL_SMTP_URL=
|
||||||
#EMAIL_SMTP_PORT=587
|
#EMAIL_SMTP_PORT=587
|
||||||
|
#RESEND_TIME=1 minute, 60 => 1hour, 1440 (60 minutes * 24 hours) => 24 hours
|
||||||
|
#RESEND_TIME=
|
||||||
|
RESEND_TIME=10
|
||||||
|
|
||||||
#EMAIL_LINK_VERIFICATION=http://localhost/vue/checkEmail/$1
|
#EMAIL_LINK_VERIFICATION=http://localhost/vue/checkEmail/$1
|
||||||
|
|
||||||
|
|||||||
@ -42,6 +42,7 @@ const loginServer = {
|
|||||||
LOGIN_SERVER_KEY: process.env.LOGIN_SERVER_KEY || 'a51ef8ac7ef1abf162fb7a65261acd7a',
|
LOGIN_SERVER_KEY: process.env.LOGIN_SERVER_KEY || 'a51ef8ac7ef1abf162fb7a65261acd7a',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resendTime = parseInt(process.env.RESEND_TIME ? process.env.RESEND_TIME : 'null')
|
||||||
const email = {
|
const email = {
|
||||||
EMAIL: process.env.EMAIL === 'true' || false,
|
EMAIL: process.env.EMAIL === 'true' || false,
|
||||||
EMAIL_USERNAME: process.env.EMAIL_USERNAME || 'gradido_email',
|
EMAIL_USERNAME: process.env.EMAIL_USERNAME || 'gradido_email',
|
||||||
@ -52,6 +53,7 @@ const email = {
|
|||||||
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',
|
EMAIL_LINK_SETPASSWORD: process.env.EMAIL_LINK_SETPASSWORD || 'http://localhost/vue/reset/$1',
|
||||||
|
RESEND_TIME: isNaN(resendTime) ? 10 : resendTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
const webhook = {
|
const webhook = {
|
||||||
|
|||||||
@ -11,4 +11,10 @@ export default class Paginated {
|
|||||||
|
|
||||||
@Field(() => Order, { nullable: true })
|
@Field(() => Order, { nullable: true })
|
||||||
order?: Order
|
order?: Order
|
||||||
|
|
||||||
|
@Field(() => Boolean, { nullable: true })
|
||||||
|
onlyCreations?: boolean
|
||||||
|
|
||||||
|
@Field(() => Int, { nullable: true })
|
||||||
|
userId?: number
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,9 @@ import { ObjectType, Field } from 'type-graphql'
|
|||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class UserAdmin {
|
export class UserAdmin {
|
||||||
|
@Field(() => Number)
|
||||||
|
userId: number
|
||||||
|
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
email: string
|
email: string
|
||||||
|
|
||||||
@ -13,4 +16,7 @@ export class UserAdmin {
|
|||||||
|
|
||||||
@Field(() => [Number])
|
@Field(() => [Number])
|
||||||
creation: number[]
|
creation: number[]
|
||||||
|
|
||||||
|
@Field(() => Boolean)
|
||||||
|
emailChecked: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { UserTransaction } from '@entity/UserTransaction'
|
|||||||
import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction'
|
import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction'
|
||||||
import { BalanceRepository } from '../../typeorm/repository/Balance'
|
import { BalanceRepository } from '../../typeorm/repository/Balance'
|
||||||
import { calculateDecay } from '../../util/decay'
|
import { calculateDecay } from '../../util/decay'
|
||||||
|
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class AdminResolver {
|
export class AdminResolver {
|
||||||
@ -28,10 +29,12 @@ export class AdminResolver {
|
|||||||
const adminUsers = await Promise.all(
|
const adminUsers = await Promise.all(
|
||||||
users.map(async (user) => {
|
users.map(async (user) => {
|
||||||
const adminUser = new UserAdmin()
|
const adminUser = new UserAdmin()
|
||||||
|
adminUser.userId = user.id
|
||||||
adminUser.firstName = user.firstName
|
adminUser.firstName = user.firstName
|
||||||
adminUser.lastName = user.lastName
|
adminUser.lastName = user.lastName
|
||||||
adminUser.email = user.email
|
adminUser.email = user.email
|
||||||
adminUser.creation = await getUserCreations(user.id)
|
adminUser.creation = await getUserCreations(user.id)
|
||||||
|
adminUser.emailChecked = await hasActivatedEmail(user.email)
|
||||||
return adminUser
|
return adminUser
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -315,3 +318,10 @@ function isCreationValid(creations: number[], amount: number, creationDate: Date
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
async function hasActivatedEmail(email: string): Promise<boolean> {
|
||||||
|
const repository = getCustomRepository(LoginUserRepository)
|
||||||
|
const user = await repository.findByEmail(email)
|
||||||
|
let emailActivate = false
|
||||||
|
if (user) emailActivate = user.emailChecked
|
||||||
|
return user ? user.emailChecked : false
|
||||||
|
}
|
||||||
|
|||||||
@ -340,6 +340,7 @@ async function listTransactions(
|
|||||||
pageSize: number,
|
pageSize: number,
|
||||||
order: Order,
|
order: Order,
|
||||||
user: dbUser,
|
user: dbUser,
|
||||||
|
onlyCreations: boolean,
|
||||||
): Promise<TransactionList> {
|
): Promise<TransactionList> {
|
||||||
let limit = pageSize
|
let limit = pageSize
|
||||||
let offset = 0
|
let offset = 0
|
||||||
@ -358,6 +359,7 @@ async function listTransactions(
|
|||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
order,
|
order,
|
||||||
|
onlyCreations,
|
||||||
)
|
)
|
||||||
skipFirstTransaction = userTransactionsCount > offset + limit
|
skipFirstTransaction = userTransactionsCount > offset + limit
|
||||||
const decay = !(currentPage > 1)
|
const decay = !(currentPage > 1)
|
||||||
@ -469,14 +471,32 @@ export class TransactionResolver {
|
|||||||
@Authorized([RIGHTS.TRANSACTION_LIST])
|
@Authorized([RIGHTS.TRANSACTION_LIST])
|
||||||
@Query(() => TransactionList)
|
@Query(() => TransactionList)
|
||||||
async transactionList(
|
async transactionList(
|
||||||
@Args() { currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
|
@Args()
|
||||||
|
{
|
||||||
|
currentPage = 1,
|
||||||
|
pageSize = 25,
|
||||||
|
order = Order.DESC,
|
||||||
|
onlyCreations = false,
|
||||||
|
userId,
|
||||||
|
}: Paginated,
|
||||||
@Ctx() context: any,
|
@Ctx() context: any,
|
||||||
): Promise<TransactionList> {
|
): Promise<TransactionList> {
|
||||||
// load user
|
// load user
|
||||||
const userRepository = getCustomRepository(UserRepository)
|
const userRepository = getCustomRepository(UserRepository)
|
||||||
const userEntity = await userRepository.findByPubkeyHex(context.pubKey)
|
let userEntity: dbUser | undefined
|
||||||
|
if (userId) {
|
||||||
|
userEntity = await userRepository.findOneOrFail({ id: userId })
|
||||||
|
} else {
|
||||||
|
userEntity = await userRepository.findByPubkeyHex(context.pubKey)
|
||||||
|
}
|
||||||
|
|
||||||
const transactions = await listTransactions(currentPage, pageSize, order, userEntity)
|
const transactions = await listTransactions(
|
||||||
|
currentPage,
|
||||||
|
pageSize,
|
||||||
|
order,
|
||||||
|
userEntity,
|
||||||
|
onlyCreations,
|
||||||
|
)
|
||||||
|
|
||||||
// get gdt sum
|
// get gdt sum
|
||||||
const resultGDTSum = await apiPost(`${CONFIG.GDT_API_URL}/GdtEntries/sumPerEmailApi`, {
|
const resultGDTSum = await apiPost(`${CONFIG.GDT_API_URL}/GdtEntries/sumPerEmailApi`, {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
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, getRepository } from 'typeorm'
|
import { getConnection, getCustomRepository, getRepository, QueryRunner } from 'typeorm'
|
||||||
import CONFIG from '../../config'
|
import CONFIG from '../../config'
|
||||||
import { User } from '../model/User'
|
import { User } from '../model/User'
|
||||||
import { User as DbUser } from '@entity/User'
|
import { User as DbUser } from '@entity/User'
|
||||||
@ -148,6 +148,66 @@ const SecretKeyCryptographyDecrypt = (encryptedMessage: Buffer, encryptionKey: B
|
|||||||
|
|
||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
const createEmailOptIn = async (
|
||||||
|
loginUserId: number,
|
||||||
|
queryRunner: QueryRunner,
|
||||||
|
): Promise<LoginEmailOptIn> => {
|
||||||
|
const loginEmailOptInRepository = await getRepository(LoginEmailOptIn)
|
||||||
|
let emailOptIn = await loginEmailOptInRepository.findOne({
|
||||||
|
userId: loginUserId,
|
||||||
|
emailOptInTypeId: EMAIL_OPT_IN_REGISTER,
|
||||||
|
})
|
||||||
|
if (emailOptIn) {
|
||||||
|
const timeElapsed = Date.now() - new Date(emailOptIn.updatedAt).getTime()
|
||||||
|
if (timeElapsed <= parseInt(CONFIG.RESEND_TIME.toString()) * 60 * 1000) {
|
||||||
|
throw new Error(
|
||||||
|
'email already sent less than ' + parseInt(CONFIG.RESEND_TIME.toString()) + ' minutes ago',
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
emailOptIn.updatedAt = new Date()
|
||||||
|
emailOptIn.resendCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emailOptIn = new LoginEmailOptIn()
|
||||||
|
emailOptIn.verificationCode = random(64)
|
||||||
|
emailOptIn.userId = loginUserId
|
||||||
|
emailOptIn.emailOptInTypeId = EMAIL_OPT_IN_REGISTER
|
||||||
|
}
|
||||||
|
await queryRunner.manager.save(emailOptIn).catch((error) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Error while saving emailOptIn', error)
|
||||||
|
throw new Error('error saving email opt in')
|
||||||
|
})
|
||||||
|
return emailOptIn
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOptInCode = async (loginUser: LoginUser): Promise<LoginEmailOptIn> => {
|
||||||
|
const loginEmailOptInRepository = await getRepository(LoginEmailOptIn)
|
||||||
|
let optInCode = await loginEmailOptInRepository.findOne({
|
||||||
|
userId: loginUser.id,
|
||||||
|
emailOptInTypeId: EMAIL_OPT_IN_RESET_PASSWORD,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check for 10 minute delay
|
||||||
|
if (optInCode) {
|
||||||
|
const timeElapsed = Date.now() - new Date(optInCode.updatedAt).getTime()
|
||||||
|
if (timeElapsed <= parseInt(CONFIG.RESEND_TIME.toString()) * 60 * 1000) {
|
||||||
|
throw new Error(
|
||||||
|
'email already sent less than ' + parseInt(CONFIG.RESEND_TIME.toString()) + ' minutes ago',
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
optInCode.updatedAt = new Date()
|
||||||
|
optInCode.resendCount++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
optInCode = new LoginEmailOptIn()
|
||||||
|
optInCode.verificationCode = random(64)
|
||||||
|
optInCode.userId = loginUser.id
|
||||||
|
optInCode.emailOptInTypeId = EMAIL_OPT_IN_RESET_PASSWORD
|
||||||
|
}
|
||||||
|
await loginEmailOptInRepository.save(optInCode)
|
||||||
|
return optInCode
|
||||||
|
}
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class UserResolver {
|
export class UserResolver {
|
||||||
@ -383,37 +443,18 @@ export class UserResolver {
|
|||||||
|
|
||||||
// Store EmailOptIn in DB
|
// Store EmailOptIn in DB
|
||||||
// TODO: this has duplicate code with sendResetPasswordEmail
|
// TODO: this has duplicate code with sendResetPasswordEmail
|
||||||
const emailOptIn = new LoginEmailOptIn()
|
const emailOptIn = await createEmailOptIn(loginUserId, queryRunner)
|
||||||
emailOptIn.userId = loginUserId
|
|
||||||
emailOptIn.verificationCode = random(64)
|
|
||||||
emailOptIn.emailOptInTypeId = EMAIL_OPT_IN_REGISTER
|
|
||||||
|
|
||||||
await queryRunner.manager.save(emailOptIn).catch((error) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('Error while saving emailOptIn', error)
|
|
||||||
throw new Error('error saving email opt in')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Send EMail to user
|
|
||||||
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
|
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
|
||||||
/\$1/g,
|
/\$1/g,
|
||||||
emailOptIn.verificationCode.toString(),
|
emailOptIn.verificationCode.toString(),
|
||||||
)
|
)
|
||||||
const emailSent = await sendEMail({
|
const emailSent = await this.sendAccountActivationEmail(
|
||||||
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
|
activationLink,
|
||||||
to: `${firstName} ${lastName} <${email}>`,
|
firstName,
|
||||||
subject: 'Gradido: E-Mail Überprüfung',
|
lastName,
|
||||||
text: `Hallo ${firstName} ${lastName},
|
email,
|
||||||
|
)
|
||||||
Deine EMail wurde soeben bei Gradido registriert.
|
|
||||||
|
|
||||||
Klicke bitte auf diesen Link, um die Registrierung abzuschließen und dein Gradido-Konto zu aktivieren:
|
|
||||||
${activationLink}
|
|
||||||
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
|
// In case EMails are disabled log the activation link for the user
|
||||||
if (!emailSent) {
|
if (!emailSent) {
|
||||||
@ -430,33 +471,78 @@ export class UserResolver {
|
|||||||
return 'success'
|
return 'success'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async sendAccountActivationEmail(
|
||||||
|
activationLink: string,
|
||||||
|
firstName: string,
|
||||||
|
lastName: string,
|
||||||
|
email: string,
|
||||||
|
) {
|
||||||
|
const emailSent = await sendEMail({
|
||||||
|
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
|
||||||
|
to: `${firstName} ${lastName} <${email}>`,
|
||||||
|
subject: 'Gradido: E-Mail Überprüfung',
|
||||||
|
text: `Hallo ${firstName} ${lastName},
|
||||||
|
|
||||||
|
Deine EMail wurde soeben bei Gradido registriert.
|
||||||
|
|
||||||
|
Klicke bitte auf diesen Link, um die Registrierung abzuschließen und dein Gradido-Konto zu aktivieren:
|
||||||
|
${activationLink}
|
||||||
|
oder kopiere den obigen Link in dein Browserfenster.
|
||||||
|
|
||||||
|
Mit freundlichen Grüßen,
|
||||||
|
dein Gradido-Team`,
|
||||||
|
})
|
||||||
|
return emailSent
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => Boolean)
|
||||||
|
async sendActivationEmail(@Arg('email') email: string): Promise<boolean> {
|
||||||
|
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||||
|
const loginUser = await loginUserRepository.findOneOrFail({ email: email })
|
||||||
|
|
||||||
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
|
await queryRunner.connect()
|
||||||
|
await queryRunner.startTransaction('READ UNCOMMITTED')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const emailOptIn = await createEmailOptIn(loginUser.id, queryRunner)
|
||||||
|
|
||||||
|
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
|
||||||
|
/\$1/g,
|
||||||
|
emailOptIn.verificationCode.toString(),
|
||||||
|
)
|
||||||
|
|
||||||
|
const emailSent = await this.sendAccountActivationEmail(
|
||||||
|
activationLink,
|
||||||
|
loginUser.firstName,
|
||||||
|
loginUser.lastName,
|
||||||
|
email,
|
||||||
|
)
|
||||||
|
|
||||||
|
// In case EMails are disabled log the activation link for the user
|
||||||
|
if (!emailSent) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Account confirmation link: ${activationLink}`)
|
||||||
|
}
|
||||||
|
await queryRunner.commitTransaction()
|
||||||
|
} catch (e) {
|
||||||
|
await queryRunner.rollbackTransaction()
|
||||||
|
throw e
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL])
|
@Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL])
|
||||||
@Query(() => Boolean)
|
@Query(() => Boolean)
|
||||||
async sendResetPasswordEmail(@Arg('email') email: string): Promise<boolean> {
|
async sendResetPasswordEmail(@Arg('email') email: string): Promise<boolean> {
|
||||||
// TODO: this has duplicate code with createUser
|
// TODO: this has duplicate code with createUser
|
||||||
|
|
||||||
const loginUserRepository = await getCustomRepository(LoginUserRepository)
|
const loginUserRepository = await getCustomRepository(LoginUserRepository)
|
||||||
const loginUser = await loginUserRepository.findOneOrFail({ email })
|
const loginUser = await loginUserRepository.findOneOrFail({ email })
|
||||||
|
|
||||||
const loginEmailOptInRepository = await getRepository(LoginEmailOptIn)
|
const optInCode = await getOptInCode(loginUser)
|
||||||
let optInCode = await loginEmailOptInRepository.findOne({
|
|
||||||
userId: loginUser.id,
|
|
||||||
emailOptInTypeId: EMAIL_OPT_IN_RESET_PASSWORD,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Check for 10 minute delay
|
|
||||||
if (optInCode) {
|
|
||||||
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')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate new OptIn Code
|
|
||||||
optInCode = new LoginEmailOptIn()
|
|
||||||
optInCode.verificationCode = random(64)
|
|
||||||
optInCode.userId = loginUser.id
|
|
||||||
optInCode.emailOptInTypeId = EMAIL_OPT_IN_RESET_PASSWORD
|
|
||||||
await loginEmailOptInRepository.save(optInCode)
|
|
||||||
|
|
||||||
const link = CONFIG.EMAIL_LINK_SETPASSWORD.replace(
|
const link = CONFIG.EMAIL_LINK_SETPASSWORD.replace(
|
||||||
/\$1/g,
|
/\$1/g,
|
||||||
|
|||||||
@ -9,7 +9,17 @@ export class UserTransactionRepository extends Repository<UserTransaction> {
|
|||||||
limit: number,
|
limit: number,
|
||||||
offset: number,
|
offset: number,
|
||||||
order: Order,
|
order: Order,
|
||||||
|
onlyCreation?: boolean,
|
||||||
): Promise<[UserTransaction[], number]> {
|
): Promise<[UserTransaction[], number]> {
|
||||||
|
if (onlyCreation) {
|
||||||
|
return this.createQueryBuilder('userTransaction')
|
||||||
|
.where('userTransaction.userId = :userId', { userId })
|
||||||
|
.andWhere('userTransaction.type = "creation"')
|
||||||
|
.orderBy('userTransaction.balanceDate', order)
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
.getManyAndCount()
|
||||||
|
}
|
||||||
return this.createQueryBuilder('userTransaction')
|
return this.createQueryBuilder('userTransaction')
|
||||||
.where('userTransaction.userId = :userId', { userId })
|
.where('userTransaction.userId = :userId', { userId })
|
||||||
.orderBy('userTransaction.balanceDate', order)
|
.orderBy('userTransaction.balanceDate', order)
|
||||||
|
|||||||
@ -47,8 +47,18 @@ export const logout = gql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const transactionsQuery = gql`
|
export const transactionsQuery = gql`
|
||||||
query($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) {
|
query(
|
||||||
transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
|
$currentPage: Int = 1
|
||||||
|
$pageSize: Int = 25
|
||||||
|
$order: Order = DESC
|
||||||
|
$onlyCreations: Boolean = false
|
||||||
|
) {
|
||||||
|
transactionList(
|
||||||
|
currentPage: $currentPage
|
||||||
|
pageSize: $pageSize
|
||||||
|
order: $order
|
||||||
|
onlyCreations: $onlyCreations
|
||||||
|
) {
|
||||||
gdtSum
|
gdtSum
|
||||||
count
|
count
|
||||||
balance
|
balance
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user