mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into 2715-feature-document-the-technical-federation-architecture
This commit is contained in:
commit
028aad7c7a
43
.github/file-filters.yml
vendored
Normal file
43
.github/file-filters.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
# These file filter patterns are used by the action https://github.com/dorny/paths-filter
|
||||
|
||||
# more differentiated filters for admin interface, which might be used later
|
||||
# admin_locales: &admin_locales
|
||||
# - 'admin/src/locales/**'
|
||||
# - 'admin/scripts/sort*'
|
||||
|
||||
# admin_stylelinting: &admin_stylelinting
|
||||
# - 'admin/{components,layouts,pages}/**/*.{scss,vue}'
|
||||
# - 'admin/.stylelintrc.js'
|
||||
|
||||
# admin_linting: &admin_linting
|
||||
# - 'admin/.eslint*'
|
||||
# - 'admin/babel.config.js'
|
||||
# - 'admin/package.json'
|
||||
# - 'admin/**/*.{js,vue}'
|
||||
# - *admin_locales
|
||||
|
||||
# admin_unit_testing: &admin_unit_testing
|
||||
# - 'admin/package.json'
|
||||
# - 'admin/{jest,vue}.config.js'
|
||||
# - 'admin/{public,run,test}/**/*'
|
||||
# - 'admin/src/!(locales)/**/*'
|
||||
|
||||
# admin_docker_building: &admin_docker_building
|
||||
# - 'admin/.dockerignore'
|
||||
# - 'admin/Dockerfile'
|
||||
# - *admin_unit_testing
|
||||
|
||||
admin: &admin
|
||||
- 'admin/**/*'
|
||||
|
||||
dht_node: &dht_node
|
||||
- 'dht-node/**/*'
|
||||
|
||||
docker: &docker
|
||||
- 'docker-compose.*'
|
||||
|
||||
federation: &federation
|
||||
- 'federation/**/*'
|
||||
|
||||
frontend: &frontend
|
||||
- 'frontend/**/*'
|
||||
58
.github/workflows/e2e-test.yml
vendored
Normal file
58
.github/workflows/e2e-test.yml
vendored
Normal 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/
|
||||
84
.github/workflows/test-admin-interface.yml
vendored
Normal file
84
.github/workflows/test-admin-interface.yml
vendored
Normal 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
|
||||
343
.github/workflows/test.yml
vendored
343
.github/workflows/test.yml
vendored
@ -3,57 +3,6 @@ name: gradido test CI
|
||||
on: push
|
||||
|
||||
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 #############################################
|
||||
##############################################################################
|
||||
@ -157,114 +106,6 @@ jobs:
|
||||
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 ##########################################################
|
||||
##############################################################################
|
||||
@ -281,7 +122,7 @@ jobs:
|
||||
# LINT BACKEND ###########################################################
|
||||
##########################################################################
|
||||
- name: backend | Lint
|
||||
run: cd backend && yarn && yarn run lint
|
||||
run: cd database && yarn && cd ../backend && yarn && yarn run lint
|
||||
|
||||
##############################################################################
|
||||
# JOB: LOCALES BACKEND #######################################################
|
||||
@ -319,68 +160,6 @@ jobs:
|
||||
- name: Database | 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 ####################################################
|
||||
##############################################################################
|
||||
@ -415,20 +194,7 @@ jobs:
|
||||
- name: backend | docker-compose database
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database
|
||||
- name: backend Unit tests | test
|
||||
run: |
|
||||
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 }}
|
||||
run: cd database && yarn && yarn build && cd ../backend && yarn && yarn test
|
||||
|
||||
##########################################################################
|
||||
# DATABASE MIGRATION TEST UP + RESET #####################################
|
||||
@ -452,108 +218,3 @@ jobs:
|
||||
run: docker-compose -f docker-compose.yml run -T database yarn up
|
||||
- name: database | 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
|
||||
- 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/
|
||||
|
||||
61
.github/workflows/test_dht-node.yml
vendored
61
.github/workflows/test_dht-node.yml
vendored
@ -1,19 +1,40 @@
|
||||
name: gradido test_dht-node CI
|
||||
name: Gradido DHT Node 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 - 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 #####################################################
|
||||
##############################################################################
|
||||
build:
|
||||
name: Docker Build Test
|
||||
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
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build `test` image
|
||||
- name: Build 'test' image
|
||||
run: |
|
||||
docker build --target test -t "gradido/dht-node:test" -f dht-node/Dockerfile .
|
||||
docker save "gradido/dht-node:test" > /tmp/dht-node.tar
|
||||
@ -28,31 +49,25 @@ jobs:
|
||||
# JOB: LINT ##################################################################
|
||||
##############################################################################
|
||||
lint:
|
||||
name: Lint
|
||||
name: Lint - DHT Node
|
||||
if: needs.files-changed.outputs.dht_node == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
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
|
||||
run: docker run --rm gradido/dht-node:test yarn run lint
|
||||
run: cd dht-node && yarn && yarn run lint
|
||||
|
||||
##############################################################################
|
||||
# JOB: UNIT TEST #############################################################
|
||||
##############################################################################
|
||||
unit_test:
|
||||
name: Unit tests
|
||||
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
|
||||
needs: [build]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
@ -83,16 +98,4 @@ jobs:
|
||||
#- name: Unit tests
|
||||
# run: cd database && yarn && yarn build && cd ../dht-node && yarn && yarn test
|
||||
- name: Unit tests
|
||||
run: |
|
||||
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 }}
|
||||
run: docker run --env NODE_ENV=test --env DB_HOST=mariadb --network gradido_internal-net --rm gradido/dht-node:test yarn run test
|
||||
|
||||
58
.github/workflows/test_federation.yml
vendored
58
.github/workflows/test_federation.yml
vendored
@ -1,13 +1,34 @@
|
||||
name: gradido test_federation CI
|
||||
name: Gradido Federation 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 - 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 #####################################################
|
||||
##############################################################################
|
||||
build:
|
||||
name: Docker Build Test
|
||||
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
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@ -28,31 +49,25 @@ jobs:
|
||||
# JOB: LINT ##################################################################
|
||||
##############################################################################
|
||||
lint:
|
||||
name: Lint
|
||||
name: Lint - Federation
|
||||
if: needs.files-changed.outputs.federation == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
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
|
||||
run: docker run --rm gradido/federation:test yarn run lint
|
||||
run: cd federation && yarn && yarn run lint
|
||||
|
||||
##############################################################################
|
||||
# JOB: UNIT TEST #############################################################
|
||||
##############################################################################
|
||||
unit_test:
|
||||
name: Unit tests
|
||||
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
|
||||
needs: [build]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
@ -84,15 +99,4 @@ jobs:
|
||||
# run: cd database && yarn && yarn build && cd ../dht-node && yarn && yarn test
|
||||
- name: Unit tests
|
||||
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
|
||||
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 }}
|
||||
docker run --env NODE_ENV=test --env DB_HOST=mariadb --network gradido_internal-net --rm gradido/federation:test yarn run test
|
||||
|
||||
84
.github/workflows/test_frontend.yml
vendored
Normal file
84
.github/workflows/test_frontend.yml
vendored
Normal 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
|
||||
68
CHANGELOG.md
68
CHANGELOG.md
@ -4,8 +4,76 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [1.19.1](https://github.com/gradido/gradido/compare/1.19.0...1.19.1)
|
||||
|
||||
- fix(frontend): admin question clickable [`#2810`](https://github.com/gradido/gradido/pull/2810)
|
||||
- refactor(frontend): change b-img to b-icon send [`#2809`](https://github.com/gradido/gradido/pull/2809)
|
||||
- fix(admin): update openCreation in case of tab open. [`#2806`](https://github.com/gradido/gradido/pull/2806)
|
||||
- fix(admin): english language for contributions in admin [`#2804`](https://github.com/gradido/gradido/pull/2804)
|
||||
- refactor(admin): event buttons for myself turned off in open contributions [`#2760`](https://github.com/gradido/gradido/pull/2760)
|
||||
- fix(admin): contribution page [`#2794`](https://github.com/gradido/gradido/pull/2794)
|
||||
- fix(frontend): info.svg [`#2798`](https://github.com/gradido/gradido/pull/2798)
|
||||
- fix(backend): add relation messages to database query [`#2795`](https://github.com/gradido/gradido/pull/2795)
|
||||
- fix(admin): header and menu [`#2793`](https://github.com/gradido/gradido/pull/2793)
|
||||
- fix(frontend): send gdd - change first submit button text to 'Check Now' [`#2774`](https://github.com/gradido/gradido/pull/2774)
|
||||
- refactor(frontend): creations generated by link NL [`#2771`](https://github.com/gradido/gradido/pull/2771)
|
||||
- refactor(frontend): creations generated by link (FR) + (NL) [`#2770`](https://github.com/gradido/gradido/pull/2770)
|
||||
- refactor(frontend): update German locales [`#2765`](https://github.com/gradido/gradido/pull/2765)
|
||||
- refactor(backend): use find contributions helper for list contributions [`#2762`](https://github.com/gradido/gradido/pull/2762)
|
||||
|
||||
#### [1.19.0](https://github.com/gradido/gradido/compare/1.18.2...1.19.0)
|
||||
|
||||
> 7 March 2023
|
||||
|
||||
- chore(release): version 1.19.0 [`#2786`](https://github.com/gradido/gradido/pull/2786)
|
||||
- fix(frontend): change contribution design [`#2731`](https://github.com/gradido/gradido/pull/2731)
|
||||
- refactor(frontend): commnity navbar- & unauthenticated b-gradido styles [`#2732`](https://github.com/gradido/gradido/pull/2732)
|
||||
- fix(database): change downwards migration to delete entries with last_announced_at IS NULL [`#2767`](https://github.com/gradido/gradido/pull/2767)
|
||||
- feat(admin): deleted contributions visible [`#2759`](https://github.com/gradido/gradido/pull/2759)
|
||||
- feat(other): e2e test user story user registration [`#2753`](https://github.com/gradido/gradido/pull/2753)
|
||||
- refactor(frontend): style and design changes to a contribution [`#2648`](https://github.com/gradido/gradido/pull/2648)
|
||||
- test(backend): add tests that ``sendContributionDeleted`` and ``sendContributionDenied`` are called [`#2740`](https://github.com/gradido/gradido/pull/2740)
|
||||
- fix(backend): set email tls true in test [`#2763`](https://github.com/gradido/gradido/pull/2763)
|
||||
- refactor(frontend): add visible event an answer question [`#2750`](https://github.com/gradido/gradido/pull/2750)
|
||||
- refactor(frontend): style sidebar, add icons [`#2737`](https://github.com/gradido/gradido/pull/2737)
|
||||
- fix(backend): possible flaky test [`#2761`](https://github.com/gradido/gradido/pull/2761)
|
||||
- fix(backend): emails – adjust namings of menus to new design [`#2756`](https://github.com/gradido/gradido/pull/2756)
|
||||
- ci(other): rename dht node and federation workflow jobs for better branch protection maintenance [`#2743`](https://github.com/gradido/gradido/pull/2743)
|
||||
- refactor(backend): combine logic for `listTransactionLinks` & `listTransactionLinksAdmin` [`#2706`](https://github.com/gradido/gradido/pull/2706)
|
||||
- refactor(backend): remove admin create contributions [`#2724`](https://github.com/gradido/gradido/pull/2724)
|
||||
- refactor(frontend): remove .vue as imports [`#2725`](https://github.com/gradido/gradido/pull/2725)
|
||||
- feat(federation): add dht-node to deployment scripts [`#2729`](https://github.com/gradido/gradido/pull/2729)
|
||||
- fix(frontend): change fetchPolicy, add scripts.update [`#2718`](https://github.com/gradido/gradido/pull/2718)
|
||||
- refactor(backend): list unconfirmed contribution to admin list all contribution [`#2666`](https://github.com/gradido/gradido/pull/2666)
|
||||
- refactor(frontend): community routes [`#2721`](https://github.com/gradido/gradido/pull/2721)
|
||||
- test(backend): authentication tests for TransactionLinkResolver [`#2705`](https://github.com/gradido/gradido/pull/2705)
|
||||
- refactor(backend): use LogError on errors [`#2679`](https://github.com/gradido/gradido/pull/2679)
|
||||
- refactor(backend): use LogError on encryptorUtils [`#2678`](https://github.com/gradido/gradido/pull/2678)
|
||||
- refactor(backend): use LogError on creations [`#2677`](https://github.com/gradido/gradido/pull/2677)
|
||||
- refactor(other): decrease docker build dependencies in test workflow [`#2719`](https://github.com/gradido/gradido/pull/2719)
|
||||
- refactor(backend): unit test for the method denyContribution [`#2639`](https://github.com/gradido/gradido/pull/2639)
|
||||
- refactor(frontend): logo inserted with better quality. [`#2646`](https://github.com/gradido/gradido/pull/2646)
|
||||
- feat(federation): harmonize and sync modules and data of federation [`#2665`](https://github.com/gradido/gradido/pull/2665)
|
||||
- feat(federation): add docker and github-workflow files [`#2680`](https://github.com/gradido/gradido/pull/2680)
|
||||
- feat(other): e2e test user authentication reset password [`#2644`](https://github.com/gradido/gradido/pull/2644)
|
||||
- refactor(admin): add tabs for all statusus on contributions [`#2623`](https://github.com/gradido/gradido/pull/2623)
|
||||
- feat(federation): implement a graphql client to request getpublickey [`#2511`](https://github.com/gradido/gradido/pull/2511)
|
||||
- refactor(frontend): style refactor mobil auth area [`#2643`](https://github.com/gradido/gradido/pull/2643)
|
||||
- refactor(backend): use LogError on TransactionResolver [`#2676`](https://github.com/gradido/gradido/pull/2676)
|
||||
- refactor(backend): event protocol rework [`#2691`](https://github.com/gradido/gradido/pull/2691)
|
||||
- refactor(backend): use LogError on TransactionLinkResolver [`#2673`](https://github.com/gradido/gradido/pull/2673)
|
||||
- fix(frontend): simple disabled function on submit send [`#2647`](https://github.com/gradido/gradido/pull/2647)
|
||||
- refactor(frontend): missing message on old transactions [`#2660`](https://github.com/gradido/gradido/pull/2660)
|
||||
- refactor(admin): remove overview and multi creation menu entry [`#2661`](https://github.com/gradido/gradido/pull/2661)
|
||||
- feat(other): add locales check to backend and integrate it to test workflow [`#2693`](https://github.com/gradido/gradido/pull/2693)
|
||||
- refactor(other): add linting rules like in backend modul [`#2695`](https://github.com/gradido/gradido/pull/2695)
|
||||
- refactor(backend): use LogError on contributionResolver [`#2669`](https://github.com/gradido/gradido/pull/2669)
|
||||
|
||||
#### [1.18.2](https://github.com/gradido/gradido/compare/1.18.1...1.18.2)
|
||||
|
||||
> 10 February 2023
|
||||
|
||||
- chore(release): version 1.18.2 [`#2700`](https://github.com/gradido/gradido/pull/2700)
|
||||
- fix(admin): deny contribution button to left [`#2699`](https://github.com/gradido/gradido/pull/2699)
|
||||
|
||||
#### [1.18.1](https://github.com/gradido/gradido/compare/1.18.0...1.18.1)
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.{js,vue}',
|
||||
'!**/node_modules/**',
|
||||
'!src/assets/**',
|
||||
'!**/?(*.)+(spec|test).js?(x)',
|
||||
],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
lines: 97,
|
||||
},
|
||||
},
|
||||
moduleFileExtensions: [
|
||||
'js',
|
||||
// 'jsx',
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Administraion Interface for Gradido",
|
||||
"main": "index.js",
|
||||
"author": "Moriz Wahl",
|
||||
"version": "1.18.2",
|
||||
"version": "1.19.1",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
@ -14,7 +14,7 @@
|
||||
"analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json",
|
||||
"lint": "eslint --max-warnings=0 --ext .js,.vue,.json .",
|
||||
"stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'",
|
||||
"test": "cross-env TZ=UTC jest --coverage",
|
||||
"test": "cross-env TZ=UTC jest",
|
||||
"locales": "scripts/sort.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -10,6 +10,7 @@ describe('ContributionMessagesList', () => {
|
||||
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
contributionState: 'PENDING',
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
<template>
|
||||
<div class="contribution-messages-list">
|
||||
<b-container>
|
||||
{{ messages.lenght }}
|
||||
<div v-for="message in messages" v-bind:key="message.id">
|
||||
<contribution-messages-list-item :message="message" />
|
||||
</div>
|
||||
</b-container>
|
||||
|
||||
<contribution-messages-formular
|
||||
:contributionId="contributionId"
|
||||
@get-list-contribution-messages="getListContributionMessages"
|
||||
@update-state="updateState"
|
||||
/>
|
||||
<div v-if="contributionState === 'PENDING' || contributionState === 'IN_PROGRESS'">
|
||||
<contribution-messages-formular
|
||||
:contributionId="contributionId"
|
||||
@get-list-contribution-messages="getListContributionMessages"
|
||||
@update-state="updateState"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@ -30,6 +31,10 @@ export default {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
contributionState: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="component-nabvar">
|
||||
<b-navbar toggleable="md" type="dark" variant="success" class="p-3">
|
||||
<b-navbar-brand to="/">
|
||||
<img src="img/brand/gradido_logo_w.png" class="navbar-brand-img" alt="..." />
|
||||
<b-navbar toggleable="md" type="dark" variant="success">
|
||||
<b-navbar-brand class="mb-2" to="/">
|
||||
<img src="img/brand/gradido_logo_w.png" class="navbar-brand-img pl-2" alt="..." />
|
||||
</b-navbar-brand>
|
||||
|
||||
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
|
||||
@ -10,7 +10,7 @@
|
||||
<b-collapse id="nav-collapse" is-nav>
|
||||
<b-navbar-nav>
|
||||
<b-nav-item to="/user">{{ $t('navbar.user_search') }}</b-nav-item>
|
||||
<b-nav-item class="bg-color-creation p-1" to="/creation-confirm">
|
||||
<b-nav-item class="bg-color-creation" to="/creation-confirm">
|
||||
{{ $t('creation') }}
|
||||
<b-badge v-show="$store.state.openCreations > 0" variant="danger">
|
||||
{{ $store.state.openCreations }}
|
||||
@ -52,6 +52,5 @@ export default {
|
||||
<style>
|
||||
.navbar-brand-img {
|
||||
height: 2rem;
|
||||
padding-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -13,17 +13,19 @@
|
||||
<b-icon :icon="getStatusIcon(row.item.state)"></b-icon>
|
||||
</template>
|
||||
<template #cell(bookmark)="row">
|
||||
<b-button
|
||||
variant="danger"
|
||||
size="md"
|
||||
@click="$emit('show-overlay', row.item, 'delete')"
|
||||
class="mr-2"
|
||||
>
|
||||
<b-icon icon="trash" variant="light"></b-icon>
|
||||
</b-button>
|
||||
<div v-if="!myself(row.item)">
|
||||
<b-button
|
||||
variant="danger"
|
||||
size="md"
|
||||
@click="$emit('show-overlay', row.item, 'delete')"
|
||||
class="mr-2"
|
||||
>
|
||||
<b-icon icon="trash" variant="light"></b-icon>
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell(editCreation)="row">
|
||||
<div v-if="$store.state.moderator.id !== row.item.userId">
|
||||
<div v-if="!myself(row.item)">
|
||||
<b-button
|
||||
v-if="row.item.moderator"
|
||||
variant="info"
|
||||
@ -36,30 +38,26 @@
|
||||
<b-button v-else @click="rowToggleDetails(row, 0)">
|
||||
<b-icon icon="chat-dots"></b-icon>
|
||||
<b-icon
|
||||
v-if="row.item.state === 'PENDING' && row.item.messageCount > 0"
|
||||
v-if="row.item.state === 'PENDING' && row.item.messagesCount > 0"
|
||||
icon="exclamation-circle-fill"
|
||||
variant="warning"
|
||||
></b-icon>
|
||||
<b-icon
|
||||
v-if="row.item.state === 'IN_PROGRESS' && row.item.messageCount > 0"
|
||||
v-if="row.item.state === 'IN_PROGRESS' && row.item.messagesCount > 0"
|
||||
icon="question-diamond"
|
||||
variant="light"
|
||||
variant="warning"
|
||||
class="pl-1"
|
||||
></b-icon>
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell(reActive)>
|
||||
<b-button variant="warning" size="md" class="mr-2">
|
||||
<b-icon icon="arrow-up" variant="light"></b-icon>
|
||||
</b-button>
|
||||
</template>
|
||||
<template #cell(chatCreation)="row">
|
||||
<b-button v-if="row.item.messagesCount > 0" @click="rowToggleDetails(row, 0)">
|
||||
<b-icon icon="chat-dots"></b-icon>
|
||||
</b-button>
|
||||
</template>
|
||||
<template #cell(deny)="row">
|
||||
<div v-if="$store.state.moderator.id !== row.item.userId">
|
||||
<div v-if="!myself(row.item)">
|
||||
<b-button
|
||||
variant="warning"
|
||||
size="md"
|
||||
@ -71,7 +69,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #cell(confirm)="row">
|
||||
<div v-if="$store.state.moderator.id !== row.item.userId">
|
||||
<div v-if="!myself(row.item)">
|
||||
<b-button
|
||||
variant="success"
|
||||
size="md"
|
||||
@ -104,6 +102,7 @@
|
||||
<div v-else>
|
||||
<contribution-messages-list
|
||||
:contributionId="row.item.id"
|
||||
:contributionState="row.item.state"
|
||||
@update-state="updateState"
|
||||
@update-user-data="updateUserData"
|
||||
/>
|
||||
@ -158,13 +157,22 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
myself(item) {
|
||||
return (
|
||||
`${item.firstName} ${item.lastName}` ===
|
||||
`${this.$store.state.moderator.firstName} ${this.$store.state.moderator.lastName}`
|
||||
)
|
||||
},
|
||||
getStatusIcon(status) {
|
||||
return iconMap[status] ? iconMap[status] : 'default-icon'
|
||||
},
|
||||
rowClass(item, type) {
|
||||
if (!item || type !== 'row') return
|
||||
if (item.state === 'CONFIRMED') return 'table-success'
|
||||
if (item.state === 'DENIED') return 'table-info'
|
||||
if (item.state === 'DENIED') return 'table-warning'
|
||||
if (item.state === 'DELETED') return 'table-danger'
|
||||
if (item.state === 'IN_PROGRESS') return 'table-primary'
|
||||
if (item.state === 'PENDING') return 'table-primary'
|
||||
},
|
||||
updateCreationData(data) {
|
||||
const row = data.row
|
||||
|
||||
@ -9,8 +9,8 @@ const apolloQueryMock = jest.fn()
|
||||
apolloQueryMock.mockResolvedValue({
|
||||
data: {
|
||||
listTransactionLinksAdmin: {
|
||||
linkCount: 8,
|
||||
linkList: [
|
||||
count: 8,
|
||||
links: [
|
||||
{
|
||||
amount: '19.99',
|
||||
code: '62ef8236ace7217fbd066c5a',
|
||||
|
||||
@ -42,8 +42,8 @@ export default {
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
this.rows = result.data.listTransactionLinksAdmin.linkCount
|
||||
this.items = result.data.listTransactionLinksAdmin.linkList
|
||||
this.rows = result.data.listTransactionLinksAdmin.count
|
||||
this.items = result.data.listTransactionLinksAdmin.links
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toastError(error.message)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const adminCreateContributionMessage = gql`
|
||||
mutation ($contributionId: Float!, $message: String!) {
|
||||
mutation ($contributionId: Int!, $message: String!) {
|
||||
adminCreateContributionMessage(contributionId: $contributionId, message: $message) {
|
||||
id
|
||||
message
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const listAllContributions = gql`
|
||||
export const adminListAllContributions = gql`
|
||||
query (
|
||||
$currentPage: Int = 1
|
||||
$pageSize: Int = 25
|
||||
$order: Order = DESC
|
||||
$statusFilter: [ContributionStatus!]
|
||||
) {
|
||||
listAllContributions(
|
||||
adminListAllContributions(
|
||||
currentPage: $currentPage
|
||||
pageSize: $pageSize
|
||||
order: $order
|
||||
@ -28,6 +28,8 @@ export const listAllContributions = gql`
|
||||
messagesCount
|
||||
deniedAt
|
||||
deniedBy
|
||||
deletedAt
|
||||
deletedBy
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const listContributionMessages = gql`
|
||||
query ($contributionId: Float!, $pageSize: Int = 25, $currentPage: Int = 1, $order: Order = ASC) {
|
||||
query ($contributionId: Int!, $pageSize: Int = 25, $currentPage: Int = 1, $order: Order = ASC) {
|
||||
listContributionMessages(
|
||||
contributionId: $contributionId
|
||||
pageSize: $pageSize
|
||||
|
||||
@ -8,8 +8,8 @@ export const listTransactionLinksAdmin = gql`
|
||||
userId: $userId
|
||||
filters: { withRedeemed: true, withExpired: true, withDeleted: true }
|
||||
) {
|
||||
linkCount
|
||||
linkList {
|
||||
count
|
||||
links {
|
||||
id
|
||||
amount
|
||||
holdAvailableAmount
|
||||
|
||||
@ -63,7 +63,6 @@
|
||||
"deleted_user": "Alle gelöschten Nutzer",
|
||||
"delete_user": "Nutzer löschen",
|
||||
"deny": "Ablehnen",
|
||||
"edit": "Bearbeiten",
|
||||
"enabled": "aktiviert",
|
||||
"error": "Fehler",
|
||||
"expired": "abgelaufen",
|
||||
@ -101,7 +100,6 @@
|
||||
"message": {
|
||||
"request": "Die Anfrage wurde gesendet."
|
||||
},
|
||||
"mod": "Mod",
|
||||
"moderator": "Moderator",
|
||||
"name": "Name",
|
||||
"navbar": {
|
||||
|
||||
@ -34,11 +34,11 @@
|
||||
"all": "All",
|
||||
"confirms": "Confirmed",
|
||||
"deleted": "Deleted",
|
||||
"denied": "Denied",
|
||||
"denied": "Rejected",
|
||||
"open": "Open"
|
||||
},
|
||||
"created": "Confirmed",
|
||||
"createdAt": "Created",
|
||||
"created": "Created for",
|
||||
"createdAt": "Created at",
|
||||
"creation": "Creation",
|
||||
"creationList": "Creation list",
|
||||
"creation_form": {
|
||||
@ -53,7 +53,7 @@
|
||||
"toasted": "Open creation ({value} GDD) for {email} has been saved and is ready for confirmation.",
|
||||
"toasted_created": "Creation has been successfully saved",
|
||||
"toasted_delete": "Open creation has been deleted",
|
||||
"toasted_denied": "Open creation has been denied",
|
||||
"toasted_denied": "Open creation has been rejected",
|
||||
"toasted_update": "Open creation {value} GDD) for {email} has been changed and is ready for confirmation.",
|
||||
"update_creation": "Creation update"
|
||||
},
|
||||
@ -63,7 +63,6 @@
|
||||
"deleted_user": "All deleted user",
|
||||
"delete_user": "Delete user",
|
||||
"deny": "Reject",
|
||||
"edit": "Edit",
|
||||
"enabled": "enabled",
|
||||
"error": "Error",
|
||||
"expired": "expired",
|
||||
@ -87,7 +86,7 @@
|
||||
"transactionlist": {
|
||||
"confirmed": "When was it confirmed by a moderator / admin.",
|
||||
"periods": "For what period was it submitted by the member.",
|
||||
"state": "[PENDING = submitted, DELETED = deleted, IN_PROGRESS = in dialogue with moderator, DENIED = denied, CONFIRMED = confirmed]",
|
||||
"state": "[PENDING = submitted, DELETED = deleted, IN_PROGRESS = in dialogue with moderator, DENIED = rejected, CONFIRMED = confirmed]",
|
||||
"submitted": "When was it submitted by the member"
|
||||
}
|
||||
},
|
||||
@ -101,7 +100,6 @@
|
||||
"message": {
|
||||
"request": "Request has been sent."
|
||||
},
|
||||
"mod": "Mod",
|
||||
"moderator": "Moderator",
|
||||
"name": "Name",
|
||||
"navbar": {
|
||||
|
||||
@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'
|
||||
import CreationConfirm from './CreationConfirm'
|
||||
import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
|
||||
import { denyContribution } from '../graphql/denyContribution'
|
||||
import { listAllContributions } from '../graphql/listAllContributions'
|
||||
import { adminListAllContributions } from '../graphql/adminListAllContributions'
|
||||
import { confirmContribution } from '../graphql/confirmContribution'
|
||||
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
|
||||
import VueApollo from 'vue-apollo'
|
||||
@ -38,7 +38,7 @@ const mocks = {
|
||||
|
||||
const defaultData = () => {
|
||||
return {
|
||||
listAllContributions: {
|
||||
adminListAllContributions: {
|
||||
contributionCount: 2,
|
||||
contributionList: [
|
||||
{
|
||||
@ -92,14 +92,14 @@ const defaultData = () => {
|
||||
|
||||
describe('CreationConfirm', () => {
|
||||
let wrapper
|
||||
const adminListAllContributionsMock = jest.fn()
|
||||
const adminDeleteContributionMock = jest.fn()
|
||||
const adminDenyContributionMock = jest.fn()
|
||||
const confirmContributionMock = jest.fn()
|
||||
|
||||
mockClient.setRequestHandler(
|
||||
listAllContributions,
|
||||
jest
|
||||
.fn()
|
||||
adminListAllContributions,
|
||||
adminListAllContributionsMock
|
||||
.mockRejectedValueOnce({ message: 'Ouch!' })
|
||||
.mockResolvedValue({ data: defaultData() }),
|
||||
)
|
||||
@ -331,78 +331,82 @@ describe('CreationConfirm', () => {
|
||||
|
||||
describe('filter tabs', () => {
|
||||
describe('click tab "confirmed"', () => {
|
||||
let refetchSpy
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
refetchSpy = jest.spyOn(wrapper.vm.$apollo.queries.ListAllContributions, 'refetch')
|
||||
await wrapper.find('a[data-test="confirmed"]').trigger('click')
|
||||
})
|
||||
|
||||
it('has statusFilter set to ["CONFIRMED"]', () => {
|
||||
expect(
|
||||
wrapper.vm.$apollo.queries.ListAllContributions.observer.options.variables,
|
||||
).toMatchObject({ statusFilter: ['CONFIRMED'] })
|
||||
})
|
||||
|
||||
it('refetches contributions', () => {
|
||||
expect(refetchSpy).toBeCalled()
|
||||
it('refetches contributions with proper filter', () => {
|
||||
expect(adminListAllContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
statusFilter: ['CONFIRMED'],
|
||||
})
|
||||
})
|
||||
|
||||
describe('click tab "open"', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
refetchSpy = jest.spyOn(wrapper.vm.$apollo.queries.ListAllContributions, 'refetch')
|
||||
await wrapper.find('a[data-test="open"]').trigger('click')
|
||||
})
|
||||
|
||||
it('has statusFilter set to ["IN_PROGRESS", "PENDING"]', () => {
|
||||
expect(
|
||||
wrapper.vm.$apollo.queries.ListAllContributions.observer.options.variables,
|
||||
).toMatchObject({ statusFilter: ['IN_PROGRESS', 'PENDING'] })
|
||||
})
|
||||
|
||||
it('refetches contributions', () => {
|
||||
expect(refetchSpy).toBeCalled()
|
||||
it('refetches contributions with proper filter', () => {
|
||||
expect(adminListAllContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
statusFilter: ['IN_PROGRESS', 'PENDING'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('click tab "denied"', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
refetchSpy = jest.spyOn(wrapper.vm.$apollo.queries.ListAllContributions, 'refetch')
|
||||
await wrapper.find('a[data-test="denied"]').trigger('click')
|
||||
})
|
||||
|
||||
it('has statusFilter set to ["DENIED"]', () => {
|
||||
expect(
|
||||
wrapper.vm.$apollo.queries.ListAllContributions.observer.options.variables,
|
||||
).toMatchObject({ statusFilter: ['DENIED'] })
|
||||
it('refetches contributions with proper filter', () => {
|
||||
expect(adminListAllContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
statusFilter: ['DENIED'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('click tab "deleted"', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
await wrapper.find('a[data-test="deleted"]').trigger('click')
|
||||
})
|
||||
|
||||
it('refetches contributions', () => {
|
||||
expect(refetchSpy).toBeCalled()
|
||||
it('refetches contributions with proper filter', () => {
|
||||
expect(adminListAllContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
statusFilter: ['DELETED'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('click tab "all"', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
refetchSpy = jest.spyOn(wrapper.vm.$apollo.queries.ListAllContributions, 'refetch')
|
||||
await wrapper.find('a[data-test="all"]').trigger('click')
|
||||
})
|
||||
|
||||
it('has statusFilter set to ["IN_PROGRESS", "PENDING", "CONFIRMED", "DENIED", "DELETED"]', () => {
|
||||
expect(
|
||||
wrapper.vm.$apollo.queries.ListAllContributions.observer.options.variables,
|
||||
).toMatchObject({
|
||||
it('refetches contributions with proper filter', () => {
|
||||
expect(adminListAllContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
statusFilter: ['IN_PROGRESS', 'PENDING', 'CONFIRMED', 'DENIED', 'DELETED'],
|
||||
})
|
||||
})
|
||||
|
||||
it('refetches contributions', () => {
|
||||
expect(refetchSpy).toBeCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -412,10 +416,20 @@ describe('CreationConfirm', () => {
|
||||
await wrapper.findComponent({ name: 'OpenCreationsTable' }).vm.$emit('update-state', 2)
|
||||
})
|
||||
|
||||
it.skip('updates the status', () => {
|
||||
it('updates the status', () => {
|
||||
expect(wrapper.vm.items.find((obj) => obj.id === 2).messagesCount).toBe(1)
|
||||
expect(wrapper.vm.items.find((obj) => obj.id === 2).state).toBe('IN_PROGRESS')
|
||||
})
|
||||
})
|
||||
|
||||
describe('unknown variant', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({ variant: 'unknown' })
|
||||
})
|
||||
|
||||
it('has overlay icon "info"', () => {
|
||||
expect(wrapper.vm.overlayIcon).toBe('info')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -5,25 +5,37 @@
|
||||
<b-tabs v-model="tabIndex" content-class="mt-3" fill>
|
||||
<b-tab active :title-link-attributes="{ 'data-test': 'open' }">
|
||||
<template #title>
|
||||
<b-icon icon="bell-fill" variant="primary"></b-icon>
|
||||
{{ $t('contributions.open') }}
|
||||
<b-badge v-if="$store.state.openCreations > 0" variant="danger">
|
||||
{{ $store.state.openCreations }}
|
||||
</b-badge>
|
||||
</template>
|
||||
</b-tab>
|
||||
<b-tab
|
||||
:title="$t('contributions.confirms')"
|
||||
:title-link-attributes="{ 'data-test': 'confirmed' }"
|
||||
/>
|
||||
<b-tab
|
||||
:title="$t('contributions.denied')"
|
||||
:title-link-attributes="{ 'data-test': 'denied' }"
|
||||
/>
|
||||
<b-tab
|
||||
:title="$t('contributions.deleted')"
|
||||
:title-link-attributes="{ 'data-test': 'deleted' }"
|
||||
/>
|
||||
<b-tab :title="$t('contributions.all')" :title-link-attributes="{ 'data-test': 'all' }" />
|
||||
<b-tab :title-link-attributes="{ 'data-test': 'confirmed' }">
|
||||
<template #title>
|
||||
<b-icon icon="check" variant="success"></b-icon>
|
||||
{{ $t('contributions.confirms') }}
|
||||
</template>
|
||||
</b-tab>
|
||||
<b-tab :title-link-attributes="{ 'data-test': 'denied' }">
|
||||
<template #title>
|
||||
<b-icon icon="x-circle" variant="warning"></b-icon>
|
||||
{{ $t('contributions.denied') }}
|
||||
</template>
|
||||
</b-tab>
|
||||
<b-tab :title-link-attributes="{ 'data-test': 'deleted' }">
|
||||
<template #title>
|
||||
<b-icon icon="trash" variant="danger"></b-icon>
|
||||
{{ $t('contributions.deleted') }}
|
||||
</template>
|
||||
</b-tab>
|
||||
<b-tab :title-link-attributes="{ 'data-test': 'all' }">
|
||||
<template #title>
|
||||
<b-icon icon="list"></b-icon>
|
||||
{{ $t('contributions.all') }}
|
||||
</template>
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</div>
|
||||
<open-creations-table
|
||||
@ -73,7 +85,7 @@
|
||||
<script>
|
||||
import Overlay from '../components/Overlay'
|
||||
import OpenCreationsTable from '../components/Tables/OpenCreationsTable'
|
||||
import { listAllContributions } from '../graphql/listAllContributions'
|
||||
import { adminListAllContributions } from '../graphql/adminListAllContributions'
|
||||
import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
|
||||
import { confirmContribution } from '../graphql/confirmContribution'
|
||||
import { denyContribution } from '../graphql/denyContribution'
|
||||
@ -172,19 +184,17 @@ export default {
|
||||
this.items.find((obj) => obj.id === id).messagesCount++
|
||||
this.items.find((obj) => obj.id === id).state = 'IN_PROGRESS'
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
statusFilter() {
|
||||
this.$apollo.queries.ListAllContributions.refetch()
|
||||
formatDateOrDash(value) {
|
||||
return value ? this.$d(new Date(value), 'short') : '—'
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
fields() {
|
||||
return [
|
||||
[
|
||||
// open contributions
|
||||
{ key: 'bookmark', label: this.$t('delete') },
|
||||
{ key: 'deny', label: this.$t('deny') },
|
||||
{ key: 'email', label: this.$t('e_mail') },
|
||||
{ key: 'firstName', label: this.$t('firstname') },
|
||||
{ key: 'lastName', label: this.$t('lastname') },
|
||||
{
|
||||
@ -199,14 +209,15 @@ export default {
|
||||
key: 'contributionDate',
|
||||
label: this.$t('created'),
|
||||
formatter: (value) => {
|
||||
return this.$d(new Date(value), 'short')
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'moderator', label: this.$t('moderator') },
|
||||
{ key: 'editCreation', label: this.$t('edit') },
|
||||
{ key: 'editCreation', label: this.$t('chat') },
|
||||
{ key: 'confirm', label: this.$t('save') },
|
||||
],
|
||||
[
|
||||
// confirmed contributions
|
||||
{ key: 'firstName', label: this.$t('firstname') },
|
||||
{ key: 'lastName', label: this.$t('lastname') },
|
||||
{
|
||||
@ -221,27 +232,28 @@ export default {
|
||||
key: 'contributionDate',
|
||||
label: this.$t('created'),
|
||||
formatter: (value) => {
|
||||
return this.$d(new Date(value), 'short')
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'createdAt',
|
||||
label: this.$t('createdAt'),
|
||||
formatter: (value) => {
|
||||
return this.$d(new Date(value), 'short')
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'confirmedAt',
|
||||
label: this.$t('contributions.confirms'),
|
||||
formatter: (value) => {
|
||||
return this.$d(new Date(value), 'short')
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'confirmedBy', label: this.$t('moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
[
|
||||
{ key: 'reActive', label: 'reActive' },
|
||||
// denied contributions
|
||||
{ key: 'firstName', label: this.$t('firstname') },
|
||||
{ key: 'lastName', label: this.$t('lastname') },
|
||||
{
|
||||
@ -256,29 +268,28 @@ export default {
|
||||
key: 'contributionDate',
|
||||
label: this.$t('created'),
|
||||
formatter: (value) => {
|
||||
return this.$d(new Date(value), 'short')
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'createdAt',
|
||||
label: this.$t('createdAt'),
|
||||
formatter: (value) => {
|
||||
return this.$d(new Date(value), 'short')
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'deniedAt',
|
||||
label: this.$t('contributions.denied'),
|
||||
formatter: (value) => {
|
||||
return this.$d(new Date(value), 'short')
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'deniedBy', label: this.$t('mod') },
|
||||
{ key: 'deniedBy', label: this.$t('moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
[],
|
||||
[
|
||||
{ key: 'state', label: 'state' },
|
||||
// deleted contributions
|
||||
{ key: 'firstName', label: this.$t('firstname') },
|
||||
{ key: 'lastName', label: this.$t('lastname') },
|
||||
{
|
||||
@ -293,24 +304,61 @@ export default {
|
||||
key: 'contributionDate',
|
||||
label: this.$t('created'),
|
||||
formatter: (value) => {
|
||||
return this.$d(new Date(value), 'short')
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'createdAt',
|
||||
label: this.$t('createdAt'),
|
||||
formatter: (value) => {
|
||||
return this.$d(new Date(value), 'short')
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'deletedAt',
|
||||
label: this.$t('contributions.deleted'),
|
||||
formatter: (value) => {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'deletedBy', label: this.$t('moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
[
|
||||
// all contributions
|
||||
{ key: 'state', label: this.$t('status') },
|
||||
{ key: 'firstName', label: this.$t('firstname') },
|
||||
{ key: 'lastName', label: this.$t('lastname') },
|
||||
{
|
||||
key: 'amount',
|
||||
label: this.$t('creation'),
|
||||
formatter: (value) => {
|
||||
return value + ' GDD'
|
||||
},
|
||||
},
|
||||
{ key: 'memo', label: this.$t('text'), class: 'text-break' },
|
||||
{
|
||||
key: 'contributionDate',
|
||||
label: this.$t('created'),
|
||||
formatter: (value) => {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'createdAt',
|
||||
label: this.$t('createdAt'),
|
||||
formatter: (value) => {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'confirmedAt',
|
||||
label: this.$t('contributions.confirms'),
|
||||
formatter: (value) => {
|
||||
return this.$d(new Date(value), 'short')
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'confirmedBy', label: this.$t('mod') },
|
||||
{ key: 'confirmedBy', label: this.$t('moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
][this.tabIndex]
|
||||
@ -349,19 +397,22 @@ export default {
|
||||
apollo: {
|
||||
ListAllContributions: {
|
||||
query() {
|
||||
return listAllContributions
|
||||
return adminListAllContributions
|
||||
},
|
||||
variables() {
|
||||
// may be at some point we need a pagination here
|
||||
return {
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
statusFilter: this.statusFilter,
|
||||
}
|
||||
},
|
||||
update({ listAllContributions }) {
|
||||
this.rows = listAllContributions.contributionCount
|
||||
this.items = listAllContributions.contributionList
|
||||
fetchPolicy: 'no-cache',
|
||||
update({ adminListAllContributions }) {
|
||||
this.rows = adminListAllContributions.contributionCount
|
||||
this.items = adminListAllContributions.contributionList
|
||||
if (this.statusFilter === FILTER_TAB_MAP[0]) {
|
||||
this.$store.commit('setOpenCreations', adminListAllContributions.contributionCount)
|
||||
}
|
||||
},
|
||||
error({ message }) {
|
||||
this.toastError(message)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Overview from './Overview'
|
||||
import { listAllContributions } from '../graphql/listAllContributions'
|
||||
import { adminListAllContributions } from '../graphql/adminListAllContributions'
|
||||
import VueApollo from 'vue-apollo'
|
||||
import { createMockClient } from 'mock-apollo-client'
|
||||
import { toastErrorSpy } from '../../test/testSetup'
|
||||
@ -30,7 +30,7 @@ const mocks = {
|
||||
|
||||
const defaultData = () => {
|
||||
return {
|
||||
listAllContributions: {
|
||||
adminListAllContributions: {
|
||||
contributionCount: 2,
|
||||
contributionList: [
|
||||
{
|
||||
@ -84,11 +84,11 @@ const defaultData = () => {
|
||||
|
||||
describe('Overview', () => {
|
||||
let wrapper
|
||||
const listAllContributionsMock = jest.fn()
|
||||
const adminListAllContributionsMock = jest.fn()
|
||||
|
||||
mockClient.setRequestHandler(
|
||||
listAllContributions,
|
||||
listAllContributionsMock
|
||||
adminListAllContributions,
|
||||
adminListAllContributionsMock
|
||||
.mockRejectedValueOnce({ message: 'Ouch!' })
|
||||
.mockResolvedValue({ data: defaultData() }),
|
||||
)
|
||||
@ -109,8 +109,8 @@ describe('Overview', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('calls the listAllContributions query', () => {
|
||||
expect(listAllContributionsMock).toBeCalledWith({
|
||||
it('calls the adminListAllContributions query', () => {
|
||||
expect(adminListAllContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { listAllContributions } from '../graphql/listAllContributions'
|
||||
import { adminListAllContributions } from '../graphql/adminListAllContributions'
|
||||
|
||||
export default {
|
||||
name: 'overview',
|
||||
@ -43,7 +43,7 @@ export default {
|
||||
apollo: {
|
||||
AllContributions: {
|
||||
query() {
|
||||
return listAllContributions
|
||||
return adminListAllContributions
|
||||
},
|
||||
variables() {
|
||||
// may be at some point we need a pagination here
|
||||
@ -51,8 +51,8 @@ export default {
|
||||
statusFilter: this.statusFilter,
|
||||
}
|
||||
},
|
||||
update({ listAllContributions }) {
|
||||
this.$store.commit('setOpenCreations', listAllContributions.contributionCount)
|
||||
update({ adminListAllContributions }) {
|
||||
this.$store.commit('setOpenCreations', adminListAllContributions.contributionCount)
|
||||
},
|
||||
error({ message }) {
|
||||
this.toastError(message)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Server
|
||||
JWT_EXPIRES_IN=1m
|
||||
JWT_EXPIRES_IN=2m
|
||||
|
||||
# Email
|
||||
EMAIL=true
|
||||
|
||||
@ -2,16 +2,10 @@ module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
// jest: true,
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['prettier', '@typescript-eslint' /*, 'jest' */],
|
||||
extends: [
|
||||
'standard',
|
||||
'eslint:recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
],
|
||||
plugins: ['prettier', '@typescript-eslint', 'type-graphql'],
|
||||
extends: ['standard', 'eslint:recommended', 'plugin:prettier/recommended'],
|
||||
// add your custom rules here
|
||||
rules: {
|
||||
'no-console': ['error'],
|
||||
@ -23,4 +17,28 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
// only for ts files
|
||||
{
|
||||
files: ['*.ts'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
'plugin:type-graphql/recommended',
|
||||
],
|
||||
rules: {
|
||||
// allow explicitly defined dangling promises
|
||||
'@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
|
||||
'no-void': ['error', { allowAsStatement: true }],
|
||||
// ignore prefer-regexp-exec rule to allow string.match(regex)
|
||||
'@typescript-eslint/prefer-regexp-exec': 'off',
|
||||
},
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
// this is to properly reference the referenced project database without requirement of compiling it
|
||||
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@ -4,6 +4,11 @@ module.exports = {
|
||||
preset: 'ts-jest',
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
lines: 80,
|
||||
},
|
||||
},
|
||||
setupFiles: ['<rootDir>/test/testSetup.ts'],
|
||||
setupFilesAfterEnv: ['<rootDir>/test/extensions.ts'],
|
||||
modulePathIgnorePatterns: ['<rootDir>/build/'],
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-backend",
|
||||
"version": "1.18.2",
|
||||
"version": "1.19.1",
|
||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/backend",
|
||||
@ -13,7 +13,7 @@
|
||||
"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",
|
||||
"lint": "eslint --max-warnings=0 --ext .js,.ts .",
|
||||
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles",
|
||||
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --forceExit --detectOpenHandles",
|
||||
"seed": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/seeds/index.ts",
|
||||
"klicktipp": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/util/klicktipp.ts",
|
||||
"locales": "scripts/sort.sh"
|
||||
@ -65,6 +65,7 @@
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-type-graphql": "^1.0.0",
|
||||
"faker": "^5.5.3",
|
||||
"jest": "^27.2.4",
|
||||
"nodemon": "^2.0.7",
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import axios from 'axios'
|
||||
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import LogError from '@/server/LogError'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const apiPost = async (url: string, payload: unknown): Promise<any> => {
|
||||
logger.trace('POST: url=' + url + ' payload=' + payload)
|
||||
logger.trace('POST', url, payload)
|
||||
return axios
|
||||
.post(url, payload)
|
||||
.then((result) => {
|
||||
logger.trace('POST-Response: result=' + result)
|
||||
logger.trace('POST-Response', result)
|
||||
if (result.status !== 200) {
|
||||
throw new Error('HTTP Status Error ' + result.status)
|
||||
throw new LogError('HTTP Status Error', result.status)
|
||||
}
|
||||
if (result.data.state !== 'success') {
|
||||
throw new Error(result.data.msg)
|
||||
@ -28,9 +31,9 @@ export const apiGet = async (url: string): Promise<any> => {
|
||||
return axios
|
||||
.get(url)
|
||||
.then((result) => {
|
||||
logger.trace('GET-Response: result=' + result)
|
||||
logger.trace('GET-Response', result)
|
||||
if (result.status !== 200) {
|
||||
throw new Error('HTTP Status Error ' + result.status)
|
||||
throw new LogError('HTTP Status Error', result.status)
|
||||
}
|
||||
if (!['success', 'warning'].includes(result.data.state)) {
|
||||
throw new Error(result.data.msg)
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { KlicktippConnector } from './klicktippConnector'
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
/* 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'
|
||||
|
||||
@ -2,7 +2,6 @@ import { RIGHTS } from './RIGHTS'
|
||||
|
||||
export const INALIENABLE_RIGHTS = [
|
||||
RIGHTS.LOGIN,
|
||||
RIGHTS.GET_COMMUNITY_INFO,
|
||||
RIGHTS.COMMUNITIES,
|
||||
RIGHTS.CREATE_USER,
|
||||
RIGHTS.SEND_RESET_PASSWORD_EMAIL,
|
||||
|
||||
@ -2,7 +2,6 @@ export enum RIGHTS {
|
||||
LOGIN = 'LOGIN',
|
||||
VERIFY_LOGIN = 'VERIFY_LOGIN',
|
||||
BALANCE = 'BALANCE',
|
||||
GET_COMMUNITY_INFO = 'GET_COMMUNITY_INFO',
|
||||
COMMUNITIES = 'COMMUNITIES',
|
||||
LIST_GDT_ENTRIES = 'LIST_GDT_ENTRIES',
|
||||
EXIST_PID = 'EXIST_PID',
|
||||
@ -54,4 +53,5 @@ export enum RIGHTS {
|
||||
UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK',
|
||||
ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',
|
||||
DENY_CONTRIBUTION = 'DENY_CONTRIBUTION',
|
||||
ADMIN_OPEN_CREATIONS = 'ADMIN_OPEN_CREATIONS',
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ Decimal.set({
|
||||
})
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0060-update_communities_table',
|
||||
DB_VERSION: '0061-event_refactoring',
|
||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||
LOG4JS_CONFIG: 'log4js-config.json',
|
||||
// default log level on production should be info
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import { createTransport } from 'nodemailer'
|
||||
import { logger, i18n } from '@test/testSetup'
|
||||
import CONFIG from '@/config'
|
||||
@ -8,6 +10,7 @@ CONFIG.EMAIL_SMTP_URL = 'EMAIL_SMTP_URL'
|
||||
CONFIG.EMAIL_SMTP_PORT = '1234'
|
||||
CONFIG.EMAIL_USERNAME = 'user'
|
||||
CONFIG.EMAIL_PASSWORD = 'pwd'
|
||||
CONFIG.EMAIL_TLS = true
|
||||
|
||||
jest.mock('nodemailer', () => {
|
||||
return {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import CONFIG from '@/config'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import path from 'path'
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { testEnvironment } from '@test/helpers'
|
||||
import { logger, i18n as localization } from '@test/testSetup'
|
||||
@ -106,7 +109,7 @@ describe('sendEmailVariants', () => {
|
||||
'you have received a message from Bibi Bloxberg regarding your common good contribution “My contribution.”.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
|
||||
'To view and reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||
@ -424,7 +427,7 @@ describe('sendEmailVariants', () => {
|
||||
'Your public good contribution “My contribution.” was rejected by Bibi Bloxberg.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
|
||||
'To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||
@ -502,7 +505,7 @@ describe('sendEmailVariants', () => {
|
||||
'Your public good contribution “My contribution.” was deleted by Bibi Bloxberg.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
|
||||
'To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||
|
||||
@ -1,212 +1,217 @@
|
||||
import { EventProtocol as DbEvent } from '@entity/EventProtocol'
|
||||
import { Event as DbEvent } from '@entity/Event'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage'
|
||||
import { Contribution as DbContribution } from '@entity/Contribution'
|
||||
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { EventProtocolType } from './EventProtocolType'
|
||||
|
||||
export const Event = (
|
||||
type: EventProtocolType,
|
||||
userId: number,
|
||||
xUserId: number | null = null,
|
||||
xCommunityId: number | null = null,
|
||||
transactionId: number | null = null,
|
||||
contributionId: number | null = null,
|
||||
affectedUser: DbUser,
|
||||
actingUser: DbUser,
|
||||
involvedUser: DbUser | null = null,
|
||||
involvedTransaction: DbTransaction | null = null,
|
||||
involvedContribution: DbContribution | null = null,
|
||||
involvedContributionMessage: DbContributionMessage | null = null,
|
||||
amount: Decimal | null = null,
|
||||
messageId: number | null = null,
|
||||
): DbEvent => {
|
||||
const event = new DbEvent()
|
||||
event.type = type
|
||||
event.userId = userId
|
||||
event.xUserId = xUserId
|
||||
event.xCommunityId = xCommunityId
|
||||
event.transactionId = transactionId
|
||||
event.contributionId = contributionId
|
||||
event.affectedUser = affectedUser
|
||||
event.actingUser = actingUser
|
||||
event.involvedUser = involvedUser
|
||||
event.involvedTransaction = involvedTransaction
|
||||
event.involvedContribution = involvedContribution
|
||||
event.involvedContributionMessage = involvedContributionMessage
|
||||
event.amount = amount
|
||||
event.messageId = messageId
|
||||
return event
|
||||
}
|
||||
|
||||
export const EVENT_CONTRIBUTION_CREATE = async (
|
||||
userId: number,
|
||||
contributionId: number,
|
||||
user: DbUser,
|
||||
contribution: DbContribution,
|
||||
amount: Decimal,
|
||||
): Promise<DbEvent> =>
|
||||
Event(
|
||||
EventProtocolType.CONTRIBUTION_CREATE,
|
||||
userId,
|
||||
user,
|
||||
user,
|
||||
null,
|
||||
null,
|
||||
contribution,
|
||||
null,
|
||||
contributionId,
|
||||
amount,
|
||||
).save()
|
||||
|
||||
export const EVENT_CONTRIBUTION_DELETE = async (
|
||||
userId: number,
|
||||
contributionId: number,
|
||||
user: DbUser,
|
||||
contribution: DbContribution,
|
||||
amount: Decimal,
|
||||
): Promise<DbEvent> =>
|
||||
Event(
|
||||
EventProtocolType.CONTRIBUTION_DELETE,
|
||||
userId,
|
||||
user,
|
||||
user,
|
||||
null,
|
||||
null,
|
||||
contribution,
|
||||
null,
|
||||
contributionId,
|
||||
amount,
|
||||
).save()
|
||||
|
||||
export const EVENT_CONTRIBUTION_UPDATE = async (
|
||||
userId: number,
|
||||
contributionId: number,
|
||||
user: DbUser,
|
||||
contribution: DbContribution,
|
||||
amount: Decimal,
|
||||
): Promise<DbEvent> =>
|
||||
Event(
|
||||
EventProtocolType.CONTRIBUTION_UPDATE,
|
||||
userId,
|
||||
user,
|
||||
user,
|
||||
null,
|
||||
null,
|
||||
contribution,
|
||||
null,
|
||||
contributionId,
|
||||
amount,
|
||||
).save()
|
||||
|
||||
export const EVENT_ADMIN_CONTRIBUTION_CREATE = async (
|
||||
userId: number,
|
||||
contributionId: number,
|
||||
user: DbUser,
|
||||
moderator: DbUser,
|
||||
contribution: DbContribution,
|
||||
amount: Decimal,
|
||||
): Promise<DbEvent> =>
|
||||
Event(
|
||||
EventProtocolType.ADMIN_CONTRIBUTION_CREATE,
|
||||
userId,
|
||||
user,
|
||||
moderator,
|
||||
null,
|
||||
null,
|
||||
contribution,
|
||||
null,
|
||||
contributionId,
|
||||
amount,
|
||||
).save()
|
||||
|
||||
export const EVENT_ADMIN_CONTRIBUTION_UPDATE = async (
|
||||
userId: number,
|
||||
contributionId: number,
|
||||
user: DbUser,
|
||||
moderator: DbUser,
|
||||
contribution: DbContribution,
|
||||
amount: Decimal,
|
||||
): Promise<DbEvent> =>
|
||||
Event(
|
||||
EventProtocolType.ADMIN_CONTRIBUTION_UPDATE,
|
||||
userId,
|
||||
user,
|
||||
moderator,
|
||||
null,
|
||||
null,
|
||||
contribution,
|
||||
null,
|
||||
contributionId,
|
||||
amount,
|
||||
).save()
|
||||
|
||||
export const EVENT_ADMIN_CONTRIBUTION_DELETE = async (
|
||||
userId: number,
|
||||
contributionId: number,
|
||||
user: DbUser,
|
||||
moderator: DbUser,
|
||||
contribution: DbContribution,
|
||||
amount: Decimal,
|
||||
): Promise<DbEvent> =>
|
||||
Event(
|
||||
EventProtocolType.ADMIN_CONTRIBUTION_DELETE,
|
||||
userId,
|
||||
user,
|
||||
moderator,
|
||||
null,
|
||||
null,
|
||||
contribution,
|
||||
null,
|
||||
contributionId,
|
||||
amount,
|
||||
).save()
|
||||
|
||||
export const EVENT_CONTRIBUTION_CONFIRM = async (
|
||||
userId: number,
|
||||
contributionId: number,
|
||||
user: DbUser,
|
||||
moderator: DbUser,
|
||||
contribution: DbContribution,
|
||||
amount: Decimal,
|
||||
): Promise<DbEvent> =>
|
||||
Event(
|
||||
EventProtocolType.CONTRIBUTION_CONFIRM,
|
||||
userId,
|
||||
user,
|
||||
moderator,
|
||||
null,
|
||||
null,
|
||||
contribution,
|
||||
null,
|
||||
contributionId,
|
||||
amount,
|
||||
).save()
|
||||
|
||||
export const EVENT_ADMIN_CONTRIBUTION_DENY = async (
|
||||
userId: number,
|
||||
xUserId: number,
|
||||
contributionId: number,
|
||||
user: DbUser,
|
||||
moderator: DbUser,
|
||||
contribution: DbContribution,
|
||||
amount: Decimal,
|
||||
): Promise<DbEvent> =>
|
||||
Event(
|
||||
EventProtocolType.ADMIN_CONTRIBUTION_DENY,
|
||||
userId,
|
||||
xUserId,
|
||||
user,
|
||||
moderator,
|
||||
null,
|
||||
null,
|
||||
contributionId,
|
||||
contribution,
|
||||
null,
|
||||
amount,
|
||||
).save()
|
||||
|
||||
export const EVENT_TRANSACTION_SEND = async (
|
||||
userId: number,
|
||||
xUserId: number,
|
||||
transactionId: number,
|
||||
user: DbUser,
|
||||
involvedUser: DbUser,
|
||||
transaction: DbTransaction,
|
||||
amount: Decimal,
|
||||
): Promise<DbEvent> =>
|
||||
Event(
|
||||
EventProtocolType.TRANSACTION_SEND,
|
||||
userId,
|
||||
xUserId,
|
||||
user,
|
||||
user,
|
||||
involvedUser,
|
||||
transaction,
|
||||
null,
|
||||
transactionId,
|
||||
null,
|
||||
amount,
|
||||
).save()
|
||||
|
||||
export const EVENT_TRANSACTION_RECEIVE = async (
|
||||
userId: number,
|
||||
xUserId: number,
|
||||
transactionId: number,
|
||||
user: DbUser,
|
||||
involvedUser: DbUser,
|
||||
transaction: DbTransaction,
|
||||
amount: Decimal,
|
||||
): Promise<DbEvent> =>
|
||||
Event(
|
||||
EventProtocolType.TRANSACTION_RECEIVE,
|
||||
userId,
|
||||
xUserId,
|
||||
user,
|
||||
involvedUser,
|
||||
involvedUser,
|
||||
transaction,
|
||||
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_LOGIN = async (user: DbUser): Promise<DbEvent> =>
|
||||
Event(EventProtocolType.LOGIN, user, user).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_ACCOUNT_MULTIREGISTRATION_EMAIL = async (user: DbUser): Promise<DbEvent> =>
|
||||
Event(EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL, user, { id: 0 } as DbUser).save()
|
||||
|
||||
export const EVENT_SEND_CONFIRMATION_EMAIL = async (userId: number): Promise<DbEvent> =>
|
||||
Event(EventProtocolType.SEND_CONFIRMATION_EMAIL, userId).save()
|
||||
export const EVENT_SEND_CONFIRMATION_EMAIL = async (user: DbUser): Promise<DbEvent> =>
|
||||
Event(EventProtocolType.SEND_CONFIRMATION_EMAIL, user, user).save()
|
||||
|
||||
export const EVENT_ADMIN_SEND_CONFIRMATION_EMAIL = async (userId: number): Promise<DbEvent> =>
|
||||
Event(EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL, userId).save()
|
||||
export const EVENT_ADMIN_SEND_CONFIRMATION_EMAIL = async (
|
||||
user: DbUser,
|
||||
moderator: DbUser,
|
||||
): Promise<DbEvent> =>
|
||||
Event(EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL, user, moderator).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 (user: DbUser): Promise<DbEvent> =>
|
||||
Event(EventProtocolType.REGISTER, user, user).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()
|
||||
export const EVENT_ACTIVATE_ACCOUNT = async (user: DbUser): Promise<DbEvent> =>
|
||||
Event(EventProtocolType.ACTIVATE_ACCOUNT, user, user).save()
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { gql } from 'graphql-request'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
@ -18,9 +21,13 @@ export async function requestGetPublicKey(dbCom: DbCommunity): Promise<string |
|
||||
}
|
||||
}
|
||||
`
|
||||
const variables = {}
|
||||
|
||||
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)
|
||||
if (data) {
|
||||
logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey)
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import { gql } from 'graphql-request'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
@ -18,9 +21,13 @@ export async function requestGetPublicKey(dbCom: DbCommunity): Promise<string |
|
||||
}
|
||||
}
|
||||
`
|
||||
const variables = {}
|
||||
|
||||
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)
|
||||
if (data) {
|
||||
logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey)
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { PatchedRequestInit } from 'graphql-request/dist/types'
|
||||
|
||||
type ClientInstance = {
|
||||
url: string
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
client: GraphQLGetClient
|
||||
}
|
||||
|
||||
export class GraphQLGetClient extends GraphQLClient {
|
||||
private static instance: GraphQLGetClient
|
||||
private static instanceArray: ClientInstance[] = []
|
||||
|
||||
/**
|
||||
* The Singleton's constructor should always be private to prevent direct
|
||||
@ -20,16 +26,18 @@ export class GraphQLGetClient extends GraphQLClient {
|
||||
* just one instance of each subclass around.
|
||||
*/
|
||||
public static getInstance(url: string): GraphQLGetClient {
|
||||
if (!GraphQLGetClient.instance) {
|
||||
GraphQLGetClient.instance = new GraphQLGetClient(url, {
|
||||
method: 'GET',
|
||||
jsonSerializer: {
|
||||
parse: JSON.parse,
|
||||
stringify: JSON.stringify,
|
||||
},
|
||||
})
|
||||
const instance = GraphQLGetClient.instanceArray.find((instance) => instance.url === url)
|
||||
if (instance) {
|
||||
return instance.client
|
||||
}
|
||||
|
||||
return GraphQLGetClient.instance
|
||||
const client = new GraphQLGetClient(url, {
|
||||
method: 'GET',
|
||||
jsonSerializer: {
|
||||
parse: JSON.parse,
|
||||
stringify: JSON.stringify,
|
||||
},
|
||||
})
|
||||
GraphQLGetClient.instanceArray.push({ url, client } as ClientInstance)
|
||||
return client
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
@ -150,7 +154,8 @@ describe('validate Communities', () => {
|
||||
})
|
||||
it('logs unsupported api for community with api 2_0 ', () => {
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
`Federation: dbCom: ${dbCom.id} with unsupported apiVersion=2_0; supported versions=1_0,1_1`,
|
||||
`Federation: dbCom: ${dbCom.id} with unsupported apiVersion=2_0; supported versions`,
|
||||
['1_0', '1_1'],
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -8,14 +8,14 @@ import { backendLogger as logger } from '@/server/logger'
|
||||
import { ApiVersionType } from './enum/apiVersionType'
|
||||
import LogError from '@/server/LogError'
|
||||
|
||||
export async function startValidateCommunities(timerInterval: number): Promise<void> {
|
||||
export function startValidateCommunities(timerInterval: number): void {
|
||||
logger.info(
|
||||
`Federation: startValidateCommunities loop with an interval of ${timerInterval} ms...`,
|
||||
)
|
||||
// TODO: replace the timer-loop by an event-based communication to verify announced foreign communities
|
||||
// better to use setTimeout twice than setInterval once -> see https://javascript.info/settimeout-setinterval
|
||||
setTimeout(function run() {
|
||||
validateCommunities()
|
||||
void validateCommunities()
|
||||
setTimeout(run, timerInterval)
|
||||
}, timerInterval)
|
||||
}
|
||||
@ -27,8 +27,8 @@ export async function validateCommunities(): Promise<void> {
|
||||
.getMany()
|
||||
|
||||
logger.debug(`Federation: found ${dbCommunities.length} dbCommunities`)
|
||||
dbCommunities.forEach(async function (dbCom) {
|
||||
logger.debug(`Federation: dbCom: ${JSON.stringify(dbCom)}`)
|
||||
for (const dbCom of dbCommunities) {
|
||||
logger.debug('Federation: dbCom', dbCom)
|
||||
const apiValueStrings: string[] = Object.values(ApiVersionType)
|
||||
logger.debug(`suppported ApiVersions=`, apiValueStrings)
|
||||
if (apiValueStrings.includes(dbCom.apiVersion)) {
|
||||
@ -38,19 +38,22 @@ export async function validateCommunities(): Promise<void> {
|
||||
try {
|
||||
const pubKey = await invokeVersionedRequestGetPublicKey(dbCom)
|
||||
logger.info(
|
||||
`Federation: received publicKey=${pubKey} from endpoint=${dbCom.endPoint}/${dbCom.apiVersion}`,
|
||||
'Federation: received publicKey from endpoint',
|
||||
pubKey,
|
||||
`${dbCom.endPoint}/${dbCom.apiVersion}`,
|
||||
)
|
||||
if (pubKey && pubKey === dbCom.publicKey.toString('hex')) {
|
||||
if (pubKey && pubKey === dbCom.publicKey.toString()) {
|
||||
logger.info(`Federation: matching publicKey: ${pubKey}`)
|
||||
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)}`)
|
||||
} 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) {
|
||||
if (!isLogError(err)) {
|
||||
logger.error(`Error:`, err)
|
||||
@ -58,10 +61,11 @@ export async function validateCommunities(): Promise<void> {
|
||||
}
|
||||
} else {
|
||||
logger.warn(
|
||||
`Federation: dbCom: ${dbCom.id} with unsupported apiVersion=${dbCom.apiVersion}; supported versions=${apiValueStrings}`,
|
||||
`Federation: dbCom: ${dbCom.id} with unsupported apiVersion=${dbCom.apiVersion}; supported versions`,
|
||||
apiValueStrings,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function isLogError(err: unknown) {
|
||||
|
||||
@ -22,7 +22,7 @@ export default class ContributionLinkArgs {
|
||||
validTo?: string | null
|
||||
|
||||
@Field(() => Decimal, { nullable: true })
|
||||
maxAmountPerMonth: Decimal | null
|
||||
maxAmountPerMonth?: Decimal | null
|
||||
|
||||
@Field(() => Int)
|
||||
maxPerCycle: number
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { ArgsType, Field, InputType } from 'type-graphql'
|
||||
import { ArgsType, Field, Int, InputType } from 'type-graphql'
|
||||
|
||||
@InputType()
|
||||
@ArgsType()
|
||||
export default class ContributionMessageArgs {
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
contributionId: number
|
||||
|
||||
@Field(() => String)
|
||||
|
||||
@ -11,11 +11,11 @@ export default class CreateUserArgs {
|
||||
@Field(() => String)
|
||||
lastName: string
|
||||
|
||||
@Field(() => String)
|
||||
language?: string // Will default to DEFAULT_LANGUAGE
|
||||
@Field(() => String, { nullable: true })
|
||||
language?: string | null
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
publisherId: number
|
||||
publisherId?: number | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
redeemCode?: string | null
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable type-graphql/invalid-nullable-input-type */
|
||||
import { ArgsType, Field, Int } from 'type-graphql'
|
||||
import { Order } from '@enum/Order'
|
||||
|
||||
|
||||
@ -7,11 +7,14 @@ export default class SearchUsersArgs {
|
||||
searchText: string
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
// eslint-disable-next-line type-graphql/invalid-nullable-input-type
|
||||
currentPage?: number
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
// eslint-disable-next-line type-graphql/invalid-nullable-input-type
|
||||
pageSize?: number
|
||||
|
||||
// eslint-disable-next-line type-graphql/wrong-decorator-signature
|
||||
@Field(() => SearchUsersFilters, { nullable: true, defaultValue: null })
|
||||
filters: SearchUsersFilters
|
||||
filters?: SearchUsersFilters | null
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@ import { Field, InputType } from 'type-graphql'
|
||||
@InputType()
|
||||
export default class SearchUsersFilters {
|
||||
@Field(() => Boolean, { nullable: true, defaultValue: null })
|
||||
byActivated: boolean
|
||||
byActivated?: boolean | null
|
||||
|
||||
@Field(() => Boolean, { nullable: true, defaultValue: null })
|
||||
byDeleted: boolean
|
||||
byDeleted?: boolean | null
|
||||
}
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
/* eslint-disable type-graphql/invalid-nullable-input-type */
|
||||
import { Field, InputType } from 'type-graphql'
|
||||
|
||||
@InputType()
|
||||
export default class TransactionLinkFilters {
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
withDeleted: boolean
|
||||
withDeleted?: boolean
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
withExpired: boolean
|
||||
withExpired?: boolean
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
withRedeemed: boolean
|
||||
withRedeemed?: boolean
|
||||
}
|
||||
|
||||
@ -9,5 +9,5 @@ export default class UnsecureLoginArgs {
|
||||
password: string
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
publisherId: number
|
||||
publisherId?: number | null
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ArgsType, Field } from 'type-graphql'
|
||||
import { ArgsType, Field, Int } from 'type-graphql'
|
||||
|
||||
@ArgsType()
|
||||
export default class UpdateUserInfosArgs {
|
||||
@ -11,8 +11,8 @@ export default class UpdateUserInfosArgs {
|
||||
@Field({ nullable: true })
|
||||
language?: string
|
||||
|
||||
@Field({ nullable: true })
|
||||
publisherId?: number
|
||||
@Field(() => Int, { nullable: true })
|
||||
publisherId?: number | null
|
||||
|
||||
@Field({ nullable: true })
|
||||
password?: string
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { AuthChecker } from 'type-graphql'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { ObjectType, Field, Int, Float } from 'type-graphql'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
@ObjectType()
|
||||
@ -19,14 +19,14 @@ export class Balance {
|
||||
@Field(() => Decimal)
|
||||
balance: Decimal
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@Field(() => Float, { nullable: true })
|
||||
balanceGDT: number | null
|
||||
|
||||
// the count of all transactions
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
count: number
|
||||
|
||||
// the count of transaction links
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
linkCount: number
|
||||
}
|
||||
|
||||
@ -1,31 +1,47 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
|
||||
@ObjectType()
|
||||
export class Community {
|
||||
constructor(json?: any) {
|
||||
if (json) {
|
||||
this.id = Number(json.id)
|
||||
this.name = json.name
|
||||
this.url = json.url
|
||||
this.description = json.description
|
||||
this.registerUrl = json.registerUrl
|
||||
}
|
||||
constructor(dbCom: DbCommunity) {
|
||||
this.id = dbCom.id
|
||||
this.foreign = dbCom.foreign
|
||||
this.publicKey = dbCom.publicKey.toString()
|
||||
this.url =
|
||||
(dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/') +
|
||||
'api/' +
|
||||
dbCom.apiVersion
|
||||
this.lastAnnouncedAt = dbCom.lastAnnouncedAt
|
||||
this.verifiedAt = dbCom.verifiedAt
|
||||
this.lastErrorAt = dbCom.lastErrorAt
|
||||
this.createdAt = dbCom.createdAt
|
||||
this.updatedAt = dbCom.updatedAt
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => Boolean)
|
||||
foreign: boolean
|
||||
|
||||
@Field(() => String)
|
||||
name: string
|
||||
publicKey: string
|
||||
|
||||
@Field(() => String)
|
||||
url: string
|
||||
|
||||
@Field(() => String)
|
||||
description: string
|
||||
@Field(() => Date, { nullable: true })
|
||||
lastAnnouncedAt: Date | null
|
||||
|
||||
@Field(() => String)
|
||||
registerUrl: string
|
||||
@Field(() => Date, { nullable: true })
|
||||
verifiedAt: Date | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
lastErrorAt: Date | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
createdAt: Date | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
updatedAt: Date | null
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
@ObjectType()
|
||||
export class DynamicStatisticsFields {
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
activeUsers: number
|
||||
|
||||
@Field(() => Decimal)
|
||||
@ -15,13 +15,13 @@ export class DynamicStatisticsFields {
|
||||
|
||||
@ObjectType()
|
||||
export class CommunityStatistics {
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
allUsers: number
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
totalUsers: number
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
deletedUsers: number
|
||||
|
||||
@Field(() => Decimal)
|
||||
|
||||
@ -12,7 +12,6 @@ export class Contribution {
|
||||
this.amount = contribution.amount
|
||||
this.memo = contribution.memo
|
||||
this.createdAt = contribution.createdAt
|
||||
this.deletedAt = contribution.deletedAt
|
||||
this.confirmedAt = contribution.confirmedAt
|
||||
this.confirmedBy = contribution.confirmedBy
|
||||
this.contributionDate = contribution.contributionDate
|
||||
@ -20,9 +19,11 @@ export class Contribution {
|
||||
this.messagesCount = contribution.messages ? contribution.messages.length : 0
|
||||
this.deniedAt = contribution.deniedAt
|
||||
this.deniedBy = contribution.deniedBy
|
||||
this.deletedAt = contribution.deletedAt
|
||||
this.deletedBy = contribution.deletedBy
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@ -40,25 +41,28 @@ export class Contribution {
|
||||
@Field(() => Date)
|
||||
createdAt: Date
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
deletedAt: Date | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
confirmedAt: Date | null
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@Field(() => Int, { nullable: true })
|
||||
confirmedBy: number | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
deniedAt: Date | null
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@Field(() => Int, { nullable: true })
|
||||
deniedBy: number | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
deletedAt: Date | null
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
deletedBy: number | null
|
||||
|
||||
@Field(() => Date)
|
||||
contributionDate: Date
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
messagesCount: number
|
||||
|
||||
@Field(() => String)
|
||||
|
||||
@ -21,7 +21,7 @@ export class ContributionLink {
|
||||
this.link = CONFIG.COMMUNITY_REDEEM_CONTRIBUTION_URL.replace(/{code}/g, this.code)
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => Decimal)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
import { ContributionLink } from '@model/ContributionLink'
|
||||
|
||||
@ObjectType()
|
||||
@ -6,6 +6,6 @@ export class ContributionLinkList {
|
||||
@Field(() => [ContributionLink])
|
||||
links: ContributionLink[]
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
count: number
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Field, ObjectType } from 'type-graphql'
|
||||
import { Field, Int, ObjectType } from 'type-graphql'
|
||||
import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage'
|
||||
import { User } from '@entity/User'
|
||||
|
||||
@ -16,7 +16,7 @@ export class ContributionMessage {
|
||||
this.isModerator = contributionMessage.isModerator
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => String)
|
||||
@ -26,7 +26,7 @@ export class ContributionMessage {
|
||||
createdAt: Date
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
updatedAt?: Date | null
|
||||
updatedAt: Date | null
|
||||
|
||||
@Field(() => String)
|
||||
type: string
|
||||
@ -37,7 +37,7 @@ export class ContributionMessage {
|
||||
@Field(() => String, { nullable: true })
|
||||
userLastName: string | null
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@Field(() => Int, { nullable: true })
|
||||
userId: number | null
|
||||
|
||||
@Field(() => Boolean)
|
||||
@ -45,7 +45,7 @@ export class ContributionMessage {
|
||||
}
|
||||
@ObjectType()
|
||||
export class ContributionMessageListResult {
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
count: number
|
||||
|
||||
@Field(() => [ContributionMessage])
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { ObjectType, Field, Float, Int } from 'type-graphql'
|
||||
import { GdtEntryType } from '@enum/GdtEntryType'
|
||||
|
||||
@ObjectType()
|
||||
@ -19,10 +21,10 @@ export class GdtEntry {
|
||||
this.gdt = json.gdt
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Float)
|
||||
amount: number
|
||||
|
||||
@Field(() => String)
|
||||
@ -40,15 +42,15 @@ export class GdtEntry {
|
||||
@Field(() => GdtEntryType)
|
||||
gdtEntryType: GdtEntryType
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Float)
|
||||
factor: number
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Float)
|
||||
amount2: number
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Float)
|
||||
factor2: number
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Float)
|
||||
gdt: number
|
||||
}
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { GdtEntry } from './GdtEntry'
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { ObjectType, Field, Int, Float } from 'type-graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class GdtEntryList {
|
||||
@ -16,15 +19,15 @@ export class GdtEntryList {
|
||||
@Field(() => String)
|
||||
state: string
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
count: number
|
||||
|
||||
@Field(() => [GdtEntry], { nullable: true })
|
||||
gdtEntries?: GdtEntry[]
|
||||
gdtEntries: GdtEntry[] | null
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Float)
|
||||
gdtSum: number
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Float)
|
||||
timeUsed: number
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
import { Decay } from './Decay'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
import Decimal from 'decimal.js-light'
|
||||
@ -41,19 +41,19 @@ export class Transaction {
|
||||
this.memo = transaction.memo
|
||||
this.creationDate = transaction.creationDate
|
||||
this.linkedUser = linkedUser
|
||||
this.linkedTransactionId = transaction.linkedTransactionId
|
||||
this.linkedTransactionId = transaction.linkedTransactionId || null
|
||||
this.linkId = transaction.contribution
|
||||
? transaction.contribution.contributionLinkId
|
||||
: transaction.transactionLinkId
|
||||
: transaction.transactionLinkId || null
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => User)
|
||||
user: User
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@Field(() => Int, { nullable: true })
|
||||
previous: number | null
|
||||
|
||||
@Field(() => TransactionTypeId)
|
||||
@ -80,10 +80,10 @@ export class Transaction {
|
||||
@Field(() => User, { nullable: true })
|
||||
linkedUser: User | null
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
linkedTransactionId?: number | null
|
||||
@Field(() => Int, { nullable: true })
|
||||
linkedTransactionId: number | null
|
||||
|
||||
// Links to the TransactionLink/ContributionLink when transaction was created by a link
|
||||
@Field(() => Number, { nullable: true })
|
||||
linkId?: number | null
|
||||
@Field(() => Int, { nullable: true })
|
||||
linkId: number | null
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ export class TransactionLink {
|
||||
this.link = CONFIG.COMMUNITY_REDEEM_URL.replace(/{code}/g, this.code)
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => User)
|
||||
@ -61,8 +61,8 @@ export class TransactionLink {
|
||||
@ObjectType()
|
||||
export class TransactionLinkResult {
|
||||
@Field(() => Int)
|
||||
linkCount: number
|
||||
count: number
|
||||
|
||||
@Field(() => [TransactionLink])
|
||||
linkList: TransactionLink[]
|
||||
links: TransactionLink[]
|
||||
}
|
||||
|
||||
@ -24,12 +24,12 @@ export class UnconfirmedContribution {
|
||||
firstName: string
|
||||
|
||||
@Field(() => Int)
|
||||
id?: number
|
||||
id: number
|
||||
|
||||
@Field(() => String)
|
||||
lastName: string
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
userId: number
|
||||
|
||||
@Field(() => String)
|
||||
@ -44,7 +44,7 @@ export class UnconfirmedContribution {
|
||||
@Field(() => Decimal)
|
||||
amount: Decimal
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@Field(() => Int, { nullable: true })
|
||||
moderator: number | null
|
||||
|
||||
@Field(() => [Decimal])
|
||||
@ -53,6 +53,6 @@ export class UnconfirmedContribution {
|
||||
@Field(() => String)
|
||||
state: string
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
messageCount: number
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
import { KlickTipp } from './KlickTipp'
|
||||
import { User as dbUser } from '@entity/User'
|
||||
import { UserContact } from './UserContact'
|
||||
@ -28,21 +28,21 @@ export class User {
|
||||
this.hideAmountGDT = user.hideAmountGDT
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => String)
|
||||
gradidoID: string
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
alias?: string
|
||||
alias: string | null
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@Field(() => Int, { nullable: true })
|
||||
emailId: number | null
|
||||
|
||||
// TODO privacy issue here
|
||||
@Field(() => String, { nullable: true })
|
||||
email: string
|
||||
email: string | null
|
||||
|
||||
@Field(() => UserContact)
|
||||
emailContact: UserContact
|
||||
@ -72,7 +72,7 @@ export class User {
|
||||
hideAmountGDT: boolean
|
||||
|
||||
// This is not the users publisherId, but the one of the users who recommend him
|
||||
@Field(() => Number, { nullable: true })
|
||||
@Field(() => Int, { nullable: true })
|
||||
publisherId: number | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
|
||||
@ -17,7 +17,7 @@ export class UserAdmin {
|
||||
this.isAdmin = user.isAdmin
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
userId: number
|
||||
|
||||
@Field(() => String)
|
||||
@ -39,10 +39,10 @@ export class UserAdmin {
|
||||
hasElopage: boolean
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
deletedAt?: Date | null
|
||||
deletedAt: Date | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
emailConfirmationSend?: string
|
||||
emailConfirmationSend: string | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
isAdmin: Date | null
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
import { UserContact as dbUserContact } from '@entity/UserContact'
|
||||
|
||||
@ObjectType()
|
||||
@ -18,13 +18,13 @@ export class UserContact {
|
||||
this.deletedAt = userContact.deletedAt
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
id: number
|
||||
|
||||
@Field(() => String)
|
||||
type: string
|
||||
|
||||
@Field(() => Number)
|
||||
@Field(() => Int)
|
||||
userId: number
|
||||
|
||||
@Field(() => String)
|
||||
@ -33,10 +33,10 @@ export class UserContact {
|
||||
// @Field(() => BigInt, { nullable: true })
|
||||
// emailVerificationCode: BigInt | null
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@Field(() => Int, { nullable: true })
|
||||
emailOptInTypeId: number | null
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@Field(() => Int, { nullable: true })
|
||||
emailResendCount: number | null
|
||||
|
||||
@Field(() => Boolean)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { Resolver, Query, Ctx, Authorized } from 'type-graphql'
|
||||
import { getCustomRepository } from '@dbTools/typeorm'
|
||||
|
||||
@ -1,21 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
import createServer from '@/server/createServer'
|
||||
import CONFIG from '@/config'
|
||||
|
||||
jest.mock('@/config')
|
||||
import { getCommunities } from '@/seeds/graphql/queries'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { testEnvironment } from '@test/helpers'
|
||||
|
||||
let query: any
|
||||
|
||||
// to do: We need a setup for the tests that closes the connection
|
||||
let con: any
|
||||
let testEnv: any
|
||||
|
||||
beforeAll(async () => {
|
||||
const server = await createServer({})
|
||||
con = server.con
|
||||
query = createTestClient(server.apollo).query
|
||||
testEnv = await testEnvironment()
|
||||
query = testEnv.query
|
||||
con = testEnv.con
|
||||
await DbCommunity.clear()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@ -23,74 +27,90 @@ afterAll(async () => {
|
||||
})
|
||||
|
||||
describe('CommunityResolver', () => {
|
||||
const getCommunityInfoQuery = `
|
||||
query {
|
||||
getCommunityInfo {
|
||||
name
|
||||
description
|
||||
url
|
||||
registerUrl
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const communities = `
|
||||
query {
|
||||
communities {
|
||||
id
|
||||
name
|
||||
url
|
||||
description
|
||||
registerUrl
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
describe('getCommunityInfo', () => {
|
||||
it('returns the default values', async () => {
|
||||
await expect(query({ query: getCommunityInfoQuery })).resolves.toMatchObject({
|
||||
data: {
|
||||
getCommunityInfo: {
|
||||
name: 'Gradido Entwicklung',
|
||||
description: 'Die lokale Entwicklungsumgebung von Gradido.',
|
||||
url: 'http://localhost/',
|
||||
registerUrl: 'http://localhost/register',
|
||||
describe('getCommunities', () => {
|
||||
let homeCom1: DbCommunity
|
||||
let homeCom2: DbCommunity
|
||||
let homeCom3: DbCommunity
|
||||
let foreignCom1: DbCommunity
|
||||
let foreignCom2: DbCommunity
|
||||
let foreignCom3: DbCommunity
|
||||
describe('with empty list', () => {
|
||||
it('returns no community entry', async () => {
|
||||
// const result: Community[] = await query({ query: getCommunities })
|
||||
// expect(result.length).toEqual(0)
|
||||
await expect(query({ query: getCommunities })).resolves.toMatchObject({
|
||||
data: {
|
||||
getCommunities: [],
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('communities', () => {
|
||||
describe('PRODUCTION = false', () => {
|
||||
beforeEach(() => {
|
||||
CONFIG.PRODUCTION = false
|
||||
describe('only home-communities entries', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
homeCom1 = DbCommunity.create()
|
||||
homeCom1.foreign = false
|
||||
homeCom1.publicKey = Buffer.from('publicKey-HomeCommunity')
|
||||
homeCom1.apiVersion = '1_0'
|
||||
homeCom1.endPoint = 'http://localhost'
|
||||
homeCom1.createdAt = new Date()
|
||||
await DbCommunity.insert(homeCom1)
|
||||
|
||||
homeCom2 = DbCommunity.create()
|
||||
homeCom2.foreign = false
|
||||
homeCom2.publicKey = Buffer.from('publicKey-HomeCommunity')
|
||||
homeCom2.apiVersion = '1_1'
|
||||
homeCom2.endPoint = 'http://localhost'
|
||||
homeCom2.createdAt = new Date()
|
||||
await DbCommunity.insert(homeCom2)
|
||||
|
||||
homeCom3 = DbCommunity.create()
|
||||
homeCom3.foreign = false
|
||||
homeCom3.publicKey = Buffer.from('publicKey-HomeCommunity')
|
||||
homeCom3.apiVersion = '2_0'
|
||||
homeCom3.endPoint = 'http://localhost'
|
||||
homeCom3.createdAt = new Date()
|
||||
await DbCommunity.insert(homeCom3)
|
||||
})
|
||||
|
||||
it('returns three communities', async () => {
|
||||
await expect(query({ query: communities })).resolves.toMatchObject({
|
||||
it('returns three home-community entries', async () => {
|
||||
await expect(query({ query: getCommunities })).resolves.toMatchObject({
|
||||
data: {
|
||||
communities: [
|
||||
getCommunities: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Gradido Entwicklung',
|
||||
description: 'Die lokale Entwicklungsumgebung von Gradido.',
|
||||
url: 'http://localhost/',
|
||||
registerUrl: 'http://localhost/register-community',
|
||||
foreign: homeCom1.foreign,
|
||||
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
|
||||
url: expect.stringMatching('http://localhost/api/1_0'),
|
||||
lastAnnouncedAt: null,
|
||||
verifiedAt: null,
|
||||
lastErrorAt: null,
|
||||
createdAt: homeCom1.createdAt.toISOString(),
|
||||
updatedAt: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Gradido Staging',
|
||||
description: 'Der Testserver der Gradido-Akademie.',
|
||||
url: 'https://stage1.gradido.net/',
|
||||
registerUrl: 'https://stage1.gradido.net/register-community',
|
||||
foreign: homeCom2.foreign,
|
||||
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
|
||||
url: expect.stringMatching('http://localhost/api/1_1'),
|
||||
lastAnnouncedAt: null,
|
||||
verifiedAt: null,
|
||||
lastErrorAt: null,
|
||||
createdAt: homeCom2.createdAt.toISOString(),
|
||||
updatedAt: null,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Gradido-Akademie',
|
||||
description: 'Freies Institut für Wirtschaftsbionik.',
|
||||
url: 'https://gradido.net',
|
||||
registerUrl: 'https://gdd1.gradido.com/register-community',
|
||||
foreign: homeCom3.foreign,
|
||||
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
|
||||
url: expect.stringMatching('http://localhost/api/2_0'),
|
||||
lastAnnouncedAt: null,
|
||||
verifiedAt: null,
|
||||
lastErrorAt: null,
|
||||
createdAt: homeCom3.createdAt.toISOString(),
|
||||
updatedAt: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -98,21 +118,104 @@ describe('CommunityResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('PRODUCTION = true', () => {
|
||||
beforeEach(() => {
|
||||
CONFIG.PRODUCTION = true
|
||||
describe('plus foreign-communities entries', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
foreignCom1 = DbCommunity.create()
|
||||
foreignCom1.foreign = true
|
||||
foreignCom1.publicKey = Buffer.from('publicKey-ForeignCommunity')
|
||||
foreignCom1.apiVersion = '1_0'
|
||||
foreignCom1.endPoint = 'http://remotehost'
|
||||
foreignCom1.createdAt = new Date()
|
||||
await DbCommunity.insert(foreignCom1)
|
||||
|
||||
foreignCom2 = DbCommunity.create()
|
||||
foreignCom2.foreign = true
|
||||
foreignCom2.publicKey = Buffer.from('publicKey-ForeignCommunity')
|
||||
foreignCom2.apiVersion = '1_1'
|
||||
foreignCom2.endPoint = 'http://remotehost'
|
||||
foreignCom2.createdAt = new Date()
|
||||
await DbCommunity.insert(foreignCom2)
|
||||
|
||||
foreignCom3 = DbCommunity.create()
|
||||
foreignCom3.foreign = true
|
||||
foreignCom3.publicKey = Buffer.from('publicKey-ForeignCommunity')
|
||||
foreignCom3.apiVersion = '1_2'
|
||||
foreignCom3.endPoint = 'http://remotehost'
|
||||
foreignCom3.createdAt = new Date()
|
||||
await DbCommunity.insert(foreignCom3)
|
||||
})
|
||||
|
||||
it('returns one community', async () => {
|
||||
await expect(query({ query: communities })).resolves.toMatchObject({
|
||||
it('returns 3x home and 3x foreign-community entries', async () => {
|
||||
await expect(query({ query: getCommunities })).resolves.toMatchObject({
|
||||
data: {
|
||||
communities: [
|
||||
getCommunities: [
|
||||
{
|
||||
id: 1,
|
||||
foreign: homeCom1.foreign,
|
||||
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
|
||||
url: expect.stringMatching('http://localhost/api/1_0'),
|
||||
lastAnnouncedAt: null,
|
||||
verifiedAt: null,
|
||||
lastErrorAt: null,
|
||||
createdAt: homeCom1.createdAt.toISOString(),
|
||||
updatedAt: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
foreign: homeCom2.foreign,
|
||||
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
|
||||
url: expect.stringMatching('http://localhost/api/1_1'),
|
||||
lastAnnouncedAt: null,
|
||||
verifiedAt: null,
|
||||
lastErrorAt: null,
|
||||
createdAt: homeCom2.createdAt.toISOString(),
|
||||
updatedAt: null,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Gradido-Akademie',
|
||||
description: 'Freies Institut für Wirtschaftsbionik.',
|
||||
url: 'https://gradido.net',
|
||||
registerUrl: 'https://gdd1.gradido.com/register-community',
|
||||
foreign: homeCom3.foreign,
|
||||
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
|
||||
url: expect.stringMatching('http://localhost/api/2_0'),
|
||||
lastAnnouncedAt: null,
|
||||
verifiedAt: null,
|
||||
lastErrorAt: null,
|
||||
createdAt: homeCom3.createdAt.toISOString(),
|
||||
updatedAt: null,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
foreign: foreignCom1.foreign,
|
||||
publicKey: expect.stringMatching('publicKey-ForeignCommunity'),
|
||||
url: expect.stringMatching('http://remotehost/api/1_0'),
|
||||
lastAnnouncedAt: null,
|
||||
verifiedAt: null,
|
||||
lastErrorAt: null,
|
||||
createdAt: foreignCom1.createdAt.toISOString(),
|
||||
updatedAt: null,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
foreign: foreignCom2.foreign,
|
||||
publicKey: expect.stringMatching('publicKey-ForeignCommunity'),
|
||||
url: expect.stringMatching('http://remotehost/api/1_1'),
|
||||
lastAnnouncedAt: null,
|
||||
verifiedAt: null,
|
||||
lastErrorAt: null,
|
||||
createdAt: foreignCom2.createdAt.toISOString(),
|
||||
updatedAt: null,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
foreign: foreignCom3.foreign,
|
||||
publicKey: expect.stringMatching('publicKey-ForeignCommunity'),
|
||||
url: expect.stringMatching('http://remotehost/api/1_2'),
|
||||
lastAnnouncedAt: null,
|
||||
verifiedAt: null,
|
||||
lastErrorAt: null,
|
||||
createdAt: foreignCom3.createdAt.toISOString(),
|
||||
updatedAt: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -1,58 +1,18 @@
|
||||
import { Resolver, Query, Authorized } from 'type-graphql'
|
||||
|
||||
import { Community } from '@model/Community'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import CONFIG from '@/config'
|
||||
|
||||
@Resolver()
|
||||
export class CommunityResolver {
|
||||
@Authorized([RIGHTS.GET_COMMUNITY_INFO])
|
||||
@Query(() => Community)
|
||||
async getCommunityInfo(): Promise<Community> {
|
||||
return new Community({
|
||||
name: CONFIG.COMMUNITY_NAME,
|
||||
description: CONFIG.COMMUNITY_DESCRIPTION,
|
||||
url: CONFIG.COMMUNITY_URL,
|
||||
registerUrl: CONFIG.COMMUNITY_REGISTER_URL,
|
||||
})
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.COMMUNITIES])
|
||||
@Query(() => [Community])
|
||||
async communities(): Promise<Community[]> {
|
||||
if (CONFIG.PRODUCTION)
|
||||
return [
|
||||
new Community({
|
||||
id: 3,
|
||||
name: 'Gradido-Akademie',
|
||||
description: 'Freies Institut für Wirtschaftsbionik.',
|
||||
url: 'https://gradido.net',
|
||||
registerUrl: 'https://gdd1.gradido.com/register-community',
|
||||
}),
|
||||
]
|
||||
return [
|
||||
new Community({
|
||||
id: 1,
|
||||
name: 'Gradido Entwicklung',
|
||||
description: 'Die lokale Entwicklungsumgebung von Gradido.',
|
||||
url: 'http://localhost/',
|
||||
registerUrl: 'http://localhost/register-community',
|
||||
}),
|
||||
new Community({
|
||||
id: 2,
|
||||
name: 'Gradido Staging',
|
||||
description: 'Der Testserver der Gradido-Akademie.',
|
||||
url: 'https://stage1.gradido.net/',
|
||||
registerUrl: 'https://stage1.gradido.net/register-community',
|
||||
}),
|
||||
new Community({
|
||||
id: 3,
|
||||
name: 'Gradido-Akademie',
|
||||
description: 'Freies Institut für Wirtschaftsbionik.',
|
||||
url: 'https://gradido.net',
|
||||
registerUrl: 'https://gdd1.gradido.com/register-community',
|
||||
}),
|
||||
]
|
||||
async getCommunities(): Promise<Community[]> {
|
||||
const dbCommunities: DbCommunity[] = await DbCommunity.find({
|
||||
order: { foreign: 'ASC', publicKey: 'ASC', apiVersion: 'ASC' },
|
||||
})
|
||||
return dbCommunities.map((dbCom: DbCommunity) => new Community(dbCom))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
@ -35,7 +35,7 @@ export class ContributionLinkResolver {
|
||||
cycle,
|
||||
validFrom,
|
||||
validTo,
|
||||
maxAmountPerMonth,
|
||||
maxAmountPerMonth = null,
|
||||
maxPerCycle,
|
||||
}: ContributionLinkArgs,
|
||||
): Promise<ContributionLink> {
|
||||
@ -114,7 +114,7 @@ export class ContributionLinkResolver {
|
||||
cycle,
|
||||
validFrom,
|
||||
validTo,
|
||||
maxAmountPerMonth,
|
||||
maxAmountPerMonth = null,
|
||||
maxPerCycle,
|
||||
}: ContributionLinkArgs,
|
||||
@Arg('id', () => Int) id: number,
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
@ -181,7 +186,7 @@ describe('ContributionMessageResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('calls sendAddedContributionMessageEmail', async () => {
|
||||
it('calls sendAddedContributionMessageEmail', () => {
|
||||
expect(sendAddedContributionMessageEmail).toBeCalledWith({
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Arg, Args, Authorized, Ctx, Mutation, Query, Resolver } from 'type-graphql'
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
|
||||
import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage'
|
||||
@ -68,7 +69,7 @@ export class ContributionMessageResolver {
|
||||
@Authorized([RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES])
|
||||
@Query(() => ContributionMessageListResult)
|
||||
async listContributionMessages(
|
||||
@Arg('contributionId') contributionId: number,
|
||||
@Arg('contributionId', () => Int) contributionId: number,
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
): Promise<ContributionMessageListResult> {
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
@ -24,7 +29,11 @@ import {
|
||||
listContributions,
|
||||
adminListAllContributions,
|
||||
} from '@/seeds/graphql/queries'
|
||||
import { sendContributionConfirmedEmail } from '@/emails/sendEmailVariants'
|
||||
import {
|
||||
sendContributionConfirmedEmail,
|
||||
sendContributionDeletedEmail,
|
||||
sendContributionDeniedEmail,
|
||||
} from '@/emails/sendEmailVariants'
|
||||
import {
|
||||
cleanDB,
|
||||
resetToken,
|
||||
@ -37,7 +46,7 @@ import { userFactory } from '@/seeds/factory/user'
|
||||
import { creationFactory } from '@/seeds/factory/creation'
|
||||
import { creations } from '@/seeds/creation/index'
|
||||
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||
import { EventProtocol } from '@entity/EventProtocol'
|
||||
import { Event as DbEvent } from '@entity/Event'
|
||||
import { Contribution } from '@entity/Contribution'
|
||||
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||
import { User } from '@entity/User'
|
||||
@ -50,21 +59,7 @@ import { ContributionListResult } from '@model/Contribution'
|
||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||
import { Order } from '@enum/Order'
|
||||
|
||||
// mock account activation email to avoid console spam
|
||||
jest.mock('@/emails/sendEmailVariants', () => {
|
||||
const originalModule = jest.requireActual('@/emails/sendEmailVariants')
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
// TODO: test the call of …
|
||||
// sendAccountActivationEmail: jest.fn((a) => originalModule.sendAccountActivationEmail(a)),
|
||||
sendContributionConfirmedEmail: jest.fn((a) =>
|
||||
originalModule.sendContributionConfirmedEmail(a),
|
||||
),
|
||||
// TODO: test the call of …
|
||||
// sendContributionRejectedEmail: jest.fn((a) => originalModule.sendContributionRejectedEmail(a)),
|
||||
}
|
||||
})
|
||||
jest.mock('@/emails/sendEmailVariants')
|
||||
|
||||
let mutate: any, query: any, con: any
|
||||
let testEnv: any
|
||||
@ -186,7 +181,7 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
afterAll(() => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
@ -275,7 +270,7 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
|
||||
describe('valid input', () => {
|
||||
it('creates contribution', async () => {
|
||||
it('creates contribution', () => {
|
||||
expect(pendingContribution.data.createContribution).toMatchObject({
|
||||
id: expect.any(Number),
|
||||
amount: '100',
|
||||
@ -284,12 +279,13 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the CONTRIBUTION_CREATE event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.CONTRIBUTION_CREATE,
|
||||
affectedUserId: bibi.id,
|
||||
actingUserId: bibi.id,
|
||||
involvedContributionId: pendingContribution.data.createContribution.id,
|
||||
amount: expect.decimalEqual(100),
|
||||
contributionId: pendingContribution.data.createContribution.id,
|
||||
userId: bibi.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -321,7 +317,7 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
afterAll(() => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
@ -462,7 +458,7 @@ describe('ContributionResolver', () => {
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
})
|
||||
contribution.contributionStatus = ContributionStatus.DELETED
|
||||
contribution.save()
|
||||
await contribution.save()
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
@ -474,7 +470,7 @@ describe('ContributionResolver', () => {
|
||||
id: pendingContribution.data.createContribution.id,
|
||||
})
|
||||
contribution.contributionStatus = ContributionStatus.PENDING
|
||||
contribution.save()
|
||||
await contribution.save()
|
||||
})
|
||||
|
||||
it('throws an error', async () => {
|
||||
@ -589,12 +585,13 @@ describe('ContributionResolver', () => {
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
})
|
||||
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.CONTRIBUTION_UPDATE,
|
||||
affectedUserId: bibi.id,
|
||||
actingUserId: bibi.id,
|
||||
involvedContributionId: pendingContribution.data.createContribution.id,
|
||||
amount: expect.decimalEqual(10),
|
||||
contributionId: pendingContribution.data.createContribution.id,
|
||||
userId: bibi.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -646,7 +643,7 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
afterAll(() => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
@ -819,16 +816,28 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the ADMIN_CONTRIBUTION_DENY event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.ADMIN_CONTRIBUTION_DENY,
|
||||
userId: bibi.id,
|
||||
xUserId: admin.id,
|
||||
contributionId: contributionToDeny.data.createContribution.id,
|
||||
affectedUserId: bibi.id,
|
||||
actingUserId: admin.id,
|
||||
involvedContributionId: contributionToDeny.data.createContribution.id,
|
||||
amount: expect.decimalEqual(100),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('calls sendContributionDeniedEmail', () => {
|
||||
expect(sendContributionDeniedEmail).toBeCalledWith({
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
email: 'bibi@bloxberg.de',
|
||||
language: 'de',
|
||||
senderFirstName: 'Peter',
|
||||
senderLastName: 'Lustig',
|
||||
contributionMemo: 'Test contribution to deny',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -854,7 +863,7 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
afterAll(() => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
@ -935,12 +944,13 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the CONTRIBUTION_DELETE event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.CONTRIBUTION_DELETE,
|
||||
contributionId: contributionToDelete.data.createContribution.id,
|
||||
affectedUserId: bibi.id,
|
||||
actingUserId: bibi.id,
|
||||
involvedContributionId: contributionToDelete.data.createContribution.id,
|
||||
amount: expect.decimalEqual(100),
|
||||
userId: bibi.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -993,7 +1003,6 @@ describe('ContributionResolver', () => {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filterConfirmed: false,
|
||||
},
|
||||
})
|
||||
expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')])
|
||||
@ -1008,11 +1017,11 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
afterAll(() => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
describe('filter confirmed is false', () => {
|
||||
describe('no status filter', () => {
|
||||
it('returns creations', async () => {
|
||||
const {
|
||||
data: { listContributions: contributionListResult },
|
||||
@ -1064,7 +1073,7 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('filter confirmed is true', () => {
|
||||
describe('with status filter [PENDING, IN_PROGRESS, DENIED, DELETED]', () => {
|
||||
it('returns only unconfirmed creations', async () => {
|
||||
const {
|
||||
data: { listContributions: contributionListResult },
|
||||
@ -1074,7 +1083,7 @@ describe('ContributionResolver', () => {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filterConfirmed: true,
|
||||
statusFilter: ['PENDING', 'IN_PROGRESS', 'DENIED', 'DELETED'],
|
||||
},
|
||||
})
|
||||
expect(contributionListResult).toMatchObject({
|
||||
@ -1139,7 +1148,7 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
afterAll(() => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
@ -1719,7 +1728,7 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
afterAll(() => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
@ -1797,7 +1806,7 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
afterAll(() => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
@ -1912,7 +1921,7 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
|
||||
describe('valid user to create for', () => {
|
||||
beforeAll(async () => {
|
||||
beforeAll(() => {
|
||||
variables.email = 'bibi@bloxberg.de'
|
||||
variables.creationDate = 'invalid-date'
|
||||
})
|
||||
@ -2018,17 +2027,18 @@ describe('ContributionResolver', () => {
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
adminCreateContribution: [1000, 1000, 590],
|
||||
adminCreateContribution: ['1000', '1000', '590'],
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('stores the ADMIN_CONTRIBUTION_CREATE event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.ADMIN_CONTRIBUTION_CREATE,
|
||||
userId: admin.id,
|
||||
affectedUserId: bibi.id,
|
||||
actingUserId: admin.id,
|
||||
amount: expect.decimalEqual(200),
|
||||
}),
|
||||
)
|
||||
@ -2226,7 +2236,7 @@ describe('ContributionResolver', () => {
|
||||
mutate({
|
||||
mutation: adminUpdateContribution,
|
||||
variables: {
|
||||
id: creation ? creation.id : -1,
|
||||
id: creation?.id,
|
||||
email: 'peter@lustig.de',
|
||||
amount: new Decimal(300),
|
||||
memo: 'Danke Peter!',
|
||||
@ -2250,10 +2260,11 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the ADMIN_CONTRIBUTION_UPDATE event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE,
|
||||
userId: admin.id,
|
||||
affectedUserId: creation?.userId,
|
||||
actingUserId: admin.id,
|
||||
amount: 300,
|
||||
}),
|
||||
)
|
||||
@ -2267,7 +2278,7 @@ describe('ContributionResolver', () => {
|
||||
mutate({
|
||||
mutation: adminUpdateContribution,
|
||||
variables: {
|
||||
id: creation ? creation.id : -1,
|
||||
id: creation?.id,
|
||||
email: 'peter@lustig.de',
|
||||
amount: new Decimal(200),
|
||||
memo: 'Das war leider zu Viel!',
|
||||
@ -2291,10 +2302,11 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the ADMIN_CONTRIBUTION_UPDATE event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE,
|
||||
userId: admin.id,
|
||||
affectedUserId: creation?.userId,
|
||||
actingUserId: admin.id,
|
||||
amount: expect.decimalEqual(200),
|
||||
}),
|
||||
)
|
||||
@ -2365,7 +2377,7 @@ describe('ContributionResolver', () => {
|
||||
mutate({
|
||||
mutation: adminDeleteContribution,
|
||||
variables: {
|
||||
id: creation ? creation.id : -1,
|
||||
id: creation?.id,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
@ -2376,14 +2388,28 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the ADMIN_CONTRIBUTION_DELETE event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.ADMIN_CONTRIBUTION_DELETE,
|
||||
userId: admin.id,
|
||||
affectedUserId: creation?.userId,
|
||||
actingUserId: admin.id,
|
||||
involvedContributionId: creation?.id,
|
||||
amount: expect.decimalEqual(200),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('calls sendContributionDeletedEmail', () => {
|
||||
expect(sendContributionDeletedEmail).toBeCalledWith({
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
email: 'peter@lustig.de',
|
||||
language: 'de',
|
||||
senderFirstName: 'Peter',
|
||||
senderLastName: 'Lustig',
|
||||
contributionMemo: 'Das war leider zu Viel!',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('creation already confirmed', () => {
|
||||
@ -2520,7 +2546,7 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the CONTRIBUTION_CONFIRM event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.CONTRIBUTION_CONFIRM,
|
||||
}),
|
||||
@ -2538,7 +2564,7 @@ describe('ContributionResolver', () => {
|
||||
expect(transaction[0].typeId).toEqual(1)
|
||||
})
|
||||
|
||||
it('calls sendContributionConfirmedEmail', async () => {
|
||||
it('calls sendContributionConfirmedEmail', () => {
|
||||
expect(sendContributionConfirmedEmail).toBeCalledWith({
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
@ -2552,7 +2578,7 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the SEND_CONFIRMATION_EMAIL event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.SEND_CONFIRMATION_EMAIL,
|
||||
}),
|
||||
@ -2732,15 +2758,6 @@ describe('ContributionResolver', () => {
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
firstName: 'Bob',
|
||||
id: expect.any(Number),
|
||||
lastName: 'der Baumeister',
|
||||
memo: 'Confirmed Contribution',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(400),
|
||||
firstName: 'Peter',
|
||||
@ -2750,6 +2767,15 @@ describe('ContributionResolver', () => {
|
||||
messagesCount: 0,
|
||||
state: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
firstName: 'Bob',
|
||||
id: expect.any(Number),
|
||||
lastName: 'der Baumeister',
|
||||
memo: 'Confirmed Contribution',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
firstName: 'Peter',
|
||||
@ -2768,15 +2794,6 @@ describe('ContributionResolver', () => {
|
||||
messagesCount: 0,
|
||||
state: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(10),
|
||||
firstName: 'Bibi',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test PENDING contribution update',
|
||||
messagesCount: 0,
|
||||
state: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(200),
|
||||
firstName: 'Peter',
|
||||
@ -2786,15 +2803,6 @@ describe('ContributionResolver', () => {
|
||||
messagesCount: 0,
|
||||
state: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(166),
|
||||
firstName: 'Räuber',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Hotzenplotz',
|
||||
memo: 'Whatever contribution',
|
||||
messagesCount: 0,
|
||||
state: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(166),
|
||||
firstName: 'Räuber',
|
||||
@ -2804,6 +2812,15 @@ describe('ContributionResolver', () => {
|
||||
messagesCount: 0,
|
||||
state: 'DENIED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(166),
|
||||
firstName: 'Räuber',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Hotzenplotz',
|
||||
memo: 'Whatever contribution',
|
||||
messagesCount: 0,
|
||||
state: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(166),
|
||||
firstName: 'Räuber',
|
||||
@ -2818,18 +2835,9 @@ describe('ContributionResolver', () => {
|
||||
firstName: 'Bibi',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test IN_PROGRESS contribution',
|
||||
memo: 'Test contribution to delete',
|
||||
messagesCount: 0,
|
||||
state: 'IN_PROGRESS',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
firstName: 'Bibi',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test contribution to confirm',
|
||||
messagesCount: 0,
|
||||
state: 'CONFIRMED',
|
||||
state: 'DELETED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
@ -2845,9 +2853,27 @@ describe('ContributionResolver', () => {
|
||||
firstName: 'Bibi',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test contribution to delete',
|
||||
memo: 'Test contribution to confirm',
|
||||
messagesCount: 0,
|
||||
state: 'DELETED',
|
||||
state: 'CONFIRMED',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(100),
|
||||
firstName: 'Bibi',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test IN_PROGRESS contribution',
|
||||
messagesCount: 1,
|
||||
state: 'IN_PROGRESS',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(10),
|
||||
firstName: 'Bibi',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Test PENDING contribution update',
|
||||
messagesCount: 1,
|
||||
state: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: expect.decimalEqual(1000),
|
||||
@ -2888,11 +2914,11 @@ describe('ContributionResolver', () => {
|
||||
state: 'PENDING',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: '200',
|
||||
firstName: 'Bibi',
|
||||
amount: '100',
|
||||
firstName: 'Peter',
|
||||
id: expect.any(Number),
|
||||
lastName: 'Bloxberg',
|
||||
memo: 'Aktives Grundeinkommen',
|
||||
lastName: 'Lustig',
|
||||
memo: 'Test env contribution',
|
||||
messagesCount: 0,
|
||||
state: 'PENDING',
|
||||
}),
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'
|
||||
import { FindOperator, IsNull, getConnection } from '@dbTools/typeorm'
|
||||
import { IsNull, getConnection } from '@dbTools/typeorm'
|
||||
|
||||
import { Contribution as DbContribution } from '@entity/Contribution'
|
||||
import { ContributionMessage } from '@entity/ContributionMessage'
|
||||
@ -27,11 +28,11 @@ import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import { Context, getUser, getClientTimezoneOffset } from '@/server/context'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import {
|
||||
getCreationDates,
|
||||
getUserCreation,
|
||||
validateContribution,
|
||||
updateCreations,
|
||||
isValidDateString,
|
||||
getOpenCreations,
|
||||
} from './util/creations'
|
||||
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
|
||||
import {
|
||||
@ -90,7 +91,7 @@ export class ContributionResolver {
|
||||
logger.trace('contribution to save', contribution)
|
||||
await DbContribution.save(contribution)
|
||||
|
||||
await EVENT_CONTRIBUTION_CREATE(user.id, contribution.id, amount)
|
||||
await EVENT_CONTRIBUTION_CREATE(user, contribution, amount)
|
||||
|
||||
return new UnconfirmedContribution(contribution, user, creations)
|
||||
}
|
||||
@ -118,7 +119,7 @@ export class ContributionResolver {
|
||||
contribution.deletedAt = new Date()
|
||||
await contribution.save()
|
||||
|
||||
await EVENT_CONTRIBUTION_DELETE(user.id, contribution.id, contribution.amount)
|
||||
await EVENT_CONTRIBUTION_DELETE(user, contribution, contribution.amount)
|
||||
|
||||
const res = await contribution.softRemove()
|
||||
return !!res
|
||||
@ -127,35 +128,26 @@ export class ContributionResolver {
|
||||
@Authorized([RIGHTS.LIST_CONTRIBUTIONS])
|
||||
@Query(() => ContributionListResult)
|
||||
async listContributions(
|
||||
@Ctx() context: Context,
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
@Arg('filterConfirmed', () => Boolean)
|
||||
filterConfirmed: boolean | null,
|
||||
@Ctx() context: Context,
|
||||
@Arg('statusFilter', () => [ContributionStatus], { nullable: true })
|
||||
statusFilter?: ContributionStatus[] | null,
|
||||
): Promise<ContributionListResult> {
|
||||
const user = getUser(context)
|
||||
const where: {
|
||||
userId: number
|
||||
confirmedBy?: FindOperator<number> | null
|
||||
} = { userId: user.id }
|
||||
|
||||
if (filterConfirmed) where.confirmedBy = IsNull()
|
||||
|
||||
const [contributions, count] = await getConnection()
|
||||
.createQueryBuilder()
|
||||
.select('c')
|
||||
.from(DbContribution, 'c')
|
||||
.leftJoinAndSelect('c.messages', 'm')
|
||||
.where(where)
|
||||
.withDeleted()
|
||||
.orderBy('c.createdAt', order)
|
||||
.limit(pageSize)
|
||||
.offset((currentPage - 1) * pageSize)
|
||||
.getManyAndCount()
|
||||
|
||||
const [dbContributions, count] = await findContributions(
|
||||
order,
|
||||
currentPage,
|
||||
pageSize,
|
||||
true,
|
||||
['messages'],
|
||||
user.id,
|
||||
statusFilter,
|
||||
)
|
||||
return new ContributionListResult(
|
||||
count,
|
||||
contributions.map((contribution) => new Contribution(contribution, user)),
|
||||
dbContributions.map((contribution) => new Contribution(contribution, user)),
|
||||
)
|
||||
}
|
||||
|
||||
@ -165,13 +157,15 @@ export class ContributionResolver {
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
@Arg('statusFilter', () => [ContributionStatus], { nullable: true })
|
||||
statusFilter?: ContributionStatus[],
|
||||
statusFilter?: ContributionStatus[] | null,
|
||||
): Promise<ContributionListResult> {
|
||||
const [dbContributions, count] = await findContributions(
|
||||
order,
|
||||
currentPage,
|
||||
pageSize,
|
||||
false,
|
||||
['user'],
|
||||
undefined,
|
||||
statusFilter,
|
||||
)
|
||||
|
||||
@ -246,22 +240,22 @@ export class ContributionResolver {
|
||||
contributionMessage.isModerator = false
|
||||
contributionMessage.userId = user.id
|
||||
contributionMessage.type = ContributionMessageType.HISTORY
|
||||
ContributionMessage.save(contributionMessage)
|
||||
await ContributionMessage.save(contributionMessage)
|
||||
|
||||
contributionToUpdate.amount = amount
|
||||
contributionToUpdate.memo = memo
|
||||
contributionToUpdate.contributionDate = new Date(creationDate)
|
||||
contributionToUpdate.contributionStatus = ContributionStatus.PENDING
|
||||
contributionToUpdate.updatedAt = new Date()
|
||||
DbContribution.save(contributionToUpdate)
|
||||
await DbContribution.save(contributionToUpdate)
|
||||
|
||||
await EVENT_CONTRIBUTION_UPDATE(user.id, contributionId, amount)
|
||||
await EVENT_CONTRIBUTION_UPDATE(user, contributionToUpdate, amount)
|
||||
|
||||
return new UnconfirmedContribution(contributionToUpdate, user, creations)
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.ADMIN_CREATE_CONTRIBUTION])
|
||||
@Mutation(() => [Number])
|
||||
@Mutation(() => [Decimal])
|
||||
async adminCreateContribution(
|
||||
@Args() { email, amount, memo, creationDate }: AdminCreateContributionArgs,
|
||||
@Ctx() context: Context,
|
||||
@ -312,7 +306,7 @@ export class ContributionResolver {
|
||||
|
||||
await DbContribution.save(contribution)
|
||||
|
||||
await EVENT_ADMIN_CONTRIBUTION_CREATE(moderator.id, contribution.id, amount)
|
||||
await EVENT_ADMIN_CONTRIBUTION_CREATE(emailContact.user, moderator, contribution, amount)
|
||||
|
||||
return getUserCreation(emailContact.userId, clientTimezoneOffset)
|
||||
}
|
||||
@ -380,7 +374,12 @@ export class ContributionResolver {
|
||||
|
||||
result.creation = await getUserCreation(emailContact.user.id, clientTimezoneOffset)
|
||||
|
||||
await EVENT_ADMIN_CONTRIBUTION_UPDATE(emailContact.user.id, contributionToUpdate.id, amount)
|
||||
await EVENT_ADMIN_CONTRIBUTION_UPDATE(
|
||||
emailContact.user,
|
||||
moderator,
|
||||
contributionToUpdate,
|
||||
amount,
|
||||
)
|
||||
|
||||
return result
|
||||
}
|
||||
@ -391,13 +390,15 @@ export class ContributionResolver {
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 3, order = Order.DESC }: Paginated,
|
||||
@Arg('statusFilter', () => [ContributionStatus], { nullable: true })
|
||||
statusFilter?: ContributionStatus[],
|
||||
statusFilter?: ContributionStatus[] | null,
|
||||
): Promise<ContributionListResult> {
|
||||
const [dbContributions, count] = await findContributions(
|
||||
order,
|
||||
currentPage,
|
||||
pageSize,
|
||||
true,
|
||||
['user', 'messages'],
|
||||
undefined,
|
||||
statusFilter,
|
||||
)
|
||||
|
||||
@ -436,9 +437,14 @@ export class ContributionResolver {
|
||||
await contribution.save()
|
||||
const res = await contribution.softRemove()
|
||||
|
||||
await EVENT_ADMIN_CONTRIBUTION_DELETE(contribution.userId, contribution.id, contribution.amount)
|
||||
await EVENT_ADMIN_CONTRIBUTION_DELETE(
|
||||
{ id: contribution.userId } as DbUser,
|
||||
moderator,
|
||||
contribution,
|
||||
contribution.amount,
|
||||
)
|
||||
|
||||
sendContributionDeletedEmail({
|
||||
void sendContributionDeletedEmail({
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
email: user.emailContact.email,
|
||||
@ -532,7 +538,7 @@ export class ContributionResolver {
|
||||
|
||||
await queryRunner.commitTransaction()
|
||||
logger.info('creation commited successfuly.')
|
||||
sendContributionConfirmedEmail({
|
||||
void sendContributionConfirmedEmail({
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
email: user.emailContact.email,
|
||||
@ -549,7 +555,7 @@ export class ContributionResolver {
|
||||
await queryRunner.release()
|
||||
}
|
||||
|
||||
await EVENT_CONTRIBUTION_CONFIRM(user.id, contribution.id, contribution.amount)
|
||||
await EVENT_CONTRIBUTION_CONFIRM(user, moderatorUser, contribution, contribution.amount)
|
||||
} finally {
|
||||
releaseLock()
|
||||
}
|
||||
@ -585,21 +591,17 @@ export class ContributionResolver {
|
||||
|
||||
@Authorized([RIGHTS.OPEN_CREATIONS])
|
||||
@Query(() => [OpenCreation])
|
||||
async openCreations(
|
||||
@Arg('userId', () => Int, { nullable: true }) userId: number | null,
|
||||
async openCreations(@Ctx() context: Context): Promise<OpenCreation[]> {
|
||||
return getOpenCreations(getUser(context).id, getClientTimezoneOffset(context))
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.ADMIN_OPEN_CREATIONS])
|
||||
@Query(() => [OpenCreation])
|
||||
async adminOpenCreations(
|
||||
@Arg('userId', () => Int) userId: number,
|
||||
@Ctx() context: Context,
|
||||
): Promise<OpenCreation[]> {
|
||||
const id = userId || getUser(context).id
|
||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||
const creationDates = getCreationDates(clientTimezoneOffset)
|
||||
const creations = await getUserCreation(id, clientTimezoneOffset)
|
||||
return creationDates.map((date, index) => {
|
||||
return {
|
||||
month: date.getMonth(),
|
||||
year: date.getFullYear(),
|
||||
amount: creations[index],
|
||||
}
|
||||
})
|
||||
return getOpenCreations(userId, getClientTimezoneOffset(context))
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.DENY_CONTRIBUTION])
|
||||
@ -640,13 +642,13 @@ export class ContributionResolver {
|
||||
const res = await contributionToUpdate.save()
|
||||
|
||||
await EVENT_ADMIN_CONTRIBUTION_DENY(
|
||||
contributionToUpdate.userId,
|
||||
moderator.id,
|
||||
contributionToUpdate.id,
|
||||
user,
|
||||
moderator,
|
||||
contributionToUpdate,
|
||||
contributionToUpdate.amount,
|
||||
)
|
||||
|
||||
sendContributionDeniedEmail({
|
||||
void sendContributionDeniedEmail({
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
email: user.emailContact.email,
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import { Resolver, Query, Args, Ctx, Authorized, Arg } from 'type-graphql'
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { Resolver, Query, Args, Ctx, Authorized, Arg, Int, Float } from 'type-graphql'
|
||||
|
||||
import { GdtEntryList } from '@model/GdtEntryList'
|
||||
import { Order } from '@enum/Order'
|
||||
@ -23,6 +26,7 @@ export class GdtResolver {
|
||||
|
||||
try {
|
||||
const resultGDT = await apiGet(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`${CONFIG.GDT_API_URL}/GdtEntries/listPerEmailApi/${userEntity.emailContact.email}/${currentPage}/${pageSize}/${order}`,
|
||||
)
|
||||
if (!resultGDT.success) {
|
||||
@ -35,7 +39,7 @@ export class GdtResolver {
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.GDT_BALANCE])
|
||||
@Query(() => Number)
|
||||
@Query(() => Float, { nullable: true })
|
||||
async gdtBalance(@Ctx() context: Context): Promise<number | null> {
|
||||
const user = getUser(context)
|
||||
try {
|
||||
@ -54,9 +58,9 @@ export class GdtResolver {
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.EXIST_PID])
|
||||
@Query(() => Number)
|
||||
@Query(() => Int)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async existPid(@Arg('pid') pid: number): Promise<number> {
|
||||
async existPid(@Arg('pid', () => Int) pid: number): Promise<number> {
|
||||
// load user
|
||||
const resultPID = await apiGet(`${CONFIG.GDT_API_URL}/publishers/checkPidApi/${pid}`)
|
||||
if (!resultPID.success) {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { Resolver, Query, Authorized, Arg, Mutation, Args } from 'type-graphql'
|
||||
|
||||
import SubscribeNewsletterArgs from '@arg/SubscribeNewsletterArgs'
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { Resolver, Query, Authorized, FieldResolver } from 'type-graphql'
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
@ -15,7 +17,7 @@ import { calculateDecay } from '@/util/decay'
|
||||
export class StatisticsResolver {
|
||||
@Authorized([RIGHTS.COMMUNITY_STATISTICS])
|
||||
@Query(() => CommunityStatistics)
|
||||
async communityStatistics(): Promise<CommunityStatistics> {
|
||||
communityStatistics(): CommunityStatistics {
|
||||
return new CommunityStatistics()
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* 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/unbound-method */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
@ -17,10 +22,12 @@ import {
|
||||
createContribution,
|
||||
updateContribution,
|
||||
createTransactionLink,
|
||||
confirmContribution,
|
||||
} from '@/seeds/graphql/mutations'
|
||||
import { listTransactionLinksAdmin } from '@/seeds/graphql/queries'
|
||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||
import { User } from '@entity/User'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { GraphQLError } from 'graphql'
|
||||
@ -137,6 +144,8 @@ describe('TransactionLinkResolver', () => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
let contributionId: number
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
@ -210,7 +219,7 @@ describe('TransactionLinkResolver', () => {
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'CL-' + contributionLink.code,
|
||||
code: `CL-${contributionLink.code}`,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
@ -249,7 +258,7 @@ describe('TransactionLinkResolver', () => {
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'CL-' + contributionLink.code,
|
||||
code: `CL-${contributionLink.code}`,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
@ -288,7 +297,7 @@ describe('TransactionLinkResolver', () => {
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'CL-' + contributionLink.code,
|
||||
code: `CL-${contributionLink.code}`,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
@ -306,7 +315,6 @@ describe('TransactionLinkResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: have this test separated into a transactionLink and a contributionLink part
|
||||
describe('redeem daily Contribution Link', () => {
|
||||
const now = new Date()
|
||||
let contributionLink: DbContributionLink | undefined
|
||||
@ -332,6 +340,10 @@ describe('TransactionLinkResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await resetEntity(Transaction)
|
||||
})
|
||||
|
||||
it('has a daily contribution link in the database', async () => {
|
||||
const cls = await DbContributionLink.find()
|
||||
expect(cls).toHaveLength(1)
|
||||
@ -373,6 +385,7 @@ describe('TransactionLinkResolver', () => {
|
||||
},
|
||||
})
|
||||
contribution = result.data.createContribution
|
||||
contributionId = result.data.createContribution.id
|
||||
})
|
||||
|
||||
it('does not allow the user to redeem the contribution link', async () => {
|
||||
@ -508,6 +521,92 @@ describe('TransactionLinkResolver', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('transaction link', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('link does not exits', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
it('throws and logs the error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'not-valid',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Transaction link not found')],
|
||||
})
|
||||
expect(logger.error).toBeCalledWith('Transaction link not found', 'not-valid')
|
||||
})
|
||||
})
|
||||
|
||||
describe('link exists', () => {
|
||||
let myCode: string
|
||||
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
await mutate({
|
||||
mutation: confirmContribution,
|
||||
variables: { id: contributionId },
|
||||
})
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
})
|
||||
const {
|
||||
data: {
|
||||
createTransactionLink: { code },
|
||||
},
|
||||
} = await mutate({
|
||||
mutation: createTransactionLink,
|
||||
variables: {
|
||||
amount: 200,
|
||||
memo: 'This is a transaction link from bibi',
|
||||
},
|
||||
})
|
||||
myCode = code
|
||||
})
|
||||
|
||||
describe('own link', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
it('throws and logs an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: myCode,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Cannot redeem own transaction link')],
|
||||
})
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Cannot redeem own transaction link',
|
||||
expect.any(Number),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -600,6 +699,26 @@ describe('TransactionLinkResolver', () => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
describe('', () => {
|
||||
it('throws error when user does not exists', async () => {
|
||||
jest.clearAllMocks()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: listTransactionLinksAdmin,
|
||||
variables: {
|
||||
userId: -1,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Could not find requested User')],
|
||||
})
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Could not find requested User', -1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('without any filters', () => {
|
||||
it('finds 6 open transaction links and no deleted or redeemed', async () => {
|
||||
await expect(
|
||||
@ -611,8 +730,8 @@ describe('TransactionLinkResolver', () => {
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listTransactionLinksAdmin: {
|
||||
linkCount: 6,
|
||||
linkList: expect.not.arrayContaining([
|
||||
count: 6,
|
||||
links: expect.not.arrayContaining([
|
||||
expect.objectContaining({
|
||||
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
||||
createdAt: expect.any(String),
|
||||
@ -647,8 +766,8 @@ describe('TransactionLinkResolver', () => {
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listTransactionLinksAdmin: {
|
||||
linkCount: 6,
|
||||
linkList: expect.not.arrayContaining([
|
||||
count: 6,
|
||||
links: expect.not.arrayContaining([
|
||||
expect.objectContaining({
|
||||
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
||||
createdAt: expect.any(String),
|
||||
@ -681,8 +800,8 @@ describe('TransactionLinkResolver', () => {
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listTransactionLinksAdmin: {
|
||||
linkCount: 7,
|
||||
linkList: expect.arrayContaining([
|
||||
count: 7,
|
||||
links: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
||||
createdAt: expect.any(String),
|
||||
@ -715,8 +834,8 @@ describe('TransactionLinkResolver', () => {
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listTransactionLinksAdmin: {
|
||||
linkCount: 7,
|
||||
linkList: expect.arrayContaining([
|
||||
count: 7,
|
||||
links: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
||||
createdAt: expect.any(String),
|
||||
@ -752,8 +871,8 @@ describe('TransactionLinkResolver', () => {
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
listTransactionLinksAdmin: {
|
||||
linkCount: 6,
|
||||
linkList: expect.arrayContaining([
|
||||
count: 6,
|
||||
links: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
||||
createdAt: expect.any(String),
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { randomBytes } from 'crypto'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
import { getConnection, MoreThan, FindOperator } from '@dbTools/typeorm'
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
|
||||
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
@ -13,7 +13,6 @@ import { User } from '@model/User'
|
||||
import { ContributionLink } from '@model/ContributionLink'
|
||||
import { Decay } from '@model/Decay'
|
||||
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
|
||||
import { Order } from '@enum/Order'
|
||||
import { ContributionType } from '@enum/ContributionType'
|
||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||
@ -35,6 +34,7 @@ import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||
import LogError from '@/server/LogError'
|
||||
|
||||
import { getLastTransaction } from './util/getLastTransaction'
|
||||
import transactionLinkList from './util/transactionLinkList'
|
||||
|
||||
// TODO: do not export, test it inside the resolver
|
||||
export const transactionLinkCode = (date: Date): string => {
|
||||
@ -145,30 +145,6 @@ export class TransactionLinkResolver {
|
||||
}
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.LIST_TRANSACTION_LINKS])
|
||||
@Query(() => [TransactionLink])
|
||||
async listTransactionLinks(
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
@Ctx() context: Context,
|
||||
): Promise<TransactionLink[]> {
|
||||
const user = getUser(context)
|
||||
// const now = new Date()
|
||||
const transactionLinks = await DbTransactionLink.find({
|
||||
where: {
|
||||
userId: user.id,
|
||||
redeemedBy: null,
|
||||
// validUntil: MoreThan(now),
|
||||
},
|
||||
order: {
|
||||
createdAt: order,
|
||||
},
|
||||
skip: (currentPage - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
return transactionLinks.map((tl) => new TransactionLink(tl, new User(user)))
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.REDEEM_TRANSACTION_LINK])
|
||||
@Mutation(() => Boolean)
|
||||
async redeemTransactionLink(
|
||||
@ -309,12 +285,20 @@ export class TransactionLinkResolver {
|
||||
return true
|
||||
} else {
|
||||
const now = new Date()
|
||||
const transactionLink = await DbTransactionLink.findOneOrFail({ code })
|
||||
const linkedUser = await DbUser.findOneOrFail(
|
||||
const transactionLink = await DbTransactionLink.findOne({ code })
|
||||
if (!transactionLink) {
|
||||
throw new LogError('Transaction link not found', code)
|
||||
}
|
||||
|
||||
const linkedUser = await DbUser.findOne(
|
||||
{ id: transactionLink.userId },
|
||||
{ relations: ['emailContact'] },
|
||||
)
|
||||
|
||||
if (!linkedUser) {
|
||||
throw new LogError('Linked user not found for given link', transactionLink.userId)
|
||||
}
|
||||
|
||||
if (user.id === linkedUser.id) {
|
||||
throw new LogError('Cannot redeem own transaction link', user.id)
|
||||
}
|
||||
@ -342,43 +326,39 @@ export class TransactionLinkResolver {
|
||||
}
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.LIST_TRANSACTION_LINKS])
|
||||
@Query(() => TransactionLinkResult)
|
||||
async listTransactionLinks(
|
||||
@Args()
|
||||
paginated: Paginated,
|
||||
@Ctx() context: Context,
|
||||
): Promise<TransactionLinkResult> {
|
||||
return transactionLinkList(
|
||||
paginated,
|
||||
{
|
||||
withDeleted: false,
|
||||
withExpired: true,
|
||||
withRedeemed: false,
|
||||
},
|
||||
getUser(context),
|
||||
)
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.LIST_TRANSACTION_LINKS_ADMIN])
|
||||
@Query(() => TransactionLinkResult)
|
||||
async listTransactionLinksAdmin(
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
paginated: Paginated,
|
||||
// eslint-disable-next-line type-graphql/wrong-decorator-signature
|
||||
@Arg('filters', () => TransactionLinkFilters, { nullable: true })
|
||||
filters: TransactionLinkFilters,
|
||||
filters: TransactionLinkFilters | null, // eslint-disable-line type-graphql/invalid-nullable-input-type
|
||||
@Arg('userId', () => Int)
|
||||
userId: number,
|
||||
): Promise<TransactionLinkResult> {
|
||||
const user = await DbUser.findOneOrFail({ id: userId })
|
||||
const where: {
|
||||
userId: number
|
||||
redeemedBy?: number | null
|
||||
validUntil?: FindOperator<Date> | null
|
||||
} = {
|
||||
userId,
|
||||
redeemedBy: null,
|
||||
validUntil: MoreThan(new Date()),
|
||||
}
|
||||
if (filters) {
|
||||
if (filters.withRedeemed) delete where.redeemedBy
|
||||
if (filters.withExpired) delete where.validUntil
|
||||
}
|
||||
const [transactionLinks, count] = await DbTransactionLink.findAndCount({
|
||||
where,
|
||||
withDeleted: filters ? filters.withDeleted : false,
|
||||
order: {
|
||||
createdAt: order,
|
||||
},
|
||||
skip: (currentPage - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
|
||||
return {
|
||||
linkCount: count,
|
||||
linkList: transactionLinks.map((tl) => new TransactionLink(tl, new User(user))),
|
||||
const user = await DbUser.findOne({ id: userId })
|
||||
if (!user) {
|
||||
throw new LogError('Could not find requested User', userId)
|
||||
}
|
||||
return transactionLinkList(paginated, filters, user)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
@ -14,7 +18,7 @@ import { bobBaumeister } from '@/seeds/users/bob-baumeister'
|
||||
import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
|
||||
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||
import { stephenHawking } from '@/seeds/users/stephen-hawking'
|
||||
import { EventProtocol } from '@entity/EventProtocol'
|
||||
import { Event as DbEvent } from '@entity/Event'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
import { User } from '@entity/User'
|
||||
import { cleanDB, testEnvironment } from '@test/helpers'
|
||||
@ -324,7 +328,7 @@ describe('send coins', () => {
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
sendCoins: 'true',
|
||||
sendCoins: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
@ -337,12 +341,13 @@ describe('send coins', () => {
|
||||
memo: 'unrepeatable memo',
|
||||
})
|
||||
|
||||
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.TRANSACTION_SEND,
|
||||
userId: user[1].id,
|
||||
transactionId: transaction[0].id,
|
||||
xUserId: user[0].id,
|
||||
affectedUserId: user[1].id,
|
||||
actingUserId: user[1].id,
|
||||
involvedUserId: user[0].id,
|
||||
involvedTransactionId: transaction[0].id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -354,12 +359,13 @@ describe('send coins', () => {
|
||||
memo: 'unrepeatable memo',
|
||||
})
|
||||
|
||||
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.TRANSACTION_RECEIVE,
|
||||
userId: user[0].id,
|
||||
transactionId: transaction[0].id,
|
||||
xUserId: user[1].id,
|
||||
affectedUserId: user[0].id,
|
||||
actingUserId: user[1].id,
|
||||
involvedUserId: user[1].id,
|
||||
involvedTransactionId: transaction[0].id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -379,7 +385,7 @@ describe('send coins', () => {
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
sendCoins: 'true',
|
||||
sendCoins: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
@ -395,7 +401,7 @@ describe('send coins', () => {
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
sendCoins: 'true',
|
||||
sendCoins: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
@ -411,7 +417,7 @@ describe('send coins', () => {
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
sendCoins: 'true',
|
||||
sendCoins: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
@ -427,7 +433,7 @@ describe('send coins', () => {
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
sendCoins: 'true',
|
||||
sendCoins: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable new-cap */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
@ -137,17 +138,12 @@ export const executeTransaction = async (
|
||||
await queryRunner.commitTransaction()
|
||||
logger.info(`commit Transaction successful...`)
|
||||
|
||||
await EVENT_TRANSACTION_SEND(
|
||||
transactionSend.userId,
|
||||
transactionSend.linkedUserId,
|
||||
transactionSend.id,
|
||||
transactionSend.amount.mul(-1),
|
||||
)
|
||||
await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount)
|
||||
|
||||
await EVENT_TRANSACTION_RECEIVE(
|
||||
transactionReceive.userId,
|
||||
transactionReceive.linkedUserId,
|
||||
transactionReceive.id,
|
||||
recipient,
|
||||
sender,
|
||||
transactionReceive,
|
||||
transactionReceive.amount,
|
||||
)
|
||||
} catch (e) {
|
||||
@ -304,7 +300,7 @@ export class TransactionResolver {
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.SEND_COINS])
|
||||
@Mutation(() => String)
|
||||
@Mutation(() => Boolean)
|
||||
async sendCoins(
|
||||
@Args() { email, amount, memo }: TransactionSendArgs,
|
||||
@Ctx() context: Context,
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
@ -35,7 +40,7 @@ import { transactionLinkFactory } from '@/seeds/factory/transactionLink'
|
||||
import { ContributionLink } from '@model/ContributionLink'
|
||||
import { TransactionLink } from '@entity/TransactionLink'
|
||||
import { EventProtocolType } from '@/event/EventProtocolType'
|
||||
import { EventProtocol } from '@entity/EventProtocol'
|
||||
import { Event as DbEvent } from '@entity/Event'
|
||||
import { validate as validateUUID, version as versionUUID } from 'uuid'
|
||||
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||
import { UserContact } from '@entity/UserContact'
|
||||
@ -182,10 +187,11 @@ describe('UserResolver', () => {
|
||||
{ email: 'peter@lustig.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.REGISTER,
|
||||
userId: userConatct.user.id,
|
||||
affectedUserId: userConatct.user.id,
|
||||
actingUserId: userConatct.user.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -210,11 +216,12 @@ describe('UserResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('stores the SEND_CONFIRMATION_EMAIL event in the database', () => {
|
||||
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
it('stores the SEND_CONFIRMATION_EMAIL event in the database', async () => {
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.SEND_CONFIRMATION_EMAIL,
|
||||
userId: user[0].id,
|
||||
affectedUserId: user[0].id,
|
||||
actingUserId: user[0].id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -226,7 +233,7 @@ describe('UserResolver', () => {
|
||||
mutation = await mutate({ mutation: createUser, variables })
|
||||
})
|
||||
|
||||
it('logs an info', async () => {
|
||||
it('logs an info', () => {
|
||||
expect(logger.info).toBeCalledWith('User already exists with this email=peter@lustig.de')
|
||||
})
|
||||
|
||||
@ -239,7 +246,7 @@ describe('UserResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('results with partly faked user with random "id"', async () => {
|
||||
it('results with partly faked user with random "id"', () => {
|
||||
expect(mutation).toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
@ -256,10 +263,11 @@ describe('UserResolver', () => {
|
||||
{ email: 'peter@lustig.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL,
|
||||
userId: userConatct.user.id,
|
||||
affectedUserId: userConatct.user.id,
|
||||
actingUserId: 0,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -283,7 +291,7 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
describe('no publisher id', () => {
|
||||
it('sets publisher id to null', async () => {
|
||||
it('sets publisher id to 0', async () => {
|
||||
await mutate({
|
||||
mutation: createUser,
|
||||
variables: { ...variables, email: 'raeuber@hotzenplotz.de', publisherId: undefined },
|
||||
@ -294,7 +302,7 @@ describe('UserResolver', () => {
|
||||
emailContact: expect.objectContaining({
|
||||
email: 'raeuber@hotzenplotz.de',
|
||||
}),
|
||||
publisherId: null,
|
||||
publisherId: 0,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
@ -355,21 +363,23 @@ describe('UserResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('stores the ACTIVATE_ACCOUNT event in the database', () => {
|
||||
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
it('stores the ACTIVATE_ACCOUNT event in the database', async () => {
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.ACTIVATE_ACCOUNT,
|
||||
userId: user[0].id,
|
||||
affectedUserId: user[0].id,
|
||||
actingUserId: user[0].id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('stores the REDEEM_REGISTER event in the database', () => {
|
||||
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
it('stores the REDEEM_REGISTER event in the database', async () => {
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.REDEEM_REGISTER,
|
||||
userId: result.data.createUser.id,
|
||||
contributionId: link.id,
|
||||
affectedUserId: result.data.createUser.id,
|
||||
actingUserId: result.data.createUser.id,
|
||||
involvedContributionId: link.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -449,10 +459,12 @@ describe('UserResolver', () => {
|
||||
})
|
||||
|
||||
it('stores the REDEEM_REGISTER event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.REDEEM_REGISTER,
|
||||
userId: newUser.data.createUser.id,
|
||||
affectedUserId: newUser.data.createUser.id,
|
||||
actingUserId: newUser.data.createUser.id,
|
||||
involvedTransactionId: transactionLink.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -680,10 +692,11 @@ describe('UserResolver', () => {
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.LOGIN,
|
||||
userId: userConatct.user.id,
|
||||
affectedUserId: userConatct.user.id,
|
||||
actingUserId: userConatct.user.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -849,7 +862,7 @@ describe('UserResolver', () => {
|
||||
it('returns true', async () => {
|
||||
await expect(mutate({ mutation: logout })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
data: { logout: 'true' },
|
||||
data: { logout: true },
|
||||
errors: undefined,
|
||||
}),
|
||||
)
|
||||
@ -927,11 +940,12 @@ describe('UserResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('stores the LOGIN event in the database', () => {
|
||||
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
it('stores the LOGIN event in the database', async () => {
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.LOGIN,
|
||||
userId: user[0].id,
|
||||
affectedUserId: user[0].id,
|
||||
actingUserId: user[0].id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -1847,10 +1861,11 @@ describe('UserResolver', () => {
|
||||
{ email: 'bibi@bloxberg.de' },
|
||||
{ relations: ['user'] },
|
||||
)
|
||||
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
await expect(DbEvent.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL,
|
||||
userId: userConatct.user.id,
|
||||
affectedUserId: userConatct.user.id,
|
||||
actingUserId: admin.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
import i18n from 'i18n'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import {
|
||||
@ -16,7 +20,9 @@ import { getConnection, getCustomRepository, IsNull, Not } from '@dbTools/typeor
|
||||
import { User as DbUser } from '@entity/User'
|
||||
import { UserContact as DbUserContact } from '@entity/UserContact'
|
||||
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
||||
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||
import { Contribution as DbContribution } from '@entity/Contribution'
|
||||
import { UserRepository } from '@repository/User'
|
||||
|
||||
import { User } from '@model/User'
|
||||
@ -166,11 +172,11 @@ export class UserResolver {
|
||||
|
||||
// Elopage Status & Stored PublisherId
|
||||
user.hasElopage = await this.hasElopage({ ...context, user: dbUser })
|
||||
logger.info('user.hasElopage=' + user.hasElopage)
|
||||
logger.info('user.hasElopage', user.hasElopage)
|
||||
if (!user.hasElopage && publisherId) {
|
||||
user.publisherId = publisherId
|
||||
dbUser.publisherId = publisherId
|
||||
DbUser.save(dbUser)
|
||||
await DbUser.save(dbUser)
|
||||
}
|
||||
|
||||
context.setHeaders.push({
|
||||
@ -178,14 +184,14 @@ export class UserResolver {
|
||||
value: encode(dbUser.gradidoID),
|
||||
})
|
||||
|
||||
await EVENT_LOGIN(user.id)
|
||||
await EVENT_LOGIN(dbUser)
|
||||
logger.info(`successful Login: ${JSON.stringify(user, null, 2)}`)
|
||||
return user
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.LOGOUT])
|
||||
@Mutation(() => String)
|
||||
async logout(): Promise<boolean> {
|
||||
@Mutation(() => Boolean)
|
||||
logout(): boolean {
|
||||
// TODO: Event still missing here!!
|
||||
// TODO: We dont need this anymore, but might need this in the future in oder to invalidate a valid JWT-Token.
|
||||
// Furthermore this hook can be useful for tracking user behaviour (did he logout or not? Warn him if he didn't on next login)
|
||||
@ -202,7 +208,7 @@ export class UserResolver {
|
||||
@Mutation(() => User)
|
||||
async createUser(
|
||||
@Args()
|
||||
{ email, firstName, lastName, language, publisherId, redeemCode = null }: CreateUserArgs,
|
||||
{ email, firstName, lastName, language, publisherId = null, redeemCode = null }: CreateUserArgs,
|
||||
): Promise<User> {
|
||||
logger.addContext('user', 'unknown')
|
||||
logger.info(
|
||||
@ -239,7 +245,7 @@ export class UserResolver {
|
||||
user.lastName = lastName
|
||||
user.language = language
|
||||
user.publisherId = publisherId
|
||||
logger.debug('partly faked user=' + user)
|
||||
logger.debug('partly faked user', user)
|
||||
|
||||
const emailSent = await sendAccountMultiRegistrationEmail({
|
||||
firstName: foundUser.firstName, // this is the real name of the email owner, but just "firstName" would be the name of the new registrant which shall not be passed to the outside
|
||||
@ -248,7 +254,7 @@ export class UserResolver {
|
||||
language: foundUser.language, // use language of the emails owner for sending
|
||||
})
|
||||
|
||||
await EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL(foundUser.id)
|
||||
await EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL(foundUser)
|
||||
|
||||
logger.info(
|
||||
`sendAccountMultiRegistrationEmail by ${firstName} ${lastName} to ${foundUser.firstName} ${foundUser.lastName} <${email}>`,
|
||||
@ -266,31 +272,37 @@ export class UserResolver {
|
||||
|
||||
const gradidoID = await newGradidoID()
|
||||
|
||||
const eventRegisterRedeem = Event(EventProtocolType.REDEEM_REGISTER, 0)
|
||||
const eventRegisterRedeem = Event(
|
||||
EventProtocolType.REDEEM_REGISTER,
|
||||
{ id: 0 } as DbUser,
|
||||
{ id: 0 } as DbUser,
|
||||
)
|
||||
let dbUser = new DbUser()
|
||||
dbUser.gradidoID = gradidoID
|
||||
dbUser.firstName = firstName
|
||||
dbUser.lastName = lastName
|
||||
dbUser.language = language
|
||||
dbUser.publisherId = publisherId
|
||||
dbUser.publisherId = publisherId || 0
|
||||
dbUser.passwordEncryptionType = PasswordEncryptionType.NO_PASSWORD
|
||||
logger.debug('new dbUser=' + dbUser)
|
||||
logger.debug('new dbUser', dbUser)
|
||||
if (redeemCode) {
|
||||
if (redeemCode.match(/^CL-/)) {
|
||||
const contributionLink = await DbContributionLink.findOne({
|
||||
code: redeemCode.replace('CL-', ''),
|
||||
})
|
||||
logger.info('redeemCode found contributionLink=' + contributionLink)
|
||||
logger.info('redeemCode found contributionLink', contributionLink)
|
||||
if (contributionLink) {
|
||||
dbUser.contributionLinkId = contributionLink.id
|
||||
eventRegisterRedeem.contributionId = contributionLink.id
|
||||
// TODO this is so wrong
|
||||
eventRegisterRedeem.involvedContribution = { id: contributionLink.id } as DbContribution
|
||||
}
|
||||
} else {
|
||||
const transactionLink = await DbTransactionLink.findOne({ code: redeemCode })
|
||||
logger.info('redeemCode found transactionLink=' + transactionLink)
|
||||
logger.info('redeemCode found transactionLink', transactionLink)
|
||||
if (transactionLink) {
|
||||
dbUser.referrerId = transactionLink.userId
|
||||
eventRegisterRedeem.transactionId = transactionLink.id
|
||||
// TODO this is so wrong
|
||||
eventRegisterRedeem.involvedTransaction = { id: transactionLink.id } as DbTransaction
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -329,7 +341,7 @@ export class UserResolver {
|
||||
})
|
||||
logger.info(`sendAccountActivationEmail of ${firstName}.${lastName} to ${email}`)
|
||||
|
||||
await EVENT_SEND_CONFIRMATION_EMAIL(dbUser.id)
|
||||
await EVENT_SEND_CONFIRMATION_EMAIL(dbUser)
|
||||
|
||||
if (!emailSent) {
|
||||
logger.debug(`Account confirmation link: ${activationLink}`)
|
||||
@ -346,10 +358,11 @@ export class UserResolver {
|
||||
logger.info('createUser() successful...')
|
||||
|
||||
if (redeemCode) {
|
||||
eventRegisterRedeem.userId = dbUser.id
|
||||
eventRegisterRedeem.affectedUser = dbUser
|
||||
eventRegisterRedeem.actingUser = dbUser
|
||||
await eventRegisterRedeem.save()
|
||||
} else {
|
||||
await EVENT_REGISTER(dbUser.id)
|
||||
await EVENT_REGISTER(dbUser)
|
||||
}
|
||||
|
||||
return new User(dbUser)
|
||||
@ -465,7 +478,7 @@ export class UserResolver {
|
||||
await queryRunner.commitTransaction()
|
||||
logger.info('User and UserContact data written successfully...')
|
||||
|
||||
await EVENT_ACTIVATE_ACCOUNT(user.id)
|
||||
await EVENT_ACTIVATE_ACCOUNT(user)
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
throw new LogError('Error on writing User and User Contact data', e)
|
||||
@ -654,7 +667,7 @@ export class UserResolver {
|
||||
return 'user.' + fieldName
|
||||
}),
|
||||
searchText,
|
||||
filters,
|
||||
filters || null,
|
||||
currentPage,
|
||||
pageSize,
|
||||
)
|
||||
@ -775,9 +788,13 @@ export class UserResolver {
|
||||
return null
|
||||
}
|
||||
|
||||
// TODO this is an admin function - needs refactor
|
||||
@Authorized([RIGHTS.SEND_ACTIVATION_EMAIL])
|
||||
@Mutation(() => Boolean)
|
||||
async sendActivationEmail(@Arg('email') email: string): Promise<boolean> {
|
||||
async sendActivationEmail(
|
||||
@Arg('email') email: string,
|
||||
@Ctx() context: Context,
|
||||
): Promise<boolean> {
|
||||
email = email.trim().toLowerCase()
|
||||
// const user = await dbUser.findOne({ id: emailContact.userId })
|
||||
const user = await findUserByEmail(email)
|
||||
@ -802,7 +819,7 @@ export class UserResolver {
|
||||
if (!emailSent) {
|
||||
logger.info(`Account confirmation link: ${activationLink}`)
|
||||
} else {
|
||||
await EVENT_ADMIN_SEND_CONFIRMATION_EMAIL(user.id)
|
||||
await EVENT_ADMIN_SEND_CONFIRMATION_EMAIL(user, getUser(context))
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import Decimal from 'decimal.js-light'
|
||||
@ -79,7 +83,7 @@ describe('semaphore', () => {
|
||||
maxPerCycle: 1,
|
||||
},
|
||||
})
|
||||
contributionLinkCode = 'CL-' + contributionLink.code
|
||||
contributionLinkCode = `CL-${contributionLink.code}`
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'bob@baumeister.de', password: 'Aa12345_' },
|
||||
@ -187,4 +191,50 @@ describe('semaphore', () => {
|
||||
await expect(confirmBibisContribution).resolves.toMatchObject({ errors: undefined })
|
||||
await expect(confirmBobsContribution).resolves.toMatchObject({ errors: undefined })
|
||||
})
|
||||
|
||||
describe('redeem transaction link twice', () => {
|
||||
let myCode: string
|
||||
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
})
|
||||
const {
|
||||
data: { createTransactionLink: bibisLink },
|
||||
} = await mutate({
|
||||
mutation: createTransactionLink,
|
||||
variables: {
|
||||
amount: 20,
|
||||
memo: 'Bibis Link',
|
||||
},
|
||||
})
|
||||
myCode = bibisLink.code
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'bob@baumeister.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
it('does not throw, but should', async () => {
|
||||
const redeem1 = mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: myCode,
|
||||
},
|
||||
})
|
||||
const redeem2 = mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: myCode,
|
||||
},
|
||||
})
|
||||
await expect(redeem1).resolves.toMatchObject({
|
||||
errors: undefined,
|
||||
})
|
||||
await expect(redeem2).resolves.toMatchObject({
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import LogError from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
import { Contribution } from '@entity/Contribution'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { FULL_CREATION_AVAILABLE, MAX_CREATION_AMOUNT } from '../const/const'
|
||||
import { OpenCreation } from '@model/OpenCreation'
|
||||
|
||||
interface CreationMap {
|
||||
id: number
|
||||
@ -100,7 +103,7 @@ const getCreationMonths = (timezoneOffset: number): number[] => {
|
||||
return getCreationDates(timezoneOffset).map((date) => date.getMonth() + 1)
|
||||
}
|
||||
|
||||
export const getCreationDates = (timezoneOffset: number): Date[] => {
|
||||
const getCreationDates = (timezoneOffset: number): Date[] => {
|
||||
const clientNow = new Date()
|
||||
clientNow.setTime(clientNow.getTime() - timezoneOffset * 60 * 1000)
|
||||
logger.info(
|
||||
@ -152,3 +155,18 @@ export const updateCreations = (
|
||||
export const isValidDateString = (dateString: string): boolean => {
|
||||
return new Date(dateString).toString() !== 'Invalid Date'
|
||||
}
|
||||
|
||||
export const getOpenCreations = async (
|
||||
userId: number,
|
||||
timezoneOffset: number,
|
||||
): Promise<OpenCreation[]> => {
|
||||
const creations = await getUserCreation(userId, timezoneOffset)
|
||||
const creationDates = getCreationDates(timezoneOffset)
|
||||
return creationDates.map((date, index) => {
|
||||
return {
|
||||
month: date.getMonth(),
|
||||
year: date.getFullYear(),
|
||||
amount: creations[index],
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -8,17 +8,21 @@ export const findContributions = async (
|
||||
currentPage: number,
|
||||
pageSize: number,
|
||||
withDeleted: boolean,
|
||||
statusFilter?: ContributionStatus[],
|
||||
relations: string[],
|
||||
userId?: number,
|
||||
statusFilter?: ContributionStatus[] | null,
|
||||
): Promise<[DbContribution[], number]> =>
|
||||
DbContribution.findAndCount({
|
||||
where: {
|
||||
...(statusFilter && statusFilter.length && { contributionStatus: In(statusFilter) }),
|
||||
...(userId && { userId }),
|
||||
},
|
||||
withDeleted: withDeleted,
|
||||
order: {
|
||||
createdAt: order,
|
||||
id: order,
|
||||
},
|
||||
relations: ['user'],
|
||||
relations,
|
||||
skip: (currentPage - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
|
||||
38
backend/src/graphql/resolver/util/transactionLinkList.ts
Normal file
38
backend/src/graphql/resolver/util/transactionLinkList.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { MoreThan } from '@dbTools/typeorm'
|
||||
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
||||
import { User as DbUser } from '@entity/User'
|
||||
import { Order } from '@enum/Order'
|
||||
import Paginated from '@arg/Paginated'
|
||||
import TransactionLinkFilters from '@arg/TransactionLinkFilters'
|
||||
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
|
||||
import { User } from '@/graphql/model/User'
|
||||
|
||||
export default async function transactionLinkList(
|
||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||
filters: TransactionLinkFilters | null,
|
||||
user: DbUser,
|
||||
): Promise<TransactionLinkResult> {
|
||||
const { withDeleted, withExpired, withRedeemed } = filters || {
|
||||
withDeleted: false,
|
||||
withExpired: false,
|
||||
withRedeemed: false,
|
||||
}
|
||||
const [transactionLinks, count] = await DbTransactionLink.findAndCount({
|
||||
where: {
|
||||
userId: user.id,
|
||||
...(!withRedeemed && { redeemedBy: null }),
|
||||
...(!withExpired && { validUntil: MoreThan(new Date()) }),
|
||||
},
|
||||
withDeleted,
|
||||
order: {
|
||||
createdAt: order,
|
||||
},
|
||||
skip: (currentPage - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
|
||||
return {
|
||||
count,
|
||||
links: transactionLinks.map((tl) => new TransactionLink(tl, new User(user))),
|
||||
}
|
||||
}
|
||||
@ -17,7 +17,7 @@ async function main() {
|
||||
console.log(`GraphIQL available at http://localhost:${CONFIG.PORT}`)
|
||||
}
|
||||
})
|
||||
startValidateCommunities(Number(CONFIG.FEDERATION_VALIDATE_COMMUNITY_TIMER))
|
||||
void startValidateCommunities(Number(CONFIG.FEDERATION_VALIDATE_COMMUNITY_TIMER))
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
"addedContributionMessage": {
|
||||
"commonGoodContributionMessage": "du hast zu deinem Gemeinwohl-Beitrag „{contributionMemo}“ eine Nachricht von {senderFirstName} {senderLastName} erhalten.",
|
||||
"subject": "Gradido: Nachricht zu deinem Gemeinwohl-Beitrag",
|
||||
"toSeeAndAnswerMessage": "Um die Nachricht zu sehen und darauf zu antworten, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
|
||||
"toSeeAndAnswerMessage": "Um die Nachricht zu sehen und darauf zu antworten, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“!"
|
||||
},
|
||||
"contributionConfirmed": {
|
||||
"commonGoodContributionConfirmed": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde soeben von {senderFirstName} {senderLastName} bestätigt und in deinem Gradido-Konto gutgeschrieben.",
|
||||
@ -26,12 +26,12 @@
|
||||
"contributionDeleted": {
|
||||
"commonGoodContributionDeleted": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} gelöscht.",
|
||||
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde gelöscht",
|
||||
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
|
||||
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“!"
|
||||
},
|
||||
"contributionDenied": {
|
||||
"commonGoodContributionDenied": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} abgelehnt.",
|
||||
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde abgelehnt",
|
||||
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
|
||||
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“!"
|
||||
},
|
||||
"general": {
|
||||
"amountGDD": "Betrag: {amountGDD} GDD",
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
"addedContributionMessage": {
|
||||
"commonGoodContributionMessage": "you have received a message from {senderFirstName} {senderLastName} regarding your common good contribution “{contributionMemo}”.",
|
||||
"subject": "Gradido: Message about your common good contribution",
|
||||
"toSeeAndAnswerMessage": "To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
|
||||
"toSeeAndAnswerMessage": "To view and reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!"
|
||||
},
|
||||
"contributionConfirmed": {
|
||||
"commonGoodContributionConfirmed": "Your public good contribution “{contributionMemo}” has just been confirmed by {senderFirstName} {senderLastName} and credited to your Gradido account.",
|
||||
@ -26,12 +26,12 @@
|
||||
"contributionDeleted": {
|
||||
"commonGoodContributionDeleted": "Your public good contribution “{contributionMemo}” was deleted by {senderFirstName} {senderLastName}.",
|
||||
"subject": "Gradido: Your common good contribution was deleted",
|
||||
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
|
||||
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!"
|
||||
},
|
||||
"contributionDenied": {
|
||||
"commonGoodContributionDenied": "Your public good contribution “{contributionMemo}” was rejected by {senderFirstName} {senderLastName}.",
|
||||
"subject": "Gradido: Your common good contribution was rejected",
|
||||
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
|
||||
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!"
|
||||
},
|
||||
"general": {
|
||||
"amountGDD": "Amount: {amountGDD} GDD",
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
/* 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 */
|
||||
import { MiddlewareFn } from 'type-graphql'
|
||||
import { /* klicktippSignIn, */ getKlickTippUser } from '@/apis/KlicktippController'
|
||||
import { KlickTipp } from '@model/KlickTipp'
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import CONFIG from '@/config'
|
||||
import LogError from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user