diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 55e28db4b..08ecc0379 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -248,7 +248,7 @@ jobs:
##########################################################################
- name: Nginx | Build `production` image
run: |
- docker build -t "gradido/nginx:latest" -t "gradido/nginx:production" -t "gradido/nginx:${VERSION}" -t "gradido/nginx:${BUILD_VERSION}" -f ./nginx/Dockerfile ./
+ docker build -t "gradido/nginx:latest" -t "gradido/nginx:production" -t "gradido/nginx:${VERSION}" -t "gradido/nginx:${BUILD_VERSION}" nginx/
docker save "gradido/nginx" > /tmp/nginx.tar
- name: Upload Artifact
uses: actions/upload-artifact@v2
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 15a736630..5dbbf685f 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -199,7 +199,7 @@ jobs:
##########################################################################
- name: nginx | Build `test` image
run: |
- docker build -t "gradido/nginx:test" -f ./nginx/Dockerfile ./
+ docker build -t "gradido/nginx:test" nginx/
docker save "gradido/nginx:test" > /tmp/nginx.tar
- name: Upload Artifact
uses: actions/upload-artifact@v2
@@ -470,7 +470,7 @@ jobs:
report_name: Coverage Admin Interface
type: lcov
result_path: ./coverage/lcov.info
- min_coverage: 76
+ min_coverage: 77
token: ${{ github.token }}
##############################################################################
@@ -520,7 +520,7 @@ jobs:
report_name: Coverage Backend
type: lcov
result_path: ./backend/coverage/lcov.info
- min_coverage: 40
+ min_coverage: 45
token: ${{ github.token }}
##############################################################################
diff --git a/.gitignore b/.gitignore
index 5682953d4..b02b9d6ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,9 @@ nbproject
.metadata
/.env
package-lock.json
+/deployment/bare_metal/.env
+/deployment/bare_metal/nginx/sites-available/gradido.conf
+/deployment/bare_metal/nginx/sites-available/update-page.conf
+/deployment/bare_metal/nginx/update-page/updating.html
+/deployment/bare_metal/log
+/deployment/bare_metal/backup
diff --git a/README.md b/README.md
index ce8e84df0..355a40b82 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ We are currently restructuring the service to reduce dependencies and unify busi
### Open the wallet
-Once you have `docker-compose` up and running, you can open [http://localhost/vue](http://localhost/vue) and create yourself a new wallet account.
+Once you have `docker-compose` up and running, you can open [http://localhost/](http://localhost/) and create yourself a new wallet account.
## How to release
@@ -90,7 +90,7 @@ Note: The Changelog will be regenerated with all tags on release on the external
| Problem | Issue | Solution | Description |
| ------- | ----- | -------- | ----------- |
| docker-compose raises database connection errors | [#1062](https://github.com/gradido/gradido/issues/1062) | End `ctrl+c` and restart the `docker-compose up` after a successful build | Several Database connection related errors occur in the docker-compose log. |
-| Wallet page is empty | [#1063](https://github.com/gradido/gradido/issues/1063) | Accept Cookies and Local Storage in your Browser | The page stays empty when navigating to [http://localhost/vue](http://localhost/vue) |
+| Wallet page is empty | [#1063](https://github.com/gradido/gradido/issues/1063) | Accept Cookies and Local Storage in your Browser | The page stays empty when navigating to [http://localhost/](http://localhost/) |
## Useful Links
diff --git a/admin/.env.dist b/admin/.env.dist
index 6d78e6782..66c84dda8 100644
--- a/admin/.env.dist
+++ b/admin/.env.dist
@@ -1,3 +1,4 @@
GRAPHQL_URI=http://localhost:4000/graphql
-WALLET_AUTH_URL=http://localhost/vue/authenticate?token=$1
+WALLET_AUTH_URL=http://localhost/authenticate?token={token}
+WALLET_URL=http://localhost/login
DEBUG_DISABLE_AUTH=false
\ No newline at end of file
diff --git a/admin/.env.template b/admin/.env.template
new file mode 100644
index 000000000..a965b1bb1
--- /dev/null
+++ b/admin/.env.template
@@ -0,0 +1,4 @@
+GRAPHQL_URI=$GRAPHQL_URI
+WALLET_AUTH_URL=$WALLET_AUTH_URL
+WALLET_URL=$WALLET_URL
+DEBUG_DISABLE_AUTH=false
\ No newline at end of file
diff --git a/admin/package.json b/admin/package.json
index c94d0a2b0..93fbed8ae 100644
--- a/admin/package.json
+++ b/admin/package.json
@@ -33,6 +33,7 @@
"core-js": "^3.6.5",
"dotenv-webpack": "^7.0.3",
"graphql": "^15.6.1",
+ "express": "^4.17.1",
"identity-obj-proxy": "^3.0.0",
"jest": "26.6.3",
"moment": "^2.29.1",
diff --git a/admin/run/server.js b/admin/run/server.js
index 97a525427..bccefc65c 100644
--- a/admin/run/server.js
+++ b/admin/run/server.js
@@ -1,15 +1,21 @@
// Imports
const express = require('express')
-const serveStatic = require('serve-static')
+const path = require('path')
-// Port
+// Host & Port
+const hostname = '127.0.0.1'
const port = process.env.PORT || 8080
// Express Server
const app = express()
-// eslint-disable-next-line node/no-path-concat
-app.use(serveStatic(__dirname + '/../dist'))
-app.listen(port)
+// Serve files
+app.use(express.static(path.join(__dirname, '../dist')))
+// Default to index.html
+app.get('*', (req, res) => {
+ res.sendFile(path.join(__dirname, '../dist/index.html'))
+})
-// eslint-disable-next-line no-console
-console.log(`http://admin:${port} server started.`)
+app.listen(port, hostname, () => {
+ // eslint-disable-next-line no-console
+ console.log('Listening at http://%s:%s/', hostname, port)
+})
diff --git a/admin/src/components/ConfirmRegisterMailFormular.spec.js b/admin/src/components/ConfirmRegisterMailFormular.spec.js
index 705a185a3..78f5791dc 100644
--- a/admin/src/components/ConfirmRegisterMailFormular.spec.js
+++ b/admin/src/components/ConfirmRegisterMailFormular.spec.js
@@ -19,6 +19,7 @@ const mocks = {
}
const propsData = {
+ checked: false,
email: 'bob@baumeister.de',
dateLastSend: '',
}
diff --git a/admin/src/components/ConfirmRegisterMailFormular.vue b/admin/src/components/ConfirmRegisterMailFormular.vue
index 5052a8a3e..635b80939 100644
--- a/admin/src/components/ConfirmRegisterMailFormular.vue
+++ b/admin/src/components/ConfirmRegisterMailFormular.vue
@@ -1,19 +1,20 @@
-
- {{ $t('unregister_mail.text', { date: dateLastSend, mail: email }) }}
-
+
{{ $t('unregister_mail.text_true', { date: dateLastSend }) }}
+
+ {{ $t('unregister_mail.text_false', { date: dateLastSend, mail: email }) }}
-
-
-
-
-
- {{ $t('unregister_mail.button') }}
-
-
-
+
+
+
+
+
+ {{ $t('unregister_mail.button') }}
+
+
+
+
@@ -23,6 +24,9 @@ import { sendActivationEmail } from '../graphql/sendActivationEmail'
export default {
name: 'ConfirmRegisterMail',
props: {
+ checked: {
+ type: Boolean,
+ },
email: {
type: String,
},
diff --git a/admin/src/components/CreationFormular.spec.js b/admin/src/components/CreationFormular.spec.js
index 388ab6aa9..5b0ac09f5 100644
--- a/admin/src/components/CreationFormular.spec.js
+++ b/admin/src/components/CreationFormular.spec.js
@@ -99,7 +99,10 @@ describe('CreationFormular', () => {
describe('with mass creation', () => {
beforeEach(async () => {
jest.clearAllMocks()
- await wrapper.setProps({ type: 'massCreation' })
+ await wrapper.setProps({ type: 'massCreation', creation: [200, 400, 600] })
+ await wrapper.setData({ rangeMin: 180 })
+ await wrapper.setData({ text: 'Test create coins' })
+ await wrapper.setData({ value: 90 })
})
describe('first radio button', () => {
diff --git a/admin/src/components/CreationFormular.vue b/admin/src/components/CreationFormular.vue
index af966a952..49993d14a 100644
--- a/admin/src/components/CreationFormular.vue
+++ b/admin/src/components/CreationFormular.vue
@@ -123,6 +123,7 @@
diff --git a/admin/src/pages/UserSearch.spec.js b/admin/src/pages/UserSearch.spec.js
index 473b2bbdc..4beda0c49 100644
--- a/admin/src/pages/UserSearch.spec.js
+++ b/admin/src/pages/UserSearch.spec.js
@@ -5,15 +5,18 @@ const localVue = global.localVue
const apolloQueryMock = jest.fn().mockResolvedValue({
data: {
- searchUsers: [
- {
- firstName: 'Bibi',
- lastName: 'Bloxberg',
- email: 'bibi@bloxberg.de',
- creation: [200, 400, 600],
- emailChecked: false,
- },
- ],
+ searchUsers: {
+ userCount: 1,
+ userList: [
+ {
+ firstName: 'Bibi',
+ lastName: 'Bloxberg',
+ email: 'bibi@bloxberg.de',
+ creation: [200, 400, 600],
+ emailChecked: false,
+ },
+ ],
+ },
},
})
@@ -61,7 +64,7 @@ describe('UserSearch', () => {
})
it('filters the users by unconfirmed emails', () => {
- expect(wrapper.vm.searchResult).toHaveLength(0)
+ expect(wrapper.vm.searchResult).toHaveLength(1)
})
})
diff --git a/admin/src/pages/UserSearch.vue b/admin/src/pages/UserSearch.vue
index 2c8b289e0..4b9bcae8a 100644
--- a/admin/src/pages/UserSearch.vue
+++ b/admin/src/pages/UserSearch.vue
@@ -3,7 +3,7 @@
- {{ $t('unregistered_emails') }}
+ {{ filterCheckedEmails ? $t('all_emails') : $t('unregistered_emails') }}
@@ -21,6 +21,14 @@
:fieldsTable="fields"
:criteria="criteria"
/>
+
@@ -42,27 +50,13 @@ export default {
{ key: 'lastName', label: this.$t('lastname') },
{
key: 'creation',
- label: this.$t('open_creation'),
+ label: [
+ this.$moment().subtract(2, 'month').format('MMM'),
+ this.$moment().subtract(1, 'month').format('MMM'),
+ this.$moment().format('MMM'),
+ ].join(' | '),
formatter: (value, key, item) => {
- return (
- `
- ` +
- this.$moment().subtract(2, 'month').format('MMMM') +
- ` - ` +
- String(value[0]) +
- ` GDD
- ` +
- this.$moment().subtract(1, 'month').format('MMMM') +
- ` - ` +
- String(value[1]) +
- ` GDD
- ` +
- this.$moment().format('MMMM') +
- ` - ` +
- String(value[2]) +
- ` GDD
- `
- )
+ return value.join(' | ')
},
},
{ key: 'show_details', label: this.$t('details') },
@@ -81,14 +75,16 @@ export default {
beforeLastMonth: {
short: this.$moment().subtract(2, 'month').format('MMMM'),
},
+ filterCheckedEmails: false,
+ rows: 0,
+ currentPage: 1,
+ perPage: 25,
}
},
-
methods: {
unconfirmedRegisterMails() {
- this.searchResult = this.searchResult.filter((user) => {
- return user.emailChecked
- })
+ this.filterCheckedEmails = !this.filterCheckedEmails
+ this.getUsers()
},
getUsers() {
this.$apollo
@@ -96,16 +92,25 @@ export default {
query: searchUsers,
variables: {
searchText: this.criteria,
+ currentPage: this.currentPage,
+ pageSize: this.perPage,
+ notActivated: this.filterCheckedEmails,
},
})
.then((result) => {
- this.searchResult = result.data.searchUsers
+ this.rows = result.data.searchUsers.userCount
+ this.searchResult = result.data.searchUsers.userList
})
.catch((error) => {
this.$toasted.error(error.message)
})
},
},
+ watch: {
+ currentPage() {
+ this.getUsers()
+ },
+ },
created() {
this.getUsers()
},
diff --git a/admin/src/plugins/apolloProvider.js b/admin/src/plugins/apolloProvider.js
index 0e342b8fc..cde5408da 100644
--- a/admin/src/plugins/apolloProvider.js
+++ b/admin/src/plugins/apolloProvider.js
@@ -2,7 +2,6 @@ import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
import VueApollo from 'vue-apollo'
import CONFIG from '../config'
import store from '../store/store'
-import router from '../router/router'
import i18n from '../i18n'
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
@@ -18,7 +17,7 @@ const authLink = new ApolloLink((operation, forward) => {
if (response.errors && response.errors[0].message === '403.13 - Client certificate revoked') {
response.errors[0].message = i18n.t('error.session-expired')
store.dispatch('logout', null)
- if (router.currentRoute.path !== '/logout') router.push('/logout')
+ window.location.assign(CONFIG.WALLET_URL)
return response
}
const newToken = operation.getContext().response.headers.get('token')
diff --git a/admin/src/plugins/apolloProvider.test.js b/admin/src/plugins/apolloProvider.test.js
index e5f394e96..75e415901 100644
--- a/admin/src/plugins/apolloProvider.test.js
+++ b/admin/src/plugins/apolloProvider.test.js
@@ -4,12 +4,10 @@ import CONFIG from '../config'
import VueApollo from 'vue-apollo'
import store from '../store/store'
-import router from '../router/router'
import i18n from '../i18n'
jest.mock('vue-apollo')
jest.mock('../store/store')
-jest.mock('../router/router')
jest.mock('../i18n')
jest.mock('apollo-boost', () => {
@@ -59,13 +57,11 @@ describe('apolloProvider', () => {
errors: [{ message: '403.13 - Client certificate revoked' }],
}
- // mock router
- const routerPushMock = jest.fn()
- router.push = routerPushMock
- router.currentRoute = {
- path: '/overview',
+ const windowLocationMock = jest.fn()
+ delete window.location
+ window.location = {
+ assign: windowLocationMock,
}
-
// mock context
const setContextMock = jest.fn()
const getContextMock = jest.fn(() => {
@@ -128,21 +124,8 @@ describe('apolloProvider', () => {
expect(storeDispatchMock).toBeCalledWith('logout', null)
})
- describe('current route is not logout', () => {
- it('redirects to logout', () => {
- expect(routerPushMock).toBeCalledWith('/logout')
- })
- })
-
- describe('current route is logout', () => {
- beforeEach(() => {
- jest.clearAllMocks()
- router.currentRoute.path = '/logout'
- })
-
- it('does not redirect to logout', () => {
- expect(routerPushMock).not.toBeCalled()
- })
+ it('redirects to logout', () => {
+ expect(windowLocationMock).toBeCalledWith('http://localhost/login')
})
})
diff --git a/admin/src/router/guards.js b/admin/src/router/guards.js
index 4ed6c8516..dd61e8657 100644
--- a/admin/src/router/guards.js
+++ b/admin/src/router/guards.js
@@ -1,7 +1,7 @@
import { verifyLogin } from '../graphql/verifyLogin'
import CONFIG from '../config'
-const addNavigationGuards = (router, store, apollo) => {
+const addNavigationGuards = (router, store, apollo, i18n) => {
// store token on `authenticate`
router.beforeEach(async (to, from, next) => {
if (to.path === '/authenticate' && to.query && to.query.token) {
@@ -14,6 +14,7 @@ const addNavigationGuards = (router, store, apollo) => {
.then((result) => {
const moderator = result.data.verifyLogin
if (moderator.isAdmin) {
+ i18n.locale = moderator.language
store.commit('moderator', moderator)
next({ path: '/' })
} else {
diff --git a/admin/src/router/guards.test.js b/admin/src/router/guards.test.js
index cd5b33e68..da4dd5969 100644
--- a/admin/src/router/guards.test.js
+++ b/admin/src/router/guards.test.js
@@ -6,9 +6,11 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
data: {
verifyLogin: {
isAdmin: true,
+ language: 'de',
},
},
})
+const i18nLocaleMock = jest.fn()
const store = {
commit: storeCommitMock,
@@ -21,7 +23,11 @@ const apollo = {
query: apolloQueryMock,
}
-addNavigationGuards(router, store, apollo)
+const i18n = {
+ locale: i18nLocaleMock,
+}
+
+addNavigationGuards(router, store, apollo, i18n)
describe('navigation guards', () => {
beforeEach(() => {
@@ -33,19 +39,23 @@ describe('navigation guards', () => {
const next = jest.fn()
describe('with valid token and as admin', () => {
- beforeEach(() => {
- navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
+ beforeEach(async () => {
+ await navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
})
- it('commits the token to the store', async () => {
+ it('commits the token to the store', () => {
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
})
- it('commits the moderator to the store', () => {
- expect(storeCommitMock).toBeCalledWith('moderator', { isAdmin: true })
+ it.skip('sets the locale', () => {
+ expect(i18nLocaleMock).toBeCalledWith('de')
})
- it('redirects to /', async () => {
+ it('commits the moderator to the store', () => {
+ expect(storeCommitMock).toBeCalledWith('moderator', { isAdmin: true, language: 'de' })
+ })
+
+ it('redirects to /', () => {
expect(next).toBeCalledWith({ path: '/' })
})
})
diff --git a/backend/.env.dist b/backend/.env.dist
index f38e31be5..785b2c15d 100644
--- a/backend/.env.dist
+++ b/backend/.env.dist
@@ -8,6 +8,7 @@ DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=gradido_community
+TYPEORM_LOGGING_RELATIVE_PATH=typeorm.backend.log
#EMAIL=true
#EMAIL_USERNAME=
@@ -19,7 +20,8 @@ DB_DATABASE=gradido_community
#RESEND_TIME=
RESEND_TIME=10
-#EMAIL_LINK_VERIFICATION=http://localhost/vue/checkEmail/$1
+#EMAIL_LINK_VERIFICATION=http://localhost/checkEmail/{code}
+#EMAIL_LINK_SETPASSWORD=http://localhost/reset/{code}
#KLICKTIPP_USER=
#KLICKTIPP_PASSWORD=
diff --git a/backend/.env.template b/backend/.env.template
new file mode 100644
index 000000000..9ff9beb01
--- /dev/null
+++ b/backend/.env.template
@@ -0,0 +1,35 @@
+PORT=4000
+JWT_SECRET=$JWT_SECRET
+JWT_EXPIRES_IN=10m
+GRAPHIQL=false
+GDT_API_URL=https://gdt.gradido.net
+DB_HOST=localhost
+DB_PORT=3306
+DB_USER=$DB_USER
+DB_PASSWORD=$DB_PASSWORD
+DB_DATABASE=gradido_community
+TYPEORM_LOGGING_RELATIVE_PATH=$TYPEORM_LOGGING_RELATIVE_PATH
+
+EMAIL=$EMAIL
+EMAIL_USERNAME=$EMAIL_USERNAME
+EMAIL_SENDER=$EMAIL_SENDER
+EMAIL_PASSWORD=$EMAIL_PASSWORD
+EMAIL_SMTP_URL=$EMAIL_SMTP_URL
+EMAIL_SMTP_PORT=587
+#RESEND_TIME=1 minute, 60 => 1hour, 1440 (60 minutes * 24 hours) => 24 hours
+RESEND_TIME=10
+
+EMAIL_LINK_VERIFICATION=$EMAIL_LINK_VERIFICATION
+EMAIL_LINK_SETPASSWORD=$EMAIL_LINK_SETPASSWORD
+
+#KLICKTIPP_USER=
+#KLICKTIPP_PASSWORD=
+#KLICKTIPP_APIKEY_DE=
+#KLICKTIPP_APIKEY_EN=
+#KLICKTIPP=true
+COMMUNITY_NAME=
+COMMUNITY_URL=
+COMMUNITY_REGISTER_URL=
+COMMUNITY_DESCRIPTION=
+
+WEBHOOK_ELOPAGE_SECRET=$WEBHOOK_ELOPAGE_SECRET
\ No newline at end of file
diff --git a/backend/jest.config.js b/backend/jest.config.js
index 9d99c68f6..981475807 100644
--- a/backend/jest.config.js
+++ b/backend/jest.config.js
@@ -1,15 +1,18 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
-module.exports = {
- verbose: true,
- preset: 'ts-jest',
- collectCoverage: true,
- collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'],
- moduleNameMapper: {
- '@entity/(.*)': '/../database/build/entity/$1',
- // This is hack to fix a problem with the library `ts-mysql-migrate` which does differentiate between its ts/js state
- '@dbTools/(.*)':
- process.env.NODE_ENV === 'development'
- ? '/../database/src/$1'
- : '/../database/build/src/$1',
- },
+module.exports = async () => {
+ process.env.TZ = 'UTC'
+ return {
+ verbose: true,
+ preset: 'ts-jest',
+ collectCoverage: true,
+ collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'],
+ moduleNameMapper: {
+ '@entity/(.*)': '/../database/build/entity/$1',
+ // This is hack to fix a problem with the library `ts-mysql-migrate` which does differentiate between its ts/js state
+ '@dbTools/(.*)':
+ process.env.NODE_ENV === 'development'
+ ? '/../database/src/$1'
+ : '/../database/build/src/$1',
+ },
+ }
}
diff --git a/backend/package.json b/backend/package.json
index c9314f0fd..b61938044 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -18,10 +18,10 @@
},
"dependencies": {
"@types/jest": "^27.0.2",
+ "apollo-log": "^1.1.0",
"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/apis/KlicktippController.ts b/backend/src/apis/KlicktippController.ts
index 544b39d97..0777211ad 100644
--- a/backend/src/apis/KlicktippController.ts
+++ b/backend/src/apis/KlicktippController.ts
@@ -5,7 +5,7 @@ import CONFIG from '../config'
const klicktippConnector = new KlicktippConnector()
-export const signIn = async (
+export const klicktippSignIn = async (
email: string,
language: string,
firstName?: string,
diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts
index 6a2c05025..8988620d7 100644
--- a/backend/src/auth/RIGHTS.ts
+++ b/backend/src/auth/RIGHTS.ts
@@ -21,4 +21,9 @@ export enum RIGHTS {
HAS_ELOPAGE = 'HAS_ELOPAGE',
// Admin
SEARCH_USERS = 'SEARCH_USERS',
+ CREATE_PENDING_CREATION = 'CREATE_PENDING_CREATION',
+ UPDATE_PENDING_CREATION = 'UPDATE_PENDING_CREATION',
+ SEARCH_PENDING_CREATION = 'SEARCH_PENDING_CREATION',
+ DELETE_PENDING_CREATION = 'DELETE_PENDING_CREATION',
+ CONFIRM_PENDING_CREATION = 'CONFIRM_PENDING_CREATION',
}
diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts
index 88d07737c..7ca55eba1 100644
--- a/backend/src/config/index.ts
+++ b/backend/src/config/index.ts
@@ -18,6 +18,7 @@ const database = {
DB_USER: process.env.DB_USER || 'root',
DB_PASSWORD: process.env.DB_PASSWORD || '',
DB_DATABASE: process.env.DB_DATABASE || 'gradido_community',
+ TYPEORM_LOGGING_RELATIVE_PATH: process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.backend.log',
}
const klicktipp = {
@@ -31,8 +32,8 @@ const klicktipp = {
const community = {
COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung',
- COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/vue/',
- COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/vue/register',
+ COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/',
+ COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/register',
COMMUNITY_DESCRIPTION:
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.',
}
@@ -51,12 +52,13 @@ const email = {
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',
- EMAIL_LINK_SETPASSWORD: process.env.EMAIL_LINK_SETPASSWORD || 'http://localhost/vue/reset/$1',
+ process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/checkEmail/{code}',
+ EMAIL_LINK_SETPASSWORD: process.env.EMAIL_LINK_SETPASSWORD || 'http://localhost/reset/{code}',
RESEND_TIME: isNaN(resendTime) ? 10 : resendTime,
}
const webhook = {
+ // Elopage
WEBHOOK_ELOPAGE_SECRET: process.env.WEBHOOK_ELOPAGE_SECRET || 'secret',
}
diff --git a/backend/src/graphql/arg/CreatePendingCreationArgs.ts b/backend/src/graphql/arg/CreatePendingCreationArgs.ts
index dfd4a4e85..b90ad3231 100644
--- a/backend/src/graphql/arg/CreatePendingCreationArgs.ts
+++ b/backend/src/graphql/arg/CreatePendingCreationArgs.ts
@@ -1,5 +1,6 @@
-import { ArgsType, Field, Float, Int } from 'type-graphql'
+import { ArgsType, Field, Float, InputType, Int } from 'type-graphql'
+@InputType()
@ArgsType()
export default class CreatePendingCreationArgs {
@Field(() => String)
diff --git a/backend/src/graphql/arg/SearchUsersArgs.ts b/backend/src/graphql/arg/SearchUsersArgs.ts
new file mode 100644
index 000000000..5b40fd9ca
--- /dev/null
+++ b/backend/src/graphql/arg/SearchUsersArgs.ts
@@ -0,0 +1,16 @@
+import { ArgsType, Field, Int } from 'type-graphql'
+
+@ArgsType()
+export default class SearchUsersArgs {
+ @Field(() => String)
+ searchText: string
+
+ @Field(() => Int, { nullable: true })
+ currentPage?: number
+
+ @Field(() => Int, { nullable: true })
+ pageSize?: number
+
+ @Field(() => Boolean, { nullable: true })
+ notActivated?: boolean
+}
diff --git a/backend/src/graphql/arg/UpdatePendingCreationArgs.ts b/backend/src/graphql/arg/UpdatePendingCreationArgs.ts
index 8cd9ccc4c..73f70c058 100644
--- a/backend/src/graphql/arg/UpdatePendingCreationArgs.ts
+++ b/backend/src/graphql/arg/UpdatePendingCreationArgs.ts
@@ -1,7 +1,7 @@
import { ArgsType, Field, Float, Int } from 'type-graphql'
@ArgsType()
-export default class CreatePendingCreationArgs {
+export default class UpdatePendingCreationArgs {
@Field(() => Int)
id: number
diff --git a/backend/src/graphql/model/CreatePendingCreations.ts b/backend/src/graphql/model/CreatePendingCreations.ts
new file mode 100644
index 000000000..8d5bcef2c
--- /dev/null
+++ b/backend/src/graphql/model/CreatePendingCreations.ts
@@ -0,0 +1,19 @@
+import { ObjectType, Field } from 'type-graphql'
+
+@ObjectType()
+export class CreatePendingCreations {
+ constructor() {
+ this.success = false
+ this.successfulCreation = []
+ this.failedCreation = []
+ }
+
+ @Field(() => Boolean)
+ success: boolean
+
+ @Field(() => [String])
+ successfulCreation: string[]
+
+ @Field(() => [String])
+ failedCreation: string[]
+}
diff --git a/backend/src/graphql/model/UserAdmin.ts b/backend/src/graphql/model/UserAdmin.ts
index befc203a5..9e28a33d6 100644
--- a/backend/src/graphql/model/UserAdmin.ts
+++ b/backend/src/graphql/model/UserAdmin.ts
@@ -1,4 +1,4 @@
-import { ObjectType, Field } from 'type-graphql'
+import { ObjectType, Field, Int } from 'type-graphql'
@ObjectType()
export class UserAdmin {
@@ -20,3 +20,12 @@ export class UserAdmin {
@Field(() => Boolean)
emailChecked: boolean
}
+
+@ObjectType()
+export class SearchUsersResult {
+ @Field(() => Int)
+ userCount: number
+
+ @Field(() => [UserAdmin])
+ userList: UserAdmin[]
+}
diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts
index cb23ed577..8a7e034a5 100644
--- a/backend/src/graphql/resolver/AdminResolver.ts
+++ b/backend/src/graphql/resolver/AdminResolver.ts
@@ -1,7 +1,11 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+
import { Resolver, Query, Arg, Args, Authorized, Mutation, Ctx } from 'type-graphql'
import { getCustomRepository, Raw } from 'typeorm'
-import { UserAdmin } from '../model/UserAdmin'
+import { UserAdmin, SearchUsersResult } from '../model/UserAdmin'
import { PendingCreation } from '../model/PendingCreation'
+import { CreatePendingCreations } from '../model/CreatePendingCreations'
import { UpdatePendingCreation } from '../model/UpdatePendingCreation'
import { RIGHTS } from '../../auth/RIGHTS'
import { TransactionRepository } from '../../typeorm/repository/Transaction'
@@ -10,6 +14,7 @@ import { LoginPendingTasksAdminRepository } from '../../typeorm/repository/Login
import { UserRepository } from '../../typeorm/repository/User'
import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs'
import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs'
+import SearchUsersArgs from '../arg/SearchUsersArgs'
import moment from 'moment'
import { Transaction } from '@entity/Transaction'
import { TransactionCreation } from '@entity/TransactionCreation'
@@ -22,11 +27,13 @@ import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
@Resolver()
export class AdminResolver {
@Authorized([RIGHTS.SEARCH_USERS])
- @Query(() => [UserAdmin])
- async searchUsers(@Arg('searchText') searchText: string): Promise {
+ @Query(() => SearchUsersResult)
+ async searchUsers(
+ @Args() { searchText, currentPage = 1, pageSize = 25, notActivated = false }: SearchUsersArgs,
+ ): Promise {
const userRepository = getCustomRepository(UserRepository)
const users = await userRepository.findBySearchCriteria(searchText)
- const adminUsers = await Promise.all(
+ let adminUsers = await Promise.all(
users.map(async (user) => {
const adminUser = new UserAdmin()
adminUser.userId = user.id
@@ -38,10 +45,15 @@ export class AdminResolver {
return adminUser
}),
)
- return adminUsers
+ if (notActivated) adminUsers = adminUsers.filter((u) => !u.emailChecked)
+ const first = (currentPage - 1) * pageSize
+ return {
+ userCount: adminUsers.length,
+ userList: adminUsers.slice(first, first + pageSize),
+ }
}
- @Authorized([RIGHTS.SEARCH_USERS])
+ @Authorized([RIGHTS.CREATE_PENDING_CREATION])
@Mutation(() => [Number])
async createPendingCreation(
@Args() { email, amount, memo, creationDate, moderator }: CreatePendingCreationArgs,
@@ -61,12 +73,38 @@ export class AdminResolver {
loginPendingTaskAdmin.memo = memo
loginPendingTaskAdmin.moderator = moderator
- loginPendingTasksAdminRepository.save(loginPendingTaskAdmin)
+ await loginPendingTasksAdminRepository.save(loginPendingTaskAdmin)
}
- return await getUserCreations(user.id)
+ return getUserCreations(user.id)
}
- @Authorized([RIGHTS.SEARCH_USERS])
+ @Authorized([RIGHTS.CREATE_PENDING_CREATION])
+ @Mutation(() => CreatePendingCreations)
+ async createPendingCreations(
+ @Arg('pendingCreations', () => [CreatePendingCreationArgs])
+ pendingCreations: CreatePendingCreationArgs[],
+ ): Promise {
+ let success = false
+ const successfulCreation: string[] = []
+ const failedCreation: string[] = []
+ for (const pendingCreation of pendingCreations) {
+ await this.createPendingCreation(pendingCreation)
+ .then(() => {
+ successfulCreation.push(pendingCreation.email)
+ success = true
+ })
+ .catch(() => {
+ failedCreation.push(pendingCreation.email)
+ })
+ }
+ return {
+ success,
+ successfulCreation,
+ failedCreation,
+ }
+ }
+
+ @Authorized([RIGHTS.UPDATE_PENDING_CREATION])
@Mutation(() => UpdatePendingCreation)
async updatePendingCreation(
@Args() { id, email, amount, memo, creationDate, moderator }: UpdatePendingCreationArgs,
@@ -96,7 +134,7 @@ export class AdminResolver {
return result
}
- @Authorized([RIGHTS.SEARCH_USERS])
+ @Authorized([RIGHTS.SEARCH_PENDING_CREATION])
@Query(() => [PendingCreation])
async getPendingCreations(): Promise {
const loginPendingTasksAdminRepository = getCustomRepository(LoginPendingTasksAdminRepository)
@@ -124,7 +162,7 @@ export class AdminResolver {
return pendingCreationsPromise.reverse()
}
- @Authorized([RIGHTS.SEARCH_USERS])
+ @Authorized([RIGHTS.DELETE_PENDING_CREATION])
@Mutation(() => Boolean)
async deletePendingCreation(@Arg('id') id: number): Promise {
const loginPendingTasksAdminRepository = getCustomRepository(LoginPendingTasksAdminRepository)
@@ -133,7 +171,7 @@ export class AdminResolver {
return !!res
}
- @Authorized([RIGHTS.SEARCH_USERS])
+ @Authorized([RIGHTS.CONFIRM_PENDING_CREATION])
@Mutation(() => Boolean)
async confirmPendingCreation(@Arg('id') id: number, @Ctx() context: any): Promise {
const loginPendingTasksAdminRepository = getCustomRepository(LoginPendingTasksAdminRepository)
@@ -310,6 +348,7 @@ function isCreationValid(creations: number[], amount: number, creationDate: Date
}
return true
}
+
async function hasActivatedEmail(email: string): Promise {
const repository = getCustomRepository(LoginUserRepository)
const user = await repository.findByEmail(email)
diff --git a/backend/src/graphql/resolver/CommunityResolver.test.ts b/backend/src/graphql/resolver/CommunityResolver.test.ts
index afc6decec..cc481c101 100644
--- a/backend/src/graphql/resolver/CommunityResolver.test.ts
+++ b/backend/src/graphql/resolver/CommunityResolver.test.ts
@@ -53,8 +53,8 @@ describe('CommunityResolver', () => {
getCommunityInfo: {
name: 'Gradido Entwicklung',
description: 'Die lokale Entwicklungsumgebung von Gradido.',
- url: 'http://localhost/vue/',
- registerUrl: 'http://localhost/vue/register',
+ url: 'http://localhost/',
+ registerUrl: 'http://localhost/register',
},
},
})
@@ -75,22 +75,22 @@ describe('CommunityResolver', () => {
id: 1,
name: 'Gradido Entwicklung',
description: 'Die lokale Entwicklungsumgebung von Gradido.',
- url: 'http://localhost/vue/',
- registerUrl: 'http://localhost/vue/register-community',
+ url: 'http://localhost/',
+ registerUrl: 'http://localhost/register-community',
},
{
id: 2,
name: 'Gradido Staging',
description: 'Der Testserver der Gradido-Akademie.',
- url: 'https://stage1.gradido.net/vue/',
- registerUrl: 'https://stage1.gradido.net/vue/register-community',
+ url: 'https://stage1.gradido.net/',
+ registerUrl: 'https://stage1.gradido.net/register-community',
},
{
id: 3,
name: 'Gradido-Akademie',
description: 'Freies Institut für Wirtschaftsbionik.',
url: 'https://gradido.net',
- registerUrl: 'https://gdd1.gradido.com/vue/register-community',
+ registerUrl: 'https://gdd1.gradido.com/register-community',
},
],
},
@@ -112,7 +112,7 @@ describe('CommunityResolver', () => {
name: 'Gradido-Akademie',
description: 'Freies Institut für Wirtschaftsbionik.',
url: 'https://gradido.net',
- registerUrl: 'https://gdd1.gradido.com/vue/register-community',
+ registerUrl: 'https://gdd1.gradido.com/register-community',
},
],
},
diff --git a/backend/src/graphql/resolver/CommunityResolver.ts b/backend/src/graphql/resolver/CommunityResolver.ts
index 5c9d46f34..1fa3887dc 100644
--- a/backend/src/graphql/resolver/CommunityResolver.ts
+++ b/backend/src/graphql/resolver/CommunityResolver.ts
@@ -29,7 +29,7 @@ export class CommunityResolver {
name: 'Gradido-Akademie',
description: 'Freies Institut für Wirtschaftsbionik.',
url: 'https://gradido.net',
- registerUrl: 'https://gdd1.gradido.com/vue/register-community',
+ registerUrl: 'https://gdd1.gradido.com/register-community',
}),
]
return [
@@ -37,22 +37,22 @@ export class CommunityResolver {
id: 1,
name: 'Gradido Entwicklung',
description: 'Die lokale Entwicklungsumgebung von Gradido.',
- url: 'http://localhost/vue/',
- registerUrl: 'http://localhost/vue/register-community',
+ url: 'http://localhost/',
+ registerUrl: 'http://localhost/register-community',
}),
new Community({
id: 2,
name: 'Gradido Staging',
description: 'Der Testserver der Gradido-Akademie.',
- url: 'https://stage1.gradido.net/vue/',
- registerUrl: 'https://stage1.gradido.net/vue/register-community',
+ url: 'https://stage1.gradido.net/',
+ registerUrl: 'https://stage1.gradido.net/register-community',
}),
new Community({
id: 3,
name: 'Gradido-Akademie',
description: 'Freies Institut für Wirtschaftsbionik.',
url: 'https://gradido.net',
- registerUrl: 'https://gdd1.gradido.com/vue/register-community',
+ registerUrl: 'https://gdd1.gradido.com/register-community',
}),
]
}
diff --git a/backend/src/graphql/resolver/KlicktippResolver.ts b/backend/src/graphql/resolver/KlicktippResolver.ts
index fdffb940a..0ba2387e3 100644
--- a/backend/src/graphql/resolver/KlicktippResolver.ts
+++ b/backend/src/graphql/resolver/KlicktippResolver.ts
@@ -6,7 +6,7 @@ import {
getKlickTippUser,
getKlicktippTagMap,
unsubscribe,
- signIn,
+ klicktippSignIn,
} from '../../apis/KlicktippController'
import { RIGHTS } from '../../auth/RIGHTS'
import SubscribeNewsletterArgs from '../arg/SubscribeNewsletterArgs'
@@ -36,6 +36,6 @@ export class KlicktippResolver {
async subscribeNewsletter(
@Args() { email, language }: SubscribeNewsletterArgs,
): Promise {
- return await signIn(email, language)
+ return await klicktippSignIn(email, language)
}
}
diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts
index d8ef1fb0f..871e1b9d6 100644
--- a/backend/src/graphql/resolver/TransactionResolver.ts
+++ b/backend/src/graphql/resolver/TransactionResolver.ts
@@ -6,7 +6,7 @@ import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql'
import { getCustomRepository, getConnection, QueryRunner } from 'typeorm'
import CONFIG from '../../config'
-import { sendEMail } from '../../util/sendEMail'
+import { sendTransactionReceivedEmail } from '../../mailer/sendTransactionReceivedEmail'
import { Transaction } from '../model/Transaction'
import { TransactionList } from '../model/TransactionList'
@@ -503,7 +503,7 @@ export class TransactionResolver {
email: userEntity.email,
})
if (!resultGDTSum.success) throw new Error(resultGDTSum.data)
- transactions.gdtSum = resultGDTSum.data.sum || 0
+ transactions.gdtSum = Number(resultGDTSum.data.sum / 100) || 0
// get balance
const balanceRepository = getCustomRepository(BalanceRepository)
@@ -651,21 +651,14 @@ export class TransactionResolver {
}
// send notification email
// TODO: translate
- await sendEMail({
- from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
- to: `${recipiantUser.firstName} ${recipiantUser.lastName} <${recipiantUser.email}>`,
- subject: 'Gradido Überweisung',
- text: `Hallo ${recipiantUser.firstName} ${recipiantUser.lastName}
-
- Du hast soeben ${amount} GDD von ${senderUser.firstName} ${senderUser.lastName} erhalten.
- ${senderUser.firstName} ${senderUser.lastName} schreibt:
-
- ${memo}
-
- Bitte antworte nicht auf diese E-Mail!
-
- Mit freundlichen Grüßen,
- dein Gradido-Team`,
+ await sendTransactionReceivedEmail({
+ senderFirstName: senderUser.firstName,
+ senderLastName: senderUser.lastName,
+ recipientFirstName: recipiantUser.firstName,
+ recipientLastName: recipiantUser.lastName,
+ email: recipiantUser.email,
+ amount,
+ memo,
})
return 'success'
diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts
index d0c144e22..02e490edd 100644
--- a/backend/src/graphql/resolver/UserResolver.test.ts
+++ b/backend/src/graphql/resolver/UserResolver.test.ts
@@ -12,12 +12,20 @@ import { LoginUserBackup } from '@entity/LoginUserBackup'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { User } from '@entity/User'
import CONFIG from '../../config'
-import { sendEMail } from '../../util/sendEMail'
+import { sendAccountActivationEmail } from '../../mailer/sendAccountActivationEmail'
+import { klicktippSignIn } from '../../apis/KlicktippController'
-jest.mock('../../util/sendEMail', () => {
+jest.mock('../../mailer/sendAccountActivationEmail', () => {
return {
__esModule: true,
- sendEMail: jest.fn(),
+ sendAccountActivationEmail: jest.fn(),
+ }
+})
+
+jest.mock('../../apis/KlicktippController', () => {
+ return {
+ __esModule: true,
+ klicktippSignIn: jest.fn(),
}
})
@@ -62,7 +70,6 @@ describe('UserResolver', () => {
let result: any
let emailOptIn: string
- let newUser: User
beforeAll(async () => {
result = await mutate({ mutation, variables })
@@ -90,7 +97,6 @@ describe('UserResolver', () => {
loginEmailOptIn = await getRepository(LoginEmailOptIn)
.createQueryBuilder('login_email_optin')
.getMany()
- newUser = user[0]
emailOptIn = loginEmailOptIn[0].verificationCode.toString()
})
@@ -164,14 +170,12 @@ describe('UserResolver', () => {
describe('account activation email', () => {
it('sends an account activation email', () => {
- const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(/\$1/g, emailOptIn)
- expect(sendEMail).toBeCalledWith({
- from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
- to: `${newUser.firstName} ${newUser.lastName} <${newUser.email}>`,
- subject: 'Gradido: E-Mail Überprüfung',
- text:
- expect.stringContaining(`Hallo ${newUser.firstName} ${newUser.lastName},`) &&
- expect.stringContaining(activationLink),
+ const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(/{code}/g, emailOptIn)
+ expect(sendAccountActivationEmail).toBeCalledWith({
+ link: activationLink,
+ firstName: 'Peter',
+ lastName: 'Lustig',
+ email: 'peter@lustig.de',
})
})
})
@@ -224,6 +228,157 @@ describe('UserResolver', () => {
})
})
})
+
+ describe('setPassword', () => {
+ const createUserMutation = gql`
+ mutation (
+ $email: String!
+ $firstName: String!
+ $lastName: String!
+ $language: String!
+ $publisherId: Int
+ ) {
+ createUser(
+ email: $email
+ firstName: $firstName
+ lastName: $lastName
+ language: $language
+ publisherId: $publisherId
+ )
+ }
+ `
+
+ const createUserVariables = {
+ email: 'peter@lustig.de',
+ firstName: 'Peter',
+ lastName: 'Lustig',
+ language: 'de',
+ publisherId: 1234,
+ }
+
+ const setPasswordMutation = gql`
+ mutation ($code: String!, $password: String!) {
+ setPassword(code: $code, password: $password)
+ }
+ `
+ let result: any
+ let emailOptIn: string
+
+ describe('valid optin code and valid password', () => {
+ let loginUser: any
+ let newLoginUser: any
+ let newUser: any
+
+ beforeAll(async () => {
+ await mutate({ mutation: createUserMutation, variables: createUserVariables })
+ const loginEmailOptIn = await getRepository(LoginEmailOptIn)
+ .createQueryBuilder('login_email_optin')
+ .getMany()
+ loginUser = await getRepository(LoginUser).createQueryBuilder('login_user').getMany()
+ emailOptIn = loginEmailOptIn[0].verificationCode.toString()
+ result = await mutate({
+ mutation: setPasswordMutation,
+ variables: { code: emailOptIn, password: 'Aa12345_' },
+ })
+ newLoginUser = await getRepository(LoginUser).createQueryBuilder('login_user').getMany()
+ newUser = await getRepository(User).createQueryBuilder('state_user').getMany()
+ })
+
+ afterAll(async () => {
+ await resetDB()
+ })
+
+ it('sets email checked to true', () => {
+ expect(newLoginUser[0].emailChecked).toBeTruthy()
+ })
+
+ it('updates the password', () => {
+ expect(newLoginUser[0].password).toEqual('3917921995996627700')
+ })
+
+ it('updates the public Key on both user tables', () => {
+ expect(newLoginUser[0].pubKey).toEqual(expect.any(Buffer))
+ expect(newLoginUser[0].pubKey).not.toEqual(loginUser[0].pubKey)
+ expect(newLoginUser[0].pubKey).toEqual(newUser[0].pubkey)
+ })
+
+ it('updates the private Key', () => {
+ expect(newLoginUser[0].privKey).toEqual(expect.any(Buffer))
+ expect(newLoginUser[0].privKey).not.toEqual(loginUser[0].privKey)
+ })
+
+ it('removes the optin', async () => {
+ await expect(
+ getRepository(LoginEmailOptIn).createQueryBuilder('login_email_optin').getMany(),
+ ).resolves.toHaveLength(0)
+ })
+
+ it('calls the klicktipp API', () => {
+ expect(klicktippSignIn).toBeCalledWith(
+ loginUser[0].email,
+ loginUser[0].language,
+ loginUser[0].firstName,
+ loginUser[0].lastName,
+ )
+ })
+
+ it('returns true', () => {
+ expect(result).toBeTruthy()
+ })
+ })
+
+ describe('no valid password', () => {
+ beforeAll(async () => {
+ await mutate({ mutation: createUserMutation, variables: createUserVariables })
+ const loginEmailOptIn = await getRepository(LoginEmailOptIn)
+ .createQueryBuilder('login_email_optin')
+ .getMany()
+ emailOptIn = loginEmailOptIn[0].verificationCode.toString()
+ result = await mutate({
+ mutation: setPasswordMutation,
+ variables: { code: emailOptIn, password: 'not-valid' },
+ })
+ })
+
+ afterAll(async () => {
+ await resetDB()
+ })
+
+ it('throws an error', () => {
+ expect(result).toEqual(
+ expect.objectContaining({
+ errors: [
+ new GraphQLError(
+ 'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
+ ),
+ ],
+ }),
+ )
+ })
+ })
+
+ describe('no valid optin code', () => {
+ beforeAll(async () => {
+ await mutate({ mutation: createUserMutation, variables: createUserVariables })
+ result = await mutate({
+ mutation: setPasswordMutation,
+ variables: { code: 'not valid', password: 'Aa12345_' },
+ })
+ })
+
+ afterAll(async () => {
+ await resetDB()
+ })
+
+ it('throws an error', () => {
+ expect(result).toEqual(
+ expect.objectContaining({
+ errors: [new GraphQLError('Could not login with emailVerificationCode')],
+ }),
+ )
+ })
+ })
+ })
})
afterAll(async () => {
diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts
index 2f98a2f6f..bf4ef2a68 100644
--- a/backend/src/graphql/resolver/UserResolver.ts
+++ b/backend/src/graphql/resolver/UserResolver.ts
@@ -20,9 +20,10 @@ import { UserRepository } from '../../typeorm/repository/User'
import { LoginUser } from '@entity/LoginUser'
import { LoginUserBackup } from '@entity/LoginUserBackup'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
-import { sendEMail } from '../../util/sendEMail'
+import { sendResetPasswordEmail } from '../../mailer/sendResetPasswordEmail'
+import { sendAccountActivationEmail } from '../../mailer/sendAccountActivationEmail'
import { LoginElopageBuysRepository } from '../../typeorm/repository/LoginElopageBuys'
-import { signIn } from '../../apis/KlicktippController'
+import { klicktippSignIn } from '../../apis/KlicktippController'
import { RIGHTS } from '../../auth/RIGHTS'
import { ServerUserRepository } from '../../typeorm/repository/ServerUser'
import { ROLE_ADMIN } from '../../auth/ROLES'
@@ -447,15 +448,15 @@ export class UserResolver {
const emailOptIn = await createEmailOptIn(loginUserId, queryRunner)
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
- /\$1/g,
+ /{code}/g,
emailOptIn.verificationCode.toString(),
)
- const emailSent = await this.sendAccountActivationEmail(
- activationLink,
+ const emailSent = await sendAccountActivationEmail({
+ link: activationLink,
firstName,
lastName,
email,
- )
+ })
// In case EMails are disabled log the activation link for the user
if (!emailSent) {
@@ -472,29 +473,6 @@ export class UserResolver {
return 'success'
}
- private sendAccountActivationEmail(
- activationLink: string,
- firstName: string,
- lastName: string,
- email: string,
- ): Promise {
- return sendEMail({
- from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
- to: `${firstName} ${lastName} <${email}>`,
- subject: 'Gradido: E-Mail Überprüfung',
- text: `Hallo ${firstName} ${lastName},
-
- Deine EMail wurde soeben bei Gradido registriert.
-
- Klicke bitte auf diesen Link, um die Registrierung abzuschließen und dein Gradido-Konto zu aktivieren:
- ${activationLink}
- oder kopiere den obigen Link in dein Browserfenster.
-
- Mit freundlichen Grüßen,
- dein Gradido-Team`,
- })
- }
-
@Mutation(() => Boolean)
async sendActivationEmail(@Arg('email') email: string): Promise {
const loginUserRepository = getCustomRepository(LoginUserRepository)
@@ -508,16 +486,16 @@ export class UserResolver {
const emailOptIn = await createEmailOptIn(loginUser.id, queryRunner)
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
- /\$1/g,
+ /{code}/g,
emailOptIn.verificationCode.toString(),
)
- const emailSent = await this.sendAccountActivationEmail(
- activationLink,
- loginUser.firstName,
- loginUser.lastName,
+ const emailSent = await sendAccountActivationEmail({
+ link: activationLink,
+ firstName: loginUser.firstName,
+ lastName: loginUser.lastName,
email,
- )
+ })
// In case EMails are disabled log the activation link for the user
if (!emailSent) {
@@ -545,22 +523,15 @@ export class UserResolver {
const optInCode = await getOptInCode(loginUser)
const link = CONFIG.EMAIL_LINK_SETPASSWORD.replace(
- /\$1/g,
+ /{code}/g,
optInCode.verificationCode.toString(),
)
- const emailSent = await sendEMail({
- from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
- to: `${loginUser.firstName} ${loginUser.lastName} <${email}>`,
- subject: 'Gradido: Reset Password',
- text: `Hallo ${loginUser.firstName} ${loginUser.lastName},
-
- Du oder jemand anderes hat für dieses Konto ein Zurücksetzen des Passworts angefordert.
- Wenn du es warst, klicke bitte auf den Link: ${link}
- oder kopiere den obigen Link in Dein Browserfenster.
-
- Mit freundlichen Grüßen,
- dein Gradido-Team`,
+ const emailSent = await sendResetPasswordEmail({
+ link,
+ firstName: loginUser.firstName,
+ lastName: loginUser.lastName,
+ email,
})
// In case EMails are disabled log the activation link for the user
@@ -670,7 +641,12 @@ export class UserResolver {
// TODO do we always signUp the user? How to handle things with old users?
if (optInCode.emailOptInTypeId === EMAIL_OPT_IN_REGISTER) {
try {
- await signIn(loginUser.email, loginUser.language, loginUser.firstName, loginUser.lastName)
+ await klicktippSignIn(
+ loginUser.email,
+ loginUser.language,
+ loginUser.firstName,
+ loginUser.lastName,
+ )
} catch {
// TODO is this a problem?
// eslint-disable-next-line no-console
@@ -738,7 +714,7 @@ export class UserResolver {
if (password && passwordNew) {
// TODO: This had some error cases defined - like missing private key. This is no longer checked.
const oldPasswordHash = SecretKeyCryptographyCreateKey(loginUser.email, password)
- if (loginUser.password !== oldPasswordHash[0].readBigUInt64LE()) {
+ if (BigInt(loginUser.password.toString()) !== oldPasswordHash[0].readBigUInt64LE()) {
throw new Error(`Old password is invalid`)
}
@@ -748,7 +724,7 @@ export class UserResolver {
const encryptedPrivkey = SecretKeyCryptographyEncrypt(privKey, newPasswordHash[1])
// Save new password hash and newly encrypted private key
- loginUser.password = newPasswordHash[0].readBigInt64LE()
+ loginUser.password = newPasswordHash[0].readBigUInt64LE()
loginUser.privKey = encryptedPrivkey
}
diff --git a/backend/src/index.ts b/backend/src/index.ts
index a2e533e34..4c08b422d 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -13,7 +13,7 @@ async function main() {
console.log(`Server is running at http://localhost:${CONFIG.PORT}`)
if (CONFIG.GRAPHIQL) {
// eslint-disable-next-line no-console
- console.log(`GraphIQL available at http://localhost:${CONFIG.PORT}/graphql`)
+ console.log(`GraphIQL available at http://localhost:${CONFIG.PORT}`)
}
})
}
diff --git a/backend/src/mailer/sendAccountActivationEmail.test.ts b/backend/src/mailer/sendAccountActivationEmail.test.ts
new file mode 100644
index 000000000..c53fc0994
--- /dev/null
+++ b/backend/src/mailer/sendAccountActivationEmail.test.ts
@@ -0,0 +1,29 @@
+import { sendAccountActivationEmail } from './sendAccountActivationEmail'
+import { sendEMail } from './sendEMail'
+
+jest.mock('./sendEMail', () => {
+ return {
+ __esModule: true,
+ sendEMail: jest.fn(),
+ }
+})
+
+describe('sendAccountActivationEmail', () => {
+ beforeEach(async () => {
+ await sendAccountActivationEmail({
+ link: 'activationLink',
+ firstName: 'Peter',
+ lastName: 'Lustig',
+ email: 'peter@lustig.de',
+ })
+ })
+
+ it('calls sendEMail', () => {
+ expect(sendEMail).toBeCalledWith({
+ to: `Peter Lustig `,
+ subject: 'Gradido: E-Mail Überprüfung',
+ text:
+ expect.stringContaining('Hallo Peter Lustig') && expect.stringContaining('activationLink'),
+ })
+ })
+})
diff --git a/backend/src/mailer/sendAccountActivationEmail.ts b/backend/src/mailer/sendAccountActivationEmail.ts
new file mode 100644
index 000000000..05c3104cb
--- /dev/null
+++ b/backend/src/mailer/sendAccountActivationEmail.ts
@@ -0,0 +1,15 @@
+import { sendEMail } from './sendEMail'
+import { accountActivation } from './text/accountActivation'
+
+export const sendAccountActivationEmail = (data: {
+ link: string
+ firstName: string
+ lastName: string
+ email: string
+}): Promise => {
+ return sendEMail({
+ to: `${data.firstName} ${data.lastName} <${data.email}>`,
+ subject: accountActivation.de.subject,
+ text: accountActivation.de.text(data),
+ })
+}
diff --git a/backend/src/mailer/sendEMail.test.ts b/backend/src/mailer/sendEMail.test.ts
new file mode 100644
index 000000000..5baae00ab
--- /dev/null
+++ b/backend/src/mailer/sendEMail.test.ts
@@ -0,0 +1,92 @@
+import { sendEMail } from './sendEMail'
+import { createTransport } from 'nodemailer'
+import CONFIG from '../config'
+
+CONFIG.EMAIL = false
+CONFIG.EMAIL_SMTP_URL = 'EMAIL_SMTP_URL'
+CONFIG.EMAIL_SMTP_PORT = '1234'
+CONFIG.EMAIL_USERNAME = 'user'
+CONFIG.EMAIL_PASSWORD = 'pwd'
+
+jest.mock('nodemailer', () => {
+ return {
+ __esModule: true,
+ createTransport: jest.fn(() => {
+ return {
+ sendMail: jest.fn(() => {
+ return {
+ messageId: 'message',
+ }
+ }),
+ }
+ }),
+ }
+})
+
+describe('sendEMail', () => {
+ let result: boolean
+ describe('config email is false', () => {
+ // eslint-disable-next-line no-console
+ const consoleLog = console.log
+ const consoleLogMock = jest.fn()
+ // eslint-disable-next-line no-console
+ console.log = consoleLogMock
+ beforeEach(async () => {
+ result = await sendEMail({
+ to: 'receiver@mail.org',
+ subject: 'Subject',
+ text: 'Text text text',
+ })
+ })
+
+ afterAll(() => {
+ // eslint-disable-next-line no-console
+ console.log = consoleLog
+ })
+
+ it('logs warining to console', () => {
+ expect(consoleLogMock).toBeCalledWith('Emails are disabled via config')
+ })
+
+ it('returns false', () => {
+ expect(result).toBeFalsy()
+ })
+ })
+
+ describe('config email is true', () => {
+ beforeEach(async () => {
+ CONFIG.EMAIL = true
+ result = await sendEMail({
+ to: 'receiver@mail.org',
+ subject: 'Subject',
+ text: 'Text text text',
+ })
+ })
+
+ it('calls the transporter', () => {
+ expect(createTransport).toBeCalledWith({
+ host: 'EMAIL_SMTP_URL',
+ port: 1234,
+ secure: false,
+ requireTLS: true,
+ auth: {
+ user: 'user',
+ pass: 'pwd',
+ },
+ })
+ })
+
+ it('calls sendMail of transporter', () => {
+ expect((createTransport as jest.Mock).mock.results[0].value.sendMail).toBeCalledWith({
+ from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
+ to: 'receiver@mail.org',
+ subject: 'Subject',
+ text: 'Text text text',
+ })
+ })
+
+ it('returns true', () => {
+ expect(result).toBeTruthy()
+ })
+ })
+})
diff --git a/backend/src/util/sendEMail.ts b/backend/src/mailer/sendEMail.ts
similarity index 85%
rename from backend/src/util/sendEMail.ts
rename to backend/src/mailer/sendEMail.ts
index 4c239980d..e26589347 100644
--- a/backend/src/util/sendEMail.ts
+++ b/backend/src/mailer/sendEMail.ts
@@ -3,7 +3,6 @@ import { createTransport } from 'nodemailer'
import CONFIG from '../config'
export const sendEMail = async (emailDef: {
- from: string
to: string
subject: string
text: string
@@ -23,7 +22,10 @@ export const sendEMail = async (emailDef: {
pass: CONFIG.EMAIL_PASSWORD,
},
})
- const info = await transporter.sendMail(emailDef)
+ const info = await transporter.sendMail({
+ ...emailDef,
+ from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
+ })
if (!info.messageId) {
throw new Error('error sending notification email, but transaction succeed')
}
diff --git a/backend/src/mailer/sendResetPasswordEmail.test.ts b/backend/src/mailer/sendResetPasswordEmail.test.ts
new file mode 100644
index 000000000..4bc8ceba0
--- /dev/null
+++ b/backend/src/mailer/sendResetPasswordEmail.test.ts
@@ -0,0 +1,28 @@
+import { sendResetPasswordEmail } from './sendResetPasswordEmail'
+import { sendEMail } from './sendEMail'
+
+jest.mock('./sendEMail', () => {
+ return {
+ __esModule: true,
+ sendEMail: jest.fn(),
+ }
+})
+
+describe('sendResetPasswordEmail', () => {
+ beforeEach(async () => {
+ await sendResetPasswordEmail({
+ link: 'resetLink',
+ firstName: 'Peter',
+ lastName: 'Lustig',
+ email: 'peter@lustig.de',
+ })
+ })
+
+ it('calls sendEMail', () => {
+ expect(sendEMail).toBeCalledWith({
+ to: `Peter Lustig `,
+ subject: 'Gradido: Passwort zurücksetzen',
+ text: expect.stringContaining('Hallo Peter Lustig') && expect.stringContaining('resetLink'),
+ })
+ })
+})
diff --git a/backend/src/mailer/sendResetPasswordEmail.ts b/backend/src/mailer/sendResetPasswordEmail.ts
new file mode 100644
index 000000000..c9f5b23e9
--- /dev/null
+++ b/backend/src/mailer/sendResetPasswordEmail.ts
@@ -0,0 +1,15 @@
+import { sendEMail } from './sendEMail'
+import { resetPassword } from './text/resetPassword'
+
+export const sendResetPasswordEmail = (data: {
+ link: string
+ firstName: string
+ lastName: string
+ email: string
+}): Promise => {
+ return sendEMail({
+ to: `${data.firstName} ${data.lastName} <${data.email}>`,
+ subject: resetPassword.de.subject,
+ text: resetPassword.de.text(data),
+ })
+}
diff --git a/backend/src/mailer/sendTransactionReceivedEmail.test.ts b/backend/src/mailer/sendTransactionReceivedEmail.test.ts
new file mode 100644
index 000000000..29f227185
--- /dev/null
+++ b/backend/src/mailer/sendTransactionReceivedEmail.test.ts
@@ -0,0 +1,35 @@
+import { sendTransactionReceivedEmail } from './sendTransactionReceivedEmail'
+import { sendEMail } from './sendEMail'
+
+jest.mock('./sendEMail', () => {
+ return {
+ __esModule: true,
+ sendEMail: jest.fn(),
+ }
+})
+
+describe('sendTransactionReceivedEmail', () => {
+ beforeEach(async () => {
+ await sendTransactionReceivedEmail({
+ senderFirstName: 'Bibi',
+ senderLastName: 'Bloxberg',
+ recipientFirstName: 'Peter',
+ recipientLastName: 'Lustig',
+ email: 'peter@lustig.de',
+ amount: 42.0,
+ memo: 'Vielen herzlichen Dank für den neuen Hexenbesen!',
+ })
+ })
+
+ it('calls sendEMail', () => {
+ expect(sendEMail).toBeCalledWith({
+ to: `Peter Lustig `,
+ subject: 'Gradido Überweisung',
+ text:
+ expect.stringContaining('Hallo Peter Lustig') &&
+ expect.stringContaining('42,00 GDD') &&
+ expect.stringContaining('Bibi Bloxberg') &&
+ expect.stringContaining('Vielen herzlichen Dank für den neuen Hexenbesen!'),
+ })
+ })
+})
diff --git a/backend/src/mailer/sendTransactionReceivedEmail.ts b/backend/src/mailer/sendTransactionReceivedEmail.ts
new file mode 100644
index 000000000..3560f6548
--- /dev/null
+++ b/backend/src/mailer/sendTransactionReceivedEmail.ts
@@ -0,0 +1,18 @@
+import { sendEMail } from './sendEMail'
+import { transactionReceived } from './text/transactionReceived'
+
+export const sendTransactionReceivedEmail = (data: {
+ senderFirstName: string
+ senderLastName: string
+ recipientFirstName: string
+ recipientLastName: string
+ email: string
+ amount: number
+ memo: string
+}): Promise => {
+ return sendEMail({
+ to: `${data.recipientFirstName} ${data.recipientLastName} <${data.email}>`,
+ subject: transactionReceived.de.subject,
+ text: transactionReceived.de.text(data),
+ })
+}
diff --git a/backend/src/mailer/text/accountActivation.ts b/backend/src/mailer/text/accountActivation.ts
new file mode 100644
index 000000000..c4f70ff0f
--- /dev/null
+++ b/backend/src/mailer/text/accountActivation.ts
@@ -0,0 +1,16 @@
+export const accountActivation = {
+ de: {
+ subject: 'Gradido: E-Mail Überprüfung',
+ text: (data: { link: string; firstName: string; lastName: string; email: string }): string =>
+ `Hallo ${data.firstName} ${data.lastName},
+
+Deine EMail wurde soeben bei Gradido registriert.
+
+Klicke bitte auf diesen Link, um die Registrierung abzuschließen und dein Gradido-Konto zu aktivieren:
+${data.link}
+oder kopiere den obigen Link in dein Browserfenster.
+
+Mit freundlichen Grüßen,
+dein Gradido-Team`,
+ },
+}
diff --git a/backend/src/mailer/text/resetPassword.ts b/backend/src/mailer/text/resetPassword.ts
new file mode 100644
index 000000000..58b13cbcd
--- /dev/null
+++ b/backend/src/mailer/text/resetPassword.ts
@@ -0,0 +1,14 @@
+export const resetPassword = {
+ de: {
+ subject: 'Gradido: Passwort zurücksetzen',
+ text: (data: { link: string; firstName: string; lastName: string; email: string }): string =>
+ `Hallo ${data.firstName} ${data.lastName},
+
+Du oder jemand anderes hat für dieses Konto ein Zurücksetzen des Passworts angefordert.
+Wenn du es warst, klicke bitte auf den Link: ${data.link}
+oder kopiere den obigen Link in Dein Browserfenster.
+
+Mit freundlichen Grüßen,
+dein Gradido-Team`,
+ },
+}
diff --git a/backend/src/mailer/text/transactionReceived.ts b/backend/src/mailer/text/transactionReceived.ts
new file mode 100644
index 000000000..3df2b718a
--- /dev/null
+++ b/backend/src/mailer/text/transactionReceived.ts
@@ -0,0 +1,27 @@
+export const transactionReceived = {
+ de: {
+ subject: 'Gradido Überweisung',
+ text: (data: {
+ senderFirstName: string
+ senderLastName: string
+ recipientFirstName: string
+ recipientLastName: string
+ email: string
+ amount: number
+ memo: string
+ }): string =>
+ `Hallo ${data.recipientFirstName} ${data.recipientLastName}
+
+Du hast soeben ${data.amount.toFixed(2).replace('.', ',')} GDD von ${data.senderFirstName} ${
+ data.senderLastName
+ } erhalten.
+${data.senderFirstName} ${data.senderLastName} schreibt:
+
+${data.memo}
+
+Bitte antworte nicht auf diese E-Mail!
+
+Mit freundlichen Grüßen,
+dein Gradido-Team`,
+ },
+}
diff --git a/backend/src/middleware/klicktippMiddleware.ts b/backend/src/middleware/klicktippMiddleware.ts
index 69a74480d..d0fde8195 100644
--- a/backend/src/middleware/klicktippMiddleware.ts
+++ b/backend/src/middleware/klicktippMiddleware.ts
@@ -1,5 +1,5 @@
import { MiddlewareFn } from 'type-graphql'
-import { /* signIn, */ getKlickTippUser } from '../apis/KlicktippController'
+import { /* klicktippSignIn, */ getKlickTippUser } from '../apis/KlicktippController'
import { KlickTipp } from '../graphql/model/KlickTipp'
import CONFIG from '../config/index'
@@ -12,7 +12,7 @@ import CONFIG from '../config/index'
// // Do Something here before resolver is called
// const result = await next()
// // Do Something here after resolver is completed
-// await signIn(result.email, result.language, result.firstName, result.lastName)
+// await klicktippSignIn(result.email, result.language, result.firstName, result.lastName)
// return result
// }
diff --git a/backend/src/server/createServer.ts b/backend/src/server/createServer.ts
index 4ed20232d..c7459b189 100644
--- a/backend/src/server/createServer.ts
+++ b/backend/src/server/createServer.ts
@@ -6,7 +6,6 @@ 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'
@@ -54,8 +53,19 @@ const createServer = async (context: any = serverContext): Promise => {
// cors
app.use(cors)
- // bodyparser
- app.use(bodyParser.json())
+ // bodyparser json
+ app.use(express.json())
+ // bodyparser text for elopage
+ app.use(express.text())
+
+ // Log every request
+ /*
+ app.use((req, res, next) => {
+ // eslint-disable-next-line no-console
+ console.log(req)
+ next()
+ })
+ */
// Elopage Webhook
app.post('/hook/elopage/' + CONFIG.WEBHOOK_ELOPAGE_SECRET, elopageWebhook)
@@ -64,10 +74,11 @@ const createServer = async (context: any = serverContext): Promise => {
const apollo = new ApolloServer({
schema: await schema(),
playground: CONFIG.GRAPHIQL,
+ introspection: CONFIG.GRAPHIQL,
context,
plugins,
})
- apollo.applyMiddleware({ app })
+ apollo.applyMiddleware({ app, path: '/' })
return { apollo, app, con }
}
diff --git a/backend/src/server/plugins.ts b/backend/src/server/plugins.ts
index 5436d595b..0bb5f9f98 100644
--- a/backend/src/server/plugins.ts
+++ b/backend/src/server/plugins.ts
@@ -1,6 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+import { ApolloLogPlugin } from 'apollo-log'
+
const plugins = [
{
requestDidStart() {
@@ -8,13 +10,18 @@ const plugins = [
willSendResponse(requestContext: any) {
const { setHeaders = [] } = requestContext.context
setHeaders.forEach(({ key, value }: { [key: string]: string }) => {
- requestContext.response.http.headers.append(key, value)
+ if (requestContext.response.http.headers.get(key)) {
+ requestContext.response.http.headers.set(key, value)
+ } else {
+ requestContext.response.http.headers.append(key, value)
+ }
})
return requestContext
},
}
},
},
+ ApolloLogPlugin(),
]
export default plugins
diff --git a/backend/src/typeorm/connection.ts b/backend/src/typeorm/connection.ts
index 5709c29b6..65e3feb1c 100644
--- a/backend/src/typeorm/connection.ts
+++ b/backend/src/typeorm/connection.ts
@@ -1,4 +1,4 @@
-import { createConnection, Connection } from 'typeorm'
+import { createConnection, Connection, FileLogger } from 'typeorm'
import CONFIG from '../config'
import { entities } from '@entity/index'
@@ -15,6 +15,10 @@ const connection = async (): Promise => {
database: CONFIG.DB_DATABASE,
entities,
synchronize: false,
+ logging: true,
+ logger: new FileLogger('all', {
+ logPath: CONFIG.TYPEORM_LOGGING_RELATIVE_PATH,
+ }),
})
} catch (error) {
// eslint-disable-next-line no-console
diff --git a/backend/src/typeorm/repository/UserTransaction.ts b/backend/src/typeorm/repository/UserTransaction.ts
index 57f89d5a5..386145d03 100644
--- a/backend/src/typeorm/repository/UserTransaction.ts
+++ b/backend/src/typeorm/repository/UserTransaction.ts
@@ -1,6 +1,7 @@
import { EntityRepository, Repository } from 'typeorm'
import { Order } from '../../graphql/enum/Order'
import { UserTransaction } from '@entity/UserTransaction'
+import { TransactionTypeId } from '../../graphql/enum/TransactionTypeId'
@EntityRepository(UserTransaction)
export class UserTransactionRepository extends Repository {
@@ -14,7 +15,9 @@ export class UserTransactionRepository extends Repository {
if (onlyCreation) {
return this.createQueryBuilder('userTransaction')
.where('userTransaction.userId = :userId', { userId })
- .andWhere('userTransaction.type = "creation"')
+ .andWhere('userTransaction.transactionTypeId = :transactionTypeId', {
+ transactionTypeId: TransactionTypeId.CREATION,
+ })
.orderBy('userTransaction.balanceDate', order)
.limit(limit)
.offset(offset)
diff --git a/backend/src/webhook/elopage.ts b/backend/src/webhook/elopage.ts
index 80fa90933..6f5b96e5f 100644
--- a/backend/src/webhook/elopage.ts
+++ b/backend/src/webhook/elopage.ts
@@ -28,19 +28,23 @@
*/
import { LoginElopageBuys } from '@entity/LoginElopageBuys'
-import { LoginUser } from '@entity/LoginUser'
+import { getCustomRepository } from 'typeorm'
import { UserResolver } from '../graphql/resolver/UserResolver'
+import { LoginElopageBuysRepository } from '../typeorm/repository/LoginElopageBuys'
+import { LoginUserRepository } from '../typeorm/repository/LoginUser'
export const elopageWebhook = async (req: any, res: any): Promise => {
+ // eslint-disable-next-line no-console
+ console.log('Elopage Hook received', req.body)
res.status(200).end() // Responding is important
-
+ const loginElopgaeBuyRepository = await getCustomRepository(LoginElopageBuysRepository)
const loginElopgaeBuy = new LoginElopageBuys()
let firstName = ''
let lastName = ''
const entries = req.body.split('&')
- entries.foreach((entry: string) => {
+ entries.forEach((entry: string) => {
const keyVal = entry.split('=')
- if (keyVal.length !== 2) {
+ if (keyVal.length > 2) {
throw new Error(`Error parsing entry '${entry}'`)
}
const key = keyVal[0]
@@ -88,8 +92,10 @@ export const elopageWebhook = async (req: any, res: any): Promise => {
lastName = val
break
default:
+ // this is too spammy
// eslint-disable-next-line no-console
- console.log(`Unknown Elopage Value '${entry}'`)
+ // console.log(`Unknown Elopage Value '${entry}'`)
+ break
}
})
@@ -101,7 +107,7 @@ export const elopageWebhook = async (req: any, res: any): Promise => {
}
// Save the hook data
- await loginElopgaeBuy.save()
+ await loginElopgaeBuyRepository.save(loginElopgaeBuy)
// create user for certain products
/*
@@ -133,7 +139,8 @@ export const elopageWebhook = async (req: any, res: any): Promise => {
}
// Do we already have such a user?
- if ((await LoginUser.count({ email })) !== 0) {
+ const loginUserRepository = await getCustomRepository(LoginUserRepository)
+ if ((await loginUserRepository.count({ email })) !== 0) {
// eslint-disable-next-line no-console
console.log(`Did not create User - already exists with email: ${email}`)
return
diff --git a/backend/yarn.lock b/backend/yarn.lock
index b46bc183d..e01796c0c 100644
--- a/backend/yarn.lock
+++ b/backend/yarn.lock
@@ -2,7 +2,7 @@
# yarn lockfile v1
-"@apollo/protobufjs@1.2.2":
+"@apollo/protobufjs@1.2.2", "@apollo/protobufjs@^1.0.3":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.2.tgz#4bd92cd7701ccaef6d517cdb75af2755f049f87c"
integrity sha512-vF+zxhPiLtkwxONs6YanSt1EpwpGilThpneExUN5K3tCymuxNnVq2yojTvnpRjv2QfsEIt/n7ozPIIzBLwGIDQ==
@@ -1273,6 +1273,24 @@ apollo-link@^1.2.14:
tslib "^1.9.3"
zen-observable-ts "^0.8.21"
+apollo-log@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/apollo-log/-/apollo-log-1.1.0.tgz#e21287c917cf735b77adc06f07034f965e9b24de"
+ integrity sha512-TciLu+85LSqk7t7ZGKrYN5jFiCcRMLujBjrLiOQGHGgVVkvmKlwK0oELSS9kiHQIhTq23p8qVVWb08spLpQ7Jw==
+ dependencies:
+ apollo-server-plugin-base "^0.10.4"
+ chalk "^4.1.0"
+ fast-safe-stringify "^2.0.7"
+ loglevelnext "^4.0.1"
+ nanoid "^3.1.20"
+
+apollo-reporting-protobuf@^0.6.2:
+ version "0.6.2"
+ resolved "https://registry.yarnpkg.com/apollo-reporting-protobuf/-/apollo-reporting-protobuf-0.6.2.tgz#5572866be9b77f133916532b10e15fbaa4158304"
+ integrity sha512-WJTJxLM+MRHNUxt1RTl4zD0HrLdH44F2mDzMweBj1yHL0kSt8I1WwoiF/wiGVSpnG48LZrBegCaOJeuVbJTbtw==
+ dependencies:
+ "@apollo/protobufjs" "^1.0.3"
+
apollo-reporting-protobuf@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/apollo-reporting-protobuf/-/apollo-reporting-protobuf-0.8.0.tgz#ae9d967934d3d8ed816fc85a0d8068ef45c371b9"
@@ -1280,6 +1298,13 @@ apollo-reporting-protobuf@^0.8.0:
dependencies:
"@apollo/protobufjs" "1.2.2"
+apollo-server-caching@^0.5.3:
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/apollo-server-caching/-/apollo-server-caching-0.5.3.tgz#cf42a77ad09a46290a246810075eaa029b5305e1"
+ integrity sha512-iMi3087iphDAI0U2iSBE9qtx9kQoMMEWr6w+LwXruBD95ek9DWyj7OeC2U/ngLjRsXM43DoBDXlu7R+uMjahrQ==
+ dependencies:
+ lru-cache "^6.0.0"
+
apollo-server-caching@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/apollo-server-caching/-/apollo-server-caching-0.7.0.tgz#e6d1e68e3bb571cba63a61f60b434fb771c6ff39"
@@ -1318,7 +1343,7 @@ apollo-server-core@^2.25.2:
subscriptions-transport-ws "^0.9.19"
uuid "^8.0.0"
-apollo-server-env@^3.1.0:
+apollo-server-env@^3.0.0, apollo-server-env@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/apollo-server-env/-/apollo-server-env-3.1.0.tgz#0733c2ef50aea596cc90cf40a53f6ea2ad402cd0"
integrity sha512-iGdZgEOAuVop3vb0F2J3+kaBVi4caMoxefHosxmgzAbbSpvWehB8Y1QiSyyMeouYC38XNVk5wnZl+jdGSsWsIQ==
@@ -1354,6 +1379,13 @@ apollo-server-express@^2.25.2:
subscriptions-transport-ws "^0.9.19"
type-is "^1.6.16"
+apollo-server-plugin-base@^0.10.4:
+ version "0.10.4"
+ resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.10.4.tgz#fbf73f64f95537ca9f9639dd7c535eb5eeb95dcd"
+ integrity sha512-HRhbyHgHFTLP0ImubQObYhSgpmVH4Rk1BinnceZmwudIVLKrqayIVOELdyext/QnSmmzg5W7vF3NLGBcVGMqDg==
+ dependencies:
+ apollo-server-types "^0.6.3"
+
apollo-server-plugin-base@^0.13.0:
version "0.13.0"
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.13.0.tgz#3f85751a420d3c4625355b6cb3fbdd2acbe71f13"
@@ -1368,6 +1400,15 @@ apollo-server-testing@^2.25.2:
dependencies:
apollo-server-core "^2.25.2"
+apollo-server-types@^0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-0.6.3.tgz#f7aa25ff7157863264d01a77d7934aa6e13399e8"
+ integrity sha512-aVR7SlSGGY41E1f11YYz5bvwA89uGmkVUtzMiklDhZ7IgRJhysT5Dflt5IuwDxp+NdQkIhVCErUXakopocFLAg==
+ dependencies:
+ apollo-reporting-protobuf "^0.6.2"
+ apollo-server-caching "^0.5.3"
+ apollo-server-env "^3.0.0"
+
apollo-server-types@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-0.9.0.tgz#ccf550b33b07c48c72f104fbe2876232b404848b"
@@ -1552,7 +1593,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.19.0, body-parser@^1.18.3:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
@@ -2559,6 +2600,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
+fast-safe-stringify@^2.0.7:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
+ integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
+
fastq@^1.6.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
@@ -4008,6 +4054,11 @@ loglevel@^1.6.7:
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
+loglevelnext@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/loglevelnext/-/loglevelnext-4.0.1.tgz#4406c6348c243a35272ac75d7d8e4e60ecbcd011"
+ integrity sha512-/tlMUn5wqgzg9msy0PiWc+8fpVXEuYPq49c2RGyw2NAh0hSrgq6j/Z3YPnwWsILMoFJ+ZT6ePHnWUonkjDnq2Q==
+
long@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
@@ -4194,6 +4245,11 @@ named-placeholders@^1.1.2:
dependencies:
lru-cache "^4.1.3"
+nanoid@^3.1.20:
+ version "3.1.32"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.32.tgz#8f96069e6239cc0a9ae8c0d3b41a3b4933a88c0a"
+ integrity sha512-F8mf7R3iT9bvThBoW4tGXhXFHCctyCiUUPrWF8WaTqa3h96d9QybkSeba43XVOOE3oiLfkVDe4bT8MeGmkrTxw==
+
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
diff --git a/community_server/src/Template/Pages/js.ctp b/community_server/src/Template/Pages/js.ctp
index 60868adc4..dd1525dbe 100644
--- a/community_server/src/Template/Pages/js.ctp
+++ b/community_server/src/Template/Pages/js.ctp
@@ -37,5 +37,5 @@
-