Merge branch 'master' into 1319-email-optin

This commit is contained in:
Moriz Wahl 2022-02-07 19:27:50 +01:00 committed by GitHub
commit 26871b4f61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 1332 additions and 900 deletions

View File

@ -448,7 +448,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: 81 min_coverage: 93
token: ${{ github.token }} token: ${{ github.token }}
############################################################################## ##############################################################################

5
.gitmodules vendored
View File

@ -1,8 +1,3 @@
[submodule "gn"]
path = gn
url = https://github.com/gradido/gn.git
branch = master
[submodule "community_server/src/protobuf"] [submodule "community_server/src/protobuf"]
path = community_server/src/protobuf path = community_server/src/protobuf
url = https://github.com/gradido/gradido_protocol.git url = https://github.com/gradido/gradido_protocol.git

View File

@ -13,3 +13,11 @@ export default {
components: { defaultLayout }, components: { defaultLayout },
} }
</script> </script>
<style>
.pointer {
cursor: pointer;
}
.pointer:hover {
background-color: rgb(216, 213, 213);
}
</style>

View File

@ -11,22 +11,36 @@ describe('UserTable', () => {
const defaultItemsUser = [ const defaultItemsUser = [
{ {
email: 'bibi@bloxberg.de', userId: 1,
firstName: 'Bibi', firstName: 'Bibi',
lastName: 'Bloxberg', lastName: 'Bloxberg',
creation: [1000, 1000, 1000], email: 'bibi@bloxberg.de',
creation: [200, 400, 600],
emailChecked: true,
}, },
{ {
email: 'bibi@bloxberg.de', userId: 2,
firstName: 'Bibi', firstName: 'Benjamin',
lastName: 'Bloxberg', lastName: 'Blümchen',
email: 'benjamin@bluemchen.de',
creation: [1000, 1000, 1000], creation: [1000, 1000, 1000],
emailChecked: true,
}, },
{ {
email: 'bibi@bloxberg.de', userId: 3,
firstName: 'Bibi', firstName: 'Peter',
lastName: 'Bloxberg', lastName: 'Lustig',
email: 'peter@lustig.de',
creation: [0, 0, 0],
emailChecked: true,
},
{
userId: 4,
firstName: 'New',
lastName: 'User',
email: 'new@user.ch',
creation: [1000, 1000, 1000], creation: [1000, 1000, 1000],
emailChecked: false,
}, },
] ]
@ -107,7 +121,7 @@ describe('UserTable', () => {
const mocks = { const mocks = {
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$d: jest.fn((d) => d), $d: jest.fn((d) => String(d)),
$apollo: { $apollo: {
query: apolloQueryMock, query: apolloQueryMock,
}, },
@ -122,7 +136,7 @@ describe('UserTable', () => {
describe('mount', () => { describe('mount', () => {
describe('type PageUserSearch', () => { describe('type PageUserSearch', () => {
beforeEach(() => { beforeEach(async () => {
wrapper = Wrapper(propsDataPageUserSearch) wrapper = Wrapper(propsDataPageUserSearch)
}) })
@ -175,12 +189,12 @@ describe('UserTable', () => {
}) })
describe('content', () => { describe('content', () => {
it('has 3 rows', () => { it('has 4 rows', () => {
expect(wrapper.findAll('tbody tr').length).toBe(3) expect(wrapper.findAll('tbody tr')).toHaveLength(4)
}) })
it('has 7 columns', () => { it('has 7 columns', () => {
expect(wrapper.findAll('tr:nth-child(1) > td').length).toBe(7) expect(wrapper.findAll('tr:nth-child(1) > td')).toHaveLength(7)
}) })
it('find button on fifth column', () => { it('find button on fifth column', () => {
@ -189,6 +203,110 @@ describe('UserTable', () => {
).toBeTruthy() ).toBeTruthy()
}) })
}) })
describe('row toggling', () => {
describe('user with email not activated', () => {
it('has no details button', () => {
expect(
wrapper.findAll('tbody > tr').at(3).findAll('td').at(4).find('button').exists(),
).toBeFalsy()
})
it('has a red confirmed button with envelope item', () => {
const row = wrapper.findAll('tbody > tr').at(3)
expect(row.findAll('td').at(5).find('button').exists()).toBeTruthy()
expect(row.findAll('td').at(5).find('button').classes('btn-danger')).toBeTruthy()
expect(row.findAll('td').at(5).find('svg').classes('bi-envelope')).toBeTruthy()
})
describe('click on envelope', () => {
beforeEach(async () => {
await wrapper
.findAll('tbody > tr')
.at(3)
.findAll('td')
.at(5)
.find('button')
.trigger('click')
})
it('opens the details', async () => {
expect(wrapper.findAll('tbody > tr')).toHaveLength(6)
expect(wrapper.findAll('tbody > tr').at(5).find('input').element.value).toBe(
'new@user.ch',
)
expect(wrapper.findAll('tbody > tr').at(5).text()).toContain(
'unregister_mail.text_false',
)
// HACK: for some reason we need to close the row details after this test
await wrapper
.findAll('tbody > tr')
.at(3)
.findAll('td')
.at(5)
.find('button')
.trigger('click')
})
describe('click on envelope again', () => {
beforeEach(async () => {
await wrapper
.findAll('tbody > tr')
.at(3)
.findAll('td')
.at(5)
.find('button')
.trigger('click')
})
it('closes the details', () => {
expect(wrapper.findAll('tbody > tr')).toHaveLength(4)
})
})
describe('click on close details', () => {
beforeEach(async () => {
await wrapper.findAll('tbody > tr').at(5).findAll('button').at(1).trigger('click')
})
it('closes the details', () => {
expect(wrapper.findAll('tbody > tr')).toHaveLength(4)
})
})
})
})
describe('different details', () => {
it.skip('shows the creation formular for second user', async () => {
await wrapper
.findAll('tbody > tr')
.at(1)
.findAll('td')
.at(4)
.find('button')
.trigger('click')
expect(wrapper.findAll('tbody > tr')).toHaveLength(6)
expect(
wrapper
.findAll('tbody > tr')
.at(3)
.find('div.component-creation-formular')
.exists(),
).toBeTruthy()
})
it.skip('shows the transactions for third user', async () => {
await wrapper
.findAll('tbody > tr')
.at(4)
.findAll('td')
.at(6)
.find('button')
.trigger('click')
expect(wrapper.findAll('tbody > tr')).toHaveLength(6)
})
})
})
}) })
}) })

View File

@ -27,15 +27,7 @@
</b-button> </b-button>
</b-jumbotron> </b-jumbotron>
</div> </div>
<b-table-lite <b-table-lite :items="itemsUser" :fields="fieldsTable" caption-top striped hover stacked="md">
:items="itemsUser"
:fields="fieldsTable"
:filter="criteria"
caption-top
striped
hover
stacked="md"
>
<template #cell(creation)="data"> <template #cell(creation)="data">
<div v-html="data.value"></div> <div v-html="data.value"></div>
</template> </template>
@ -125,7 +117,7 @@
</row-details> </row-details>
</template> </template>
<template #cell(bookmark)="row"> <template #cell(bookmark)="row">
<div v-show="type === 'UserListSearch'"> <div v-if="type === 'UserListSearch'">
<b-button <b-button
v-if="row.item.emailChecked" v-if="row.item.emailChecked"
variant="warning" variant="warning"
@ -141,7 +133,7 @@
variant="danger" variant="danger"
v-show="type === 'UserListMassCreation' || type === 'PageCreationConfirm'" v-show="type === 'UserListMassCreation' || type === 'PageCreationConfirm'"
size="md" size="md"
@click="overlayShow('remove', row.item)" @click="bookmarkRemove(row.item)"
class="mr-2" class="mr-2"
> >
<b-icon icon="x" variant="light"></b-icon> <b-icon icon="x" variant="light"></b-icon>
@ -187,15 +179,6 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
criteria: {
type: String,
required: false,
default: '',
},
creation: {
type: Array,
required: false,
},
}, },
components: { components: {
CreationFormular, CreationFormular,
@ -259,13 +242,6 @@ export default {
this.overlayBookmarkType = bookmarkType this.overlayBookmarkType = bookmarkType
this.overlayItem = item this.overlayItem = item
if (bookmarkType === 'remove') {
this.overlayText.header = this.$t('overlay.remove.title')
this.overlayText.text1 = this.$t('overlay.remove.text')
this.overlayText.text2 = this.$t('overlay.remove.question')
this.overlayText.button_ok = this.$t('overlay.remove.yes')
this.overlayText.button_cancel = this.$t('overlay.remove.no')
}
if (bookmarkType === 'confirm') { if (bookmarkType === 'confirm') {
this.overlayText.header = this.$t('overlay.confirm.title') this.overlayText.header = this.$t('overlay.confirm.title')
this.overlayText.text1 = this.$t('overlay.confirm.text') this.overlayText.text1 = this.$t('overlay.confirm.text')
@ -275,9 +251,6 @@ export default {
} }
}, },
overlayOK(bookmarkType, item) { overlayOK(bookmarkType, item) {
if (bookmarkType === 'remove') {
this.bookmarkRemove(item)
}
if (bookmarkType === 'confirm') { if (bookmarkType === 'confirm') {
this.$emit('confirm-creation', item) this.$emit('confirm-creation', item)
} }

View File

@ -54,6 +54,7 @@
} }
}, },
"remove": "Entfernen", "remove": "Entfernen",
"remove_all": "alle Nutzer entfernen",
"transaction": "Transaktion", "transaction": "Transaktion",
"transactionlist": { "transactionlist": {
"amount": "Betrag", "amount": "Betrag",

View File

@ -54,6 +54,7 @@
} }
}, },
"remove": "Remove", "remove": "Remove",
"remove_all": "Remove all users",
"transaction": "Transaction", "transaction": "Transaction",
"transactionlist": { "transactionlist": {
"amount": "Amount", "amount": "Amount",

View File

@ -1,6 +1,9 @@
export const creationMonths = { export const creationMonths = {
props: { props: {
creation: [1000, 1000, 1000], creation: {
type: Array,
default: () => [1000, 1000, 1000],
},
}, },
computed: { computed: {
creationDates() { creationDates() {
@ -31,5 +34,8 @@ export const creationMonths = {
} }
}) })
}, },
creationLabel() {
return this.creationDates.map((date) => this.$d(date, 'monthShort')).join(' | ')
},
}, },
} }

View File

@ -1,4 +1,4 @@
import { shallowMount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import Creation from './Creation.vue' import Creation from './Creation.vue'
const localVue = global.localVue const localVue = global.localVue
@ -14,6 +14,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: true,
}, },
{ {
userId: 2, userId: 2,
@ -21,6 +22,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
lastName: 'Blümchen', lastName: 'Blümchen',
email: 'benjamin@bluemchen.de', email: 'benjamin@bluemchen.de',
creation: [800, 600, 400], creation: [800, 600, 400],
emailChecked: true,
}, },
], ],
}, },
@ -51,10 +53,10 @@ describe('Creation', () => {
let wrapper let wrapper
const Wrapper = () => { const Wrapper = () => {
return shallowMount(Creation, { localVue, mocks }) return mount(Creation, { localVue, mocks })
} }
describe('shallowMount', () => { describe('mount', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks() jest.clearAllMocks()
wrapper = Wrapper() wrapper = Wrapper()
@ -77,64 +79,66 @@ describe('Creation', () => {
) )
}) })
it('sets the data of itemsList', () => { it('has two rows in the left table', () => {
expect(wrapper.vm.itemsList).toEqual([ expect(wrapper.findAll('table').at(0).findAll('tbody > tr')).toHaveLength(2)
{ })
userId: 1,
firstName: 'Bibi', it('has nwo rows in the right table', () => {
lastName: 'Bloxberg', expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(0)
email: 'bibi@bloxberg.de', })
creation: [200, 400, 600],
showDetails: false, it('has correct data in first row ', () => {
}, expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain('Bibi')
{ expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
userId: 2, 'Bloxberg',
firstName: 'Benjamin', )
lastName: 'Blümchen', expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
email: 'benjamin@bluemchen.de', '200 | 400 | 600',
creation: [800, 600, 400], )
showDetails: false, expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
}, 'bibi@bloxberg.de',
]) )
})
it('has correct data in second row ', () => {
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).text()).toContain(
'Benjamin',
)
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).text()).toContain(
'Blümchen',
)
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).text()).toContain(
'800 | 600 | 400',
)
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).text()).toContain(
'benjamin@bluemchen.de',
)
}) })
}) })
describe('push item', () => { describe('push item', () => {
beforeEach(() => { beforeEach(() => {
wrapper.findComponent({ name: 'UserTable' }).vm.$emit('push-item', { wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).find('button').trigger('click')
userId: 2,
firstName: 'Benjamin',
lastName: 'Blümchen',
email: 'benjamin@bluemchen.de',
creation: [800, 600, 400],
showDetails: false,
})
}) })
it('removes the pushed item from itemsList', () => { it('has one item in left table', () => {
expect(wrapper.vm.itemsList).toEqual([ expect(wrapper.findAll('table').at(0).findAll('tbody > tr')).toHaveLength(1)
{
userId: 1,
firstName: 'Bibi',
lastName: 'Bloxberg',
email: 'bibi@bloxberg.de',
creation: [200, 400, 600],
showDetails: false,
},
])
}) })
it('adds the pushed item to itemsMassCreation', () => { it('has one item in right table', () => {
expect(wrapper.vm.itemsMassCreation).toEqual([ expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(1)
{ })
userId: 2,
firstName: 'Benjamin', it('has the correct user in left table', () => {
lastName: 'Blümchen', expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
email: 'benjamin@bluemchen.de', 'bibi@bloxberg.de',
creation: [800, 600, 400], )
showDetails: false, })
},
]) it('has the correct user in right table', () => {
expect(wrapper.findAll('table').at(1).findAll('tbody > tr').at(0).text()).toContain(
'benjamin@bluemchen.de',
)
}) })
it('updates userSelectedInMassCreation in store', () => { it('updates userSelectedInMassCreation in store', () => {
@ -146,88 +150,58 @@ describe('Creation', () => {
email: 'benjamin@bluemchen.de', email: 'benjamin@bluemchen.de',
creation: [800, 600, 400], creation: [800, 600, 400],
showDetails: false, showDetails: false,
}, emailChecked: true,
])
})
})
describe('remove item', () => {
beforeEach(async () => {
await wrapper.findComponent({ name: 'UserTable' }).vm.$emit('push-item', {
userId: 2,
firstName: 'Benjamin',
lastName: 'Blümchen',
email: 'benjamin@bluemchen.de',
creation: [800, 600, 400],
showDetails: false,
})
await wrapper
.findAllComponents({ name: 'UserTable' })
.at(1)
.vm.$emit('remove-item', {
userId: 2,
firstName: 'Benjamin',
lastName: 'Blümchen',
email: 'benjamin@bluemchen.de',
creation: [800, 600, 400],
showDetails: false,
})
})
it('adds the removed item to itemsList', () => {
expect(wrapper.vm.itemsList).toEqual([
{
userId: 2,
firstName: 'Benjamin',
lastName: 'Blümchen',
email: 'benjamin@bluemchen.de',
creation: [800, 600, 400],
showDetails: false,
},
{
userId: 1,
firstName: 'Bibi',
lastName: 'Bloxberg',
email: 'bibi@bloxberg.de',
creation: [200, 400, 600],
showDetails: false,
}, },
]) ])
}) })
it('removes the item from itemsMassCreation', () => { describe('remove item', () => {
expect(wrapper.vm.itemsMassCreation).toEqual([]) beforeEach(async () => {
}) await wrapper
.findAll('table')
it('commits empty array as userSelectedInMassCreation', () => { .at(1)
expect(storeCommitMock).toBeCalledWith('setUserSelectedInMassCreation', []) .findAll('tbody > tr')
}) .at(0)
}) .find('button')
.trigger('click')
describe('remove all bookmarks', () => { })
beforeEach(async () => {
await wrapper.findComponent({ name: 'UserTable' }).vm.$emit('push-item', { it('has two items in left table', () => {
userId: 2, expect(wrapper.findAll('table').at(0).findAll('tbody > tr')).toHaveLength(2)
firstName: 'Benjamin', })
lastName: 'Blümchen',
email: 'benjamin@bluemchen.de', it('has the removed user in first row', () => {
creation: [800, 600, 400], expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
showDetails: false, 'benjamin@bluemchen.de',
)
})
it('has no items in right table', () => {
expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(0)
})
it('commits empty array as userSelectedInMassCreation', () => {
expect(storeCommitMock).toBeCalledWith('setUserSelectedInMassCreation', [])
}) })
jest.clearAllMocks()
wrapper.findComponent({ name: 'CreationFormular' }).vm.$emit('remove-all-bookmark')
}) })
it('removes all items from itemsMassCreation', () => { describe('remove all bookmarks', () => {
expect(wrapper.vm.itemsMassCreation).toEqual([]) beforeEach(async () => {
}) jest.clearAllMocks()
await wrapper.find('button.btn-light').trigger('click')
})
it('commits empty array to userSelectedInMassCreation', () => { it('has no items in right table', () => {
expect(storeCommitMock).toBeCalledWith('setUserSelectedInMassCreation', []) expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(0)
}) })
it('calls searchUsers', () => { it('commits empty array to userSelectedInMassCreation', () => {
expect(apolloQueryMock).toBeCalled() expect(storeCommitMock).toBeCalledWith('setUserSelectedInMassCreation', [])
})
it('calls searchUsers', () => {
expect(apolloQueryMock).toBeCalled()
})
}) })
}) })
@ -241,22 +215,24 @@ describe('Creation', () => {
email: 'benjamin@bluemchen.de', email: 'benjamin@bluemchen.de',
creation: [800, 600, 400], creation: [800, 600, 400],
showDetails: false, showDetails: false,
emailChecked: true,
}, },
] ]
wrapper = Wrapper() wrapper = Wrapper()
}) })
it('has only one item itemsList', () => { it('has one item in left table', () => {
expect(wrapper.vm.itemsList).toEqual([ expect(wrapper.findAll('table').at(0).findAll('tbody > tr')).toHaveLength(1)
{ })
userId: 1,
firstName: 'Bibi', it('has one item in right table', () => {
lastName: 'Bloxberg', expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(1)
email: 'bibi@bloxberg.de', })
creation: [200, 400, 600],
showDetails: false, it('has the stored user in second row', () => {
}, expect(wrapper.findAll('table').at(1).findAll('tbody > tr').at(0).text()).toContain(
]) 'benjamin@bluemchen.de',
)
}) })
}) })
@ -265,17 +241,38 @@ describe('Creation', () => {
jest.clearAllMocks() jest.clearAllMocks()
}) })
it('calls API when criteria changes', async () => { describe('search criteria', () => {
await wrapper.setData({ criteria: 'XX' }) beforeEach(async () => {
expect(apolloQueryMock).toBeCalledWith( await wrapper.setData({ criteria: 'XX' })
expect.objectContaining({ })
variables: {
searchText: 'XX', it('calls API when criteria changes', async () => {
currentPage: 1, expect(apolloQueryMock).toBeCalledWith(
pageSize: 25, expect.objectContaining({
}, variables: {
}), searchText: 'XX',
) currentPage: 1,
pageSize: 25,
},
}),
)
})
describe('reset search criteria', () => {
it('calls the API', async () => {
jest.clearAllMocks()
await wrapper.find('.test-click-clear-criteria').trigger('click')
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
variables: {
searchText: '',
currentPage: 1,
pageSize: 25,
},
}),
)
})
})
}) })
it('calls API when currentPage changes', async () => { it('calls API when currentPage changes', async () => {

View File

@ -3,19 +3,25 @@
<b-row> <b-row>
<b-col cols="12" lg="6"> <b-col cols="12" lg="6">
<label>Usersuche</label> <label>Usersuche</label>
<b-input <b-input-group>
type="text" <b-form-input
v-model="criteria" type="text"
class="shadow p-3 mb-5 bg-white rounded" class="test-input-criteria"
placeholder="User suche" v-model="criteria"
></b-input> :placeholder="$t('user_search')"
></b-form-input>
<b-input-group-append class="test-click-clear-criteria" @click="criteria = ''">
<b-input-group-text class="pointer">
<b-icon icon="x" />
</b-input-group-text>
</b-input-group-append>
</b-input-group>
<user-table <user-table
v-if="itemsList.length > 0" v-if="itemsList.length > 0"
type="UserListSearch" type="UserListSearch"
:itemsUser="itemsList" :itemsUser="itemsList"
:fieldsTable="Searchfields" :fieldsTable="Searchfields"
:criteria="criteria"
:creation="creation"
@push-item="pushItem" @push-item="pushItem"
/> />
<b-pagination <b-pagination
@ -27,16 +33,22 @@
></b-pagination> ></b-pagination>
</b-col> </b-col>
<b-col cols="12" lg="6" class="shadow p-3 mb-5 rounded bg-info"> <b-col cols="12" lg="6" class="shadow p-3 mb-5 rounded bg-info">
<user-table <div v-show="itemsMassCreation.length > 0">
v-show="itemsMassCreation.length > 0" <div class="text-right pr-4 mb-1">
class="shadow p-3 mb-5 bg-white rounded" <b-button @click="removeAllBookmarks()" variant="light">
type="UserListMassCreation" <b-icon icon="x" scale="2" variant="danger"></b-icon>
:itemsUser="itemsMassCreation"
:fieldsTable="fields" {{ $t('remove_all') }}
:criteria="null" </b-button>
:creation="creation" </div>
@remove-item="removeItem" <user-table
/> class="shadow p-3 mb-5 bg-white rounded"
type="UserListMassCreation"
:itemsUser="itemsMassCreation"
:fieldsTable="fields"
@remove-item="removeItem"
/>
</div>
<div v-if="itemsMassCreation.length === 0"> <div v-if="itemsMassCreation.length === 0">
{{ $t('multiple_creation_text') }} {{ $t('multiple_creation_text') }}
</div> </div>
@ -45,7 +57,7 @@
type="massCreation" type="massCreation"
:creation="creation" :creation="creation"
:items="itemsMassCreation" :items="itemsMassCreation"
@remove-all-bookmark="removeAllBookmark" @remove-all-bookmark="removeAllBookmarks"
/> />
</b-col> </b-col>
</b-row> </b-row>
@ -55,9 +67,11 @@
import CreationFormular from '../components/CreationFormular.vue' import CreationFormular from '../components/CreationFormular.vue'
import UserTable from '../components/UserTable.vue' import UserTable from '../components/UserTable.vue'
import { searchUsers } from '../graphql/searchUsers' import { searchUsers } from '../graphql/searchUsers'
import { creationMonths } from '../mixins/creationMonths'
export default { export default {
name: 'Creation', name: 'Creation',
mixins: [creationMonths],
components: { components: {
CreationFormular, CreationFormular,
UserTable, UserTable,
@ -69,7 +83,6 @@ export default {
itemsMassCreation: this.$store.state.userSelectedInMassCreation, itemsMassCreation: this.$store.state.userSelectedInMassCreation,
radioSelectedMass: '', radioSelectedMass: '',
criteria: '', criteria: '',
creation: [null, null, null],
rows: 0, rows: 0,
currentPage: 1, currentPage: 1,
perPage: 25, perPage: 25,
@ -126,7 +139,7 @@ export default {
) )
this.$store.commit('setUserSelectedInMassCreation', this.itemsMassCreation) this.$store.commit('setUserSelectedInMassCreation', this.itemsMassCreation)
}, },
removeAllBookmark() { removeAllBookmarks() {
this.itemsMassCreation = [] this.itemsMassCreation = []
this.$store.commit('setUserSelectedInMassCreation', []) this.$store.commit('setUserSelectedInMassCreation', [])
this.getUsers() this.getUsers()
@ -163,16 +176,6 @@ export default {
{ key: 'bookmark', label: this.$t('remove') }, { key: 'bookmark', label: this.$t('remove') },
] ]
}, },
creationLabel() {
const now = new Date(this.now)
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1)
const beforeLastMonth = new Date(now.getFullYear(), now.getMonth() - 2, 1)
return [
this.$d(beforeLastMonth, 'monthShort'),
this.$d(lastMonth, 'monthShort'),
this.$d(now, 'monthShort'),
].join(' | ')
},
}, },
watch: { watch: {
currentPage() { currentPage() {

View File

@ -78,6 +78,7 @@ 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 setOpenCreations to store', () => {
expect(storeCommitMock).toBeCalledWith('setOpenCreations', 2) expect(storeCommitMock).toBeCalledWith('setOpenCreations', 2)
}) })
@ -85,7 +86,7 @@ describe('CreationConfirm', () => {
describe('remove creation with success', () => { describe('remove creation with success', () => {
beforeEach(async () => { beforeEach(async () => {
await wrapper.findComponent({ name: 'UserTable' }).vm.$emit('remove-creation', { id: 1 }) await wrapper.findAll('tr').at(1).findAll('button').at(0).trigger('click')
}) })
it('calls the deletePendingCreation mutation', () => { it('calls the deletePendingCreation mutation', () => {
@ -107,7 +108,7 @@ describe('CreationConfirm', () => {
describe('remove creation with error', () => { describe('remove creation with error', () => {
beforeEach(async () => { beforeEach(async () => {
apolloMutateMock.mockRejectedValue({ message: 'Ouchhh!' }) apolloMutateMock.mockRejectedValue({ message: 'Ouchhh!' })
await wrapper.findComponent({ name: 'UserTable' }).vm.$emit('remove-creation', { id: 1 }) await wrapper.findAll('tr').at(1).findAll('button').at(0).trigger('click')
}) })
it('toasts an error message', () => { it('toasts an error message', () => {
@ -118,22 +119,52 @@ describe('CreationConfirm', () => {
describe('confirm creation with success', () => { describe('confirm creation with success', () => {
beforeEach(async () => { beforeEach(async () => {
apolloMutateMock.mockResolvedValue({}) apolloMutateMock.mockResolvedValue({})
await wrapper.findComponent({ name: 'UserTable' }).vm.$emit('confirm-creation', { id: 2 }) await wrapper.findAll('tr').at(2).findAll('button').at(2).trigger('click')
}) })
it('calls the confirmPendingCreation mutation', () => { describe('overlay', () => {
expect(apolloMutateMock).toBeCalledWith({ it('opens the overlay', () => {
mutation: confirmPendingCreation, expect(wrapper.find('#overlay').isVisible()).toBeTruthy()
variables: { id: 2 },
}) })
})
it('commits openCreationsMinus to store', () => { describe('cancel confirmation', () => {
expect(storeCommitMock).toBeCalledWith('openCreationsMinus', 1) beforeEach(async () => {
}) await wrapper.find('#overlay').findAll('button').at(0).trigger('click')
})
it('toasts a success message', () => { it('closes the overlay', () => {
expect(toastedSuccessMock).toBeCalledWith('creation_form.toasted_created') expect(wrapper.find('#overlay').isVisible()).toBeFalsy()
})
it('still has 2 items in the table', () => {
expect(wrapper.findAll('tbody > tr')).toHaveLength(2)
})
})
describe('confirm creation', () => {
beforeEach(async () => {
await wrapper.find('#overlay').findAll('button').at(1).trigger('click')
})
it('calls the confirmPendingCreation mutation', () => {
expect(apolloMutateMock).toBeCalledWith({
mutation: confirmPendingCreation,
variables: { id: 2 },
})
})
it('commits openCreationsMinus to store', () => {
expect(storeCommitMock).toBeCalledWith('openCreationsMinus', 1)
})
it('toasts a success message', () => {
expect(toastedSuccessMock).toBeCalledWith('creation_form.toasted_created')
})
it('has 1 item left in the table', () => {
expect(wrapper.findAll('tbody > tr')).toHaveLength(1)
})
})
}) })
}) })

View File

@ -9,10 +9,35 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
userCount: 1, userCount: 1,
userList: [ userList: [
{ {
userId: 1,
firstName: 'Bibi', firstName: 'Bibi',
lastName: 'Bloxberg', lastName: 'Bloxberg',
email: 'bibi@bloxberg.de', email: 'bibi@bloxberg.de',
creation: [200, 400, 600], creation: [200, 400, 600],
emailChecked: true,
},
{
userId: 2,
firstName: 'Benjamin',
lastName: 'Blümchen',
email: 'benjamin@bluemchen.de',
creation: [1000, 1000, 1000],
emailChecked: true,
},
{
userId: 3,
firstName: 'Peter',
lastName: 'Lustig',
email: 'peter@lustig.de',
creation: [0, 0, 0],
emailChecked: true,
},
{
userId: 4,
firstName: 'New',
lastName: 'User',
email: 'new@user.ch',
creation: [1000, 1000, 1000],
emailChecked: false, emailChecked: false,
}, },
], ],
@ -24,7 +49,7 @@ const toastErrorMock = jest.fn()
const mocks = { const mocks = {
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$d: jest.fn((d) => d), $d: jest.fn((d) => String(d)),
$apollo: { $apollo: {
query: apolloQueryMock, query: apolloQueryMock,
}, },
@ -42,6 +67,7 @@ describe('UserSearch', () => {
describe('mount', () => { describe('mount', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks()
wrapper = Wrapper() wrapper = Wrapper()
}) })
@ -49,13 +75,90 @@ describe('UserSearch', () => {
expect(wrapper.find('div.user-search').exists()).toBeTruthy() expect(wrapper.find('div.user-search').exists()).toBeTruthy()
}) })
it('calls the API', () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
variables: {
searchText: '',
currentPage: 1,
pageSize: 25,
notActivated: false,
},
}),
)
})
describe('unconfirmed emails', () => { describe('unconfirmed emails', () => {
beforeEach(async () => { beforeEach(async () => {
await wrapper.find('button.btn-block').trigger('click') await wrapper.find('button.btn-block').trigger('click')
}) })
it('filters the users by unconfirmed emails', () => { it('calls API with filter', () => {
expect(wrapper.vm.searchResult).toHaveLength(1) expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
variables: {
searchText: '',
currentPage: 1,
pageSize: 25,
notActivated: true,
},
}),
)
})
})
describe('pagination', () => {
beforeEach(async () => {
wrapper.setData({ currentPage: 2 })
})
it('calls the API with new page', () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
variables: {
searchText: '',
currentPage: 2,
pageSize: 25,
notActivated: false,
},
}),
)
})
})
describe('user search', () => {
beforeEach(async () => {
wrapper.setData({ criteria: 'search string' })
})
it('calls the API with search string', () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
variables: {
searchText: 'search string',
currentPage: 1,
pageSize: 25,
notActivated: false,
},
}),
)
})
describe('reset the search field', () => {
it('calls the API with empty criteria', async () => {
jest.clearAllMocks()
await wrapper.find('.test-click-clear-criteria').trigger('click')
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
variables: {
searchText: '',
currentPage: 1,
pageSize: 25,
notActivated: false,
},
}),
)
})
}) })
}) })

View File

@ -7,20 +7,22 @@
</b-button> </b-button>
</div> </div>
<label>{{ $t('user_search') }}</label> <label>{{ $t('user_search') }}</label>
<b-input <div>
type="text" <b-input-group>
v-model="criteria" <b-form-input
class="shadow p-3 mb-3 bg-white rounded" type="text"
:placeholder="$t('user_search')" class="test-input-criteria"
@input="getUsers" v-model="criteria"
></b-input> :placeholder="$t('user_search')"
></b-form-input>
<user-table <b-input-group-append class="test-click-clear-criteria" @click="criteria = ''">
type="PageUserSearch" <b-input-group-text class="pointer">
:itemsUser="searchResult" <b-icon icon="x" />
:fieldsTable="fields" </b-input-group-text>
:criteria="criteria" </b-input-group-append>
/> </b-input-group>
</div>
<user-table type="PageUserSearch" :itemsUser="searchResult" :fieldsTable="fields" />
<b-pagination <b-pagination
pills pills
size="lg" size="lg"
@ -35,9 +37,11 @@
<script> <script>
import UserTable from '../components/UserTable.vue' import UserTable from '../components/UserTable.vue'
import { searchUsers } from '../graphql/searchUsers' import { searchUsers } from '../graphql/searchUsers'
import { creationMonths } from '../mixins/creationMonths'
export default { export default {
name: 'UserSearch', name: 'UserSearch',
mixins: [creationMonths],
components: { components: {
UserTable, UserTable,
}, },
@ -83,16 +87,11 @@ export default {
currentPage() { currentPage() {
this.getUsers() this.getUsers()
}, },
criteria() {
this.getUsers()
},
}, },
computed: { computed: {
lastMonthDate() {
const now = new Date(this.now)
return new Date(now.getFullYear(), now.getMonth() - 1, 1)
},
beforeLastMonthDate() {
const now = new Date(this.now)
return new Date(now.getFullYear(), now.getMonth() - 2, 1)
},
fields() { fields() {
return [ return [
{ key: 'email', label: this.$t('e_mail') }, { key: 'email', label: this.$t('e_mail') },
@ -100,11 +99,7 @@ export default {
{ key: 'lastName', label: this.$t('lastname') }, { key: 'lastName', label: this.$t('lastname') },
{ {
key: 'creation', key: 'creation',
label: [ label: this.creationLabel,
this.$d(this.beforeLastMonthDate, 'monthShort'),
this.$d(this.lastMonthDate, 'monthShort'),
this.$d(this.now, 'monthShort'),
].join(' | '),
formatter: (value, key, item) => { formatter: (value, key, item) => {
return value.join(' | ') return value.join(' | ')
}, },

View File

@ -7,5 +7,4 @@ export const INALIENABLE_RIGHTS = [
RIGHTS.CREATE_USER, RIGHTS.CREATE_USER,
RIGHTS.SEND_RESET_PASSWORD_EMAIL, RIGHTS.SEND_RESET_PASSWORD_EMAIL,
RIGHTS.SET_PASSWORD, RIGHTS.SET_PASSWORD,
RIGHTS.CHECK_USERNAME,
] ]

View File

@ -17,7 +17,6 @@ export enum RIGHTS {
SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL', SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL',
SET_PASSWORD = 'SET_PASSWORD', SET_PASSWORD = 'SET_PASSWORD',
UPDATE_USER_INFOS = 'UPDATE_USER_INFOS', UPDATE_USER_INFOS = 'UPDATE_USER_INFOS',
CHECK_USERNAME = 'CHECK_USERNAME',
HAS_ELOPAGE = 'HAS_ELOPAGE', HAS_ELOPAGE = 'HAS_ELOPAGE',
// Admin // Admin
SEARCH_USERS = 'SEARCH_USERS', SEARCH_USERS = 'SEARCH_USERS',

View File

@ -4,7 +4,7 @@ import dotenv from 'dotenv'
dotenv.config() dotenv.config()
const constants = { const constants = {
DB_VERSION: '0016-transaction_signatures', DB_VERSION: '0020-rename_and_clean_state_users',
} }
const server = { const server = {

View File

@ -1,7 +0,0 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export default class CheckUsernameArgs {
@Field(() => String)
username: string
}

View File

@ -8,12 +8,6 @@ export default class UpdateUserInfosArgs {
@Field({ nullable: true }) @Field({ nullable: true })
lastName?: string lastName?: string
@Field({ nullable: true })
description?: string
@Field({ nullable: true })
username?: string
@Field({ nullable: true }) @Field({ nullable: true })
language?: string language?: string

View File

@ -16,8 +16,6 @@ export class User {
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
this.username = json.username
this.description = json.description
this.pubkey = json.public_hex this.pubkey = json.public_hex
this.language = json.language this.language = json.language
this.publisherId = json.publisher_id this.publisherId = json.publisher_id
@ -37,12 +35,6 @@ export class User {
@Field(() => String) @Field(() => String)
lastName: string lastName: string
@Field(() => String, { nullable: true })
username?: string
@Field(() => String, { nullable: true })
description?: string
@Field(() => String) @Field(() => String)
pubkey: string pubkey: string
/* /*
@ -55,9 +47,6 @@ export class User {
@Field(() =>>> Boolean) @Field(() =>>> Boolean)
emailChecked: boolean emailChecked: boolean
@Field(() => Boolean)
passphraseShown: boolean
*/ */
@Field(() => String) @Field(() => String)
@ -68,10 +57,6 @@ export class User {
disabled: boolean disabled: boolean
*/ */
/* I suggest to have a group as type here
@Field(() => ID)
groupId: number
*/
// what is publisherId? // what is publisherId?
@Field(() => Int, { nullable: true }) @Field(() => Int, { nullable: true })
publisherId?: number publisherId?: number

View File

@ -21,8 +21,8 @@ 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'
import { AdminPendingCreation } from '@entity/AdminPendingCreation' import { AdminPendingCreation } from '@entity/AdminPendingCreation'
import { User as dbUser } from '@entity/User'
@Resolver() @Resolver()
export class AdminResolver { export class AdminResolver {
@ -378,7 +378,6 @@ function isCreationValid(creations: number[], amount: number, creationDate: Date
} }
async function hasActivatedEmail(email: string): Promise<boolean> { async function hasActivatedEmail(email: string): Promise<boolean> {
const repository = getCustomRepository(LoginUserRepository) const user = await dbUser.findOne({ email })
const user = await repository.findByEmail(email)
return user ? user.emailChecked : false return user ? user.emailChecked : false
} }

View File

@ -33,7 +33,6 @@ import { calculateDecay, calculateDecayWithInterval } from '../../util/decay'
import { TransactionTypeId } from '../enum/TransactionTypeId' import { TransactionTypeId } from '../enum/TransactionTypeId'
import { TransactionType } from '../enum/TransactionType' import { TransactionType } from '../enum/TransactionType'
import { hasUserAmount, isHexPublicKey } from '../../util/validate' import { hasUserAmount, isHexPublicKey } from '../../util/validate'
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
import { RIGHTS } from '../../auth/RIGHTS' import { RIGHTS } from '../../auth/RIGHTS'
// Helper function // Helper function
@ -290,14 +289,13 @@ async function addUserTransaction(
} }
async function getPublicKey(email: string): Promise<string | null> { async function getPublicKey(email: string): Promise<string | null> {
const loginUserRepository = getCustomRepository(LoginUserRepository) const user = await dbUser.findOne({ email: email })
const loginUser = await loginUserRepository.findOne({ email: email })
// User not found // User not found
if (!loginUser) { if (!user) {
return null return null
} }
return loginUser.pubKey.toString('hex') return user.pubKey.toString('hex')
} }
@Resolver() @Resolver()
@ -364,7 +362,7 @@ export class TransactionResolver {
// validate sender user (logged in) // validate sender user (logged in)
const userRepository = getCustomRepository(UserRepository) const userRepository = getCustomRepository(UserRepository)
const senderUser = await userRepository.findByPubkeyHex(context.pubKey) const senderUser = await userRepository.findByPubkeyHex(context.pubKey)
if (senderUser.pubkey.length !== 32) { if (senderUser.pubKey.length !== 32) {
throw new Error('invalid sender public key') throw new Error('invalid sender public key')
} }
if (!hasUserAmount(senderUser, amount)) { if (!hasUserAmount(senderUser, amount)) {
@ -454,7 +452,7 @@ export class TransactionResolver {
const transactionSendCoin = new dbTransactionSendCoin() const transactionSendCoin = new dbTransactionSendCoin()
transactionSendCoin.transactionId = transaction.id transactionSendCoin.transactionId = transaction.id
transactionSendCoin.userId = senderUser.id transactionSendCoin.userId = senderUser.id
transactionSendCoin.senderPublic = senderUser.pubkey transactionSendCoin.senderPublic = senderUser.pubKey
transactionSendCoin.recipiantUserId = recipiantUser.id transactionSendCoin.recipiantUserId = recipiantUser.id
transactionSendCoin.recipiantPublic = Buffer.from(recipiantPublicKey, 'hex') transactionSendCoin.recipiantPublic = Buffer.from(recipiantPublicKey, 'hex')
transactionSendCoin.amount = centAmount transactionSendCoin.amount = centAmount

View File

@ -8,17 +8,13 @@ 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'
import { encode } from '../../auth/JWT' import { encode } from '../../auth/JWT'
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 { klicktippNewsletterStateMiddleware } from '../../middleware/klicktippMiddleware' import { klicktippNewsletterStateMiddleware } from '../../middleware/klicktippMiddleware'
import { UserSettingRepository } from '../../typeorm/repository/UserSettingRepository' import { UserSettingRepository } from '../../typeorm/repository/UserSettingRepository'
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
import { Setting } from '../enum/Setting' import { Setting } from '../enum/Setting'
import { UserRepository } from '../../typeorm/repository/User' import { UserRepository } from '../../typeorm/repository/User'
import { LoginUser } from '@entity/LoginUser'
import { LoginUserBackup } from '@entity/LoginUserBackup'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { sendResetPasswordEmail } from '../../mailer/sendResetPasswordEmail' import { sendResetPasswordEmail } from '../../mailer/sendResetPasswordEmail'
import { sendAccountActivationEmail } from '../../mailer/sendAccountActivationEmail' import { sendAccountActivationEmail } from '../../mailer/sendAccountActivationEmail'
@ -27,7 +23,6 @@ import { klicktippSignIn } 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'
import { randomBytes } from 'crypto'
const EMAIL_OPT_IN_RESET_PASSWORD = 2 const EMAIL_OPT_IN_RESET_PASSWORD = 2
const EMAIL_OPT_IN_REGISTER = 1 const EMAIL_OPT_IN_REGISTER = 1
@ -186,10 +181,10 @@ const createEmailOptIn = async (
return emailOptIn return emailOptIn
} }
const getOptInCode = async (loginUser: LoginUser): Promise<LoginEmailOptIn> => { const getOptInCode = async (loginUserId: number): Promise<LoginEmailOptIn> => {
const loginEmailOptInRepository = await getRepository(LoginEmailOptIn) const loginEmailOptInRepository = await getRepository(LoginEmailOptIn)
let optInCode = await loginEmailOptInRepository.findOne({ let optInCode = await loginEmailOptInRepository.findOne({
userId: loginUser.id, userId: loginUserId,
emailOptInTypeId: EMAIL_OPT_IN_RESET_PASSWORD, emailOptInTypeId: EMAIL_OPT_IN_RESET_PASSWORD,
}) })
@ -207,7 +202,7 @@ const getOptInCode = async (loginUser: LoginUser): Promise<LoginEmailOptIn> => {
} else { } else {
optInCode = new LoginEmailOptIn() optInCode = new LoginEmailOptIn()
optInCode.verificationCode = random(64) optInCode.verificationCode = random(64)
optInCode.userId = loginUser.id optInCode.userId = loginUserId
optInCode.emailOptInTypeId = EMAIL_OPT_IN_RESET_PASSWORD optInCode.emailOptInTypeId = EMAIL_OPT_IN_RESET_PASSWORD
} }
await loginEmailOptInRepository.save(optInCode) await loginEmailOptInRepository.save(optInCode)
@ -223,17 +218,13 @@ export class UserResolver {
// TODO refactor and do not have duplicate code with login(see below) // TODO refactor and do not have duplicate code with login(see below)
const userRepository = getCustomRepository(UserRepository) const userRepository = getCustomRepository(UserRepository)
const userEntity = await userRepository.findByPubkeyHex(context.pubKey) const userEntity = await userRepository.findByPubkeyHex(context.pubKey)
const loginUserRepository = getCustomRepository(LoginUserRepository)
const loginUser = await loginUserRepository.findByEmail(userEntity.email)
const user = new User() const user = new User()
user.id = userEntity.id 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
user.username = userEntity.username user.pubkey = userEntity.pubKey.toString('hex')
user.description = loginUser.description user.language = userEntity.language
user.pubkey = userEntity.pubkey.toString('hex')
user.language = loginUser.language
// Elopage Status & Stored PublisherId // Elopage Status & Stored PublisherId
user.hasElopage = await this.hasElopage(context) user.hasElopage = await this.hasElopage(context)
@ -259,76 +250,48 @@ export class UserResolver {
@Ctx() context: any, @Ctx() context: any,
): Promise<User> { ): Promise<User> {
email = email.trim().toLowerCase() email = email.trim().toLowerCase()
const loginUserRepository = getCustomRepository(LoginUserRepository) const dbUser = await DbUser.findOneOrFail({ 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) { if (!dbUser.emailChecked) {
throw new Error('User email not validated') throw new Error('User email not validated')
} }
if (loginUser.password === BigInt(0)) { if (dbUser.password === BigInt(0)) {
// TODO we want to catch this on the frontend and ask the user to check his emails or resend code // 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') throw new Error('User has no password set yet')
} }
if (!loginUser.pubKey || !loginUser.privKey) { if (!dbUser.pubKey || !dbUser.privKey) {
// TODO we want to catch this on the frontend and ask the user to check his emails or resend code // 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') 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(dbUser.password.toString())
if (loginUserPassword !== passwordHash[0].readBigUInt64LE()) { if (loginUserPassword !== passwordHash[0].readBigUInt64LE()) {
throw new Error('No user with this credentials') throw new Error('No user with this credentials')
} }
// TODO: If user has no pubKey Create it again and update user.
const userRepository = getCustomRepository(UserRepository)
let userEntity: void | DbUser
const loginUserPubKey = loginUser.pubKey
const loginUserPubKeyString = loginUserPubKey.toString('hex')
userEntity = await userRepository.findByPubkeyHex(loginUserPubKeyString).catch(() => {
// User not stored in state_users
// TODO: Check with production data - email is unique which can cause problems
userEntity = new DbUser()
userEntity.firstName = loginUser.firstName
userEntity.lastName = loginUser.lastName
userEntity.username = loginUser.username
userEntity.email = loginUser.email
userEntity.pubkey = loginUser.pubKey
userRepository.save(userEntity).catch(() => {
throw new Error('error by save userEntity')
})
})
if (!userEntity) {
throw new Error('error with cannot happen')
}
const user = new User() const user = new User()
user.id = userEntity.id user.id = dbUser.id
user.email = email user.email = email
user.firstName = loginUser.firstName user.firstName = dbUser.firstName
user.lastName = loginUser.lastName user.lastName = dbUser.lastName
user.username = loginUser.username user.pubkey = dbUser.pubKey.toString('hex')
user.description = loginUser.description user.language = dbUser.language
user.pubkey = loginUserPubKeyString
user.language = loginUser.language
// Elopage Status & Stored PublisherId // Elopage Status & Stored PublisherId
user.hasElopage = await this.hasElopage({ pubKey: loginUserPubKeyString }) user.hasElopage = await this.hasElopage({ pubKey: dbUser.pubKey.toString('hex') })
if (!user.hasElopage && publisherId) { if (!user.hasElopage && publisherId) {
user.publisherId = publisherId user.publisherId = publisherId
// TODO: Check if we can use updateUserInfos // TODO: Check if we can use updateUserInfos
// await this.updateUserInfos({ publisherId }, { pubKey: loginUser.pubKey }) // await this.updateUserInfos({ publisherId }, { pubKey: loginUser.pubKey })
const loginUserRepository = getCustomRepository(LoginUserRepository) dbUser.publisherId = publisherId
const loginUser = await loginUserRepository.findOneOrFail({ email: userEntity.email }) DbUser.save(dbUser)
loginUser.publisherId = publisherId
loginUserRepository.save(loginUser)
} }
// coinAnimation // coinAnimation
const userSettingRepository = getCustomRepository(UserSettingRepository) const userSettingRepository = getCustomRepository(UserSettingRepository)
const coinanimation = await userSettingRepository const coinanimation = await userSettingRepository
.readBoolean(userEntity.id, Setting.COIN_ANIMATION) .readBoolean(dbUser.id, Setting.COIN_ANIMATION)
.catch((error) => { .catch((error) => {
throw new Error(error) throw new Error(error)
}) })
@ -341,7 +304,7 @@ export class UserResolver {
context.setHeaders.push({ context.setHeaders.push({
key: 'token', key: 'token',
value: encode(loginUser.pubKey), value: encode(dbUser.pubKey),
}) })
return user return user
@ -371,13 +334,6 @@ export class UserResolver {
language = DEFAULT_LANGUAGE language = DEFAULT_LANGUAGE
} }
// Validate username
// TODO: never true
const username = ''
if (username.length > 3 && !this.checkUsername({ username })) {
throw new Error('Username already in use')
}
// Validate email unique // Validate email unique
// TODO: i can register an email in upper/lower case twice // TODO: i can register an email in upper/lower case twice
const userRepository = getCustomRepository(UserRepository) const userRepository = getCustomRepository(UserRepository)
@ -393,18 +349,18 @@ export class UserResolver {
// const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1]) // const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
const emailHash = getEmailHash(email) const emailHash = getEmailHash(email)
// Table: login_users const dbUser = new DbUser()
const loginUser = new LoginUser() dbUser.email = email
loginUser.email = email dbUser.firstName = firstName
loginUser.firstName = firstName dbUser.lastName = lastName
loginUser.lastName = lastName dbUser.emailHash = emailHash
loginUser.username = username dbUser.language = language
loginUser.description = '' dbUser.publisherId = publisherId
dbUser.passphrase = passphrase.join(' ')
// TODO this field has no null allowed unlike the loginServer table
// dbUser.pubKey = Buffer.from(randomBytes(32)) // Buffer.alloc(32, 0) default to 0000...
// dbUser.pubkey = keyPair[0]
// loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash // loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
loginUser.emailHash = emailHash
loginUser.language = language
loginUser.groupId = 1
loginUser.publisherId = publisherId
// loginUser.pubKey = keyPair[0] // loginUser.pubKey = keyPair[0]
// loginUser.privKey = encryptedPrivkey // loginUser.privKey = encryptedPrivkey
@ -412,43 +368,15 @@ export class UserResolver {
await queryRunner.connect() await queryRunner.connect()
await queryRunner.startTransaction('READ UNCOMMITTED') await queryRunner.startTransaction('READ UNCOMMITTED')
try { try {
const { id: loginUserId } = await queryRunner.manager.save(loginUser).catch((error) => { await queryRunner.manager.save(dbUser).catch((error) => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('insert LoginUser failed', error) console.log('Error while saving dbUser', error)
throw new Error('insert user failed')
})
// Table: login_user_backups
const loginUserBackup = new LoginUserBackup()
loginUserBackup.userId = loginUserId
loginUserBackup.passphrase = passphrase.join(' ') // login server saves trailing space
loginUserBackup.mnemonicType = 2 // ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER;
await queryRunner.manager.save(loginUserBackup).catch((error) => {
// eslint-disable-next-line no-console
console.log('insert LoginUserBackup failed', error)
throw new Error('insert user backup failed')
})
// Table: state_users
const dbUser = new DbUser()
dbUser.email = email
dbUser.firstName = firstName
dbUser.lastName = lastName
dbUser.username = username
// TODO this field has no null allowed unlike the loginServer table
dbUser.pubkey = Buffer.from(randomBytes(32)) // Buffer.alloc(32, 0) default to 0000...
// dbUser.pubkey = keyPair[0]
await queryRunner.manager.save(dbUser).catch((er) => {
// eslint-disable-next-line no-console
console.log('Error while saving dbUser', er)
throw new Error('error saving user') throw new Error('error saving user')
}) })
// Store EmailOptIn in DB // Store EmailOptIn in DB
// TODO: this has duplicate code with sendResetPasswordEmail // TODO: this has duplicate code with sendResetPasswordEmail
const emailOptIn = await createEmailOptIn(loginUserId, queryRunner) const emailOptIn = await createEmailOptIn(dbUser.id, queryRunner)
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace( const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
/{code}/g, /{code}/g,
@ -480,15 +408,14 @@ export class UserResolver {
@Authorized([RIGHTS.SEND_ACTIVATION_EMAIL]) @Authorized([RIGHTS.SEND_ACTIVATION_EMAIL])
@Mutation(() => Boolean) @Mutation(() => Boolean)
async sendActivationEmail(@Arg('email') email: string): Promise<boolean> { async sendActivationEmail(@Arg('email') email: string): Promise<boolean> {
const loginUserRepository = getCustomRepository(LoginUserRepository) const user = await DbUser.findOneOrFail({ email: email })
const loginUser = await loginUserRepository.findOneOrFail({ email: email })
const queryRunner = getConnection().createQueryRunner() const queryRunner = getConnection().createQueryRunner()
await queryRunner.connect() await queryRunner.connect()
await queryRunner.startTransaction('READ UNCOMMITTED') await queryRunner.startTransaction('READ UNCOMMITTED')
try { try {
const emailOptIn = await createEmailOptIn(loginUser.id, queryRunner) const emailOptIn = await createEmailOptIn(user.id, queryRunner)
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace( const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
/{code}/g, /{code}/g,
@ -497,8 +424,8 @@ export class UserResolver {
const emailSent = await sendAccountActivationEmail({ const emailSent = await sendAccountActivationEmail({
link: activationLink, link: activationLink,
firstName: loginUser.firstName, firstName: user.firstName,
lastName: loginUser.lastName, lastName: user.lastName,
email, email,
}) })
@ -522,10 +449,9 @@ export class UserResolver {
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 user = await DbUser.findOneOrFail({ email })
const loginUser = await loginUserRepository.findOneOrFail({ email })
const optInCode = await getOptInCode(loginUser) const optInCode = await getOptInCode(user.id)
const link = CONFIG.EMAIL_LINK_SETPASSWORD.replace( const link = CONFIG.EMAIL_LINK_SETPASSWORD.replace(
/{code}/g, /{code}/g,
@ -534,8 +460,8 @@ export class UserResolver {
const emailSent = await sendResetPasswordEmail({ const emailSent = await sendResetPasswordEmail({
link, link,
firstName: loginUser.firstName, firstName: user.firstName,
lastName: loginUser.lastName, lastName: user.lastName,
email, email,
}) })
@ -575,34 +501,18 @@ export class UserResolver {
throw new Error('Code is older than 10 minutes') throw new Error('Code is older than 10 minutes')
} }
// 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 // load user
const dbUserRepository = await getCustomRepository(UserRepository) const user = await DbUser.findOneOrFail({ id: optInCode.userId }).catch(() => {
const dbUser = await dbUserRepository.findOneOrFail({ email: loginUser.email }).catch(() => { throw new Error('Could not find corresponding Login User')
throw new Error('Could not find corresponding User')
}) })
const loginUserBackupRepository = await getRepository(LoginUserBackup)
let loginUserBackup = await loginUserBackupRepository.findOne({ userId: loginUser.id })
// Generate Passphrase if needed // Generate Passphrase if needed
if (!loginUserBackup) { if (!user.passphrase) {
const passphrase = PassphraseGenerate() const passphrase = PassphraseGenerate()
loginUserBackup = new LoginUserBackup() user.passphrase = passphrase.join(' ')
loginUserBackup.userId = loginUser.id
loginUserBackup.passphrase = passphrase.join(' ') // login server saves trailing space
loginUserBackup.mnemonicType = 2 // ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER;
loginUserBackupRepository.save(loginUserBackup)
} }
const passphrase = loginUserBackup.passphrase.split(' ') const passphrase = user.passphrase.split(' ')
if (passphrase.length < PHRASE_WORD_COUNT) { if (passphrase.length < PHRASE_WORD_COUNT) {
// TODO if this can happen we cannot recover from that // TODO if this can happen we cannot recover from that
// this seem to be good on production data, if we dont // this seem to be good on production data, if we dont
@ -611,29 +521,23 @@ export class UserResolver {
} }
// Activate EMail // Activate EMail
loginUser.emailChecked = true user.emailChecked = true
// Update Password // Update Password
const passwordHash = SecretKeyCryptographyCreateKey(loginUser.email, password) // return short and long hash const passwordHash = SecretKeyCryptographyCreateKey(user.email, password) // return short and long hash
const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1]) const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash user.password = passwordHash[0].readBigUInt64LE() // using the shorthash
loginUser.pubKey = keyPair[0] user.pubKey = keyPair[0]
loginUser.privKey = encryptedPrivkey user.privKey = encryptedPrivkey
dbUser.pubkey = keyPair[0]
const queryRunner = getConnection().createQueryRunner() const queryRunner = getConnection().createQueryRunner()
await queryRunner.connect() await queryRunner.connect()
await queryRunner.startTransaction('READ UNCOMMITTED') await queryRunner.startTransaction('READ UNCOMMITTED')
try { try {
// Save loginUser
await queryRunner.manager.save(loginUser).catch((error) => {
throw new Error('error saving loginUser: ' + error)
})
// Save user // Save user
await queryRunner.manager.save(dbUser).catch((error) => { await queryRunner.manager.save(user).catch((error) => {
throw new Error('error saving user: ' + error) throw new Error('error saving user: ' + error)
}) })
@ -654,12 +558,7 @@ export class UserResolver {
// TODO do we always signUp the user? How to handle things with old users? // TODO do we always signUp the user? How to handle things with old users?
if (optInCode.emailOptInTypeId === EMAIL_OPT_IN_REGISTER) { if (optInCode.emailOptInTypeId === EMAIL_OPT_IN_REGISTER) {
try { try {
await klicktippSignIn( await klicktippSignIn(user.email, user.language, user.firstName, user.lastName)
loginUser.email,
loginUser.language,
loginUser.firstName,
loginUser.lastName,
)
} catch { } catch {
// TODO is this a problem? // TODO is this a problem?
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -677,8 +576,6 @@ export class UserResolver {
{ {
firstName, firstName,
lastName, lastName,
description,
username,
language, language,
publisherId, publisherId,
password, password,
@ -689,61 +586,42 @@ export class UserResolver {
): Promise<boolean> { ): Promise<boolean> {
const userRepository = getCustomRepository(UserRepository) const userRepository = getCustomRepository(UserRepository)
const userEntity = await userRepository.findByPubkeyHex(context.pubKey) const userEntity = await userRepository.findByPubkeyHex(context.pubKey)
const loginUserRepository = getCustomRepository(LoginUserRepository)
const loginUser = await loginUserRepository.findOneOrFail({ email: userEntity.email })
if (username) {
throw new Error('change username currently not supported!')
// TODO: this error was thrown on login_server whenever you tried to change the username
// to anything except "" which is an exception to the rules below. Those were defined
// aswell, even tho never used.
// ^[a-zA-Z][a-zA-Z0-9_-]*$
// username must start with [a-z] or [A-Z] and than can contain also [0-9], - and _
// username already used
// userEntity.username = username
}
if (firstName) { if (firstName) {
loginUser.firstName = firstName
userEntity.firstName = firstName userEntity.firstName = firstName
} }
if (lastName) { if (lastName) {
loginUser.lastName = lastName
userEntity.lastName = lastName userEntity.lastName = lastName
} }
if (description) {
loginUser.description = description
}
if (language) { if (language) {
if (!isLanguage(language)) { if (!isLanguage(language)) {
throw new Error(`"${language}" isn't a valid language`) throw new Error(`"${language}" isn't a valid language`)
} }
loginUser.language = language userEntity.language = language
} }
if (password && passwordNew) { if (password && passwordNew) {
// TODO: This had some error cases defined - like missing private key. This is no longer checked. // TODO: This had some error cases defined - like missing private key. This is no longer checked.
const oldPasswordHash = SecretKeyCryptographyCreateKey(loginUser.email, password) const oldPasswordHash = SecretKeyCryptographyCreateKey(userEntity.email, password)
if (BigInt(loginUser.password.toString()) !== oldPasswordHash[0].readBigUInt64LE()) { if (BigInt(userEntity.password.toString()) !== oldPasswordHash[0].readBigUInt64LE()) {
throw new Error(`Old password is invalid`) throw new Error(`Old password is invalid`)
} }
const privKey = SecretKeyCryptographyDecrypt(loginUser.privKey, oldPasswordHash[1]) const privKey = SecretKeyCryptographyDecrypt(userEntity.privKey, oldPasswordHash[1])
const newPasswordHash = SecretKeyCryptographyCreateKey(loginUser.email, passwordNew) // return short and long hash const newPasswordHash = SecretKeyCryptographyCreateKey(userEntity.email, passwordNew) // return short and long hash
const encryptedPrivkey = SecretKeyCryptographyEncrypt(privKey, newPasswordHash[1]) const encryptedPrivkey = SecretKeyCryptographyEncrypt(privKey, newPasswordHash[1])
// Save new password hash and newly encrypted private key // Save new password hash and newly encrypted private key
loginUser.password = newPasswordHash[0].readBigUInt64LE() userEntity.password = newPasswordHash[0].readBigUInt64LE()
loginUser.privKey = encryptedPrivkey userEntity.privKey = encryptedPrivkey
} }
// Save publisherId only if Elopage is not yet registered // Save publisherId only if Elopage is not yet registered
if (publisherId && !(await this.hasElopage(context))) { if (publisherId && !(await this.hasElopage(context))) {
loginUser.publisherId = publisherId userEntity.publisherId = publisherId
} }
const queryRunner = getConnection().createQueryRunner() const queryRunner = getConnection().createQueryRunner()
@ -760,10 +638,6 @@ export class UserResolver {
}) })
} }
await queryRunner.manager.save(loginUser).catch((error) => {
throw new Error('error saving loginUser: ' + error)
})
await queryRunner.manager.save(userEntity).catch((error) => { await queryRunner.manager.save(userEntity).catch((error) => {
throw new Error('error saving user: ' + error) throw new Error('error saving user: ' + error)
}) })
@ -779,30 +653,6 @@ export class UserResolver {
return true return true
} }
@Authorized([RIGHTS.CHECK_USERNAME])
@Query(() => Boolean)
async checkUsername(@Args() { username }: CheckUsernameArgs): Promise<boolean> {
// Username empty?
if (username === '') {
throw new Error('Username must be set.')
}
// Do we fullfil the minimum character length?
const MIN_CHARACTERS_USERNAME = 2
if (username.length < MIN_CHARACTERS_USERNAME) {
throw new Error(`Username must be at minimum ${MIN_CHARACTERS_USERNAME} characters long.`)
}
const usersFound = await LoginUser.count({ username })
// Username already present?
if (usersFound !== 0) {
throw new Error(`Username "${username}" already taken.`)
}
return true
}
@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> {

View File

@ -1,24 +0,0 @@
import { EntityRepository, Repository } from '@dbTools/typeorm'
import { LoginUser } from '@entity/LoginUser'
@EntityRepository(LoginUser)
export class LoginUserRepository extends Repository<LoginUser> {
async findByEmail(email: string): Promise<LoginUser> {
return this.createQueryBuilder('loginUser')
.where('loginUser.email = :email', { email })
.getOneOrFail()
}
async findBySearchCriteria(searchCriteria: string): Promise<LoginUser[]> {
return await this.createQueryBuilder('user')
.where(
'user.firstName like :name or user.lastName like :lastName or user.email like :email',
{
name: `%${searchCriteria}%`,
lastName: `%${searchCriteria}%`,
email: `%${searchCriteria}%`,
},
)
.getMany()
}
}

View File

@ -1,5 +0,0 @@
import { EntityRepository, Repository } from '@dbTools/typeorm'
import { LoginUserBackup } from '@entity/LoginUserBackup'
@EntityRepository(LoginUserBackup)
export class LoginUserBackupRepository extends Repository<LoginUserBackup> {}

View File

@ -5,7 +5,7 @@ import { User } from '@entity/User'
export class UserRepository extends Repository<User> { export class UserRepository extends Repository<User> {
async findByPubkeyHex(pubkeyHex: string): Promise<User> { async findByPubkeyHex(pubkeyHex: string): Promise<User> {
return this.createQueryBuilder('user') return this.createQueryBuilder('user')
.where('hex(user.pubkey) = :pubkeyHex', { pubkeyHex }) .where('hex(user.pubKey) = :pubkeyHex', { pubkeyHex })
.getOneOrFail() .getOneOrFail()
} }

View File

@ -31,7 +31,7 @@ import { LoginElopageBuys } from '@entity/LoginElopageBuys'
import { getCustomRepository } from '@dbTools/typeorm' import { getCustomRepository } from '@dbTools/typeorm'
import { UserResolver } from '../graphql/resolver/UserResolver' import { UserResolver } from '../graphql/resolver/UserResolver'
import { LoginElopageBuysRepository } from '../typeorm/repository/LoginElopageBuys' import { LoginElopageBuysRepository } from '../typeorm/repository/LoginElopageBuys'
import { LoginUserRepository } from '../typeorm/repository/LoginUser' import { User as dbUser } from '@entity/User'
export const elopageWebhook = async (req: any, res: any): Promise<void> => { export const elopageWebhook = async (req: any, res: any): Promise<void> => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -114,8 +114,7 @@ export const elopageWebhook = async (req: any, res: any): Promise<void> => {
} }
// Do we already have such a user? // Do we already have such a user?
const loginUserRepository = await getCustomRepository(LoginUserRepository) if ((await dbUser.count({ email })) !== 0) {
if ((await loginUserRepository.count({ email })) !== 0) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(`Did not create User - already exists with email: ${email}`) console.log(`Did not create User - already exists with email: ${email}`)
return return

View File

@ -1,20 +0,0 @@
worker_count = 2
io_worker_count = 1
data_root_folder = /opt/instance/.gradido
hedera_mirror_endpoint = hcs.testnet.mirrornode.hedera.com:5600
sibling_node_file = /opt/instance/.gradido/sibling_nodes.txt
#group_requests_endpoint = 0.0.0.0:13701
#record_requests_endpoint = 0.0.0.0:13702
#manage_network_requests_endpoint = 0.0.0.0:13703
grpc_endpoint = 0.0.0.0:13701
json_rpc_port = 13702
# larger value, larger batch, less concurrency
blockchain_append_batch_size = 1000
#blochchain_init_batch_size = 1000
#block_record_outbound_batch_size = 100
general_batch_size = 1000
group_register_topic_id = 0.0.79574
topic_reset_allowed = 1

View File

@ -7,13 +7,13 @@ export class User extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true }) @PrimaryGeneratedColumn('increment', { unsigned: true })
id: number id: number
@Column({ name: 'index_id', default: 0 }) @Column({ name: 'index_id', type: 'smallint', default: 0, nullable: false })
indexId: number indexId: number
@Column({ name: 'group_id', default: 0, unsigned: true }) @Column({ name: 'group_id', default: 0, unsigned: true })
groupId: number groupId: number
@Column({ type: 'binary', length: 32, name: 'public_key' }) @Column({ name: 'public_key', type: 'binary', length: 32, default: null, nullable: true })
pubkey: Buffer pubkey: Buffer
@Column({ length: 255, nullable: true, default: null, collation: 'utf8mb4_unicode_ci' }) @Column({ length: 255, nullable: true, default: null, collation: 'utf8mb4_unicode_ci' })
@ -40,7 +40,7 @@ export class User extends BaseEntity {
@Column({ length: 255, nullable: true, default: null, collation: 'utf8mb4_unicode_ci' }) @Column({ length: 255, nullable: true, default: null, collation: 'utf8mb4_unicode_ci' })
username: string username: string
@Column() @Column({ type: 'bool', default: false })
disabled: boolean disabled: boolean
@OneToOne(() => Balance, (balance) => balance.user) @OneToOne(() => Balance, (balance) => balance.user)

View File

@ -7,13 +7,13 @@ export class User extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true }) @PrimaryGeneratedColumn('increment', { unsigned: true })
id: number id: number
@Column({ name: 'index_id', default: 0 }) @Column({ name: 'index_id', type: 'smallint', default: 0, nullable: false })
indexId: number indexId: number
@Column({ name: 'group_id', default: 0, unsigned: true }) @Column({ name: 'group_id', default: 0, unsigned: true })
groupId: number groupId: number
@Column({ type: 'binary', length: 32, name: 'public_key' }) @Column({ name: 'public_key', type: 'binary', length: 32, default: null, nullable: true })
pubkey: Buffer pubkey: Buffer
@Column({ length: 255, nullable: true, default: null, collation: 'utf8mb4_unicode_ci' }) @Column({ length: 255, nullable: true, default: null, collation: 'utf8mb4_unicode_ci' })
@ -40,7 +40,7 @@ export class User extends BaseEntity {
@Column({ length: 255, nullable: true, default: null, collation: 'utf8mb4_unicode_ci' }) @Column({ length: 255, nullable: true, default: null, collation: 'utf8mb4_unicode_ci' })
username: string username: string
@Column() @Column({ type: 'bool', default: false })
disabled: boolean disabled: boolean
@OneToMany(() => UserSetting, (userSetting) => userSetting.user) @OneToMany(() => UserSetting, (userSetting) => userSetting.user)

View File

@ -1,5 +1,5 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm' import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm'
import { LoginUserBackup } from '../LoginUserBackup' import { LoginUserBackup } from './LoginUserBackup'
// 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('login_users') @Entity('login_users')
@ -19,7 +19,7 @@ export class LoginUser extends BaseEntity {
@Column({ length: 255, default: '' }) @Column({ length: 255, default: '' })
username: string username: string
@Column({ default: '', nullable: true }) @Column({ type: 'mediumtext', default: '', nullable: true })
description: string description: string
@Column({ type: 'bigint', default: 0, unsigned: true }) @Column({ type: 'bigint', default: 0, unsigned: true })
@ -34,19 +34,19 @@ export class LoginUser extends BaseEntity {
@Column({ name: 'email_hash', type: 'binary', length: 32, default: null, nullable: true }) @Column({ name: 'email_hash', type: 'binary', length: 32, default: null, nullable: true })
emailHash: Buffer emailHash: Buffer
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' }) @Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP', nullable: false })
createdAt: Date createdAt: Date
@Column({ name: 'email_checked', default: 0 }) @Column({ name: 'email_checked', type: 'bool', nullable: false, default: false })
emailChecked: boolean emailChecked: boolean
@Column({ name: 'passphrase_shown', default: 0 }) @Column({ name: 'passphrase_shown', type: 'bool', nullable: false, default: false })
passphraseShown: boolean passphraseShown: boolean
@Column({ length: 4, default: 'de' }) @Column({ length: 4, default: 'de', nullable: false })
language: string language: string
@Column({ default: 0 }) @Column({ type: 'bool', default: false })
disabled: boolean disabled: boolean
@Column({ name: 'group_id', default: 0, unsigned: true }) @Column({ name: 'group_id', default: 0, unsigned: true })

View File

@ -1,5 +1,5 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, JoinColumn, OneToOne } from 'typeorm' import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, JoinColumn, OneToOne } from 'typeorm'
import { LoginUser } from '../LoginUser' import { LoginUser } from './LoginUser'
@Entity('login_user_backups') @Entity('login_user_backups')
export class LoginUserBackup extends BaseEntity { export class LoginUserBackup extends BaseEntity {

View File

@ -1,5 +1,5 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm' import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm'
import { LoginUserBackup } from '../LoginUserBackup' import { LoginUserBackup } from '../0003-login_server_tables/LoginUserBackup'
// 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('login_users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' }) @Entity('login_users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
@ -19,7 +19,7 @@ export class LoginUser extends BaseEntity {
@Column({ length: 255, default: '', collation: 'utf8mb4_unicode_ci' }) @Column({ length: 255, default: '', collation: 'utf8mb4_unicode_ci' })
username: string username: string
@Column({ default: '', collation: 'utf8mb4_unicode_ci', nullable: true }) @Column({ type: 'mediumtext', default: '', collation: 'utf8mb4_unicode_ci', nullable: true })
description: string description: string
@Column({ type: 'bigint', default: 0, unsigned: true }) @Column({ type: 'bigint', default: 0, unsigned: true })
@ -34,19 +34,19 @@ export class LoginUser extends BaseEntity {
@Column({ name: 'email_hash', type: 'binary', length: 32, default: null, nullable: true }) @Column({ name: 'email_hash', type: 'binary', length: 32, default: null, nullable: true })
emailHash: Buffer emailHash: Buffer
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' }) @Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP', nullable: false })
createdAt: Date createdAt: Date
@Column({ name: 'email_checked', default: 0 }) @Column({ name: 'email_checked', type: 'bool', nullable: false, default: false })
emailChecked: boolean emailChecked: boolean
@Column({ name: 'passphrase_shown', default: 0 }) @Column({ name: 'passphrase_shown', type: 'bool', nullable: false, default: false })
passphraseShown: boolean passphraseShown: boolean
@Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci' }) @Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false })
language: string language: string
@Column({ default: 0 }) @Column({ type: 'bool', default: false })
disabled: boolean disabled: boolean
@Column({ name: 'group_id', default: 0, unsigned: true }) @Column({ name: 'group_id', default: 0, unsigned: true })

View File

@ -0,0 +1,16 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity('login_user_backups')
export class LoginUserBackup extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ type: 'text', name: 'passphrase', nullable: false })
passphrase: string
@Column({ name: 'user_id', nullable: false })
userId: number
@Column({ name: 'mnemonic_type', default: -1 })
mnemonicType: number
}

View File

@ -0,0 +1,74 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'
import { UserSetting } from '../UserSetting'
@Entity('state_users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
export class User extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ name: 'login_user_id', default: null, unsigned: true })
loginUserId: number
@Column({ name: 'index_id', type: 'smallint', default: 0, nullable: false })
indexId: number
@Column({ name: 'public_key', type: 'binary', length: 32, default: null, nullable: true })
pubKey: Buffer
@Column({ name: 'privkey', type: 'binary', length: 80, default: null, nullable: true })
privKey: Buffer
@Column({ length: 255, unique: true, nullable: false, collation: 'utf8mb4_unicode_ci' })
email: string
@Column({
name: 'first_name',
length: 255,
nullable: true,
default: null,
collation: 'utf8mb4_unicode_ci',
})
firstName: string
@Column({
name: 'last_name',
length: 255,
nullable: true,
default: null,
collation: 'utf8mb4_unicode_ci',
})
lastName: string
@Column({ length: 255, nullable: true, default: null, collation: 'utf8mb4_unicode_ci' })
username: string
@Column({ type: 'bool', default: false })
disabled: boolean
@Column({ type: 'mediumtext', default: '', collation: 'utf8mb4_unicode_ci', nullable: true })
description: string
@Column({ type: 'bigint', default: 0, unsigned: true })
password: BigInt
@Column({ name: 'email_hash', type: 'binary', length: 32, default: null, nullable: true })
emailHash: Buffer
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP', nullable: false })
createdAt: Date
@Column({ name: 'email_checked', type: 'bool', nullable: false, default: false })
emailChecked: boolean
@Column({ name: 'passphrase_shown', type: 'bool', nullable: false, default: false })
passphraseShown: boolean
@Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false })
language: string
@Column({ name: 'publisher_id', default: 0 })
publisherId: number
@OneToMany(() => UserSetting, (userSetting) => userSetting.user)
settings: UserSetting[]
}

View File

@ -0,0 +1,83 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'
import { UserSetting } from '../UserSetting'
@Entity('state_users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
export class User extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ name: 'login_user_id', default: null, unsigned: true })
loginUserId: number
@Column({ name: 'index_id', type: 'smallint', default: 0, nullable: false })
indexId: number
@Column({ name: 'public_key', type: 'binary', length: 32, default: null, nullable: true })
pubKey: Buffer
@Column({ name: 'privkey', type: 'binary', length: 80, default: null, nullable: true })
privKey: Buffer
@Column({ length: 255, unique: true, nullable: false, collation: 'utf8mb4_unicode_ci' })
email: string
@Column({
name: 'first_name',
length: 255,
nullable: true,
default: null,
collation: 'utf8mb4_unicode_ci',
})
firstName: string
@Column({
name: 'last_name',
length: 255,
nullable: true,
default: null,
collation: 'utf8mb4_unicode_ci',
})
lastName: string
@Column({ length: 255, nullable: true, default: null, collation: 'utf8mb4_unicode_ci' })
username: string
@Column({ type: 'bool', default: false })
disabled: boolean
@Column({ type: 'mediumtext', default: '', collation: 'utf8mb4_unicode_ci', nullable: true })
description: string
@Column({ type: 'bigint', default: 0, unsigned: true })
password: BigInt
@Column({ name: 'email_hash', type: 'binary', length: 32, default: null, nullable: true })
emailHash: Buffer
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP', nullable: false })
createdAt: Date
@Column({ name: 'email_checked', type: 'bool', nullable: false, default: false })
emailChecked: boolean
@Column({ name: 'passphrase_shown', type: 'bool', nullable: false, default: false })
passphraseShown: boolean
@Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false })
language: string
@Column({ name: 'publisher_id', default: 0 })
publisherId: number
@Column({
type: 'text',
name: 'passphrase',
collation: 'utf8mb4_unicode_ci',
nullable: true,
default: null,
})
passphrase: string
@OneToMany(() => UserSetting, (userSetting) => userSetting.user)
settings: UserSetting[]
}

View File

@ -0,0 +1,80 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'
import { UserSetting } from '../UserSetting'
@Entity('state_users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
export class User extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ name: 'index_id', type: 'smallint', default: 0, nullable: false })
indexId: number
@Column({ name: 'public_key', type: 'binary', length: 32, default: null, nullable: true })
pubKey: Buffer
@Column({ name: 'privkey', type: 'binary', length: 80, default: null, nullable: true })
privKey: Buffer
@Column({ length: 255, unique: true, nullable: false, collation: 'utf8mb4_unicode_ci' })
email: string
@Column({
name: 'first_name',
length: 255,
nullable: true,
default: null,
collation: 'utf8mb4_unicode_ci',
})
firstName: string
@Column({
name: 'last_name',
length: 255,
nullable: true,
default: null,
collation: 'utf8mb4_unicode_ci',
})
lastName: string
@Column({ length: 255, nullable: true, default: null, collation: 'utf8mb4_unicode_ci' })
username: string
@Column({ type: 'bool', default: false })
disabled: boolean
@Column({ type: 'mediumtext', default: '', collation: 'utf8mb4_unicode_ci', nullable: true })
description: string
@Column({ type: 'bigint', default: 0, unsigned: true })
password: BigInt
@Column({ name: 'email_hash', type: 'binary', length: 32, default: null, nullable: true })
emailHash: Buffer
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP', nullable: false })
createdAt: Date
@Column({ name: 'email_checked', type: 'bool', nullable: false, default: false })
emailChecked: boolean
@Column({ name: 'passphrase_shown', type: 'bool', nullable: false, default: false })
passphraseShown: boolean
@Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false })
language: string
@Column({ name: 'publisher_id', default: 0 })
publisherId: number
@Column({
type: 'text',
name: 'passphrase',
collation: 'utf8mb4_unicode_ci',
nullable: true,
default: null,
})
passphrase: string
@OneToMany(() => UserSetting, (userSetting) => userSetting.user)
settings: UserSetting[]
}

View File

@ -0,0 +1,68 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'
import { UserSetting } from '../UserSetting'
@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
export class User extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ name: 'public_key', type: 'binary', length: 32, default: null, nullable: true })
pubKey: Buffer
@Column({ name: 'privkey', type: 'binary', length: 80, default: null, nullable: true })
privKey: Buffer
@Column({ length: 255, unique: true, nullable: false, collation: 'utf8mb4_unicode_ci' })
email: string
@Column({
name: 'first_name',
length: 255,
nullable: true,
default: null,
collation: 'utf8mb4_unicode_ci',
})
firstName: string
@Column({
name: 'last_name',
length: 255,
nullable: true,
default: null,
collation: 'utf8mb4_unicode_ci',
})
lastName: string
@Column({ type: 'bool', default: false })
disabled: boolean
@Column({ type: 'bigint', default: 0, unsigned: true })
password: BigInt
@Column({ name: 'email_hash', type: 'binary', length: 32, default: null, nullable: true })
emailHash: Buffer
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP', nullable: false })
createdAt: Date
@Column({ name: 'email_checked', type: 'bool', nullable: false, default: false })
emailChecked: boolean
@Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false })
language: string
@Column({ name: 'publisher_id', default: 0 })
publisherId: number
@Column({
type: 'text',
name: 'passphrase',
collation: 'utf8mb4_unicode_ci',
nullable: true,
default: null,
})
passphrase: string
@OneToMany(() => UserSetting, (userSetting) => userSetting.user)
settings: UserSetting[]
}

View File

@ -1 +0,0 @@
export { LoginUser } from './0006-login_users_collation/LoginUser'

View File

@ -1 +0,0 @@
export { LoginUserBackup } from './0003-login_server_tables/LoginUserBackup'

View File

@ -1 +1 @@
export { User } from './0002-add_settings/User' export { User } from './0020-rename_and_clean_state_users/User'

View File

@ -1,8 +1,6 @@
import { Balance } from './Balance' import { Balance } from './Balance'
import { LoginElopageBuys } from './LoginElopageBuys' import { LoginElopageBuys } from './LoginElopageBuys'
import { LoginEmailOptIn } from './LoginEmailOptIn' import { LoginEmailOptIn } from './LoginEmailOptIn'
import { LoginUser } from './LoginUser'
import { LoginUserBackup } from './LoginUserBackup'
import { Migration } from './Migration' import { Migration } from './Migration'
import { ServerUser } from './ServerUser' import { ServerUser } from './ServerUser'
import { Transaction } from './Transaction' import { Transaction } from './Transaction'
@ -18,8 +16,6 @@ export const entities = [
Balance, Balance,
LoginElopageBuys, LoginElopageBuys,
LoginEmailOptIn, LoginEmailOptIn,
LoginUser,
LoginUserBackup,
Migration, Migration,
ServerUser, ServerUser,
Transaction, Transaction,

View File

@ -0,0 +1,150 @@
/* MIGRATION TO COMBINE LOGIN_USERS WITH STATE_USERS TABLE
*
* This migration combines the table `login_users` with
* the `state_users` table, where the later is the target.
*/
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
// Drop column `group_id` since it contains uniform data which is not the same as the uniform data
// on login_users. Since we do not need this data anyway, we sjust throw it away.
await queryFn('ALTER TABLE `state_users` DROP COLUMN `group_id`;')
// Remove the unique constraint from the pubkey
await queryFn('ALTER TABLE `state_users` DROP INDEX `public_key`;')
// Allow NULL on the `state_users` pubkey like it is allowed on `login_users`
await queryFn('ALTER TABLE `state_users` MODIFY COLUMN `public_key` binary(32) DEFAULT NULL;')
// instead use a unique constraint for the email like on `login_users`
// therefore do not allow null on `email` anymore
await queryFn(
'ALTER TABLE `state_users` MODIFY COLUMN `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL;',
)
await queryFn('ALTER TABLE `state_users` ADD CONSTRAINT `email` UNIQUE KEY (`email`);')
// Create `login_user_id` column - to store the login_users.id field to not break references.
await queryFn(
'ALTER TABLE `state_users` ADD COLUMN `login_user_id` int(10) unsigned DEFAULT NULL AFTER `id`;',
)
// Create missing data columns for the data stored in `login_users`
await queryFn(
"ALTER TABLE `state_users` ADD COLUMN `description` mediumtext COLLATE utf8mb4_unicode_ci DEFAULT '' AFTER `disabled`;",
)
await queryFn(
'ALTER TABLE `state_users` ADD COLUMN `password` bigint(20) unsigned DEFAULT 0 AFTER `description`;',
)
await queryFn(
'ALTER TABLE `state_users` ADD COLUMN `privkey` binary(80) DEFAULT NULL AFTER `public_key`;',
)
await queryFn(
'ALTER TABLE `state_users` ADD COLUMN `email_hash` binary(32) DEFAULT NULL AFTER `password`;',
)
await queryFn(
'ALTER TABLE `state_users` ADD COLUMN `created` datetime NOT NULL DEFAULT current_timestamp() AFTER `email_hash`;',
)
await queryFn(
'ALTER TABLE `state_users` ADD COLUMN `email_checked` tinyint(4) NOT NULL DEFAULT 0 AFTER `created`;',
)
await queryFn(
'ALTER TABLE `state_users` ADD COLUMN `passphrase_shown` tinyint(4) NOT NULL DEFAULT 0 AFTER `email_checked`;',
)
await queryFn(
"ALTER TABLE `state_users` ADD COLUMN `language` varchar(4) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'de' AFTER `passphrase_shown`;",
)
await queryFn(
'ALTER TABLE `state_users` ADD COLUMN `publisher_id` int(11) DEFAULT 0 AFTER `language`;',
)
// Move data from `login_users` to the newly modified `state_users` table.
// The following rules for overwriting data applies:
// email is the matching criteria
// public_key is overwritten by `login_users`.`pubkey` (we have validated the passphrases here) (2 keys differ)
// first_name is more accurate on `state_users` and stays unchanged (1 users with different first_* & last_name)
// last_name is more accurate on `state_users` and stays unchanged (1 users with different first_* & last_name)
// username does not contain any relevant data, either NULL or '' and therefore we do not change anything here
// disabled does not differ, we can omit it
await queryFn(`
UPDATE state_users
LEFT JOIN login_users ON state_users.email = login_users.email
SET state_users.public_key = login_users.pubkey,
state_users.login_user_id = login_users.id,
state_users.description = login_users.description,
state_users.password = login_users.password,
state_users.privkey = login_users.privkey,
state_users.email_hash = login_users.email_hash,
state_users.created = login_users.created,
state_users.email_checked = login_users.email_checked,
state_users.passphrase_shown = login_users.passphrase_shown,
state_users.language = login_users.language,
state_users.publisher_id = login_users.publisher_id
;
`)
// Drop `login_users` table
await queryFn('DROP TABLE `login_users`;')
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`
CREATE TABLE \`login_users\` (
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
\`email\` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
\`first_name\` varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL,
\`last_name\` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '',
\`username\` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '',
\`description\` mediumtext COLLATE utf8mb4_unicode_ci DEFAULT '',
\`password\` bigint(20) unsigned DEFAULT 0,
\`pubkey\` binary(32) DEFAULT NULL,
\`privkey\` binary(80) DEFAULT NULL,
\`email_hash\` binary(32) DEFAULT NULL,
\`created\` datetime NOT NULL DEFAULT current_timestamp(),
\`email_checked\` tinyint(4) NOT NULL DEFAULT 0,
\`passphrase_shown\` tinyint(4) NOT NULL DEFAULT 0,
\`language\` varchar(4) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'de',
\`disabled\` tinyint(4) DEFAULT 0,
\`group_id\` int(10) unsigned DEFAULT 0,
\`publisher_id\` int(11) DEFAULT 0,
PRIMARY KEY (\`id\`),
UNIQUE KEY \`email\` (\`email\`)
) ENGINE=InnoDB AUTO_INCREMENT=2363 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`)
await queryFn(`
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 )
( SELECT login_user_id AS id, email, first_name,
last_name, username, description, password,
public_key AS pubkey, privkey, email_hash,
created, email_checked, passphrase_shown,
language, disabled, '1' AS group_id,
publisher_id
FROM state_users )
;
`)
await queryFn('ALTER TABLE `state_users` DROP COLUMN `publisher_id`;')
await queryFn('ALTER TABLE `state_users` DROP COLUMN `language`;')
await queryFn('ALTER TABLE `state_users` DROP COLUMN `passphrase_shown`;')
await queryFn('ALTER TABLE `state_users` DROP COLUMN `email_checked`;')
await queryFn('ALTER TABLE `state_users` DROP COLUMN `created`;')
await queryFn('ALTER TABLE `state_users` DROP COLUMN `email_hash`;')
await queryFn('ALTER TABLE `state_users` DROP COLUMN `privkey`;')
await queryFn('ALTER TABLE `state_users` DROP COLUMN `password`;')
await queryFn('ALTER TABLE `state_users` DROP COLUMN `description`;')
await queryFn('ALTER TABLE `state_users` DROP COLUMN `login_user_id`;')
await queryFn('ALTER TABLE `state_users` DROP INDEX `email`;')
await queryFn(
'ALTER TABLE `state_users` MODIFY COLUMN `email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL;',
)
// Note: if the public_key is NULL, we need to set a random key in order to meet the constraint
await queryFn(
'UPDATE `state_users` SET public_key = UNHEX(SHA1(RAND())) WHERE public_key IS NULL;',
)
await queryFn('ALTER TABLE `state_users` MODIFY COLUMN `public_key` binary(32) NOT NULL;')
await queryFn('ALTER TABLE `state_users` ADD CONSTRAINT `public_key` UNIQUE KEY (`public_key`);')
await queryFn(
'ALTER TABLE `state_users` ADD COLUMN `group_id` int(10) unsigned NOT NULL DEFAULT 0 AFTER index_id;',
)
}

View File

@ -0,0 +1,48 @@
/* MIGRATION TO COMBINE LOGIN_BACKUP_USERS TABLE WITH STATE_USERS
*
* This migration combines the table `login_user_backups` into
* the `state_users` table, where the later is the target.
*/
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
// We only keep the passphrase, the mnemonic type is a constant,
// since every passphrase was converted to mnemonic type 2
await queryFn(
'ALTER TABLE `state_users` ADD COLUMN `passphrase` text DEFAULT NULL AFTER `publisher_id`;',
)
// Move data from `login_user_backups` to the newly modified `state_users` table.
await queryFn(`
UPDATE state_users
LEFT JOIN login_user_backups ON state_users.login_user_id = login_user_backups.user_id
SET state_users.passphrase = login_user_backups.passphrase
WHERE login_user_backups.passphrase IS NOT NULL
;
`)
// Drop `login_user_backups` table
await queryFn('DROP TABLE `login_user_backups`;')
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`
CREATE TABLE \`login_user_backups\` (
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
\`user_id\` int(11) NOT NULL,
\`passphrase\` text NOT NULL,
\`mnemonic_type\` int(11) DEFAULT -1,
PRIMARY KEY (\`id\`)
) ENGINE=InnoDB AUTO_INCREMENT=1862 DEFAULT CHARSET=utf8mb4;
`)
await queryFn(`
INSERT INTO login_user_backups
( user_id, passphrase, mnemonic_type )
( SELECT login_user_id AS user_id,
passphrase,
'2' as mnemonic_type
FROM state_users
WHERE passphrase IS NOT NULL )
;
`)
await queryFn('ALTER TABLE `state_users` DROP COLUMN `passphrase`;')
}

View File

@ -0,0 +1,57 @@
/* MIGRATION TO REPLACE LOGIN_USER_ID WITH STATE_USER_ID
*
* This migration replaces the `login_user_id with` the
* `state_user.id` and removes corresponding columns.
* The table affected is `login_email_opt_in`
*/
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
// Delete email opt in codes which can not be linked to an user
await queryFn(`
DELETE FROM \`login_email_opt_in\`
WHERE user_id NOT IN
( SELECT login_user_id FROM state_users )
`)
// Replace user_id in `login_email_opt_in`
await queryFn(`
UPDATE login_email_opt_in
LEFT JOIN state_users ON state_users.login_user_id = login_email_opt_in.user_id
SET login_email_opt_in.user_id = state_users.id;
`)
// Remove the column `login_user_id` from `state_users`
await queryFn('ALTER TABLE `state_users` DROP COLUMN `login_user_id`;')
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
'ALTER TABLE `state_users` ADD COLUMN `login_user_id` int(10) unsigned DEFAULT NULL AFTER `id`;',
)
// Instead of generating new `login_user_id`'s we just use the id of state user.
// This way we do not need to alter the `user_id`'s of `login_email_opt_in` table
// at all when migrating down.
// This is possible since there are no old `login_user.id` referenced anymore and
// we can freely choose them
await queryFn('UPDATE `state_users` SET login_user_id = id')
// Insert back broken data, since we generate new `user_id`'s the old data might be now
// linked to existing accounts. To prevent that all invalid `user_id`'s are now negative.
// This renders them invalid while still keeping the original value
await queryFn(`
INSERT INTO login_email_opt_in
(id, user_id, verification_code, email_opt_in_type_id, created, resend_count, updated)
VALUES
('38','-41','7544440030630126261','0','2019-11-09 13:58:21','0','2020-07-17 13:58:29'),
('1262','-1185','2702555860489093775','3','2020-10-17 00:57:29','0','2020-10-17 00:57:29'),
('1431','-1319','9846213635571107141','3','2020-12-29 00:07:32','0','2020-12-29 00:07:32'),
('1548','-1185','1009203004512986277','1','2021-01-26 01:07:29','0','2021-01-26 01:07:29'),
('1549','-1185','2144334450300724903','1','2021-01-26 01:07:32','0','2021-01-26 01:07:32'),
('1683','-1525','14803676216828342915','3','2021-03-10 08:39:39','0','2021-03-10 08:39:39'),
('1899','-1663','16616172057370363741','3','2021-04-12 14:49:18','0','2021-04-12 14:49:18'),
('2168','-1865','13129474130315401087','3','2021-07-08 11:58:54','0','2021-07-08 11:58:54'),
('2274','-1935','5775135935896874129','3','2021-08-24 11:40:04','0','2021-08-24 11:40:04'),
('2318','-1967','5713731625139303791','3','2021-09-06 21:38:30','0','2021-09-06 21:38:30'),
('2762','-2263','6997866521554931275','1','2021-12-25 11:44:30','0','2021-12-25 11:44:30');
`)
}

View File

@ -0,0 +1,39 @@
/* MIGRATION TO CLEAN UP AND RENAME STATE_USERS
*
* This migration renames 'state_users` to `users`
* and removes columns with no meaningful value
*/
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
// rename `state_users` table to `users`
await queryFn('RENAME TABLE `state_users` TO `users`;')
// Remove the column `index_id` from `users`, it only contains 0 as value
await queryFn('ALTER TABLE `users` DROP COLUMN `index_id`;')
// Remove the column `username` from `users`, it contains only '' or NULL
await queryFn('ALTER TABLE `users` DROP COLUMN `username`;')
// Remove the column `description` from `users`, it contains only '' or NULL
await queryFn('ALTER TABLE `users` DROP COLUMN `description`;')
// Remove the column `passphrase_shown` from `users`, it contains only 0 as value
await queryFn('ALTER TABLE `users` DROP COLUMN `passphrase_shown`;')
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
'ALTER TABLE `users` ADD COLUMN `passphrase_shown` tinyint(4) NOT NULL DEFAULT 0 AFTER `email_checked`;',
)
await queryFn(
"ALTER TABLE `users` ADD COLUMN `description` mediumtext COLLATE utf8mb4_unicode_ci DEFAULT '' AFTER `disabled`;",
)
await queryFn(
'ALTER TABLE `users` ADD COLUMN `username` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL AFTER `last_name`;',
)
await queryFn(
'ALTER TABLE `users` ADD COLUMN `index_id` smallint(6) NOT NULL DEFAULT 0 AFTER `id`;',
)
await queryFn('RENAME TABLE `users` TO `state_users`;')
}

View File

@ -1,18 +0,0 @@
import Faker from 'faker'
import { define } from 'typeorm-seeding'
import { LoginUserBackup } from '../../entity/LoginUserBackup'
import { LoginUserBackupContext } from '../interface/UserContext'
define(LoginUserBackup, (faker: typeof Faker, context?: LoginUserBackupContext) => {
if (!context || !context.userId) {
throw new Error('LoginUserBackup: No userId present!')
}
const userBackup = new LoginUserBackup()
// TODO: Get the real passphrase
userBackup.passphrase = context.passphrase ? context.passphrase : faker.random.words(24)
userBackup.mnemonicType = context.mnemonicType ? context.mnemonicType : 2
userBackup.userId = context.userId
return userBackup
})

View File

@ -1,30 +0,0 @@
import Faker from 'faker'
import { define } from 'typeorm-seeding'
import { LoginUser } from '../../entity/LoginUser'
import { randomBytes } from 'crypto'
import { LoginUserContext } from '../interface/UserContext'
define(LoginUser, (faker: typeof Faker, context?: LoginUserContext) => {
if (!context) context = {}
const user = new LoginUser()
user.email = context.email ? context.email : faker.internet.email()
user.firstName = context.firstName ? context.firstName : faker.name.firstName()
user.lastName = context.lastName ? context.lastName : faker.name.lastName()
user.username = context.username ? context.username : faker.internet.userName()
user.description = context.description ? context.description : faker.random.words(4)
// TODO Create real password and keys/hash
user.password = context.password ? context.password : BigInt(0)
user.pubKey = context.pubKey ? context.pubKey : randomBytes(32)
user.privKey = context.privKey ? context.privKey : randomBytes(80)
user.emailHash = context.emailHash ? context.emailHash : randomBytes(32)
user.createdAt = context.createdAt ? context.createdAt : faker.date.recent()
user.emailChecked = context.emailChecked === undefined ? false : context.emailChecked
user.passphraseShown = context.passphraseShown ? context.passphraseShown : false
user.language = context.language ? context.language : 'en'
user.disabled = context.disabled ? context.disabled : false
user.groupId = context.groupId ? context.groupId : 1
user.publisherId = context.publisherId ? context.publisherId : 0
return user
})

View File

@ -1,21 +1,27 @@
import Faker from 'faker' import Faker from 'faker'
import { define } from 'typeorm-seeding' import { define } from 'typeorm-seeding'
import { User } from '../../entity/User' import { User } from '../../entity/User'
import { randomBytes } from 'crypto' import { randomBytes, randomInt } from 'crypto'
import { UserContext } from '../interface/UserContext' import { UserContext } from '../interface/UserContext'
define(User, (faker: typeof Faker, context?: UserContext) => { define(User, (faker: typeof Faker, context?: UserContext) => {
if (!context) context = {} if (!context) context = {}
const user = new User() const user = new User()
user.pubkey = context.pubkey ? context.pubkey : randomBytes(32) user.pubKey = context.pubKey ? context.pubKey : randomBytes(32)
user.email = context.email ? context.email : faker.internet.email() user.email = context.email ? context.email : faker.internet.email()
user.firstName = context.firstName ? context.firstName : faker.name.firstName() user.firstName = context.firstName ? context.firstName : faker.name.firstName()
user.lastName = context.lastName ? context.lastName : faker.name.lastName() user.lastName = context.lastName ? context.lastName : faker.name.lastName()
user.username = context.username ? context.username : faker.internet.userName()
user.disabled = context.disabled ? context.disabled : false user.disabled = context.disabled ? context.disabled : false
user.groupId = 0 // TODO Create real password and keys/hash
user.indexId = 0 user.password = context.password ? context.password : BigInt(0)
user.privKey = context.privKey ? context.privKey : randomBytes(80)
user.emailHash = context.emailHash ? context.emailHash : randomBytes(32)
user.createdAt = context.createdAt ? context.createdAt : faker.date.recent()
user.emailChecked = context.emailChecked === undefined ? false : context.emailChecked
user.language = context.language ? context.language : 'en'
user.publisherId = context.publisherId ? context.publisherId : 0
user.passphrase = context.passphrase ? context.passphrase : faker.random.words(24)
return user return user
}) })

View File

@ -1,35 +1,17 @@
export interface UserContext { export interface UserContext {
pubkey?: Buffer
email?: string
firstName?: string
lastName?: string
username?: string
disabled?: boolean
}
export interface LoginUserContext {
email?: string
firstName?: string
lastName?: string
username?: string
description?: string
password?: BigInt
pubKey?: Buffer pubKey?: Buffer
email?: string
firstName?: string
lastName?: string
disabled?: boolean
password?: BigInt
privKey?: Buffer privKey?: Buffer
emailHash?: Buffer emailHash?: Buffer
createdAt?: Date createdAt?: Date
emailChecked?: boolean emailChecked?: boolean
passphraseShown?: boolean
language?: string language?: string
disabled?: boolean
groupId?: number
publisherId?: number publisherId?: number
}
export interface LoginUserBackupContext {
userId?: number
passphrase?: string passphrase?: string
mnemonicType?: number
} }
export interface ServerUserContext { export interface ServerUserContext {
@ -42,8 +24,3 @@ export interface ServerUserContext {
created?: Date created?: Date
modified?: Date modified?: Date
} }
export interface LoginUserRolesContext {
userId?: number
roleId?: number
}

View File

@ -1,24 +1,19 @@
export interface UserInterface { export interface UserInterface {
// from login user (contains state user) // from user
email?: string email?: string
firstName?: string firstName?: string
lastName?: string lastName?: string
username?: string
description?: string
password?: BigInt password?: BigInt
pubKey?: Buffer pubKey?: Buffer
privKey?: Buffer privKey?: Buffer
emailHash?: Buffer emailHash?: Buffer
createdAt?: Date createdAt?: Date
emailChecked?: boolean emailChecked?: boolean
passphraseShown?: boolean
language?: string language?: string
disabled?: boolean disabled?: boolean
groupId?: number groupId?: number
publisherId?: number publisherId?: number
// from login user backup
passphrase?: string passphrase?: string
mnemonicType?: number
// from server user // from server user
serverUserPassword?: string serverUserPassword?: string
role?: string role?: string

View File

@ -1,10 +1,4 @@
import { import { UserContext, ServerUserContext } from '../../interface/UserContext'
UserContext,
LoginUserContext,
LoginUserBackupContext,
ServerUserContext,
LoginUserRolesContext,
} from '../../interface/UserContext'
import { import {
BalanceContext, BalanceContext,
TransactionContext, TransactionContext,
@ -13,8 +7,6 @@ import {
} from '../../interface/TransactionContext' } 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 { LoginUserBackup } from '../../../entity/LoginUserBackup'
import { ServerUser } from '../../../entity/ServerUser' import { ServerUser } from '../../../entity/ServerUser'
import { Balance } from '../../../entity/Balance' import { Balance } from '../../../entity/Balance'
import { Transaction } from '../../../entity/Transaction' import { Transaction } from '../../../entity/Transaction'
@ -24,9 +16,6 @@ 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> => {
const user = await factory(User)(createUserContext(userData)).create() const user = await factory(User)(createUserContext(userData)).create()
if (!userData.email) userData.email = user.email
const loginUser = await factory(LoginUser)(createLoginUserContext(userData)).create()
await factory(LoginUserBackup)(createLoginUserBackupContext(userData, loginUser)).create()
if (userData.isAdmin) { if (userData.isAdmin) {
await factory(ServerUser)(createServerUserContext(userData)).create() await factory(ServerUser)(createServerUserContext(userData)).create()
@ -49,51 +38,24 @@ export const userSeeder = async (factory: Factory, userData: UserInterface): Pro
const createUserContext = (context: UserInterface): UserContext => { const createUserContext = (context: UserInterface): UserContext => {
return { return {
pubkey: context.pubKey,
email: context.email,
firstName: context.firstName,
lastName: context.lastName,
username: context.username,
disabled: context.disabled,
}
}
const createLoginUserContext = (context: UserInterface): LoginUserContext => {
return {
email: context.email,
firstName: context.firstName,
lastName: context.lastName,
username: context.username,
description: context.description,
password: context.password,
pubKey: context.pubKey, pubKey: context.pubKey,
email: context.email,
firstName: context.firstName,
lastName: context.lastName,
disabled: context.disabled,
password: context.password,
privKey: context.privKey, privKey: context.privKey,
emailHash: context.emailHash, emailHash: context.emailHash,
createdAt: context.createdAt, createdAt: context.createdAt,
emailChecked: context.emailChecked, emailChecked: context.emailChecked,
passphraseShown: context.passphraseShown,
language: context.language, language: context.language,
disabled: context.disabled,
groupId: context.groupId,
publisherId: context.publisherId, publisherId: context.publisherId,
} }
} }
const createLoginUserBackupContext = (
context: UserInterface,
loginUser: LoginUser,
): LoginUserBackupContext => {
return {
passphrase: context.passphrase,
mnemonicType: context.mnemonicType,
userId: loginUser.id,
}
}
const createServerUserContext = (context: UserInterface): ServerUserContext => { const createServerUserContext = (context: UserInterface): ServerUserContext => {
return { return {
role: context.role, role: context.role,
username: context.username,
password: context.serverUserPassword, password: context.serverUserPassword,
email: context.email, email: context.email,
activated: context.activated, activated: context.activated,
@ -103,13 +65,6 @@ const createServerUserContext = (context: UserInterface): ServerUserContext => {
} }
} }
const createLoginUserRolesContext = (loginUser: LoginUser): LoginUserRolesContext => {
return {
userId: loginUser.id,
roleId: 1,
}
}
const createBalanceContext = (context: UserInterface, user: User): BalanceContext => { const createBalanceContext = (context: UserInterface, user: User): BalanceContext => {
return { return {
modified: context.balanceModified, modified: context.balanceModified,

View File

@ -3,7 +3,7 @@ export const bibiBloxberg = {
firstName: 'Bibi', firstName: 'Bibi',
lastName: 'Bloxberg', lastName: 'Bloxberg',
username: 'bibi', username: 'bibi',
description: 'Hex Hex', // description: 'Hex Hex',
password: BigInt('12825419584724616625'), password: BigInt('12825419584724616625'),
pubKey: Buffer.from('42de7e4754625b730018c3b4ea745a4d043d9d867af352d0f08871793dfa6743', 'hex'), pubKey: Buffer.from('42de7e4754625b730018c3b4ea745a4d043d9d867af352d0f08871793dfa6743', 'hex'),
privKey: Buffer.from( privKey: Buffer.from(
@ -13,7 +13,6 @@ export const bibiBloxberg = {
emailHash: Buffer.from('38a0d8c8658a5681cc1180c5d9e2b2a18e4f611db8ab3ca61de4aa91ae94219b', 'hex'), emailHash: Buffer.from('38a0d8c8658a5681cc1180c5d9e2b2a18e4f611db8ab3ca61de4aa91ae94219b', 'hex'),
createdAt: new Date('2021-11-26T11:32:16'), createdAt: new Date('2021-11-26T11:32:16'),
emailChecked: true, emailChecked: true,
passphraseShown: false,
language: 'de', language: 'de',
disabled: false, disabled: false,
groupId: 1, groupId: 1,

View File

@ -3,7 +3,7 @@ export const bobBaumeister = {
firstName: 'Bob', firstName: 'Bob',
lastName: 'der Baumeister', lastName: 'der Baumeister',
username: 'bob', username: 'bob',
description: 'Können wir das schaffen? Ja, wir schaffen das!', // description: 'Können wir das schaffen? Ja, wir schaffen das!',
password: BigInt('3296644341468822636'), password: BigInt('3296644341468822636'),
pubKey: Buffer.from('a509d9a146374fc975e3677db801ae8a4a83bff9dea96da64053ff6de6b2dd7e', 'hex'), pubKey: Buffer.from('a509d9a146374fc975e3677db801ae8a4a83bff9dea96da64053ff6de6b2dd7e', 'hex'),
privKey: Buffer.from( privKey: Buffer.from(
@ -13,7 +13,6 @@ export const bobBaumeister = {
emailHash: Buffer.from('4b8ce4e175587aaf33da19e272719da1a547daff557820191fab0c65c5a3b7f1', 'hex'), emailHash: Buffer.from('4b8ce4e175587aaf33da19e272719da1a547daff557820191fab0c65c5a3b7f1', 'hex'),
createdAt: new Date('2021-11-26T11:36:31'), createdAt: new Date('2021-11-26T11:36:31'),
emailChecked: true, emailChecked: true,
passphraseShown: false,
language: 'de', language: 'de',
disabled: false, disabled: false,
groupId: 1, groupId: 1,

View File

@ -3,13 +3,12 @@ export const garrickOllivander = {
firstName: 'Garrick', firstName: 'Garrick',
lastName: 'Ollivander', lastName: 'Ollivander',
username: 'garrick', username: 'garrick',
description: `Curious ... curious ... // description: `Curious ... curious ...
Renowned wandmaker Mr Ollivander owns the wand shop Ollivanders: Makers of Fine Wands Since 382 BC in Diagon Alley. His shop is widely considered the best place to purchase a wand.`, // Renowned wandmaker Mr Ollivander owns the wand shop Ollivanders: Makers of Fine Wands Since 382 BC in Diagon Alley. His shop is widely considered the best place to purchase a wand.`,
password: BigInt('0'), password: BigInt('0'),
emailHash: Buffer.from('91e358000e908146342789979d62a7255b2b88a71dad0c6a10e32af44be57886', 'hex'), emailHash: Buffer.from('91e358000e908146342789979d62a7255b2b88a71dad0c6a10e32af44be57886', 'hex'),
createdAt: new Date('2022-01-10T10:23:17'), createdAt: new Date('2022-01-10T10:23:17'),
emailChecked: false, emailChecked: false,
passphraseShown: false,
language: 'en', language: 'en',
disabled: false, disabled: false,
groupId: 1, groupId: 1,

View File

@ -3,7 +3,7 @@ export const peterLustig = {
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
username: 'peter', username: 'peter',
description: 'Latzhose und Nickelbrille', // description: 'Latzhose und Nickelbrille',
password: BigInt('3917921995996627700'), password: BigInt('3917921995996627700'),
pubKey: Buffer.from('7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770', 'hex'), pubKey: Buffer.from('7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770', 'hex'),
privKey: Buffer.from( privKey: Buffer.from(
@ -13,7 +13,6 @@ export const peterLustig = {
emailHash: Buffer.from('9f700e6f6ec351a140b674c0edd4479509697b023bd8bee8826915ef6c2af036', 'hex'), emailHash: Buffer.from('9f700e6f6ec351a140b674c0edd4479509697b023bd8bee8826915ef6c2af036', 'hex'),
createdAt: new Date('2020-11-25T10:48:43'), createdAt: new Date('2020-11-25T10:48:43'),
emailChecked: true, emailChecked: true,
passphraseShown: false,
language: 'de', language: 'de',
disabled: false, disabled: false,
groupId: 1, groupId: 1,

View File

@ -3,7 +3,7 @@ export const raeuberHotzenplotz = {
firstName: 'Räuber', firstName: 'Räuber',
lastName: 'Hotzenplotz', lastName: 'Hotzenplotz',
username: 'räuber', username: 'räuber',
description: 'Pfefferpistole', // description: 'Pfefferpistole',
password: BigInt('12123692783243004812'), password: BigInt('12123692783243004812'),
pubKey: Buffer.from('d7c70f94234dff071d982aa8f41583876c356599773b5911b39080da2b8c2d2b', 'hex'), pubKey: Buffer.from('d7c70f94234dff071d982aa8f41583876c356599773b5911b39080da2b8c2d2b', 'hex'),
privKey: Buffer.from( privKey: Buffer.from(
@ -13,7 +13,6 @@ export const raeuberHotzenplotz = {
emailHash: Buffer.from('ec8d34112adb40ff2f6538b05660b03440372690f034cd7d6322d17020233c77', 'hex'), emailHash: Buffer.from('ec8d34112adb40ff2f6538b05660b03440372690f034cd7d6322d17020233c77', 'hex'),
createdAt: new Date('2021-11-26T11:32:16'), createdAt: new Date('2021-11-26T11:32:16'),
emailChecked: true, emailChecked: true,
passphraseShown: false,
language: 'de', language: 'de',
disabled: false, disabled: false,
groupId: 1, groupId: 1,

View File

@ -22,8 +22,6 @@ export const updateUserInfos = gql`
mutation( mutation(
$firstName: String $firstName: String
$lastName: String $lastName: String
$description: String
$username: String
$password: String $password: String
$passwordNew: String $passwordNew: String
$locale: String $locale: String
@ -32,8 +30,6 @@ export const updateUserInfos = gql`
updateUserInfos( updateUserInfos(
firstName: $firstName firstName: $firstName
lastName: $lastName lastName: $lastName
description: $description
username: $username
password: $password password: $password
passwordNew: $passwordNew passwordNew: $passwordNew
language: $locale language: $locale

View File

@ -4,11 +4,9 @@ export const login = gql`
query($email: String!, $password: String!, $publisherId: Int) { query($email: String!, $password: String!, $publisherId: Int) {
login(email: $email, password: $password, publisherId: $publisherId) { login(email: $email, password: $password, publisherId: $publisherId) {
email email
username
firstName firstName
lastName lastName
language language
description
coinanimation coinanimation
klickTipp { klickTipp {
newsletterState newsletterState
@ -24,11 +22,9 @@ export const verifyLogin = gql`
query { query {
verifyLogin { verifyLogin {
email email
username
firstName firstName
lastName lastName
language language
description
coinanimation coinanimation
klickTipp { klickTipp {
newsletterState newsletterState
@ -93,12 +89,6 @@ export const sendResetPasswordEmail = gql`
} }
` `
export const checkUsername = gql`
query($username: String!) {
checkUsername(username: $username)
}
`
export const listGDTEntriesQuery = gql` export const listGDTEntriesQuery = gql`
query($currentPage: Int!, $pageSize: Int!) { query($currentPage: Int!, $pageSize: Int!) {
listGDTEntries(currentPage: $currentPage, pageSize: $pageSize) { listGDTEntries(currentPage: $currentPage, pageSize: $pageSize) {

View File

@ -69,6 +69,7 @@
"memo": "Nachricht", "memo": "Nachricht",
"message": "Nachricht", "message": "Nachricht",
"new_balance": "Neuer Kontostand nach Bestätigung", "new_balance": "Neuer Kontostand nach Bestätigung",
"no_gdd_available": "Du hast keine GDD zum versenden.",
"password": "Passwort", "password": "Passwort",
"passwordRepeat": "Passwort wiederholen", "passwordRepeat": "Passwort wiederholen",
"password_new": "Neues Passwort", "password_new": "Neues Passwort",

View File

@ -69,6 +69,7 @@
"memo": "Message", "memo": "Message",
"message": "Message", "message": "Message",
"new_balance": "Account balance after confirmation", "new_balance": "Account balance after confirmation",
"no_gdd_available": "You do not have GDD to send.",
"password": "Password", "password": "Password",
"passwordRepeat": "Repeat password", "passwordRepeat": "Repeat password",
"password_new": "New password", "password_new": "New password",

View File

@ -15,18 +15,15 @@ export const mutations = {
email: (state, email) => { email: (state, email) => {
state.email = email state.email = email
}, },
username: (state, username) => { // username: (state, username) => {
state.username = username // state.username = username
}, // },
firstName: (state, firstName) => { firstName: (state, firstName) => {
state.firstName = firstName state.firstName = firstName
}, },
lastName: (state, lastName) => { lastName: (state, lastName) => {
state.lastName = lastName state.lastName = lastName
}, },
description: (state, description) => {
state.description = description
},
token: (state, token) => { token: (state, token) => {
state.token = token state.token = token
}, },
@ -56,10 +53,9 @@ export const actions = {
login: ({ dispatch, commit }, data) => { login: ({ dispatch, commit }, data) => {
commit('email', data.email) commit('email', data.email)
commit('language', data.language) commit('language', data.language)
commit('username', data.username) // commit('username', data.username)
commit('firstName', data.firstName) commit('firstName', data.firstName)
commit('lastName', data.lastName) commit('lastName', data.lastName)
commit('description', data.description)
commit('coinanimation', data.coinanimation) commit('coinanimation', data.coinanimation)
commit('newsletterState', data.klickTipp.newsletterState) commit('newsletterState', data.klickTipp.newsletterState)
commit('hasElopage', data.hasElopage) commit('hasElopage', data.hasElopage)
@ -69,10 +65,9 @@ export const actions = {
logout: ({ commit, state }) => { logout: ({ commit, state }) => {
commit('token', null) commit('token', null)
commit('email', null) commit('email', null)
commit('username', '') // commit('username', '')
commit('firstName', '') commit('firstName', '')
commit('lastName', '') commit('lastName', '')
commit('description', '')
commit('coinanimation', true) commit('coinanimation', true)
commit('newsletterState', null) commit('newsletterState', null)
commit('hasElopage', false) commit('hasElopage', false)
@ -96,8 +91,7 @@ try {
language: null, language: null,
firstName: '', firstName: '',
lastName: '', lastName: '',
username: '', // username: '',
description: '',
token: null, token: null,
isAdmin: false, isAdmin: false,
coinanimation: true, coinanimation: true,

View File

@ -18,10 +18,8 @@ const {
language, language,
email, email,
token, token,
username,
firstName, firstName,
lastName, lastName,
description,
coinanimation, coinanimation,
newsletterState, newsletterState,
publisherId, publisherId,
@ -65,14 +63,6 @@ describe('Vuex store', () => {
}) })
}) })
describe('username', () => {
it('sets the state of username', () => {
const state = { username: null }
username(state, 'user')
expect(state.username).toEqual('user')
})
})
describe('firstName', () => { describe('firstName', () => {
it('sets the state of firstName', () => { it('sets the state of firstName', () => {
const state = { firstName: null } const state = { firstName: null }
@ -89,14 +79,6 @@ describe('Vuex store', () => {
}) })
}) })
describe('description', () => {
it('sets the state of description', () => {
const state = { description: null }
description(state, 'Nickelbrille')
expect(state.description).toEqual('Nickelbrille')
})
})
describe('coinanimation', () => { describe('coinanimation', () => {
it('sets the state of coinanimation', () => { it('sets the state of coinanimation', () => {
const state = { coinanimation: true } const state = { coinanimation: true }
@ -169,10 +151,8 @@ describe('Vuex store', () => {
const commitedData = { const commitedData = {
email: 'user@example.org', email: 'user@example.org',
language: 'de', language: 'de',
username: 'peter',
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
description: 'Nickelbrille',
coinanimation: false, coinanimation: false,
klickTipp: { klickTipp: {
newsletterState: true, newsletterState: true,
@ -182,9 +162,9 @@ describe('Vuex store', () => {
isAdmin: true, isAdmin: true,
} }
it('calls eleven commits', () => { it('calls nine commits', () => {
login({ commit, state }, commitedData) login({ commit, state }, commitedData)
expect(commit).toHaveBeenCalledTimes(11) expect(commit).toHaveBeenCalledTimes(9)
}) })
it('commits email', () => { it('commits email', () => {
@ -197,49 +177,39 @@ describe('Vuex store', () => {
expect(commit).toHaveBeenNthCalledWith(2, 'language', 'de') expect(commit).toHaveBeenNthCalledWith(2, 'language', 'de')
}) })
it('commits username', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(3, 'username', 'peter')
})
it('commits firstName', () => { it('commits firstName', () => {
login({ commit, state }, commitedData) login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(4, 'firstName', 'Peter') expect(commit).toHaveBeenNthCalledWith(3, 'firstName', 'Peter')
}) })
it('commits lastName', () => { it('commits lastName', () => {
login({ commit, state }, commitedData) login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(5, 'lastName', 'Lustig') expect(commit).toHaveBeenNthCalledWith(4, 'lastName', 'Lustig')
})
it('commits description', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(6, 'description', 'Nickelbrille')
}) })
it('commits coinanimation', () => { it('commits coinanimation', () => {
login({ commit, state }, commitedData) login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(7, 'coinanimation', false) expect(commit).toHaveBeenNthCalledWith(5, 'coinanimation', false)
}) })
it('commits newsletterState', () => { it('commits newsletterState', () => {
login({ commit, state }, commitedData) login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(8, 'newsletterState', true) expect(commit).toHaveBeenNthCalledWith(6, 'newsletterState', true)
}) })
it('commits hasElopage', () => { it('commits hasElopage', () => {
login({ commit, state }, commitedData) login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(9, 'hasElopage', false) expect(commit).toHaveBeenNthCalledWith(7, 'hasElopage', false)
}) })
it('commits publisherId', () => { it('commits publisherId', () => {
login({ commit, state }, commitedData) login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(10, 'publisherId', 1234) expect(commit).toHaveBeenNthCalledWith(8, 'publisherId', 1234)
}) })
it('commits isAdmin', () => { it('commits isAdmin', () => {
login({ commit, state }, commitedData) login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(11, 'isAdmin', true) expect(commit).toHaveBeenNthCalledWith(9, 'isAdmin', true)
}) })
}) })
@ -247,9 +217,9 @@ describe('Vuex store', () => {
const commit = jest.fn() const commit = jest.fn()
const state = {} const state = {}
it('calls eleven commits', () => { it('calls nine commits', () => {
logout({ commit, state }) logout({ commit, state })
expect(commit).toHaveBeenCalledTimes(11) expect(commit).toHaveBeenCalledTimes(9)
}) })
it('commits token', () => { it('commits token', () => {
@ -262,49 +232,39 @@ describe('Vuex store', () => {
expect(commit).toHaveBeenNthCalledWith(2, 'email', null) expect(commit).toHaveBeenNthCalledWith(2, 'email', null)
}) })
it('commits username', () => {
logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(3, 'username', '')
})
it('commits firstName', () => { it('commits firstName', () => {
logout({ commit, state }) logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(4, 'firstName', '') expect(commit).toHaveBeenNthCalledWith(3, 'firstName', '')
}) })
it('commits lastName', () => { it('commits lastName', () => {
logout({ commit, state }) logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(5, 'lastName', '') expect(commit).toHaveBeenNthCalledWith(4, 'lastName', '')
})
it('commits description', () => {
logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(6, 'description', '')
}) })
it('commits coinanimation', () => { it('commits coinanimation', () => {
logout({ commit, state }) logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(7, 'coinanimation', true) expect(commit).toHaveBeenNthCalledWith(5, 'coinanimation', true)
}) })
it('commits newsletterState', () => { it('commits newsletterState', () => {
logout({ commit, state }) logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(8, 'newsletterState', null) expect(commit).toHaveBeenNthCalledWith(6, 'newsletterState', null)
}) })
it('commits hasElopage', () => { it('commits hasElopage', () => {
logout({ commit, state }) logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(9, 'hasElopage', false) expect(commit).toHaveBeenNthCalledWith(7, 'hasElopage', false)
}) })
it('commits publisherId', () => { it('commits publisherId', () => {
logout({ commit, state }) logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(10, 'publisherId', null) expect(commit).toHaveBeenNthCalledWith(8, 'publisherId', null)
}) })
it('commits isAdmin', () => { it('commits isAdmin', () => {
logout({ commit, state }) logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(11, 'isAdmin', false) expect(commit).toHaveBeenNthCalledWith(9, 'isAdmin', false)
}) })
// how to get this working? // how to get this working?

View File

@ -1,7 +1,6 @@
import { configure, extend } from 'vee-validate' import { configure, extend } from 'vee-validate'
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
import { required, email, min, max, is_not } from 'vee-validate/dist/rules' import { required, email, min, max, is_not } from 'vee-validate/dist/rules'
import { checkUsername } from './graphql/queries'
export const loadAllRules = (i18nCallback) => { export const loadAllRules = (i18nCallback) => {
configure({ configure({
@ -49,32 +48,6 @@ export const loadAllRules = (i18nCallback) => {
}, },
}) })
extend('gddUsernameUnique', {
async validate(value) {
this.$apollo
.query({
query: checkUsername,
variables: {
username: value,
},
})
.then((result) => {
return result.data.checkUsername
})
.catch(() => {
return false
})
},
message: (_, values) => i18nCallback.t('form.validation.usernmae-unique', values),
})
extend('gddUsernameRgex', {
validate(value) {
return !!value.match(/^[a-zA-Z][-_a-zA-Z0-9]{2,}$/)
},
message: (_, values) => i18nCallback.t('form.validation.usernmae-regex', values),
})
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
extend('is_not', { extend('is_not', {
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase

View File

@ -97,6 +97,12 @@ export default {
onReset() { onReset() {
this.currentTransactionStep = 0 this.currentTransactionStep = 0
}, },
updateTransactions(pagination) {
this.$emit('update-transactions', pagination)
},
},
created() {
this.updateTransactions(0)
}, },
} }
</script> </script>

View File

@ -21,7 +21,7 @@ describe('GddSend', () => {
} }
const propsData = { const propsData = {
balance: 100.0, balance: 0.0,
} }
const Wrapper = () => { const Wrapper = () => {
@ -37,7 +37,44 @@ describe('GddSend', () => {
expect(wrapper.find('div.transaction-form').exists()).toBeTruthy() expect(wrapper.find('div.transaction-form').exists()).toBeTruthy()
}) })
describe('transaction form disable because balance 0,0 GDD', () => {
it('has a disabled input field of type email', () => {
expect(wrapper.find('#input-group-1').find('input').attributes('disabled')).toBe('disabled')
})
it('has a disabled input field for amount', () => {
expect(wrapper.find('#input-2').find('input').attributes('disabled')).toBe('disabled')
})
it('has a disabled textarea field ', () => {
expect(wrapper.find('#input-3').find('textarea').attributes('disabled')).toBe('disabled')
})
it('has a message indicating that there are no GDDs to send ', () => {
expect(wrapper.find('.text-danger').text()).toBe('form.no_gdd_available')
})
it('has no reset button and no submit button ', () => {
expect(wrapper.find('.test-buttons').exists()).toBeFalsy()
})
})
describe('transaction form', () => { describe('transaction form', () => {
beforeEach(() => {
wrapper.setProps({ balance: 100.0 })
})
describe('transaction form show because balance 100,0 GDD', () => {
it('has no warning message ', () => {
expect(wrapper.find('.text-danger').exists()).toBeFalsy()
})
it('has a reset button', () => {
expect(wrapper.find('.test-buttons').findAll('button').at(0).attributes('type')).toBe(
'reset',
)
})
it('has a submit button', () => {
expect(wrapper.find('.test-buttons').findAll('button').at(1).attributes('type')).toBe(
'submit',
)
})
})
describe('email field', () => { describe('email field', () => {
it('has an input field of type email', () => { it('has an input field of type email', () => {
expect(wrapper.find('#input-group-1').find('input').attributes('type')).toBe('email') expect(wrapper.find('#input-group-1').find('input').attributes('type')).toBe('email')

View File

@ -41,6 +41,7 @@
placeholder="E-Mail" placeholder="E-Mail"
style="font-size: large" style="font-size: large"
class="pl-3" class="pl-3"
:disabled="isBalanceDisabled"
></b-form-input> ></b-form-input>
</b-input-group> </b-input-group>
<b-col v-if="errors"> <b-col v-if="errors">
@ -76,6 +77,7 @@
:placeholder="$n(0.01)" :placeholder="$n(0.01)"
style="font-size: large" style="font-size: large"
class="pl-3" class="pl-3"
:disabled="isBalanceDisabled"
></b-form-input> ></b-form-input>
</b-input-group> </b-input-group>
<b-col v-if="errors"> <b-col v-if="errors">
@ -105,6 +107,7 @@
v-model="form.memo" v-model="form.memo"
class="pl-3" class="pl-3"
style="font-size: large" style="font-size: large"
:disabled="isBalanceDisabled"
></b-form-textarea> ></b-form-textarea>
</b-input-group> </b-input-group>
<b-col v-if="errors"> <b-col v-if="errors">
@ -114,7 +117,10 @@
</div> </div>
<br /> <br />
<b-row> <div v-if="!!isBalanceDisabled" class="text-danger">
{{ $t('form.no_gdd_available') }}
</div>
<b-row v-else class="test-buttons">
<b-col> <b-col>
<b-button type="reset" variant="secondary" @click="onReset"> <b-button type="reset" variant="secondary" @click="onReset">
{{ $t('form.reset') }} {{ $t('form.reset') }}
@ -192,6 +198,11 @@ export default {
this.form.email = this.form.email.trim() this.form.email = this.form.email.trim()
}, },
}, },
computed: {
isBalanceDisabled() {
return this.balance <= 0 ? 'disabled' : false
},
},
} }
</script> </script>
<style> <style>

View File

@ -19,7 +19,6 @@ describe('UserCard_FormUserData', () => {
state: { state: {
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
description: '',
}, },
commit: storeCommitMock, commit: storeCommitMock,
}, },
@ -59,10 +58,6 @@ describe('UserCard_FormUserData', () => {
expect(wrapper.findAll('div.col').at(4).text()).toBe('Lustig') expect(wrapper.findAll('div.col').at(4).text()).toBe('Lustig')
}) })
it('renders the description', () => {
expect(wrapper.findAll('div.col').at(6).text()).toBe('')
})
describe('edit user data', () => { describe('edit user data', () => {
beforeEach(async () => { beforeEach(async () => {
await wrapper.find('svg.bi-pencil').trigger('click') await wrapper.find('svg.bi-pencil').trigger('click')
@ -80,11 +75,9 @@ describe('UserCard_FormUserData', () => {
it('does not change the userdate when cancel is clicked', async () => { it('does not change the userdate when cancel is clicked', async () => {
await wrapper.findAll('input').at(0).setValue('Petra') await wrapper.findAll('input').at(0).setValue('Petra')
await wrapper.findAll('input').at(1).setValue('Lustiger') await wrapper.findAll('input').at(1).setValue('Lustiger')
await wrapper.find('textarea').setValue('Keine Nickelbrille')
await wrapper.find('svg.bi-x-circle').trigger('click') await wrapper.find('svg.bi-x-circle').trigger('click')
expect(wrapper.findAll('div.col').at(2).text()).toBe('Peter') expect(wrapper.findAll('div.col').at(2).text()).toBe('Peter')
expect(wrapper.findAll('div.col').at(4).text()).toBe('Lustig') expect(wrapper.findAll('div.col').at(4).text()).toBe('Lustig')
expect(wrapper.findAll('div.col').at(6).text()).toBe('')
}) })
it('has a submit button', () => { it('has a submit button', () => {
@ -108,7 +101,6 @@ describe('UserCard_FormUserData', () => {
jest.clearAllMocks() jest.clearAllMocks()
await wrapper.findAll('input').at(0).setValue('Petra') await wrapper.findAll('input').at(0).setValue('Petra')
await wrapper.findAll('input').at(1).setValue('Lustiger') await wrapper.findAll('input').at(1).setValue('Lustiger')
await wrapper.find('textarea').setValue('Keine Nickelbrille')
await wrapper.find('form').trigger('keyup') await wrapper.find('form').trigger('keyup')
await wrapper.find('button[type="submit"]').trigger('click') await wrapper.find('button[type="submit"]').trigger('click')
await flushPromises() await flushPromises()
@ -120,7 +112,6 @@ describe('UserCard_FormUserData', () => {
variables: { variables: {
firstName: 'Petra', firstName: 'Petra',
lastName: 'Lustiger', lastName: 'Lustiger',
description: 'Keine Nickelbrille',
}, },
}), }),
) )
@ -134,10 +125,6 @@ describe('UserCard_FormUserData', () => {
expect(storeCommitMock).toBeCalledWith('lastName', 'Lustiger') expect(storeCommitMock).toBeCalledWith('lastName', 'Lustiger')
}) })
it('commits description to store', () => {
expect(storeCommitMock).toBeCalledWith('description', 'Keine Nickelbrille')
})
it('toasts a success message', () => { it('toasts a success message', () => {
expect(toastSuccessMock).toBeCalledWith('settings.name.change-success') expect(toastSuccessMock).toBeCalledWith('settings.name.change-success')
}) })
@ -155,7 +142,6 @@ describe('UserCard_FormUserData', () => {
jest.clearAllMocks() jest.clearAllMocks()
await wrapper.findAll('input').at(0).setValue('Petra') await wrapper.findAll('input').at(0).setValue('Petra')
await wrapper.findAll('input').at(1).setValue('Lustiger') await wrapper.findAll('input').at(1).setValue('Lustiger')
await wrapper.find('textarea').setValue('Keine Nickelbrille')
await wrapper.find('form').trigger('keyup') await wrapper.find('form').trigger('keyup')
await wrapper.find('button[type="submit"]').trigger('click') await wrapper.find('button[type="submit"]').trigger('click')
await flushPromises() await flushPromises()
@ -167,7 +153,6 @@ describe('UserCard_FormUserData', () => {
variables: { variables: {
firstName: 'Petra', firstName: 'Petra',
lastName: 'Lustiger', lastName: 'Lustiger',
description: 'Keine Nickelbrille',
}, },
}), }),
) )

View File

@ -43,17 +43,6 @@
<b-input type="text" v-model="form.lastName"></b-input> <b-input type="text" v-model="form.lastName"></b-input>
</b-col> </b-col>
</b-row> </b-row>
<b-row class="mb-3" v-show="false">
<b-col class="col-12">
<small>{{ $t('form.description') }}</small>
</b-col>
<b-col v-if="showUserData" class="col-12">
{{ form.description }}
</b-col>
<b-col v-else class="col-12">
<b-textarea rows="3" max-rows="6" v-model="form.description"></b-textarea>
</b-col>
</b-row>
<b-row class="text-right" v-if="!showUserData"> <b-row class="text-right" v-if="!showUserData">
<b-col> <b-col>
@ -85,7 +74,6 @@ export default {
form: { form: {
firstName: this.$store.state.firstName, firstName: this.$store.state.firstName,
lastName: this.$store.state.lastName, lastName: this.$store.state.lastName,
description: this.$store.state.description,
}, },
loading: true, loading: true,
} }
@ -94,14 +82,12 @@ export default {
cancelEdit() { cancelEdit() {
this.form.firstName = this.$store.state.firstName this.form.firstName = this.$store.state.firstName
this.form.lastName = this.$store.state.lastName this.form.lastName = this.$store.state.lastName
this.form.description = this.$store.state.description
this.showUserData = true this.showUserData = true
}, },
loadSubmitButton() { loadSubmitButton() {
if ( if (
this.form.firstName !== this.$store.state.firstName || this.form.firstName !== this.$store.state.firstName ||
this.form.lastName !== this.$store.state.lastName || this.form.lastName !== this.$store.state.lastName
this.form.description !== this.$store.state.description
) { ) {
this.loading = false this.loading = false
} else { } else {
@ -116,13 +102,11 @@ export default {
variables: { variables: {
firstName: this.form.firstName, firstName: this.form.firstName,
lastName: this.form.lastName, lastName: this.form.lastName,
description: this.form.description,
}, },
}) })
.then(() => { .then(() => {
this.$store.commit('firstName', this.form.firstName) this.$store.commit('firstName', this.form.firstName)
this.$store.commit('lastName', this.form.lastName) this.$store.commit('lastName', this.form.lastName)
this.$store.commit('description', this.form.description)
this.showUserData = true this.showUserData = true
this.$toasted.success(this.$t('settings.name.change-success')) this.$toasted.success(this.$t('settings.name.change-success'))
}) })

View File

@ -34,6 +34,14 @@ export default {
balance: { type: Number, default: 0 }, balance: { type: Number, default: 0 },
transactionCount: { type: Number, default: 0 }, transactionCount: { type: Number, default: 0 },
}, },
methods: {
updateTransactions(pagination) {
this.$emit('update-transactions', pagination)
},
},
created() {
this.updateTransactions(0)
},
} }
</script> </script>
<style> <style>

1
gn

@ -1 +0,0 @@
Subproject commit 5437e2f882c54efe4f501f7cd0d97f53806d0b74