Merge remote-tracking branch 'origin/master' into 1011-Visualize_and_refine_the_register_process

This commit is contained in:
Claus-Peter Hübner 2022-01-19 01:36:30 +01:00
commit 1fac3a56c7
159 changed files with 2936 additions and 910 deletions

View File

@ -248,7 +248,7 @@ jobs:
########################################################################## ##########################################################################
- name: Nginx | Build `production` image - name: Nginx | Build `production` image
run: | 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 docker save "gradido/nginx" > /tmp/nginx.tar
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2

View File

@ -199,7 +199,7 @@ jobs:
########################################################################## ##########################################################################
- name: nginx | Build `test` image - name: nginx | Build `test` image
run: | 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 docker save "gradido/nginx:test" > /tmp/nginx.tar
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
@ -470,7 +470,7 @@ jobs:
report_name: Coverage Admin Interface report_name: Coverage Admin Interface
type: lcov type: lcov
result_path: ./coverage/lcov.info result_path: ./coverage/lcov.info
min_coverage: 76 min_coverage: 77
token: ${{ github.token }} token: ${{ github.token }}
############################################################################## ##############################################################################
@ -520,7 +520,7 @@ jobs:
report_name: Coverage Backend report_name: Coverage Backend
type: lcov type: lcov
result_path: ./backend/coverage/lcov.info result_path: ./backend/coverage/lcov.info
min_coverage: 40 min_coverage: 45
token: ${{ github.token }} token: ${{ github.token }}
############################################################################## ##############################################################################

6
.gitignore vendored
View File

@ -5,3 +5,9 @@ nbproject
.metadata .metadata
/.env /.env
package-lock.json 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

View File

@ -67,7 +67,7 @@ We are currently restructuring the service to reduce dependencies and unify busi
### Open the wallet ### 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 ## 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 | | 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. | | 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 ## Useful Links

View File

@ -1,3 +1,4 @@
GRAPHQL_URI=http://localhost:4000/graphql 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 DEBUG_DISABLE_AUTH=false

4
admin/.env.template Normal file
View File

@ -0,0 +1,4 @@
GRAPHQL_URI=$GRAPHQL_URI
WALLET_AUTH_URL=$WALLET_AUTH_URL
WALLET_URL=$WALLET_URL
DEBUG_DISABLE_AUTH=false

View File

@ -33,6 +33,7 @@
"core-js": "^3.6.5", "core-js": "^3.6.5",
"dotenv-webpack": "^7.0.3", "dotenv-webpack": "^7.0.3",
"graphql": "^15.6.1", "graphql": "^15.6.1",
"express": "^4.17.1",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "26.6.3", "jest": "26.6.3",
"moment": "^2.29.1", "moment": "^2.29.1",

View File

@ -1,15 +1,21 @@
// Imports // Imports
const express = require('express') 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 const port = process.env.PORT || 8080
// Express Server // Express Server
const app = express() const app = express()
// eslint-disable-next-line node/no-path-concat // Serve files
app.use(serveStatic(__dirname + '/../dist')) app.use(express.static(path.join(__dirname, '../dist')))
app.listen(port) // Default to index.html
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../dist/index.html'))
})
// eslint-disable-next-line no-console app.listen(port, hostname, () => {
console.log(`http://admin:${port} server started.`) // eslint-disable-next-line no-console
console.log('Listening at http://%s:%s/', hostname, port)
})

View File

@ -19,6 +19,7 @@ const mocks = {
} }
const propsData = { const propsData = {
checked: false,
email: 'bob@baumeister.de', email: 'bob@baumeister.de',
dateLastSend: '', dateLastSend: '',
} }

View File

@ -1,19 +1,20 @@
<template> <template>
<div class="component-confirm-register-mail"> <div class="component-confirm-register-mail">
<div class="shadow p-3 mb-5 bg-white rounded"> <div class="shadow p-3 mb-5 bg-white rounded">
<div class="h5"> <div v-if="checked">{{ $t('unregister_mail.text_true', { date: dateLastSend }) }}</div>
{{ $t('unregister_mail.text', { date: dateLastSend, mail: email }) }} <div v-else>
</div> {{ $t('unregister_mail.text_false', { date: dateLastSend, mail: email }) }}
<!-- Using components --> <!-- Using components -->
<b-input-group :prepend="$t('unregister_mail.info')" class="mt-3"> <b-input-group :prepend="$t('unregister_mail.info')" class="mt-3">
<b-form-input readonly :value="email"></b-form-input> <b-form-input readonly :value="email"></b-form-input>
<b-input-group-append> <b-input-group-append>
<b-button variant="outline-success" class="test-button" @click="sendRegisterMail"> <b-button variant="outline-success" class="test-button" @click="sendRegisterMail">
{{ $t('unregister_mail.button') }} {{ $t('unregister_mail.button') }}
</b-button> </b-button>
</b-input-group-append> </b-input-group-append>
</b-input-group> </b-input-group>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -23,6 +24,9 @@ import { sendActivationEmail } from '../graphql/sendActivationEmail'
export default { export default {
name: 'ConfirmRegisterMail', name: 'ConfirmRegisterMail',
props: { props: {
checked: {
type: Boolean,
},
email: { email: {
type: String, type: String,
}, },

View File

@ -8,7 +8,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
transactionList: { transactionList: {
transactions: [ transactions: [
{ {
type: 'created', type: 'creation',
balance: 100, balance: 100,
decayStart: 0, decayStart: 0,
decayEnd: 0, decayEnd: 0,
@ -27,7 +27,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
}, },
}, },
{ {
type: 'created', type: 'creation',
balance: 200, balance: 200,
decayStart: 0, decayStart: 0,
decayEnd: 0, decayEnd: 0,
@ -58,9 +58,7 @@ const mocks = {
query: apolloQueryMock, query: apolloQueryMock,
}, },
$toasted: { $toasted: {
global: { error: toastedErrorMock,
error: toastedErrorMock,
},
}, },
} }

View File

@ -30,10 +30,10 @@ export default {
}, },
}) })
.then((result) => { .then((result) => {
this.items = result.data.transactionList.transactions this.items = result.data.transactionList.transactions.filter((t) => t.type === 'creation')
}) })
.catch((error) => { .catch((error) => {
this.$toasted.global.error(error.message) this.$toasted.error(error.message)
}) })
}, },
}, },

View File

@ -53,13 +53,17 @@ describe('NavBar', () => {
}) })
describe('logout', () => { describe('logout', () => {
// const assignLocationSpy = jest.fn() const windowLocationMock = jest.fn()
beforeEach(async () => { beforeEach(async () => {
delete window.location
window.location = {
assign: windowLocationMock,
}
await wrapper.findAll('a').at(6).trigger('click') await wrapper.findAll('a').at(6).trigger('click')
}) })
it('redirects to /logout', () => { it('redirects to /logout', () => {
expect(routerPushMock).toBeCalledWith('/logout') expect(windowLocationMock).toBeCalledWith('http://localhost/login')
}) })
it('dispatches logout to store', () => { it('dispatches logout to store', () => {

View File

@ -33,11 +33,12 @@ export default {
name: 'navbar', name: 'navbar',
methods: { methods: {
logout() { logout() {
window.location.assign(CONFIG.WALLET_URL)
// window.location = CONFIG.WALLET_URL
this.$store.dispatch('logout') this.$store.dispatch('logout')
this.$router.push('/logout')
}, },
wallet() { wallet() {
window.location = CONFIG.WALLET_AUTH_URL.replace('$1', this.$store.state.token) window.location = CONFIG.WALLET_AUTH_URL.replace('{token}', this.$store.state.token)
this.$store.dispatch('logout') // logout without redirect this.$store.dispatch('logout') // logout without redirect
}, },
}, },

View File

@ -3,31 +3,287 @@ import UserTable from './UserTable.vue'
const localVue = global.localVue const localVue = global.localVue
const apolloQueryMock = jest.fn()
apolloQueryMock.mockResolvedValue()
describe('UserTable', () => { describe('UserTable', () => {
let wrapper let wrapper
const propsData = { const defaultItemsUser = [
type: 'Type', {
itemsUser: [], email: 'bibi@bloxberg.de',
fieldsTable: [], firstName: 'Bibi',
creation: [], lastName: 'Bloxberg',
creation: [1000, 1000, 1000],
},
{
email: 'bibi@bloxberg.de',
firstName: 'Bibi',
lastName: 'Bloxberg',
creation: [1000, 1000, 1000],
},
{
email: 'bibi@bloxberg.de',
firstName: 'Bibi',
lastName: 'Bloxberg',
creation: [1000, 1000, 1000],
},
]
const confirmationItemsUser = [
{
email: 'bibi@bloxberg.de',
firstName: 'Bibi',
lastName: 'Bloxberg',
amount: 10,
memo: 'Test 1',
date: '11-09-2001',
moderator: 1,
},
{
email: 'bibi@bloxberg.de',
firstName: 'Bibi',
lastName: 'Bloxberg',
amount: 10,
memo: 'Test 2',
date: '21-09-2001',
moderator: 1,
},
{
email: 'bibi@bloxberg.de',
firstName: 'Bibi',
lastName: 'Bloxberg',
amount: 10,
memo: 'Test 3',
date: '30-09-2001',
moderator: 1,
},
]
const propsDataPageUserSearch = {
type: 'PageUserSearch',
itemsUser: defaultItemsUser,
fieldsTable: [
'email',
'firstName',
'lastName',
'creation',
'show_details',
'confirm_mail',
'transactions_list',
],
}
const propsDataUserListSearch = {
type: 'UserListSearch',
itemsUser: defaultItemsUser,
fieldsTable: ['bookmark', 'email', 'firstName', 'lastName', 'creation'],
creation: [1000, 1000, 1000],
}
const propsDataUserListMassCreation = {
type: 'UserListMassCreation',
itemsUser: defaultItemsUser,
fieldsTable: ['email', 'firstName', 'lastName', 'creation', 'bookmark'],
creation: [1000, 1000, 1000],
}
const propsDataPageCreationConfirm = {
type: 'PageCreationConfirm',
itemsUser: confirmationItemsUser,
fieldsTable: [
'bookmark',
'email',
'firstName',
'lastName',
'amount',
'memo',
'date',
'moderator',
'edit_creation',
'confirm',
],
} }
const mocks = { const mocks = {
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$moment: jest.fn(() => {
return {
format: jest.fn((m) => m),
subtract: jest.fn(() => {
return {
format: jest.fn((m) => m),
}
}),
}
}),
$apollo: {
query: apolloQueryMock,
},
$store: {
commit: jest.fn(),
},
} }
const Wrapper = () => { const Wrapper = (propsData) => {
return mount(UserTable, { localVue, propsData, mocks }) return mount(UserTable, { localVue, propsData, mocks })
} }
describe('mount', () => { describe('mount', () => {
beforeEach(() => { describe('type PageUserSearch', () => {
wrapper = Wrapper() beforeEach(() => {
wrapper = Wrapper(propsDataPageUserSearch)
})
it('has a DIV element with the class.component-user-table', () => {
expect(wrapper.find('.component-user-table').exists()).toBeTruthy()
})
it('has a DIV element with the id overlay that is not displayed', () => {
expect(wrapper.find('#overlay').exists()).toBeTruthy()
expect(wrapper.find('#overlay').attributes('style')).toBe('display: none;')
})
describe('table', () => {
it('has a table', () => {
expect(wrapper.find('table').exists()).toBeTruthy()
})
describe('header definition', () => {
it('has 4 column', () => {
expect(wrapper.findAll('th').length).toBe(7)
})
it('has Email as first column', () => {
expect(wrapper.find('th[aria-colindex="1"] div').text()).toBe('Email')
})
it('has First Name as second column', () => {
expect(wrapper.find('th[aria-colindex="2"] div').text()).toBe('First Name')
})
it('has Last Name as third column', () => {
expect(wrapper.find('th[aria-colindex="3"] div').text()).toBe('Last Name')
})
it('has Creation as fourth column', () => {
expect(wrapper.find('th[aria-colindex="4"] div').text()).toBe('Creation')
})
it('has Creation as fifth column', () => {
expect(wrapper.find('th[aria-colindex="5"] div').text()).toBe('Show Details')
})
it('has Creation as sixth column', () => {
expect(wrapper.find('th[aria-colindex="6"] div').text()).toBe('Confirm Mail')
})
it('has Creation as seventh column', () => {
expect(wrapper.find('th[aria-colindex="7"] div').text()).toBe('Transactions List')
})
})
describe('content', () => {
it('has 3 rows', () => {
expect(wrapper.findAll('tbody tr').length).toBe(3)
})
it('has 7 columns', () => {
expect(wrapper.findAll('tr:nth-child(1) > td').length).toBe(7)
})
it('click button on fifth column', () => {
wrapper.find('tbody tr td[aria-colindex="5"] button').trigger('click')
})
})
})
// it('expect(wrapper.html()).', () => {
// // eslint-disable-next-line no-console
// console.log(wrapper.html())
// })
}) })
it('has a DIV element with the class.component-user-table', () => { describe('type UserListSearch', () => {
expect(wrapper.find('.component-user-table').exists()).toBeTruthy() beforeEach(() => {
wrapper = Wrapper(propsDataUserListSearch)
})
it('has a DIV element with the class.component-user-table', () => {
expect(wrapper.find('.component-user-table').exists()).toBeTruthy()
})
// it('expect(wrapper.html()).', () => {
// // eslint-disable-next-line no-console
// console.log(wrapper.html())
// })
}) })
describe('type UserListMassCreation', () => {
beforeEach(() => {
wrapper = Wrapper(propsDataUserListMassCreation)
})
it('has a DIV element with the class.component-user-table', () => {
expect(wrapper.find('.component-user-table').exists()).toBeTruthy()
})
// it('expect(wrapper.html()).', () => {
// // eslint-disable-next-line no-console
// console.log(wrapper.html())
// })
})
describe('type PageCreationConfirm', () => {
beforeEach(() => {
wrapper = Wrapper(propsDataPageCreationConfirm)
})
it('has a DIV element with the class.component-user-table', () => {
expect(wrapper.find('.component-user-table').exists()).toBeTruthy()
})
// it('expect(wrapper.html()).', () => {
// // eslint-disable-next-line no-console
// console.log(wrapper.html())
// })
})
/**
<user-table
v-if="itemsList.length > 0"
type="UserListSearch"
:itemsUser="itemsList"
:fieldsTable="Searchfields"
:criteria="criteria"
:creation="creation"
@update-item="updateItem"
/>
<user-table
v-show="itemsMassCreation.length > 0"
class="shadow p-3 mb-5 bg-white rounded"
type="UserListMassCreation"
:itemsUser="itemsMassCreation"
:fieldsTable="fields"
:criteria="null"
:creation="creation"
@update-item="updateItem"
/>
<user-table
class="mt-4"
type="PageCreationConfirm"
:itemsUser="confirmResult"
:fieldsTable="fields"
@remove-confirm-result="removeConfirmResult"
/>
<user-table
type="PageUserSearch"
:itemsUser="searchResult"
:fieldsTable="fields"
:criteria="criteria"
/>
*/
}) })
}) })

View File

@ -74,6 +74,7 @@
<template #row-details="row"> <template #row-details="row">
<row-details <row-details
v-if="type !== 'UserListSearch' && type !== 'UserListMassCreation'"
:row="row" :row="row"
:type="type" :type="type"
:slotName="slotName" :slotName="slotName"
@ -107,6 +108,7 @@
</template> </template>
<template #show-register-mail> <template #show-register-mail>
<confirm-register-mail-formular <confirm-register-mail-formular
:checked="row.item.emailChecked"
:email="row.item.email" :email="row.item.email"
:dateLastSend="$moment().subtract(1, 'month').format('dddd, DD.MMMM.YYYY HH:mm')," :dateLastSend="$moment().subtract(1, 'month').format('dddd, DD.MMMM.YYYY HH:mm'),"
/> />
@ -232,6 +234,9 @@ export default {
row.toggleDetails() row.toggleDetails()
this.slotIndex = index this.slotIndex = index
this.openRow = row this.openRow = row
if (this.type === 'PageCreationConfirm') {
this.creationUserData = row.item
}
} }
} else { } else {
row.toggleDetails() row.toggleDetails()

View File

@ -19,7 +19,8 @@ const environment = {
const endpoints = { const endpoints = {
GRAPHQL_URI: process.env.GRAPHQL_URI || 'http://localhost:4000/graphql', GRAPHQL_URI: process.env.GRAPHQL_URI || 'http://localhost:4000/graphql',
WALLET_AUTH_URL: process.env.WALLET_AUTH_URL || 'http://localhost/vue/authenticate?token=$1', WALLET_AUTH_URL: process.env.WALLET_AUTH_URL || 'http://localhost/authenticate?token={token}',
WALLET_URL: process.env.WALLET_URL || 'http://localhost/login',
} }
const debug = { const debug = {

View File

@ -1,14 +1,22 @@
import gql from 'graphql-tag' import gql from 'graphql-tag'
export const searchUsers = gql` export const searchUsers = gql`
query ($searchText: String!) { query ($searchText: String!, $currentPage: Int, $pageSize: Int, $notActivated: Boolean) {
searchUsers(searchText: $searchText) { searchUsers(
userId searchText: $searchText
firstName currentPage: $currentPage
lastName pageSize: $pageSize
email notActivated: $notActivated
creation ) {
emailChecked userCount
userList {
userId
firstName
lastName
email
creation
emailChecked
}
} }
} }
` `

View File

@ -7,6 +7,7 @@ export const verifyLogin = gql`
lastName lastName
isAdmin isAdmin
id id
language
} }
} }
` `

View File

@ -1,8 +1,9 @@
{ {
"all_emails": "Alle Nutzer",
"bookmark": "bookmark", "bookmark": "bookmark",
"confirmed": "bestätigt", "confirmed": "bestätigt",
"creation_form": { "creation_form": {
"creation_for": "Schöpfung für ", "creation_for": "Schöpfung für",
"enter_text": "Text eintragen", "enter_text": "Text eintragen",
"form": "Schöpfungsformular", "form": "Schöpfungsformular",
"min_characters": "Mindestens 10 Zeichen eingeben", "min_characters": "Mindestens 10 Zeichen eingeben",
@ -53,13 +54,14 @@
"transactionlist": { "transactionlist": {
"title": "Alle geschöpften Transaktionen für den Nutzer" "title": "Alle geschöpften Transaktionen für den Nutzer"
}, },
"unregistered_emails": "Unregistrierte E-Mails", "unregistered_emails": "Nur unregistrierte Nutzer",
"unregister_mail": { "unregister_mail": {
"button": "Registrierungs-Email bestätigen, jetzt senden", "button": "Registrierungs-Email bestätigen, jetzt senden",
"error": "Fehler beim Senden des Bestätigungs-Links an den Benutzer: {message}", "error": "Fehler beim Senden des Bestätigungs-Links an den Benutzer: {message}",
"info": "Email bestätigen, wiederholt senden an:", "info": "Email bestätigen, wiederholt senden an:",
"success": "Erfolgreiches Senden des Bestätigungs-Links an die E-Mail des Nutzers! ({email})", "success": "Erfolgreiches Senden des Bestätigungs-Links an die E-Mail des Nutzers! ({email})",
"text": " Die letzte Email wurde am <b>{date} Uhr</b> an das Mitglied ({mail}) gesendet." "text_false": " Die letzte Email wurde am {date} Uhr an das Mitglied ({mail}) gesendet.",
"text_true": " Die Email wurde am {date} Uhr bestätigt."
}, },
"user_search": "Nutzer-Suche" "user_search": "Nutzer-Suche"
} }

View File

@ -1,8 +1,9 @@
{ {
"all_emails": "All users",
"bookmark": "Remember", "bookmark": "Remember",
"confirmed": "confirmed", "confirmed": "confirmed",
"creation_form": { "creation_form": {
"creation_for": "Creation for ", "creation_for": "Creation for",
"enter_text": "Enter text", "enter_text": "Enter text",
"form": "Creation form", "form": "Creation form",
"min_characters": "Enter at least 10 characters", "min_characters": "Enter at least 10 characters",
@ -53,13 +54,14 @@
"transactionlist": { "transactionlist": {
"title": "All creation-transactions for the user" "title": "All creation-transactions for the user"
}, },
"unregistered_emails": "Unregistered e-mails", "unregistered_emails": "Only unregistered users",
"unregister_mail": { "unregister_mail": {
"button": "Confirm registration email, send now", "button": "Confirm registration email, send now",
"error": "Error sending the confirmation link to the user: {message}", "error": "Error sending the confirmation link to the user: {message}",
"info": "Confirm email, send repeatedly to:", "info": "Confirm email, send repeatedly to:",
"success": "Successfully send the confirmation link to the user's email! ({email})", "success": "Successfully send the confirmation link to the user's email! ({email})",
"text": " The last email was sent to the member ({mail}) on <b>{date} clock</b>." "text_false": "The last email was sent to the member ({mail}) on {date} clock.",
"text_true": "The email was confirmed on {date} clock."
}, },
"user_search": "User search" "user_search": "User search"
} }

View File

@ -42,7 +42,7 @@ Vue.use(Toasted, {
}, },
}) })
addNavigationGuards(router, store, apolloProvider.defaultClient) addNavigationGuards(router, store, apolloProvider.defaultClient, i18n)
new Vue({ new Vue({
moment, moment,

View File

@ -6,22 +6,25 @@ const localVue = global.localVue
const apolloQueryMock = jest.fn().mockResolvedValue({ const apolloQueryMock = jest.fn().mockResolvedValue({
data: { data: {
searchUsers: [ searchUsers: {
{ userCount: 2,
id: 1, userList: [
firstName: 'Bibi', {
lastName: 'Bloxberg', userId: 1,
email: 'bibi@bloxberg.de', firstName: 'Bibi',
creation: [200, 400, 600], lastName: 'Bloxberg',
}, email: 'bibi@bloxberg.de',
{ creation: [200, 400, 600],
id: 2, },
firstName: 'Benjamin', {
lastName: 'Blümchen', userId: 2,
email: 'benjamin@bluemchen.de', firstName: 'Benjamin',
creation: [800, 600, 400], lastName: 'Blümchen',
}, email: 'benjamin@bluemchen.de',
], creation: [800, 600, 400],
},
],
},
}, },
}) })
@ -35,6 +38,16 @@ const mocks = {
$toasted: { $toasted: {
error: toastErrorMock, error: toastErrorMock,
}, },
$moment: jest.fn(() => {
return {
format: jest.fn((m) => m),
subtract: jest.fn(() => {
return {
format: jest.fn((m) => m),
}
}),
}
}),
} }
describe('Creation', () => { describe('Creation', () => {
@ -61,7 +74,7 @@ describe('Creation', () => {
it('sets the data of itemsList', () => { it('sets the data of itemsList', () => {
expect(wrapper.vm.itemsList).toEqual([ expect(wrapper.vm.itemsList).toEqual([
{ {
id: 1, userId: 1,
firstName: 'Bibi', firstName: 'Bibi',
lastName: 'Bloxberg', lastName: 'Bloxberg',
email: 'bibi@bloxberg.de', email: 'bibi@bloxberg.de',
@ -69,7 +82,7 @@ describe('Creation', () => {
showDetails: false, showDetails: false,
}, },
{ {
id: 2, userId: 2,
firstName: 'Benjamin', firstName: 'Benjamin',
lastName: 'Blümchen', lastName: 'Blümchen',
email: 'benjamin@bluemchen.de', email: 'benjamin@bluemchen.de',
@ -90,7 +103,7 @@ describe('Creation', () => {
wrapper.findComponent({ name: 'UserTable' }).vm.$emit( wrapper.findComponent({ name: 'UserTable' }).vm.$emit(
'update-item', 'update-item',
{ {
id: 2, userId: 2,
firstName: 'Benjamin', firstName: 'Benjamin',
lastName: 'Blümchen', lastName: 'Blümchen',
email: 'benjamin@bluemchen.de', email: 'benjamin@bluemchen.de',
@ -104,7 +117,7 @@ describe('Creation', () => {
it('removes the pushed item from itemsList', () => { it('removes the pushed item from itemsList', () => {
expect(wrapper.vm.itemsList).toEqual([ expect(wrapper.vm.itemsList).toEqual([
{ {
id: 1, userId: 1,
firstName: 'Bibi', firstName: 'Bibi',
lastName: 'Bloxberg', lastName: 'Bloxberg',
email: 'bibi@bloxberg.de', email: 'bibi@bloxberg.de',
@ -117,7 +130,7 @@ describe('Creation', () => {
it('adds the pushed item to itemsMassCreation', () => { it('adds the pushed item to itemsMassCreation', () => {
expect(wrapper.vm.itemsMassCreation).toEqual([ expect(wrapper.vm.itemsMassCreation).toEqual([
{ {
id: 2, userId: 2,
firstName: 'Benjamin', firstName: 'Benjamin',
lastName: 'Blümchen', lastName: 'Blümchen',
email: 'benjamin@bluemchen.de', email: 'benjamin@bluemchen.de',
@ -132,7 +145,7 @@ describe('Creation', () => {
wrapper.findComponent({ name: 'UserTable' }).vm.$emit( wrapper.findComponent({ name: 'UserTable' }).vm.$emit(
'update-item', 'update-item',
{ {
id: 2, userId: 2,
firstName: 'Benjamin', firstName: 'Benjamin',
lastName: 'Blümchen', lastName: 'Blümchen',
email: 'benjamin@bluemchen.de', email: 'benjamin@bluemchen.de',
@ -150,7 +163,7 @@ describe('Creation', () => {
it('adds the item to itemsList', () => { it('adds the item to itemsList', () => {
expect(wrapper.vm.itemsList).toEqual([ expect(wrapper.vm.itemsList).toEqual([
{ {
id: 1, userId: 1,
firstName: 'Bibi', firstName: 'Bibi',
lastName: 'Bloxberg', lastName: 'Bloxberg',
email: 'bibi@bloxberg.de', email: 'bibi@bloxberg.de',
@ -158,7 +171,7 @@ describe('Creation', () => {
showDetails: false, showDetails: false,
}, },
{ {
id: 2, userId: 2,
firstName: 'Benjamin', firstName: 'Benjamin',
lastName: 'Blümchen', lastName: 'Blümchen',
email: 'benjamin@bluemchen.de', email: 'benjamin@bluemchen.de',
@ -196,7 +209,7 @@ describe('Creation', () => {
await wrapper.findComponent({ name: 'UserTable' }).vm.$emit( await wrapper.findComponent({ name: 'UserTable' }).vm.$emit(
'update-item', 'update-item',
{ {
id: 2, userId: 2,
firstName: 'Benjamin', firstName: 'Benjamin',
lastName: 'Blümchen', lastName: 'Blümchen',
email: 'benjamin@bluemchen.de', email: 'benjamin@bluemchen.de',
@ -217,6 +230,22 @@ describe('Creation', () => {
}) })
}) })
describe('watchers', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('calls API when criteria changes', async () => {
await wrapper.setData({ criteria: 'XX' })
expect(apolloQueryMock).toBeCalled()
})
it('calls API when currentPage changes', async () => {
await wrapper.setData({ currentPage: 2 })
expect(apolloQueryMock).toBeCalled()
})
})
describe('apollo returns error', () => { describe('apollo returns error', () => {
beforeEach(() => { beforeEach(() => {
apolloQueryMock.mockRejectedValue({ apolloQueryMock.mockRejectedValue({

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="creation"> <div class="creation">
<b-row> <b-row>
<b-col cols="12" lg="5"> <b-col cols="12" lg="6">
<label>Usersuche</label> <label>Usersuche</label>
<b-input <b-input
type="text" type="text"
@ -18,8 +18,15 @@
:creation="creation" :creation="creation"
@update-item="updateItem" @update-item="updateItem"
/> />
<b-pagination
pills
v-model="currentPage"
per-page="perPage"
:total-rows="rows"
align="center"
></b-pagination>
</b-col> </b-col>
<b-col cols="12" lg="7" class="shadow p-3 mb-5 rounded bg-info"> <b-col cols="12" lg="6" class="shadow p-3 mb-5 rounded bg-info">
<user-table <user-table
v-show="itemsMassCreation.length > 0" v-show="itemsMassCreation.length > 0"
class="shadow p-3 mb-5 bg-white rounded" class="shadow p-3 mb-5 bg-white rounded"
@ -62,14 +69,38 @@ export default {
{ key: 'bookmark', label: 'bookmark' }, { key: 'bookmark', label: 'bookmark' },
{ key: 'firstName', label: this.$t('firstname') }, { key: 'firstName', label: this.$t('firstname') },
{ key: 'lastName', label: this.$t('lastname') }, { key: 'lastName', label: this.$t('lastname') },
{ key: 'creation', label: this.$t('open_creations') }, {
key: 'creation',
// label: this.$t('open_creation') + 'Jan | Feb | März',
label:
this.$moment().subtract(2, 'month').format('MMM') +
' | ' +
this.$moment().subtract(1, 'month').format('MMM') +
' | ' +
this.$moment().format('MMM'),
formatter: (value, key, item) => {
return String(value[0]) + ` | ` + String(value[1]) + ` | ` + String(value[2])
},
},
{ key: 'email', label: this.$t('e_mail') }, { key: 'email', label: this.$t('e_mail') },
], ],
fields: [ fields: [
{ key: 'email', label: this.$t('e_mail') }, { key: 'email', label: this.$t('e_mail') },
{ key: 'firstName', label: this.$t('firstname') }, { key: 'firstName', label: this.$t('firstname') },
{ key: 'lastName', label: this.$t('lastname') }, { key: 'lastName', label: this.$t('lastname') },
{ key: 'creation', label: this.$t('open_creations') }, {
key: 'creation',
// label: this.$t('open_creation') + 'Jan | Feb | März',
label:
this.$moment().subtract(2, 'month').format('MMM') +
' | ' +
this.$moment().subtract(1, 'month').format('MMM') +
' | ' +
this.$moment().format('MMM'),
formatter: (value, key, item) => {
return String(value[0]) + ` | ` + String(value[1]) + ` | ` + String(value[2])
},
},
{ key: 'bookmark', label: this.$t('remove') }, { key: 'bookmark', label: this.$t('remove') },
], ],
itemsList: [], itemsList: [],
@ -77,6 +108,9 @@ export default {
radioSelectedMass: '', radioSelectedMass: '',
criteria: '', criteria: '',
creation: [null, null, null], creation: [null, null, null],
rows: 0,
currentPage: 1,
perPage: 25,
} }
}, },
async created() { async created() {
@ -89,10 +123,13 @@ export default {
query: searchUsers, query: searchUsers,
variables: { variables: {
searchText: this.criteria, searchText: this.criteria,
currentPage: this.currentPage,
pageSize: this.perPage,
}, },
}) })
.then((result) => { .then((result) => {
this.itemsList = result.data.searchUsers.map((user) => { this.rows = result.data.searchUsers.userCount
this.itemsList = result.data.searchUsers.userList.map((user) => {
return { return {
...user, ...user,
showDetails: false, showDetails: false,
@ -109,16 +146,16 @@ export default {
switch (event) { switch (event) {
case 'push': case 'push':
findArr = this.itemsList.find((arr) => arr.id === e.id) findArr = this.itemsList.find((item) => e.userId === item.userId)
index = this.itemsList.indexOf(findArr) index = this.itemsList.indexOf(findArr)
this.itemsList.splice(index, 1) this.itemsList.splice(index, 1)
this.itemsMassCreation.push(e) this.itemsMassCreation.push(findArr)
break break
case 'remove': case 'remove':
findArr = this.itemsMassCreation.find((arr) => arr.id === e.id) findArr = this.itemsMassCreation.find((item) => e.userId === item.userId)
index = this.itemsMassCreation.indexOf(findArr) index = this.itemsMassCreation.indexOf(findArr)
this.itemsMassCreation.splice(index, 1) this.itemsMassCreation.splice(index, 1)
this.itemsList.push(e) this.itemsList.push(findArr)
break break
default: default:
throw new Error(event) throw new Error(event)
@ -129,5 +166,13 @@ export default {
this.itemsMassCreation = [] this.itemsMassCreation = []
}, },
}, },
watch: {
currentPage() {
this.getUsers()
},
criteria() {
this.getUsers()
},
},
} }
</script> </script>

View File

@ -5,15 +5,18 @@ const localVue = global.localVue
const apolloQueryMock = jest.fn().mockResolvedValue({ const apolloQueryMock = jest.fn().mockResolvedValue({
data: { data: {
searchUsers: [ searchUsers: {
{ userCount: 1,
firstName: 'Bibi', userList: [
lastName: 'Bloxberg', {
email: 'bibi@bloxberg.de', firstName: 'Bibi',
creation: [200, 400, 600], lastName: 'Bloxberg',
emailChecked: false, email: 'bibi@bloxberg.de',
}, creation: [200, 400, 600],
], emailChecked: false,
},
],
},
}, },
}) })
@ -61,7 +64,7 @@ describe('UserSearch', () => {
}) })
it('filters the users by unconfirmed emails', () => { it('filters the users by unconfirmed emails', () => {
expect(wrapper.vm.searchResult).toHaveLength(0) expect(wrapper.vm.searchResult).toHaveLength(1)
}) })
}) })

View File

@ -3,7 +3,7 @@
<div style="text-align: right"> <div style="text-align: right">
<b-button block variant="danger" @click="unconfirmedRegisterMails"> <b-button block variant="danger" @click="unconfirmedRegisterMails">
<b-icon icon="envelope" variant="light"></b-icon> <b-icon icon="envelope" variant="light"></b-icon>
{{ $t('unregistered_emails') }} {{ filterCheckedEmails ? $t('all_emails') : $t('unregistered_emails') }}
</b-button> </b-button>
</div> </div>
<label>{{ $t('user_search') }}</label> <label>{{ $t('user_search') }}</label>
@ -21,6 +21,14 @@
:fieldsTable="fields" :fieldsTable="fields"
:criteria="criteria" :criteria="criteria"
/> />
<b-pagination
pills
size="lg"
v-model="currentPage"
per-page="perPage"
:total-rows="rows"
align="center"
></b-pagination>
<div></div> <div></div>
</div> </div>
</template> </template>
@ -42,27 +50,13 @@ export default {
{ key: 'lastName', label: this.$t('lastname') }, { key: 'lastName', label: this.$t('lastname') },
{ {
key: 'creation', 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) => { formatter: (value, key, item) => {
return ( return value.join(' | ')
`
<div>` +
this.$moment().subtract(2, 'month').format('MMMM') +
` - ` +
String(value[0]) +
` GDD</div>
<div>` +
this.$moment().subtract(1, 'month').format('MMMM') +
` - ` +
String(value[1]) +
` GDD</div>
<div>` +
this.$moment().format('MMMM') +
` - ` +
String(value[2]) +
` GDD</div>
`
)
}, },
}, },
{ key: 'show_details', label: this.$t('details') }, { key: 'show_details', label: this.$t('details') },
@ -81,14 +75,16 @@ export default {
beforeLastMonth: { beforeLastMonth: {
short: this.$moment().subtract(2, 'month').format('MMMM'), short: this.$moment().subtract(2, 'month').format('MMMM'),
}, },
filterCheckedEmails: false,
rows: 0,
currentPage: 1,
perPage: 25,
} }
}, },
methods: { methods: {
unconfirmedRegisterMails() { unconfirmedRegisterMails() {
this.searchResult = this.searchResult.filter((user) => { this.filterCheckedEmails = !this.filterCheckedEmails
return user.emailChecked this.getUsers()
})
}, },
getUsers() { getUsers() {
this.$apollo this.$apollo
@ -96,16 +92,25 @@ export default {
query: searchUsers, query: searchUsers,
variables: { variables: {
searchText: this.criteria, searchText: this.criteria,
currentPage: this.currentPage,
pageSize: this.perPage,
notActivated: this.filterCheckedEmails,
}, },
}) })
.then((result) => { .then((result) => {
this.searchResult = result.data.searchUsers this.rows = result.data.searchUsers.userCount
this.searchResult = result.data.searchUsers.userList
}) })
.catch((error) => { .catch((error) => {
this.$toasted.error(error.message) this.$toasted.error(error.message)
}) })
}, },
}, },
watch: {
currentPage() {
this.getUsers()
},
},
created() { created() {
this.getUsers() this.getUsers()
}, },

View File

@ -2,7 +2,6 @@ import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
import VueApollo from 'vue-apollo' import VueApollo from 'vue-apollo'
import CONFIG from '../config' import CONFIG from '../config'
import store from '../store/store' import store from '../store/store'
import router from '../router/router'
import i18n from '../i18n' import i18n from '../i18n'
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI }) 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') { if (response.errors && response.errors[0].message === '403.13 - Client certificate revoked') {
response.errors[0].message = i18n.t('error.session-expired') response.errors[0].message = i18n.t('error.session-expired')
store.dispatch('logout', null) store.dispatch('logout', null)
if (router.currentRoute.path !== '/logout') router.push('/logout') window.location.assign(CONFIG.WALLET_URL)
return response return response
} }
const newToken = operation.getContext().response.headers.get('token') const newToken = operation.getContext().response.headers.get('token')

View File

@ -4,12 +4,10 @@ import CONFIG from '../config'
import VueApollo from 'vue-apollo' import VueApollo from 'vue-apollo'
import store from '../store/store' import store from '../store/store'
import router from '../router/router'
import i18n from '../i18n' import i18n from '../i18n'
jest.mock('vue-apollo') jest.mock('vue-apollo')
jest.mock('../store/store') jest.mock('../store/store')
jest.mock('../router/router')
jest.mock('../i18n') jest.mock('../i18n')
jest.mock('apollo-boost', () => { jest.mock('apollo-boost', () => {
@ -59,13 +57,11 @@ describe('apolloProvider', () => {
errors: [{ message: '403.13 - Client certificate revoked' }], errors: [{ message: '403.13 - Client certificate revoked' }],
} }
// mock router const windowLocationMock = jest.fn()
const routerPushMock = jest.fn() delete window.location
router.push = routerPushMock window.location = {
router.currentRoute = { assign: windowLocationMock,
path: '/overview',
} }
// mock context // mock context
const setContextMock = jest.fn() const setContextMock = jest.fn()
const getContextMock = jest.fn(() => { const getContextMock = jest.fn(() => {
@ -128,21 +124,8 @@ describe('apolloProvider', () => {
expect(storeDispatchMock).toBeCalledWith('logout', null) expect(storeDispatchMock).toBeCalledWith('logout', null)
}) })
describe('current route is not logout', () => { it('redirects to logout', () => {
it('redirects to logout', () => { expect(windowLocationMock).toBeCalledWith('http://localhost/login')
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()
})
}) })
}) })

View File

@ -1,7 +1,7 @@
import { verifyLogin } from '../graphql/verifyLogin' import { verifyLogin } from '../graphql/verifyLogin'
import CONFIG from '../config' import CONFIG from '../config'
const addNavigationGuards = (router, store, apollo) => { const addNavigationGuards = (router, store, apollo, i18n) => {
// store token on `authenticate` // store token on `authenticate`
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
if (to.path === '/authenticate' && to.query && to.query.token) { if (to.path === '/authenticate' && to.query && to.query.token) {
@ -14,6 +14,7 @@ const addNavigationGuards = (router, store, apollo) => {
.then((result) => { .then((result) => {
const moderator = result.data.verifyLogin const moderator = result.data.verifyLogin
if (moderator.isAdmin) { if (moderator.isAdmin) {
i18n.locale = moderator.language
store.commit('moderator', moderator) store.commit('moderator', moderator)
next({ path: '/' }) next({ path: '/' })
} else { } else {

View File

@ -6,9 +6,11 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
data: { data: {
verifyLogin: { verifyLogin: {
isAdmin: true, isAdmin: true,
language: 'de',
}, },
}, },
}) })
const i18nLocaleMock = jest.fn()
const store = { const store = {
commit: storeCommitMock, commit: storeCommitMock,
@ -21,7 +23,11 @@ const apollo = {
query: apolloQueryMock, query: apolloQueryMock,
} }
addNavigationGuards(router, store, apollo) const i18n = {
locale: i18nLocaleMock,
}
addNavigationGuards(router, store, apollo, i18n)
describe('navigation guards', () => { describe('navigation guards', () => {
beforeEach(() => { beforeEach(() => {
@ -33,19 +39,23 @@ describe('navigation guards', () => {
const next = jest.fn() const next = jest.fn()
describe('with valid token and as admin', () => { describe('with valid token and as admin', () => {
beforeEach(() => { beforeEach(async () => {
navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next) 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') expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
}) })
it('commits the moderator to the store', () => { it.skip('sets the locale', () => {
expect(storeCommitMock).toBeCalledWith('moderator', { isAdmin: true }) 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: '/' }) expect(next).toBeCalledWith({ path: '/' })
}) })
}) })

View File

@ -8,6 +8,7 @@ DB_PORT=3306
DB_USER=root DB_USER=root
DB_PASSWORD= DB_PASSWORD=
DB_DATABASE=gradido_community DB_DATABASE=gradido_community
TYPEORM_LOGGING_RELATIVE_PATH=typeorm.backend.log
#EMAIL=true #EMAIL=true
#EMAIL_USERNAME= #EMAIL_USERNAME=
@ -19,7 +20,8 @@ DB_DATABASE=gradido_community
#RESEND_TIME= #RESEND_TIME=
RESEND_TIME=10 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_USER=
#KLICKTIPP_PASSWORD= #KLICKTIPP_PASSWORD=

35
backend/.env.template Normal file
View File

@ -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

View File

@ -1,15 +1,18 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = { module.exports = async () => {
verbose: true, process.env.TZ = 'UTC'
preset: 'ts-jest', return {
collectCoverage: true, verbose: true,
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'], preset: 'ts-jest',
moduleNameMapper: { collectCoverage: true,
'@entity/(.*)': '<rootDir>/../database/build/entity/$1', collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'],
// This is hack to fix a problem with the library `ts-mysql-migrate` which does differentiate between its ts/js state moduleNameMapper: {
'@dbTools/(.*)': '@entity/(.*)': '<rootDir>/../database/build/entity/$1',
process.env.NODE_ENV === 'development' // This is hack to fix a problem with the library `ts-mysql-migrate` which does differentiate between its ts/js state
? '<rootDir>/../database/src/$1' '@dbTools/(.*)':
: '<rootDir>/../database/build/src/$1', process.env.NODE_ENV === 'development'
}, ? '<rootDir>/../database/src/$1'
: '<rootDir>/../database/build/src/$1',
},
}
} }

View File

@ -18,10 +18,10 @@
}, },
"dependencies": { "dependencies": {
"@types/jest": "^27.0.2", "@types/jest": "^27.0.2",
"apollo-log": "^1.1.0",
"apollo-server-express": "^2.25.2", "apollo-server-express": "^2.25.2",
"apollo-server-testing": "^2.25.2", "apollo-server-testing": "^2.25.2",
"axios": "^0.21.1", "axios": "^0.21.1",
"body-parser": "^1.19.0",
"class-validator": "^0.13.1", "class-validator": "^0.13.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",

View File

@ -5,7 +5,7 @@ import CONFIG from '../config'
const klicktippConnector = new KlicktippConnector() const klicktippConnector = new KlicktippConnector()
export const signIn = async ( export const klicktippSignIn = async (
email: string, email: string,
language: string, language: string,
firstName?: string, firstName?: string,

View File

@ -18,6 +18,7 @@ const database = {
DB_USER: process.env.DB_USER || 'root', DB_USER: process.env.DB_USER || 'root',
DB_PASSWORD: process.env.DB_PASSWORD || '', DB_PASSWORD: process.env.DB_PASSWORD || '',
DB_DATABASE: process.env.DB_DATABASE || 'gradido_community', DB_DATABASE: process.env.DB_DATABASE || 'gradido_community',
TYPEORM_LOGGING_RELATIVE_PATH: process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.backend.log',
} }
const klicktipp = { const klicktipp = {
@ -31,8 +32,8 @@ const klicktipp = {
const community = { const community = {
COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung', COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung',
COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/vue/', COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/',
COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/vue/register', COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/register',
COMMUNITY_DESCRIPTION: COMMUNITY_DESCRIPTION:
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.', 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_URL: process.env.EMAIL_SMTP_URL || 'gmail.com',
EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '587', EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '587',
EMAIL_LINK_VERIFICATION: EMAIL_LINK_VERIFICATION:
process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/vue/checkEmail/$1', process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/checkEmail/{code}',
EMAIL_LINK_SETPASSWORD: process.env.EMAIL_LINK_SETPASSWORD || 'http://localhost/vue/reset/$1', EMAIL_LINK_SETPASSWORD: process.env.EMAIL_LINK_SETPASSWORD || 'http://localhost/reset/{code}',
RESEND_TIME: isNaN(resendTime) ? 10 : resendTime, RESEND_TIME: isNaN(resendTime) ? 10 : resendTime,
} }
const webhook = { const webhook = {
// Elopage
WEBHOOK_ELOPAGE_SECRET: process.env.WEBHOOK_ELOPAGE_SECRET || 'secret', WEBHOOK_ELOPAGE_SECRET: process.env.WEBHOOK_ELOPAGE_SECRET || 'secret',
} }

View File

@ -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
}

View File

@ -1,4 +1,4 @@
import { ObjectType, Field } from 'type-graphql' import { ObjectType, Field, Int } from 'type-graphql'
@ObjectType() @ObjectType()
export class UserAdmin { export class UserAdmin {
@ -20,3 +20,12 @@ export class UserAdmin {
@Field(() => Boolean) @Field(() => Boolean)
emailChecked: boolean emailChecked: boolean
} }
@ObjectType()
export class SearchUsersResult {
@Field(() => Int)
userCount: number
@Field(() => [UserAdmin])
userList: UserAdmin[]
}

View File

@ -1,6 +1,9 @@
/* 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 { Resolver, Query, Arg, Args, Authorized, Mutation, Ctx } from 'type-graphql'
import { getCustomRepository, Raw } from 'typeorm' import { getCustomRepository, Raw } from 'typeorm'
import { UserAdmin } from '../model/UserAdmin' import { UserAdmin, SearchUsersResult } from '../model/UserAdmin'
import { PendingCreation } from '../model/PendingCreation' import { PendingCreation } from '../model/PendingCreation'
import { CreatePendingCreations } from '../model/CreatePendingCreations' import { CreatePendingCreations } from '../model/CreatePendingCreations'
import { UpdatePendingCreation } from '../model/UpdatePendingCreation' import { UpdatePendingCreation } from '../model/UpdatePendingCreation'
@ -11,6 +14,7 @@ import { LoginPendingTasksAdminRepository } from '../../typeorm/repository/Login
import { UserRepository } from '../../typeorm/repository/User' import { UserRepository } from '../../typeorm/repository/User'
import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs' import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs'
import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs' import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs'
import SearchUsersArgs from '../arg/SearchUsersArgs'
import moment from 'moment' import moment from 'moment'
import { Transaction } from '@entity/Transaction' import { Transaction } from '@entity/Transaction'
import { TransactionCreation } from '@entity/TransactionCreation' import { TransactionCreation } from '@entity/TransactionCreation'
@ -23,11 +27,13 @@ import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
@Resolver() @Resolver()
export class AdminResolver { export class AdminResolver {
@Authorized([RIGHTS.SEARCH_USERS]) @Authorized([RIGHTS.SEARCH_USERS])
@Query(() => [UserAdmin]) @Query(() => SearchUsersResult)
async searchUsers(@Arg('searchText') searchText: string): Promise<UserAdmin[]> { async searchUsers(
@Args() { searchText, currentPage = 1, pageSize = 25, notActivated = false }: SearchUsersArgs,
): Promise<SearchUsersResult> {
const userRepository = getCustomRepository(UserRepository) const userRepository = getCustomRepository(UserRepository)
const users = await userRepository.findBySearchCriteria(searchText) const users = await userRepository.findBySearchCriteria(searchText)
const adminUsers = await Promise.all( let adminUsers = await Promise.all(
users.map(async (user) => { users.map(async (user) => {
const adminUser = new UserAdmin() const adminUser = new UserAdmin()
adminUser.userId = user.id adminUser.userId = user.id
@ -39,7 +45,12 @@ export class AdminResolver {
return adminUser 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.CREATE_PENDING_CREATION]) @Authorized([RIGHTS.CREATE_PENDING_CREATION])
@ -62,9 +73,9 @@ export class AdminResolver {
loginPendingTaskAdmin.memo = memo loginPendingTaskAdmin.memo = memo
loginPendingTaskAdmin.moderator = moderator loginPendingTaskAdmin.moderator = moderator
loginPendingTasksAdminRepository.save(loginPendingTaskAdmin) await loginPendingTasksAdminRepository.save(loginPendingTaskAdmin)
} }
return await getUserCreations(user.id) return getUserCreations(user.id)
} }
@Authorized([RIGHTS.CREATE_PENDING_CREATION]) @Authorized([RIGHTS.CREATE_PENDING_CREATION])

View File

@ -53,8 +53,8 @@ describe('CommunityResolver', () => {
getCommunityInfo: { getCommunityInfo: {
name: 'Gradido Entwicklung', name: 'Gradido Entwicklung',
description: 'Die lokale Entwicklungsumgebung von Gradido.', description: 'Die lokale Entwicklungsumgebung von Gradido.',
url: 'http://localhost/vue/', url: 'http://localhost/',
registerUrl: 'http://localhost/vue/register', registerUrl: 'http://localhost/register',
}, },
}, },
}) })
@ -75,22 +75,22 @@ describe('CommunityResolver', () => {
id: 1, id: 1,
name: 'Gradido Entwicklung', name: 'Gradido Entwicklung',
description: 'Die lokale Entwicklungsumgebung von Gradido.', description: 'Die lokale Entwicklungsumgebung von Gradido.',
url: 'http://localhost/vue/', url: 'http://localhost/',
registerUrl: 'http://localhost/vue/register-community', registerUrl: 'http://localhost/register-community',
}, },
{ {
id: 2, id: 2,
name: 'Gradido Staging', name: 'Gradido Staging',
description: 'Der Testserver der Gradido-Akademie.', description: 'Der Testserver der Gradido-Akademie.',
url: 'https://stage1.gradido.net/vue/', url: 'https://stage1.gradido.net/',
registerUrl: 'https://stage1.gradido.net/vue/register-community', registerUrl: 'https://stage1.gradido.net/register-community',
}, },
{ {
id: 3, id: 3,
name: 'Gradido-Akademie', name: 'Gradido-Akademie',
description: 'Freies Institut für Wirtschaftsbionik.', description: 'Freies Institut für Wirtschaftsbionik.',
url: 'https://gradido.net', 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', name: 'Gradido-Akademie',
description: 'Freies Institut für Wirtschaftsbionik.', description: 'Freies Institut für Wirtschaftsbionik.',
url: 'https://gradido.net', url: 'https://gradido.net',
registerUrl: 'https://gdd1.gradido.com/vue/register-community', registerUrl: 'https://gdd1.gradido.com/register-community',
}, },
], ],
}, },

View File

@ -29,7 +29,7 @@ export class CommunityResolver {
name: 'Gradido-Akademie', name: 'Gradido-Akademie',
description: 'Freies Institut für Wirtschaftsbionik.', description: 'Freies Institut für Wirtschaftsbionik.',
url: 'https://gradido.net', url: 'https://gradido.net',
registerUrl: 'https://gdd1.gradido.com/vue/register-community', registerUrl: 'https://gdd1.gradido.com/register-community',
}), }),
] ]
return [ return [
@ -37,22 +37,22 @@ export class CommunityResolver {
id: 1, id: 1,
name: 'Gradido Entwicklung', name: 'Gradido Entwicklung',
description: 'Die lokale Entwicklungsumgebung von Gradido.', description: 'Die lokale Entwicklungsumgebung von Gradido.',
url: 'http://localhost/vue/', url: 'http://localhost/',
registerUrl: 'http://localhost/vue/register-community', registerUrl: 'http://localhost/register-community',
}), }),
new Community({ new Community({
id: 2, id: 2,
name: 'Gradido Staging', name: 'Gradido Staging',
description: 'Der Testserver der Gradido-Akademie.', description: 'Der Testserver der Gradido-Akademie.',
url: 'https://stage1.gradido.net/vue/', url: 'https://stage1.gradido.net/',
registerUrl: 'https://stage1.gradido.net/vue/register-community', registerUrl: 'https://stage1.gradido.net/register-community',
}), }),
new Community({ new Community({
id: 3, id: 3,
name: 'Gradido-Akademie', name: 'Gradido-Akademie',
description: 'Freies Institut für Wirtschaftsbionik.', description: 'Freies Institut für Wirtschaftsbionik.',
url: 'https://gradido.net', url: 'https://gradido.net',
registerUrl: 'https://gdd1.gradido.com/vue/register-community', registerUrl: 'https://gdd1.gradido.com/register-community',
}), }),
] ]
} }

View File

@ -6,7 +6,7 @@ import {
getKlickTippUser, getKlickTippUser,
getKlicktippTagMap, getKlicktippTagMap,
unsubscribe, unsubscribe,
signIn, klicktippSignIn,
} from '../../apis/KlicktippController' } from '../../apis/KlicktippController'
import { RIGHTS } from '../../auth/RIGHTS' import { RIGHTS } from '../../auth/RIGHTS'
import SubscribeNewsletterArgs from '../arg/SubscribeNewsletterArgs' import SubscribeNewsletterArgs from '../arg/SubscribeNewsletterArgs'
@ -36,6 +36,6 @@ export class KlicktippResolver {
async subscribeNewsletter( async subscribeNewsletter(
@Args() { email, language }: SubscribeNewsletterArgs, @Args() { email, language }: SubscribeNewsletterArgs,
): Promise<boolean> { ): Promise<boolean> {
return await signIn(email, language) return await klicktippSignIn(email, language)
} }
} }

View File

@ -6,7 +6,7 @@ import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql'
import { getCustomRepository, getConnection, QueryRunner } from 'typeorm' import { getCustomRepository, getConnection, QueryRunner } from 'typeorm'
import CONFIG from '../../config' import CONFIG from '../../config'
import { sendEMail } from '../../util/sendEMail' import { sendTransactionReceivedEmail } from '../../mailer/sendTransactionReceivedEmail'
import { Transaction } from '../model/Transaction' import { Transaction } from '../model/Transaction'
import { TransactionList } from '../model/TransactionList' import { TransactionList } from '../model/TransactionList'
@ -503,7 +503,7 @@ export class TransactionResolver {
email: userEntity.email, email: userEntity.email,
}) })
if (!resultGDTSum.success) throw new Error(resultGDTSum.data) if (!resultGDTSum.success) throw new Error(resultGDTSum.data)
transactions.gdtSum = resultGDTSum.data.sum || 0 transactions.gdtSum = Number(resultGDTSum.data.sum / 100) || 0
// get balance // get balance
const balanceRepository = getCustomRepository(BalanceRepository) const balanceRepository = getCustomRepository(BalanceRepository)
@ -651,21 +651,14 @@ export class TransactionResolver {
} }
// send notification email // send notification email
// TODO: translate // TODO: translate
await sendEMail({ await sendTransactionReceivedEmail({
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`, senderFirstName: senderUser.firstName,
to: `${recipiantUser.firstName} ${recipiantUser.lastName} <${recipiantUser.email}>`, senderLastName: senderUser.lastName,
subject: 'Gradido Überweisung', recipientFirstName: recipiantUser.firstName,
text: `Hallo ${recipiantUser.firstName} ${recipiantUser.lastName} recipientLastName: recipiantUser.lastName,
email: recipiantUser.email,
Du hast soeben ${amount} GDD von ${senderUser.firstName} ${senderUser.lastName} erhalten. amount,
${senderUser.firstName} ${senderUser.lastName} schreibt: memo,
${memo}
Bitte antworte nicht auf diese E-Mail!
Mit freundlichen Grüßen,
dein Gradido-Team`,
}) })
return 'success' return 'success'

View File

@ -12,12 +12,20 @@ import { LoginUserBackup } from '@entity/LoginUserBackup'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { User } from '@entity/User' import { User } from '@entity/User'
import CONFIG from '../../config' 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 { return {
__esModule: true, __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 result: any
let emailOptIn: string let emailOptIn: string
let newUser: User
beforeAll(async () => { beforeAll(async () => {
result = await mutate({ mutation, variables }) result = await mutate({ mutation, variables })
@ -90,7 +97,6 @@ describe('UserResolver', () => {
loginEmailOptIn = await getRepository(LoginEmailOptIn) loginEmailOptIn = await getRepository(LoginEmailOptIn)
.createQueryBuilder('login_email_optin') .createQueryBuilder('login_email_optin')
.getMany() .getMany()
newUser = user[0]
emailOptIn = loginEmailOptIn[0].verificationCode.toString() emailOptIn = loginEmailOptIn[0].verificationCode.toString()
}) })
@ -164,14 +170,12 @@ describe('UserResolver', () => {
describe('account activation email', () => { describe('account activation email', () => {
it('sends an account activation email', () => { it('sends an account activation email', () => {
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(/\$1/g, emailOptIn) const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(/{code}/g, emailOptIn)
expect(sendEMail).toBeCalledWith({ expect(sendAccountActivationEmail).toBeCalledWith({
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`, link: activationLink,
to: `${newUser.firstName} ${newUser.lastName} <${newUser.email}>`, firstName: 'Peter',
subject: 'Gradido: E-Mail Überprüfung', lastName: 'Lustig',
text: email: 'peter@lustig.de',
expect.stringContaining(`Hallo ${newUser.firstName} ${newUser.lastName},`) &&
expect.stringContaining(activationLink),
}) })
}) })
}) })
@ -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 () => { afterAll(async () => {

View File

@ -20,9 +20,10 @@ import { UserRepository } from '../../typeorm/repository/User'
import { LoginUser } from '@entity/LoginUser' import { LoginUser } from '@entity/LoginUser'
import { LoginUserBackup } from '@entity/LoginUserBackup' import { LoginUserBackup } from '@entity/LoginUserBackup'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' 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 { LoginElopageBuysRepository } from '../../typeorm/repository/LoginElopageBuys'
import { signIn } from '../../apis/KlicktippController' import { klicktippSignIn } from '../../apis/KlicktippController'
import { RIGHTS } from '../../auth/RIGHTS' import { RIGHTS } from '../../auth/RIGHTS'
import { ServerUserRepository } from '../../typeorm/repository/ServerUser' import { ServerUserRepository } from '../../typeorm/repository/ServerUser'
import { ROLE_ADMIN } from '../../auth/ROLES' import { ROLE_ADMIN } from '../../auth/ROLES'
@ -447,15 +448,15 @@ export class UserResolver {
const emailOptIn = await createEmailOptIn(loginUserId, queryRunner) const emailOptIn = await createEmailOptIn(loginUserId, queryRunner)
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace( const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
/\$1/g, /{code}/g,
emailOptIn.verificationCode.toString(), emailOptIn.verificationCode.toString(),
) )
const emailSent = await this.sendAccountActivationEmail( const emailSent = await sendAccountActivationEmail({
activationLink, link: activationLink,
firstName, firstName,
lastName, lastName,
email, email,
) })
// In case EMails are disabled log the activation link for the user // In case EMails are disabled log the activation link for the user
if (!emailSent) { if (!emailSent) {
@ -472,29 +473,6 @@ export class UserResolver {
return 'success' return 'success'
} }
private sendAccountActivationEmail(
activationLink: string,
firstName: string,
lastName: string,
email: string,
): Promise<boolean> {
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) @Mutation(() => Boolean)
async sendActivationEmail(@Arg('email') email: string): Promise<boolean> { async sendActivationEmail(@Arg('email') email: string): Promise<boolean> {
const loginUserRepository = getCustomRepository(LoginUserRepository) const loginUserRepository = getCustomRepository(LoginUserRepository)
@ -508,16 +486,16 @@ export class UserResolver {
const emailOptIn = await createEmailOptIn(loginUser.id, queryRunner) const emailOptIn = await createEmailOptIn(loginUser.id, queryRunner)
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace( const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
/\$1/g, /{code}/g,
emailOptIn.verificationCode.toString(), emailOptIn.verificationCode.toString(),
) )
const emailSent = await this.sendAccountActivationEmail( const emailSent = await sendAccountActivationEmail({
activationLink, link: activationLink,
loginUser.firstName, firstName: loginUser.firstName,
loginUser.lastName, lastName: loginUser.lastName,
email, email,
) })
// In case EMails are disabled log the activation link for the user // In case EMails are disabled log the activation link for the user
if (!emailSent) { if (!emailSent) {
@ -545,22 +523,15 @@ export class UserResolver {
const optInCode = await getOptInCode(loginUser) const optInCode = await getOptInCode(loginUser)
const link = CONFIG.EMAIL_LINK_SETPASSWORD.replace( const link = CONFIG.EMAIL_LINK_SETPASSWORD.replace(
/\$1/g, /{code}/g,
optInCode.verificationCode.toString(), optInCode.verificationCode.toString(),
) )
const emailSent = await sendEMail({ const emailSent = await sendResetPasswordEmail({
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`, link,
to: `${loginUser.firstName} ${loginUser.lastName} <${email}>`, firstName: loginUser.firstName,
subject: 'Gradido: Reset Password', lastName: loginUser.lastName,
text: `Hallo ${loginUser.firstName} ${loginUser.lastName}, email,
Du oder jemand anderes hat für dieses Konto ein Zurücksetzen des Passworts angefordert.
Wenn du es warst, klicke bitte auf den Link: ${link}
oder kopiere den obigen Link in Dein Browserfenster.
Mit freundlichen Grüßen,
dein Gradido-Team`,
}) })
// In case EMails are disabled log the activation link for the user // 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? // TODO do we always signUp the user? How to handle things with old users?
if (optInCode.emailOptInTypeId === EMAIL_OPT_IN_REGISTER) { if (optInCode.emailOptInTypeId === EMAIL_OPT_IN_REGISTER) {
try { try {
await signIn(loginUser.email, loginUser.language, loginUser.firstName, loginUser.lastName) await klicktippSignIn(
loginUser.email,
loginUser.language,
loginUser.firstName,
loginUser.lastName,
)
} catch { } catch {
// TODO is this a problem? // TODO is this a problem?
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -738,7 +714,7 @@ export class UserResolver {
if (password && passwordNew) { if (password && passwordNew) {
// TODO: This had some error cases defined - like missing private key. This is no longer checked. // TODO: This had some error cases defined - like missing private key. This is no longer checked.
const oldPasswordHash = SecretKeyCryptographyCreateKey(loginUser.email, password) const oldPasswordHash = SecretKeyCryptographyCreateKey(loginUser.email, password)
if (loginUser.password !== oldPasswordHash[0].readBigUInt64LE()) { if (BigInt(loginUser.password.toString()) !== oldPasswordHash[0].readBigUInt64LE()) {
throw new Error(`Old password is invalid`) throw new Error(`Old password is invalid`)
} }
@ -748,7 +724,7 @@ export class UserResolver {
const encryptedPrivkey = SecretKeyCryptographyEncrypt(privKey, newPasswordHash[1]) const encryptedPrivkey = SecretKeyCryptographyEncrypt(privKey, newPasswordHash[1])
// Save new password hash and newly encrypted private key // Save new password hash and newly encrypted private key
loginUser.password = newPasswordHash[0].readBigInt64LE() loginUser.password = newPasswordHash[0].readBigUInt64LE()
loginUser.privKey = encryptedPrivkey loginUser.privKey = encryptedPrivkey
} }

View File

@ -13,7 +13,7 @@ async function main() {
console.log(`Server is running at http://localhost:${CONFIG.PORT}`) console.log(`Server is running at http://localhost:${CONFIG.PORT}`)
if (CONFIG.GRAPHIQL) { if (CONFIG.GRAPHIQL) {
// eslint-disable-next-line no-console // 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}`)
} }
}) })
} }

View File

@ -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 <peter@lustig.de>`,
subject: 'Gradido: E-Mail Überprüfung',
text:
expect.stringContaining('Hallo Peter Lustig') && expect.stringContaining('activationLink'),
})
})
})

View File

@ -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<boolean> => {
return sendEMail({
to: `${data.firstName} ${data.lastName} <${data.email}>`,
subject: accountActivation.de.subject,
text: accountActivation.de.text(data),
})
}

View File

@ -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()
})
})
})

View File

@ -3,7 +3,6 @@ import { createTransport } from 'nodemailer'
import CONFIG from '../config' import CONFIG from '../config'
export const sendEMail = async (emailDef: { export const sendEMail = async (emailDef: {
from: string
to: string to: string
subject: string subject: string
text: string text: string
@ -23,7 +22,10 @@ export const sendEMail = async (emailDef: {
pass: CONFIG.EMAIL_PASSWORD, 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) { if (!info.messageId) {
throw new Error('error sending notification email, but transaction succeed') throw new Error('error sending notification email, but transaction succeed')
} }

View File

@ -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 <peter@lustig.de>`,
subject: 'Gradido: Passwort zurücksetzen',
text: expect.stringContaining('Hallo Peter Lustig') && expect.stringContaining('resetLink'),
})
})
})

View File

@ -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<boolean> => {
return sendEMail({
to: `${data.firstName} ${data.lastName} <${data.email}>`,
subject: resetPassword.de.subject,
text: resetPassword.de.text(data),
})
}

View File

@ -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 <peter@lustig.de>`,
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!'),
})
})
})

View File

@ -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<boolean> => {
return sendEMail({
to: `${data.recipientFirstName} ${data.recipientLastName} <${data.email}>`,
subject: transactionReceived.de.subject,
text: transactionReceived.de.text(data),
})
}

View File

@ -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`,
},
}

View File

@ -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`,
},
}

View File

@ -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`,
},
}

View File

@ -1,5 +1,5 @@
import { MiddlewareFn } from 'type-graphql' import { MiddlewareFn } from 'type-graphql'
import { /* signIn, */ getKlickTippUser } from '../apis/KlicktippController' import { /* klicktippSignIn, */ getKlickTippUser } from '../apis/KlicktippController'
import { KlickTipp } from '../graphql/model/KlickTipp' import { KlickTipp } from '../graphql/model/KlickTipp'
import CONFIG from '../config/index' import CONFIG from '../config/index'
@ -12,7 +12,7 @@ import CONFIG from '../config/index'
// // Do Something here before resolver is called // // Do Something here before resolver is called
// const result = await next() // const result = await next()
// // Do Something here after resolver is completed // // Do Something here after resolver is completed
// await signIn(result.email, result.language, result.firstName, result.lastName) // await klicktippSignIn(result.email, result.language, result.firstName, result.lastName)
// return result // return result
// } // }

View File

@ -6,7 +6,6 @@ import 'module-alias/register'
import { ApolloServer } from 'apollo-server-express' import { ApolloServer } from 'apollo-server-express'
import express from 'express' import express from 'express'
import bodyParser from 'body-parser'
// database // database
import connection from '../typeorm/connection' import connection from '../typeorm/connection'
@ -54,8 +53,19 @@ const createServer = async (context: any = serverContext): Promise<any> => {
// cors // cors
app.use(cors) app.use(cors)
// bodyparser // bodyparser json
app.use(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 // Elopage Webhook
app.post('/hook/elopage/' + CONFIG.WEBHOOK_ELOPAGE_SECRET, elopageWebhook) app.post('/hook/elopage/' + CONFIG.WEBHOOK_ELOPAGE_SECRET, elopageWebhook)
@ -64,10 +74,11 @@ const createServer = async (context: any = serverContext): Promise<any> => {
const apollo = new ApolloServer({ const apollo = new ApolloServer({
schema: await schema(), schema: await schema(),
playground: CONFIG.GRAPHIQL, playground: CONFIG.GRAPHIQL,
introspection: CONFIG.GRAPHIQL,
context, context,
plugins, plugins,
}) })
apollo.applyMiddleware({ app }) apollo.applyMiddleware({ app, path: '/' })
return { apollo, app, con } return { apollo, app, con }
} }

View File

@ -1,6 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ApolloLogPlugin } from 'apollo-log'
const plugins = [ const plugins = [
{ {
requestDidStart() { requestDidStart() {
@ -8,13 +10,18 @@ const plugins = [
willSendResponse(requestContext: any) { willSendResponse(requestContext: any) {
const { setHeaders = [] } = requestContext.context const { setHeaders = [] } = requestContext.context
setHeaders.forEach(({ key, value }: { [key: string]: string }) => { 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 return requestContext
}, },
} }
}, },
}, },
ApolloLogPlugin(),
] ]
export default plugins export default plugins

View File

@ -1,4 +1,4 @@
import { createConnection, Connection } from 'typeorm' import { createConnection, Connection, FileLogger } from 'typeorm'
import CONFIG from '../config' import CONFIG from '../config'
import { entities } from '@entity/index' import { entities } from '@entity/index'
@ -15,6 +15,10 @@ const connection = async (): Promise<Connection | null> => {
database: CONFIG.DB_DATABASE, database: CONFIG.DB_DATABASE,
entities, entities,
synchronize: false, synchronize: false,
logging: true,
logger: new FileLogger('all', {
logPath: CONFIG.TYPEORM_LOGGING_RELATIVE_PATH,
}),
}) })
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

View File

@ -1,6 +1,7 @@
import { EntityRepository, Repository } from 'typeorm' import { EntityRepository, Repository } from 'typeorm'
import { Order } from '../../graphql/enum/Order' import { Order } from '../../graphql/enum/Order'
import { UserTransaction } from '@entity/UserTransaction' import { UserTransaction } from '@entity/UserTransaction'
import { TransactionTypeId } from '../../graphql/enum/TransactionTypeId'
@EntityRepository(UserTransaction) @EntityRepository(UserTransaction)
export class UserTransactionRepository extends Repository<UserTransaction> { export class UserTransactionRepository extends Repository<UserTransaction> {
@ -14,7 +15,9 @@ export class UserTransactionRepository extends Repository<UserTransaction> {
if (onlyCreation) { if (onlyCreation) {
return this.createQueryBuilder('userTransaction') return this.createQueryBuilder('userTransaction')
.where('userTransaction.userId = :userId', { userId }) .where('userTransaction.userId = :userId', { userId })
.andWhere('userTransaction.type = "creation"') .andWhere('userTransaction.transactionTypeId = :transactionTypeId', {
transactionTypeId: TransactionTypeId.CREATION,
})
.orderBy('userTransaction.balanceDate', order) .orderBy('userTransaction.balanceDate', order)
.limit(limit) .limit(limit)
.offset(offset) .offset(offset)

View File

@ -28,19 +28,23 @@
*/ */
import { LoginElopageBuys } from '@entity/LoginElopageBuys' import { LoginElopageBuys } from '@entity/LoginElopageBuys'
import { LoginUser } from '@entity/LoginUser' import { getCustomRepository } from 'typeorm'
import { UserResolver } from '../graphql/resolver/UserResolver' 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<void> => { export const elopageWebhook = async (req: any, res: any): Promise<void> => {
// eslint-disable-next-line no-console
console.log('Elopage Hook received', req.body)
res.status(200).end() // Responding is important res.status(200).end() // Responding is important
const loginElopgaeBuyRepository = await getCustomRepository(LoginElopageBuysRepository)
const loginElopgaeBuy = new LoginElopageBuys() const loginElopgaeBuy = new LoginElopageBuys()
let firstName = '' let firstName = ''
let lastName = '' let lastName = ''
const entries = req.body.split('&') const entries = req.body.split('&')
entries.foreach((entry: string) => { entries.forEach((entry: string) => {
const keyVal = entry.split('=') const keyVal = entry.split('=')
if (keyVal.length !== 2) { if (keyVal.length > 2) {
throw new Error(`Error parsing entry '${entry}'`) throw new Error(`Error parsing entry '${entry}'`)
} }
const key = keyVal[0] const key = keyVal[0]
@ -88,8 +92,10 @@ export const elopageWebhook = async (req: any, res: any): Promise<void> => {
lastName = val lastName = val
break break
default: default:
// this is too spammy
// eslint-disable-next-line no-console // 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<void> => {
} }
// Save the hook data // Save the hook data
await loginElopgaeBuy.save() await loginElopgaeBuyRepository.save(loginElopgaeBuy)
// create user for certain products // create user for certain products
/* /*
@ -133,7 +139,8 @@ export const elopageWebhook = async (req: any, res: any): Promise<void> => {
} }
// Do we already have such a user? // 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 // eslint-disable-next-line no-console
console.log(`Did not create User - already exists with email: ${email}`) console.log(`Did not create User - already exists with email: ${email}`)
return return

View File

@ -2,7 +2,7 @@
# yarn lockfile v1 # yarn lockfile v1
"@apollo/protobufjs@1.2.2": "@apollo/protobufjs@1.2.2", "@apollo/protobufjs@^1.0.3":
version "1.2.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.2.tgz#4bd92cd7701ccaef6d517cdb75af2755f049f87c" resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.2.tgz#4bd92cd7701ccaef6d517cdb75af2755f049f87c"
integrity sha512-vF+zxhPiLtkwxONs6YanSt1EpwpGilThpneExUN5K3tCymuxNnVq2yojTvnpRjv2QfsEIt/n7ozPIIzBLwGIDQ== integrity sha512-vF+zxhPiLtkwxONs6YanSt1EpwpGilThpneExUN5K3tCymuxNnVq2yojTvnpRjv2QfsEIt/n7ozPIIzBLwGIDQ==
@ -1273,6 +1273,24 @@ apollo-link@^1.2.14:
tslib "^1.9.3" tslib "^1.9.3"
zen-observable-ts "^0.8.21" 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: apollo-reporting-protobuf@^0.8.0:
version "0.8.0" version "0.8.0"
resolved "https://registry.yarnpkg.com/apollo-reporting-protobuf/-/apollo-reporting-protobuf-0.8.0.tgz#ae9d967934d3d8ed816fc85a0d8068ef45c371b9" 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: dependencies:
"@apollo/protobufjs" "1.2.2" "@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: apollo-server-caching@^0.7.0:
version "0.7.0" version "0.7.0"
resolved "https://registry.yarnpkg.com/apollo-server-caching/-/apollo-server-caching-0.7.0.tgz#e6d1e68e3bb571cba63a61f60b434fb771c6ff39" 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" subscriptions-transport-ws "^0.9.19"
uuid "^8.0.0" 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" version "3.1.0"
resolved "https://registry.yarnpkg.com/apollo-server-env/-/apollo-server-env-3.1.0.tgz#0733c2ef50aea596cc90cf40a53f6ea2ad402cd0" resolved "https://registry.yarnpkg.com/apollo-server-env/-/apollo-server-env-3.1.0.tgz#0733c2ef50aea596cc90cf40a53f6ea2ad402cd0"
integrity sha512-iGdZgEOAuVop3vb0F2J3+kaBVi4caMoxefHosxmgzAbbSpvWehB8Y1QiSyyMeouYC38XNVk5wnZl+jdGSsWsIQ== integrity sha512-iGdZgEOAuVop3vb0F2J3+kaBVi4caMoxefHosxmgzAbbSpvWehB8Y1QiSyyMeouYC38XNVk5wnZl+jdGSsWsIQ==
@ -1354,6 +1379,13 @@ apollo-server-express@^2.25.2:
subscriptions-transport-ws "^0.9.19" subscriptions-transport-ws "^0.9.19"
type-is "^1.6.16" 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: apollo-server-plugin-base@^0.13.0:
version "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" 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: dependencies:
apollo-server-core "^2.25.2" 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: apollo-server-types@^0.9.0:
version "0.9.0" version "0.9.0"
resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-0.9.0.tgz#ccf550b33b07c48c72f104fbe2876232b404848b" 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" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 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" version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== 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" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= 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: fastq@^1.6.0:
version "1.13.0" version "1.13.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" 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" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== 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: long@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
@ -4194,6 +4245,11 @@ named-placeholders@^1.1.2:
dependencies: dependencies:
lru-cache "^4.1.3" 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: natural-compare@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"

View File

@ -37,5 +37,5 @@
</div> </div>
<!-- built files will be auto injected --> <!-- built files will be auto injected -->
<script type="text/javascript">csfr = "<?= $this->request->getParam('_csrfToken') ?>";</script> <script type="text/javascript">csfr = "<?= $this->request->getParam('_csrfToken') ?>";</script>
<script type="text/javascript" src="/vue/app.js"></script></body> <script type="text/javascript" src="/app.js"></script></body>
</html> </html>

8
database/.env.template Normal file
View File

@ -0,0 +1,8 @@
DB_HOST=localhost
DB_PORT=3306
DB_USER=$DB_USER
DB_PASSWORD=$DB_PASSWORD
DB_DATABASE=gradido_community
MIGRATIONS_TABLE=migrations
TYPEORM_SEEDING_FACTORIES=src/factories/**/*{.ts,.js}

View File

@ -19,7 +19,7 @@ define(LoginUser, (faker: typeof Faker, context?: LoginUserContext) => {
user.privKey = context.privKey ? context.privKey : randomBytes(80) user.privKey = context.privKey ? context.privKey : randomBytes(80)
user.emailHash = context.emailHash ? context.emailHash : randomBytes(32) user.emailHash = context.emailHash ? context.emailHash : randomBytes(32)
user.createdAt = context.createdAt ? context.createdAt : faker.date.recent() user.createdAt = context.createdAt ? context.createdAt : faker.date.recent()
user.emailChecked = context.emailChecked ? context.emailChecked : true user.emailChecked = context.emailChecked === undefined ? false : context.emailChecked
user.passphraseShown = context.passphraseShown ? context.passphraseShown : false user.passphraseShown = context.passphraseShown ? context.passphraseShown : false
user.language = context.language ? context.language : 'en' user.language = context.language ? context.language : 'en'
user.disabled = context.disabled ? context.disabled : false user.disabled = context.disabled ? context.disabled : false

View File

@ -6,6 +6,8 @@ import { CreatePeterLustigSeed } from './seeds/users/peter-lustig.admin.seed'
import { CreateBibiBloxbergSeed } from './seeds/users/bibi-bloxberg.seed' import { CreateBibiBloxbergSeed } from './seeds/users/bibi-bloxberg.seed'
import { CreateRaeuberHotzenplotzSeed } from './seeds/users/raeuber-hotzenplotz.seed' import { CreateRaeuberHotzenplotzSeed } from './seeds/users/raeuber-hotzenplotz.seed'
import { CreateBobBaumeisterSeed } from './seeds/users/bob-baumeister.seed' import { CreateBobBaumeisterSeed } from './seeds/users/bob-baumeister.seed'
import { CreateGarrickOllivanderSeed } from './seeds/users/garrick-ollivander.seed'
import { CreateUserSeed } from './seeds/create-user.seed'
import { DecayStartBlockSeed } from './seeds/decay-start-block.seed' import { DecayStartBlockSeed } from './seeds/decay-start-block.seed'
import { resetDB, pool, migration } from './helpers' import { resetDB, pool, migration } from './helpers'
@ -44,6 +46,11 @@ const run = async (command: string) => {
await runSeeder(CreateBibiBloxbergSeed) await runSeeder(CreateBibiBloxbergSeed)
await runSeeder(CreateRaeuberHotzenplotzSeed) await runSeeder(CreateRaeuberHotzenplotzSeed)
await runSeeder(CreateBobBaumeisterSeed) await runSeeder(CreateBobBaumeisterSeed)
// eslint-disable-next-line prefer-spread
Array.apply(null, Array(96)).forEach(async () => {
await runSeeder(CreateUserSeed)
})
await runSeeder(CreateGarrickOllivanderSeed)
break break
default: default:
throw new Error(`Unsupported command ${command}`) throw new Error(`Unsupported command ${command}`)

View File

@ -23,7 +23,7 @@ export interface LoginUserContext {
language?: string language?: string
disabled?: boolean disabled?: boolean
groupId?: number groupId?: number
publisherId?: number | null publisherId?: number
} }
export interface LoginUserBackupContext { export interface LoginUserBackupContext {

View File

@ -15,7 +15,7 @@ export interface UserInterface {
language?: string language?: string
disabled?: boolean disabled?: boolean
groupId?: number groupId?: number
publisherId?: number | null publisherId?: number
// from login user backup // from login user backup
passphrase?: string passphrase?: string
mnemonicType?: number mnemonicType?: number

View File

@ -1,11 +1,8 @@
import { Factory, Seeder } from 'typeorm-seeding' import { Factory, Seeder } from 'typeorm-seeding'
import { User } from '../../entity/User' import { userSeeder } from './helpers/user-helpers'
// import { LoginUser } from '../../entity/LoginUser'
export class CreateUserSeed implements Seeder { export class CreateUserSeed implements Seeder {
public async run(factory: Factory): Promise<void> { public async run(factory: Factory): Promise<void> {
// const loginUser = await factory(LoginUser)().make() await userSeeder(factory, {})
// console.log(loginUser.email)
await factory(User)().create()
} }
} }

View File

@ -27,6 +27,7 @@ import { Factory } from 'typeorm-seeding'
export const userSeeder = async (factory: Factory, userData: UserInterface): Promise<void> => { export const userSeeder = async (factory: Factory, userData: UserInterface): Promise<void> => {
const user = await factory(User)(createUserContext(userData)).create() const user = await factory(User)(createUserContext(userData)).create()
if (!userData.email) userData.email = user.email
const loginUser = await factory(LoginUser)(createLoginUserContext(userData)).create() const loginUser = await factory(LoginUser)(createLoginUserContext(userData)).create()
await factory(LoginUserBackup)(createLoginUserBackupContext(userData, loginUser)).create() await factory(LoginUserBackup)(createLoginUserBackupContext(userData, loginUser)).create()

View File

@ -17,7 +17,6 @@ export const bibiBloxberg = {
language: 'de', language: 'de',
disabled: false, disabled: false,
groupId: 1, groupId: 1,
publisherId: null,
passphrase: passphrase:
'knife normal level all hurdle crucial color avoid warrior stadium road bachelor affair topple hawk pottery right afford immune two ceiling budget glance hour ', 'knife normal level all hurdle crucial color avoid warrior stadium road bachelor affair topple hawk pottery right afford immune two ceiling budget glance hour ',
mnemonicType: 2, mnemonicType: 2,

View File

@ -17,7 +17,6 @@ export const bobBaumeister = {
language: 'de', language: 'de',
disabled: false, disabled: false,
groupId: 1, groupId: 1,
publisherId: null,
passphrase: passphrase:
'detail master source effort unable waste tilt flush domain orchard art truck hint barrel response gate impose peanut secret merry three uncle wink resource ', 'detail master source effort unable waste tilt flush domain orchard art truck hint barrel response gate impose peanut secret merry three uncle wink resource ',
mnemonicType: 2, mnemonicType: 2,

View File

@ -0,0 +1,9 @@
import { Factory, Seeder } from 'typeorm-seeding'
import { garrickOllivander } from './garrick-ollivander'
import { userSeeder } from '../helpers/user-helpers'
export class CreateGarrickOllivanderSeed implements Seeder {
public async run(factory: Factory): Promise<void> {
await userSeeder(factory, garrickOllivander)
}
}

View File

@ -0,0 +1,21 @@
export const garrickOllivander = {
email: 'garrick@ollivander.com',
firstName: 'Garrick',
lastName: 'Ollivander',
username: 'garrick',
description: `Curious ... curious ...
Renowned wandmaker Mr Ollivander owns the wand shop Ollivanders: Makers of Fine Wands Since 382 BC in Diagon Alley. His shop is widely considered the best place to purchase a wand.`,
password: BigInt('0'),
emailHash: Buffer.from('91e358000e908146342789979d62a7255b2b88a71dad0c6a10e32af44be57886', 'hex'),
createdAt: new Date('2022-01-10T10:23:17'),
emailChecked: false,
passphraseShown: false,
language: 'en',
disabled: false,
groupId: 1,
passphrase:
'human glide theory clump wish history other duty door fringe neck industry ostrich equal plate diesel tornado neck people antenna door category moon hen ',
mnemonicType: 2,
isAdmin: false,
addBalance: false,
}

View File

@ -17,7 +17,6 @@ export const peterLustig = {
language: 'de', language: 'de',
disabled: false, disabled: false,
groupId: 1, groupId: 1,
publisherId: null,
passphrase: passphrase:
'okay property choice naive calm present weird increase stuff royal vibrant frame attend wood one else tribe pull hedgehog woman kitchen hawk snack smart ', 'okay property choice naive calm present weird increase stuff royal vibrant frame attend wood one else tribe pull hedgehog woman kitchen hawk snack smart ',
mnemonicType: 2, mnemonicType: 2,

View File

@ -17,7 +17,6 @@ export const raeuberHotzenplotz = {
language: 'de', language: 'de',
disabled: false, disabled: false,
groupId: 1, groupId: 1,
publisherId: null,
passphrase: passphrase:
'gospel trip tenant mouse spider skill auto curious man video chief response same little over expire drum display fancy clinic keen throw urge basket ', 'gospel trip tenant mouse spider skill auto curious man video chief response same little over expire drum display fancy clinic keen throw urge basket ',
mnemonicType: 2, mnemonicType: 2,

View File

@ -0,0 +1,39 @@
GRADIDO_LOG_PATH=/home/gradido/gradido/deployment/bare_metal/log
# start script
DEPLOY_SEED_DATA=false
# nginx
NGINX_REWRITE_LEGACY_URLS=true
NGINX_SSL=true
NGINX_SERVER_NAME=stage1.gradido.net
NGINX_SSL_CERTIFICATE=/etc/letsencrypt/live/stage1.gradido.net/fullchain.pem
NGINX_SSL_CERTIFICATE_KEY=/etc/letsencrypt/live/stage1.gradido.net/privkey.pem
NGINX_SSL_DHPARAM=/etc/letsencrypt/ssl-dhparams.pem
NGINX_SSL_INCLUDE=/etc/letsencrypt/options-ssl-nginx.conf
NGINX_UPDATE_PAGE_ROOT=/home/gradido/gradido/deployment/bare_metal/nginx/update-page
# webhook
WEBHOOK_GITHUB_SECRET=secret
WEBHOOK_GITHUB_BRANCH=master
# backend
EMAIL=true
EMAIL_USERNAME=peter@lustig.de
EMAIL_SENDER=peter@lustig.de
EMAIL_PASSWORD=1234
EMAIL_SMTP_URL=smtp.lustig.de
EMAIL_LINK_VERIFICATION=https://stage1.gradido.net/checkEmail/{code}
EMAIL_LINK_SETPASSWORD=https://stage1.gradido.net/reset/{code}
TYPEORM_LOGGING_RELATIVE_PATH=../deployment/bare_metal/log/typeorm.backend.log
WEBHOOK_ELOPAGE_SECRET=secret
# frontend
GRAPHQL_URI=https://stage1.gradido.net/graphql
ADMIN_AUTH_URL=https://stage1.gradido.net/admin/authenticate?token={token}
# admin
WALLET_AUTH_URL=https://stage1.gradido.net/authenticate?token={token}
WALLET_URL=https://stage1.gradido.net/login

28
deployment/bare_metal/backup.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/bash
# This script will shut down all services, backup the whole database and restart the services
# Find current directory & configure paths
set -o allexport
SCRIPT_PATH=$(realpath $0)
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
PROJECT_ROOT=$SCRIPT_DIR/../..
set +o allexport
# Load backend .env for DB_USERNAME, DB_PASSWORD & DB_DATABASE
# NOTE: all config values will be in process.env when starting
# the services and will therefore take precedence over the .env
if [ -f "$PROJECT_ROOT/backend/.env" ]; then
export $(cat $PROJECT_ROOT/backend/.env | sed 's/#.*//g' | xargs)
else
export $(cat $PROJECT_ROOT/backend/.env.dist | sed 's/#.*//g' | xargs)
fi
# Stop Services
pm2 stop gradido-backend
# Backup data
mysqldump --databases --single-transaction --quick --lock-tables=false > ${SCRIPT_DIR}/backup/mariadb-backup-$(date +%d-%m-%Y_%H-%M-%S).sql -u ${DB_USER} -p${DB_PASSWORD} ${DB_DATABASE}
# Start Services
pm2 start gradido-backend

View File

View File

@ -1,29 +0,0 @@
#!/bin/bash
# stop login_server running in screen
screen -XS login quit
# rebuild login-server
cd ../../login_server
if [ ! -d "./build" ] ; then
cd scripts
./prepare_build.sh
cd ..
fi
cd build
cmake ..
make -j$(nproc) Gradido_LoginServer
# rebuild locales
cd ../scripts
./compile_pot.sh
cd ../src/LOCALE
cp *.mo *.po /etc/grd_login/LOCALE/
cd ../../build/bin
# start login-server
screen -dmS 'login_server' bash -c './Gradido_LoginServer'

126
deployment/bare_metal/install.sh Executable file
View File

@ -0,0 +1,126 @@
#!/bin/bash
# This install script requires the minimum requirements already installed.
# How to do this is described in detail in [setup.md](./setup.md)
# Find current directory & configure paths
set -o allexport
SCRIPT_PATH=$(realpath $0)
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
PROJECT_ROOT=$SCRIPT_DIR/../..
set +o allexport
# Load .env or .env.dist if not present
# NOTE: all config values will be in process.env when starting
# the services and will therefore take precedence over the .env
if [ -f "$SCRIPT_DIR/.env" ]; then
export $(cat $SCRIPT_DIR/.env | sed 's/#.*//g' | xargs)
else
export $(cat $SCRIPT_DIR/.env.dist | sed 's/#.*//g' | xargs)
fi
# Configure git
git config pull.ff only
# Install mariadb
sudo apt-get install -y mariadb-server
sudo mysql_secure_installation
# Enter current password for root (enter for none): enter
# Switch to unix_socket authentication [Y/n] Y
# Change the root password? [Y/n] n
# Remove anonymous users? [Y/n] Y
# Disallow root login remotely? [Y/n] Y
# Remove test database and access to it? [Y/n] Y
# Reload privilege tables now? [Y/n] Y
# Install nginx
sudo apt-get install -y nginx
sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /home/gradido/gradido/deployment/bare_metal/nginx/sites-available/gradido.conf /etc/nginx/sites-available
# sudo ln -s /etc/nginx/sites-available/gradido.conf /etc/nginx/sites-enabled
sudo ln -s /home/gradido/gradido/deployment/bare_metal/nginx/sites-available/update-page.conf /etc/nginx/sites-available
sudo ln -s /home/gradido/gradido/deployment/bare_metal/nginx/common /etc/nginx/
sudo rmdir /etc/nginx/conf.d
sudo ln -s /home/gradido/gradido/deployment/bare_metal/nginx/conf.d /etc/nginx/
# Allow nginx configuration and restart for gradido
#TODO generate file
sudo nano /etc/sudoers.d/gradido
> gradido ALL=(ALL) NOPASSWD: /etc/init.d/nginx start,/etc/init.d/nginx stop,/etc/init.d/nginx restart
sudo chmod a+rw /etc/nginx/sites-enabled
# Install node 16.x
curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt-get install -y nodejs
sudo apt-get install -y build-essential
# Install yarn
sudo apt-get install -y curl
sudo apt-get install -y gnupg
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install -y yarn
# Install pm2
sudo yarn global add pm2
pm2 startup
> execute command output in shell
# Install certbot
sudo apt-get install -y certbot
sudo apt-get install -y python3-certbot-nginx
sudo certbot
> Enter email address (used for urgent renewal and security notices) > support@gradido.net
> Please read the Terms of Service at > Y
> Would you be willing, once your first certificate is successfully issued, to > N
> No names were found in your configuration files. Please enter in your domain > stage1.gradido.net
# Install logrotate
# sudo apt-get install -y logrotate
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_DIR/logrotate/gradido.conf.template > $SCRIPT_DIR/logrotate/gradido.conf
sudo mv $SCRIPT_DIR/logrotate/gradido.conf /etc/logrotate.d/gradido.conf
sudo chown root:root /etc/logrotate.d/gradido.conf
# Install mysql autobackup
sudo apt-get install -y automysqlbackup
# Webhooks (optional) (for development)
sudo apt install -y webhook
# TODO generate
# put hook into github
# TODO adjust secret
# TODO adjust branch if needed
# https://stage1.gradido.net/hooks/github
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_DIR/webhook/hooks.json.template > ~/hooks.json
webhook -hooks ~/hooks.json &
# or for debugging
# webhook -hooks ~/hooks.json -verbose
# create db user
export DB_USER=gradido
export DB_PASSWORD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo);
sudo mysql <<EOFMYSQL
CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASSWORD';
GRANT ALL PRIVILEGES ON *.* TO '$DB_USER'@'localhost';
FLUSH PRIVILEGES;
EOFMYSQL
# Configure database
# TODO - do this in the start.sh to regenerate configs on each deploy
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/database/.env.template > $PROJECT_ROOT/database/.env
# Configure backend
export JWT_SECRET=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo);
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/backend/.env.template > $PROJECT_ROOT/backend/.env
# Configure frontend
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/frontend/.env.template > $PROJECT_ROOT/frontend/.env
# Configure admin
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/admin/.env.template > $PROJECT_ROOT/admin/.env
# Start gradido
# Note: on first startup some errors will occur - nothing serious
./start.sh

View File

View File

@ -0,0 +1,7 @@
$GRADIDO_LOG_PATH/* {
weekly
rotate 26
size 10M
compress
delaycompress
}

View File

@ -0,0 +1,9 @@
#!/bin/bash
# update system
sudo apt-get update
sudo apt-get upgrade
# update pm2
sudo yarn global add pm2
pm2 update

View File

@ -0,0 +1,54 @@
# Deny access to readme.(txt|html) or license.(txt|html) or example.(txt|html) and other common git related files
location ~* "/(^$|readme|license|example|README|LEGALNOTICE|INSTALLATION|CHANGELOG)\.(txt|html|md)" {
deny all;
}
# Deny access to backup extensions & log files
location ~* "\.(old|orig|original|php#|php~|php_bak|save|swo|aspx?|tpl|sh|bash|bak?|cfg|cgi|dll|exe|git|hg|ini|jsp|log|mdb|out|sql|svn|swp|tar|rdf)$" {
deny all;
}
# deny access to hidden files and directories
location ~ /\.(?!well-known\/) {
deny all;
}
# deny access to base64 encoded urls
location ~* "(base64_encode)(.*)(\()" {
deny all;
}
# deny access to url with the javascript eval() function
location ~* "(eval\()" {
deny all;
}
# deny access to url which include \"127.0.0.1\"
location ~* "(127\.0\.0\.1)" {
deny all;
}
location ~* "(GLOBALS|REQUEST)(=|\[|%)" {
deny all;
}
location ~* "(<|%3C).*script.*(>|%3)" {
deny all;
}
location ~ "(\\|\.\.\.|\.\./|~|`|<|>|\|)" {
deny all;
}
location ~* "(\'|\")(.*)(drop|insert|md5|select|union)" {
deny all;
}
location ~* "(https?|ftp|php):/" {
deny all;
}
location ~* "(=\\\'|=\\%27|/\\\'/?)\." {
deny all;
}
location ~ "(\{0\}|\(/\(|\.\.\.|\+\+\+|\\\"\\\")" {
deny all;
}
location ~ "(~|`|<|>|:|;|%|\\|\s|\{|\}|\[|\]|\|)" {
deny all;
}
location ~* "(&pws=0|_vti_|\(null\)|\{\$itemURL\}|echo(.*)kae|boot\.ini|etc/passwd|eval\(|self/environ|(wp-)?config\.|cgi-|muieblack)" {
deny all;
}
location ~* "/(^$|mobiquo|phpinfo|shell|sqlpatch|thumb|thumb_editor|thumbopen|timthumb|webshell|config|configuration)\.php" {
deny all;
}

View File

@ -0,0 +1,9 @@
# Prevent browsers from incorrectly detecting non-scripts as scripts
# https://infosec.mozilla.org/guidelines/web_security#x-content-type-options
add_header X-Content-Type-Options "nosniff";
# prevent clickjacking: https://www.owasp.org/index.php/Clickjacking
# https://geekflare.com/add-x-frame-options-nginx/
# https://infosec.mozilla.org/guidelines/web_security#x-frame-options
add_header Content-Security-Policy "frame-ancestors 'none'";
add_header X-Frame-Options "DENY";

View File

@ -0,0 +1,4 @@
log_format gradido_log '$remote_addr - $remote_user [$time_local] '
'"$request_method $status $request_uri"'
' "$http_referer" "$http_user_agent"'
' $server_protocol $body_bytes_sent $request_time';

View File

@ -1,73 +0,0 @@
server {
server_name _;
listen 80;
listen [::]:80;
include /etc/nginx/common/protect.conf;
include /etc/nginx/common/protect_add_header.conf;
root /var/www/html/gradido/community_server/webroot;
index index.php;
gzip_static on;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}
location ~ /\.ht {
deny all;
}
location /vue {
alias /var/www/html/gradido/frontend/dist;
index index.html;
location ~* \.(png)$ {
expires 39d;
}
try_files $uri $uri/ /index.html = 404;
}
location /account {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
rewrite /account/(.*) /$1 break;
proxy_pass http://127.0.0.1:1200;
proxy_redirect off;
}
location /login_api {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
rewrite /login_api/(.*) /$1 break;
proxy_pass http://127.0.0.1:1201;
proxy_redirect off;
}
location / {
try_files $uri $uri/ /index.php?$args;
}
access_log /var/log/nginx/access.log main;
}

View File

@ -0,0 +1,125 @@
server {
if ($host = $NGINX_SERVER_NAME) {
return 301 https://$host$request_uri;
}
server_name $NGINX_SERVER_NAME;
listen 80;
listen [::]:80;
return 404;
}
server {
server_name $NGINX_SERVER_NAME;
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
ssl_certificate $NGINX_SSL_CERTIFICATE;
ssl_certificate_key $NGINX_SSL_CERTIFICATE_KEY;
include $NGINX_SSL_INCLUDE;
ssl_dhparam $NGINX_SSL_DHPARAM;
include /etc/nginx/common/protect.conf;
include /etc/nginx/common/protect_add_header.conf;
#gzip_static on;
gzip on;
gzip_proxied any;
gzip_types
text/css
text/javascript
text/xml
text/plain
application/javascript
application/x-javascript
application/json;
# Legacy URLS
set $REWRITE_LEGACY_URLS "$NGINX_REWRITE_LEGACY_URLS";
if ($REWRITE_LEGACY_URLS = 'true') {
rewrite ^/vue/?(.*)$ /$1 permanent;
}
# Frontend (default)
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
access_log $GRADIDO_LOG_PATH/nginx-access.frontend.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.frontend.log warn;
}
# Backend
location /graphql {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:4000;
proxy_redirect off;
access_log $GRADIDO_LOG_PATH/nginx-access.backend.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.backend.log warn;
}
# Backend webhooks
location /hook {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:4000/hook;
proxy_redirect off;
access_log $GRADIDO_LOG_PATH/nginx-access.backend.hook.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.backend.hook.log warn;
}
# Webhook reverse proxy
location /hooks/ {
proxy_pass http://127.0.0.1:9000/hooks/;
access_log $GRADIDO_LOG_PATH/nginx-access.hooks.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.hooks.log warn;
}
# Admin Frontend
location /admin {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8080/;
proxy_redirect off;
access_log $GRADIDO_LOG_PATH/nginx-access.admin.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.admin.log warn;
}
# TODO this could be a performance optimization
#location /vue {
# alias /var/www/html/gradido/frontend/dist;
# index index.html;
#
# location ~* \.(png)$ {
# expires 39d;
# }
# try_files $uri $uri/ /index.html = 404;
#}
}

View File

@ -0,0 +1,111 @@
server {
server_name $NGINX_SERVER_NAME;
listen 80;
listen [::]:80;
include /etc/nginx/common/protect.conf;
include /etc/nginx/common/protect_add_header.conf;
#gzip_static on;
gzip on;
gzip_proxied any;
gzip_types
text/css
text/javascript
text/xml
text/plain
application/javascript
application/x-javascript
application/json;
# Legacy URLS
set $REWRITE_LEGACY_URLS "$NGINX_REWRITE_LEGACY_URLS";
if ($REWRITE_LEGACY_URLS = 'true') {
rewrite ^/vue/?(.*)$ /$1 permanent;
}
# Frontend (default)
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
access_log $GRADIDO_LOG_PATH/nginx-access.frontend.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.frontend.log warn;
}
# Backend
location /graphql {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:4000;
proxy_redirect off;
access_log $GRADIDO_LOG_PATH/nginx-access.backend.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.backend.log warn;
}
# Backend webhooks
location /hook {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
# no trailing slash to keep the hook/ prefix
proxy_pass http://127.0.0.1:4000/hook;
proxy_redirect off;
access_log $GRADIDO_LOG_PATH/nginx-access.backend.hook.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.backend.hook.log warn;
}
# Webhook reverse proxy
location /hooks/ {
proxy_pass http://127.0.0.1:9000/hooks/;
access_log $GRADIDO_LOG_PATH/nginx-access.hooks.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.hooks.log warn;
}
# Admin Frontend
location /admin {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8080/;
proxy_redirect off;
access_log $GRADIDO_LOG_PATH/nginx-access.admin.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.admin.log warn;
}
# TODO this could be a performance optimization
#location /vue {
# alias /var/www/html/gradido/frontend/dist;
# index index.html;
#
# location ~* \.(png)$ {
# expires 39d;
# }
# try_files $uri $uri/ /index.html = 404;
#}
}

View File

@ -1,29 +0,0 @@
server {
server_name _;
listen 80;
listen [::]:80;
include /etc/nginx/common/protect.conf;
include /etc/nginx/common/protect_add_header.conf;
root /var/www/html/;
index updating.html;
location /account {
alias /var/www/html/;
index updating.html;
}
location /vue {
alias /var/www/html/;
index updating.html;
}
location ~ /\.ht {
deny all;
}
access_log /var/log/nginx/access.log main;
}

View File

@ -0,0 +1,37 @@
server {
if ($host = $NGINX_SERVER_NAME) {
return 301 https://$host$request_uri;
}
server_name $NGINX_SERVER_NAME;
listen 80;
listen [::]:80;
return 404;
}
server {
server_name $NGINX_SERVER_NAME;
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
ssl_certificate $NGINX_SSL_CERTIFICATE;
ssl_certificate_key $NGINX_SSL_CERTIFICATE_KEY;
include $NGINX_SSL_INCLUDE;
ssl_dhparam $NGINX_SSL_DHPARAM;
include /etc/nginx/common/protect.conf;
include /etc/nginx/common/protect_add_header.conf;
gzip on;
root $NGINX_UPDATE_PAGE_ROOT;
index updating.html;
location / {
try_files /updating.html =404;
}
access_log $GRADIDO_LOG_PATH/nginx-access.update-page.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.update-page.log warn;
}

View File

@ -0,0 +1,22 @@
server {
server_name _;
listen 80;
listen [::]:80;
include /etc/nginx/common/protect.conf;
include /etc/nginx/common/protect_add_header.conf;
gzip on;
root $NGINX_UPDATE_PAGE_ROOT;
index updating.html;
location / {
try_files /updating.html =404;
}
access_log $GRADIDO_LOG_PATH/nginx-access.update-page.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.update-page.log warn;
}

View File

@ -0,0 +1,3 @@
Gradido is currently updating...<br>
please stand by and try again in some minutes<br>
<br>

View File

@ -1 +0,0 @@
Gradido Servers are updating..., please stand by and try again in some minutes

View File

@ -0,0 +1,70 @@
# phpmyadmin
echo "install and secure phpmyadmin"
sudo apt install phpmyadmin
cd /etc/phpmyadmin/conf.d
sudo cat <<EOF > pma_secure.php
<?php
# PhpMyAdmin Settings
# This should be set to a random string of at least 32 chars
$cfg['blowfish_secret'] = '3!#32@3sa(+=_4?),5XP_:U%%8\34sdfSdg43yH#{o';
$i=0;
$i++;
$cfg['Servers'][$i]['auth_type'] = 'cookie';
$cfg['Servers'][$i]['AllowNoPassword'] = false;
$cfg['Servers'][$i]['AllowRoot'] = false;
?>
EOF
phpmyadminPwd = $(openssl passwd)
echo "Please give a username for phpmyadmin, but not root"
read phpmyadmin_user
# TODO: check if phpmyadmin_user isn't really root
sudo cat <<EOF > /etc/nginx/pma_pass
$phpmyadmin_user:$phpmyadminPwd
EOF
serverIP = $(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1')
sudo cat <<EOF > /etc/nginx/sites-available/phpmyadmin
server {
listen 80 ;
listen [::]:80;
server_name $serverIP;
location ~* \.(png|jpg|ico)$ {
expires 30d;
}
location ~* \.(js|css) {
expires 30d;
}
location /phpmyadmin {
root /usr/share/phpmyadmin
index index.php;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}
location / {
try_files $uri $uri/ /index.php?$args;
}
}
location ~ /\.ht {
deny all;
}
access_log /var/log/nginx/access.log main;
}
EOF
sudo ln -s /etc/nginx/sites-available/phpmyadmin /etc/nginx/sites-enabled

View File

@ -0,0 +1,42 @@
#!/bin/bash
# This script will shut down all services, replace the whole database with the selected backup and restart the services
# Find current directory & configure paths
set -o allexport
SCRIPT_PATH=$(realpath $0)
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
PROJECT_ROOT=$SCRIPT_DIR/../..
set +o allexport
# Parameter is a proper file?
export BACKUP_FILE=${SCRIPT_DIR}/backup/$1
if [ ! -f "$BACKUP_FILE" ]; then
return "File '$BACKUP_FILE' does not exist" 2>/dev/null || exit 1
fi
# Load backend .env for DB_USERNAME, DB_PASSWORD & DB_DATABASE
# NOTE: all config values will be in process.env when starting
# the services and will therefore take precedence over the .env
if [ -f "$PROJECT_ROOT/backend/.env" ]; then
export $(cat $PROJECT_ROOT/backend/.env | sed 's/#.*//g' | xargs)
else
export $(cat $PROJECT_ROOT/backend/.env.dist | sed 's/#.*//g' | xargs)
fi
# Stop gradido-backend service
pm2 stop gradido-backend
# Backup data
mysqldump --databases --single-transaction --quick --lock-tables=false > ${SCRIPT_DIR}/backup/mariadb-restore-backup-$(date +%d-%m-%Y_%H-%M-%S).sql -u ${DB_USER} -p${DB_PASSWORD} ${DB_DATABASE}
# Restore Data
mysql -u ${DB_USER} -p${DB_PASSWORD} <<EOFMYSQL
source $BACKUP_FILE
EOFMYSQL
# Update database if needed (use dev_up for seeding setups)
yarn --cwd $PROJECT_ROOT/database up
# Start gradido-backend service
pm2 start gradido-backend

Some files were not shown because too many files have changed in this diff Show More