+
Die anzahl der offene Schöpfungen stimmen nicht! Diese wird bei absenden im $store
hochgezählt. Die Liste die hier angezeigt wird ist SIMULIERT!
@@ -8,9 +8,8 @@
class="mt-4"
type="PageCreationConfirm"
:itemsUser="confirmResult"
- :creation="creation"
:fieldsTable="fields"
- @update-confirm-result="updateConfirmResult"
+ @remove-confirm-result="removeConfirmResult"
/>
@@ -18,7 +17,7 @@
import UserTable from '../components/UserTable.vue'
export default {
- name: 'creation_confirm',
+ name: 'CreationConfirm',
components: {
UserTable,
},
@@ -28,11 +27,23 @@ export default {
fields: [
{ key: 'bookmark', label: 'löschen' },
{ key: 'email', label: 'Email' },
- { key: 'first_name', label: 'Vorname' },
- { key: 'last_name', label: 'Nachname' },
- { key: 'creation_gdd', label: 'GDD' },
+ { key: 'firstName', label: 'Vorname' },
+ { key: 'lastName', label: 'Nachname' },
+ {
+ key: 'creation_gdd',
+ label: 'Schöpfung',
+ formatter: (value) => {
+ return value + ' GDD'
+ },
+ },
{ key: 'text', label: 'Text' },
- { key: 'creation_date', label: 'Datum' },
+ {
+ key: 'creation_date',
+ label: 'Datum',
+ formatter: (value) => {
+ return value.long
+ },
+ },
{ key: 'creation_moderator', label: 'Moderator' },
{ key: 'edit_creation', label: 'ändern' },
{ key: 'confirm', label: 'speichern' },
@@ -41,67 +52,81 @@ export default {
{
id: 1,
email: 'dickerson@web.de',
- first_name: 'Dickerson',
- last_name: 'Macdonald',
- creation: '450,200,700',
+ firstName: 'Dickerson',
+ lastName: 'Macdonald',
+ creation: '[450,200,700]',
creation_gdd: '1000',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam ',
- creation_date: '01/11/2021',
+ creation_date: {
+ short: 'November',
+ long: '22/11/2021',
+ },
creation_moderator: 'Manuela Gast',
},
{
id: 2,
email: 'larsen@woob.de',
- first_name: 'Larsen',
- last_name: 'Shaw',
- creation: '300,200,1000',
+ firstName: 'Larsen',
+ lastName: 'Shaw',
+ creation: '[300,200,1000]',
creation_gdd: '1000',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam ',
- creation_date: '01/11/2021',
+ creation_date: {
+ short: 'November',
+ long: '03/11/2021',
+ },
creation_moderator: 'Manuela Gast',
},
{
id: 3,
email: 'geneva@tete.de',
- first_name: 'Geneva',
- last_name: 'Wilson',
- creation: '350,200,900',
+ firstName: 'Geneva',
+ lastName: 'Wilson',
+ creation: '[350,200,900]',
creation_gdd: '1000',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam',
- creation_date: '01/11/2021',
+ creation_date: {
+ short: 'September',
+ long: '27/09/2021',
+ },
creation_moderator: 'Manuela Gast',
},
{
id: 4,
email: 'viewrter@asdfvb.com',
- first_name: 'Soledare',
- last_name: 'Takker',
+ firstName: 'Soledare',
+ lastName: 'Takker',
+ creation: '[100,400,800]',
creation_gdd: '500',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo ',
-
- creation_date: '01/10/2021',
+ creation_date: {
+ short: 'Oktober',
+ long: '12/10/2021',
+ },
creation_moderator: 'Evelyn Roller',
},
{
id: 5,
email: 'dickerson@web.de',
- first_name: 'Dickerson',
- last_name: 'Macdonald',
- creation: '100,400,800',
+ firstName: 'Dickerson',
+ lastName: 'Macdonald',
+ creation: '[100,400,800]',
creation_gdd: '200',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At',
- creation_date: '01/09/2021',
+ creation_date: {
+ short: 'September',
+ long: '05/09/2021',
+ },
creation_moderator: 'Manuela Gast',
},
],
- creation: [null, null, null],
}
},
methods: {
- updateConfirmResult(e, event) {
+ removeConfirmResult(e, event) {
if (event === 'remove') {
let index = 0
let findArr = {}
diff --git a/admin/src/pages/UserSearch.vue b/admin/src/pages/UserSearch.vue
index c7ed5ffef..ae0ade7b2 100644
--- a/admin/src/pages/UserSearch.vue
+++ b/admin/src/pages/UserSearch.vue
@@ -1,11 +1,12 @@
-
+
Usersuche
diff --git a/admin/src/router/router.test.js b/admin/src/router/router.test.js
new file mode 100644
index 000000000..8e2e70d4d
--- /dev/null
+++ b/admin/src/router/router.test.js
@@ -0,0 +1,92 @@
+import router from './router'
+
+describe('router', () => {
+ describe('options', () => {
+ const { options } = router
+ const { scrollBehavior, routes } = options
+
+ it('has "/admin" as base', () => {
+ expect(options).toEqual(
+ expect.objectContaining({
+ base: '/admin',
+ }),
+ )
+ })
+
+ it('has "active" as linkActiveClass', () => {
+ expect(options).toEqual(
+ expect.objectContaining({
+ linkActiveClass: 'active',
+ }),
+ )
+ })
+
+ it('has "history" as mode', () => {
+ expect(options).toEqual(
+ expect.objectContaining({
+ mode: 'history',
+ }),
+ )
+ })
+
+ describe('scroll behavior', () => {
+ it('returns save position when given', () => {
+ expect(scrollBehavior({}, {}, 'given')).toBe('given')
+ })
+
+ it('returns selector when hash is given', () => {
+ expect(scrollBehavior({ hash: '#to' }, {})).toEqual({ selector: '#to' })
+ })
+
+ it('returns top left coordinates as default', () => {
+ expect(scrollBehavior({}, {})).toEqual({ x: 0, y: 0 })
+ })
+ })
+
+ describe('routes', () => {
+ it('has "/overview" as default', async () => {
+ const component = await routes.find((r) => r.path === '/').component()
+ expect(component.default.name).toBe('overview')
+ })
+
+ it('has fourteen routes defined', () => {
+ expect(routes).toHaveLength(6)
+ })
+
+ describe('overview', () => {
+ it('loads the "Overview" component', async () => {
+ const component = await routes.find((r) => r.path === '/overview').component()
+ expect(component.default.name).toBe('overview')
+ })
+ })
+
+ describe('user', () => {
+ it('loads the "UserSearch" component', async () => {
+ const component = await routes.find((r) => r.path === '/user').component()
+ expect(component.default.name).toBe('UserSearch')
+ })
+ })
+
+ describe('creation', () => {
+ it('loads the "Creation" component', async () => {
+ const component = await routes.find((r) => r.path === '/creation').component()
+ expect(component.default.name).toBe('Creation')
+ })
+ })
+
+ describe('creation-confirm', () => {
+ it('loads the "CreationConfirm" component', async () => {
+ const component = await routes.find((r) => r.path === '/creation-confirm').component()
+ expect(component.default.name).toBe('CreationConfirm')
+ })
+ })
+
+ describe('not found page', () => {
+ it('renders the "NotFound" component', async () => {
+ const component = await routes.find((r) => r.path === '*').component()
+ expect(component.default.name).toEqual('not-found')
+ })
+ })
+ })
+ })
+})
diff --git a/admin/src/store/store.js b/admin/src/store/store.js
index 7820296c6..5f8b4249d 100644
--- a/admin/src/store/store.js
+++ b/admin/src/store/store.js
@@ -7,10 +7,10 @@ Vue.use(Vuex)
export const mutations = {
openCreationsPlus: (state, i) => {
- state.openCreations = state.openCreations + i
+ state.openCreations += i
},
openCreationsMinus: (state, i) => {
- state.openCreations = state.openCreations - i
+ state.openCreations -= i
},
resetOpenCreations: (state) => {
state.openCreations = 0
diff --git a/admin/src/store/store.test.js b/admin/src/store/store.test.js
new file mode 100644
index 000000000..81d75f05f
--- /dev/null
+++ b/admin/src/store/store.test.js
@@ -0,0 +1,39 @@
+import { mutations } from './store'
+
+const { token, openCreationsPlus, openCreationsMinus, resetOpenCreations } = mutations
+
+describe('Vuex store', () => {
+ describe('mutations', () => {
+ describe('token', () => {
+ it('sets the state of token', () => {
+ const state = { token: null }
+ token(state, '1234')
+ expect(state.token).toEqual('1234')
+ })
+ })
+
+ describe('openCreationsPlus', () => {
+ it('increases the open creations by a given number', () => {
+ const state = { openCreations: 0 }
+ openCreationsPlus(state, 12)
+ expect(state.openCreations).toEqual(12)
+ })
+ })
+
+ describe('openCreationsMinus', () => {
+ it('decreases the open creations by a given number', () => {
+ const state = { openCreations: 12 }
+ openCreationsMinus(state, 2)
+ expect(state.openCreations).toEqual(10)
+ })
+ })
+
+ describe('resetOpenCreations', () => {
+ it('sets the open creations to 0', () => {
+ const state = { openCreations: 24 }
+ resetOpenCreations(state)
+ expect(state.openCreations).toEqual(0)
+ })
+ })
+ })
+})
diff --git a/admin/src/views/Creation.spec.js b/admin/src/views/Creation.spec.js
new file mode 100644
index 000000000..02c2ed4ce
--- /dev/null
+++ b/admin/src/views/Creation.spec.js
@@ -0,0 +1,59 @@
+import { mount } from '@vue/test-utils'
+import Creation from './Creation.vue'
+
+const localVue = global.localVue
+
+const apolloQueryMock = jest.fn().mockResolvedValue({
+ data: {
+ searchUsers: [
+ {
+ firstName: 'Bibi',
+ lastName: 'Bloxberg',
+ email: 'bibi@bloxberg.de',
+ creation: [200, 400, 600],
+ },
+ ],
+ },
+})
+
+const toastErrorMock = jest.fn()
+
+const mocks = {
+ $apollo: {
+ query: apolloQueryMock,
+ },
+ $toasted: {
+ error: toastErrorMock,
+ },
+}
+
+describe('Creation', () => {
+ let wrapper
+
+ const Wrapper = () => {
+ return mount(Creation, { localVue, mocks })
+ }
+
+ describe('mount', () => {
+ beforeEach(() => {
+ wrapper = Wrapper()
+ })
+
+ it('has a DIV element with the class.creation', () => {
+ expect(wrapper.find('div.creation').exists()).toBeTruthy()
+ })
+
+ describe('apollo returns error', () => {
+ beforeEach(() => {
+ apolloQueryMock.mockRejectedValue({
+ message: 'Ouch',
+ })
+ wrapper = Wrapper()
+ })
+
+ it('toasts an error message', () => {
+ expect(toastErrorMock).toBeCalledWith('Ouch')
+ })
+ })
+ })
+})
diff --git a/admin/src/views/CreationConfirm.spec.js b/admin/src/views/CreationConfirm.spec.js
new file mode 100644
index 000000000..caf94cd37
--- /dev/null
+++ b/admin/src/views/CreationConfirm.spec.js
@@ -0,0 +1,53 @@
+import { mount } from '@vue/test-utils'
+import CreationConfirm from './CreationConfirm.vue'
+
+const localVue = global.localVue
+
+const storeCommitMock = jest.fn()
+
+const mocks = {
+ $store: {
+ commit: storeCommitMock,
+ },
+}
+
+describe('CreationConfirm', () => {
+ let wrapper
+
+ const Wrapper = () => {
+ return mount(CreationConfirm, { localVue, mocks })
+ }
+
+ describe('mount', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ wrapper = Wrapper()
+ })
+
+ it('has a DIV element with the class.creation-confirm', () => {
+ expect(wrapper.find('div.creation-confirm').exists()).toBeTruthy()
+ })
+
+ describe('store', () => {
+ it('commits resetOpenCreations to store', () => {
+ expect(storeCommitMock).toBeCalledWith('resetOpenCreations')
+ })
+
+ it('commits openCreationsPlus to store', () => {
+ expect(storeCommitMock).toBeCalledWith('openCreationsPlus', 5)
+ })
+ })
+
+ describe('confirm creation', () => {
+ beforeEach(async () => {
+ await wrapper
+ .findComponent({ name: 'UserTable' })
+ .vm.$emit('remove-confirm-result', 1, 'remove')
+ })
+
+ it('commits openCreationsMinus to store', () => {
+ expect(storeCommitMock).toBeCalledWith('openCreationsMinus', 1)
+ })
+ })
+ })
+})
diff --git a/admin/src/views/UserSearch.spec.js b/admin/src/views/UserSearch.spec.js
new file mode 100644
index 000000000..37ba4f5ec
--- /dev/null
+++ b/admin/src/views/UserSearch.spec.js
@@ -0,0 +1,59 @@
+import { mount } from '@vue/test-utils'
+import UserSearch from './UserSearch.vue'
+
+const localVue = global.localVue
+
+const apolloQueryMock = jest.fn().mockResolvedValue({
+ data: {
+ searchUsers: [
+ {
+ firstName: 'Bibi',
+ lastName: 'Bloxberg',
+ email: 'bibi@bloxberg.de',
+ creation: [200, 400, 600],
+ },
+ ],
+ },
+})
+
+const toastErrorMock = jest.fn()
+
+const mocks = {
+ $apollo: {
+ query: apolloQueryMock,
+ },
+ $toasted: {
+ error: toastErrorMock,
+ },
+}
+
+describe('UserSearch', () => {
+ let wrapper
+
+ const Wrapper = () => {
+ return mount(UserSearch, { localVue, mocks })
+ }
+
+ describe('mount', () => {
+ beforeEach(() => {
+ wrapper = Wrapper()
+ })
+
+ it('has a DIV element with the class.user-search', () => {
+ expect(wrapper.find('div.user-search').exists()).toBeTruthy()
+ })
+
+ describe('apollo returns error', () => {
+ beforeEach(() => {
+ apolloQueryMock.mockRejectedValue({
+ message: 'Ouch',
+ })
+ wrapper = Wrapper()
+ })
+
+ it('toasts an error message', () => {
+ expect(toastErrorMock).toBeCalledWith('Ouch')
+ })
+ })
+ })
+})
diff --git a/admin/test/testSetup.js b/admin/test/testSetup.js
index 118c0b1ce..caaa3c19c 100644
--- a/admin/test/testSetup.js
+++ b/admin/test/testSetup.js
@@ -1,10 +1,14 @@
import { createLocalVue } from '@vue/test-utils'
import Vue from 'vue'
-import { BootstrapVue } from 'bootstrap-vue'
+import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
+
+// without this async calls are not working
+import 'regenerator-runtime'
global.localVue = createLocalVue()
global.localVue.use(BootstrapVue)
+global.localVue.use(IconsPlugin)
// throw errors for vue warnings to force the programmers to take care about warnings
Vue.config.warnHandler = (w) => {
diff --git a/admin/yarn.lock b/admin/yarn.lock
index 0c8f9965e..d7960320b 100644
--- a/admin/yarn.lock
+++ b/admin/yarn.lock
@@ -10623,7 +10623,7 @@ regenerator-runtime@^0.11.0:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-regenerator-runtime@^0.13.4:
+regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.9:
version "0.13.9"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
diff --git a/backend/.env.dist b/backend/.env.dist
index b4a91026a..1b485b8e4 100644
--- a/backend/.env.dist
+++ b/backend/.env.dist
@@ -30,4 +30,6 @@ COMMUNITY_URL=
COMMUNITY_REGISTER_URL=
COMMUNITY_DESCRIPTION=
LOGIN_APP_SECRET=21ffbbc616fe
-LOGIN_SERVER_KEY=a51ef8ac7ef1abf162fb7a65261acd7a
\ No newline at end of file
+LOGIN_SERVER_KEY=a51ef8ac7ef1abf162fb7a65261acd7a
+
+WEBHOOK_ELOPAGE_SECRET=secret
\ No newline at end of file
diff --git a/backend/package.json b/backend/package.json
index bc098958f..375046363 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -20,6 +20,7 @@
"apollo-server-express": "^2.25.2",
"apollo-server-testing": "^2.25.2",
"axios": "^0.21.1",
+ "body-parser": "^1.19.0",
"class-validator": "^0.13.1",
"cors": "^2.8.5",
"dotenv": "^10.0.0",
diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts
index f21082d1d..eab1b4608 100644
--- a/backend/src/config/index.ts
+++ b/backend/src/config/index.ts
@@ -51,14 +51,25 @@ const email = {
EMAIL_PASSWORD: process.env.EMAIL_PASSWORD || 'xxx',
EMAIL_SMTP_URL: process.env.EMAIL_SMTP_URL || 'gmail.com',
EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '587',
-
EMAIL_LINK_VERIFICATION:
process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/vue/checkEmail/$1',
}
+const webhook = {
+ WEBHOOK_ELOPAGE_SECRET: process.env.WEBHOOK_ELOPAGE_SECRET || 'secret',
+}
+
// This is needed by graphql-directive-auth
process.env.APP_SECRET = server.JWT_SECRET
-const CONFIG = { ...server, ...database, ...klicktipp, ...community, ...email, ...loginServer }
+const CONFIG = {
+ ...server,
+ ...database,
+ ...klicktipp,
+ ...community,
+ ...email,
+ ...loginServer,
+ ...webhook,
+}
export default CONFIG
diff --git a/backend/src/graphql/arg/CreateUserArgs.ts b/backend/src/graphql/arg/CreateUserArgs.ts
index 3d09e56eb..0d63e76bb 100644
--- a/backend/src/graphql/arg/CreateUserArgs.ts
+++ b/backend/src/graphql/arg/CreateUserArgs.ts
@@ -12,10 +12,7 @@ export default class CreateUserArgs {
lastName: string
@Field(() => String)
- password: string
-
- @Field(() => String)
- language: string
+ language?: string // Will default to DEFAULT_LANGUAGE
@Field(() => Int, { nullable: true })
publisherId: number
diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts
index d72f19456..6245ef8ba 100644
--- a/backend/src/graphql/directive/isAuthorized.ts
+++ b/backend/src/graphql/directive/isAuthorized.ts
@@ -2,9 +2,6 @@
import { AuthChecker } from 'type-graphql'
-import CONFIG from '../../config'
-import { apiGet } from '../../apis/HttpRequest'
-
import decode from '../../jwt/decode'
import encode from '../../jwt/encode'
@@ -13,7 +10,7 @@ const isAuthorized: AuthChecker = async (
) => {
if (context.token) {
const decoded = decode(context.token)
- context.pubKey = decoded.pubKey
+ context.pubKey = Buffer.from(decoded.pubKey).toString('hex')
context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) })
return true
}
diff --git a/backend/src/graphql/model/UserAdmin.ts b/backend/src/graphql/model/UserAdmin.ts
new file mode 100644
index 000000000..cbf8dd21c
--- /dev/null
+++ b/backend/src/graphql/model/UserAdmin.ts
@@ -0,0 +1,16 @@
+import { ObjectType, Field } from 'type-graphql'
+
+@ObjectType()
+export class UserAdmin {
+ @Field(() => String)
+ email: string
+
+ @Field(() => String)
+ firstName: string
+
+ @Field(() => String)
+ lastName: string
+
+ @Field(() => [Number])
+ creation: number[]
+}
diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts
new file mode 100644
index 000000000..9af50faad
--- /dev/null
+++ b/backend/src/graphql/resolver/AdminResolver.ts
@@ -0,0 +1,26 @@
+import { Resolver, Query, Arg } from 'type-graphql'
+import { getCustomRepository } from 'typeorm'
+import { UserAdmin } from '../model/UserAdmin'
+import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
+
+@Resolver()
+export class AdminResolver {
+ @Query(() => [UserAdmin])
+ async searchUsers(@Arg('searchText') searchText: string): Promise {
+ const loginUserRepository = getCustomRepository(LoginUserRepository)
+ const loginUsers = await loginUserRepository.findBySearchCriteria(searchText)
+ const users = loginUsers.map((loginUser) => {
+ const user = new UserAdmin()
+ user.firstName = loginUser.firstName
+ user.lastName = loginUser.lastName
+ user.email = loginUser.email
+ user.creation = [
+ (Math.floor(Math.random() * 50) + 1) * 20,
+ (Math.floor(Math.random() * 50) + 1) * 20,
+ (Math.floor(Math.random() * 50) + 1) * 20,
+ ]
+ return user
+ })
+ return users
+ }
+}
diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts
index 968ce9d4c..ae9e318ae 100644
--- a/backend/src/graphql/resolver/TransactionResolver.ts
+++ b/backend/src/graphql/resolver/TransactionResolver.ts
@@ -613,9 +613,6 @@ export class TransactionResolver {
await queryRunner.commitTransaction()
} catch (e) {
await queryRunner.rollbackTransaction()
- throw e
- } finally {
- await queryRunner.release()
// TODO: This is broken code - we should never correct an autoincrement index in production
// according to dario it is required tho to properly work. The index of the table is used as
// index for the transaction which requires a chain without gaps
@@ -627,6 +624,9 @@ export class TransactionResolver {
// eslint-disable-next-line no-console
console.log('problems with reset auto increment: %o', error)
})
+ throw e
+ } finally {
+ await queryRunner.release()
}
// send notification email
// TODO: translate
diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts
index 71225c25a..c416a1874 100644
--- a/backend/src/graphql/resolver/UserResolver.ts
+++ b/backend/src/graphql/resolver/UserResolver.ts
@@ -22,14 +22,14 @@ import {
} from '../../middleware/klicktippMiddleware'
import { CheckEmailResponse } from '../model/CheckEmailResponse'
import { UserSettingRepository } from '../../typeorm/repository/UserSettingRepository'
+import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
import { Setting } from '../enum/Setting'
import { UserRepository } from '../../typeorm/repository/User'
import { LoginUser } from '@entity/LoginUser'
-import { LoginElopageBuys } from '@entity/LoginElopageBuys'
import { LoginUserBackup } from '@entity/LoginUserBackup'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { sendEMail } from '../../util/sendEMail'
-import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
+import { LoginElopageBuysRepository } from '../../typeorm/repository/LoginElopageBuys'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const sodium = require('sodium-native')
@@ -231,33 +231,33 @@ export class UserResolver {
@Ctx() context: any,
): Promise {
email = email.trim().toLowerCase()
- const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
-
- // if there is no user, throw an authentication error
- if (!result.success) {
- throw new Error(result.data)
- }
-
- context.setHeaders.push({
- key: 'token',
- value: encode(result.data.user.public_hex),
+ // const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
+ // UnsecureLogin
+ const loginUserRepository = getCustomRepository(LoginUserRepository)
+ const loginUser = await loginUserRepository.findByEmail(email).catch(() => {
+ throw new Error('No user with this credentials')
})
- const user = new User(result.data.user)
- // Hack: Database Field is not validated properly and not nullable
- if (user.publisherId === 0) {
- user.publisherId = undefined
+ if (!loginUser.emailChecked) throw new Error('user email not validated')
+ const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
+ const loginUserPassword = BigInt(loginUser.password.toString())
+ if (loginUserPassword !== passwordHash[0].readBigUInt64LE()) {
+ throw new Error('No user with this credentials')
}
- user.hasElopage = result.data.hasElopage
- // read additional settings from settings table
+ // TODO: If user has no pubKey Create it again and update user.
+
const userRepository = getCustomRepository(UserRepository)
let userEntity: void | DbUser
- userEntity = await userRepository.findByPubkeyHex(user.pubkey).catch(() => {
+ const loginUserPubKey = loginUser.pubKey
+ const loginUserPubKeyString = loginUserPubKey.toString('hex')
+ userEntity = await userRepository.findByPubkeyHex(loginUserPubKeyString).catch(() => {
+ // User not stored in state_users
+ // TODO: Check with production data - email is unique which can cause problems
userEntity = new DbUser()
- userEntity.firstName = user.firstName
- userEntity.lastName = user.lastName
- userEntity.username = user.username
- userEntity.email = user.email
- userEntity.pubkey = Buffer.from(user.pubkey, 'hex')
+ userEntity.firstName = loginUser.firstName
+ userEntity.lastName = loginUser.lastName
+ userEntity.username = loginUser.username
+ userEntity.email = loginUser.email
+ userEntity.pubkey = loginUser.pubKey
userRepository.save(userEntity).catch(() => {
throw new Error('error by save userEntity')
@@ -267,16 +267,28 @@ export class UserResolver {
throw new Error('error with cannot happen')
}
- // Save publisherId if Elopage is not yet registered
+ const user = new User()
+ user.email = email
+ user.firstName = loginUser.firstName
+ user.lastName = loginUser.lastName
+ user.username = loginUser.username
+ user.description = loginUser.description
+ user.pubkey = loginUserPubKeyString
+ user.language = loginUser.language
+
+ // Elopage Status & Stored PublisherId
+ user.hasElopage = await this.hasElopage({ pubKey: loginUserPubKeyString })
if (!user.hasElopage && publisherId) {
user.publisherId = publisherId
-
+ // TODO: Check if we can use updateUserInfos
+ // await this.updateUserInfos({ publisherId }, { pubKey: loginUser.pubKey })
const loginUserRepository = getCustomRepository(LoginUserRepository)
const loginUser = await loginUserRepository.findOneOrFail({ email: userEntity.email })
loginUser.publisherId = publisherId
loginUserRepository.save(loginUser)
}
+ // coinAnimation
const userSettingRepository = getCustomRepository(UserSettingRepository)
const coinanimation = await userSettingRepository
.readBoolean(userEntity.id, Setting.COIN_ANIMATION)
@@ -285,6 +297,12 @@ export class UserResolver {
})
user.coinanimation = coinanimation
user.isAdmin = true // TODO implement
+
+ context.setHeaders.push({
+ key: 'token',
+ value: encode(loginUser.pubKey),
+ })
+
return user
}
@@ -316,22 +334,23 @@ export class UserResolver {
@Mutation(() => String)
async createUser(
- @Args() { email, firstName, lastName, password, language, publisherId }: CreateUserArgs,
+ @Args() { email, firstName, lastName, language, publisherId }: CreateUserArgs,
): Promise {
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
// default int publisher_id = 0;
// Validate Language (no throw)
- if (!isLanguage(language)) {
+ if (!language || !isLanguage(language)) {
language = DEFAULT_LANGUAGE
}
+ // TODO: Register process
// Validate Password
- if (!isPassword(password)) {
- throw new Error(
- 'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
- )
- }
+ // if (!isPassword(password)) {
+ // throw new Error(
+ // 'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
+ // )
+ // }
// Validate username
// TODO: never true
@@ -349,11 +368,13 @@ export class UserResolver {
throw new Error(`User already exists.`)
}
- const passphrase = PassphraseGenerate()
- const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
- const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
+ // TODO: Register process
+ // const passphrase = PassphraseGenerate()
+ // const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
+ // const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
+ // const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
+
const emailHash = getEmailHash(email)
- const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
// Table: login_users
const loginUser = new LoginUser()
@@ -362,13 +383,15 @@ export class UserResolver {
loginUser.lastName = lastName
loginUser.username = username
loginUser.description = ''
- loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
+ // TODO: Register process
+ // loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
loginUser.emailHash = emailHash
loginUser.language = language
loginUser.groupId = 1
loginUser.publisherId = publisherId
- loginUser.pubKey = keyPair[0]
- loginUser.privKey = encryptedPrivkey
+ // TODO: Register process
+ // loginUser.pubKey = keyPair[0]
+ // loginUser.privKey = encryptedPrivkey
const queryRunner = getConnection().createQueryRunner()
await queryRunner.connect()
@@ -380,21 +403,24 @@ export class UserResolver {
throw new Error('insert user failed')
})
+ // TODO: Register process
// Table: login_user_backups
- const loginUserBackup = new LoginUserBackup()
- loginUserBackup.userId = loginUserId
- loginUserBackup.passphrase = passphrase.join(' ') + ' ' // login server saves trailing space
- loginUserBackup.mnemonicType = 2 // ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER;
+ // const loginUserBackup = new LoginUserBackup()
+ // loginUserBackup.userId = loginUserId
+ // loginUserBackup.passphrase = passphrase.join(' ') + ' ' // login server saves trailing space
+ // loginUserBackup.mnemonicType = 2 // ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER;
- await queryRunner.manager.save(loginUserBackup).catch((error) => {
- // eslint-disable-next-line no-console
- console.log('insert LoginUserBackup failed', error)
- throw new Error('insert user backup failed')
- })
+ // TODO: Register process
+ // await queryRunner.manager.save(loginUserBackup).catch((error) => {
+ // // eslint-disable-next-line no-console
+ // console.log('insert LoginUserBackup failed', error)
+ // throw new Error('insert user backup failed')
+ // })
// Table: state_users
const dbUser = new DbUser()
- dbUser.pubkey = keyPair[0]
+ // TODO: Register process
+ // dbUser.pubkey = keyPair[0]
dbUser.email = email
dbUser.firstName = firstName
dbUser.lastName = lastName
@@ -568,7 +594,7 @@ export class UserResolver {
await queryRunner.startTransaction('READ UNCOMMITTED')
try {
- if (coinanimation) {
+ if (coinanimation !== null && coinanimation !== undefined) {
queryRunner.manager
.getCustomRepository(UserSettingRepository)
.setOrUpdate(userEntity.id, Setting.COIN_ANIMATION, coinanimation.toString())
@@ -640,7 +666,8 @@ export class UserResolver {
return false
}
- const elopageBuyCount = await LoginElopageBuys.count({ payerEmail: userEntity.email })
+ const loginElopageBuysRepository = getCustomRepository(LoginElopageBuysRepository)
+ const elopageBuyCount = await loginElopageBuysRepository.count({ payerEmail: userEntity.email })
return elopageBuyCount > 0
}
}
diff --git a/backend/src/server/createServer.ts b/backend/src/server/createServer.ts
index 4350483ff..28e0e1ce4 100644
--- a/backend/src/server/createServer.ts
+++ b/backend/src/server/createServer.ts
@@ -6,6 +6,7 @@ import 'module-alias/register'
import { ApolloServer } from 'apollo-server-express'
import express from 'express'
+import bodyParser from 'body-parser'
// database
import connection from '../typeorm/connection'
@@ -22,6 +23,9 @@ import CONFIG from '../config'
// graphql
import schema from '../graphql/schema'
+// webhooks
+import { elopageWebhook } from '../webhook/elopage'
+
// TODO implement
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
@@ -50,6 +54,12 @@ const createServer = async (context: any = serverContext): Promise => {
// cors
app.use(cors)
+ // bodyparser
+ app.use(bodyParser.json())
+
+ // Elopage Webhook
+ app.post('/hook/elopage/' + CONFIG.WEBHOOK_ELOPAGE_SECRET, elopageWebhook)
+
// Apollo Server
const apollo = new ApolloServer({
schema: await schema(),
diff --git a/backend/src/typeorm/repository/LoginElopageBuys.ts b/backend/src/typeorm/repository/LoginElopageBuys.ts
new file mode 100644
index 000000000..15f2a8492
--- /dev/null
+++ b/backend/src/typeorm/repository/LoginElopageBuys.ts
@@ -0,0 +1,5 @@
+import { EntityRepository, Repository } from 'typeorm'
+import { LoginElopageBuys } from '@entity/LoginElopageBuys'
+
+@EntityRepository(LoginElopageBuys)
+export class LoginElopageBuysRepository extends Repository {}
diff --git a/backend/src/typeorm/repository/LoginUser.ts b/backend/src/typeorm/repository/LoginUser.ts
index d0db007d0..ac7ff31b6 100644
--- a/backend/src/typeorm/repository/LoginUser.ts
+++ b/backend/src/typeorm/repository/LoginUser.ts
@@ -2,4 +2,23 @@ import { EntityRepository, Repository } from 'typeorm'
import { LoginUser } from '@entity/LoginUser'
@EntityRepository(LoginUser)
-export class LoginUserRepository extends Repository {}
+export class LoginUserRepository extends Repository {
+ async findByEmail(email: string): Promise {
+ return this.createQueryBuilder('loginUser')
+ .where('loginUser.email = :email', { email })
+ .getOneOrFail()
+ }
+
+ async findBySearchCriteria(searchCriteria: string): Promise {
+ 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()
+ }
+}
diff --git a/backend/src/typeorm/repository/User.ts b/backend/src/typeorm/repository/User.ts
index 441c1b2c8..e127c179c 100644
--- a/backend/src/typeorm/repository/User.ts
+++ b/backend/src/typeorm/repository/User.ts
@@ -9,6 +9,15 @@ export class UserRepository extends Repository {
.getOneOrFail()
}
+ async findByPubkeyHexBuffer(pubkeyHexBuffer: Buffer): Promise {
+ const pubKeyString = pubkeyHexBuffer.toString('hex')
+ return await this.findByPubkeyHex(pubKeyString)
+ }
+
+ async findByEmail(email: string): Promise {
+ return this.createQueryBuilder('user').where('user.email = :email', { email }).getOneOrFail()
+ }
+
async getUsersIndiced(userIds: number[]): Promise {
if (!userIds.length) return []
const users = await this.createQueryBuilder('user')
diff --git a/backend/src/webhook/elopage.ts b/backend/src/webhook/elopage.ts
new file mode 100644
index 000000000..90cdb159f
--- /dev/null
+++ b/backend/src/webhook/elopage.ts
@@ -0,0 +1,154 @@
+/*
+ Elopage Webhook
+
+ Those are all available infos:
+ HandleElopageRequestTask: order_id 849951
+
+ Es gibt 5 elopage request mit dieser order_id
+ Alle von der gleichen Person, aber unterschiedliche Events:
+ 2019-12-03: chargeback.successful
+ 29.10.2019: order.subscription.paused
+ 2019-12-06: payment.successful
+ 29.10.2019: order.subscription.paying
+ 2091-12-07: payment.pending
+
+
+ order_id=849951&order_token=y22MJxHr9XzzPiaaH9GU&payment_session_id=849951&payment_session_token=y22MJxHr9XzzPiaaH9GU&action=payment_processed&initiator&payer[email]=theodora.mis%40gmx.ch&payer[first_name]=Theodora&payer[last_name]=Mis&payer[country]=Schweiz&payer[country_code]=CH&payer[city]=St.+Gallen&payer[street]=Vonwilstrasse+23&payer[street_number]&payer[zip]=9000&payer[company]&payer[vat_no]&payer[phone]&gift_receiver&publisher[id]=691&publisher[email]=joytopia%40gmail.com&publisher[first_name]=Bernd&publisher[last_name]=H%C3%BCckst%C3%A4dt&publisher[street]=Pfarrweg+2&publisher[zip]=74653&publisher[city]=K%C3%BCnzelsau&publisher[country]=Deutschland&publisher[phone]=%2B4979405460810&team_members&product_id=43944&product[id]=43944&product[slug]=gold-de&product[name]=Gold-Mitgliedschaft&product[type]=membership&product[price]=40.0&product[affiliate_program_id]=111&upsell&membership[id]=43944&membership[name]=Gold-Mitgliedschaft&membership[membership_product_1]=Werkzeuge+%26+Ressourcen+%28Gold%29&membership[membership_product_1_id]=44982&membership[membership_product_2]=Zertifizierung+zum%2Fr+Gradido-Botschafter%2Fin&membership[membership_product_2_id]=43970&membership[membership_product_3]=Seminar+3+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_3_id]=43969&membership[membership_product_4]=Potential-Entfaltungs-Techniken&membership[membership_product_4_id]=43954&membership[membership_product_5]=Seminar+2+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_5_id]=43896&membership[membership_product_6]=Kongresspaket%3A+Gesundes+Geld+f%C3%BCr+eine+gesunde+Welt&membership[membership_product_6_id]=14590&membership[membership_product_7]=Deine+Gold-Mitgliedschaft+bei+Gradido&membership[membership_product_7_id]=43951&membership[membership_product_8]=Gradido+E-Book%2C+H%C3%B6rspiel+%22Joytopia%22++%E2%80%93+und+100+Vorteile&membership[membership_product_8_id]=7312&membership[membership_product_9]=Danke%2C+dass+Du+hilfst+Gradido+in+die+Welt+zu+bringen%21&membership[membership_product_9_id]=43744&membership[membership_product_10]=Basis-Informationen+zu+Gradido&membership[membership_product_10_id]=42600&membership[membership_product_11]=Seminar+1+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_11_id]=43882&membership[membership_product_12]=Musical+%22Gradido+%E2%80%93+gemeinsam+retten+wir+die+Welt%22&membership[membership_product_12_id]=43886&membership[membership_product_13]=Premium+Community+%26+Markt&membership[membership_product_13_id]=43885&membership[membership_product_14]=Gradido+Buch+ungek%C3%BCrzte+Version+%26+%C3%9Cbersetzungen&membership[membership_product_14_id]=43887&membership[membership_product_15]=Online-Konferenzen&membership[membership_product_15_id]=43919&membership[membership_product_16]=Gradido+H%C3%B6rbuch&membership[membership_product_16_id]=43920&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&pricing_plan[name]=Monatlich&add_id_1&add_id_2&campaign_id¤cy=EUR&coupon_code&recurring=yes&recurring_form=subscription&payment_state=payment_paused&payment_method=sepa&opt_ins&payments_schedule[][rate]=1&payments_schedule[][state]=debt&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.10.2019&payments_schedule[][rate]=2&payments_schedule[][state]=pending&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.11.2019&payments_schedule[][rate]=3&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.12.2019&payments_schedule[][rate]=4&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.01.2020&payments_schedule[][rate]=5&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.02.2020&payments_schedule[][rate]=6&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.03.2020&payments_schedule[][rate]=7&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.04.2020&payments_schedule[][rate]=8&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.05.2020&payments_schedule[][rate]=9&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.06.2020&payments_schedule[][rate]=10&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.07.2020&payments_schedule[][rate]=11&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.08.2020&payments_schedule[][rate]=12&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.09.2020&payments_schedule[][rate]=13&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.10.2020&payments_schedule[][rate]=14&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=0.0&payments_schedule[][date]=29.11.2020&payments_count=0&payments_count_expected&with_test_period=false&with_custom_start=false&created=29.10.2019+13%3A17&id=58268076&invoice_number&revenue=-40.0&amount=-23.72&fee=-16.28&vat_rate=0.0&vat_amount=0.0&state=successful&refunded_transfer_id=52876337&invoice_link&credit_memo_link=http%3A%2F%2Felopage.com%2Fcommon%2Fcredit_memos%2F12410%3Ftoken%3D6dyBsddt6gsJpX8Fq-M2&success_link=http%3A%2F%2Felopage.com%2Fs%2Fgradido%2Fpayment%2Fy22MJxHr9XzzPiaaH9GU&error_msg&created_date=2019-12-03T22%3A15Z&success_date=2019-12-03T22%3A15Z&success_date_short=2019-12-03&created_date_utc=03.12.2019+22%3A15&success_date_utc=03.12.2019+22%3A15&team_member_commissions&event=chargeback.successful
+ order_id=849951&order_token=y22MJxHr9XzzPiaaH9GU&payment_session_id=849951&payment_session_token=y22MJxHr9XzzPiaaH9GU&action=subscription_state_changed&initiator&payer[email]=theodora.mis%40gmx.ch&payer[first_name]=Theodora&payer[last_name]=Mis&payer[country]=Schweiz&payer[country_code]=CH&payer[city]=St.+Gallen&payer[street]=Vonwilstrasse+23&payer[street_number]&payer[zip]=9000&payer[company]&payer[vat_no]&payer[phone]&gift_receiver&publisher[id]=691&publisher[email]=joytopia%40gmail.com&publisher[first_name]=Bernd&publisher[last_name]=H%C3%BCckst%C3%A4dt&publisher[street]=Pfarrweg+2&publisher[zip]=74653&publisher[city]=K%C3%BCnzelsau&publisher[country]=Deutschland&publisher[phone]=%2B4979405460810&team_members&product_id=43944&product[id]=43944&product[slug]=gold-de&product[name]=Gold-Mitgliedschaft&product[type]=membership&product[price]=40.0&product[affiliate_program_id]=111&upsell&membership[id]=43944&membership[name]=Gold-Mitgliedschaft&membership[membership_product_1]=Werkzeuge+%26+Ressourcen+%28Gold%29&membership[membership_product_1_id]=44982&membership[membership_product_2]=Zertifizierung+zum%2Fr+Gradido-Botschafter%2Fin&membership[membership_product_2_id]=43970&membership[membership_product_3]=Seminar+3+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_3_id]=43969&membership[membership_product_4]=Potential-Entfaltungs-Techniken&membership[membership_product_4_id]=43954&membership[membership_product_5]=Seminar+2+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_5_id]=43896&membership[membership_product_6]=Kongresspaket%3A+Gesundes+Geld+f%C3%BCr+eine+gesunde+Welt&membership[membership_product_6_id]=14590&membership[membership_product_7]=Deine+Gold-Mitgliedschaft+bei+Gradido&membership[membership_product_7_id]=43951&membership[membership_product_8]=Gradido+E-Book%2C+H%C3%B6rspiel+%22Joytopia%22++%E2%80%93+und+100+Vorteile&membership[membership_product_8_id]=7312&membership[membership_product_9]=Danke%2C+dass+Du+hilfst+Gradido+in+die+Welt+zu+bringen%21&membership[membership_product_9_id]=43744&membership[membership_product_10]=Basis-Informationen+zu+Gradido&membership[membership_product_10_id]=42600&membership[membership_product_11]=Seminar+1+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_11_id]=43882&membership[membership_product_12]=Musical+%22Gradido+%E2%80%93+gemeinsam+retten+wir+die+Welt%22&membership[membership_product_12_id]=43886&membership[membership_product_13]=Premium+Community+%26+Markt&membership[membership_product_13_id]=43885&membership[membership_product_14]=Gradido+Buch+ungek%C3%BCrzte+Version+%26+%C3%9Cbersetzungen&membership[membership_product_14_id]=43887&membership[membership_product_15]=Online-Konferenzen&membership[membership_product_15_id]=43919&membership[membership_product_16]=Gradido+H%C3%B6rbuch&membership[membership_product_16_id]=43920&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&pricing_plan[name]=Monatlich&add_id_1&add_id_2&campaign_id¤cy=EUR&coupon_code&recurring=yes&recurring_form=subscription&payment_state=payment_paused&payment_method=sepa&opt_ins&payments_schedule[][rate]=1&payments_schedule[][state]=debt&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.10.2019&payments_schedule[][rate]=2&payments_schedule[][state]=pending&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.11.2019&payments_schedule[][rate]=3&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.12.2019&payments_schedule[][rate]=4&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.01.2020&payments_schedule[][rate]=5&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.02.2020&payments_schedule[][rate]=6&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.03.2020&payments_schedule[][rate]=7&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.04.2020&payments_schedule[][rate]=8&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.05.2020&payments_schedule[][rate]=9&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.06.2020&payments_schedule[][rate]=10&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.07.2020&payments_schedule[][rate]=11&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.08.2020&payments_schedule[][rate]=12&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.09.2020&payments_schedule[][rate]=13&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.10.2020&payments_schedule[][rate]=14&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=0.0&payments_schedule[][date]=29.11.2020&payments_count=0&payments_count_expected&with_test_period=false&with_custom_start=false&created=29.10.2019+13%3A17&event=order.subscription.paused
+ order_id=849951&order_token=y22MJxHr9XzzPiaaH9GU&payment_session_id=849951&payment_session_token=y22MJxHr9XzzPiaaH9GU&action=payment_processed&initiator&payer[email]=theodora.mis%40gmx.ch&payer[first_name]=Theodora&payer[last_name]=Mis&payer[country]=Schweiz&payer[country_code]=CH&payer[city]=St.+Gallen&payer[street]=Vonwilstrasse+23&payer[street_number]&payer[zip]=9000&payer[company]&payer[vat_no]&payer[phone]&gift_receiver&publisher[id]=691&publisher[email]=joytopia%40gmail.com&publisher[first_name]=Bernd&publisher[last_name]=H%C3%BCckst%C3%A4dt&publisher[street]=Pfarrweg+2&publisher[zip]=74653&publisher[city]=K%C3%BCnzelsau&publisher[country]=Deutschland&publisher[phone]=%2B4979405460810&team_members&product_id=43944&product[id]=43944&product[slug]=gold-de&product[name]=Gold-Mitgliedschaft&product[type]=membership&product[price]=40.0&product[affiliate_program_id]=111&upsell&membership[id]=43944&membership[name]=Gold-Mitgliedschaft&membership[membership_product_1]=Werkzeuge+%26+Ressourcen+%28Gold%29&membership[membership_product_1_id]=44982&membership[membership_product_2]=Zertifizierung+zum%2Fr+Gradido-Botschafter%2Fin&membership[membership_product_2_id]=43970&membership[membership_product_3]=Seminar+3+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_3_id]=43969&membership[membership_product_4]=Potential-Entfaltungs-Techniken&membership[membership_product_4_id]=43954&membership[membership_product_5]=Seminar+2+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_5_id]=43896&membership[membership_product_6]=Kongresspaket%3A+Gesundes+Geld+f%C3%BCr+eine+gesunde+Welt&membership[membership_product_6_id]=14590&membership[membership_product_7]=Deine+Gold-Mitgliedschaft+bei+Gradido&membership[membership_product_7_id]=43951&membership[membership_product_8]=Gradido+E-Book%2C+H%C3%B6rspiel+%22Joytopia%22++%E2%80%93+und+100+Vorteile&membership[membership_product_8_id]=7312&membership[membership_product_9]=Danke%2C+dass+Du+hilfst+Gradido+in+die+Welt+zu+bringen%21&membership[membership_product_9_id]=43744&membership[membership_product_10]=Basis-Informationen+zu+Gradido&membership[membership_product_10_id]=42600&membership[membership_product_11]=Seminar+1+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_11_id]=43882&membership[membership_product_12]=Musical+%22Gradido+%E2%80%93+gemeinsam+retten+wir+die+Welt%22&membership[membership_product_12_id]=43886&membership[membership_product_13]=Premium+Community+%26+Markt&membership[membership_product_13_id]=43885&membership[membership_product_14]=Gradido+Buch+ungek%C3%BCrzte+Version+%26+%C3%9Cbersetzungen&membership[membership_product_14_id]=43887&membership[membership_product_15]=Online-Konferenzen&membership[membership_product_15_id]=43919&membership[membership_product_16]=Gradido+H%C3%B6rbuch&membership[membership_product_16_id]=43920&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&pricing_plan[name]=Monatlich&add_id_1&add_id_2&campaign_id¤cy=EUR&coupon_code&recurring=yes&recurring_form=subscription&payment_state=active_subscription&payment_method=sepa&opt_ins&payments_schedule[][rate]=1&payments_schedule[][state]=debt&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.10.2019&payments_schedule[][rate]=2&payments_schedule[][state]=paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.11.2019&payments_schedule[][rate]=3&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.12.2019&payments_schedule[][rate]=4&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.01.2020&payments_schedule[][rate]=5&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.02.2020&payments_schedule[][rate]=6&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.03.2020&payments_schedule[][rate]=7&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.04.2020&payments_schedule[][rate]=8&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.05.2020&payments_schedule[][rate]=9&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.06.2020&payments_schedule[][rate]=10&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.07.2020&payments_schedule[][rate]=11&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.08.2020&payments_schedule[][rate]=12&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.09.2020&payments_schedule[][rate]=13&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.10.2020&payments_schedule[][rate]=14&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=0.0&payments_schedule[][date]=29.11.2020&payments_schedule[][rate]=15&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=0.0&payments_schedule[][date]=29.12.2020&payments_count=1&payments_count_expected&with_test_period=false&with_custom_start=false&created=29.10.2019+13%3A17&id=57354055&invoice_number=111-1839-000000677&revenue=40.0&amount=23.72&fee=16.28&vat_rate=0.0&vat_amount=0.0&state=successful&refunded_transfer_id&invoice_link=http%3A%2F%2Felopage.com%2Fcommon%2Finvoices%2F450856%2Fdownload.pdf%3Ftoken%3DGR7bG7zcbgCzNJEPLDss&credit_memo_link&success_link=http%3A%2F%2Felopage.com%2Fs%2Fgradido%2Fpayment%2Fy22MJxHr9XzzPiaaH9GU&error_msg&created_date=2019-11-29T07%3A19Z&success_date=2019-12-06T13%3A12Z&success_date_short=2019-12-06&created_date_utc=29.11.2019+07%3A19&success_date_utc=06.12.2019+13%3A12&team_member_commissions&event=payment.successful
+ order_id=849951&order_token=y22MJxHr9XzzPiaaH9GU&payment_session_id=849951&payment_session_token=y22MJxHr9XzzPiaaH9GU&action=subscription_state_changed&initiator&payer[email]=theodora.mis%40gmx.ch&payer[first_name]=Theodora&payer[last_name]=Mis&payer[country]=Schweiz&payer[country_code]=CH&payer[city]=St.+Gallen&payer[street]=Vonwilstrasse+23&payer[street_number]&payer[zip]=9000&payer[company]&payer[vat_no]&payer[phone]&gift_receiver&publisher[id]=691&publisher[email]=joytopia%40gmail.com&publisher[first_name]=Bernd&publisher[last_name]=H%C3%BCckst%C3%A4dt&publisher[street]=Pfarrweg+2&publisher[zip]=74653&publisher[city]=K%C3%BCnzelsau&publisher[country]=Deutschland&publisher[phone]=%2B4979405460810&team_members&product_id=43944&product[id]=43944&product[slug]=gold-de&product[name]=Gold-Mitgliedschaft&product[type]=membership&product[price]=40.0&product[affiliate_program_id]=111&upsell&membership[id]=43944&membership[name]=Gold-Mitgliedschaft&membership[membership_product_1]=Werkzeuge+%26+Ressourcen+%28Gold%29&membership[membership_product_1_id]=44982&membership[membership_product_2]=Zertifizierung+zum%2Fr+Gradido-Botschafter%2Fin&membership[membership_product_2_id]=43970&membership[membership_product_3]=Seminar+3+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_3_id]=43969&membership[membership_product_4]=Potential-Entfaltungs-Techniken&membership[membership_product_4_id]=43954&membership[membership_product_5]=Seminar+2+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_5_id]=43896&membership[membership_product_6]=Kongresspaket%3A+Gesundes+Geld+f%C3%BCr+eine+gesunde+Welt&membership[membership_product_6_id]=14590&membership[membership_product_7]=Deine+Gold-Mitgliedschaft+bei+Gradido&membership[membership_product_7_id]=43951&membership[membership_product_8]=Gradido+E-Book%2C+H%C3%B6rspiel+%22Joytopia%22++%E2%80%93+und+100+Vorteile&membership[membership_product_8_id]=7312&membership[membership_product_9]=Danke%2C+dass+Du+hilfst+Gradido+in+die+Welt+zu+bringen%21&membership[membership_product_9_id]=43744&membership[membership_product_10]=Basis-Informationen+zu+Gradido&membership[membership_product_10_id]=42600&membership[membership_product_11]=Seminar+1+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_11_id]=43882&membership[membership_product_12]=Musical+%22Gradido+%E2%80%93+gemeinsam+retten+wir+die+Welt%22&membership[membership_product_12_id]=43886&membership[membership_product_13]=Premium+Community+%26+Markt&membership[membership_product_13_id]=43885&membership[membership_product_14]=Gradido+Buch+ungek%C3%BCrzte+Version+%26+%C3%9Cbersetzungen&membership[membership_product_14_id]=43887&membership[membership_product_15]=Online-Konferenzen&membership[membership_product_15_id]=43919&membership[membership_product_16]=Gradido+H%C3%B6rbuch&membership[membership_product_16_id]=43920&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&pricing_plan[name]=Monatlich&add_id_1&add_id_2&campaign_id¤cy=EUR&coupon_code&recurring=yes&recurring_form=subscription&payment_state=active_subscription&payment_method=sepa&opt_ins&payments_schedule[][rate]=1&payments_schedule[][state]=debt&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.10.2019&payments_schedule[][rate]=2&payments_schedule[][state]=paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.11.2019&payments_schedule[][rate]=3&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.12.2019&payments_schedule[][rate]=4&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.01.2020&payments_schedule[][rate]=5&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.02.2020&payments_schedule[][rate]=6&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.03.2020&payments_schedule[][rate]=7&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.04.2020&payments_schedule[][rate]=8&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.05.2020&payments_schedule[][rate]=9&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.06.2020&payments_schedule[][rate]=10&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.07.2020&payments_schedule[][rate]=11&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.08.2020&payments_schedule[][rate]=12&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.09.2020&payments_schedule[][rate]=13&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.10.2020&payments_schedule[][rate]=14&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=0.0&payments_schedule[][date]=29.11.2020&payments_schedule[][rate]=15&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=0.0&payments_schedule[][date]=29.12.2020&payments_count=1&payments_count_expected&with_test_period=false&with_custom_start=false&created=29.10.2019+13%3A17&event=order.subscription.paying
+ order_id=849951&order_token=y22MJxHr9XzzPiaaH9GU&payment_session_id=849951&payment_session_token=y22MJxHr9XzzPiaaH9GU&action=payment_processed&initiator&payer[email]=theodora.mis%40gmx.ch&payer[first_name]=Theodora&payer[last_name]=Mis&payer[country]=Schweiz&payer[country_code]=CH&payer[city]=St.+Gallen&payer[street]=Vonwilstrasse+23&payer[street_number]&payer[zip]=9000&payer[company]&payer[vat_no]&payer[phone]&gift_receiver&publisher[id]=691&publisher[email]=joytopia%40gmail.com&publisher[first_name]=Bernd&publisher[last_name]=H%C3%BCckst%C3%A4dt&publisher[street]=Pfarrweg+2&publisher[zip]=74653&publisher[city]=K%C3%BCnzelsau&publisher[country]=Deutschland&publisher[phone]=%2B4979405460810&team_members&product_id=43944&product[id]=43944&product[slug]=gold-de&product[name]=Gold-Mitgliedschaft&product[type]=membership&product[price]=40.0&product[affiliate_program_id]=111&upsell&membership[id]=43944&membership[name]=Gold-Mitgliedschaft&membership[membership_product_1]=Werkzeuge+%26+Ressourcen+%28Gold%29&membership[membership_product_1_id]=44982&membership[membership_product_2]=Zertifizierung+zum%2Fr+Gradido-Botschafter%2Fin&membership[membership_product_2_id]=43970&membership[membership_product_3]=Seminar+3+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_3_id]=43969&membership[membership_product_4]=Potential-Entfaltungs-Techniken&membership[membership_product_4_id]=43954&membership[membership_product_5]=Seminar+2+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_5_id]=43896&membership[membership_product_6]=Kongresspaket%3A+Gesundes+Geld+f%C3%BCr+eine+gesunde+Welt&membership[membership_product_6_id]=14590&membership[membership_product_7]=Deine+Gold-Mitgliedschaft+bei+Gradido&membership[membership_product_7_id]=43951&membership[membership_product_8]=Gradido+E-Book%2C+H%C3%B6rspiel+%22Joytopia%22++%E2%80%93+und+100+Vorteile&membership[membership_product_8_id]=7312&membership[membership_product_9]=Danke%2C+dass+Du+hilfst+Gradido+in+die+Welt+zu+bringen%21&membership[membership_product_9_id]=43744&membership[membership_product_10]=Basis-Informationen+zu+Gradido&membership[membership_product_10_id]=42600&membership[membership_product_11]=Seminar+1+Nat%C3%BCrliche+%C3%96konomie+des+Lebens&membership[membership_product_11_id]=43882&membership[membership_product_12]=Musical+%22Gradido+%E2%80%93+gemeinsam+retten+wir+die+Welt%22&membership[membership_product_12_id]=43886&membership[membership_product_13]=Premium+Community+%26+Markt&membership[membership_product_13_id]=43885&membership[membership_product_14]=Gradido+Buch+ungek%C3%BCrzte+Version+%26+%C3%9Cbersetzungen&membership[membership_product_14_id]=43887&membership[membership_product_15]=Online-Konferenzen&membership[membership_product_15_id]=43919&membership[membership_product_16]=Gradido+H%C3%B6rbuch&membership[membership_product_16_id]=43920&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&events[]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&tickets[][codes]&tickets[][ticket_attendees]&pricing_plan[name]=Monatlich&add_id_1&add_id_2&campaign_id¤cy=EUR&coupon_code&recurring=yes&recurring_form=subscription&payment_state=active_subscription&payment_method=sepa&opt_ins&payments_schedule[][rate]=1&payments_schedule[][state]=pending&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.10.2019&payments_schedule[][rate]=2&payments_schedule[][state]=paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.11.2019&payments_schedule[][rate]=3&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.12.2019&payments_schedule[][rate]=4&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.01.2020&payments_schedule[][rate]=5&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.02.2020&payments_schedule[][rate]=6&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.03.2020&payments_schedule[][rate]=7&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.04.2020&payments_schedule[][rate]=8&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.05.2020&payments_schedule[][rate]=9&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.06.2020&payments_schedule[][rate]=10&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.07.2020&payments_schedule[][rate]=11&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.08.2020&payments_schedule[][rate]=12&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.09.2020&payments_schedule[][rate]=13&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=40.0&payments_schedule[][date]=29.10.2020&payments_schedule[][rate]=14&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=0.0&payments_schedule[][date]=29.11.2020&payments_schedule[][rate]=15&payments_schedule[][state]=to_be_paid&payments_schedule[][amount]=0.0&payments_schedule[][date]=29.12.2020&payments_count=1&payments_count_expected&with_test_period=false&with_custom_start=false&created=29.10.2019+13%3A17&id=58838098&invoice_number=111-1839-000000689&revenue=40.0&amount=23.72&fee=16.28&vat_rate=0.0&vat_amount=0.0&state=pending&refunded_transfer_id&invoice_link=http%3A%2F%2Felopage.com%2Fcommon%2Finvoices%2F470009%2Fdownload.pdf%3Ftoken%3DZ_gogUf8tpKxcHhB-7Cz&credit_memo_link&success_link=http%3A%2F%2Felopage.com%2Fs%2Fgradido%2Fpayment%2Fy22MJxHr9XzzPiaaH9GU&error_msg&created_date=2019-12-07T07%3A19Z&success_date&success_date_short&created_date_utc=07.12.2019+07%3A19&success_date_utc&team_member_commissions&event=payment.pending
+
+ Additional we have the Elopage API docu:
+ https://apidoc.elopage.com/#webhooks
+
+ I assume that the webhook arrives via POST and transmits a string as shown above
+*/
+
+import { LoginElopageBuys } from '@entity/LoginElopageBuys'
+import { LoginUser } from '@entity/LoginUser'
+import { randomBytes } from 'crypto'
+import { UserResolver } from '../graphql/resolver/UserResolver'
+
+export const elopageWebhook = async (req: any, res: any): Promise => {
+ res.status(200).end() // Responding is important
+
+ const loginElopgaeBuy = new LoginElopageBuys()
+ let firstName = ''
+ let lastName = ''
+ const entries = req.body.split('&')
+ entries.foreach((entry: string) => {
+ const keyVal = entry.split('=')
+ if (keyVal.length !== 2) {
+ throw new Error(`Error parsing entry '${entry}'`)
+ }
+ const key = keyVal[0]
+ const val = decodeURIComponent(keyVal[1]).replace('+', ' ').trim()
+ switch (key) {
+ case 'product[affiliate_program_id]':
+ loginElopgaeBuy.affiliateProgramId = parseInt(val)
+ break
+ case 'publisher[id]':
+ loginElopgaeBuy.publisherId = parseInt(val)
+ break
+ case 'order_id':
+ loginElopgaeBuy.orderId = parseInt(val)
+ break
+ case 'product_id':
+ loginElopgaeBuy.productId = parseInt(val)
+ break
+ case 'product[price]':
+ // TODO: WHAT THE ACTUAL FUK? Please save this as float in the future directly in the database
+ loginElopgaeBuy.productPrice = Math.trunc(parseFloat(val) * 100)
+ break
+ case 'payer[email]':
+ loginElopgaeBuy.payerEmail = val
+ break
+ case 'publisher[email]':
+ loginElopgaeBuy.publisherEmail = val
+ break
+ case 'payment_state':
+ loginElopgaeBuy.payed = val === 'paid'
+ break
+ case 'success_date':
+ loginElopgaeBuy.successDate = new Date(val)
+ break
+ case 'event':
+ loginElopgaeBuy.event = val
+ break
+ case 'membership[id]':
+ // TODO this was never set on login_server - its unclear if this is the correct value
+ loginElopgaeBuy.elopageUserId = parseInt(val)
+ break
+ case 'payer[first_name]':
+ firstName = val
+ break
+ case 'payer[last_name]':
+ lastName = val
+ break
+ default:
+ // eslint-disable-next-line no-console
+ console.log(`Unknown Elopage Value '${entry}'`)
+ }
+ })
+
+ // Do not process certain events
+ if (['lesson.viewed', 'lesson.completed', 'lesson.commented'].includes(loginElopgaeBuy.event)) {
+ // eslint-disable-next-line no-console
+ console.log('User viewed, completed or commented - not saving hook')
+ return
+ }
+
+ // Save the hook data
+ await loginElopgaeBuy.save()
+
+ // create user for certain products
+ /*
+ Registrierung - Schritt 1 von 3, 36001
+ Gradido-Basis, 43741
+ Premium-Mitgliedschaft, 43870
+ Gold-Mitgliedschaft, 43944
+ Business-Mitgliedschaft, 43960
+ Förderbeitrag: 49106
+ */
+ if ([36001, 43741, 43870, 43944, 43960, 49106].includes(loginElopgaeBuy.productId)) {
+ const email = loginElopgaeBuy.payerEmail
+
+ const VALIDATE_EMAIL = /^[a-zA-Z0-9.!#$%&?*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
+ const VALIDATE_NAME = /^<>&;]{2,}$/
+
+ // Validate inputs
+ if (
+ email === '' ||
+ !email.match(VALIDATE_EMAIL) ||
+ firstName === '' ||
+ firstName.match(VALIDATE_NAME) ||
+ lastName === '' ||
+ lastName.match(VALIDATE_NAME)
+ ) {
+ // eslint-disable-next-line no-console
+ console.log(`Could not create User ${firstName} ${lastName} with email: ${email}`)
+ return
+ }
+
+ // Do we already have such a user?
+ if ((await LoginUser.count({ email })) !== 0) {
+ // eslint-disable-next-line no-console
+ console.log(`Did not create User - already exists with email: ${email}`)
+ return
+ }
+
+ const userResolver = new UserResolver()
+ try {
+ await userResolver.createUser({
+ email,
+ firstName,
+ lastName,
+ publisherId: loginElopgaeBuy.publisherId,
+ })
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.log(`Could not create User for ${email}. Following Error occured:`, error)
+ }
+ }
+}
diff --git a/backend/yarn.lock b/backend/yarn.lock
index b411bcf60..5b74ba7c3 100644
--- a/backend/yarn.lock
+++ b/backend/yarn.lock
@@ -1552,7 +1552,7 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
-body-parser@1.19.0, body-parser@^1.18.3:
+body-parser@1.19.0, body-parser@^1.18.3, body-parser@^1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
diff --git a/database/entity/ServerUser.ts b/database/entity/ServerUser.ts
new file mode 100644
index 000000000..e776093ac
--- /dev/null
+++ b/database/entity/ServerUser.ts
@@ -0,0 +1,31 @@
+import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
+
+@Entity('server_users')
+export class ServerUser extends BaseEntity {
+ @PrimaryGeneratedColumn('increment', { unsigned: true })
+ id: number
+
+ @Column({ length: 50 })
+ username: string
+
+ @Column({ type: 'bigint', unsigned: true })
+ password: BigInt
+
+ @Column({ length: 50, unique: true })
+ email: string
+
+ @Column({ length: 20, default: 'admin' })
+ role: string
+
+ @Column({ default: 0 })
+ activated: number
+
+ @Column({ name: 'last_login', default: null, nullable: true })
+ lastLogin: Date
+
+ @Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' })
+ created: Date
+
+ @Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' })
+ modified: Date
+}
diff --git a/docker-compose.override.yml b/docker-compose.override.yml
index bd23b43b3..83f38a95f 100644
--- a/docker-compose.override.yml
+++ b/docker-compose.override.yml
@@ -8,8 +8,6 @@ services:
image: gradido/frontend:development
build:
target: development
- networks:
- - external-net
environment:
- NODE_ENV="development"
# - DEBUG=true
@@ -27,8 +25,6 @@ services:
image: gradido/admin:development
build:
target: development
- networks:
- - external-net
environment:
- NODE_ENV="development"
# - DEBUG=true
diff --git a/docker-compose.yml b/docker-compose.yml
index b212c6f21..9352fd162 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -15,6 +15,7 @@ services:
context: ./frontend
target: production
networks:
+ - external-net
- internal-net
ports:
- 3000:3000
@@ -39,6 +40,7 @@ services:
context: ./admin
target: production
networks:
+ - external-net
- internal-net
ports:
- 8080:8080
diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json
index 606fa161a..b0dfe36d4 100644
--- a/frontend/src/locales/de.json
+++ b/frontend/src/locales/de.json
@@ -47,6 +47,7 @@
"change-password": "Fehler beim Ändern des Passworts",
"error": "Fehler",
"no-account": "Leider konnten wir keinen Account finden mit diesen Daten!",
+ "no-email-verify": "Die Email wurde noch nicht bestätigt, bitte überprüfe deine Emails und klicke auf den Aktivierungslink!",
"session-expired": "Sitzung abgelaufen!"
},
"form": {
@@ -181,9 +182,12 @@
"uppercase": "Ein Großbuchstabe erforderlich."
},
"thx": {
+ "activateEmail": "Deine Email wurde noch nicht aktiviert, bitte überprüfe deine Email und Klicke den Aktivierungslink!",
"checkEmail": "Deine Email würde erfolgreich verifiziert.",
"email": "Wir haben dir eine eMail gesendet.",
- "register": "Du bist jetzt registriert.",
+ "emailActivated": "Danke dass Du deine Email bestätigt hast.",
+ "errorTitle": "Achtung!",
+ "register": "Du bist jetzt registriert, bitte überprüfe deine Emails und klicke auf den Aktivierungslink.",
"reset": "Dein Passwort wurde geändert.",
"title": "Danke!"
}
diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json
index 3e3941415..135729ffa 100644
--- a/frontend/src/locales/en.json
+++ b/frontend/src/locales/en.json
@@ -47,6 +47,7 @@
"change-password": "Error while changing password",
"error": "Error",
"no-account": "Unfortunately we could not find an account to the given data!",
+ "no-email-verify": "Your email is not activated yet, please check your emails and click the activation link!",
"session-expired": "The session expired"
},
"form": {
@@ -181,9 +182,12 @@
"uppercase": "One uppercase letter required."
},
"thx": {
+ "activateEmail": "Your email has not been activated yet, please check your emails and click the activation link!",
"checkEmail": "Your email has been successfully verified.",
"email": "We have sent you an email.",
- "register": "You are registred now.",
+ "emailActivated": "Thank you your email has been activated.",
+ "errorTitle": "Attention!",
+ "register": "You are registered now, please check your emails and click the activation link.",
"reset": "Your password has been changed.",
"title": "Thank you!"
}
diff --git a/frontend/src/routes/routes.js b/frontend/src/routes/routes.js
index 6e7d8fadf..f6975d09d 100755
--- a/frontend/src/routes/routes.js
+++ b/frontend/src/routes/routes.js
@@ -50,7 +50,7 @@ const routes = [
path: '/thx/:comingFrom',
component: () => import('../views/Pages/thx.vue'),
beforeEnter: (to, from, next) => {
- const validFrom = ['password', 'reset', 'register']
+ const validFrom = ['password', 'reset', 'register', 'login']
if (!validFrom.includes(from.path.split('/')[1])) {
next({ path: '/login' })
} else {
diff --git a/frontend/src/views/Pages/Login.vue b/frontend/src/views/Pages/Login.vue
index de1ae993a..45e700099 100755
--- a/frontend/src/views/Pages/Login.vue
+++ b/frontend/src/views/Pages/Login.vue
@@ -104,9 +104,14 @@ export default {
this.$router.push('/overview')
loader.hide()
})
- .catch(() => {
+ .catch((error) => {
+ if (!error.message.includes('user email not validated')) {
+ this.$toasted.error(this.$t('error.no-account'))
+ } else {
+ // : this.$t('error.no-email-verify')
+ this.$router.push('/thx/login')
+ }
loader.hide()
- this.$toasted.error(this.$t('error.no-account'))
})
},
},
diff --git a/frontend/src/views/Pages/Register.vue b/frontend/src/views/Pages/Register.vue
index 00114eb04..ea4000cff 100755
--- a/frontend/src/views/Pages/Register.vue
+++ b/frontend/src/views/Pages/Register.vue
@@ -161,6 +161,7 @@ import InputEmail from '../../components/Inputs/InputEmail.vue'
import InputPasswordConfirmation from '../../components/Inputs/InputPasswordConfirmation.vue'
import LanguageSwitchSelect from '../../components/LanguageSwitchSelect.vue'
import { registerUser } from '../../graphql/mutations'
+import { localeChanged } from 'vee-validate'
import { getCommunityInfoMixin } from '../../mixins/getCommunityInfo'
export default {
@@ -189,6 +190,9 @@ export default {
methods: {
updateLanguage(e) {
this.language = e
+ this.$store.commit('language', this.language)
+ this.$i18n.locale = this.language
+ localeChanged(this.language)
},
getValidationState({ dirty, validated, valid = null }) {
return dirty || validated ? valid : null
diff --git a/frontend/src/views/Pages/thx.vue b/frontend/src/views/Pages/thx.vue
index 9d9143456..5884cc61c 100644
--- a/frontend/src/views/Pages/thx.vue
+++ b/frontend/src/views/Pages/thx.vue
@@ -4,10 +4,12 @@
@@ -17,25 +19,33 @@