Merge branch 'master' into refactor-find-contribtiions

This commit is contained in:
Moriz Wahl 2023-03-21 14:37:07 +01:00
commit b27db4eeaf
58 changed files with 1024 additions and 684 deletions

43
.github/file-filters.yml vendored Normal file
View File

@ -0,0 +1,43 @@
# These file filter patterns are used by the action https://github.com/dorny/paths-filter
# more differentiated filters for admin interface, which might be used later
# admin_locales: &admin_locales
# - 'admin/src/locales/**'
# - 'admin/scripts/sort*'
# admin_stylelinting: &admin_stylelinting
# - 'admin/{components,layouts,pages}/**/*.{scss,vue}'
# - 'admin/.stylelintrc.js'
# admin_linting: &admin_linting
# - 'admin/.eslint*'
# - 'admin/babel.config.js'
# - 'admin/package.json'
# - 'admin/**/*.{js,vue}'
# - *admin_locales
# admin_unit_testing: &admin_unit_testing
# - 'admin/package.json'
# - 'admin/{jest,vue}.config.js'
# - 'admin/{public,run,test}/**/*'
# - 'admin/src/!(locales)/**/*'
# admin_docker_building: &admin_docker_building
# - 'admin/.dockerignore'
# - 'admin/Dockerfile'
# - *admin_unit_testing
admin: &admin
- 'admin/**/*'
dht_node: &dht_node
- 'dht-node/**/*'
docker: &docker
- 'docker-compose.*'
federation: &federation
- 'federation/**/*'
frontend: &frontend
- 'frontend/**/*'

58
.github/workflows/e2e-test.yml vendored Normal file
View File

@ -0,0 +1,58 @@
name: Gradido End-to-End Test CI
on: push
jobs:
end-to-end-tests:
name: End-to-End Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Boot up test system | docker-compose mariadb
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach mariadb
- name: Boot up test system | docker-compose database
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database
- name: Boot up test system | docker-compose backend
run: |
cd backend
cp .env.test_e2e .env
cd ..
docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend
- name: Sleep for 10 seconds
run: sleep 10s
- name: Boot up test system | seed backend
run: |
sudo chown runner:docker -R *
cd database
yarn && yarn dev_reset
cd ../backend
yarn && yarn seed
cd ..
- name: Boot up test system | docker-compose frontends
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps frontend admin nginx
- name: Boot up test system | docker-compose mailserver
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mailserver
- name: Sleep for 15 seconds
run: sleep 15s
- name: End-to-end tests | run tests
id: e2e-tests
run: |
cd e2e-tests/
yarn
yarn run cypress run
- name: End-to-end tests | if tests failed, upload screenshots
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
uses: actions/upload-artifact@v3
with:
name: cypress-screenshots
path: /home/runner/work/gradido/gradido/e2e-tests/cypress/screenshots/

View File

@ -0,0 +1,84 @@
name: Gradido Admin Interface Test CI
on: push
jobs:
# only (but most important) job from this workflow required for pull requests
# check results serve as run conditions for all other jobs here
files-changed:
name: Detect File Changes - Admin Interface
runs-on: ubuntu-latest
outputs:
admin: ${{ steps.changes.outputs.admin }}
steps:
- uses: actions/checkout@v3.3.0
- name: Check for admin interface file changes
uses: dorny/paths-filter@v2.11.1
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
list-files: shell
build_test:
if: needs.files-changed.outputs.admin == 'true'
name: Docker Build Test - Admin Interface
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Admin Interface | Build 'test' image
run: docker build --target test -t "gradido/admin:test" admin/ --build-arg NODE_ENV="test"
unit_test:
if: needs.files-changed.outputs.admin == 'true'
name: Unit Tests - Admin Interface
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Admin Interface | Unit tests
run: cd admin && yarn && yarn run test
lint:
if: needs.files-changed.outputs.admin == 'true'
name: Lint - Admin Interface
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Admin Interface | Lint
run: cd admin && yarn && yarn run lint
stylelint:
if: needs.files-changed.outputs.admin == 'true'
name: Stylelint - Admin Interface
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Admin Interface | Stylelint
run: cd admin && yarn && yarn run stylelint
locales:
if: needs.files-changed.outputs.admin == 'true'
name: Locales - Admin Interface
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Admin Interface | Locales
run: cd admin && yarn && yarn run locales

View File

@ -3,57 +3,6 @@ name: gradido test CI
on: push on: push
jobs: jobs:
##############################################################################
# JOB: DOCKER BUILD TEST FRONTEND ############################################
##############################################################################
build_test_frontend:
name: Docker Build Test - Frontend
runs-on: ubuntu-latest
#needs: [nothing]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# FRONTEND ###############################################################
##########################################################################
- name: Frontend | Build `test` image
run: |
docker build --target test -t "gradido/frontend:test" frontend/
docker save "gradido/frontend:test" > /tmp/frontend.tar
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: docker-frontend-test
path: /tmp/frontend.tar
##############################################################################
# JOB: DOCKER BUILD TEST ADMIN INTERFACE #####################################
##############################################################################
build_test_admin:
name: Docker Build Test - Admin Interface
runs-on: ubuntu-latest
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# ADMIN INTERFACE ########################################################
##########################################################################
- name: Admin | Build `test` image
run: |
docker build --target test -t "gradido/admin:test" admin/ --build-arg NODE_ENV="test"
docker save "gradido/admin:test" > /tmp/admin.tar
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: docker-admin-test
path: /tmp/admin.tar
############################################################################## ##############################################################################
# JOB: DOCKER BUILD TEST BACKEND ############################################# # JOB: DOCKER BUILD TEST BACKEND #############################################
############################################################################## ##############################################################################
@ -157,114 +106,6 @@ jobs:
name: docker-nginx-test name: docker-nginx-test
path: /tmp/nginx.tar path: /tmp/nginx.tar
##############################################################################
# JOB: LOCALES FRONTEND ######################################################
##############################################################################
locales_frontend:
name: Locales - Frontend
runs-on: ubuntu-latest
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# LOCALES FRONTEND #######################################################
##########################################################################
- name: Frontend | Locales
run: cd frontend && yarn && yarn run locales
##############################################################################
# JOB: LINT FRONTEND #########################################################
##############################################################################
lint_frontend:
name: Lint - Frontend
runs-on: ubuntu-latest
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# LINT FRONTEND ##########################################################
##########################################################################
- name: Frontend | Lint
run: cd frontend && yarn && yarn run lint
##############################################################################
# JOB: STYLELINT FRONTEND ####################################################
##############################################################################
stylelint_frontend:
name: Stylelint - Frontend
runs-on: ubuntu-latest
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# STYLELINT FRONTEND #####################################################
##########################################################################
- name: Frontend | Stylelint
run: cd frontend && yarn && yarn run stylelint
##############################################################################
# JOB: LINT ADMIN INTERFACE ##################################################
##############################################################################
lint_admin:
name: Lint - Admin Interface
runs-on: ubuntu-latest
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# LINT ADMIN INTERFACE ###################################################
##########################################################################
- name: Admin Interface | Lint
run: cd admin && yarn && yarn run lint
##############################################################################
# JOB: STYLELINT ADMIN INTERFACE #############################################
##############################################################################
stylelint_admin:
name: Stylelint - Admin Interface
runs-on: ubuntu-latest
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# STYLELINT ADMIN INTERFACE ##############################################
##########################################################################
- name: Admin Interface | Stylelint
run: cd admin && yarn && yarn run stylelint
##############################################################################
# JOB: LOCALES ADMIN #########################################################
##############################################################################
locales_admin:
name: Locales - Admin Interface
runs-on: ubuntu-latest
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# LOCALES FRONTEND #######################################################
##########################################################################
- name: Admin | Locales
run: cd admin && yarn && yarn run locales
############################################################################## ##############################################################################
# JOB: LINT BACKEND ########################################################## # JOB: LINT BACKEND ##########################################################
############################################################################## ##############################################################################
@ -319,68 +160,6 @@ jobs:
- name: Database | Lint - name: Database | Lint
run: cd database && yarn && yarn run lint run: cd database && yarn && yarn run lint
##############################################################################
# JOB: UNIT TEST FRONTEND ###################################################
##############################################################################
unit_test_frontend:
name: Unit tests - Frontend
runs-on: ubuntu-latest
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# UNIT TESTS FRONTEND ####################################################
##########################################################################
- name: Frontend | Unit tests
run: |
cd frontend && yarn && yarn run test
cp -r ./coverage ../
##########################################################################
# COVERAGE CHECK FRONTEND ################################################
##########################################################################
- name: frontend | Coverage check
uses: webcraftmedia/coverage-check-action@master
with:
report_name: Coverage Frontend
type: lcov
result_path: ./frontend/coverage/lcov.info
min_coverage: 95
token: ${{ github.token }}
##############################################################################
# JOB: UNIT TEST ADMIN INTERFACE #############################################
##############################################################################
unit_test_admin:
name: Unit tests - Admin Interface
runs-on: ubuntu-latest
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# UNIT TESTS ADMIN INTERFACE #############################################
##########################################################################
- name: Admin Interface | Unit tests
run: |
cd admin && yarn && yarn run test
cp -r ./coverage ../
##########################################################################
# COVERAGE CHECK ADMIN INTERFACE #########################################
##########################################################################
- name: Admin Interface | Coverage check
uses: webcraftmedia/coverage-check-action@master
with:
report_name: Coverage Admin Interface
type: lcov
result_path: ./admin/coverage/lcov.info
min_coverage: 97
token: ${{ github.token }}
############################################################################## ##############################################################################
# JOB: UNIT TEST BACKEND #################################################### # JOB: UNIT TEST BACKEND ####################################################
############################################################################## ##############################################################################
@ -415,20 +194,7 @@ jobs:
- name: backend | docker-compose database - name: backend | docker-compose database
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database
- name: backend Unit tests | test - name: backend Unit tests | test
run: | run: cd database && yarn && yarn build && cd ../backend && yarn && yarn test
cd database && yarn && yarn build && cd ../backend && yarn && yarn test
cp -r ./coverage ../
##########################################################################
# COVERAGE CHECK BACKEND #################################################
##########################################################################
- name: backend | Coverage check
uses: webcraftmedia/coverage-check-action@master
with:
report_name: Coverage Backend
type: lcov
result_path: ./backend/coverage/lcov.info
min_coverage: 81
token: ${{ github.token }}
########################################################################## ##########################################################################
# DATABASE MIGRATION TEST UP + RESET ##################################### # DATABASE MIGRATION TEST UP + RESET #####################################
@ -452,108 +218,3 @@ jobs:
run: docker-compose -f docker-compose.yml run -T database yarn up run: docker-compose -f docker-compose.yml run -T database yarn up
- name: database | reset - name: database | reset
run: docker-compose -f docker-compose.yml run -T database yarn reset run: docker-compose -f docker-compose.yml run -T database yarn reset
##############################################################################
# JOB: END-TO-END TESTS #####################################################
##############################################################################
end-to-end-tests:
name: End-to-End Tests
runs-on: ubuntu-latest
needs: [build_test_mariadb, build_test_database_up, build_test_admin, build_test_frontend, build_test_nginx]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# DOWNLOAD DOCKER IMAGES #################################################
##########################################################################
- name: Download Docker Image (Mariadb)
uses: actions/download-artifact@v3
with:
name: docker-mariadb-test
path: /tmp
- name: Load Docker Image (Mariadb)
run: docker load < /tmp/mariadb.tar
- name: Download Docker Image (Database Up)
uses: actions/download-artifact@v3
with:
name: docker-database-test_up
path: /tmp
- name: Load Docker Image (Database Up)
run: docker load < /tmp/database_up.tar
- name: Download Docker Image (Frontend)
uses: actions/download-artifact@v3
with:
name: docker-frontend-test
path: /tmp
- name: Load Docker Image (Frontend)
run: docker load < /tmp/frontend.tar
- name: Download Docker Image (Admin Interface)
uses: actions/download-artifact@v3
with:
name: docker-admin-test
path: /tmp
- name: Load Docker Image (Admin Interface)
run: docker load < /tmp/admin.tar
- name: Download Docker Image (Nginx)
uses: actions/download-artifact@v3
with:
name: docker-nginx-test
path: /tmp
- name: Load Docker Image (Nginx)
run: docker load < /tmp/nginx.tar
##########################################################################
# BOOT UP THE TEST SYSTEM ################################################
##########################################################################
- name: Boot up test system | docker-compose mariadb
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach mariadb
- name: Boot up test system | docker-compose database
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database
- name: Boot up test system | docker-compose backend
run: |
cd backend
cp .env.test_e2e .env
cd ..
docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps backend
- name: Sleep for 10 seconds
run: sleep 10s
- name: Boot up test system | seed backend
run: |
sudo chown runner:docker -R *
cd database
yarn && yarn dev_reset
cd ../backend
yarn && yarn seed
cd ..
- name: Boot up test system | docker-compose frontends
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps frontend admin nginx
- name: Boot up test system | docker-compose mailserver
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mailserver
- name: Sleep for 15 seconds
run: sleep 15s
##########################################################################
# END-TO-END TESTS #######################################################
##########################################################################
- name: End-to-end tests | run tests
id: e2e-tests
run: |
cd e2e-tests/
yarn
yarn run cypress run --spec cypress/e2e/User.Authentication.feature,cypress/e2e/User.Authentication.ResetPassword.feature,cypress/e2e/User.Registration.feature
- name: End-to-end tests | if tests failed, upload screenshots
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
uses: actions/upload-artifact@v3
with:
name: cypress-screenshots
path: /home/runner/work/gradido/gradido/e2e-tests/cypress/screenshots/

View File

@ -3,17 +3,38 @@ name: Gradido DHT Node Test CI
on: push on: push
jobs: jobs:
# only (but most important) job from this workflow required for pull requests
# check results serve as run conditions for all other jobs here
files-changed:
name: Detect File Changes - DHT Node
runs-on: ubuntu-latest
outputs:
dht_node: ${{ steps.changes.outputs.dht_node }}
docker: ${{ steps.changes.outputs.docker }}
steps:
- uses: actions/checkout@v3.3.0
- name: Check for frontend file changes
uses: dorny/paths-filter@v2.11.1
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
list-files: shell
############################################################################## ##############################################################################
# JOB: DOCKER BUILD TEST ##################################################### # JOB: DOCKER BUILD TEST #####################################################
############################################################################## ##############################################################################
build: build:
name: Docker Build Test - DHT Node name: Docker Build Test - DHT Node
if: needs.files-changed.outputs.dht_node == 'true' || needs.files-changed.outputs.docker == 'true'
needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Build `test` image - name: Build 'test' image
run: | run: |
docker build --target test -t "gradido/dht-node:test" -f dht-node/Dockerfile . docker build --target test -t "gradido/dht-node:test" -f dht-node/Dockerfile .
docker save "gradido/dht-node:test" > /tmp/dht-node.tar docker save "gradido/dht-node:test" > /tmp/dht-node.tar
@ -29,30 +50,24 @@ jobs:
############################################################################## ##############################################################################
lint: lint:
name: Lint - DHT Node name: Lint - DHT Node
if: needs.files-changed.outputs.dht_node == 'true'
needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [build]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Download Docker Image
uses: actions/download-artifact@v3
with:
name: docker-dht-node-test
path: /tmp
- name: Load Docker Image
run: docker load < /tmp/dht-node.tar
- name: Lint - name: Lint
run: docker run --rm gradido/dht-node:test yarn run lint run: cd dht-node && yarn && yarn run lint
############################################################################## ##############################################################################
# JOB: UNIT TEST ############################################################# # JOB: UNIT TEST #############################################################
############################################################################## ##############################################################################
unit_test: unit_test:
name: Unit Tests - DHT Node name: Unit Tests - DHT Node
if: needs.files-changed.outputs.dht_node == 'true' || needs.files-changed.outputs.docker == 'true'
needs: [files-changed, build]
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [build]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -83,16 +98,4 @@ jobs:
#- name: Unit tests #- name: Unit tests
# run: cd database && yarn && yarn build && cd ../dht-node && yarn && yarn test # run: cd database && yarn && yarn build && cd ../dht-node && yarn && yarn test
- name: Unit tests - name: Unit tests
run: | run: docker run --env NODE_ENV=test --env DB_HOST=mariadb --network gradido_internal-net --rm gradido/dht-node:test yarn run test
docker run --env NODE_ENV=test --env DB_HOST=mariadb --network gradido_internal-net -v ~/coverage:/app/coverage --rm gradido/dht-node:test yarn run test
cp -r ~/coverage ./coverage
- name: Coverage check
uses: webcraftmedia/coverage-check-action@master
with:
report_name: Coverage DHT Node
type: lcov
#result_path: ./dht-node/coverage/lcov.info
result_path: ./coverage/lcov.info
min_coverage: 79
token: ${{ github.token }}

View File

@ -3,11 +3,32 @@ name: Gradido Federation Test CI
on: push on: push
jobs: jobs:
# only (but most important) job from this workflow required for pull requests
# check results serve as run conditions for all other jobs here
files-changed:
name: Detect File Changes - Federation
runs-on: ubuntu-latest
outputs:
docker: ${{ steps.changes.outputs.docker }}
federation: ${{ steps.changes.outputs.federation }}
steps:
- uses: actions/checkout@v3.3.0
- name: Check for frontend file changes
uses: dorny/paths-filter@v2.11.1
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
list-files: shell
############################################################################## ##############################################################################
# JOB: DOCKER BUILD TEST ##################################################### # JOB: DOCKER BUILD TEST #####################################################
############################################################################## ##############################################################################
build: build:
name: Docker Build Test - Federation name: Docker Build Test - Federation
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.federation == 'true'
needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@ -29,30 +50,24 @@ jobs:
############################################################################## ##############################################################################
lint: lint:
name: Lint - Federation name: Lint - Federation
if: needs.files-changed.outputs.federation == 'true'
needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [build]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Download Docker Image
uses: actions/download-artifact@v3
with:
name: docker-federation-test
path: /tmp
- name: Load Docker Image
run: docker load < /tmp/federation.tar
- name: Lint - name: Lint
run: docker run --rm gradido/federation:test yarn run lint run: cd federation && yarn && yarn run lint
############################################################################## ##############################################################################
# JOB: UNIT TEST ############################################################# # JOB: UNIT TEST #############################################################
############################################################################## ##############################################################################
unit_test: unit_test:
name: Unit Tests - Federation name: Unit Tests - Federation
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.federation == 'true'
needs: [files-changed, build]
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [build]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -84,15 +99,4 @@ jobs:
# run: cd database && yarn && yarn build && cd ../dht-node && yarn && yarn test # run: cd database && yarn && yarn build && cd ../dht-node && yarn && yarn test
- name: Unit tests - name: Unit tests
run: | run: |
docker run --env NODE_ENV=test --env DB_HOST=mariadb --network gradido_internal-net -v ~/coverage:/app/coverage --rm gradido/federation:test yarn run test docker run --env NODE_ENV=test --env DB_HOST=mariadb --network gradido_internal-net --rm gradido/federation:test yarn run test
cp -r ~/coverage ./coverage
- name: Coverage check
uses: webcraftmedia/coverage-check-action@master
with:
report_name: Coverage Federation
type: lcov
#result_path: ./federation/coverage/lcov.info
result_path: ./coverage/lcov.info
min_coverage: 72
token: ${{ github.token }}

84
.github/workflows/test_frontend.yml vendored Normal file
View File

@ -0,0 +1,84 @@
name: Gradido Frontend Test CI
on: push
jobs:
# only (but most important) job from this workflow required for pull requests
# check results serve as run conditions for all other jobs here
files-changed:
name: Detect File Changes - Frontend
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.changes.outputs.frontend }}
steps:
- uses: actions/checkout@v3.3.0
- name: Check for frontend file changes
uses: dorny/paths-filter@v2.11.1
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
list-files: shell
build_test:
if: needs.files-changed.outputs.frontend == 'true'
name: Docker Build Test - Frontend
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Frontend | Build 'test' image
run: docker build --target test -t "gradido/frontend:test" frontend/ --build-arg NODE_ENV="test"
unit_test:
if: needs.files-changed.outputs.frontend == 'true'
name: Unit Tests - Frontend
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Frontend | Unit tests
run: cd frontend && yarn && yarn run test
lint:
if: needs.files-changed.outputs.frontend == 'true'
name: Lint - Frontend
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Frontend | Lint
run: cd frontend && yarn && yarn run lint
stylelint:
if: needs.files-changed.outputs.frontend == 'true'
name: Stylelint - Frontend
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Frontend | Stylelint
run: cd frontend && yarn && yarn run stylelint
locales:
if: needs.files-changed.outputs.frontend == 'true'
name: Locales - Frontend
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Frontend | Locales
run: cd frontend && yarn && yarn run locales

View File

@ -1,11 +1,17 @@
module.exports = { module.exports = {
verbose: true, verbose: true,
collectCoverage: true,
collectCoverageFrom: [ collectCoverageFrom: [
'src/**/*.{js,vue}', 'src/**/*.{js,vue}',
'!**/node_modules/**', '!**/node_modules/**',
'!src/assets/**', '!src/assets/**',
'!**/?(*.)+(spec|test).js?(x)', '!**/?(*.)+(spec|test).js?(x)',
], ],
coverageThreshold: {
global: {
lines: 97,
},
},
moduleFileExtensions: [ moduleFileExtensions: [
'js', 'js',
// 'jsx', // 'jsx',

View File

@ -14,7 +14,7 @@
"analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json", "analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json",
"lint": "eslint --max-warnings=0 --ext .js,.vue,.json .", "lint": "eslint --max-warnings=0 --ext .js,.vue,.json .",
"stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'", "stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'",
"test": "cross-env TZ=UTC jest --coverage", "test": "cross-env TZ=UTC jest",
"locales": "scripts/sort.sh" "locales": "scripts/sort.sh"
}, },
"dependencies": { "dependencies": {

View File

@ -39,7 +39,7 @@
}, },
"created": "Created for", "created": "Created for",
"createdAt": "Created at", "createdAt": "Created at",
"creation": "Amount", "creation": "Creation",
"creationList": "Creation list", "creationList": "Creation list",
"creation_form": { "creation_form": {
"creation_for": "Active Basic Income for", "creation_for": "Active Basic Income for",

View File

@ -1,5 +1,5 @@
# Server # Server
JWT_EXPIRES_IN=1m JWT_EXPIRES_IN=2m
# Email # Email
EMAIL=true EMAIL=true

View File

@ -4,6 +4,11 @@ module.exports = {
preset: 'ts-jest', preset: 'ts-jest',
collectCoverage: true, collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
coverageThreshold: {
global: {
lines: 80,
},
},
setupFiles: ['<rootDir>/test/testSetup.ts'], setupFiles: ['<rootDir>/test/testSetup.ts'],
setupFilesAfterEnv: ['<rootDir>/test/extensions.ts'], setupFilesAfterEnv: ['<rootDir>/test/extensions.ts'],
modulePathIgnorePatterns: ['<rootDir>/build/'], modulePathIgnorePatterns: ['<rootDir>/build/'],

View File

@ -13,7 +13,7 @@
"start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js", "start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js",
"dev": "cross-env TZ=UTC nodemon -w src --ext ts --exec ts-node -r tsconfig-paths/register src/index.ts", "dev": "cross-env TZ=UTC nodemon -w src --ext ts --exec ts-node -r tsconfig-paths/register src/index.ts",
"lint": "eslint --max-warnings=0 --ext .js,.ts .", "lint": "eslint --max-warnings=0 --ext .js,.ts .",
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles", "test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --forceExit --detectOpenHandles",
"seed": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/seeds/index.ts", "seed": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/seeds/index.ts",
"klicktipp": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/util/klicktipp.ts", "klicktipp": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/util/klicktipp.ts",
"locales": "scripts/sort.sh" "locales": "scripts/sort.sh"

View File

@ -10,7 +10,7 @@ Decimal.set({
}) })
const constants = { const constants = {
DB_VERSION: '0060-update_communities_table', DB_VERSION: '0061-event_refactoring',
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
LOG4JS_CONFIG: 'log4js-config.json', LOG4JS_CONFIG: 'log4js-config.json',
// default log level on production should be info // default log level on production should be info

View File

@ -1,212 +1,217 @@
import { EventProtocol as DbEvent } from '@entity/EventProtocol' import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage'
import { Contribution as DbContribution } from '@entity/Contribution'
import { Transaction as DbTransaction } from '@entity/Transaction'
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
import { EventProtocolType } from './EventProtocolType' import { EventProtocolType } from './EventProtocolType'
export const Event = ( export const Event = (
type: EventProtocolType, type: EventProtocolType,
userId: number, affectedUser: DbUser,
xUserId: number | null = null, actingUser: DbUser,
xCommunityId: number | null = null, involvedUser: DbUser | null = null,
transactionId: number | null = null, involvedTransaction: DbTransaction | null = null,
contributionId: number | null = null, involvedContribution: DbContribution | null = null,
involvedContributionMessage: DbContributionMessage | null = null,
amount: Decimal | null = null, amount: Decimal | null = null,
messageId: number | null = null,
): DbEvent => { ): DbEvent => {
const event = new DbEvent() const event = new DbEvent()
event.type = type event.type = type
event.userId = userId event.affectedUser = affectedUser
event.xUserId = xUserId event.actingUser = actingUser
event.xCommunityId = xCommunityId event.involvedUser = involvedUser
event.transactionId = transactionId event.involvedTransaction = involvedTransaction
event.contributionId = contributionId event.involvedContribution = involvedContribution
event.involvedContributionMessage = involvedContributionMessage
event.amount = amount event.amount = amount
event.messageId = messageId
return event return event
} }
export const EVENT_CONTRIBUTION_CREATE = async ( export const EVENT_CONTRIBUTION_CREATE = async (
userId: number, user: DbUser,
contributionId: number, contribution: DbContribution,
amount: Decimal, amount: Decimal,
): Promise<DbEvent> => ): Promise<DbEvent> =>
Event( Event(
EventProtocolType.CONTRIBUTION_CREATE, EventProtocolType.CONTRIBUTION_CREATE,
userId, user,
user,
null, null,
null, null,
contribution,
null, null,
contributionId,
amount, amount,
).save() ).save()
export const EVENT_CONTRIBUTION_DELETE = async ( export const EVENT_CONTRIBUTION_DELETE = async (
userId: number, user: DbUser,
contributionId: number, contribution: DbContribution,
amount: Decimal, amount: Decimal,
): Promise<DbEvent> => ): Promise<DbEvent> =>
Event( Event(
EventProtocolType.CONTRIBUTION_DELETE, EventProtocolType.CONTRIBUTION_DELETE,
userId, user,
user,
null, null,
null, null,
contribution,
null, null,
contributionId,
amount, amount,
).save() ).save()
export const EVENT_CONTRIBUTION_UPDATE = async ( export const EVENT_CONTRIBUTION_UPDATE = async (
userId: number, user: DbUser,
contributionId: number, contribution: DbContribution,
amount: Decimal, amount: Decimal,
): Promise<DbEvent> => ): Promise<DbEvent> =>
Event( Event(
EventProtocolType.CONTRIBUTION_UPDATE, EventProtocolType.CONTRIBUTION_UPDATE,
userId, user,
user,
null, null,
null, null,
contribution,
null, null,
contributionId,
amount, amount,
).save() ).save()
export const EVENT_ADMIN_CONTRIBUTION_CREATE = async ( export const EVENT_ADMIN_CONTRIBUTION_CREATE = async (
userId: number, user: DbUser,
contributionId: number, moderator: DbUser,
contribution: DbContribution,
amount: Decimal, amount: Decimal,
): Promise<DbEvent> => ): Promise<DbEvent> =>
Event( Event(
EventProtocolType.ADMIN_CONTRIBUTION_CREATE, EventProtocolType.ADMIN_CONTRIBUTION_CREATE,
userId, user,
moderator,
null, null,
null, null,
contribution,
null, null,
contributionId,
amount, amount,
).save() ).save()
export const EVENT_ADMIN_CONTRIBUTION_UPDATE = async ( export const EVENT_ADMIN_CONTRIBUTION_UPDATE = async (
userId: number, user: DbUser,
contributionId: number, moderator: DbUser,
contribution: DbContribution,
amount: Decimal, amount: Decimal,
): Promise<DbEvent> => ): Promise<DbEvent> =>
Event( Event(
EventProtocolType.ADMIN_CONTRIBUTION_UPDATE, EventProtocolType.ADMIN_CONTRIBUTION_UPDATE,
userId, user,
moderator,
null, null,
null, null,
contribution,
null, null,
contributionId,
amount, amount,
).save() ).save()
export const EVENT_ADMIN_CONTRIBUTION_DELETE = async ( export const EVENT_ADMIN_CONTRIBUTION_DELETE = async (
userId: number, user: DbUser,
contributionId: number, moderator: DbUser,
contribution: DbContribution,
amount: Decimal, amount: Decimal,
): Promise<DbEvent> => ): Promise<DbEvent> =>
Event( Event(
EventProtocolType.ADMIN_CONTRIBUTION_DELETE, EventProtocolType.ADMIN_CONTRIBUTION_DELETE,
userId, user,
moderator,
null, null,
null, null,
contribution,
null, null,
contributionId,
amount, amount,
).save() ).save()
export const EVENT_CONTRIBUTION_CONFIRM = async ( export const EVENT_CONTRIBUTION_CONFIRM = async (
userId: number, user: DbUser,
contributionId: number, moderator: DbUser,
contribution: DbContribution,
amount: Decimal, amount: Decimal,
): Promise<DbEvent> => ): Promise<DbEvent> =>
Event( Event(
EventProtocolType.CONTRIBUTION_CONFIRM, EventProtocolType.CONTRIBUTION_CONFIRM,
userId, user,
moderator,
null, null,
null, null,
contribution,
null, null,
contributionId,
amount, amount,
).save() ).save()
export const EVENT_ADMIN_CONTRIBUTION_DENY = async ( export const EVENT_ADMIN_CONTRIBUTION_DENY = async (
userId: number, user: DbUser,
xUserId: number, moderator: DbUser,
contributionId: number, contribution: DbContribution,
amount: Decimal, amount: Decimal,
): Promise<DbEvent> => ): Promise<DbEvent> =>
Event( Event(
EventProtocolType.ADMIN_CONTRIBUTION_DENY, EventProtocolType.ADMIN_CONTRIBUTION_DENY,
userId, user,
xUserId, moderator,
null, null,
null, null,
contributionId, contribution,
null,
amount, amount,
).save() ).save()
export const EVENT_TRANSACTION_SEND = async ( export const EVENT_TRANSACTION_SEND = async (
userId: number, user: DbUser,
xUserId: number, involvedUser: DbUser,
transactionId: number, transaction: DbTransaction,
amount: Decimal, amount: Decimal,
): Promise<DbEvent> => ): Promise<DbEvent> =>
Event( Event(
EventProtocolType.TRANSACTION_SEND, EventProtocolType.TRANSACTION_SEND,
userId, user,
xUserId, user,
involvedUser,
transaction,
null, null,
transactionId,
null, null,
amount, amount,
).save() ).save()
export const EVENT_TRANSACTION_RECEIVE = async ( export const EVENT_TRANSACTION_RECEIVE = async (
userId: number, user: DbUser,
xUserId: number, involvedUser: DbUser,
transactionId: number, transaction: DbTransaction,
amount: Decimal, amount: Decimal,
): Promise<DbEvent> => ): Promise<DbEvent> =>
Event( Event(
EventProtocolType.TRANSACTION_RECEIVE, EventProtocolType.TRANSACTION_RECEIVE,
userId, user,
xUserId, involvedUser,
involvedUser,
transaction,
null, null,
transactionId,
null, null,
amount, amount,
).save() ).save()
export const EVENT_LOGIN = async (userId: number): Promise<DbEvent> => export const EVENT_LOGIN = async (user: DbUser): Promise<DbEvent> =>
Event(EventProtocolType.LOGIN, userId, null, null, null, null, null, null).save() Event(EventProtocolType.LOGIN, user, user).save()
export const EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL = async ( export const EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL = async (user: DbUser): Promise<DbEvent> =>
userId: number, Event(EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL, user, { id: 0 } as DbUser).save()
): Promise<DbEvent> => Event(EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL, userId).save()
export const EVENT_SEND_CONFIRMATION_EMAIL = async (userId: number): Promise<DbEvent> => export const EVENT_SEND_CONFIRMATION_EMAIL = async (user: DbUser): Promise<DbEvent> =>
Event(EventProtocolType.SEND_CONFIRMATION_EMAIL, userId).save() Event(EventProtocolType.SEND_CONFIRMATION_EMAIL, user, user).save()
export const EVENT_ADMIN_SEND_CONFIRMATION_EMAIL = async (userId: number): Promise<DbEvent> => export const EVENT_ADMIN_SEND_CONFIRMATION_EMAIL = async (
Event(EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL, userId).save() user: DbUser,
moderator: DbUser,
): Promise<DbEvent> =>
Event(EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL, user, moderator).save()
/* export const EVENT_REDEEM_REGISTER = async ( export const EVENT_REGISTER = async (user: DbUser): Promise<DbEvent> =>
userId: number, Event(EventProtocolType.REGISTER, user, user).save()
transactionId: number | null = null,
contributionId: number | null = null,
): Promise<Event> =>
Event(
EventProtocolType.REDEEM_REGISTER,
userId,
null,
null,
transactionId,
contributionId,
).save()
*/
export const EVENT_REGISTER = async (userId: number): Promise<DbEvent> => export const EVENT_ACTIVATE_ACCOUNT = async (user: DbUser): Promise<DbEvent> =>
Event(EventProtocolType.REGISTER, userId).save() Event(EventProtocolType.ACTIVATE_ACCOUNT, user, user).save()
export const EVENT_ACTIVATE_ACCOUNT = async (userId: number): Promise<DbEvent> =>
Event(EventProtocolType.ACTIVATE_ACCOUNT, userId).save()

View File

@ -21,9 +21,13 @@ export async function requestGetPublicKey(dbCom: DbCommunity): Promise<string |
} }
} }
` `
const variables = {}
try { try {
const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest(query) const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest(
query,
variables,
)
logger.debug(`Response-Data:`, data, errors, extensions, headers, status) logger.debug(`Response-Data:`, data, errors, extensions, headers, status)
if (data) { if (data) {
logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey) logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey)

View File

@ -21,9 +21,13 @@ export async function requestGetPublicKey(dbCom: DbCommunity): Promise<string |
} }
} }
` `
const variables = {}
try { try {
const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest(query) const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest(
query,
variables,
)
logger.debug(`Response-Data:`, data, errors, extensions, headers, status) logger.debug(`Response-Data:`, data, errors, extensions, headers, status)
if (data) { if (data) {
logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey) logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey)

View File

@ -1,8 +1,14 @@
import { GraphQLClient } from 'graphql-request' import { GraphQLClient } from 'graphql-request'
import { PatchedRequestInit } from 'graphql-request/dist/types' import { PatchedRequestInit } from 'graphql-request/dist/types'
type ClientInstance = {
url: string
// eslint-disable-next-line no-use-before-define
client: GraphQLGetClient
}
export class GraphQLGetClient extends GraphQLClient { export class GraphQLGetClient extends GraphQLClient {
private static instance: GraphQLGetClient private static instanceArray: ClientInstance[] = []
/** /**
* The Singleton's constructor should always be private to prevent direct * The Singleton's constructor should always be private to prevent direct
@ -20,16 +26,18 @@ export class GraphQLGetClient extends GraphQLClient {
* just one instance of each subclass around. * just one instance of each subclass around.
*/ */
public static getInstance(url: string): GraphQLGetClient { public static getInstance(url: string): GraphQLGetClient {
if (!GraphQLGetClient.instance) { const instance = GraphQLGetClient.instanceArray.find((instance) => instance.url === url)
GraphQLGetClient.instance = new GraphQLGetClient(url, { if (instance) {
return instance.client
}
const client = new GraphQLGetClient(url, {
method: 'GET', method: 'GET',
jsonSerializer: { jsonSerializer: {
parse: JSON.parse, parse: JSON.parse,
stringify: JSON.stringify, stringify: JSON.stringify,
}, },
}) })
} GraphQLGetClient.instanceArray.push({ url, client } as ClientInstance)
return client
return GraphQLGetClient.instance
} }
} }

View File

@ -42,17 +42,18 @@ export async function validateCommunities(): Promise<void> {
pubKey, pubKey,
`${dbCom.endPoint}/${dbCom.apiVersion}`, `${dbCom.endPoint}/${dbCom.apiVersion}`,
) )
if (pubKey && pubKey === dbCom.publicKey.toString('hex')) { if (pubKey && pubKey === dbCom.publicKey.toString()) {
logger.info(`Federation: matching publicKey: ${pubKey}`) logger.info(`Federation: matching publicKey: ${pubKey}`)
await DbCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() }) await DbCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() })
logger.debug(`Federation: updated dbCom: ${JSON.stringify(dbCom)}`) logger.debug(`Federation: updated dbCom: ${JSON.stringify(dbCom)}`)
} else {
logger.warn(
`Federation: received not matching publicKey -> received: ${
pubKey || 'null'
}, expected: ${dbCom.publicKey.toString()} `,
)
// DbCommunity.delete({ id: dbCom.id })
} }
/*
else {
logger.warn(`Federation: received unknown publicKey -> delete dbCom with id=${dbCom.id} `)
DbCommunity.delete({ id: dbCom.id })
}
*/
} catch (err) { } catch (err) {
if (!isLogError(err)) { if (!isLogError(err)) {
logger.error(`Error:`, err) logger.error(`Error:`, err)

View File

@ -46,7 +46,7 @@ import { userFactory } from '@/seeds/factory/user'
import { creationFactory } from '@/seeds/factory/creation' import { creationFactory } from '@/seeds/factory/creation'
import { creations } from '@/seeds/creation/index' import { creations } from '@/seeds/creation/index'
import { peterLustig } from '@/seeds/users/peter-lustig' import { peterLustig } from '@/seeds/users/peter-lustig'
import { EventProtocol } from '@entity/EventProtocol' import { Event as DbEvent } from '@entity/Event'
import { Contribution } from '@entity/Contribution' import { Contribution } from '@entity/Contribution'
import { Transaction as DbTransaction } from '@entity/Transaction' import { Transaction as DbTransaction } from '@entity/Transaction'
import { User } from '@entity/User' import { User } from '@entity/User'
@ -279,12 +279,13 @@ describe('ContributionResolver', () => {
}) })
it('stores the CONTRIBUTION_CREATE event in the database', async () => { it('stores the CONTRIBUTION_CREATE event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.CONTRIBUTION_CREATE, type: EventProtocolType.CONTRIBUTION_CREATE,
affectedUserId: bibi.id,
actingUserId: bibi.id,
involvedContributionId: pendingContribution.data.createContribution.id,
amount: expect.decimalEqual(100), amount: expect.decimalEqual(100),
contributionId: pendingContribution.data.createContribution.id,
userId: bibi.id,
}), }),
) )
}) })
@ -584,12 +585,13 @@ describe('ContributionResolver', () => {
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
}) })
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.CONTRIBUTION_UPDATE, type: EventProtocolType.CONTRIBUTION_UPDATE,
affectedUserId: bibi.id,
actingUserId: bibi.id,
involvedContributionId: pendingContribution.data.createContribution.id,
amount: expect.decimalEqual(10), amount: expect.decimalEqual(10),
contributionId: pendingContribution.data.createContribution.id,
userId: bibi.id,
}), }),
) )
}) })
@ -814,12 +816,12 @@ describe('ContributionResolver', () => {
}) })
it('stores the ADMIN_CONTRIBUTION_DENY event in the database', async () => { it('stores the ADMIN_CONTRIBUTION_DENY event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.ADMIN_CONTRIBUTION_DENY, type: EventProtocolType.ADMIN_CONTRIBUTION_DENY,
userId: bibi.id, affectedUserId: bibi.id,
xUserId: admin.id, actingUserId: admin.id,
contributionId: contributionToDeny.data.createContribution.id, involvedContributionId: contributionToDeny.data.createContribution.id,
amount: expect.decimalEqual(100), amount: expect.decimalEqual(100),
}), }),
) )
@ -942,12 +944,13 @@ describe('ContributionResolver', () => {
}) })
it('stores the CONTRIBUTION_DELETE event in the database', async () => { it('stores the CONTRIBUTION_DELETE event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.CONTRIBUTION_DELETE, type: EventProtocolType.CONTRIBUTION_DELETE,
contributionId: contributionToDelete.data.createContribution.id, affectedUserId: bibi.id,
actingUserId: bibi.id,
involvedContributionId: contributionToDelete.data.createContribution.id,
amount: expect.decimalEqual(100), amount: expect.decimalEqual(100),
userId: bibi.id,
}), }),
) )
}) })
@ -2031,10 +2034,11 @@ describe('ContributionResolver', () => {
}) })
it('stores the ADMIN_CONTRIBUTION_CREATE event in the database', async () => { it('stores the ADMIN_CONTRIBUTION_CREATE event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.ADMIN_CONTRIBUTION_CREATE, type: EventProtocolType.ADMIN_CONTRIBUTION_CREATE,
userId: admin.id, affectedUserId: bibi.id,
actingUserId: admin.id,
amount: expect.decimalEqual(200), amount: expect.decimalEqual(200),
}), }),
) )
@ -2232,7 +2236,7 @@ describe('ContributionResolver', () => {
mutate({ mutate({
mutation: adminUpdateContribution, mutation: adminUpdateContribution,
variables: { variables: {
id: creation ? creation.id : -1, id: creation?.id,
email: 'peter@lustig.de', email: 'peter@lustig.de',
amount: new Decimal(300), amount: new Decimal(300),
memo: 'Danke Peter!', memo: 'Danke Peter!',
@ -2256,10 +2260,11 @@ describe('ContributionResolver', () => {
}) })
it('stores the ADMIN_CONTRIBUTION_UPDATE event in the database', async () => { it('stores the ADMIN_CONTRIBUTION_UPDATE event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE, type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE,
userId: admin.id, affectedUserId: creation?.userId,
actingUserId: admin.id,
amount: 300, amount: 300,
}), }),
) )
@ -2273,7 +2278,7 @@ describe('ContributionResolver', () => {
mutate({ mutate({
mutation: adminUpdateContribution, mutation: adminUpdateContribution,
variables: { variables: {
id: creation ? creation.id : -1, id: creation?.id,
email: 'peter@lustig.de', email: 'peter@lustig.de',
amount: new Decimal(200), amount: new Decimal(200),
memo: 'Das war leider zu Viel!', memo: 'Das war leider zu Viel!',
@ -2297,10 +2302,11 @@ describe('ContributionResolver', () => {
}) })
it('stores the ADMIN_CONTRIBUTION_UPDATE event in the database', async () => { it('stores the ADMIN_CONTRIBUTION_UPDATE event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE, type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE,
userId: admin.id, affectedUserId: creation?.userId,
actingUserId: admin.id,
amount: expect.decimalEqual(200), amount: expect.decimalEqual(200),
}), }),
) )
@ -2371,7 +2377,7 @@ describe('ContributionResolver', () => {
mutate({ mutate({
mutation: adminDeleteContribution, mutation: adminDeleteContribution,
variables: { variables: {
id: creation ? creation.id : -1, id: creation?.id,
}, },
}), }),
).resolves.toEqual( ).resolves.toEqual(
@ -2382,10 +2388,12 @@ describe('ContributionResolver', () => {
}) })
it('stores the ADMIN_CONTRIBUTION_DELETE event in the database', async () => { it('stores the ADMIN_CONTRIBUTION_DELETE event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.ADMIN_CONTRIBUTION_DELETE, type: EventProtocolType.ADMIN_CONTRIBUTION_DELETE,
userId: admin.id, affectedUserId: creation?.userId,
actingUserId: admin.id,
involvedContributionId: creation?.id,
amount: expect.decimalEqual(200), amount: expect.decimalEqual(200),
}), }),
) )
@ -2538,7 +2546,7 @@ describe('ContributionResolver', () => {
}) })
it('stores the CONTRIBUTION_CONFIRM event in the database', async () => { it('stores the CONTRIBUTION_CONFIRM event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.CONTRIBUTION_CONFIRM, type: EventProtocolType.CONTRIBUTION_CONFIRM,
}), }),
@ -2570,7 +2578,7 @@ describe('ContributionResolver', () => {
}) })
it('stores the SEND_CONFIRMATION_EMAIL event in the database', async () => { it('stores the SEND_CONFIRMATION_EMAIL event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.SEND_CONFIRMATION_EMAIL, type: EventProtocolType.SEND_CONFIRMATION_EMAIL,
}), }),

View File

@ -91,7 +91,7 @@ export class ContributionResolver {
logger.trace('contribution to save', contribution) logger.trace('contribution to save', contribution)
await DbContribution.save(contribution) await DbContribution.save(contribution)
await EVENT_CONTRIBUTION_CREATE(user.id, contribution.id, amount) await EVENT_CONTRIBUTION_CREATE(user, contribution, amount)
return new UnconfirmedContribution(contribution, user, creations) return new UnconfirmedContribution(contribution, user, creations)
} }
@ -119,7 +119,7 @@ export class ContributionResolver {
contribution.deletedAt = new Date() contribution.deletedAt = new Date()
await contribution.save() await contribution.save()
await EVENT_CONTRIBUTION_DELETE(user.id, contribution.id, contribution.amount) await EVENT_CONTRIBUTION_DELETE(user, contribution, contribution.amount)
const res = await contribution.softRemove() const res = await contribution.softRemove()
return !!res return !!res
@ -247,7 +247,7 @@ export class ContributionResolver {
contributionToUpdate.updatedAt = new Date() contributionToUpdate.updatedAt = new Date()
await DbContribution.save(contributionToUpdate) await DbContribution.save(contributionToUpdate)
await EVENT_CONTRIBUTION_UPDATE(user.id, contributionId, amount) await EVENT_CONTRIBUTION_UPDATE(user, contributionToUpdate, amount)
return new UnconfirmedContribution(contributionToUpdate, user, creations) return new UnconfirmedContribution(contributionToUpdate, user, creations)
} }
@ -304,7 +304,7 @@ export class ContributionResolver {
await DbContribution.save(contribution) await DbContribution.save(contribution)
await EVENT_ADMIN_CONTRIBUTION_CREATE(moderator.id, contribution.id, amount) await EVENT_ADMIN_CONTRIBUTION_CREATE(emailContact.user, moderator, contribution, amount)
return getUserCreation(emailContact.userId, clientTimezoneOffset) return getUserCreation(emailContact.userId, clientTimezoneOffset)
} }
@ -372,7 +372,12 @@ export class ContributionResolver {
result.creation = await getUserCreation(emailContact.user.id, clientTimezoneOffset) result.creation = await getUserCreation(emailContact.user.id, clientTimezoneOffset)
await EVENT_ADMIN_CONTRIBUTION_UPDATE(emailContact.user.id, contributionToUpdate.id, amount) await EVENT_ADMIN_CONTRIBUTION_UPDATE(
emailContact.user,
moderator,
contributionToUpdate,
amount,
)
return result return result
} }
@ -432,7 +437,12 @@ export class ContributionResolver {
await contribution.save() await contribution.save()
const res = await contribution.softRemove() const res = await contribution.softRemove()
await EVENT_ADMIN_CONTRIBUTION_DELETE(contribution.userId, contribution.id, contribution.amount) await EVENT_ADMIN_CONTRIBUTION_DELETE(
{ id: contribution.userId } as DbUser,
moderator,
contribution,
contribution.amount,
)
void sendContributionDeletedEmail({ void sendContributionDeletedEmail({
firstName: user.firstName, firstName: user.firstName,
@ -545,7 +555,7 @@ export class ContributionResolver {
await queryRunner.release() await queryRunner.release()
} }
await EVENT_CONTRIBUTION_CONFIRM(user.id, contribution.id, contribution.amount) await EVENT_CONTRIBUTION_CONFIRM(user, moderatorUser, contribution, contribution.amount)
} finally { } finally {
releaseLock() releaseLock()
} }
@ -605,9 +615,9 @@ export class ContributionResolver {
const res = await contributionToUpdate.save() const res = await contributionToUpdate.save()
await EVENT_ADMIN_CONTRIBUTION_DENY( await EVENT_ADMIN_CONTRIBUTION_DENY(
contributionToUpdate.userId, user,
moderator.id, moderator,
contributionToUpdate.id, contributionToUpdate,
contributionToUpdate.amount, contributionToUpdate.amount,
) )

View File

@ -18,7 +18,7 @@ import { bobBaumeister } from '@/seeds/users/bob-baumeister'
import { garrickOllivander } from '@/seeds/users/garrick-ollivander' import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
import { peterLustig } from '@/seeds/users/peter-lustig' import { peterLustig } from '@/seeds/users/peter-lustig'
import { stephenHawking } from '@/seeds/users/stephen-hawking' import { stephenHawking } from '@/seeds/users/stephen-hawking'
import { EventProtocol } from '@entity/EventProtocol' import { Event as DbEvent } from '@entity/Event'
import { Transaction } from '@entity/Transaction' import { Transaction } from '@entity/Transaction'
import { User } from '@entity/User' import { User } from '@entity/User'
import { cleanDB, testEnvironment } from '@test/helpers' import { cleanDB, testEnvironment } from '@test/helpers'
@ -341,12 +341,13 @@ describe('send coins', () => {
memo: 'unrepeatable memo', memo: 'unrepeatable memo',
}) })
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.TRANSACTION_SEND, type: EventProtocolType.TRANSACTION_SEND,
userId: user[1].id, affectedUserId: user[1].id,
transactionId: transaction[0].id, actingUserId: user[1].id,
xUserId: user[0].id, involvedUserId: user[0].id,
involvedTransactionId: transaction[0].id,
}), }),
) )
}) })
@ -358,12 +359,13 @@ describe('send coins', () => {
memo: 'unrepeatable memo', memo: 'unrepeatable memo',
}) })
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.TRANSACTION_RECEIVE, type: EventProtocolType.TRANSACTION_RECEIVE,
userId: user[0].id, affectedUserId: user[0].id,
transactionId: transaction[0].id, actingUserId: user[1].id,
xUserId: user[1].id, involvedUserId: user[1].id,
involvedTransactionId: transaction[0].id,
}), }),
) )
}) })

View File

@ -138,17 +138,12 @@ export const executeTransaction = async (
await queryRunner.commitTransaction() await queryRunner.commitTransaction()
logger.info(`commit Transaction successful...`) logger.info(`commit Transaction successful...`)
await EVENT_TRANSACTION_SEND( await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount)
transactionSend.userId,
transactionSend.linkedUserId,
transactionSend.id,
transactionSend.amount.mul(-1),
)
await EVENT_TRANSACTION_RECEIVE( await EVENT_TRANSACTION_RECEIVE(
transactionReceive.userId, recipient,
transactionReceive.linkedUserId, sender,
transactionReceive.id, transactionReceive,
transactionReceive.amount, transactionReceive.amount,
) )
} catch (e) { } catch (e) {

View File

@ -40,7 +40,7 @@ import { transactionLinkFactory } from '@/seeds/factory/transactionLink'
import { ContributionLink } from '@model/ContributionLink' import { ContributionLink } from '@model/ContributionLink'
import { TransactionLink } from '@entity/TransactionLink' import { TransactionLink } from '@entity/TransactionLink'
import { EventProtocolType } from '@/event/EventProtocolType' import { EventProtocolType } from '@/event/EventProtocolType'
import { EventProtocol } from '@entity/EventProtocol' import { Event as DbEvent } from '@entity/Event'
import { validate as validateUUID, version as versionUUID } from 'uuid' import { validate as validateUUID, version as versionUUID } from 'uuid'
import { peterLustig } from '@/seeds/users/peter-lustig' import { peterLustig } from '@/seeds/users/peter-lustig'
import { UserContact } from '@entity/UserContact' import { UserContact } from '@entity/UserContact'
@ -187,10 +187,11 @@ describe('UserResolver', () => {
{ email: 'peter@lustig.de' }, { email: 'peter@lustig.de' },
{ relations: ['user'] }, { relations: ['user'] },
) )
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.REGISTER, type: EventProtocolType.REGISTER,
userId: userConatct.user.id, affectedUserId: userConatct.user.id,
actingUserId: userConatct.user.id,
}), }),
) )
}) })
@ -216,10 +217,11 @@ describe('UserResolver', () => {
}) })
it('stores the SEND_CONFIRMATION_EMAIL event in the database', async () => { it('stores the SEND_CONFIRMATION_EMAIL event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.SEND_CONFIRMATION_EMAIL, type: EventProtocolType.SEND_CONFIRMATION_EMAIL,
userId: user[0].id, affectedUserId: user[0].id,
actingUserId: user[0].id,
}), }),
) )
}) })
@ -261,10 +263,11 @@ describe('UserResolver', () => {
{ email: 'peter@lustig.de' }, { email: 'peter@lustig.de' },
{ relations: ['user'] }, { relations: ['user'] },
) )
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL, type: EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL,
userId: userConatct.user.id, affectedUserId: userConatct.user.id,
actingUserId: 0,
}), }),
) )
}) })
@ -361,20 +364,22 @@ describe('UserResolver', () => {
}) })
it('stores the ACTIVATE_ACCOUNT event in the database', async () => { it('stores the ACTIVATE_ACCOUNT event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.ACTIVATE_ACCOUNT, type: EventProtocolType.ACTIVATE_ACCOUNT,
userId: user[0].id, affectedUserId: user[0].id,
actingUserId: user[0].id,
}), }),
) )
}) })
it('stores the REDEEM_REGISTER event in the database', async () => { it('stores the REDEEM_REGISTER event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.REDEEM_REGISTER, type: EventProtocolType.REDEEM_REGISTER,
userId: result.data.createUser.id, affectedUserId: result.data.createUser.id,
contributionId: link.id, actingUserId: result.data.createUser.id,
involvedContributionId: link.id,
}), }),
) )
}) })
@ -454,10 +459,12 @@ describe('UserResolver', () => {
}) })
it('stores the REDEEM_REGISTER event in the database', async () => { it('stores the REDEEM_REGISTER event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.REDEEM_REGISTER, type: EventProtocolType.REDEEM_REGISTER,
userId: newUser.data.createUser.id, affectedUserId: newUser.data.createUser.id,
actingUserId: newUser.data.createUser.id,
involvedTransactionId: transactionLink.id,
}), }),
) )
}) })
@ -685,10 +692,11 @@ describe('UserResolver', () => {
{ email: 'bibi@bloxberg.de' }, { email: 'bibi@bloxberg.de' },
{ relations: ['user'] }, { relations: ['user'] },
) )
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.LOGIN, type: EventProtocolType.LOGIN,
userId: userConatct.user.id, affectedUserId: userConatct.user.id,
actingUserId: userConatct.user.id,
}), }),
) )
}) })
@ -933,10 +941,11 @@ describe('UserResolver', () => {
}) })
it('stores the LOGIN event in the database', async () => { it('stores the LOGIN event in the database', async () => {
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.LOGIN, type: EventProtocolType.LOGIN,
userId: user[0].id, affectedUserId: user[0].id,
actingUserId: user[0].id,
}), }),
) )
}) })
@ -1852,10 +1861,11 @@ describe('UserResolver', () => {
{ email: 'bibi@bloxberg.de' }, { email: 'bibi@bloxberg.de' },
{ relations: ['user'] }, { relations: ['user'] },
) )
await expect(EventProtocol.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL, type: EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL,
userId: userConatct.user.id, affectedUserId: userConatct.user.id,
actingUserId: admin.id,
}), }),
) )
}) })

View File

@ -20,7 +20,9 @@ import { getConnection, getCustomRepository, IsNull, Not } from '@dbTools/typeor
import { User as DbUser } from '@entity/User' import { User as DbUser } from '@entity/User'
import { UserContact as DbUserContact } from '@entity/UserContact' import { UserContact as DbUserContact } from '@entity/UserContact'
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink' import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { Transaction as DbTransaction } from '@entity/Transaction'
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { Contribution as DbContribution } from '@entity/Contribution'
import { UserRepository } from '@repository/User' import { UserRepository } from '@repository/User'
import { User } from '@model/User' import { User } from '@model/User'
@ -182,7 +184,7 @@ export class UserResolver {
value: encode(dbUser.gradidoID), value: encode(dbUser.gradidoID),
}) })
await EVENT_LOGIN(user.id) await EVENT_LOGIN(dbUser)
logger.info(`successful Login: ${JSON.stringify(user, null, 2)}`) logger.info(`successful Login: ${JSON.stringify(user, null, 2)}`)
return user return user
} }
@ -252,7 +254,7 @@ export class UserResolver {
language: foundUser.language, // use language of the emails owner for sending language: foundUser.language, // use language of the emails owner for sending
}) })
await EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL(foundUser.id) await EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL(foundUser)
logger.info( logger.info(
`sendAccountMultiRegistrationEmail by ${firstName} ${lastName} to ${foundUser.firstName} ${foundUser.lastName} <${email}>`, `sendAccountMultiRegistrationEmail by ${firstName} ${lastName} to ${foundUser.firstName} ${foundUser.lastName} <${email}>`,
@ -270,7 +272,11 @@ export class UserResolver {
const gradidoID = await newGradidoID() const gradidoID = await newGradidoID()
const eventRegisterRedeem = Event(EventProtocolType.REDEEM_REGISTER, 0) const eventRegisterRedeem = Event(
EventProtocolType.REDEEM_REGISTER,
{ id: 0 } as DbUser,
{ id: 0 } as DbUser,
)
let dbUser = new DbUser() let dbUser = new DbUser()
dbUser.gradidoID = gradidoID dbUser.gradidoID = gradidoID
dbUser.firstName = firstName dbUser.firstName = firstName
@ -287,14 +293,16 @@ export class UserResolver {
logger.info('redeemCode found contributionLink', contributionLink) logger.info('redeemCode found contributionLink', contributionLink)
if (contributionLink) { if (contributionLink) {
dbUser.contributionLinkId = contributionLink.id dbUser.contributionLinkId = contributionLink.id
eventRegisterRedeem.contributionId = contributionLink.id // TODO this is so wrong
eventRegisterRedeem.involvedContribution = { id: contributionLink.id } as DbContribution
} }
} else { } else {
const transactionLink = await DbTransactionLink.findOne({ code: redeemCode }) const transactionLink = await DbTransactionLink.findOne({ code: redeemCode })
logger.info('redeemCode found transactionLink', transactionLink) logger.info('redeemCode found transactionLink', transactionLink)
if (transactionLink) { if (transactionLink) {
dbUser.referrerId = transactionLink.userId dbUser.referrerId = transactionLink.userId
eventRegisterRedeem.transactionId = transactionLink.id // TODO this is so wrong
eventRegisterRedeem.involvedTransaction = { id: transactionLink.id } as DbTransaction
} }
} }
} }
@ -333,7 +341,7 @@ export class UserResolver {
}) })
logger.info(`sendAccountActivationEmail of ${firstName}.${lastName} to ${email}`) logger.info(`sendAccountActivationEmail of ${firstName}.${lastName} to ${email}`)
await EVENT_SEND_CONFIRMATION_EMAIL(dbUser.id) await EVENT_SEND_CONFIRMATION_EMAIL(dbUser)
if (!emailSent) { if (!emailSent) {
logger.debug(`Account confirmation link: ${activationLink}`) logger.debug(`Account confirmation link: ${activationLink}`)
@ -350,10 +358,11 @@ export class UserResolver {
logger.info('createUser() successful...') logger.info('createUser() successful...')
if (redeemCode) { if (redeemCode) {
eventRegisterRedeem.userId = dbUser.id eventRegisterRedeem.affectedUser = dbUser
eventRegisterRedeem.actingUser = dbUser
await eventRegisterRedeem.save() await eventRegisterRedeem.save()
} else { } else {
await EVENT_REGISTER(dbUser.id) await EVENT_REGISTER(dbUser)
} }
return new User(dbUser) return new User(dbUser)
@ -469,7 +478,7 @@ export class UserResolver {
await queryRunner.commitTransaction() await queryRunner.commitTransaction()
logger.info('User and UserContact data written successfully...') logger.info('User and UserContact data written successfully...')
await EVENT_ACTIVATE_ACCOUNT(user.id) await EVENT_ACTIVATE_ACCOUNT(user)
} catch (e) { } catch (e) {
await queryRunner.rollbackTransaction() await queryRunner.rollbackTransaction()
throw new LogError('Error on writing User and User Contact data', e) throw new LogError('Error on writing User and User Contact data', e)
@ -779,9 +788,13 @@ export class UserResolver {
return null return null
} }
// TODO this is an admin function - needs refactor
@Authorized([RIGHTS.SEND_ACTIVATION_EMAIL]) @Authorized([RIGHTS.SEND_ACTIVATION_EMAIL])
@Mutation(() => Boolean) @Mutation(() => Boolean)
async sendActivationEmail(@Arg('email') email: string): Promise<boolean> { async sendActivationEmail(
@Arg('email') email: string,
@Ctx() context: Context,
): Promise<boolean> {
email = email.trim().toLowerCase() email = email.trim().toLowerCase()
// const user = await dbUser.findOne({ id: emailContact.userId }) // const user = await dbUser.findOne({ id: emailContact.userId })
const user = await findUserByEmail(email) const user = await findUserByEmail(email)
@ -806,7 +819,7 @@ export class UserResolver {
if (!emailSent) { if (!emailSent) {
logger.info(`Account confirmation link: ${activationLink}`) logger.info(`Account confirmation link: ${activationLink}`)
} else { } else {
await EVENT_ADMIN_SEND_CONFIRMATION_EMAIL(user.id) await EVENT_ADMIN_SEND_CONFIRMATION_EMAIL(user, getUser(context))
} }
return true return true

View File

@ -0,0 +1,83 @@
import { Contribution } from '../Contribution'
import { ContributionMessage } from '../ContributionMessage'
import { User } from '../User'
import { Transaction } from '../Transaction'
import Decimal from 'decimal.js-light'
import {
BaseEntity,
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
ManyToOne,
JoinColumn,
} from 'typeorm'
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
@Entity('events')
export class Event extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ length: 100, nullable: false, collation: 'utf8mb4_unicode_ci' })
type: string
@CreateDateColumn({
name: 'created_at',
type: 'datetime',
default: () => 'CURRENT_TIMESTAMP(3)',
nullable: false,
})
createdAt: Date
@Column({ name: 'affected_user_id', unsigned: true, nullable: false })
affectedUserId: number
@ManyToOne(() => User)
@JoinColumn({ name: 'affected_user_id', referencedColumnName: 'id' })
affectedUser: User
@Column({ name: 'acting_user_id', unsigned: true, nullable: false })
actingUserId: number
@ManyToOne(() => User)
@JoinColumn({ name: 'acting_user_id', referencedColumnName: 'id' })
actingUser: User
@Column({ name: 'involved_user_id', type: 'int', unsigned: true, nullable: true })
involvedUserId: number | null
@ManyToOne(() => User)
@JoinColumn({ name: 'involved_user_id', referencedColumnName: 'id' })
involvedUser: User | null
@Column({ name: 'involved_transaction_id', type: 'int', unsigned: true, nullable: true })
involvedTransactionId: number | null
@ManyToOne(() => Transaction)
@JoinColumn({ name: 'involved_transaction_id', referencedColumnName: 'id' })
involvedTransaction: Transaction | null
@Column({ name: 'involved_contribution_id', type: 'int', unsigned: true, nullable: true })
involvedContributionId: number | null
@ManyToOne(() => Contribution)
@JoinColumn({ name: 'involved_contribution_id', referencedColumnName: 'id' })
involvedContribution: Contribution | null
@Column({ name: 'involved_contribution_message_id', type: 'int', unsigned: true, nullable: true })
involvedContributionMessageId: number | null
@ManyToOne(() => ContributionMessage)
@JoinColumn({ name: 'involved_contribution_message_id', referencedColumnName: 'id' })
involvedContributionMessage: ContributionMessage | null
@Column({
type: 'decimal',
precision: 40,
scale: 20,
nullable: true,
transformer: DecimalTransformer,
})
amount: Decimal | null
}

1
database/entity/Event.ts Normal file
View File

@ -0,0 +1 @@
export { Event } from './0061-event_refactoring/Event'

View File

@ -1 +0,0 @@
export { EventProtocol } from './0050-add_messageId_to_event_protocol/EventProtocol'

View File

@ -7,21 +7,21 @@ import { TransactionLink } from './TransactionLink'
import { User } from './User' import { User } from './User'
import { UserContact } from './UserContact' import { UserContact } from './UserContact'
import { Contribution } from './Contribution' import { Contribution } from './Contribution'
import { EventProtocol } from './EventProtocol' import { Event } from './Event'
import { ContributionMessage } from './ContributionMessage' import { ContributionMessage } from './ContributionMessage'
import { Community } from './Community' import { Community } from './Community'
export const entities = [ export const entities = [
Community,
Contribution, Contribution,
ContributionLink, ContributionLink,
ContributionMessage,
Event,
LoginElopageBuys, LoginElopageBuys,
LoginEmailOptIn, LoginEmailOptIn,
Migration, Migration,
Transaction, Transaction,
TransactionLink, TransactionLink,
User, User,
EventProtocol,
ContributionMessage,
UserContact, UserContact,
Community,
] ]

View File

@ -0,0 +1,98 @@
/* MIGRATION TO REFACTOR THE EVENT_PROTOCOL TABLE
*
* This migration refactors the `event_protocol` table.
* It renames the table to `event`, introduces new fields and renames others.
*/
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn('RENAME TABLE `event_protocol` TO `events`;')
await queryFn('ALTER TABLE `events` RENAME COLUMN `user_id` TO `affected_user_id`;')
await queryFn(
'ALTER TABLE `events` ADD COLUMN `acting_user_id` int(10) unsigned DEFAULT NULL AFTER `affected_user_id`;',
)
await queryFn('UPDATE `events` SET `acting_user_id` = `affected_user_id`;')
await queryFn(
'ALTER TABLE `events` MODIFY COLUMN `acting_user_id` int(10) unsigned NOT NULL AFTER `affected_user_id`;',
)
await queryFn('ALTER TABLE `events` RENAME COLUMN `x_user_id` TO `involved_user_id`;')
await queryFn('ALTER TABLE `events` DROP COLUMN `x_community_id`;')
await queryFn('ALTER TABLE `events` RENAME COLUMN `transaction_id` TO `involved_transaction_id`;')
await queryFn(
'ALTER TABLE `events` RENAME COLUMN `contribution_id` TO `involved_contribution_id`;',
)
await queryFn(
'ALTER TABLE `events` MODIFY COLUMN `message_id` int(10) unsigned DEFAULT NULL AFTER `involved_contribution_id`;',
)
await queryFn(
'ALTER TABLE `events` RENAME COLUMN `message_id` TO `involved_contribution_message_id`;',
)
// Moderator id was saved in former user_id
await queryFn(
'UPDATE `events` LEFT JOIN `contributions` ON events.involved_contribution_id = contributions.id SET affected_user_id=contributions.user_id WHERE `type` = "ADMIN_CONTRIBUTION_CREATE";',
)
// inconsistent data on this type, since not all data can be reconstructed
await queryFn(
'UPDATE `events` LEFT JOIN `contributions` ON events.involved_contribution_id = contributions.id SET acting_user_id=0 WHERE `type` = "ADMIN_CONTRIBUTION_UPDATE";',
)
await queryFn(
'UPDATE `events` LEFT JOIN `contributions` ON events.involved_contribution_id = contributions.id SET acting_user_id=contributions.deleted_by WHERE `type` = "ADMIN_CONTRIBUTION_DELETE";',
)
await queryFn(
'UPDATE `events` LEFT JOIN `contributions` ON events.involved_contribution_id = contributions.id SET acting_user_id=contributions.confirmed_by WHERE `type` = "CONTRIBUTION_CONFIRM";',
)
await queryFn(
'UPDATE `events` LEFT JOIN `contributions` ON events.involved_contribution_id = contributions.id SET involved_user_id=NULL, acting_user_id=contributions.denied_by WHERE `type` = "ADMIN_CONTRIBUTION_DENY";',
)
await queryFn(
'UPDATE `events` SET acting_user_id=involved_user_id WHERE `type` = "TRANSACTION_RECEIVE";',
)
await queryFn('UPDATE `events` SET amount = amount * -1 WHERE `type` = "TRANSACTION_SEND";')
await queryFn(
'ALTER TABLE `events` MODIFY COLUMN `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3);',
)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
'ALTER TABLE `events` MODIFY COLUMN `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP();',
)
await queryFn('UPDATE `events` SET amount = amount * -1 WHERE `type` = "TRANSACTION_SEND";')
await queryFn(
'UPDATE `events` SET involved_user_id=acting_user_id WHERE `type` = "ADMIN_CONTRIBUTION_DENY";',
)
await queryFn(
'UPDATE `events` SET affected_user_id=acting_user_id WHERE `type` = "ADMIN_CONTRIBUTION_CREATE";',
)
await queryFn(
'ALTER TABLE `events` RENAME COLUMN `involved_contribution_message_id` TO `message_id`;',
)
await queryFn(
'ALTER TABLE `events` MODIFY COLUMN `message_id` int(10) unsigned DEFAULT NULL AFTER `amount`;',
)
await queryFn(
'ALTER TABLE `events` RENAME COLUMN `involved_contribution_id` TO `contribution_id`;',
)
await queryFn('ALTER TABLE `events` RENAME COLUMN `involved_transaction_id` TO `transaction_id`;')
await queryFn(
'ALTER TABLE `events` ADD COLUMN `x_community_id` int(10) unsigned DEFAULT NULL AFTER `involved_user_id`;',
)
await queryFn('ALTER TABLE `events` RENAME COLUMN `involved_user_id` TO `x_user_id`;')
await queryFn('ALTER TABLE `events` DROP COLUMN `acting_user_id`;')
await queryFn('ALTER TABLE `events` RENAME COLUMN `affected_user_id` TO `user_id`;')
await queryFn('RENAME TABLE `events` TO `event_protocol`;')
}

View File

@ -16,9 +16,7 @@
"dev_up": "cross-env TZ=UTC ts-node src/index.ts up", "dev_up": "cross-env TZ=UTC ts-node src/index.ts up",
"dev_down": "cross-env TZ=UTC ts-node src/index.ts down", "dev_down": "cross-env TZ=UTC ts-node src/index.ts down",
"dev_reset": "cross-env TZ=UTC ts-node src/index.ts reset", "dev_reset": "cross-env TZ=UTC ts-node src/index.ts reset",
"lint": "eslint --max-warnings=0 --ext .js,.ts .", "lint": "eslint --max-warnings=0 --ext .js,.ts ."
"seed:config": "ts-node ./node_modules/typeorm-seeding/dist/cli.js config",
"seed": "cross-env TZ=UTC ts-node src/index.ts seed"
}, },
"devDependencies": { "devDependencies": {
"@types/faker": "^5.5.9", "@types/faker": "^5.5.9",

View File

@ -65,7 +65,9 @@ FEDERATION_COMMUNITY_URL=http://stage1.gradido.net
# the api port is the baseport, which will be added with the api-version, e.g. 1_0 = 5010 # the api port is the baseport, which will be added with the api-version, e.g. 1_0 = 5010
FEDERATION_COMMUNITY_API_PORT=5000 FEDERATION_COMMUNITY_API_PORT=5000
FEDERATION_CONFIG_VERSION=v1.2023-01-09
# comma separated list of api-versions, which cause starting several federation modules
FEDERATION_COMMUNITY_APIS=1_0,1_1,2_0
# database # database
DATABASE_CONFIG_VERSION=v1.2022-03-18 DATABASE_CONFIG_VERSION=v1.2022-03-18

View File

@ -0,0 +1,15 @@
location /api/$FEDERATION_APIVERSION {
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:$FEDERATION_PORT;
proxy_redirect off;
access_log $GRADIDO_LOG_PATH/nginx-access.federation-$FEDERATION_PORT.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.federation-$FEDERATION_PORT.log warn;
}

View File

@ -112,6 +112,9 @@ server {
error_log $GRADIDO_LOG_PATH/nginx-error.admin.log warn; error_log $GRADIDO_LOG_PATH/nginx-error.admin.log warn;
} }
# Federation
$FEDERATION_NGINX_CONF
# TODO this could be a performance optimization # TODO this could be a performance optimization
#location /vue { #location /vue {
# alias /var/www/html/gradido/frontend/dist; # alias /var/www/html/gradido/frontend/dist;

View File

@ -98,6 +98,9 @@ server {
error_log $GRADIDO_LOG_PATH/nginx-error.admin.log warn; error_log $GRADIDO_LOG_PATH/nginx-error.admin.log warn;
} }
# Federation
$FEDERATION_NGINX_CONF
# TODO this could be a performance optimization # TODO this could be a performance optimization
#location /vue { #location /vue {
# alias /var/www/html/gradido/frontend/dist; # alias /var/www/html/gradido/frontend/dist;

View File

@ -59,8 +59,9 @@ ln -s /etc/nginx/sites-available/update-page.conf /etc/nginx/sites-enabled/
sudo /etc/init.d/nginx restart sudo /etc/init.d/nginx restart
# stop all services # stop all services
echo 'Stopping all Gradido services' >> $UPDATE_HTML echo 'Stop and delete all Gradido services' >> $UPDATE_HTML
pm2 stop all pm2 delete all
pm2 save
# git # git
BRANCH=${1:-master} BRANCH=${1:-master}
@ -73,12 +74,41 @@ git pull
export BUILD_COMMIT="$(git rev-parse HEAD)" export BUILD_COMMIT="$(git rev-parse HEAD)"
# Generate gradido.conf from template # Generate gradido.conf from template
# *** 1st prepare for each apiversion the federation conf for nginx from federation-template
# *** set FEDERATION_PORT from FEDERATION_COMMUNITY_APIS and create gradido-federation.conf file
rm -f $NGINX_CONFIG_DIR/gradido.conf.tmp
rm -f $NGINX_CONFIG_DIR/gradido-federation.conf.locations
echo "====================================================================================================" >> $UPDATE_HTML
IFS="," read -a API_ARRAY <<< $FEDERATION_COMMUNITY_APIS
for api in "${API_ARRAY[@]}"
do
export FEDERATION_APIVERSION=$api
# calculate port by remove '_' and add value of api to baseport
port=${api//_/}
FEDERATION_PORT=${FEDERATION_COMMUNITY_API_PORT:-5000}
FEDERATION_PORT=$(($FEDERATION_PORT + $port))
export FEDERATION_PORT
echo "create ngingx config: location /api/$FEDERATION_APIVERSION to http://127.0.0.1:$FEDERATION_PORT" >> $UPDATE_HTML
envsubst '$FEDERATION_APIVERSION, $FEDERATION_PORT' < $NGINX_CONFIG_DIR/gradido-federation.conf.template >> $NGINX_CONFIG_DIR/gradido-federation.conf.locations
done
unset FEDERATION_APIVERSION
unset FEDERATION_PORT
echo "====================================================================================================" >> $UPDATE_HTML
# *** 2nd read gradido-federation.conf file in env variable to be replaced in 3rd step
export FEDERATION_NGINX_CONF=$(< $NGINX_CONFIG_DIR/gradido-federation.conf.locations)
# *** 3rd generate gradido nginx config including federation modules per api-version
echo 'Generate new gradido nginx config' >> $UPDATE_HTML echo 'Generate new gradido nginx config' >> $UPDATE_HTML
case "$NGINX_SSL" in case "$NGINX_SSL" in
true) TEMPLATE_FILE="gradido.conf.ssl.template" ;; true) TEMPLATE_FILE="gradido.conf.ssl.template" ;;
*) TEMPLATE_FILE="gradido.conf.template" ;; *) TEMPLATE_FILE="gradido.conf.template" ;;
esac esac
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $NGINX_CONFIG_DIR/$TEMPLATE_FILE > $NGINX_CONFIG_DIR/gradido.conf envsubst '$FEDERATION_NGINX_CONF' < $NGINX_CONFIG_DIR/$TEMPLATE_FILE > $NGINX_CONFIG_DIR/gradido.conf.tmp
unset FEDERATION_NGINX_CONF
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $NGINX_CONFIG_DIR/gradido.conf.tmp > $NGINX_CONFIG_DIR/gradido.conf
rm $NGINX_CONFIG_DIR/gradido.conf.tmp
rm $NGINX_CONFIG_DIR/gradido-federation.conf.locations
# Generate update-page.conf from template # Generate update-page.conf from template
echo 'Generate new update-page nginx config' >> $UPDATE_HTML echo 'Generate new update-page nginx config' >> $UPDATE_HTML
@ -94,11 +124,13 @@ cp -f $PROJECT_ROOT/backend/.env $PROJECT_ROOT/backend/.env.bak
cp -f $PROJECT_ROOT/frontend/.env $PROJECT_ROOT/frontend/.env.bak cp -f $PROJECT_ROOT/frontend/.env $PROJECT_ROOT/frontend/.env.bak
cp -f $PROJECT_ROOT/admin/.env $PROJECT_ROOT/admin/.env.bak cp -f $PROJECT_ROOT/admin/.env $PROJECT_ROOT/admin/.env.bak
cp -f $PROJECT_ROOT/dht-node/.env $PROJECT_ROOT/dht-node/.env.bak cp -f $PROJECT_ROOT/dht-node/.env $PROJECT_ROOT/dht-node/.env.bak
cp -f $PROJECT_ROOT/federation/.env $PROJECT_ROOT/federation/.env.bak
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/database/.env.template > $PROJECT_ROOT/database/.env envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/database/.env.template > $PROJECT_ROOT/database/.env
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/backend/.env.template > $PROJECT_ROOT/backend/.env envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/backend/.env.template > $PROJECT_ROOT/backend/.env
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/frontend/.env.template > $PROJECT_ROOT/frontend/.env envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/frontend/.env.template > $PROJECT_ROOT/frontend/.env
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/admin/.env.template > $PROJECT_ROOT/admin/.env envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/admin/.env.template > $PROJECT_ROOT/admin/.env
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/dht-node/.env.template > $PROJECT_ROOT/dht-node/.env envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/dht-node/.env.template > $PROJECT_ROOT/dht-node/.env
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/federation/.env.template > $PROJECT_ROOT/federation/.env
# Install & build database # Install & build database
echo 'Updating database' >> $UPDATE_HTML echo 'Updating database' >> $UPDATE_HTML
@ -124,7 +156,6 @@ if [ "$DEPLOY_SEED_DATA" = "true" ]; then
fi fi
# TODO maybe handle this differently? # TODO maybe handle this differently?
export NODE_ENV=production export NODE_ENV=production
pm2 delete gradido-backend
pm2 start --name gradido-backend "yarn --cwd $PROJECT_ROOT/backend start" -l $GRADIDO_LOG_PATH/pm2.backend.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS' pm2 start --name gradido-backend "yarn --cwd $PROJECT_ROOT/backend start" -l $GRADIDO_LOG_PATH/pm2.backend.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
pm2 save pm2 save
@ -137,7 +168,6 @@ yarn install
yarn build yarn build
# TODO maybe handle this differently? # TODO maybe handle this differently?
export NODE_ENV=production export NODE_ENV=production
pm2 delete gradido-frontend
pm2 start --name gradido-frontend "yarn --cwd $PROJECT_ROOT/frontend start" -l $GRADIDO_LOG_PATH/pm2.frontend.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS' pm2 start --name gradido-frontend "yarn --cwd $PROJECT_ROOT/frontend start" -l $GRADIDO_LOG_PATH/pm2.frontend.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
pm2 save pm2 save
@ -150,7 +180,6 @@ yarn install
yarn build yarn build
# TODO maybe handle this differently? # TODO maybe handle this differently?
export NODE_ENV=production export NODE_ENV=production
pm2 delete gradido-admin
pm2 start --name gradido-admin "yarn --cwd $PROJECT_ROOT/admin start" -l $GRADIDO_LOG_PATH/pm2.admin.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS' pm2 start --name gradido-admin "yarn --cwd $PROJECT_ROOT/admin start" -l $GRADIDO_LOG_PATH/pm2.admin.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
pm2 save pm2 save
@ -163,15 +192,49 @@ yarn install
yarn build yarn build
# TODO maybe handle this differently? # TODO maybe handle this differently?
export NODE_ENV=production export NODE_ENV=production
pm2 delete gradido-dht-node
if [ ! -z $FEDERATION_DHT_TOPIC ]; then if [ ! -z $FEDERATION_DHT_TOPIC ]; then
pm2 start --name gradido-dht-node "yarn --cwd $PROJECT_ROOT/dht-node start" -l $GRADIDO_LOG_PATH/pm2.dht-node.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS' pm2 start --name gradido-dht-node "yarn --cwd $PROJECT_ROOT/dht-node start" -l $GRADIDO_LOG_PATH/pm2.dht-node.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
pm2 save
else else
echo "=====================================================================" echo "=====================================================================" >> $UPDATE_HTML
echo "WARNING: FEDERATION_DHT_TOPIC not configured. DHT-Node not started..." echo "WARNING: FEDERATION_DHT_TOPIC not configured. DHT-Node not started..." >> $UPDATE_HTML
echo "=====================================================================" echo "=====================================================================" >> $UPDATE_HTML
fi fi
pm2 save
# Install & build federation
echo 'Updating federation' >> $UPDATE_HTML
cd $PROJECT_ROOT/federation
# TODO maybe handle this differently?
unset NODE_ENV
yarn install
yarn build
# TODO maybe handle this differently?
export NODE_ENV=production
# set FEDERATION_PORT from FEDERATION_COMMUNITY_APIS
IFS="," read -a API_ARRAY <<< $FEDERATION_COMMUNITY_APIS
for api in "${API_ARRAY[@]}"
do
export FEDERATION_API=$api
echo "FEDERATION_API=$FEDERATION_API" >> $UPDATE_HTML
export MODULENAME=gradido-federation-$api
echo "MODULENAME=$MODULENAME" >> $UPDATE_HTML
# calculate port by remove '_' and add value of api to baseport
port=${api//_/}
FEDERATION_PORT=${FEDERATION_COMMUNITY_API_PORT:-5000}
FEDERATION_PORT=$(($FEDERATION_PORT + $port))
export FEDERATION_PORT
echo "====================================================" >> $UPDATE_HTML
echo " start $MODULENAME listening on port=$FEDERATION_PORT" >> $UPDATE_HTML
echo "====================================================" >> $UPDATE_HTML
# pm2 delete $MODULENAME
pm2 start --name $MODULENAME "yarn --cwd $PROJECT_ROOT/federation start" -l $GRADIDO_LOG_PATH/pm2.$MODULENAME.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
pm2 save
done
# let nginx showing gradido # let nginx showing gradido
echo 'Configuring nginx to serve gradido again' >> $UPDATE_HTML echo 'Configuring nginx to serve gradido again' >> $UPDATE_HTML

View File

@ -4,6 +4,11 @@ module.exports = {
preset: 'ts-jest', preset: 'ts-jest',
collectCoverage: true, collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
coverageThreshold: {
global: {
lines: 80,
},
},
setupFiles: ['<rootDir>/test/testSetup.ts'], setupFiles: ['<rootDir>/test/testSetup.ts'],
setupFilesAfterEnv: [], setupFilesAfterEnv: [],
modulePathIgnorePatterns: ['<rootDir>/build/'], modulePathIgnorePatterns: ['<rootDir>/build/'],

View File

@ -13,7 +13,7 @@
"start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js", "start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js",
"dev": "cross-env TZ=UTC nodemon -w src --ext ts --exec ts-node -r dotenv/config -r tsconfig-paths/register src/index.ts", "dev": "cross-env TZ=UTC nodemon -w src --ext ts --exec ts-node -r dotenv/config -r tsconfig-paths/register src/index.ts",
"lint": "eslint --max-warnings=0 --ext .js,.ts .", "lint": "eslint --max-warnings=0 --ext .js,.ts .",
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles" "test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --forceExit --detectOpenHandles"
}, },
"dependencies": { "dependencies": {
"@hyperswarm/dht": "^6.4.4", "@hyperswarm/dht": "^6.4.4",

View File

@ -3,7 +3,7 @@ import dotenv from 'dotenv'
dotenv.config() dotenv.config()
const constants = { const constants = {
DB_VERSION: '0060-update_communities_table', DB_VERSION: '0061-event_refactoring',
LOG4JS_CONFIG: 'log4js-config.json', LOG4JS_CONFIG: 'log4js-config.json',
// default log level on production should be info // default log level on production should be info
LOG_LEVEL: process.env.LOG_LEVEL || 'info', LOG_LEVEL: process.env.LOG_LEVEL || 'info',

View File

@ -719,11 +719,11 @@ describe('federation', () => {
JSON.stringify([ JSON.stringify([
{ {
api: '1_0', api: '1_0',
url: 'http://localhost:5001/api/', url: 'http://localhost/api/',
}, },
{ {
api: '2_0', api: '2_0',
url: 'http://localhost:5002/api/', url: 'http://localhost/api/',
}, },
]), ]),
), ),
@ -747,7 +747,7 @@ describe('federation', () => {
foreign: true, foreign: true,
publicKey: expect.any(Buffer), publicKey: expect.any(Buffer),
apiVersion: '1_0', apiVersion: '1_0',
endPoint: 'http://localhost:5001/api/', endPoint: 'http://localhost/api/',
lastAnnouncedAt: expect.any(Date), lastAnnouncedAt: expect.any(Date),
createdAt: expect.any(Date), createdAt: expect.any(Date),
updatedAt: null, updatedAt: null,
@ -764,7 +764,7 @@ describe('federation', () => {
foreign: true, foreign: true,
publicKey: expect.any(Buffer), publicKey: expect.any(Buffer),
apiVersion: '2_0', apiVersion: '2_0',
endPoint: 'http://localhost:5002/api/', endPoint: 'http://localhost/api/',
lastAnnouncedAt: expect.any(Date), lastAnnouncedAt: expect.any(Date),
createdAt: expect.any(Date), createdAt: expect.any(Date),
updatedAt: null, updatedAt: null,
@ -786,15 +786,15 @@ describe('federation', () => {
JSON.stringify([ JSON.stringify([
{ {
api: '1_0', api: '1_0',
url: 'http://localhost:5001/api/', url: 'http://localhost/api/',
}, },
{ {
api: '1_1', api: '1_1',
url: 'http://localhost:5002/api/', url: 'http://localhost/api/',
}, },
{ {
api: '2_0', api: '2_0',
url: 'http://localhost:5003/api/', url: 'http://localhost/api/',
}, },
]), ]),
), ),

View File

@ -181,11 +181,9 @@ export const startDHT = async (topic: string): Promise<void> => {
async function writeHomeCommunityEnries(pubKey: any): Promise<CommunityApi[]> { async function writeHomeCommunityEnries(pubKey: any): Promise<CommunityApi[]> {
const homeApiVersions: CommunityApi[] = Object.values(ApiVersionType).map(function (apiEnum) { const homeApiVersions: CommunityApi[] = Object.values(ApiVersionType).map(function (apiEnum) {
const port =
Number.parseInt(CONFIG.FEDERATION_COMMUNITY_API_PORT) + Number(apiEnum.replace('_', ''))
const comApi: CommunityApi = { const comApi: CommunityApi = {
api: apiEnum, api: apiEnum,
url: CONFIG.FEDERATION_COMMUNITY_URL + ':' + port.toString() + '/api/', url: CONFIG.FEDERATION_COMMUNITY_URL + '/api/',
} }
return comApi return comApi
}) })

View File

@ -56,7 +56,7 @@ export default defineConfig({
env: { env: {
backendURL: 'http://localhost:4000', backendURL: 'http://localhost:4000',
mailserverURL: 'http://localhost:1080', mailserverURL: 'http://localhost:1080',
loginQuery: `query ($email: String!, $password: String!, $publisherId: Int) { loginQuery: `mutation ($email: String!, $password: String!, $publisherId: Int) {
login(email: $email, password: $password, publisherId: $publisherId) { login(email: $email, password: $password, publisherId: $publisherId) {
email email
firstName firstName
@ -69,7 +69,8 @@ export default defineConfig({
hasElopage hasElopage
publisherId publisherId
isAdmin isAdmin
creation hideAmountGDD
hideAmountGDT
__typename __typename
} }
}`, }`,

View File

@ -6,12 +6,12 @@ Feature: User Authentication - reset password
# Background: # Background:
# Given the following "users" are in the database: # Given the following "users" are in the database:
# | email | password | name | # | email | password | name |
# | bibi@bloxberg.de | Aa12345_ | Bibi Bloxberg | # | raeuber@hotzenplotz.de | Aa12345_ | Räuber Hotzenplotz |
Scenario: Reset password from signin page successfully Scenario: Reset password from signin page successfully
Given the user navigates to page "/login" Given the user navigates to page "/login"
And the user navigates to the forgot password page And the user navigates to the forgot password page
When the user enters the e-mail address "bibi@bloxberg.de" When the user enters the e-mail address "raeuber@hotzenplotz.de"
And the user submits the e-mail form And the user submits the e-mail form
Then the user receives an e-mail containing the "password reset" link Then the user receives an e-mail containing the "password reset" link
When the user opens the "password reset" link in the browser When the user opens the "password reset" link in the browser
@ -19,7 +19,7 @@ Feature: User Authentication - reset password
And the user repeats the password "12345Aa_" And the user repeats the password "12345Aa_"
And the user submits the password form And the user submits the password form
And the user clicks the sign in button And the user clicks the sign in button
Then the user submits the credentials "bibi@bloxberg.de" "Aa12345_" Then the user submits the credentials "raeuber@hotzenplotz.de" "Aa12345_"
And the user cannot login And the user cannot login
But the user submits the credentials "bibi@bloxberg.de" "12345Aa_" But the user submits the credentials "raeuber@hotzenplotz.de" "12345Aa_"
And the user is logged in with username "Bibi Bloxberg" And the user is logged in with username "Räuber Hotzenplotz"

View File

@ -6,13 +6,13 @@ Feature: User profile - change password
# TODO for these pre-conditions utilize seeding or API check, if user exists in test system # TODO for these pre-conditions utilize seeding or API check, if user exists in test system
# Given the following "users" are in the database: # Given the following "users" are in the database:
# | email | password | name | # | email | password | name |
# | bibi@bloxberg.de | Aa12345_ | Bibi Bloxberg | | # | bob@baumeister.de | Aa12345_ | Bob der Baumeister |
# TODO instead of credentials use the name of an user object (see seeds in backend) # TODO instead of credentials use the name of an user object (see seeds in backend)
Given the user is logged in as "bibi@bloxberg.de" "Aa12345_" Given the user is logged in as "bob@baumeister.de" "Aa12345_"
Scenario: Change password successfully Scenario: Change password successfully
Given the user navigates to page "/profile" Given the user navigates to page "/settings"
And the user opens the change password menu And the user opens the change password menu
When the user fills the password form with: When the user fills the password form with:
| Old password | Aa12345_ | | Old password | Aa12345_ |
@ -21,7 +21,7 @@ Feature: User profile - change password
And the user submits the password form And the user submits the password form
And the user is presented a "success" message And the user is presented a "success" message
And the user logs out And the user logs out
Then the user submits the credentials "bibi@bloxberg.de" "Aa12345_" Then the user submits the credentials "bob@baumeister.de" "Aa12345_"
And the user cannot login And the user cannot login
But the user submits the credentials "bibi@bloxberg.de" "12345Aa_" But the user submits the credentials "bob@baumeister.de" "12345Aa_"
And the user is logged in with username "Bibi Bloxberg" And the user is logged in with username "Bob der Baumeister"

View File

@ -11,7 +11,7 @@ export class SideNavMenu {
} }
logout() { logout() {
cy.get(this.logoutMenu).click() cy.get('.main-sidebar').find(this.logoutMenu).click()
return this return this
} }
} }

11
federation/.env.dist Normal file
View File

@ -0,0 +1,11 @@
# Database
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=gradido_community
DB_USER=root
DB_PASSWORD=
# Federation
FEDERATION_API=1_0
FEDERATION_PORT=5010
FEDERATION_COMMUNITY_URL=http://localhost

16
federation/.env.template Normal file
View File

@ -0,0 +1,16 @@
CONFIG_VERSION=$FEDERATION_CONFIG_VERSION
LOG_LEVEL=$LOG_LEVEL
# this is set fix to false, because it is important for 'production' environments. only set to true if a graphql-playground should be in use
GRAPHIQL=false
# Database
DB_HOST=$DB_HOST
DB_PORT=$DB_PORT
DB_DATABASE=$DB_DATABASE
DB_USER=$DB_USER
DB_PASSWORD=$DB_PASSWORD
# Federation
FEDERATION_COMMUNITY_URL=$FEDERATION_COMMUNITY_URL

View File

@ -9,6 +9,11 @@ module.exports = {
'!src/seeds/**', '!src/seeds/**',
'!build/**', '!build/**',
], ],
coverageThreshold: {
global: {
lines: 72,
},
},
setupFiles: ['<rootDir>/test/testSetup.ts'], setupFiles: ['<rootDir>/test/testSetup.ts'],
setupFilesAfterEnv: [], setupFilesAfterEnv: [],
modulePathIgnorePatterns: ['<rootDir>/build/'], modulePathIgnorePatterns: ['<rootDir>/build/'],

View File

@ -11,7 +11,7 @@
"build": "tsc --build", "build": "tsc --build",
"clean": "tsc --build --clean", "clean": "tsc --build --clean",
"start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js", "start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js",
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles", "test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --forceExit --detectOpenHandles",
"dev": "cross-env TZ=UTC nodemon -w src --ext ts --exec ts-node -r dotenv/config -r tsconfig-paths/register src/index.ts", "dev": "cross-env TZ=UTC nodemon -w src --ext ts --exec ts-node -r dotenv/config -r tsconfig-paths/register src/index.ts",
"lint": "eslint --max-warnings=0 --ext .js,.ts ." "lint": "eslint --max-warnings=0 --ext .js,.ts ."
}, },

View File

@ -11,7 +11,7 @@ Decimal.set({
*/ */
const constants = { const constants = {
DB_VERSION: '0060-update_communities_table', DB_VERSION: '0061-event_refactoring',
// DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 // DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
LOG4JS_CONFIG: 'log4js-config.json', LOG4JS_CONFIG: 'log4js-config.json',
// default log level on production should be info // default log level on production should be info
@ -24,7 +24,6 @@ const constants = {
} }
const server = { const server = {
PORT: process.env.PORT || 5010,
// JWT_SECRET: process.env.JWT_SECRET || 'secret123', // JWT_SECRET: process.env.JWT_SECRET || 'secret123',
// JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '10m', // JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '10m',
GRAPHIQL: process.env.GRAPHIQL === 'true' || false, GRAPHIQL: process.env.GRAPHIQL === 'true' || false,
@ -40,21 +39,6 @@ const database = {
TYPEORM_LOGGING_RELATIVE_PATH: TYPEORM_LOGGING_RELATIVE_PATH:
process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.backend.log', process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.backend.log',
} }
/*
const community = {
COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung',
COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/',
COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/register',
COMMUNITY_REDEEM_URL: process.env.COMMUNITY_REDEEM_URL || 'http://localhost/redeem/{code}',
COMMUNITY_REDEEM_CONTRIBUTION_URL:
process.env.COMMUNITY_REDEEM_CONTRIBUTION_URL || 'http://localhost/redeem/CL-{code}',
COMMUNITY_DESCRIPTION:
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.',
}
*/
// This is needed by graphql-directive-auth
// process.env.APP_SECRET = server.JWT_SECRET
// Check config version // Check config version
constants.CONFIG_VERSION.CURRENT = constants.CONFIG_VERSION.CURRENT =
@ -71,10 +55,8 @@ if (
} }
const federation = { const federation = {
// FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || null,
// FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
FEDERATION_PORT: process.env.FEDERATION_PORT || 5010,
FEDERATION_API: process.env.FEDERATION_API || '1_0', FEDERATION_API: process.env.FEDERATION_API || '1_0',
FEDERATION_PORT: process.env.FEDERATION_PORT || 5010,
FEDERATION_COMMUNITY_URL: process.env.FEDERATION_COMMUNITY_URL || null, FEDERATION_COMMUNITY_URL: process.env.FEDERATION_COMMUNITY_URL || null,
} }

View File

@ -0,0 +1,13 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Field, ObjectType } from 'type-graphql'
@ObjectType()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export class GetPublicKeyResult {
constructor(pubKey: string) {
this.publicKey = pubKey
}
@Field(() => String)
publicKey: string
}

View File

@ -0,0 +1,20 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Query, Resolver } from 'type-graphql'
import { federationLogger as logger } from '@/server/logger'
import { Community as DbCommunity } from '@entity/Community'
import { GetPublicKeyResult } from '../model/GetPublicKeyResult'
@Resolver()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export class PublicKeyResolver {
@Query(() => GetPublicKeyResult)
async getPublicKey(): Promise<GetPublicKeyResult> {
logger.info(`getPublicKey()...`)
const homeCom = await DbCommunity.findOneOrFail({
foreign: false,
apiVersion: '1_0',
})
logger.info(`getPublicKey()... with publicKey=${homeCom.publicKey}`)
return new GetPublicKeyResult(homeCom.publicKey.toString())
}
}

View File

@ -20,7 +20,7 @@ async function main() {
if (CONFIG.GRAPHIQL) { if (CONFIG.GRAPHIQL) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log( console.log(
`GraphIQL available at http://localhost:${CONFIG.FEDERATION_PORT}` `GraphIQL available at ${CONFIG.FEDERATION_COMMUNITY_URL}/api/${CONFIG.FEDERATION_API}`
) )
} }
}) })

View File

@ -1,13 +1,18 @@
module.exports = { module.exports = {
verbose: true, verbose: true,
collectCoverage: true,
collectCoverageFrom: ['src/**/*.{js,vue}', '!**/node_modules/**', '!**/?(*.)+(spec|test).js?(x)'], collectCoverageFrom: ['src/**/*.{js,vue}', '!**/node_modules/**', '!**/?(*.)+(spec|test).js?(x)'],
coverageThreshold: {
global: {
lines: 95,
},
},
moduleFileExtensions: [ moduleFileExtensions: [
'js', 'js',
// 'jsx', // 'jsx',
'json', 'json',
'vue', 'vue',
], ],
// coverageReporters: ['lcov', 'text'],
moduleNameMapper: { moduleNameMapper: {
'\\.(css|less)$': 'identity-obj-proxy', '\\.(css|less)$': 'identity-obj-proxy',
'\\.(scss)$': '<rootDir>/src/assets/mocks/styleMock.js', '\\.(scss)$': '<rootDir>/src/assets/mocks/styleMock.js',

View File

@ -10,7 +10,7 @@
"analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json", "analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json",
"lint": "eslint --max-warnings=0 --ext .js,.vue,.json .", "lint": "eslint --max-warnings=0 --ext .js,.vue,.json .",
"stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'", "stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'",
"test": "cross-env TZ=UTC jest --coverage", "test": "cross-env TZ=UTC jest",
"locales": "scripts/sort.sh" "locales": "scripts/sort.sh"
}, },
"dependencies": { "dependencies": {

View File

@ -49,15 +49,15 @@ describe('Sidebar', () => {
expect(wrapper.findAll('.nav-item').at(2).text()).toEqual('navigation.transactions') expect(wrapper.findAll('.nav-item').at(2).text()).toEqual('navigation.transactions')
}) })
it('has nav-item "gdt.gdt" in navbar', () => { it('has nav-item "creation" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('creation') expect(wrapper.findAll('.nav-item').at(3).text()).toEqual('creation')
}) })
it('has nav-item "creation" in navbar', () => { it('has nav-item "GDT" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(4).text()).toContain('GDT') expect(wrapper.findAll('.nav-item').at(4).text()).toContain('GDT')
}) })
it('has nav-item "Information" in navbar', () => { it('has nav-item "navigation.info" in navbar', () => {
expect(wrapper.findAll('.nav-item').at(5).text()).toContain('navigation.info') expect(wrapper.findAll('.nav-item').at(5).text()).toContain('navigation.info')
}) })
}) })
@ -68,13 +68,13 @@ describe('Sidebar', () => {
expect(wrapper.findAll('ul').at(1).findAll('.nav-item')).toHaveLength(2) expect(wrapper.findAll('ul').at(1).findAll('.nav-item')).toHaveLength(2)
}) })
it('has nav-item "navigation.info" in navbar', () => { it('has nav-item "navigation.settings" in navbar', () => {
expect(wrapper.findAll('ul').at(1).findAll('.nav-item').at(0).text()).toEqual( expect(wrapper.findAll('ul').at(1).findAll('.nav-item').at(0).text()).toEqual(
'navigation.settings', 'navigation.settings',
) )
}) })
it('has nav-item "navigation.settings" in navbar', () => { it('has nav-item "navigation.logout" in navbar', () => {
expect(wrapper.findAll('ul').at(1).findAll('.nav-item').at(1).text()).toEqual( expect(wrapper.findAll('ul').at(1).findAll('.nav-item').at(1).text()).toEqual(
'navigation.logout', 'navigation.logout',
) )

View File

@ -48,7 +48,12 @@
<b-icon icon="shield-check" aria-hidden="true"></b-icon> <b-icon icon="shield-check" aria-hidden="true"></b-icon>
<span class="ml-2">{{ $t('navigation.admin_area') }}</span> <span class="ml-2">{{ $t('navigation.admin_area') }}</span>
</b-nav-item> </b-nav-item>
<b-nav-item class="font-weight-bold" @click="$emit('logout')" active-class="activeRoute"> <b-nav-item
class="font-weight-bold"
@click="$emit('logout')"
active-class="activeRoute"
data-test="logout-menu"
>
<b-img src="/img/svg/logout.svg" height="20" class="svg-icon" /> <b-img src="/img/svg/logout.svg" height="20" class="svg-icon" />
<span class="ml-2 text-205">{{ $t('navigation.logout') }}</span> <span class="ml-2 text-205">{{ $t('navigation.logout') }}</span>
</b-nav-item> </b-nav-item>

View File

@ -7,6 +7,8 @@ FRONTEND_DIR="${PROJECT_DIR}/frontend/"
BACKEND_DIR="${PROJECT_DIR}/backend/" BACKEND_DIR="${PROJECT_DIR}/backend/"
DATABASE_DIR="${PROJECT_DIR}/database/" DATABASE_DIR="${PROJECT_DIR}/database/"
ADMIN_DIR="${PROJECT_DIR}/admin/" ADMIN_DIR="${PROJECT_DIR}/admin/"
DHTNODE_DIR="${PROJECT_DIR}/dht-node/"
FEDERATION_DIR="${PROJECT_DIR}/federation/"
# navigate to project directory # navigate to project directory
cd ${PROJECT_DIR} cd ${PROJECT_DIR}
@ -26,6 +28,10 @@ cd ${DATABASE_DIR}
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION} yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
cd ${ADMIN_DIR} cd ${ADMIN_DIR}
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION} yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
cd ${DHTNODE_DIR}
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
cd ${FEDERATION_DIR}
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
# generate changelog # generate changelog
cd ${PROJECT_DIR} cd ${PROJECT_DIR}