Merge branch 'master' into eslint-plugin-promise

This commit is contained in:
Ulf Gebhardt 2023-04-05 00:59:54 +02:00 committed by GitHub
commit b92a190e9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
258 changed files with 4834 additions and 3164 deletions

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

@ -0,0 +1,46 @@
# 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/**/*'
nginx: &nginx
- 'nginx/**/*'

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

32
.github/workflows/test-nginx.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: Gradido Nginx Test CI
on: push
jobs:
files-changed:
name: Detect File Changes - Nginx
runs-on: ubuntu-latest
outputs:
nginx: ${{ steps.changes.outputs.nginx }}
steps:
- uses: actions/checkout@v3.3.0
- name: Check for nginx 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_nginx:
name: Docker Build Test - Nginx
if: needs.files-changed.outputs.nginx == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: nginx | Build 'test' image
run: docker build -t "gradido/nginx:test" nginx/

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 #############################################
############################################################################## ##############################################################################
@ -132,139 +81,6 @@ jobs:
name: docker-mariadb-test name: docker-mariadb-test
path: /tmp/mariadb.tar path: /tmp/mariadb.tar
##############################################################################
# JOB: DOCKER BUILD TEST NGINX ###############################################
##############################################################################
build_test_nginx:
name: Docker Build Test - Nginx
runs-on: ubuntu-latest
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# BUILD NGINX DOCKER IMAGE ###############################################
##########################################################################
- name: nginx | Build `test` image
run: |
docker build -t "gradido/nginx:test" nginx/
docker save "gradido/nginx:test" > /tmp/nginx.tar
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: docker-nginx-test
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 +135,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 +169,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: 80
token: ${{ github.token }}
########################################################################## ##########################################################################
# DATABASE MIGRATION TEST UP + RESET ##################################### # DATABASE MIGRATION TEST UP + RESET #####################################
@ -452,108 +193,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

@ -76,7 +76,11 @@ git clone git@github.com:gradido/gradido.git
git submodule update --recursive --init git submodule update --recursive --init
``` ```
### 2. Run docker-compose ### 2. Install modules
You can go in each under folder (admin, frontend, database, backend, ...) and call ``yarn`` in each folder or you can call ``yarn installAll``.
### 3. Run docker-compose
Run docker-compose to bring up the development environment Run docker-compose to bring up the development environment

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

@ -28,6 +28,7 @@ const mocks = {
let propsData let propsData
let wrapper let wrapper
let spy
describe('ChangeUserRoleFormular', () => { describe('ChangeUserRoleFormular', () => {
const Wrapper = () => { const Wrapper = () => {
@ -70,12 +71,16 @@ describe('ChangeUserRoleFormular', () => {
expect(wrapper.text()).toContain('userRole.notChangeYourSelf') expect(wrapper.text()).toContain('userRole.notChangeYourSelf')
}) })
it('has role select disabled', () => { it('has no role select', () => {
expect(wrapper.find('select[disabled="disabled"]').exists()).toBe(true) expect(wrapper.find('select.role-select').exists()).toBe(false)
})
it('has no button', () => {
expect(wrapper.find('button.btn.btn-dange').exists()).toBe(false)
}) })
}) })
describe('change others role', () => { describe("change other user's role", () => {
let rolesToSelect let rolesToSelect
describe('general', () => { describe('general', () => {
@ -106,19 +111,12 @@ describe('ChangeUserRoleFormular', () => {
expect(wrapper.find('select.role-select[disabled="disabled"]').exists()).toBe(false) expect(wrapper.find('select.role-select[disabled="disabled"]').exists()).toBe(false)
}) })
describe('on API error', () => { it('has "change_user_role" button', () => {
beforeEach(() => { expect(wrapper.find('button.btn.btn-danger').text()).toBe('change_user_role')
apolloMutateMock.mockRejectedValue({ message: 'Oh no!' })
rolesToSelect.at(1).setSelected()
})
it('toasts an error message', () => {
expect(toastErrorSpy).toBeCalledWith('Oh no!')
})
}) })
}) })
describe('user is usual user', () => { describe('user has role "usual user"', () => {
beforeEach(() => { beforeEach(() => {
apolloMutateMock.mockResolvedValue({ apolloMutateMock.mockResolvedValue({
data: { data: {
@ -141,6 +139,10 @@ describe('ChangeUserRoleFormular', () => {
describe('change select to', () => { describe('change select to', () => {
describe('same role', () => { describe('same role', () => {
it('has "change_user_role" button disabled', () => {
expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(true)
})
it('does not call the API', () => { it('does not call the API', () => {
rolesToSelect.at(0).setSelected() rolesToSelect.at(0).setSelected()
expect(apolloMutateMock).not.toHaveBeenCalled() expect(apolloMutateMock).not.toHaveBeenCalled()
@ -152,39 +154,75 @@ describe('ChangeUserRoleFormular', () => {
rolesToSelect.at(1).setSelected() rolesToSelect.at(1).setSelected()
}) })
it('calls the API', () => { it('has "change_user_role" button enabled', () => {
expect(apolloMutateMock).toBeCalledWith( expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true)
expect.objectContaining({ expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(
mutation: setUserRole, false,
variables: {
userId: 1,
isAdmin: true,
},
}),
) )
}) })
it('emits "updateIsAdmin"', () => { describe('clicking the "change_user_role" button', () => {
expect(wrapper.emitted('updateIsAdmin')).toEqual( beforeEach(async () => {
expect.arrayContaining([ spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
expect.arrayContaining([ spy.mockImplementation(() => Promise.resolve(true))
{ await wrapper.find('button').trigger('click')
userId: 1, await wrapper.vm.$nextTick()
isAdmin: expect.any(Date), })
},
]),
]),
)
})
it('toasts success message', () => { it('calls the modal', () => {
expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') expect(wrapper.emitted('showModal'))
expect(spy).toHaveBeenCalled()
})
describe('confirm role change with success', () => {
it('calls the API', () => {
expect(apolloMutateMock).toBeCalledWith(
expect.objectContaining({
mutation: setUserRole,
variables: {
userId: 1,
isAdmin: true,
},
}),
)
})
it('emits "updateIsAdmin"', () => {
expect(wrapper.emitted('updateIsAdmin')).toEqual(
expect.arrayContaining([
expect.arrayContaining([
{
userId: 1,
isAdmin: expect.any(Date),
},
]),
]),
)
})
it('toasts success message', () => {
expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo')
})
})
describe('confirm role change with error', () => {
beforeEach(async () => {
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
apolloMutateMock.mockRejectedValue({ message: 'Oh no!' })
await wrapper.find('button').trigger('click')
await wrapper.vm.$nextTick()
})
it('toasts an error message', () => {
expect(toastErrorSpy).toBeCalledWith('Oh no!')
})
})
}) })
}) })
}) })
}) })
describe('user is admin', () => { describe('user has role "admin"', () => {
beforeEach(() => { beforeEach(() => {
apolloMutateMock.mockResolvedValue({ apolloMutateMock.mockResolvedValue({
data: { data: {
@ -207,6 +245,10 @@ describe('ChangeUserRoleFormular', () => {
describe('change select to', () => { describe('change select to', () => {
describe('same role', () => { describe('same role', () => {
it('has "change_user_role" button disabled', () => {
expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(true)
})
it('does not call the API', () => { it('does not call the API', () => {
rolesToSelect.at(1).setSelected() rolesToSelect.at(1).setSelected()
expect(apolloMutateMock).not.toHaveBeenCalled() expect(apolloMutateMock).not.toHaveBeenCalled()
@ -218,33 +260,69 @@ describe('ChangeUserRoleFormular', () => {
rolesToSelect.at(0).setSelected() rolesToSelect.at(0).setSelected()
}) })
it('calls the API', () => { it('has "change_user_role" button enabled', () => {
expect(apolloMutateMock).toBeCalledWith( expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true)
expect.objectContaining({ expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(
mutation: setUserRole, false,
variables: {
userId: 1,
isAdmin: false,
},
}),
) )
}) })
it('emits "updateIsAdmin"', () => { describe('clicking the "change_user_role" button', () => {
expect(wrapper.emitted('updateIsAdmin')).toEqual( beforeEach(async () => {
expect.arrayContaining([ spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
expect.arrayContaining([ spy.mockImplementation(() => Promise.resolve(true))
{ await wrapper.find('button').trigger('click')
userId: 1, await wrapper.vm.$nextTick()
isAdmin: null, })
},
]),
]),
)
})
it('toasts success message', () => { it('calls the modal', () => {
expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') expect(wrapper.emitted('showModal'))
expect(spy).toHaveBeenCalled()
})
describe('confirm role change with success', () => {
it('calls the API', () => {
expect(apolloMutateMock).toBeCalledWith(
expect.objectContaining({
mutation: setUserRole,
variables: {
userId: 1,
isAdmin: false,
},
}),
)
})
it('emits "updateIsAdmin"', () => {
expect(wrapper.emitted('updateIsAdmin')).toEqual(
expect.arrayContaining([
expect.arrayContaining([
{
userId: 1,
isAdmin: null,
},
]),
]),
)
})
it('toasts success message', () => {
expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo')
})
})
describe('confirm role change with error', () => {
beforeEach(async () => {
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
apolloMutateMock.mockRejectedValue({ message: 'Oh no!' })
await wrapper.find('button').trigger('click')
await wrapper.vm.$nextTick()
})
it('toasts an error message', () => {
expect(toastErrorSpy).toBeCalledWith('Oh no!')
})
})
}) })
}) })
}) })

View File

@ -4,19 +4,23 @@
<div v-if="item.userId === $store.state.moderator.id" class="m-3 mb-4"> <div v-if="item.userId === $store.state.moderator.id" class="m-3 mb-4">
{{ $t('userRole.notChangeYourSelf') }} {{ $t('userRole.notChangeYourSelf') }}
</div> </div>
<div class="m-3"> <div v-else class="m-3">
<label for="role" class="mr-3">{{ $t('userRole.selectLabel') }}</label> <label for="role" class="mr-3">{{ $t('userRole.selectLabel') }}</label>
<b-form-select <b-form-select class="role-select" v-model="roleSelected" :options="roles" />
class="role-select" <div class="mt-3 mb-5">
v-model="roleSelected" <b-button
:options="roles" variant="danger"
:disabled="item.userId === $store.state.moderator.id" v-b-modal.user-role-modal
/> :disabled="currentRole === roleSelected"
@click="showModal()"
>
{{ $t('change_user_role') }}
</b-button>
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { setUserRole } from '../graphql/setUserRole' import { setUserRole } from '../graphql/setUserRole'
@ -35,6 +39,7 @@ export default {
}, },
data() { data() {
return { return {
currentRole: this.item.isAdmin ? rolesValues.admin : rolesValues.user,
roleSelected: this.item.isAdmin ? rolesValues.admin : rolesValues.user, roleSelected: this.item.isAdmin ? rolesValues.admin : rolesValues.user,
roles: [ roles: [
{ value: rolesValues.user, text: this.$t('userRole.selectRoles.user') }, { value: rolesValues.user, text: this.$t('userRole.selectRoles.user') },
@ -42,14 +47,35 @@ export default {
], ],
} }
}, },
watch: {
roleSelected(newRole, oldRole) {
if (newRole !== oldRole) {
this.setUserRole(newRole, oldRole)
}
},
},
methods: { methods: {
showModal() {
this.$bvModal
.msgBoxConfirm(
this.$t('overlay.changeUserRole.question', {
username: `${this.item.firstName} ${this.item.lastName}`,
newRole:
this.roleSelected === 'admin'
? this.$t('userRole.selectRoles.admin')
: this.$t('userRole.selectRoles.user'),
}),
{
cancelTitle: this.$t('overlay.cancel'),
centered: true,
hideHeaderClose: true,
title: this.$t('overlay.changeUserRole.title'),
okTitle: this.$t('overlay.changeUserRole.yes'),
okVariant: 'danger',
},
)
.then((okClicked) => {
if (okClicked) {
this.setUserRole(this.roleSelected, this.currentRole)
}
})
.catch((error) => {
this.toastError(error.message)
})
},
setUserRole(newRole, oldRole) { setUserRole(newRole, oldRole) {
this.$apollo this.$apollo
.mutate({ .mutate({

View File

@ -2,14 +2,18 @@ import { mount } from '@vue/test-utils'
import CreationFormular from './CreationFormular' import CreationFormular from './CreationFormular'
import { adminCreateContribution } from '../graphql/adminCreateContribution' import { adminCreateContribution } from '../graphql/adminCreateContribution'
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup' import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
import VueApollo from 'vue-apollo'
import { createMockClient } from 'mock-apollo-client'
import { adminOpenCreations } from '../graphql/adminOpenCreations'
const mockClient = createMockClient()
const apolloProvider = new VueApollo({
defaultClient: mockClient,
})
const localVue = global.localVue const localVue = global.localVue
localVue.use(VueApollo)
const apolloMutateMock = jest.fn().mockResolvedValue({
data: {
adminCreateContribution: [0, 0, 0],
},
})
const stateCommitMock = jest.fn() const stateCommitMock = jest.fn()
const mocks = { const mocks = {
@ -18,9 +22,6 @@ const mocks = {
const date = new Date(d) const date = new Date(d)
return date.toISOString().split('T')[0] return date.toISOString().split('T')[0]
}), }),
$apollo: {
mutate: apolloMutateMock,
},
$store: { $store: {
commit: stateCommitMock, commit: stateCommitMock,
}, },
@ -31,7 +32,8 @@ const propsData = {
creation: [], creation: [],
} }
const now = new Date(Date.now()) const now = new Date()
const getCreationDate = (sub) => { const getCreationDate = (sub) => {
const date = sub === 0 ? now : new Date(now.getFullYear(), now.getMonth() - sub, 1, 0) const date = sub === 0 ? now : new Date(now.getFullYear(), now.getMonth() - sub, 1, 0)
return date.toISOString().split('T')[0] return date.toISOString().split('T')[0]
@ -40,8 +42,43 @@ const getCreationDate = (sub) => {
describe('CreationFormular', () => { describe('CreationFormular', () => {
let wrapper let wrapper
const adminOpenCreationsMock = jest.fn()
const adminCreateContributionMock = jest.fn()
mockClient.setRequestHandler(
adminOpenCreations,
adminOpenCreationsMock.mockResolvedValue({
data: {
adminOpenCreations: [
{
month: new Date(now.getFullYear(), now.getMonth() - 2).getMonth(),
year: new Date(now.getFullYear(), now.getMonth() - 2).getFullYear(),
amount: '200',
},
{
month: new Date(now.getFullYear(), now.getMonth() - 1).getMonth(),
year: new Date(now.getFullYear(), now.getMonth() - 1).getFullYear(),
amount: '400',
},
{
month: now.getMonth(),
year: now.getFullYear(),
amount: '600',
},
],
},
}),
)
mockClient.setRequestHandler(
adminCreateContribution,
adminCreateContributionMock.mockResolvedValue({
data: {
adminCreateContribution: [0, 0, 0],
},
}),
)
const Wrapper = () => { const Wrapper = () => {
return mount(CreationFormular, { localVue, mocks, propsData }) return mount(CreationFormular, { localVue, mocks, propsData, apolloProvider })
} }
describe('mount', () => { describe('mount', () => {
@ -107,17 +144,12 @@ describe('CreationFormular', () => {
}) })
it('sends ... to apollo', () => { it('sends ... to apollo', () => {
expect(apolloMutateMock).toBeCalledWith( expect(adminCreateContributionMock).toBeCalledWith({
expect.objectContaining({ email: 'benjamin@bluemchen.de',
mutation: adminCreateContribution, creationDate: getCreationDate(2),
variables: { amount: 90,
email: 'benjamin@bluemchen.de', memo: 'Test create coins',
creationDate: getCreationDate(2), })
amount: 90,
memo: 'Test create coins',
},
}),
)
}) })
it('emits update-user-data', () => { it('emits update-user-data', () => {
@ -144,7 +176,7 @@ describe('CreationFormular', () => {
describe('sendForm with server error', () => { describe('sendForm with server error', () => {
beforeEach(async () => { beforeEach(async () => {
apolloMutateMock.mockRejectedValueOnce({ message: 'Ouch!' }) adminCreateContributionMock.mockRejectedValueOnce({ message: 'Ouch!' })
await wrapper.find('.test-submit').trigger('click') await wrapper.find('.test-submit').trigger('click')
}) })
@ -212,7 +244,7 @@ describe('CreationFormular', () => {
}) })
it('sends ... to apollo', () => { it('sends ... to apollo', () => {
expect(apolloMutateMock).toBeCalled() expect(adminCreateContributionMock).toBeCalled()
}) })
}) })
@ -275,7 +307,7 @@ describe('CreationFormular', () => {
}) })
it('sends mutation to apollo', () => { it('sends mutation to apollo', () => {
expect(apolloMutateMock).toBeCalled() expect(adminCreateContributionMock).toBeCalled()
}) })
it('toast success message', () => { it('toast success message', () => {

View File

@ -117,10 +117,6 @@ export default {
return {} return {}
}, },
}, },
creation: {
type: Array,
required: true,
},
}, },
data() { data() {
return { return {
@ -129,6 +125,7 @@ export default {
rangeMin: 0, rangeMin: 0,
rangeMax: 1000, rangeMax: 1000,
selected: '', selected: '',
userId: this.item.userId,
} }
}, },
methods: { methods: {
@ -136,7 +133,7 @@ export default {
// do we want to reset the memo everytime the month changes? // do we want to reset the memo everytime the month changes?
this.text = this.$t('creation_form.creation_for') + ' ' + name.short + ' ' + name.year this.text = this.$t('creation_form.creation_for') + ' ' + name.short + ' ' + name.year
this.rangeMin = 0 this.rangeMin = 0
this.rangeMax = name.creation this.rangeMax = Number(name.creation)
}, },
submitCreation() { submitCreation() {
this.$apollo this.$apollo
@ -167,6 +164,10 @@ export default {
this.$refs.creationForm.reset() this.$refs.creationForm.reset()
this.value = 0 this.value = 0
}) })
.finally(() => {
this.$apollo.queries.OpenCreations.refetch()
this.selected = ''
})
}, },
}, },
watch: { watch: {

View File

@ -1,43 +1,75 @@
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import CreationTransactionList from './CreationTransactionList' import CreationTransactionList from './CreationTransactionList'
import { toastErrorSpy } from '../../test/testSetup' import { toastErrorSpy } from '../../test/testSetup'
import VueApollo from 'vue-apollo'
import { createMockClient } from 'mock-apollo-client'
import { adminListContributions } from '../graphql/adminListContributions'
const mockClient = createMockClient()
const apolloProvider = new VueApollo({
defaultClient: mockClient,
})
const localVue = global.localVue const localVue = global.localVue
localVue.use(VueApollo)
const apolloQueryMock = jest.fn().mockResolvedValue({ const defaultData = () => {
data: { return {
creationTransactionList: { adminListContributions: {
contributionCount: 2, contributionCount: 2,
contributionList: [ contributionList: [
{ {
id: 1, id: 1,
amount: 5.8, firstName: 'Bibi',
createdAt: '2022-09-21T11:09:51.000Z', lastName: 'Bloxberg',
confirmedAt: null, userId: 99,
contributionDate: '2022-08-01T00:00:00.000Z', email: 'bibi@bloxberg.de',
memo: 'für deine Hilfe, Fräulein Rottenmeier', amount: 500,
memo: 'Danke für alles',
date: new Date(),
moderator: 1,
state: 'PENDING', state: 'PENDING',
creation: [500, 500, 500],
messagesCount: 0,
deniedBy: null,
deniedAt: null,
confirmedBy: null,
confirmedAt: null,
contributionDate: new Date(),
deletedBy: null,
deletedAt: null,
createdAt: new Date(),
}, },
{ {
id: 2, id: 2,
amount: '47', firstName: 'Räuber',
createdAt: '2022-09-21T11:09:28.000Z', lastName: 'Hotzenplotz',
confirmedAt: '2022-09-21T11:09:28.000Z', userId: 100,
contributionDate: '2022-08-01T00:00:00.000Z', email: 'raeuber@hotzenplotz.de',
memo: 'für deine Hilfe, Frau Holle', amount: 1000000,
state: 'CONFIRMED', memo: 'Gut Ergattert',
date: new Date(),
moderator: 1,
state: 'PENDING',
creation: [500, 500, 500],
messagesCount: 0,
deniedBy: null,
deniedAt: null,
confirmedBy: null,
confirmedAt: new Date(),
contributionDate: new Date(),
deletedBy: null,
deletedAt: null,
createdAt: new Date(),
}, },
], ],
}, },
}, }
}) }
const mocks = { const mocks = {
$d: jest.fn((t) => t), $d: jest.fn((t) => t),
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$apollo: {
query: apolloQueryMock,
},
} }
const propsData = { const propsData = {
@ -48,55 +80,53 @@ const propsData = {
describe('CreationTransactionList', () => { describe('CreationTransactionList', () => {
let wrapper let wrapper
const adminListContributionsMock = jest.fn()
mockClient.setRequestHandler(
adminListContributions,
adminListContributionsMock
.mockRejectedValueOnce({ message: 'Ouch!' })
.mockResolvedValue({ data: defaultData() }),
)
const Wrapper = () => { const Wrapper = () => {
return mount(CreationTransactionList, { localVue, mocks, propsData }) return mount(CreationTransactionList, { localVue, mocks, propsData, apolloProvider })
} }
describe('mount', () => { describe('mount', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks()
wrapper = Wrapper() wrapper = Wrapper()
}) })
it('sends query to Apollo when created', () => { describe('server error', () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
variables: {
currentPage: 1,
pageSize: 10,
order: 'DESC',
userId: 1,
},
}),
)
})
it('has two values for the transaction', () => {
expect(wrapper.find('tbody').findAll('tr').length).toBe(2)
})
describe('query transaction with error', () => {
beforeEach(() => {
apolloQueryMock.mockRejectedValue({ message: 'OUCH!' })
wrapper = Wrapper()
})
it('calls the API', () => {
expect(apolloQueryMock).toBeCalled()
})
it('toast error', () => { it('toast error', () => {
expect(toastErrorSpy).toBeCalledWith('OUCH!') expect(toastErrorSpy).toBeCalledWith('Ouch!')
}) })
}) })
describe('watch currentPage', () => { describe('sever success', () => {
beforeEach(async () => { it('sends query to Apollo when created', () => {
jest.clearAllMocks() expect(adminListContributionsMock).toBeCalledWith({
await wrapper.setData({ currentPage: 2 }) currentPage: 1,
pageSize: 10,
order: 'DESC',
userId: 1,
})
}) })
it('returns the string in normal order if reversed property is not true', () => { it('has two values for the transaction', () => {
expect(wrapper.vm.currentPage).toBe(2) expect(wrapper.find('tbody').findAll('tr').length).toBe(2)
})
describe('watch currentPage', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.setData({ currentPage: 2 })
})
it('returns the string in normal order if reversed property is not true', () => {
expect(wrapper.vm.currentPage).toBe(2)
})
}) })
}) })
}) })

View File

@ -42,7 +42,7 @@
</div> </div>
</template> </template>
<script> <script>
import { creationTransactionList } from '../graphql/creationTransactionList' import { adminListContributions } from '../graphql/adminListContributions'
export default { export default {
name: 'CreationTransactionList', name: 'CreationTransactionList',
props: { props: {
@ -92,33 +92,26 @@ export default {
], ],
} }
}, },
methods: { apollo: {
getTransactions() { AdminListContributions: {
this.$apollo query() {
.query({ return adminListContributions
query: creationTransactionList, },
variables: { variables() {
currentPage: this.currentPage, return {
pageSize: this.perPage, currentPage: this.currentPage,
order: 'DESC', pageSize: this.perPage,
userId: parseInt(this.userId), order: 'DESC',
}, userId: parseInt(this.userId),
}) }
.then((result) => { },
this.rows = result.data.creationTransactionList.contributionCount update({ adminListContributions }) {
this.items = result.data.creationTransactionList.contributionList this.rows = adminListContributions.contributionCount
}) this.items = adminListContributions.contributionList
.catch((error) => { },
this.toastError(error.message) error({ message }) {
}) this.toastError(message)
}, },
},
created() {
this.getTransactions()
},
watch: {
currentPage() {
this.getTransactions()
}, },
}, },
} }

View File

@ -35,6 +35,7 @@ const propsData = {
describe('DeletedUserFormular', () => { describe('DeletedUserFormular', () => {
let wrapper let wrapper
let spy
const Wrapper = () => { const Wrapper = () => {
return mount(DeletedUserFormular, { localVue, mocks, propsData }) return mount(DeletedUserFormular, { localVue, mocks, propsData })
@ -62,6 +63,10 @@ describe('DeletedUserFormular', () => {
it('shows a text that you cannot delete yourself', () => { it('shows a text that you cannot delete yourself', () => {
expect(wrapper.text()).toBe('removeNotSelf') expect(wrapper.text()).toBe('removeNotSelf')
}) })
it('has no "delete_user" button', () => {
expect(wrapper.find('button').exists()).toBe(false)
})
}) })
describe('delete other user', () => { describe('delete other user', () => {
@ -71,35 +76,32 @@ describe('DeletedUserFormular', () => {
userId: 1, userId: 1,
deletedAt: null, deletedAt: null,
}, },
static: true,
}) })
}) })
it('has a checkbox', () => {
expect(wrapper.find('input[type="checkbox"]').exists()).toBe(true)
})
it('shows the text "delete_user"', () => { it('shows the text "delete_user"', () => {
expect(wrapper.text()).toBe('delete_user') expect(wrapper.text()).toBe('delete_user')
}) })
describe('click on checkbox', () => { it('has a "delete_user" button', () => {
expect(wrapper.find('button').text()).toBe('delete_user')
})
describe('click on "delete_user" button', () => {
beforeEach(async () => { beforeEach(async () => {
await wrapper.find('input[type="checkbox"]').setChecked() spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
spy.mockImplementation(() => Promise.resolve(true))
await wrapper.find('button').trigger('click')
await wrapper.vm.$nextTick()
}) })
it('has a confirmation button', () => { it('calls the modal', () => {
expect(wrapper.find('button').exists()).toBe(true) expect(wrapper.emitted('showDeleteModal'))
}) expect(spy).toHaveBeenCalled()
it('has the button text "delete_user"', () => {
expect(wrapper.find('button').text()).toBe('delete_user')
}) })
describe('confirm delete with success', () => { describe('confirm delete with success', () => {
beforeEach(async () => {
await wrapper.find('button').trigger('click')
})
it('calls the API', () => { it('calls the API', () => {
expect(apolloMutateMock).toBeCalledWith( expect(apolloMutateMock).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
@ -123,32 +125,20 @@ describe('DeletedUserFormular', () => {
]), ]),
) )
}) })
it('unchecks the checkbox', () => {
expect(wrapper.find('input').attributes('checked')).toBe(undefined)
})
}) })
describe('confirm delete with error', () => { describe('confirm delete with error', () => {
beforeEach(async () => { beforeEach(async () => {
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
apolloMutateMock.mockRejectedValue({ message: 'Oh no!' }) apolloMutateMock.mockRejectedValue({ message: 'Oh no!' })
await wrapper.find('button').trigger('click') await wrapper.find('button').trigger('click')
await wrapper.vm.$nextTick()
}) })
it('toasts an error message', () => { it('toasts an error message', () => {
expect(toastErrorSpy).toBeCalledWith('Oh no!') expect(toastErrorSpy).toBeCalledWith('Oh no!')
}) })
}) })
describe('click on checkbox again', () => {
beforeEach(async () => {
await wrapper.find('input[type="checkbox"]').setChecked(false)
})
it('has no confirmation button anymore', () => {
expect(wrapper.find('button').exists()).toBe(false)
})
})
}) })
}) })
@ -162,37 +152,33 @@ describe('DeletedUserFormular', () => {
}) })
}) })
it('has a checkbox', () => {
expect(wrapper.find('input[type="checkbox"]').exists()).toBe(true)
})
it('shows the text "undelete_user"', () => { it('shows the text "undelete_user"', () => {
expect(wrapper.text()).toBe('undelete_user') expect(wrapper.text()).toBe('undelete_user')
}) })
describe('click on checkbox', () => { it('has a "undelete_user" button', () => {
expect(wrapper.find('button').text()).toBe('undelete_user')
})
describe('click on "undelete_user" button', () => {
beforeEach(async () => { beforeEach(async () => {
apolloMutateMock.mockResolvedValue({ apolloMutateMock.mockResolvedValue({
data: { data: {
unDeleteUser: null, unDeleteUser: null,
}, },
}) })
await wrapper.find('input[type="checkbox"]').setChecked() spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
spy.mockImplementation(() => Promise.resolve(true))
await wrapper.find('button').trigger('click')
await wrapper.vm.$nextTick()
}) })
it('has a confirmation button', () => { it('calls the modal', () => {
expect(wrapper.find('button').exists()).toBe(true) expect(wrapper.emitted('showUndeleteModal'))
}) expect(spy).toHaveBeenCalled()
it('has the button text "undelete_user"', () => {
expect(wrapper.find('button').text()).toBe('undelete_user')
}) })
describe('confirm recover with success', () => { describe('confirm recover with success', () => {
beforeEach(async () => {
await wrapper.find('button').trigger('click')
})
it('calls the API', () => { it('calls the API', () => {
expect(apolloMutateMock).toBeCalledWith( expect(apolloMutateMock).toBeCalledWith(
expect.objectContaining({ expect.objectContaining({
@ -205,7 +191,7 @@ describe('DeletedUserFormular', () => {
}) })
it('emits update deleted At', () => { it('emits update deleted At', () => {
expect(wrapper.emitted('updateDeletedAt')).toEqual( expect(wrapper.emitted('updateDeletedAt')).toMatchObject(
expect.arrayContaining([ expect.arrayContaining([
expect.arrayContaining([ expect.arrayContaining([
{ {
@ -216,10 +202,6 @@ describe('DeletedUserFormular', () => {
]), ]),
) )
}) })
it('unchecks the checkbox', () => {
expect(wrapper.find('input').attributes('checked')).toBe(undefined)
})
}) })
describe('confirm recover with error', () => { describe('confirm recover with error', () => {
@ -232,16 +214,6 @@ describe('DeletedUserFormular', () => {
expect(toastErrorSpy).toBeCalledWith('Oh no!') expect(toastErrorSpy).toBeCalledWith('Oh no!')
}) })
}) })
describe('click on checkbox again', () => {
beforeEach(async () => {
await wrapper.find('input[type="checkbox"]').setChecked(false)
})
it('has no confirmation button anymore', () => {
expect(wrapper.find('button').exists()).toBe(false)
})
})
}) })
}) })
}) })

View File

@ -4,15 +4,16 @@
{{ $t('removeNotSelf') }} {{ $t('removeNotSelf') }}
</div> </div>
<div v-else class="mt-5"> <div v-else class="mt-5">
<b-form-checkbox switch size="lg" v-model="checked">
<div>{{ item.deletedAt ? $t('undelete_user') : $t('delete_user') }}</div>
</b-form-checkbox>
<div class="mt-3 mb-5"> <div class="mt-3 mb-5">
<b-button v-if="checked && item.deletedAt === null" variant="danger" @click="deleteUser"> <b-button
v-if="!item.deletedAt"
variant="danger"
v-b-modal.delete-user-modal
@click="showDeleteModal()"
>
{{ $t('delete_user') }} {{ $t('delete_user') }}
</b-button> </b-button>
<b-button v-if="checked && item.deletedAt !== null" variant="success" @click="unDeleteUser"> <b-button v-else variant="success" v-b-modal.delete-user-modal @click="showUndeleteModal()">
{{ $t('undelete_user') }} {{ $t('undelete_user') }}
</b-button> </b-button>
</div> </div>
@ -31,12 +32,56 @@ export default {
required: true, required: true,
}, },
}, },
data() {
return {
checked: false,
}
},
methods: { methods: {
showDeleteModal() {
this.$bvModal
.msgBoxConfirm(
this.$t('overlay.deleteUser.question', {
username: `${this.item.firstName} ${this.item.lastName}`,
}),
{
cancelTitle: this.$t('overlay.cancel'),
centered: true,
hideHeaderClose: true,
title: this.$t('overlay.deleteUser.title'),
okTitle: this.$t('overlay.deleteUser.yes'),
okVariant: 'danger',
static: true,
},
)
.then((okClicked) => {
if (okClicked) {
this.deleteUser()
}
})
.catch((error) => {
this.toastError(error.message)
})
},
showUndeleteModal() {
this.$bvModal
.msgBoxConfirm(
this.$t('overlay.undeleteUser.question', {
username: `${this.item.firstName} ${this.item.lastName}`,
}),
{
cancelTitle: this.$t('overlay.cancel'),
centered: true,
hideHeaderClose: true,
title: this.$t('overlay.undeleteUser.title'),
okTitle: this.$t('overlay.undeleteUser.yes'),
okVariant: 'success',
},
)
.then((okClicked) => {
if (okClicked) {
this.unDeleteUser()
}
})
.catch((error) => {
this.toastError(error.message)
})
},
deleteUser() { deleteUser() {
this.$apollo this.$apollo
.mutate({ .mutate({
@ -50,7 +95,6 @@ export default {
userId: this.item.userId, userId: this.item.userId,
deletedAt: result.data.deleteUser, deletedAt: result.data.deleteUser,
}) })
this.checked = false
}) })
.catch((error) => { .catch((error) => {
this.toastError(error.message) this.toastError(error.message)
@ -69,7 +113,6 @@ export default {
userId: this.item.userId, userId: this.item.userId,
deletedAt: result.data.unDeleteUser, deletedAt: result.data.unDeleteUser,
}) })
this.checked = false
}) })
.catch((error) => { .catch((error) => {
this.toastError(error.message) this.toastError(error.message)

View File

@ -1,19 +1,18 @@
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import EditCreationFormular from './EditCreationFormular' import EditCreationFormular from './EditCreationFormular'
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup' import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
import VueApollo from 'vue-apollo'
import { createMockClient } from 'mock-apollo-client'
import { adminOpenCreations } from '../graphql/adminOpenCreations'
import { adminUpdateContribution } from '../graphql/adminUpdateContribution'
const mockClient = createMockClient()
const apolloProvider = new VueApollo({
defaultClient: mockClient,
})
const localVue = global.localVue const localVue = global.localVue
localVue.use(VueApollo)
const apolloMutateMock = jest.fn().mockResolvedValue({
data: {
adminUpdateContribution: {
creation: [0, 0, 0],
amount: 500,
date: new Date(),
memo: 'Test Schöpfung 2',
},
},
})
const stateCommitMock = jest.fn() const stateCommitMock = jest.fn()
@ -23,22 +22,18 @@ const mocks = {
const date = new Date(d) const date = new Date(d)
return date.toISOString().split('T')[0] return date.toISOString().split('T')[0]
}), }),
$apollo: {
mutate: apolloMutateMock,
},
$store: { $store: {
commit: stateCommitMock, commit: stateCommitMock,
}, },
} }
const now = new Date(Date.now()) const now = new Date()
const getCreationDate = (sub) => { const getCreationDate = (sub) => {
const date = sub === 0 ? now : new Date(now.getFullYear(), now.getMonth() - sub, 1, 0) const date = sub === 0 ? now : new Date(now.getFullYear(), now.getMonth() - sub, 1, 0)
return date.toISOString().split('T')[0] return date.toISOString().split('T')[0]
} }
const propsData = { const propsData = {
creation: [200, 400, 600],
creationUserData: { creationUserData: {
memo: 'Test schöpfung 1', memo: 'Test schöpfung 1',
amount: 100, amount: 100,
@ -46,20 +41,65 @@ const propsData = {
}, },
item: { item: {
id: 0, id: 0,
email: 'bob@baumeister.de', amount: '300',
contributionDate: `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`,
}, },
} }
const data = () => {
return { creation: ['1000', '1000', '400'] }
}
describe('EditCreationFormular', () => { describe('EditCreationFormular', () => {
let wrapper let wrapper
const adminUpdateContributionMock = jest.fn()
const adminOpenCreationsMock = jest.fn()
mockClient.setRequestHandler(
adminOpenCreations,
adminOpenCreationsMock.mockResolvedValue({
data: {
adminOpenCreations: [
{
month: new Date(now.getFullYear(), now.getMonth() - 2).getMonth(),
year: new Date(now.getFullYear(), now.getMonth() - 2).getFullYear(),
amount: '1000',
},
{
month: new Date(now.getFullYear(), now.getMonth() - 1).getMonth(),
year: new Date(now.getFullYear(), now.getMonth() - 1).getFullYear(),
amount: '1000',
},
{
month: now.getMonth(),
year: now.getFullYear(),
amount: '400',
},
],
},
}),
)
mockClient.setRequestHandler(
adminUpdateContribution,
adminUpdateContributionMock.mockResolvedValue({
data: {
adminUpdateContribution: {
amount: '600',
date: new Date(),
memo: 'This is my memo',
},
},
}),
)
const Wrapper = () => { const Wrapper = () => {
return mount(EditCreationFormular, { localVue, mocks, propsData }) return mount(EditCreationFormular, { localVue, mocks, propsData, data, apolloProvider })
} }
describe('mount', () => { describe('mount', () => {
beforeEach(() => { beforeEach(async () => {
wrapper = Wrapper() wrapper = Wrapper()
await wrapper.vm.$nextTick()
}) })
it('has a DIV element with the class.component-edit-creation-formular', () => { it('has a DIV element with the class.component-edit-creation-formular', () => {
@ -89,42 +129,16 @@ describe('EditCreationFormular', () => {
}) })
it('calls the API', () => { it('calls the API', () => {
expect(apolloMutateMock).toBeCalledWith( expect(adminUpdateContributionMock).toBeCalledWith({
expect.objectContaining({ id: 0,
variables: { creationDate: getCreationDate(0),
id: 0, amount: 500,
email: 'bob@baumeister.de', memo: 'Test Schöpfung 2',
creationDate: getCreationDate(0), })
amount: 500,
memo: 'Test Schöpfung 2',
},
}),
)
})
it('emits update-user-data', () => {
expect(wrapper.emitted('update-user-data')).toEqual([
[
{
id: 0,
email: 'bob@baumeister.de',
},
[0, 0, 0],
],
])
}) })
it('emits update-creation-data', () => { it('emits update-creation-data', () => {
expect(wrapper.emitted('update-creation-data')).toEqual([ expect(wrapper.emitted('update-creation-data')).toBeTruthy()
[
{
amount: 500,
date: expect.any(Date),
memo: 'Test Schöpfung 2',
row: expect.any(Object),
},
],
])
}) })
it('toasts a success message', () => { it('toasts a success message', () => {
@ -134,7 +148,7 @@ describe('EditCreationFormular', () => {
describe('change and save memo and value with error', () => { describe('change and save memo and value with error', () => {
beforeEach(async () => { beforeEach(async () => {
apolloMutateMock.mockRejectedValue({ message: 'Oh no!' }) adminUpdateContributionMock.mockRejectedValue({ message: 'Oh no!' })
await wrapper.find('input[type="number"]').setValue(500) await wrapper.find('input[type="number"]').setValue(500)
await wrapper.find('textarea').setValue('Test Schöpfung 2') await wrapper.find('textarea').setValue('Test Schöpfung 2')
await wrapper.find('.test-submit').trigger('click') await wrapper.find('.test-submit').trigger('click')

View File

@ -96,18 +96,14 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
creation: {
type: Array,
required: true,
},
}, },
data() { data() {
return { return {
text: !this.creationUserData.memo ? '' : this.creationUserData.memo, text: !this.creationUserData.memo ? '' : this.creationUserData.memo,
value: !this.creationUserData.amount ? 0 : Number(this.creationUserData.amount), value: !this.creationUserData.amount ? 0 : Number(this.creationUserData.amount),
rangeMin: 0, rangeMin: 0,
rangeMax: 1000, selected: this.selectedComputed,
selected: '', userId: this.item.userId,
} }
}, },
methods: { methods: {
@ -117,20 +113,13 @@ export default {
mutation: adminUpdateContribution, mutation: adminUpdateContribution,
variables: { variables: {
id: this.item.id, id: this.item.id,
email: this.item.email,
creationDate: this.selected.date, creationDate: this.selected.date,
amount: Number(this.value), amount: Number(this.value),
memo: this.text, memo: this.text,
}, },
}) })
.then((result) => { .then((result) => {
this.$emit('update-user-data', this.item, result.data.adminUpdateContribution.creation) this.$emit('update-creation-data')
this.$emit('update-creation-data', {
amount: Number(result.data.adminUpdateContribution.amount),
date: result.data.adminUpdateContribution.date,
memo: result.data.adminUpdateContribution.memo,
row: this.row,
})
this.toastSuccess( this.toastSuccess(
this.$t('creation_form.toasted_update', { this.$t('creation_form.toasted_update', {
value: this.value, value: this.value,
@ -149,15 +138,29 @@ export default {
// Den geschöpften Wert auf o setzen // Den geschöpften Wert auf o setzen
this.value = 0 this.value = 0
}) })
.finally(() => {
this.$apollo.queries.OpenCreations.refetch()
})
}, },
}, },
created() { computed: {
if (this.creationUserData.date) { creationIndex() {
const month = this.$d(new Date(this.creationUserData.date), 'month') const month = this.$d(new Date(this.item.contributionDate), 'month')
const index = this.radioOptions.findIndex((obj) => obj.item.short === month) return this.radioOptions.findIndex((obj) => {
this.selected = this.radioOptions[index].item return obj.item.short === month
this.rangeMax = Number(this.creation[index]) + Number(this.creationUserData.amount) })
} },
selectedComputed() {
return this.radioOptions[this.creationIndex].item
},
rangeMax() {
return Number(this.creation[this.creationIndex]) + Number(this.item.amount)
},
},
watch: {
selectedComputed() {
this.selected = this.selectedComputed
},
}, },
} }
</script> </script>

View File

@ -5,7 +5,6 @@ const localVue = global.localVue
const apolloMutateMock = jest.fn().mockResolvedValue({}) const apolloMutateMock = jest.fn().mockResolvedValue({})
const apolloQueryMock = jest.fn().mockResolvedValue({}) const apolloQueryMock = jest.fn().mockResolvedValue({})
const toggleDetailsMock = jest.fn()
const propsData = { const propsData = {
items: [ items: [
@ -17,7 +16,7 @@ const propsData = {
amount: 300, amount: 300,
memo: 'Aktives Grundeinkommen für Januar 2022', memo: 'Aktives Grundeinkommen für Januar 2022',
date: '2022-01-01T00:00:00.000Z', date: '2022-01-01T00:00:00.000Z',
moderator: 1, moderatorId: 1,
creation: [700, 1000, 1000], creation: [700, 1000, 1000],
__typename: 'PendingCreation', __typename: 'PendingCreation',
}, },
@ -29,7 +28,7 @@ const propsData = {
amount: 210, amount: 210,
memo: 'Aktives Grundeinkommen für Januar 2022', memo: 'Aktives Grundeinkommen für Januar 2022',
date: '2022-01-01T00:00:00.000Z', date: '2022-01-01T00:00:00.000Z',
moderator: null, moderatorId: null,
creation: [790, 1000, 1000], creation: [790, 1000, 1000],
__typename: 'PendingCreation', __typename: 'PendingCreation',
}, },
@ -41,7 +40,7 @@ const propsData = {
amount: 330, amount: 330,
memo: 'Aktives Grundeinkommen für Januar 2022', memo: 'Aktives Grundeinkommen für Januar 2022',
date: '2022-01-01T00:00:00.000Z', date: '2022-01-01T00:00:00.000Z',
moderator: 1, moderatorId: 1,
creation: [670, 1000, 1000], creation: [670, 1000, 1000],
__typename: 'PendingCreation', __typename: 'PendingCreation',
}, },
@ -83,7 +82,7 @@ const mocks = {
$store: { $store: {
state: { state: {
moderator: { moderator: {
id: 0, id: 1,
name: 'test moderator', name: 'test moderator',
}, },
}, },
@ -132,14 +131,6 @@ describe('OpenCreationsTable', () => {
}) })
}) })
describe('call updateUserData', () => {
it('user creations has updated data', async () => {
wrapper.vm.updateUserData(propsData.items[0], [444, 555, 666])
await wrapper.vm.$nextTick()
expect(wrapper.vm.items[0].creation).toEqual([444, 555, 666])
})
})
describe('call updateState', () => { describe('call updateState', () => {
beforeEach(() => { beforeEach(() => {
wrapper.vm.updateState(4) wrapper.vm.updateState(4)
@ -149,40 +140,5 @@ describe('OpenCreationsTable', () => {
expect(wrapper.vm.$root.$emit('update-state', 4)).toBeTruthy() expect(wrapper.vm.$root.$emit('update-state', 4)).toBeTruthy()
}) })
}) })
describe('call updateCreationData', () => {
const date = new Date()
beforeEach(() => {
wrapper.vm.updateCreationData({
amount: Number(80.0),
date: date,
memo: 'Test memo',
row: {
item: {},
detailsShowing: false,
toggleDetails: toggleDetailsMock,
},
})
})
it('emits update-state', () => {
expect(
wrapper.vm.$emit('update-contributions', {
amount: Number(80.0),
date: date,
memo: 'Test memo',
row: {
item: {},
detailsShowing: false,
toggleDetails: toggleDetailsMock,
},
}),
).toBeTruthy()
})
it('calls toggleDetails', () => {
expect(toggleDetailsMock).toBeCalled()
})
})
}) })
}) })

View File

@ -27,9 +27,10 @@
<template #cell(editCreation)="row"> <template #cell(editCreation)="row">
<div v-if="!myself(row.item)"> <div v-if="!myself(row.item)">
<b-button <b-button
v-if="row.item.moderator" v-if="row.item.moderatorId"
variant="info" variant="info"
size="md" size="md"
:index="0"
@click="rowToggleDetails(row, 0)" @click="rowToggleDetails(row, 0)"
class="mr-2" class="mr-2"
> >
@ -89,14 +90,13 @@
@row-toggle-details="rowToggleDetails(row, 0)" @row-toggle-details="rowToggleDetails(row, 0)"
> >
<template #show-creation> <template #show-creation>
<div v-if="row.item.moderator"> <div v-if="row.item.moderatorId">
<edit-creation-formular <edit-creation-formular
type="singleCreation" type="singleCreation"
:creation="row.item.creation"
:item="row.item" :item="row.item"
:row="row" :row="row"
:creationUserData="creationUserData" :creationUserData="creationUserData"
@update-creation-data="updateCreationData" @update-creation-data="$emit('update-contributions')"
/> />
</div> </div>
<div v-else> <div v-else>
@ -104,7 +104,6 @@
:contributionId="row.item.id" :contributionId="row.item.id"
:contributionState="row.item.state" :contributionState="row.item.state"
@update-state="updateState" @update-state="updateState"
@update-user-data="updateUserData"
/> />
</div> </div>
</template> </template>
@ -146,22 +145,9 @@ export default {
required: true, required: true,
}, },
}, },
data() {
return {
creationUserData: {
amount: null,
date: null,
memo: null,
moderator: null,
},
}
},
methods: { methods: {
myself(item) { myself(item) {
return ( return item.userId === this.$store.state.moderator.id
`${item.firstName} ${item.lastName}` ===
`${this.$store.state.moderator.firstName} ${this.$store.state.moderator.lastName}`
)
}, },
getStatusIcon(status) { getStatusIcon(status) {
return iconMap[status] ? iconMap[status] : 'default-icon' return iconMap[status] ? iconMap[status] : 'default-icon'
@ -174,16 +160,6 @@ export default {
if (item.state === 'IN_PROGRESS') return 'table-primary' if (item.state === 'IN_PROGRESS') return 'table-primary'
if (item.state === 'PENDING') return 'table-primary' if (item.state === 'PENDING') return 'table-primary'
}, },
updateCreationData(data) {
const row = data.row
this.$emit('update-contributions', data)
delete data.row
this.creationUserData = { ...this.creationUserData, ...data }
row.toggleDetails()
},
updateUserData(rowItem, newCreation) {
rowItem.creation = newCreation
},
updateState(id) { updateState(id) {
this.$emit('update-state', id) this.$emit('update-state', id)
}, },

View File

@ -1,17 +1,19 @@
import gql from 'graphql-tag' import gql from 'graphql-tag'
export const adminListAllContributions = gql` export const adminListContributions = gql`
query ( query (
$currentPage: Int = 1 $currentPage: Int = 1
$pageSize: Int = 25 $pageSize: Int = 25
$order: Order = DESC $order: Order = DESC
$statusFilter: [ContributionStatus!] $statusFilter: [ContributionStatus!]
$userId: Int
) { ) {
adminListAllContributions( adminListContributions(
currentPage: $currentPage currentPage: $currentPage
pageSize: $pageSize pageSize: $pageSize
order: $order order: $order
statusFilter: $statusFilter statusFilter: $statusFilter
userId: $userId
) { ) {
contributionCount contributionCount
contributionList { contributionList {
@ -30,6 +32,8 @@ export const adminListAllContributions = gql`
deniedBy deniedBy
deletedAt deletedAt
deletedBy deletedBy
moderatorId
userId
} }
} }
} }

View File

@ -0,0 +1,11 @@
import gql from 'graphql-tag'
export const adminOpenCreations = gql`
query ($userId: Int!) {
adminOpenCreations(userId: $userId) {
year
month
amount
}
}
`

View File

@ -1,18 +1,11 @@
import gql from 'graphql-tag' import gql from 'graphql-tag'
export const adminUpdateContribution = gql` export const adminUpdateContribution = gql`
mutation ($id: Int!, $email: String!, $amount: Decimal!, $memo: String!, $creationDate: String!) { mutation ($id: Int!, $amount: Decimal!, $memo: String!, $creationDate: String!) {
adminUpdateContribution( adminUpdateContribution(id: $id, amount: $amount, memo: $memo, creationDate: $creationDate) {
id: $id
email: $email
amount: $amount
memo: $memo
creationDate: $creationDate
) {
amount amount
date date
memo memo
creation
} }
} }
` `

View File

@ -1,23 +0,0 @@
import gql from 'graphql-tag'
export const creationTransactionList = gql`
query ($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC, $userId: Int!) {
creationTransactionList(
currentPage: $currentPage
pageSize: $pageSize
order: $order
userId: $userId
) {
contributionCount
contributionList {
id
amount
createdAt
confirmedAt
contributionDate
memo
state
}
}
}
`

View File

@ -1,6 +1,7 @@
{ {
"all_emails": "Alle Nutzer", "all_emails": "Alle Nutzer",
"back": "zurück", "back": "zurück",
"change_user_role": "Nutzerrolle ändern",
"chat": "Chat", "chat": "Chat",
"contributionLink": { "contributionLink": {
"amount": "Betrag", "amount": "Betrag",
@ -114,6 +115,11 @@
"open_creations": "Offene Schöpfungen", "open_creations": "Offene Schöpfungen",
"overlay": { "overlay": {
"cancel": "Abbrechen", "cancel": "Abbrechen",
"changeUserRole": {
"question": "Willst du die Rolle von {username} wirklich zu {newRole} ändern?",
"title": "Nutzerrolle ändern",
"yes": "Ja, Nutzerrolle ändern"
},
"confirm": { "confirm": {
"question": "Willst du diesen Gemeinwohl-Beitrag wirklich bestätigen und gutschreiben?", "question": "Willst du diesen Gemeinwohl-Beitrag wirklich bestätigen und gutschreiben?",
"text": "Nach dem Speichern ist der Datensatz nicht mehr änderbar. Bitte überprüfe genau, dass alles stimmt.", "text": "Nach dem Speichern ist der Datensatz nicht mehr änderbar. Bitte überprüfe genau, dass alles stimmt.",
@ -126,11 +132,21 @@
"title": "Gemeinwohl-Beitrag löschen!", "title": "Gemeinwohl-Beitrag löschen!",
"yes": "Ja, Beitrag löschen!" "yes": "Ja, Beitrag löschen!"
}, },
"deleteUser": {
"question": "Willst du {username} wirklich löschen?",
"title": "Nutzer löschen",
"yes": "Ja, Nutzer löschen"
},
"deny": { "deny": {
"question": "Willst du diesen Gemeinwohl-Beitrag wirklich ablehnen?", "question": "Willst du diesen Gemeinwohl-Beitrag wirklich ablehnen?",
"text": "Nach dem Speichern ist der Datensatz nicht mehr änderbar und kann auch nicht mehr gelöscht werden. Bitte überprüfe genau, dass alles stimmt.", "text": "Nach dem Speichern ist der Datensatz nicht mehr änderbar und kann auch nicht mehr gelöscht werden. Bitte überprüfe genau, dass alles stimmt.",
"title": "Gemeinwohl-Beitrag ablehnen!", "title": "Gemeinwohl-Beitrag ablehnen!",
"yes": "Ja, Beitrag ablehnen und speichern!" "yes": "Ja, Beitrag ablehnen und speichern!"
},
"undeleteUser": {
"question": "Willst du wirklich {username} wiederherstellen?",
"title": "Nutzer wiederherstellen",
"yes": "Ja, Nutzer wiederherstellen"
} }
}, },
"redeemed": "eingelöst", "redeemed": "eingelöst",

View File

@ -1,6 +1,7 @@
{ {
"all_emails": "All users", "all_emails": "All users",
"back": "back", "back": "back",
"change_user_role": "Change user role",
"chat": "Chat", "chat": "Chat",
"contributionLink": { "contributionLink": {
"amount": "Amount", "amount": "Amount",
@ -39,7 +40,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",
@ -114,6 +115,11 @@
"open_creations": "Open creations", "open_creations": "Open creations",
"overlay": { "overlay": {
"cancel": "Cancel", "cancel": "Cancel",
"changeUserRole": {
"question": "Do you really want to change {username}'s role to {newRole}?",
"title": "Change user role",
"yes": "Yes, change user role"
},
"confirm": { "confirm": {
"question": "Do you really want to carry out and finally save this pre-stored creation?", "question": "Do you really want to carry out and finally save this pre-stored creation?",
"text": "After saving, the record can no longer be changed. Please check carefully that everything is correct.", "text": "After saving, the record can no longer be changed. Please check carefully that everything is correct.",
@ -126,11 +132,21 @@
"title": "Delete creation!", "title": "Delete creation!",
"yes": "Yes, delete and save creation!" "yes": "Yes, delete and save creation!"
}, },
"deleteUser": {
"question": "Do you really want to delete {username}?",
"title": "Delete user",
"yes": "Yes, delete user"
},
"deny": { "deny": {
"question": "Do you really want to carry out and finally save this pre-stored creation?", "question": "Do you really want to carry out and finally save this pre-stored creation?",
"text": "After saving, the record can no longer be changed or deleted. Please check carefully that everything is correct.", "text": "After saving, the record can no longer be changed or deleted. Please check carefully that everything is correct.",
"title": "Reject creation!", "title": "Reject creation!",
"yes": "Yes, reject and save creation!" "yes": "Yes, reject and save creation!"
},
"undeleteUser": {
"question": "Do you really want to undelete {username}",
"title": "Undelete user",
"yes": "Yes,undelete user"
} }
}, },
"redeemed": "redeemed", "redeemed": "redeemed",

View File

@ -1,9 +1,11 @@
import { adminOpenCreations } from '../graphql/adminOpenCreations'
export const creationMonths = { export const creationMonths = {
props: { data() {
creation: { return {
type: Array, creation: [1000, 1000, 1000],
default: () => [1000, 1000, 1000], userId: 0,
}, }
}, },
computed: { computed: {
creationDates() { creationDates() {
@ -38,4 +40,23 @@ export const creationMonths = {
return this.creationDates.map((date) => this.$d(date, 'monthShort')).join(' | ') return this.creationDates.map((date) => this.$d(date, 'monthShort')).join(' | ')
}, },
}, },
apollo: {
OpenCreations: {
query() {
return adminOpenCreations
},
variables() {
return {
userId: this.userId,
}
},
fetchPolicy: 'no-cache',
update({ adminOpenCreations }) {
this.creation = adminOpenCreations.map((obj) => obj.amount)
},
error({ message }) {
this.toastError(message)
},
},
},
} }

View File

@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'
import CreationConfirm from './CreationConfirm' import CreationConfirm from './CreationConfirm'
import { adminDeleteContribution } from '../graphql/adminDeleteContribution' import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
import { denyContribution } from '../graphql/denyContribution' import { denyContribution } from '../graphql/denyContribution'
import { adminListAllContributions } from '../graphql/adminListAllContributions' import { adminListContributions } from '../graphql/adminListContributions'
import { confirmContribution } from '../graphql/confirmContribution' import { confirmContribution } from '../graphql/confirmContribution'
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup' import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
import VueApollo from 'vue-apollo' import VueApollo from 'vue-apollo'
@ -38,7 +38,7 @@ const mocks = {
const defaultData = () => { const defaultData = () => {
return { return {
adminListAllContributions: { adminListContributions: {
contributionCount: 2, contributionCount: 2,
contributionList: [ contributionList: [
{ {
@ -92,14 +92,14 @@ const defaultData = () => {
describe('CreationConfirm', () => { describe('CreationConfirm', () => {
let wrapper let wrapper
const adminListAllContributionsMock = jest.fn() const adminListContributionsMock = jest.fn()
const adminDeleteContributionMock = jest.fn() const adminDeleteContributionMock = jest.fn()
const adminDenyContributionMock = jest.fn() const adminDenyContributionMock = jest.fn()
const confirmContributionMock = jest.fn() const confirmContributionMock = jest.fn()
mockClient.setRequestHandler( mockClient.setRequestHandler(
adminListAllContributions, adminListContributions,
adminListAllContributionsMock adminListContributionsMock
.mockRejectedValueOnce({ message: 'Ouch!' }) .mockRejectedValueOnce({ message: 'Ouch!' })
.mockResolvedValue({ data: defaultData() }), .mockResolvedValue({ data: defaultData() }),
) )
@ -337,7 +337,7 @@ describe('CreationConfirm', () => {
}) })
it('refetches contributions with proper filter', () => { it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({ expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1, currentPage: 1,
order: 'DESC', order: 'DESC',
pageSize: 25, pageSize: 25,
@ -352,7 +352,7 @@ describe('CreationConfirm', () => {
}) })
it('refetches contributions with proper filter', () => { it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({ expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1, currentPage: 1,
order: 'DESC', order: 'DESC',
pageSize: 25, pageSize: 25,
@ -368,7 +368,7 @@ describe('CreationConfirm', () => {
}) })
it('refetches contributions with proper filter', () => { it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({ expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1, currentPage: 1,
order: 'DESC', order: 'DESC',
pageSize: 25, pageSize: 25,
@ -384,7 +384,7 @@ describe('CreationConfirm', () => {
}) })
it('refetches contributions with proper filter', () => { it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({ expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1, currentPage: 1,
order: 'DESC', order: 'DESC',
pageSize: 25, pageSize: 25,
@ -400,7 +400,7 @@ describe('CreationConfirm', () => {
}) })
it('refetches contributions with proper filter', () => { it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({ expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1, currentPage: 1,
order: 'DESC', order: 'DESC',
pageSize: 25, pageSize: 25,

View File

@ -44,7 +44,7 @@
:fields="fields" :fields="fields"
@show-overlay="showOverlay" @show-overlay="showOverlay"
@update-state="updateStatus" @update-state="updateStatus"
@update-contributions="$apollo.queries.AllContributions.refetch()" @update-contributions="$apollo.queries.ListAllContributions.refetch()"
/> />
<b-pagination <b-pagination
@ -85,7 +85,7 @@
<script> <script>
import Overlay from '../components/Overlay' import Overlay from '../components/Overlay'
import OpenCreationsTable from '../components/Tables/OpenCreationsTable' import OpenCreationsTable from '../components/Tables/OpenCreationsTable'
import { adminListAllContributions } from '../graphql/adminListAllContributions' import { adminListContributions } from '../graphql/adminListContributions'
import { adminDeleteContribution } from '../graphql/adminDeleteContribution' import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
import { confirmContribution } from '../graphql/confirmContribution' import { confirmContribution } from '../graphql/confirmContribution'
import { denyContribution } from '../graphql/denyContribution' import { denyContribution } from '../graphql/denyContribution'
@ -212,7 +212,7 @@ export default {
return this.formatDateOrDash(value) return this.formatDateOrDash(value)
}, },
}, },
{ key: 'moderator', label: this.$t('moderator') }, { key: 'moderatorId', label: this.$t('moderator') },
{ key: 'editCreation', label: this.$t('chat') }, { key: 'editCreation', label: this.$t('chat') },
{ key: 'confirm', label: this.$t('save') }, { key: 'confirm', label: this.$t('save') },
], ],
@ -397,7 +397,7 @@ export default {
apollo: { apollo: {
ListAllContributions: { ListAllContributions: {
query() { query() {
return adminListAllContributions return adminListContributions
}, },
variables() { variables() {
return { return {
@ -407,11 +407,11 @@ export default {
} }
}, },
fetchPolicy: 'no-cache', fetchPolicy: 'no-cache',
update({ adminListAllContributions }) { update({ adminListContributions }) {
this.rows = adminListAllContributions.contributionCount this.rows = adminListContributions.contributionCount
this.items = adminListAllContributions.contributionList this.items = adminListContributions.contributionList
if (this.statusFilter === FILTER_TAB_MAP[0]) { if (this.statusFilter === FILTER_TAB_MAP[0]) {
this.$store.commit('setOpenCreations', adminListAllContributions.contributionCount) this.$store.commit('setOpenCreations', adminListContributions.contributionCount)
} }
}, },
error({ message }) { error({ message }) {

View File

@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import Overview from './Overview' import Overview from './Overview'
import { adminListAllContributions } from '../graphql/adminListAllContributions' import { adminListContributions } from '../graphql/adminListContributions'
import VueApollo from 'vue-apollo' import VueApollo from 'vue-apollo'
import { createMockClient } from 'mock-apollo-client' import { createMockClient } from 'mock-apollo-client'
import { toastErrorSpy } from '../../test/testSetup' import { toastErrorSpy } from '../../test/testSetup'
@ -30,7 +30,7 @@ const mocks = {
const defaultData = () => { const defaultData = () => {
return { return {
adminListAllContributions: { adminListContributions: {
contributionCount: 2, contributionCount: 2,
contributionList: [ contributionList: [
{ {
@ -42,7 +42,7 @@ const defaultData = () => {
amount: 500, amount: 500,
memo: 'Danke für alles', memo: 'Danke für alles',
date: new Date(), date: new Date(),
moderator: 1, moderatorId: 1,
state: 'PENDING', state: 'PENDING',
creation: [500, 500, 500], creation: [500, 500, 500],
messagesCount: 0, messagesCount: 0,
@ -64,7 +64,7 @@ const defaultData = () => {
amount: 1000000, amount: 1000000,
memo: 'Gut Ergattert', memo: 'Gut Ergattert',
date: new Date(), date: new Date(),
moderator: 1, moderatorId: 1,
state: 'PENDING', state: 'PENDING',
creation: [500, 500, 500], creation: [500, 500, 500],
messagesCount: 0, messagesCount: 0,
@ -84,11 +84,11 @@ const defaultData = () => {
describe('Overview', () => { describe('Overview', () => {
let wrapper let wrapper
const adminListAllContributionsMock = jest.fn() const adminListContributionsMock = jest.fn()
mockClient.setRequestHandler( mockClient.setRequestHandler(
adminListAllContributions, adminListContributions,
adminListAllContributionsMock adminListContributionsMock
.mockRejectedValueOnce({ message: 'Ouch!' }) .mockRejectedValueOnce({ message: 'Ouch!' })
.mockResolvedValue({ data: defaultData() }), .mockResolvedValue({ data: defaultData() }),
) )
@ -109,8 +109,8 @@ describe('Overview', () => {
}) })
}) })
it('calls the adminListAllContributions query', () => { it('calls the adminListContributions query', () => {
expect(adminListAllContributionsMock).toBeCalledWith({ expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1, currentPage: 1,
order: 'DESC', order: 'DESC',
pageSize: 25, pageSize: 25,

View File

@ -31,7 +31,7 @@
</div> </div>
</template> </template>
<script> <script>
import { adminListAllContributions } from '../graphql/adminListAllContributions' import { adminListContributions } from '../graphql/adminListContributions'
export default { export default {
name: 'overview', name: 'overview',
@ -43,7 +43,7 @@ export default {
apollo: { apollo: {
AllContributions: { AllContributions: {
query() { query() {
return adminListAllContributions return adminListContributions
}, },
variables() { variables() {
// may be at some point we need a pagination here // may be at some point we need a pagination here
@ -51,8 +51,8 @@ export default {
statusFilter: this.statusFilter, statusFilter: this.statusFilter,
} }
}, },
update({ adminListAllContributions }) { update({ adminListContributions }) {
this.$store.commit('setOpenCreations', adminListAllContributions.contributionCount) this.$store.commit('setOpenCreations', adminListContributions.contributionCount)
}, },
error({ message }) { error({ message }) {
this.toastError(message) this.toastError(message)

View File

@ -25,7 +25,7 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
email: 'benjamin@bluemchen.de', email: 'benjamin@bluemchen.de',
creation: [1000, 1000, 1000], creation: [1000, 1000, 1000],
emailChecked: true, emailChecked: true,
deletedAt: null, deletedAt: new Date(),
}, },
{ {
userId: 3, userId: 3,
@ -243,6 +243,17 @@ describe('UserSearch', () => {
}) })
}) })
describe('recover user', () => {
const userId = 2
beforeEach(() => {
wrapper.findComponent({ name: 'SearchUserTable' }).vm.$emit('updateDeletedAt', userId, null)
})
it('toasts a success message', () => {
expect(toastSuccessSpy).toBeCalledWith('user_recovered')
})
})
describe('apollo returns error', () => { describe('apollo returns error', () => {
beforeEach(() => { beforeEach(() => {
apolloQueryMock.mockRejectedValue({ apolloQueryMock.mockRejectedValue({

View File

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

View File

@ -55,7 +55,7 @@ module.exports = {
'import/named': 'error', 'import/named': 'error',
'import/namespace': 'error', 'import/namespace': 'error',
'import/no-absolute-path': 'error', 'import/no-absolute-path': 'error',
'import/no-cycle': 'error', 'import/no-cycle': 'off',
'import/no-dynamic-require': 'error', 'import/no-dynamic-require': 'error',
'import/no-internal-modules': 'off', 'import/no-internal-modules': 'off',
'import/no-relative-packages': 'error', 'import/no-relative-packages': 'error',
@ -76,7 +76,30 @@ module.exports = {
'import/no-named-default': 'error', 'import/no-named-default': 'error',
'import/no-namespace': 'error', 'import/no-namespace': 'error',
'import/no-unassigned-import': 'error', 'import/no-unassigned-import': 'error',
'import/order': 'error', 'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
'newlines-between': 'always',
pathGroups: [
{
pattern: '@?*/**',
group: 'external',
position: 'after',
},
{
pattern: '@/**',
group: 'external',
position: 'after',
},
],
alphabetize: {
order: 'asc' /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */,
caseInsensitive: true /* ignore case. Options: [true, false] */,
},
distinctGroup: true,
},
],
'import/prefer-default-export': 'off', // TODO 'import/prefer-default-export': 'off', // TODO
// n // n
'n/handle-callback-err': 'error', 'n/handle-callback-err': 'error',
@ -101,7 +124,7 @@ module.exports = {
'n/shebang': 'error', 'n/shebang': 'error',
'n/callback-return': 'error', 'n/callback-return': 'error',
'n/exports-style': 'error', 'n/exports-style': 'error',
'n/file-extension-in-import': ['error', 'never'], 'n/file-extension-in-import': 'off',
'n/global-require': 'error', 'n/global-require': 'error',
'n/no-mixed-requires': 'error', 'n/no-mixed-requires': 'error',
'n/no-process-env': 'error', 'n/no-process-env': 'error',
@ -148,6 +171,8 @@ module.exports = {
'no-void': ['error', { allowAsStatement: true }], 'no-void': ['error', { allowAsStatement: true }],
// ignore prefer-regexp-exec rule to allow string.match(regex) // ignore prefer-regexp-exec rule to allow string.match(regex)
'@typescript-eslint/prefer-regexp-exec': 'off', '@typescript-eslint/prefer-regexp-exec': 'off',
// this should not run on ts files: https://github.com/import-js/eslint-plugin-import/issues/2215#issuecomment-911245486
'import/unambiguous': 'off',
}, },
parserOptions: { parserOptions: {
tsconfigRootDir: './', tsconfigRootDir: './',

View File

@ -0,0 +1 @@
declare module 'klicktipp-api'

View File

@ -5,6 +5,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: 85,
},
},
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 .", "lint": "eslint --max-warnings=0 .",
"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"
@ -72,6 +72,7 @@
"faker": "^5.5.3", "faker": "^5.5.3",
"graphql-tag": "^2.12.6", "graphql-tag": "^2.12.6",
"jest": "^27.2.4", "jest": "^27.2.4",
"klicktipp-api": "^1.0.2",
"nodemon": "^2.0.7", "nodemon": "^2.0.7",
"prettier": "^2.3.1", "prettier": "^2.3.1",
"ts-jest": "^27.0.5", "ts-jest": "^27.0.5",

View File

@ -3,8 +3,8 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-argument */
import axios from 'axios' import axios from 'axios'
import { backendLogger as logger } from '@/server/logger'
import LogError from '@/server/LogError' import LogError from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export const apiPost = async (url: string, payload: unknown): Promise<any> => { export const apiPost = async (url: string, payload: unknown): Promise<any> => {

View File

@ -1,11 +1,14 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { KlicktippConnector } from './klicktippConnector'
import CONFIG from '@/config' import CONFIG from '@/config'
// eslint-disable-next-line import/no-relative-parent-imports
import KlicktippConnector from 'klicktipp-api'
const klicktippConnector = new KlicktippConnector() const klicktippConnector = new KlicktippConnector()
export const klicktippSignIn = async ( export const klicktippSignIn = async (

View File

@ -1,624 +0,0 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import axios, { AxiosRequestConfig, Method } from 'axios'
export class KlicktippConnector {
private baseURL: string
private sessionName: string
private sessionId: string
private error: string
constructor(service?: string) {
this.baseURL = service !== undefined ? service : 'https://api.klicktipp.com'
this.sessionName = ''
this.sessionId = ''
}
/**
* Get last error
*
* @return string an error description of the last error
*/
getLastError(): string {
const result = this.error
return result
}
/**
* login
*
* @param username The login name of the user to login.
* @param password The password of the user.
* @return TRUE on success
*/
async login(username: string, password: string): Promise<boolean> {
if (!(username.length > 0 && password.length > 0)) {
throw new Error('Klicktipp Login failed: Illegal Arguments')
}
const res = await this.httpRequest('/account/login', 'POST', { username, password }, false)
if (!res.isAxiosError) {
this.sessionId = res.data.sessid
this.sessionName = res.data.session_name
return true
}
throw new Error(`Klicktipp Login failed: ${res.response.statusText}`)
}
/**
* Logs out the user currently logged in.
*
* @return TRUE on success
*/
async logout(): Promise<boolean> {
const res = await this.httpRequest('/account/logout', 'POST')
if (!res.isAxiosError) {
this.sessionId = ''
this.sessionName = ''
return true
}
throw new Error(`Klicktipp Logout failed: ${res.response.statusText}`)
}
/**
* Get all subscription processes (lists) of the logged in user. Requires to be logged in.
*
* @return A associative obeject <list id> => <list name>
*/
async subscriptionProcessIndex(): Promise<any> {
const res = await this.httpRequest('/list', 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscription process index failed: ${res.response.statusText}`)
}
/**
* Get subscription process (list) definition. Requires to be logged in.
*
* @param listid The id of the subscription process
*
* @return An object representing the Klicktipp subscription process.
*/
async subscriptionProcessGet(listid: string): Promise<any> {
if (!listid || listid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// retrieve
const res = await this.httpRequest(`/subscriber/${listid}`, 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscription process get failed: ${res.response.statusText}`)
}
/**
* Get subscription process (list) redirection url for given subscription.
*
* @param listid The id of the subscription process.
* @param email The email address of the subscriber.
*
* @return A redirection url as defined in the subscription process.
*/
async subscriptionProcessRedirect(listid: string, email: string): Promise<any> {
if (!listid || listid === '' || !email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// update
const data = { listid, email }
const res = await this.httpRequest('/list/redirect', 'POST', data)
if (!res.isAxiosError) {
return res.data
}
throw new Error(
`Klicktipp Subscription process get redirection url failed: ${res.response.statusText}`,
)
}
/**
* Get all manual tags of the logged in user. Requires to be logged in.
*
* @return A associative object <tag id> => <tag name>
*/
async tagIndex(): Promise<any> {
const res = await this.httpRequest('/tag', 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Tag index failed: ${res.response.statusText}`)
}
/**
* Get a tag definition. Requires to be logged in.
*
* @param tagid The tag id.
*
* @return An object representing the Klicktipp tag object.
*/
async tagGet(tagid: string): Promise<any> {
if (!tagid || tagid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
const res = await this.httpRequest(`/tag/${tagid}`, 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Tag get failed: ${res.response.statusText}`)
}
/**
* Create a new manual tag. Requires to be logged in.
*
* @param name The name of the tag.
* @param text (optional) An additional description of the tag.
*
* @return The id of the newly created tag or false if failed.
*/
async tagCreate(name: string, text?: string): Promise<boolean> {
if (!name || name === '') {
throw new Error('Klicktipp Illegal Arguments')
}
const data = {
name,
text: text !== undefined ? text : '',
}
const res = await this.httpRequest('/tag', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Tag creation failed: ${res.response.statusText}`)
}
/**
* Updates a tag. Requires to be logged in.
*
* @param tagid The tag id used to identify which tag to modify.
* @param name (optional) The new tag name. Set empty to leave it unchanged.
* @param text (optional) The new tag description. Set empty to leave it unchanged.
*
* @return TRUE on success
*/
async tagUpdate(tagid: string, name?: string, text?: string): Promise<boolean> {
if (!tagid || tagid === '' || (name === '' && text === '')) {
throw new Error('Klicktipp Illegal Arguments')
}
const data = {
name: name !== undefined ? name : '',
text: text !== undefined ? text : '',
}
const res = await this.httpRequest(`/tag/${tagid}`, 'PUT', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Tag update failed: ${res.response.statusText}`)
}
/**
* Deletes a tag. Requires to be logged in.
*
* @param tagid The user id of the user to delete.
*
* @return TRUE on success
*/
async tagDelete(tagid: string): Promise<boolean> {
if (!tagid || tagid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
const res = await this.httpRequest(`/tag/${tagid}`, 'DELETE')
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Tag deletion failed: ${res.response.statusText}`)
}
/**
* Get all contact fields of the logged in user. Requires to be logged in.
*
* @return A associative object <field id> => <field name>
*/
async fieldIndex(): Promise<any> {
const res = await this.httpRequest('/field', 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Field index failed: ${res.response.statusText}`)
}
/**
* Subscribe an email. Requires to be logged in.
*
* @param email The email address of the subscriber.
* @param listid (optional) The id subscription process.
* @param tagid (optional) The id of the manual tag the subscriber will be tagged with.
* @param fields (optional) Additional fields of the subscriber.
*
* @return An object representing the Klicktipp subscriber object.
*/
async subscribe(
email: string,
listid?: number,
tagid?: number,
fields?: any,
smsnumber?: string,
): Promise<any> {
if ((!email || email === '') && smsnumber === '') {
throw new Error('Illegal Arguments')
}
// subscribe
const data = {
email,
fields: fields !== undefined ? fields : {},
smsnumber: smsnumber !== undefined ? smsnumber : '',
listid: listid !== undefined ? listid : 0,
tagid: tagid !== undefined ? tagid : 0,
}
const res = await this.httpRequest('/subscriber', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscription failed: ${res.response.statusText}`)
}
/**
* Unsubscribe an email. Requires to be logged in.
*
* @param email The email address of the subscriber.
*
* @return TRUE on success
*/
async unsubscribe(email: string): Promise<boolean> {
if (!email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// unsubscribe
const data = { email }
const res = await this.httpRequest('/subscriber/unsubscribe', 'POST', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Unsubscription failed: ${res.response.statusText}`)
}
/**
* Tag an email. Requires to be logged in.
*
* @param email The email address of the subscriber.
* @param tagids an array of the manual tag(s) the subscriber will be tagged with.
*
* @return TRUE on success
*/
async tag(email: string, tagids: string): Promise<boolean> {
if (!email || email === '' || !tagids || tagids === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// tag
const data = {
email,
tagids,
}
const res = await this.httpRequest('/subscriber/tag', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Tagging failed: ${res.response.statusText}`)
}
/**
* Untag an email. Requires to be logged in.
*
* @param mixed $email The email address of the subscriber.
* @param mixed $tagid The id of the manual tag that will be removed from the subscriber.
*
* @return TRUE on success.
*/
async untag(email: string, tagid: string): Promise<boolean> {
if (!email || email === '' || !tagid || tagid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// subscribe
const data = {
email,
tagid,
}
const res = await this.httpRequest('/subscriber/untag', 'POST', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Untagging failed: ${res.response.statusText}`)
}
/**
* Resend an autoresponder for an email address. Requires to be logged in.
*
* @param email A valid email address
* @param autoresponder An id of the autoresponder
*
* @return TRUE on success
*/
async resend(email: string, autoresponder: string): Promise<boolean> {
if (!email || email === '' || !autoresponder || autoresponder === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// resend/reset autoresponder
const data = { email, autoresponder }
const res = await this.httpRequest('/subscriber/resend', 'POST', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Resend failed: ${res.response.statusText}`)
}
/**
* Get all active subscribers. Requires to be logged in.
*
* @return An array of subscriber ids.
*/
async subscriberIndex(): Promise<[string]> {
const res = await this.httpRequest('/subscriber', 'GET', undefined, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscriber index failed: ${res.response.statusText}`)
}
/**
* Get subscriber information. Requires to be logged in.
*
* @param subscriberid The subscriber id.
*
* @return An object representing the Klicktipp subscriber.
*/
async subscriberGet(subscriberid: string): Promise<any> {
if (!subscriberid || subscriberid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// retrieve
const res = await this.httpRequest(`/subscriber/${subscriberid}`, 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscriber get failed: ${res.response.statusText}`)
}
/**
* Get a subscriber id by email. Requires to be logged in.
*
* @param email The email address of the subscriber.
*
* @return The id of the subscriber. Use subscriber_get to get subscriber details.
*/
async subscriberSearch(email: string): Promise<any> {
if (!email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// search
const data = { email }
const res = await this.httpRequest('/subscriber/search', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscriber search failed: ${res.response.statusText}`)
}
/**
* Get all active subscribers tagged with the given tag id. Requires to be logged in.
*
* @param tagid The id of the tag.
*
* @return An array with id -> subscription date of the tagged subscribers. Use subscriber_get to get subscriber details.
*/
async subscriberTagged(tagid: string): Promise<any> {
if (!tagid || tagid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// search
const data = { tagid }
const res = await this.httpRequest('/subscriber/tagged', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp subscriber tagged failed: ${res.response.statusText}`)
}
/**
* Updates a subscriber. Requires to be logged in.
*
* @param subscriberid The id of the subscriber to update.
* @param fields (optional) The fields of the subscriber to update
* @param newemail (optional) The new email of the subscriber to update
*
* @return TRUE on success
*/
async subscriberUpdate(
subscriberid: string,
fields?: any,
newemail?: string,
newsmsnumber?: string,
): Promise<boolean> {
if (!subscriberid || subscriberid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// update
const data = {
fields: fields !== undefined ? fields : {},
newemail: newemail !== undefined ? newemail : '',
newsmsnumber: newsmsnumber !== undefined ? newsmsnumber : '',
}
const res = await this.httpRequest(`/subscriber/${subscriberid}`, 'PUT', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Subscriber update failed: ${res.response.statusText}`)
}
/**
* Delete a subscribe. Requires to be logged in.
*
* @param subscriberid The id of the subscriber to update.
*
* @return TRUE on success.
*/
async subscriberDelete(subscriberid: string): Promise<boolean> {
if (!subscriberid || subscriberid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// delete
const res = await this.httpRequest(`/subscriber/${subscriberid}`, 'DELETE', {}, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Subscriber deletion failed: ${res.response.statusText}`)
}
/**
* Subscribe an email. Requires an api key.
*
* @param apikey The api key (listbuildng configuration).
* @param email The email address of the subscriber.
* @param fields (optional) Additional fields of the subscriber.
*
* @return A redirection url as defined in the subscription process.
*/
async signin(apikey: string, email: string, fields?: any, smsnumber?: string): Promise<boolean> {
if (!apikey || apikey === '' || ((!email || email === '') && smsnumber === '')) {
throw new Error('Klicktipp Illegal Arguments')
}
// subscribe
const data = {
apikey,
email,
fields: fields !== undefined ? fields : {},
smsnumber: smsnumber !== undefined ? smsnumber : '',
}
const res = await this.httpRequest('/subscriber/signin', 'POST', data)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Subscription failed: ${res.response.statusText}`)
}
/**
* Untag an email. Requires an api key.
*
* @param apikey The api key (listbuildng configuration).
* @param email The email address of the subscriber.
*
* @return TRUE on success
*/
async signout(apikey: string, email: string): Promise<boolean> {
if (!apikey || apikey === '' || !email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// untag
const data = { apikey, email }
const res = await this.httpRequest('/subscriber/signout', 'POST', data)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Untagging failed: ${res.response.statusText}`)
}
/**
* Unsubscribe an email. Requires an api key.
*
* @param apikey The api key (listbuildng configuration).
* @param email The email address of the subscriber.
*
* @return TRUE on success
*/
async signoff(apikey: string, email: string): Promise<boolean> {
if (!apikey || apikey === '' || !email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// unsubscribe
const data = { apikey, email }
const res = await this.httpRequest('/subscriber/signoff', 'POST', data)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Unsubscription failed: ${res.response.statusText}`)
}
async httpRequest(path: string, method?: Method, data?: any, usesession?: boolean): Promise<any> {
if (method === undefined) {
method = 'GET'
}
const options: AxiosRequestConfig = {
baseURL: this.baseURL,
method,
url: path,
data,
headers: {
'Content-Type': 'application/json',
Content: 'application/json',
Cookie:
usesession && this.sessionName !== '' ? `${this.sessionName}=${this.sessionId}` : '',
},
}
return axios(options)
.then((res) => res)
.catch((error) => error)
}
}

View File

@ -2,7 +2,6 @@ import { RIGHTS } from './RIGHTS'
export const INALIENABLE_RIGHTS = [ export const INALIENABLE_RIGHTS = [
RIGHTS.LOGIN, RIGHTS.LOGIN,
RIGHTS.GET_COMMUNITY_INFO,
RIGHTS.COMMUNITIES, RIGHTS.COMMUNITIES,
RIGHTS.CREATE_USER, RIGHTS.CREATE_USER,
RIGHTS.SEND_RESET_PASSWORD_EMAIL, RIGHTS.SEND_RESET_PASSWORD_EMAIL,

View File

@ -1,8 +1,10 @@
import { verify, sign } from 'jsonwebtoken' import { verify, sign } from 'jsonwebtoken'
import { CustomJwtPayload } from './CustomJwtPayload'
import CONFIG from '@/config/' import CONFIG from '@/config/'
import LogError from '@/server/LogError' import LogError from '@/server/LogError'
import { CustomJwtPayload } from './CustomJwtPayload'
export const decode = (token: string): CustomJwtPayload | null => { export const decode = (token: string): CustomJwtPayload | null => {
if (!token) throw new LogError('401 Unauthorized') if (!token) throw new LogError('401 Unauthorized')
try { try {

View File

@ -2,7 +2,6 @@ export enum RIGHTS {
LOGIN = 'LOGIN', LOGIN = 'LOGIN',
VERIFY_LOGIN = 'VERIFY_LOGIN', VERIFY_LOGIN = 'VERIFY_LOGIN',
BALANCE = 'BALANCE', BALANCE = 'BALANCE',
GET_COMMUNITY_INFO = 'GET_COMMUNITY_INFO',
COMMUNITIES = 'COMMUNITIES', COMMUNITIES = 'COMMUNITIES',
LIST_GDT_ENTRIES = 'LIST_GDT_ENTRIES', LIST_GDT_ENTRIES = 'LIST_GDT_ENTRIES',
EXIST_PID = 'EXIST_PID', EXIST_PID = 'EXIST_PID',
@ -36,6 +35,7 @@ export enum RIGHTS {
CREATE_CONTRIBUTION_MESSAGE = 'CREATE_CONTRIBUTION_MESSAGE', CREATE_CONTRIBUTION_MESSAGE = 'CREATE_CONTRIBUTION_MESSAGE',
LIST_ALL_CONTRIBUTION_MESSAGES = 'LIST_ALL_CONTRIBUTION_MESSAGES', LIST_ALL_CONTRIBUTION_MESSAGES = 'LIST_ALL_CONTRIBUTION_MESSAGES',
OPEN_CREATIONS = 'OPEN_CREATIONS', OPEN_CREATIONS = 'OPEN_CREATIONS',
USER = 'USER',
// Admin // Admin
SEARCH_USERS = 'SEARCH_USERS', SEARCH_USERS = 'SEARCH_USERS',
SET_USER_ROLE = 'SET_USER_ROLE', SET_USER_ROLE = 'SET_USER_ROLE',
@ -44,14 +44,14 @@ export enum RIGHTS {
ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION', ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION',
ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION', ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION',
ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION', ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION',
LIST_UNCONFIRMED_CONTRIBUTIONS = 'LIST_UNCONFIRMED_CONTRIBUTIONS', ADMIN_LIST_CONTRIBUTIONS = 'ADMIN_LIST_CONTRIBUTIONS',
CONFIRM_CONTRIBUTION = 'CONFIRM_CONTRIBUTION', CONFIRM_CONTRIBUTION = 'CONFIRM_CONTRIBUTION',
SEND_ACTIVATION_EMAIL = 'SEND_ACTIVATION_EMAIL', SEND_ACTIVATION_EMAIL = 'SEND_ACTIVATION_EMAIL',
CREATION_TRANSACTION_LIST = 'CREATION_TRANSACTION_LIST',
LIST_TRANSACTION_LINKS_ADMIN = 'LIST_TRANSACTION_LINKS_ADMIN', LIST_TRANSACTION_LINKS_ADMIN = 'LIST_TRANSACTION_LINKS_ADMIN',
CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK', CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK',
DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK', DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK',
UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK', UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK',
ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE', ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',
DENY_CONTRIBUTION = 'DENY_CONTRIBUTION', DENY_CONTRIBUTION = 'DENY_CONTRIBUTION',
ADMIN_OPEN_CREATIONS = 'ADMIN_OPEN_CREATIONS',
} }

View File

@ -34,6 +34,7 @@ export const ROLE_USER = new Role('user', [
RIGHTS.CREATE_CONTRIBUTION_MESSAGE, RIGHTS.CREATE_CONTRIBUTION_MESSAGE,
RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES, RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES,
RIGHTS.OPEN_CREATIONS, RIGHTS.OPEN_CREATIONS,
RIGHTS.USER,
]) ])
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights

View File

@ -1,8 +1,8 @@
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env) // ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
/* eslint-disable n/no-process-env */ /* eslint-disable n/no-process-env */
import dotenv from 'dotenv'
import { Decimal } from 'decimal.js-light' import { Decimal } from 'decimal.js-light'
import dotenv from 'dotenv'
dotenv.config() dotenv.config()
@ -12,7 +12,7 @@ Decimal.set({
}) })
const constants = { const constants = {
DB_VERSION: '0060-update_communities_table', DB_VERSION: '0064-event_rename',
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,10 +1,13 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/unbound-method */ /* eslint-disable @typescript-eslint/unbound-method */
import { createTransport } from 'nodemailer' import { createTransport } from 'nodemailer'
import { sendEmailTranslated } from './sendEmailTranslated'
import { logger, i18n } from '@test/testSetup' import { logger, i18n } from '@test/testSetup'
import CONFIG from '@/config' import CONFIG from '@/config'
import { sendEmailTranslated } from './sendEmailTranslated'
CONFIG.EMAIL = false CONFIG.EMAIL = false
CONFIG.EMAIL_SMTP_URL = 'EMAIL_SMTP_URL' CONFIG.EMAIL_SMTP_URL = 'EMAIL_SMTP_URL'
CONFIG.EMAIL_SMTP_PORT = '1234' CONFIG.EMAIL_SMTP_PORT = '1234'

View File

@ -1,11 +1,13 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
import path from 'path' import path from 'path'
import { createTransport } from 'nodemailer'
import Email from 'email-templates' import Email from 'email-templates'
import i18n from 'i18n' import i18n from 'i18n'
import { backendLogger as logger } from '@/server/logger' import { createTransport } from 'nodemailer'
import CONFIG from '@/config' import CONFIG from '@/config'
import LogError from '@/server/LogError' import LogError from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
export const sendEmailTranslated = async (params: { export const sendEmailTranslated = async (params: {
receiver: { receiver: {

View File

@ -4,6 +4,13 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { Decimal } from 'decimal.js-light' import { Decimal } from 'decimal.js-light'
import { testEnvironment } from '@test/helpers'
import { logger, i18n as localization } from '@test/testSetup'
import CONFIG from '@/config'
import { sendEmailTranslated } from './sendEmailTranslated'
import { import {
sendAddedContributionMessageEmail, sendAddedContributionMessageEmail,
sendAccountActivationEmail, sendAccountActivationEmail,
@ -15,10 +22,6 @@ import {
sendTransactionLinkRedeemedEmail, sendTransactionLinkRedeemedEmail,
sendTransactionReceivedEmail, sendTransactionReceivedEmail,
} from './sendEmailVariants' } from './sendEmailVariants'
import { sendEmailTranslated } from './sendEmailTranslated'
import { testEnvironment } from '@test/helpers'
import { logger, i18n as localization } from '@test/testSetup'
import CONFIG from '@/config'
let con: any let con: any
let testEnv: any let testEnv: any

View File

@ -1,8 +1,10 @@
import { Decimal } from 'decimal.js-light' import { Decimal } from 'decimal.js-light'
import { sendEmailTranslated } from './sendEmailTranslated'
import CONFIG from '@/config' import CONFIG from '@/config'
import { decimalSeparatorByLanguage } from '@/util/utilities' import { decimalSeparatorByLanguage } from '@/util/utilities'
import { sendEmailTranslated } from './sendEmailTranslated'
export const sendAddedContributionMessageEmail = (data: { export const sendAddedContributionMessageEmail = (data: {
firstName: string firstName: string
lastName: string lastName: string

View File

@ -0,0 +1,25 @@
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_CONFIRM = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_CONFIRM,
user,
moderator,
null,
null,
contribution,
null,
null,
null,
amount,
).save()

View File

@ -0,0 +1,25 @@
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_CREATE = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_CREATE,
user,
moderator,
null,
null,
contribution,
null,
null,
null,
amount,
).save()

View File

@ -0,0 +1,25 @@
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_DELETE = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_DELETE,
user,
moderator,
null,
null,
contribution,
null,
null,
null,
amount,
).save()

View File

@ -0,0 +1,25 @@
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_DENY = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_DENY,
user,
moderator,
null,
null,
contribution,
null,
null,
null,
amount,
).save()

View File

@ -0,0 +1,24 @@
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_LINK_CREATE = async (
moderator: DbUser,
contributionLink: DbContributionLink,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_LINK_CREATE,
{ id: 0 } as DbUser,
moderator,
null,
null,
null,
null,
null,
contributionLink,
amount,
).save()

View File

@ -0,0 +1,21 @@
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_LINK_DELETE = async (
moderator: DbUser,
contributionLink: DbContributionLink,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_LINK_DELETE,
{ id: 0 } as DbUser,
moderator,
null,
null,
null,
null,
null,
contributionLink,
).save()

View File

@ -0,0 +1,24 @@
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE = async (
moderator: DbUser,
contributionLink: DbContributionLink,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_LINK_UPDATE,
{ id: 0 } as DbUser,
moderator,
null,
null,
null,
null,
null,
contributionLink,
amount,
).save()

View File

@ -0,0 +1,24 @@
import { Contribution as DbContribution } from '@entity/Contribution'
import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage'
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
contributionMessage: DbContributionMessage,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_MESSAGE_CREATE,
user,
moderator,
null,
null,
contribution,
contributionMessage,
null,
null,
).save()

View File

@ -0,0 +1,25 @@
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_UPDATE = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_UPDATE,
user,
moderator,
null,
null,
contribution,
null,
null,
null,
amount,
).save()

View File

@ -0,0 +1,7 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_USER_DELETE = async (user: DbUser, moderator: DbUser): Promise<DbEvent> =>
Event(EventType.ADMIN_USER_DELETE, user, moderator).save()

View File

@ -0,0 +1,9 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_USER_ROLE_SET = async (
user: DbUser,
moderator: DbUser,
): Promise<DbEvent> => Event(EventType.ADMIN_USER_ROLE_SET, user, moderator).save()

View File

@ -0,0 +1,9 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_USER_UNDELETE = async (
user: DbUser,
moderator: DbUser,
): Promise<DbEvent> => Event(EventType.ADMIN_USER_UNDELETE, user, moderator).save()

View File

@ -0,0 +1,24 @@
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_CONTRIBUTION_CREATE = async (
user: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.CONTRIBUTION_CREATE,
user,
user,
null,
null,
contribution,
null,
null,
null,
amount,
).save()

View File

@ -0,0 +1,24 @@
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_CONTRIBUTION_DELETE = async (
user: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.CONTRIBUTION_DELETE,
user,
user,
null,
null,
contribution,
null,
null,
null,
amount,
).save()

View File

@ -0,0 +1,28 @@
import { Contribution as DbContribution } from '@entity/Contribution'
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { Event as DbEvent } from '@entity/Event'
import { Transaction as DbTransaction } from '@entity/Transaction'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_CONTRIBUTION_LINK_REDEEM = async (
user: DbUser,
transaction: DbTransaction,
contribution: DbContribution,
contributionLink: DbContributionLink,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.CONTRIBUTION_LINK_REDEEM,
user,
user,
null,
transaction,
contribution,
null,
null,
contributionLink,
amount,
).save()

View File

@ -0,0 +1,23 @@
import { Contribution as DbContribution } from '@entity/Contribution'
import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage'
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_CONTRIBUTION_MESSAGE_CREATE = async (
user: DbUser,
contribution: DbContribution,
contributionMessage: DbContributionMessage,
): Promise<DbEvent> =>
Event(
EventType.CONTRIBUTION_MESSAGE_CREATE,
user,
user,
null,
null,
contribution,
contributionMessage,
null,
null,
).save()

View File

@ -0,0 +1,24 @@
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_CONTRIBUTION_UPDATE = async (
user: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.CONTRIBUTION_UPDATE,
user,
user,
null,
null,
contribution,
null,
null,
null,
amount,
).save()

View File

@ -0,0 +1,7 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_EMAIL_ACCOUNT_MULTIREGISTRATION = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.EMAIL_ACCOUNT_MULTIREGISTRATION, user, { id: 0 } as DbUser).save()

View File

@ -0,0 +1,9 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_EMAIL_ADMIN_CONFIRMATION = async (
user: DbUser,
moderator: DbUser,
): Promise<DbEvent> => Event(EventType.EMAIL_ADMIN_CONFIRMATION, user, moderator).save()

View File

@ -0,0 +1,7 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_EMAIL_CONFIRMATION = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.EMAIL_CONFIRMATION, user, user).save()

View File

@ -0,0 +1,7 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_EMAIL_FORGOT_PASSWORD = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.EMAIL_FORGOT_PASSWORD, user, { id: 0 } as DbUser).save()

View File

@ -0,0 +1,24 @@
import { Event as DbEvent } from '@entity/Event'
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_TRANSACTION_LINK_CREATE = async (
user: DbUser,
transactionLink: DbTransactionLink,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.TRANSACTION_LINK_CREATE,
user,
user,
null,
null,
null,
null,
transactionLink,
null,
amount,
).save()

View File

@ -0,0 +1,20 @@
import { Event as DbEvent } from '@entity/Event'
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_TRANSACTION_LINK_DELETE = async (
user: DbUser,
transactionLink: DbTransactionLink,
): Promise<DbEvent> =>
Event(
EventType.TRANSACTION_LINK_DELETE,
user,
user,
null,
null,
null,
null,
transactionLink,
).save()

View File

@ -0,0 +1,25 @@
import { Event as DbEvent } from '@entity/Event'
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_TRANSACTION_LINK_REDEEM = async (
user: DbUser,
senderUser: DbUser,
transactionLink: DbTransactionLink,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.TRANSACTION_LINK_REDEEM,
user,
user,
senderUser,
null,
null,
null,
transactionLink,
null,
amount,
).save()

View File

@ -0,0 +1,25 @@
import { Event as DbEvent } from '@entity/Event'
import { Transaction as DbTransaction } from '@entity/Transaction'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_TRANSACTION_RECEIVE = async (
user: DbUser,
involvedUser: DbUser,
transaction: DbTransaction,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.TRANSACTION_RECEIVE,
user,
involvedUser,
involvedUser,
transaction,
null,
null,
null,
null,
amount,
).save()

View File

@ -0,0 +1,25 @@
import { Event as DbEvent } from '@entity/Event'
import { Transaction as DbTransaction } from '@entity/Transaction'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
export const EVENT_TRANSACTION_SEND = async (
user: DbUser,
involvedUser: DbUser,
transaction: DbTransaction,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.TRANSACTION_SEND,
user,
user,
involvedUser,
transaction,
null,
null,
null,
null,
amount,
).save()

View File

@ -0,0 +1,7 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_USER_ACTIVATE_ACCOUNT = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.USER_ACTIVATE_ACCOUNT, user, user).save()

View File

@ -0,0 +1,7 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_USER_INFO_UPDATE = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.USER_INFO_UPDATE, user, user).save()

View File

@ -0,0 +1,7 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_USER_LOGIN = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.USER_LOGIN, user, user).save()

View File

@ -0,0 +1,7 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_USER_LOGOUT = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.USER_LOGOUT, user, user).save()

View File

@ -0,0 +1,7 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
export const EVENT_USER_REGISTER = async (user: DbUser): Promise<DbEvent> =>
Event(EventType.USER_REGISTER, user, user).save()

View File

@ -1,212 +1,70 @@
import { EventProtocol as DbEvent } from '@entity/EventProtocol' import { Contribution as DbContribution } from '@entity/Contribution'
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage'
import { Event as DbEvent } from '@entity/Event'
import { Transaction as DbTransaction } from '@entity/Transaction'
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light' import { Decimal } from 'decimal.js-light'
import { EventProtocolType } from './EventProtocolType'
import { EventType } from './EventType'
export const Event = ( export const Event = (
type: EventProtocolType, type: EventType,
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,
involvedTransactionLink: DbTransactionLink | null = null,
involvedContributionLink: DbContributionLink | 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.involvedTransactionLink = involvedTransactionLink
event.involvedContributionLink = involvedContributionLink
event.amount = amount event.amount = amount
event.messageId = messageId
return event return event
} }
export const EVENT_CONTRIBUTION_CREATE = async ( export { EventType }
userId: number,
contributionId: number,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.CONTRIBUTION_CREATE,
userId,
null,
null,
null,
contributionId,
amount,
).save()
export const EVENT_CONTRIBUTION_DELETE = async ( export { EVENT_ADMIN_CONTRIBUTION_CONFIRM } from './EVENT_ADMIN_CONTRIBUTION_CONFIRM'
userId: number, export { EVENT_ADMIN_CONTRIBUTION_CREATE } from './EVENT_ADMIN_CONTRIBUTION_CREATE'
contributionId: number, export { EVENT_ADMIN_CONTRIBUTION_DELETE } from './EVENT_ADMIN_CONTRIBUTION_DELETE'
amount: Decimal, export { EVENT_ADMIN_CONTRIBUTION_DENY } from './EVENT_ADMIN_CONTRIBUTION_DENY'
): Promise<DbEvent> => export { EVENT_ADMIN_CONTRIBUTION_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_UPDATE'
Event( export { EVENT_ADMIN_CONTRIBUTION_LINK_CREATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_CREATE'
EventProtocolType.CONTRIBUTION_DELETE, export { EVENT_ADMIN_CONTRIBUTION_LINK_DELETE } from './EVENT_ADMIN_CONTRIBUTION_LINK_DELETE'
userId, export { EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE'
null, export { EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE'
null, export { EVENT_ADMIN_USER_DELETE } from './EVENT_ADMIN_USER_DELETE'
null, export { EVENT_ADMIN_USER_UNDELETE } from './EVENT_ADMIN_USER_UNDELETE'
contributionId, export { EVENT_ADMIN_USER_ROLE_SET } from './EVENT_ADMIN_USER_ROLE_SET'
amount, export { EVENT_CONTRIBUTION_CREATE } from './EVENT_CONTRIBUTION_CREATE'
).save() export { EVENT_CONTRIBUTION_DELETE } from './EVENT_CONTRIBUTION_DELETE'
export { EVENT_CONTRIBUTION_UPDATE } from './EVENT_CONTRIBUTION_UPDATE'
export const EVENT_CONTRIBUTION_UPDATE = async ( export { EVENT_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_CONTRIBUTION_MESSAGE_CREATE'
userId: number, export { EVENT_CONTRIBUTION_LINK_REDEEM } from './EVENT_CONTRIBUTION_LINK_REDEEM'
contributionId: number, export { EVENT_EMAIL_ACCOUNT_MULTIREGISTRATION } from './EVENT_EMAIL_ACCOUNT_MULTIREGISTRATION'
amount: Decimal, export { EVENT_EMAIL_ADMIN_CONFIRMATION } from './EVENT_EMAIL_ADMIN_CONFIRMATION'
): Promise<DbEvent> => export { EVENT_EMAIL_CONFIRMATION } from './EVENT_EMAIL_CONFIRMATION'
Event( export { EVENT_EMAIL_FORGOT_PASSWORD } from './EVENT_EMAIL_FORGOT_PASSWORD'
EventProtocolType.CONTRIBUTION_UPDATE, export { EVENT_TRANSACTION_SEND } from './EVENT_TRANSACTION_SEND'
userId, export { EVENT_TRANSACTION_RECEIVE } from './EVENT_TRANSACTION_RECEIVE'
null, export { EVENT_TRANSACTION_LINK_CREATE } from './EVENT_TRANSACTION_LINK_CREATE'
null, export { EVENT_TRANSACTION_LINK_DELETE } from './EVENT_TRANSACTION_LINK_DELETE'
null, export { EVENT_TRANSACTION_LINK_REDEEM } from './EVENT_TRANSACTION_LINK_REDEEM'
contributionId, export { EVENT_USER_ACTIVATE_ACCOUNT } from './EVENT_USER_ACTIVATE_ACCOUNT'
amount, export { EVENT_USER_INFO_UPDATE } from './EVENT_USER_INFO_UPDATE'
).save() export { EVENT_USER_LOGIN } from './EVENT_USER_LOGIN'
export { EVENT_USER_LOGOUT } from './EVENT_USER_LOGOUT'
export const EVENT_ADMIN_CONTRIBUTION_CREATE = async ( export { EVENT_USER_REGISTER } from './EVENT_USER_REGISTER'
userId: number,
contributionId: number,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.ADMIN_CONTRIBUTION_CREATE,
userId,
null,
null,
null,
contributionId,
amount,
).save()
export const EVENT_ADMIN_CONTRIBUTION_UPDATE = async (
userId: number,
contributionId: number,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.ADMIN_CONTRIBUTION_UPDATE,
userId,
null,
null,
null,
contributionId,
amount,
).save()
export const EVENT_ADMIN_CONTRIBUTION_DELETE = async (
userId: number,
contributionId: number,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.ADMIN_CONTRIBUTION_DELETE,
userId,
null,
null,
null,
contributionId,
amount,
).save()
export const EVENT_CONTRIBUTION_CONFIRM = async (
userId: number,
contributionId: number,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.CONTRIBUTION_CONFIRM,
userId,
null,
null,
null,
contributionId,
amount,
).save()
export const EVENT_ADMIN_CONTRIBUTION_DENY = async (
userId: number,
xUserId: number,
contributionId: number,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.ADMIN_CONTRIBUTION_DENY,
userId,
xUserId,
null,
null,
contributionId,
amount,
).save()
export const EVENT_TRANSACTION_SEND = async (
userId: number,
xUserId: number,
transactionId: number,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.TRANSACTION_SEND,
userId,
xUserId,
null,
transactionId,
null,
amount,
).save()
export const EVENT_TRANSACTION_RECEIVE = async (
userId: number,
xUserId: number,
transactionId: number,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.TRANSACTION_RECEIVE,
userId,
xUserId,
null,
transactionId,
null,
amount,
).save()
export const EVENT_LOGIN = async (userId: number): Promise<DbEvent> =>
Event(EventProtocolType.LOGIN, userId, null, null, null, null, null, null).save()
export const EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL = async (
userId: number,
): Promise<DbEvent> => Event(EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL, userId).save()
export const EVENT_SEND_CONFIRMATION_EMAIL = async (userId: number): Promise<DbEvent> =>
Event(EventProtocolType.SEND_CONFIRMATION_EMAIL, userId).save()
export const EVENT_ADMIN_SEND_CONFIRMATION_EMAIL = async (userId: number): Promise<DbEvent> =>
Event(EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL, userId).save()
/* export const EVENT_REDEEM_REGISTER = async (
userId: number,
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> =>
Event(EventProtocolType.REGISTER, userId).save()
export const EVENT_ACTIVATE_ACCOUNT = async (userId: number): Promise<DbEvent> =>
Event(EventProtocolType.ACTIVATE_ACCOUNT, userId).save()

View File

@ -1,50 +1,58 @@
export enum EventProtocolType { export enum EventType {
// VISIT_GRADIDO = 'VISIT_GRADIDO', // TODO CONTRIBUTION_CONFIRM = 'CONTRIBUTION_CONFIRM',
REGISTER = 'REGISTER', ADMIN_CONTRIBUTION_CONFIRM = 'ADMIN_CONTRIBUTION_CONFIRM',
REDEEM_REGISTER = 'REDEEM_REGISTER',
// VERIFY_REDEEM = 'VERIFY_REDEEM',
// INACTIVE_ACCOUNT = 'INACTIVE_ACCOUNT',
SEND_CONFIRMATION_EMAIL = 'SEND_CONFIRMATION_EMAIL',
ADMIN_SEND_CONFIRMATION_EMAIL = 'ADMIN_SEND_CONFIRMATION_EMAIL',
SEND_ACCOUNT_MULTIREGISTRATION_EMAIL = 'SEND_ACCOUNT_MULTIREGISTRATION_EMAIL',
// CONFIRM_EMAIL = 'CONFIRM_EMAIL',
// REGISTER_EMAIL_KLICKTIPP = 'REGISTER_EMAIL_KLICKTIPP',
LOGIN = 'LOGIN',
// LOGOUT = 'LOGOUT',
// REDEEM_LOGIN = 'REDEEM_LOGIN',
ACTIVATE_ACCOUNT = 'ACTIVATE_ACCOUNT',
// SEND_FORGOT_PASSWORD_EMAIL = 'SEND_FORGOT_PASSWORD_EMAIL',
// PASSWORD_CHANGE = 'PASSWORD_CHANGE',
// SEND_TRANSACTION_SEND_EMAIL = 'SEND_TRANSACTION_SEND_EMAIL',
// SEND_TRANSACTION_RECEIVE_EMAIL = 'SEND_TRANSACTION_RECEIVE_EMAIL',
TRANSACTION_SEND = 'TRANSACTION_SEND',
// TRANSACTION_SEND_REDEEM = 'TRANSACTION_SEND_REDEEM',
// TRANSACTION_REPEATE_REDEEM = 'TRANSACTION_REPEATE_REDEEM',
// TRANSACTION_CREATION = 'TRANSACTION_CREATION',
TRANSACTION_RECEIVE = 'TRANSACTION_RECEIVE',
// TRANSACTION_RECEIVE_REDEEM = 'TRANSACTION_RECEIVE_REDEEM',
// SEND_TRANSACTION_LINK_REDEEM_EMAIL = 'SEND_TRANSACTION_LINK_REDEEM_EMAIL',
// SEND_ADDED_CONTRIBUTION_EMAIL = 'SEND_ADDED_CONTRIBUTION_EMAIL',
// SEND_CONTRIBUTION_CONFIRM_EMAIL = 'SEND_CONTRIBUTION_CONFIRM_EMAIL',
CONTRIBUTION_CREATE = 'CONTRIBUTION_CREATE',
CONTRIBUTION_CONFIRM = 'CONTRIBUTION_CONFIRM',
// CONTRIBUTION_DENY = 'CONTRIBUTION_DENY',
// CONTRIBUTION_LINK_DEFINE = 'CONTRIBUTION_LINK_DEFINE',
// CONTRIBUTION_LINK_ACTIVATE_REDEEM = 'CONTRIBUTION_LINK_ACTIVATE_REDEEM',
CONTRIBUTION_DELETE = 'CONTRIBUTION_DELETE',
CONTRIBUTION_UPDATE = 'CONTRIBUTION_UPDATE',
ADMIN_CONTRIBUTION_CREATE = 'ADMIN_CONTRIBUTION_CREATE', ADMIN_CONTRIBUTION_CREATE = 'ADMIN_CONTRIBUTION_CREATE',
ADMIN_CONTRIBUTION_DELETE = 'ADMIN_CONTRIBUTION_DELETE', ADMIN_CONTRIBUTION_DELETE = 'ADMIN_CONTRIBUTION_DELETE',
ADMIN_CONTRIBUTION_DENY = 'ADMIN_CONTRIBUTION_DENY', ADMIN_CONTRIBUTION_DENY = 'ADMIN_CONTRIBUTION_DENY',
ADMIN_CONTRIBUTION_UPDATE = 'ADMIN_CONTRIBUTION_UPDATE', ADMIN_CONTRIBUTION_UPDATE = 'ADMIN_CONTRIBUTION_UPDATE',
ADMIN_CONTRIBUTION_LINK_CREATE = 'ADMIN_CONTRIBUTION_LINK_CREATE',
ADMIN_CONTRIBUTION_LINK_DELETE = 'ADMIN_CONTRIBUTION_LINK_DELETE',
ADMIN_CONTRIBUTION_LINK_UPDATE = 'ADMIN_CONTRIBUTION_LINK_UPDATE',
ADMIN_CONTRIBUTION_MESSAGE_CREATE = 'ADMIN_CONTRIBUTION_MESSAGE_CREATE',
ADMIN_USER_DELETE = 'ADMIN_USER_DELETE',
ADMIN_USER_UNDELETE = 'ADMIN_USER_UNDELETE',
ADMIN_USER_ROLE_SET = 'ADMIN_USER_ROLE_SET',
CONTRIBUTION_CREATE = 'CONTRIBUTION_CREATE',
CONTRIBUTION_DELETE = 'CONTRIBUTION_DELETE',
CONTRIBUTION_UPDATE = 'CONTRIBUTION_UPDATE',
CONTRIBUTION_MESSAGE_CREATE = 'CONTRIBUTION_MESSAGE_CREATE',
CONTRIBUTION_LINK_REDEEM = 'CONTRIBUTION_LINK_REDEEM',
EMAIL_ACCOUNT_MULTIREGISTRATION = 'EMAIL_ACCOUNT_MULTIREGISTRATION',
EMAIL_ADMIN_CONFIRMATION = 'EMAIL_ADMIN_CONFIRMATION',
EMAIL_CONFIRMATION = 'EMAIL_CONFIRMATION',
EMAIL_FORGOT_PASSWORD = 'EMAIL_FORGOT_PASSWORD',
TRANSACTION_SEND = 'TRANSACTION_SEND',
TRANSACTION_RECEIVE = 'TRANSACTION_RECEIVE',
TRANSACTION_LINK_CREATE = 'TRANSACTION_LINK_CREATE',
TRANSACTION_LINK_DELETE = 'TRANSACTION_LINK_DELETE',
TRANSACTION_LINK_REDEEM = 'TRANSACTION_LINK_REDEEM',
USER_ACTIVATE_ACCOUNT = 'ACTIVATE_ACCOUNT',
USER_INFO_UPDATE = 'USER_INFO_UPDATE',
USER_LOGIN = 'USER_LOGIN',
USER_LOGOUT = 'USER_LOGOUT',
USER_REGISTER = 'USER_REGISTER',
USER_REGISTER_REDEEM = 'USER_REGISTER_REDEEM',
// VISIT_GRADIDO = 'VISIT_GRADIDO',
// VERIFY_REDEEM = 'VERIFY_REDEEM',
// INACTIVE_ACCOUNT = 'INACTIVE_ACCOUNT',
// CONFIRM_EMAIL = 'CONFIRM_EMAIL',
// REGISTER_EMAIL_KLICKTIPP = 'REGISTER_EMAIL_KLICKTIPP',
// REDEEM_LOGIN = 'REDEEM_LOGIN',
// SEND_FORGOT_PASSWORD_EMAIL = 'SEND_FORGOT_PASSWORD_EMAIL',
// PASSWORD_CHANGE = 'PASSWORD_CHANGE',
// SEND_TRANSACTION_SEND_EMAIL = 'SEND_TRANSACTION_SEND_EMAIL',
// SEND_TRANSACTION_RECEIVE_EMAIL = 'SEND_TRANSACTION_RECEIVE_EMAIL',
// TRANSACTION_SEND_REDEEM = 'TRANSACTION_SEND_REDEEM',
// TRANSACTION_REPEATE_REDEEM = 'TRANSACTION_REPEATE_REDEEM',
// TRANSACTION_CREATION = 'TRANSACTION_CREATION',
// TRANSACTION_RECEIVE_REDEEM = 'TRANSACTION_RECEIVE_REDEEM',
// SEND_TRANSACTION_LINK_REDEEM_EMAIL = 'SEND_TRANSACTION_LINK_REDEEM_EMAIL',
// SEND_ADDED_CONTRIBUTION_EMAIL = 'SEND_ADDED_CONTRIBUTION_EMAIL',
// SEND_CONTRIBUTION_CONFIRM_EMAIL = 'SEND_CONTRIBUTION_CONFIRM_EMAIL',
// CONTRIBUTION_LINK_ACTIVATE_REDEEM = 'CONTRIBUTION_LINK_ACTIVATE_REDEEM',
// USER_CREATE_CONTRIBUTION_MESSAGE = 'USER_CREATE_CONTRIBUTION_MESSAGE', // USER_CREATE_CONTRIBUTION_MESSAGE = 'USER_CREATE_CONTRIBUTION_MESSAGE',
// ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE', // ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',
// DELETE_USER = 'DELETE_USER', // DELETE_USER = 'DELETE_USER',
// UNDELETE_USER = 'UNDELETE_USER', // UNDELETE_USER = 'UNDELETE_USER',
// CHANGE_USER_ROLE = 'CHANGE_USER_ROLE', // CHANGE_USER_ROLE = 'CHANGE_USER_ROLE',
// ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION',
// ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION',
// CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK',
// DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK',
// UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK',
} }

View File

@ -1,11 +1,12 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { gql } from 'graphql-request'
import { Community as DbCommunity } from '@entity/Community' import { Community as DbCommunity } from '@entity/Community'
import { gql } from 'graphql-request'
import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient' import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient'
import { backendLogger as logger } from '@/server/logger'
import LogError from '@/server/LogError' import LogError from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
export async function requestGetPublicKey(dbCom: DbCommunity): Promise<string | undefined> { export async function requestGetPublicKey(dbCom: DbCommunity): Promise<string | undefined> {
let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/'
@ -21,9 +22,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,11 +1,12 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { gql } from 'graphql-request'
import { Community as DbCommunity } from '@entity/Community' import { Community as DbCommunity } from '@entity/Community'
import { gql } from 'graphql-request'
import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient' import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient'
import { backendLogger as logger } from '@/server/logger'
import LogError from '@/server/LogError' import LogError from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
export async function requestGetPublicKey(dbCom: DbCommunity): Promise<string | undefined> { export async function requestGetPublicKey(dbCom: DbCommunity): Promise<string | undefined> {
let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/'
@ -21,9 +22,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,9 +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'
export class GraphQLGetClient extends GraphQLClient { type ClientInstance = {
url: string
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
private static instance: GraphQLGetClient client: GraphQLGetClient
}
export class GraphQLGetClient extends GraphQLClient {
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
@ -21,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) {
method: 'GET', return instance.client
jsonSerializer: {
parse: JSON.parse,
stringify: JSON.stringify,
},
})
} }
const client = new GraphQLGetClient(url, {
return GraphQLGetClient.instance method: 'GET',
jsonSerializer: {
parse: JSON.parse,
stringify: JSON.stringify,
},
})
GraphQLGetClient.instanceArray.push({ url, client } as ClientInstance)
return client
} }
} }

View File

@ -6,9 +6,11 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Community as DbCommunity } from '@entity/Community' import { Community as DbCommunity } from '@entity/Community'
import { validateCommunities } from './validateCommunities'
import { logger } from '@test/testSetup'
import { testEnvironment, cleanDB } from '@test/helpers' import { testEnvironment, cleanDB } from '@test/helpers'
import { logger } from '@test/testSetup'
import { validateCommunities } from './validateCommunities'
let con: any let con: any
let testEnv: any let testEnv: any

View File

@ -1,12 +1,14 @@
import { Community as DbCommunity } from '@entity/Community'
import { IsNull } from '@dbTools/typeorm' import { IsNull } from '@dbTools/typeorm'
import { Community as DbCommunity } from '@entity/Community'
import LogError from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
import { requestGetPublicKey as v1_0_requestGetPublicKey } from './client/1_0/FederationClient' import { requestGetPublicKey as v1_0_requestGetPublicKey } from './client/1_0/FederationClient'
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
import { requestGetPublicKey as v1_1_requestGetPublicKey } from './client/1_1/FederationClient' import { requestGetPublicKey as v1_1_requestGetPublicKey } from './client/1_1/FederationClient'
import { ApiVersionType } from './enum/apiVersionType' import { ApiVersionType } from './enum/apiVersionType'
import { backendLogger as logger } from '@/server/logger'
import LogError from '@/server/LogError'
export function startValidateCommunities(timerInterval: number): void { export function startValidateCommunities(timerInterval: number): void {
logger.info( logger.info(
@ -42,17 +44,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

@ -1,5 +1,5 @@
import { ArgsType, Field, InputType } from 'type-graphql'
import { Decimal } from 'decimal.js-light' import { Decimal } from 'decimal.js-light'
import { ArgsType, Field, InputType } from 'type-graphql'
@InputType() @InputType()
@ArgsType() @ArgsType()

View File

@ -1,14 +1,11 @@
import { ArgsType, Field, Int } from 'type-graphql'
import { Decimal } from 'decimal.js-light' import { Decimal } from 'decimal.js-light'
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType() @ArgsType()
export default class AdminUpdateContributionArgs { export default class AdminUpdateContributionArgs {
@Field(() => Int) @Field(() => Int)
id: number id: number
@Field(() => String)
email: string
@Field(() => Decimal) @Field(() => Decimal)
amount: Decimal amount: Decimal

View File

@ -1,5 +1,5 @@
import { ArgsType, Field, InputType } from 'type-graphql'
import { Decimal } from 'decimal.js-light' import { Decimal } from 'decimal.js-light'
import { ArgsType, Field, InputType } from 'type-graphql'
@InputType() @InputType()
@ArgsType() @ArgsType()

View File

@ -1,5 +1,5 @@
import { ArgsType, Field, Int } from 'type-graphql'
import { Decimal } from 'decimal.js-light' import { Decimal } from 'decimal.js-light'
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType() @ArgsType()
export default class ContributionLinkArgs { export default class ContributionLinkArgs {

View File

@ -1,5 +1,6 @@
/* eslint-disable type-graphql/invalid-nullable-input-type */ /* eslint-disable type-graphql/invalid-nullable-input-type */
import { ArgsType, Field, Int } from 'type-graphql' import { ArgsType, Field, Int } from 'type-graphql'
import { Order } from '@enum/Order' import { Order } from '@enum/Order'
@ArgsType() @ArgsType()

View File

@ -1,4 +1,5 @@
import { ArgsType, Field, Int } from 'type-graphql' import { ArgsType, Field, Int } from 'type-graphql'
import SearchUsersFilters from '@arg/SearchUsersFilters' import SearchUsersFilters from '@arg/SearchUsersFilters'
@ArgsType() @ArgsType()

View File

@ -1,10 +0,0 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export default class SubscribeNewsletterArgs {
@Field(() => String)
email: string
@Field(() => String)
language: string
}

View File

@ -1,5 +1,5 @@
import { ArgsType, Field } from 'type-graphql'
import { Decimal } from 'decimal.js-light' import { Decimal } from 'decimal.js-light'
import { ArgsType, Field } from 'type-graphql'
@ArgsType() @ArgsType()
export default class TransactionLinkArgs { export default class TransactionLinkArgs {

View File

@ -1,10 +1,10 @@
import { ArgsType, Field } from 'type-graphql'
import { Decimal } from 'decimal.js-light' import { Decimal } from 'decimal.js-light'
import { ArgsType, Field } from 'type-graphql'
@ArgsType() @ArgsType()
export default class TransactionSendArgs { export default class TransactionSendArgs {
@Field(() => String) @Field(() => String)
email: string identifier: string
@Field(() => Decimal) @Field(() => Decimal)
amount: Decimal amount: Decimal

View File

@ -2,13 +2,14 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-argument */
import { AuthChecker } from 'type-graphql'
import { User } from '@entity/User' import { User } from '@entity/User'
import { decode, encode } from '@/auth/JWT' import { AuthChecker } from 'type-graphql'
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES'
import { RIGHTS } from '@/auth/RIGHTS'
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS' import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
import { decode, encode } from '@/auth/JWT'
import { RIGHTS } from '@/auth/RIGHTS'
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES'
import LogError from '@/server/LogError' import LogError from '@/server/LogError'
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => { const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {

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