mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into 2562-style-and-design-changes-to-a-contribution
This commit is contained in:
commit
7da4c99ad4
207
.github/workflows/test.yml
vendored
207
.github/workflows/test.yml
vendored
@ -163,7 +163,6 @@ jobs:
|
|||||||
locales_frontend:
|
locales_frontend:
|
||||||
name: Locales - Frontend
|
name: Locales - Frontend
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build_test_frontend]
|
|
||||||
steps:
|
steps:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# CHECKOUT CODE ##########################################################
|
# CHECKOUT CODE ##########################################################
|
||||||
@ -171,20 +170,10 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
|
||||||
##########################################################################
|
|
||||||
- name: Download Docker Image (Frontend)
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: docker-frontend-test
|
|
||||||
path: /tmp
|
|
||||||
- name: Load Docker Image
|
|
||||||
run: docker load < /tmp/frontend.tar
|
|
||||||
##########################################################################
|
|
||||||
# LOCALES FRONTEND #######################################################
|
# LOCALES FRONTEND #######################################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
- name: Frontend | Locales
|
- name: Frontend | Locales
|
||||||
run: docker run --rm gradido/frontend:test yarn run locales
|
run: cd frontend && yarn && yarn run locales
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# JOB: LINT FRONTEND #########################################################
|
# JOB: LINT FRONTEND #########################################################
|
||||||
@ -192,7 +181,6 @@ jobs:
|
|||||||
lint_frontend:
|
lint_frontend:
|
||||||
name: Lint - Frontend
|
name: Lint - Frontend
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build_test_frontend]
|
|
||||||
steps:
|
steps:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# CHECKOUT CODE ##########################################################
|
# CHECKOUT CODE ##########################################################
|
||||||
@ -200,20 +188,10 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
|
||||||
##########################################################################
|
|
||||||
- name: Download Docker Image (Frontend)
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: docker-frontend-test
|
|
||||||
path: /tmp
|
|
||||||
- name: Load Docker Image
|
|
||||||
run: docker load < /tmp/frontend.tar
|
|
||||||
##########################################################################
|
|
||||||
# LINT FRONTEND ##########################################################
|
# LINT FRONTEND ##########################################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
- name: Frontend | Lint
|
- name: Frontend | Lint
|
||||||
run: docker run --rm gradido/frontend:test yarn run lint
|
run: cd frontend && yarn && yarn run lint
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# JOB: STYLELINT FRONTEND ####################################################
|
# JOB: STYLELINT FRONTEND ####################################################
|
||||||
@ -221,7 +199,6 @@ jobs:
|
|||||||
stylelint_frontend:
|
stylelint_frontend:
|
||||||
name: Stylelint - Frontend
|
name: Stylelint - Frontend
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build_test_frontend]
|
|
||||||
steps:
|
steps:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# CHECKOUT CODE ##########################################################
|
# CHECKOUT CODE ##########################################################
|
||||||
@ -229,20 +206,10 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
|
||||||
##########################################################################
|
|
||||||
- name: Download Docker Image (Frontend)
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: docker-frontend-test
|
|
||||||
path: /tmp
|
|
||||||
- name: Load Docker Image
|
|
||||||
run: docker load < /tmp/frontend.tar
|
|
||||||
##########################################################################
|
|
||||||
# STYLELINT FRONTEND #####################################################
|
# STYLELINT FRONTEND #####################################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
- name: Frontend | Stylelint
|
- name: Frontend | Stylelint
|
||||||
run: docker run --rm gradido/frontend:test yarn run stylelint
|
run: cd frontend && yarn && yarn run stylelint
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# JOB: LINT ADMIN INTERFACE ##################################################
|
# JOB: LINT ADMIN INTERFACE ##################################################
|
||||||
@ -250,7 +217,6 @@ jobs:
|
|||||||
lint_admin:
|
lint_admin:
|
||||||
name: Lint - Admin Interface
|
name: Lint - Admin Interface
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build_test_admin]
|
|
||||||
steps:
|
steps:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# CHECKOUT CODE ##########################################################
|
# CHECKOUT CODE ##########################################################
|
||||||
@ -258,28 +224,17 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
|
||||||
##########################################################################
|
|
||||||
- name: Download Docker Image (Admin Interface)
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: docker-admin-test
|
|
||||||
path: /tmp
|
|
||||||
- name: Load Docker Image
|
|
||||||
run: docker load < /tmp/admin.tar
|
|
||||||
##########################################################################
|
|
||||||
# LINT ADMIN INTERFACE ###################################################
|
# LINT ADMIN INTERFACE ###################################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
- name: Admin Interface | Lint
|
- name: Admin Interface | Lint
|
||||||
run: docker run --rm gradido/admin:test yarn run lint
|
run: cd admin && yarn && yarn run lint
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# JOB: STYLELINT ADMIN INTERFACE ##############################################
|
# JOB: STYLELINT ADMIN INTERFACE #############################################
|
||||||
##############################################################################
|
##############################################################################
|
||||||
stylelint_admin:
|
stylelint_admin:
|
||||||
name: Stylelint - Admin Interface
|
name: Stylelint - Admin Interface
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build_test_admin]
|
|
||||||
steps:
|
steps:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# CHECKOUT CODE ##########################################################
|
# CHECKOUT CODE ##########################################################
|
||||||
@ -287,20 +242,10 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
|
||||||
##########################################################################
|
|
||||||
- name: Download Docker Image (Admin Interface)
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: docker-admin-test
|
|
||||||
path: /tmp
|
|
||||||
- name: Load Docker Image
|
|
||||||
run: docker load < /tmp/admin.tar
|
|
||||||
##########################################################################
|
|
||||||
# STYLELINT ADMIN INTERFACE ##############################################
|
# STYLELINT ADMIN INTERFACE ##############################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
- name: Admin Interface | Stylelint
|
- name: Admin Interface | Stylelint
|
||||||
run: docker run --rm gradido/admin:test yarn run stylelint
|
run: cd admin && yarn && yarn run stylelint
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# JOB: LOCALES ADMIN #########################################################
|
# JOB: LOCALES ADMIN #########################################################
|
||||||
@ -308,7 +253,6 @@ jobs:
|
|||||||
locales_admin:
|
locales_admin:
|
||||||
name: Locales - Admin Interface
|
name: Locales - Admin Interface
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build_test_admin]
|
|
||||||
steps:
|
steps:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# CHECKOUT CODE ##########################################################
|
# CHECKOUT CODE ##########################################################
|
||||||
@ -316,20 +260,10 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
|
||||||
##########################################################################
|
|
||||||
- name: Download Docker Image (Admin Interface)
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: docker-admin-test
|
|
||||||
path: /tmp
|
|
||||||
- name: Load Docker Image
|
|
||||||
run: docker load < /tmp/admin.tar
|
|
||||||
##########################################################################
|
|
||||||
# LOCALES FRONTEND #######################################################
|
# LOCALES FRONTEND #######################################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
- name: admin | Locales
|
- name: Admin | Locales
|
||||||
run: docker run --rm gradido/admin:test yarn run locales
|
run: cd admin && yarn && yarn run locales
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# JOB: LINT BACKEND ##########################################################
|
# JOB: LINT BACKEND ##########################################################
|
||||||
@ -337,7 +271,6 @@ jobs:
|
|||||||
lint_backend:
|
lint_backend:
|
||||||
name: Lint - Backend
|
name: Lint - Backend
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build_test_backend]
|
|
||||||
steps:
|
steps:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# CHECKOUT CODE ##########################################################
|
# CHECKOUT CODE ##########################################################
|
||||||
@ -345,28 +278,35 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
|
||||||
##########################################################################
|
|
||||||
- name: Download Docker Image (Backend)
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: docker-backend-test
|
|
||||||
path: /tmp
|
|
||||||
- name: Load Docker Image
|
|
||||||
run: docker load < /tmp/backend.tar
|
|
||||||
##########################################################################
|
|
||||||
# LINT BACKEND ###########################################################
|
# LINT BACKEND ###########################################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
- name: backend | Lint
|
- name: backend | Lint
|
||||||
run: docker run --rm gradido/backend:test yarn run lint
|
run: cd backend && yarn && yarn run lint
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# JOB: LOCALES BACKEND #######################################################
|
||||||
|
##############################################################################
|
||||||
|
locales_backend:
|
||||||
|
name: Locales - Backend
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
##########################################################################
|
||||||
|
# CHECKOUT CODE ##########################################################
|
||||||
|
##########################################################################
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
##########################################################################
|
||||||
|
# LOCALES BACKEND #####################################################
|
||||||
|
##########################################################################
|
||||||
|
- name: Backend | Locales
|
||||||
|
run: cd backend && yarn && yarn locales
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# JOB: LINT DATABASE UP ######################################################
|
# JOB: LINT DATABASE UP ######################################################
|
||||||
##############################################################################
|
##############################################################################
|
||||||
lint_database_up:
|
lint_database_up:
|
||||||
name: Lint - Database Up
|
name: Lint - Database Up
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build_test_database_up]
|
|
||||||
steps:
|
steps:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# CHECKOUT CODE ##########################################################
|
# CHECKOUT CODE ##########################################################
|
||||||
@ -374,20 +314,10 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
|
||||||
##########################################################################
|
|
||||||
- name: Download Docker Image (Backend)
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: docker-database-test_up
|
|
||||||
path: /tmp
|
|
||||||
- name: Load Docker Image
|
|
||||||
run: docker load < /tmp/database_up.tar
|
|
||||||
##########################################################################
|
|
||||||
# LINT DATABASE ##########################################################
|
# LINT DATABASE ##########################################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
- name: database | Lint
|
- name: Database | Lint
|
||||||
run: docker run --rm gradido/database:test_up yarn run lint
|
run: cd database && yarn && yarn run lint
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# JOB: UNIT TEST FRONTEND ###################################################
|
# JOB: UNIT TEST FRONTEND ###################################################
|
||||||
@ -395,7 +325,6 @@ jobs:
|
|||||||
unit_test_frontend:
|
unit_test_frontend:
|
||||||
name: Unit tests - Frontend
|
name: Unit tests - Frontend
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build_test_frontend]
|
|
||||||
steps:
|
steps:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# CHECKOUT CODE ##########################################################
|
# CHECKOUT CODE ##########################################################
|
||||||
@ -403,30 +332,12 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# DOWNLOAD DOCKER IMAGES #################################################
|
|
||||||
##########################################################################
|
|
||||||
- name: Download Docker Image (Frontend)
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: docker-frontend-test
|
|
||||||
path: /tmp
|
|
||||||
- name: Load Docker Image
|
|
||||||
run: docker load < /tmp/frontend.tar
|
|
||||||
##########################################################################
|
|
||||||
# UNIT TESTS FRONTEND ####################################################
|
# UNIT TESTS FRONTEND ####################################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
- name: frontend | Unit tests
|
- name: Frontend | Unit tests
|
||||||
run: |
|
run: |
|
||||||
docker run --env NODE_ENV=test -v ~/coverage:/app/coverage --rm gradido/frontend:test yarn run test
|
cd frontend && yarn && yarn run test
|
||||||
cp -r ~/coverage ./coverage
|
cp -r ./coverage ../
|
||||||
##########################################################################
|
|
||||||
# COVERAGE REPORT FRONTEND ###############################################
|
|
||||||
##########################################################################
|
|
||||||
#- name: frontend | Coverage report
|
|
||||||
# uses: romeovs/lcov-reporter-action@v0.2.21
|
|
||||||
# with:
|
|
||||||
# github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
# lcov-file: ./coverage/lcov.info
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# COVERAGE CHECK FRONTEND ################################################
|
# COVERAGE CHECK FRONTEND ################################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
@ -435,7 +346,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
report_name: Coverage Frontend
|
report_name: Coverage Frontend
|
||||||
type: lcov
|
type: lcov
|
||||||
result_path: ./coverage/lcov.info
|
result_path: ./frontend/coverage/lcov.info
|
||||||
min_coverage: 95
|
min_coverage: 95
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
@ -445,7 +356,6 @@ jobs:
|
|||||||
unit_test_admin:
|
unit_test_admin:
|
||||||
name: Unit tests - Admin Interface
|
name: Unit tests - Admin Interface
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build_test_admin]
|
|
||||||
steps:
|
steps:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# CHECKOUT CODE ##########################################################
|
# CHECKOUT CODE ##########################################################
|
||||||
@ -453,22 +363,12 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# DOWNLOAD DOCKER IMAGES #################################################
|
|
||||||
##########################################################################
|
|
||||||
- name: Download Docker Image (Admin Interface)
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: docker-admin-test
|
|
||||||
path: /tmp
|
|
||||||
- name: Load Docker Image
|
|
||||||
run: docker load < /tmp/admin.tar
|
|
||||||
##########################################################################
|
|
||||||
# UNIT TESTS ADMIN INTERFACE #############################################
|
# UNIT TESTS ADMIN INTERFACE #############################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
- name: Admin Interface | Unit tests
|
- name: Admin Interface | Unit tests
|
||||||
run: |
|
run: |
|
||||||
docker run -v ~/coverage:/app/coverage --rm gradido/admin:test yarn run test
|
cd admin && yarn && yarn run test
|
||||||
cp -r ~/coverage ./coverage
|
cp -r ./coverage ../
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# COVERAGE CHECK ADMIN INTERFACE #########################################
|
# COVERAGE CHECK ADMIN INTERFACE #########################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
@ -477,8 +377,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
report_name: Coverage Admin Interface
|
report_name: Coverage Admin Interface
|
||||||
type: lcov
|
type: lcov
|
||||||
result_path: ./coverage/lcov.info
|
result_path: ./admin/coverage/lcov.info
|
||||||
min_coverage: 96
|
min_coverage: 97
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
@ -515,8 +415,9 @@ jobs:
|
|||||||
- name: backend | docker-compose database
|
- name: backend | docker-compose database
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database
|
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database
|
||||||
- name: backend Unit tests | test
|
- name: backend Unit tests | test
|
||||||
run: cd database && yarn && yarn build && cd ../backend && yarn && yarn test
|
run: |
|
||||||
# run: docker-compose -f docker-compose.yml -f docker-compose.test.yml exec -T backend yarn test
|
cd database && yarn && yarn build && cd ../backend && yarn && yarn test
|
||||||
|
cp -r ./coverage ../
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# COVERAGE CHECK BACKEND #################################################
|
# COVERAGE CHECK BACKEND #################################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
@ -526,7 +427,7 @@ jobs:
|
|||||||
report_name: Coverage Backend
|
report_name: Coverage Backend
|
||||||
type: lcov
|
type: lcov
|
||||||
result_path: ./backend/coverage/lcov.info
|
result_path: ./backend/coverage/lcov.info
|
||||||
min_coverage: 78
|
min_coverage: 80
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
@ -558,7 +459,7 @@ jobs:
|
|||||||
end-to-end-tests:
|
end-to-end-tests:
|
||||||
name: End-to-End Tests
|
name: End-to-End Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build_test_mariadb, build_test_database_up, build_test_backend, build_test_admin, build_test_frontend, build_test_nginx]
|
needs: [build_test_mariadb, build_test_database_up, build_test_admin, build_test_frontend, build_test_nginx]
|
||||||
steps:
|
steps:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# CHECKOUT CODE ##########################################################
|
# CHECKOUT CODE ##########################################################
|
||||||
@ -582,13 +483,6 @@ jobs:
|
|||||||
path: /tmp
|
path: /tmp
|
||||||
- name: Load Docker Image (Database Up)
|
- name: Load Docker Image (Database Up)
|
||||||
run: docker load < /tmp/database_up.tar
|
run: docker load < /tmp/database_up.tar
|
||||||
- name: Download Docker Image (Backend)
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: docker-backend-test
|
|
||||||
path: /tmp
|
|
||||||
- name: Load Docker Image (Backend)
|
|
||||||
run: docker load < /tmp/backend.tar
|
|
||||||
- name: Download Docker Image (Frontend)
|
- name: Download Docker Image (Frontend)
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
@ -621,7 +515,11 @@ jobs:
|
|||||||
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database
|
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database
|
||||||
|
|
||||||
- name: Boot up test system | docker-compose backend
|
- name: Boot up test system | docker-compose backend
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps 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
|
- name: Sleep for 10 seconds
|
||||||
run: sleep 10s
|
run: sleep 10s
|
||||||
@ -638,6 +536,9 @@ jobs:
|
|||||||
- name: Boot up test system | docker-compose frontends
|
- 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
|
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
|
- name: Sleep for 15 seconds
|
||||||
run: sleep 15s
|
run: sleep 15s
|
||||||
|
|
||||||
@ -647,12 +548,12 @@ jobs:
|
|||||||
- name: End-to-end tests | run tests
|
- name: End-to-end tests | run tests
|
||||||
id: e2e-tests
|
id: e2e-tests
|
||||||
run: |
|
run: |
|
||||||
cd e2e-tests/cypress/tests/
|
cd e2e-tests/
|
||||||
yarn
|
yarn
|
||||||
yarn run cypress run --spec cypress/e2e/User.Authentication.feature
|
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
|
- name: End-to-end tests | if tests failed, upload screenshots
|
||||||
if: steps.e2e-tests.outcome == 'failure'
|
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: cypress-screenshots
|
name: cypress-screenshots
|
||||||
path: /home/runner/work/gradido/gradido/e2e-tests/cypress/tests/cypress/screenshots/
|
path: /home/runner/work/gradido/gradido/e2e-tests/cypress/screenshots/
|
||||||
|
|||||||
10
.github/workflows/test_dht-node.yml
vendored
10
.github/workflows/test_dht-node.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: gradido test_dht-node CI
|
name: Gradido DHT Node Test CI
|
||||||
|
|
||||||
on: push
|
on: push
|
||||||
|
|
||||||
@ -7,7 +7,7 @@ jobs:
|
|||||||
# JOB: DOCKER BUILD TEST #####################################################
|
# JOB: DOCKER BUILD TEST #####################################################
|
||||||
##############################################################################
|
##############################################################################
|
||||||
build:
|
build:
|
||||||
name: Docker Build Test
|
name: Docker Build Test - DHT Node
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@ -28,7 +28,7 @@ jobs:
|
|||||||
# JOB: LINT ##################################################################
|
# JOB: LINT ##################################################################
|
||||||
##############################################################################
|
##############################################################################
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint - DHT Node
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build]
|
needs: [build]
|
||||||
steps:
|
steps:
|
||||||
@ -50,7 +50,7 @@ jobs:
|
|||||||
# JOB: UNIT TEST #############################################################
|
# JOB: UNIT TEST #############################################################
|
||||||
##############################################################################
|
##############################################################################
|
||||||
unit_test:
|
unit_test:
|
||||||
name: Unit tests
|
name: Unit Tests - DHT Node
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build]
|
needs: [build]
|
||||||
steps:
|
steps:
|
||||||
@ -90,7 +90,7 @@ jobs:
|
|||||||
- name: Coverage check
|
- name: Coverage check
|
||||||
uses: webcraftmedia/coverage-check-action@master
|
uses: webcraftmedia/coverage-check-action@master
|
||||||
with:
|
with:
|
||||||
report_name: Coverage dht-node
|
report_name: Coverage DHT Node
|
||||||
type: lcov
|
type: lcov
|
||||||
#result_path: ./dht-node/coverage/lcov.info
|
#result_path: ./dht-node/coverage/lcov.info
|
||||||
result_path: ./coverage/lcov.info
|
result_path: ./coverage/lcov.info
|
||||||
|
|||||||
98
.github/workflows/test_federation.yml
vendored
Normal file
98
.github/workflows/test_federation.yml
vendored
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
name: Gradido Federation Test CI
|
||||||
|
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
##############################################################################
|
||||||
|
# JOB: DOCKER BUILD TEST #####################################################
|
||||||
|
##############################################################################
|
||||||
|
build:
|
||||||
|
name: Docker Build Test - Federation
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Build `test` image
|
||||||
|
run: |
|
||||||
|
docker build --target test -t "gradido/federation:test" -f federation/Dockerfile .
|
||||||
|
docker save "gradido/federation:test" > /tmp/federation.tar
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: docker-federation-test
|
||||||
|
path: /tmp/federation.tar
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# JOB: LINT ##################################################################
|
||||||
|
##############################################################################
|
||||||
|
lint:
|
||||||
|
name: Lint - Federation
|
||||||
|
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
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# JOB: UNIT TEST #############################################################
|
||||||
|
##############################################################################
|
||||||
|
unit_test:
|
||||||
|
name: Unit Tests - Federation
|
||||||
|
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: docker-compose mariadb
|
||||||
|
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mariadb
|
||||||
|
|
||||||
|
- name: Sleep for 30 seconds
|
||||||
|
run: sleep 30s
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: docker-compose database
|
||||||
|
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database
|
||||||
|
|
||||||
|
- name: Sleep for 30 seconds
|
||||||
|
run: sleep 30s
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
#- 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/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 }}
|
||||||
@ -4,8 +4,15 @@ 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).
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||||
|
|
||||||
|
#### [1.18.2](https://github.com/gradido/gradido/compare/1.18.1...1.18.2)
|
||||||
|
|
||||||
|
- 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.18.1](https://github.com/gradido/gradido/compare/1.18.0...1.18.1)
|
||||||
|
|
||||||
|
> 10 February 2023
|
||||||
|
|
||||||
|
- chore(release): version 1.18.1 [`#2698`](https://github.com/gradido/gradido/pull/2698)
|
||||||
- fix(frontend): fix is last month for empty form date [`#2697`](https://github.com/gradido/gradido/pull/2697)
|
- fix(frontend): fix is last month for empty form date [`#2697`](https://github.com/gradido/gradido/pull/2697)
|
||||||
- fix(frontend): community link [`#2696`](https://github.com/gradido/gradido/pull/2696)
|
- fix(frontend): community link [`#2696`](https://github.com/gradido/gradido/pull/2696)
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"description": "Administraion Interface for Gradido",
|
"description": "Administraion Interface for Gradido",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "Moriz Wahl",
|
"author": "Moriz Wahl",
|
||||||
"version": "1.18.1",
|
"version": "1.18.2",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import defaultLayout from '@/layouts/defaultLayout.vue'
|
import defaultLayout from '@/layouts/defaultLayout'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ChangeUserRoleFormular from './ChangeUserRoleFormular.vue'
|
import ChangeUserRoleFormular from './ChangeUserRoleFormular'
|
||||||
import { setUserRole } from '../graphql/setUserRole'
|
import { setUserRole } from '../graphql/setUserRole'
|
||||||
import { toastSuccessSpy, toastErrorSpy } from '../../test/testSetup'
|
import { toastSuccessSpy, toastErrorSpy } from '../../test/testSetup'
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ConfirmRegisterMailFormular from './ConfirmRegisterMailFormular.vue'
|
import ConfirmRegisterMailFormular from './ConfirmRegisterMailFormular'
|
||||||
|
|
||||||
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
|
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ContributionLink from './ContributionLink.vue'
|
import ContributionLink from './ContributionLink'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
@ -42,14 +42,73 @@ describe('ContributionLink', () => {
|
|||||||
expect(wrapper.find('div.contribution-link').exists()).toBe(true)
|
expect(wrapper.find('div.contribution-link').exists()).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('emits toggle::collapse new Contribution', async () => {
|
it('has one contribution link in table', () => {
|
||||||
wrapper.vm.editContributionLinkData()
|
expect(wrapper.find('div.contribution-link-list').find('tbody').findAll('tr')).toHaveLength(1)
|
||||||
expect(wrapper.vm.$root.$emit('bv::toggle::collapse', 'newContribution')).toBeTruthy()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('emits toggle::collapse close Contribution-Form ', async () => {
|
it('has contribution form not visible by default', () => {
|
||||||
wrapper.vm.closeContributionForm()
|
expect(wrapper.find('#newContribution').isVisible()).toBe(false)
|
||||||
expect(wrapper.vm.$root.$emit('bv::toggle::collapse', 'newContribution')).toBeTruthy()
|
})
|
||||||
|
|
||||||
|
describe('click on create new contribution', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.find('[data-test="new-contribution-link-button"]').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows the contribution form', () => {
|
||||||
|
expect(wrapper.find('#newContribution').isVisible()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('click on create new contribution again', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.find('[data-test="new-contribution-link-button"]').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('closes the contribution form', () => {
|
||||||
|
expect(wrapper.find('#newContribution').isVisible()).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('click on close button', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.find('button.btn-secondary').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('closes the contribution form', () => {
|
||||||
|
expect(wrapper.find('#newContribution').isVisible()).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('edit contribution link', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper
|
||||||
|
.find('div.contribution-link-list')
|
||||||
|
.find('tbody')
|
||||||
|
.findAll('tr')
|
||||||
|
.at(0)
|
||||||
|
.findAll('button')
|
||||||
|
.at(1)
|
||||||
|
.trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows the contribution form', () => {
|
||||||
|
expect(wrapper.find('#newContribution').isVisible()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not show the new contribution button', () => {
|
||||||
|
expect(wrapper.find('[data-test="new-contribution-link-button"]').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('click on close button', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.find('button.btn-secondary').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('closes the contribution form', () => {
|
||||||
|
expect(wrapper.find('#newContribution').isVisible()).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -10,8 +10,9 @@
|
|||||||
>
|
>
|
||||||
<b-button
|
<b-button
|
||||||
v-if="!editContributionLink"
|
v-if="!editContributionLink"
|
||||||
v-b-toggle.newContribution
|
@click="visible = !visible"
|
||||||
class="my-3 d-flex justify-content-left"
|
class="my-3 d-flex justify-content-left"
|
||||||
|
data-test="new-contribution-link-button"
|
||||||
>
|
>
|
||||||
{{ $t('math.plus') }} {{ $t('contributionLink.newContributionLink') }}
|
{{ $t('math.plus') }} {{ $t('contributionLink.newContributionLink') }}
|
||||||
</b-button>
|
</b-button>
|
||||||
@ -42,8 +43,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import ContributionLinkForm from '../ContributionLink/ContributionLinkForm.vue'
|
import ContributionLinkForm from '../ContributionLink/ContributionLinkForm'
|
||||||
import ContributionLinkList from '../ContributionLink/ContributionLinkList.vue'
|
import ContributionLinkList from '../ContributionLink/ContributionLinkList'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ContributionLink',
|
name: 'ContributionLink',
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ContributionLinkForm from './ContributionLinkForm.vue'
|
import ContributionLinkForm from './ContributionLinkForm'
|
||||||
import { toastErrorSpy, toastSuccessSpy } from '../../../test/testSetup'
|
import { toastErrorSpy, toastSuccessSpy } from '../../../test/testSetup'
|
||||||
import { createContributionLink } from '@/graphql/createContributionLink.js'
|
import { createContributionLink } from '@/graphql/createContributionLink.js'
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ContributionLinkList from './ContributionLinkList.vue'
|
import ContributionLinkList from './ContributionLinkList'
|
||||||
import { toastSuccessSpy, toastErrorSpy } from '../../../test/testSetup'
|
import { toastSuccessSpy, toastErrorSpy } from '../../../test/testSetup'
|
||||||
// import { deleteContributionLink } from '../graphql/deleteContributionLink'
|
// import { deleteContributionLink } from '../graphql/deleteContributionLink'
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { deleteContributionLink } from '@/graphql/deleteContributionLink.js'
|
import { deleteContributionLink } from '@/graphql/deleteContributionLink.js'
|
||||||
import FigureQrCode from '../FigureQrCode.vue'
|
import FigureQrCode from '../FigureQrCode'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ContributionLinkList',
|
name: 'ContributionLinkList',
|
||||||
@ -70,8 +70,6 @@ export default {
|
|||||||
formatter: (value, key, item) => {
|
formatter: (value, key, item) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
return this.$d(new Date(value))
|
return this.$d(new Date(value))
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -81,8 +79,6 @@ export default {
|
|||||||
formatter: (value, key, item) => {
|
formatter: (value, key, item) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
return this.$d(new Date(value))
|
return this.$d(new Date(value))
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ContributionMessagesFormular from './ContributionMessagesFormular.vue'
|
import ContributionMessagesFormular from './ContributionMessagesFormular'
|
||||||
import { toastErrorSpy, toastSuccessSpy } from '../../../test/testSetup'
|
import { toastErrorSpy, toastSuccessSpy } from '../../../test/testSetup'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ContributionMessagesList from './ContributionMessagesList.vue'
|
import ContributionMessagesList from './ContributionMessagesList'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
|||||||
@ -15,8 +15,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import ContributionMessagesListItem from './slots/ContributionMessagesListItem.vue'
|
import ContributionMessagesListItem from './slots/ContributionMessagesListItem'
|
||||||
import ContributionMessagesFormular from '../ContributionMessages/ContributionMessagesFormular.vue'
|
import ContributionMessagesFormular from '../ContributionMessages/ContributionMessagesFormular'
|
||||||
import { listContributionMessages } from '../../graphql/listContributionMessages.js'
|
import { listContributionMessages } from '../../graphql/listContributionMessages.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ContributionMessagesListItem from './ContributionMessagesListItem.vue'
|
import ContributionMessagesListItem from './ContributionMessagesListItem'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import ParseMessage from '@/components/ContributionMessages/ParseMessage.vue'
|
import ParseMessage from '@/components/ContributionMessages/ParseMessage'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ContributionMessagesListItem',
|
name: 'ContributionMessagesListItem',
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import CreationFormular from './CreationFormular.vue'
|
import CreationFormular from './CreationFormular'
|
||||||
import { adminCreateContribution } from '../graphql/adminCreateContribution'
|
import { adminCreateContribution } from '../graphql/adminCreateContribution'
|
||||||
import { adminCreateContributions } from '../graphql/adminCreateContributions'
|
|
||||||
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
|
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
@ -328,122 +327,6 @@ describe('CreationFormular', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('mass creation with success', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
apolloMutateMock.mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
adminCreateContributions: {
|
|
||||||
success: true,
|
|
||||||
successfulContribution: ['bob@baumeister.de', 'bibi@bloxberg.de'],
|
|
||||||
failedContribution: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await wrapper.setProps({
|
|
||||||
type: 'massCreation',
|
|
||||||
creation: [200, 400, 600],
|
|
||||||
items: [{ email: 'bob@baumeister.de' }, { email: 'bibi@bloxberg.de' }],
|
|
||||||
})
|
|
||||||
await wrapper.findAll('input[type="radio"]').at(1).setChecked()
|
|
||||||
await wrapper.find('textarea').setValue('Test mass create coins')
|
|
||||||
await wrapper.find('input[type="number"]').setValue(200)
|
|
||||||
await wrapper.find('.test-submit').trigger('click')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('calls the API', () => {
|
|
||||||
expect(apolloMutateMock).toBeCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
mutation: adminCreateContributions,
|
|
||||||
variables: {
|
|
||||||
pendingCreations: [
|
|
||||||
{
|
|
||||||
email: 'bob@baumeister.de',
|
|
||||||
creationDate: getCreationDate(1),
|
|
||||||
amount: 200,
|
|
||||||
memo: 'Test mass create coins',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
email: 'bibi@bloxberg.de',
|
|
||||||
creationDate: getCreationDate(1),
|
|
||||||
amount: 200,
|
|
||||||
memo: 'Test mass create coins',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('updates open creations in store', () => {
|
|
||||||
expect(stateCommitMock).toBeCalledWith('openCreationsPlus', 2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('emits remove-all-bookmark', () => {
|
|
||||||
expect(wrapper.emitted('remove-all-bookmark')).toBeTruthy()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('mass creation with success but all failed', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
apolloMutateMock.mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
adminCreateContributions: {
|
|
||||||
success: true,
|
|
||||||
successfulContribution: [],
|
|
||||||
failedContribution: ['bob@baumeister.de', 'bibi@bloxberg.de'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await wrapper.setProps({
|
|
||||||
type: 'massCreation',
|
|
||||||
creation: [200, 400, 600],
|
|
||||||
items: [{ email: 'bob@baumeister.de' }, { email: 'bibi@bloxberg.de' }],
|
|
||||||
})
|
|
||||||
await wrapper.findAll('input[type="radio"]').at(1).setChecked()
|
|
||||||
await wrapper.find('textarea').setValue('Test mass create coins')
|
|
||||||
await wrapper.find('input[type="number"]').setValue(200)
|
|
||||||
await wrapper.find('.test-submit').trigger('click')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('updates open creations in store', () => {
|
|
||||||
expect(stateCommitMock).toBeCalledWith('openCreationsPlus', 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('emits remove all bookmarks', () => {
|
|
||||||
expect(wrapper.emitted('remove-all-bookmark')).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('emits toast failed creations with two emails', () => {
|
|
||||||
expect(wrapper.emitted('toast-failed-creations')).toEqual([
|
|
||||||
[['bob@baumeister.de', 'bibi@bloxberg.de']],
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('mass creation with error', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
apolloMutateMock.mockRejectedValue({
|
|
||||||
message: 'Oh no!',
|
|
||||||
})
|
|
||||||
await wrapper.setProps({
|
|
||||||
type: 'massCreation',
|
|
||||||
creation: [200, 400, 600],
|
|
||||||
items: [{ email: 'bob@baumeister.de' }, { email: 'bibi@bloxberg.de' }],
|
|
||||||
})
|
|
||||||
await wrapper.findAll('input[type="radio"]').at(1).setChecked()
|
|
||||||
await wrapper.find('textarea').setValue('Test mass create coins')
|
|
||||||
await wrapper.find('input[type="number"]').setValue(200)
|
|
||||||
await wrapper.find('.test-submit').trigger('click')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('toasts an error message', () => {
|
|
||||||
expect(toastErrorSpy).toBeCalledWith('Oh no!')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -86,16 +86,11 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { adminCreateContribution } from '../graphql/adminCreateContribution'
|
import { adminCreateContribution } from '../graphql/adminCreateContribution'
|
||||||
import { adminCreateContributions } from '../graphql/adminCreateContributions'
|
|
||||||
import { creationMonths } from '../mixins/creationMonths'
|
import { creationMonths } from '../mixins/creationMonths'
|
||||||
export default {
|
export default {
|
||||||
name: 'CreationFormular',
|
name: 'CreationFormular',
|
||||||
mixins: [creationMonths],
|
mixins: [creationMonths],
|
||||||
props: {
|
props: {
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
pagetype: {
|
pagetype: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@ -140,78 +135,38 @@ export default {
|
|||||||
updateRadioSelected(name) {
|
updateRadioSelected(name) {
|
||||||
// do we want to reset the memo everytime the month changes?
|
// do we want to reset the memo everytime the month changes?
|
||||||
this.text = this.$t('creation_form.creation_for') + ' ' + name.short + ' ' + name.year
|
this.text = this.$t('creation_form.creation_for') + ' ' + name.short + ' ' + name.year
|
||||||
if (this.type === 'singleCreation') {
|
this.rangeMin = 0
|
||||||
this.rangeMin = 0
|
this.rangeMax = name.creation
|
||||||
this.rangeMax = name.creation
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
submitCreation() {
|
submitCreation() {
|
||||||
let submitObj = []
|
this.$apollo
|
||||||
if (this.type === 'massCreation') {
|
.mutate({
|
||||||
this.items.forEach((item) => {
|
mutation: adminCreateContribution,
|
||||||
submitObj.push({
|
variables: {
|
||||||
email: item.email,
|
email: this.item.email,
|
||||||
creationDate: this.selected.date,
|
creationDate: this.selected.date,
|
||||||
amount: Number(this.value),
|
amount: Number(this.value),
|
||||||
memo: this.text,
|
memo: this.text,
|
||||||
})
|
},
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
this.$emit('update-user-data', this.item, result.data.adminCreateContribution)
|
||||||
|
this.$store.commit('openCreationsPlus', 1)
|
||||||
|
this.toastSuccess(
|
||||||
|
this.$t('creation_form.toasted', {
|
||||||
|
value: this.value,
|
||||||
|
email: this.item.email,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
// what is this? Tests says that this.text is not reseted
|
||||||
|
this.$refs.creationForm.reset()
|
||||||
|
this.value = 0
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.toastError(error.message)
|
||||||
|
this.$refs.creationForm.reset()
|
||||||
|
this.value = 0
|
||||||
})
|
})
|
||||||
this.$apollo
|
|
||||||
.mutate({
|
|
||||||
mutation: adminCreateContributions,
|
|
||||||
variables: {
|
|
||||||
pendingCreations: submitObj,
|
|
||||||
},
|
|
||||||
fetchPolicy: 'no-cache',
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
const failedContributions = []
|
|
||||||
this.$store.commit(
|
|
||||||
'openCreationsPlus',
|
|
||||||
result.data.adminCreateContributions.successfulContribution.length,
|
|
||||||
)
|
|
||||||
if (result.data.adminCreateContributions.failedContribution.length > 0) {
|
|
||||||
result.data.adminCreateContributions.failedContribution.forEach((email) => {
|
|
||||||
failedContributions.push(email)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.$emit('remove-all-bookmark')
|
|
||||||
this.$emit('toast-failed-creations', failedContributions)
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.toastError(error.message)
|
|
||||||
})
|
|
||||||
} else if (this.type === 'singleCreation') {
|
|
||||||
submitObj = {
|
|
||||||
email: this.item.email,
|
|
||||||
creationDate: this.selected.date,
|
|
||||||
amount: Number(this.value),
|
|
||||||
memo: this.text,
|
|
||||||
}
|
|
||||||
this.$apollo
|
|
||||||
.mutate({
|
|
||||||
mutation: adminCreateContribution,
|
|
||||||
variables: submitObj,
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
this.$emit('update-user-data', this.item, result.data.adminCreateContribution)
|
|
||||||
this.$store.commit('openCreationsPlus', 1)
|
|
||||||
this.toastSuccess(
|
|
||||||
this.$t('creation_form.toasted', {
|
|
||||||
value: this.value,
|
|
||||||
email: this.item.email,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
// what is this? Tests says that this.text is not reseted
|
|
||||||
this.$refs.creationForm.reset()
|
|
||||||
this.value = 0
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.toastError(error.message)
|
|
||||||
this.$refs.creationForm.reset()
|
|
||||||
this.value = 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import CreationTransactionList from './CreationTransactionList.vue'
|
import CreationTransactionList from './CreationTransactionList'
|
||||||
import { toastErrorSpy } from '../../test/testSetup'
|
import { toastErrorSpy } from '../../test/testSetup'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
@ -88,5 +88,16 @@ describe('CreationTransactionList', () => {
|
|||||||
expect(toastErrorSpy).toBeCalledWith('OUCH!')
|
expect(toastErrorSpy).toBeCalledWith('OUCH!')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('watch currentPage', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setData({ currentPage: 2 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns the string in normal order if reversed property is not true', () => {
|
||||||
|
expect(wrapper.vm.currentPage).toBe(2)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import DeletedUserFormular from './DeletedUserFormular.vue'
|
import DeletedUserFormular from './DeletedUserFormular'
|
||||||
import { deleteUser } from '../graphql/deleteUser'
|
import { deleteUser } from '../graphql/deleteUser'
|
||||||
import { unDeleteUser } from '../graphql/unDeleteUser'
|
import { unDeleteUser } from '../graphql/unDeleteUser'
|
||||||
import { toastErrorSpy } from '../../test/testSetup'
|
import { toastErrorSpy } from '../../test/testSetup'
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import EditCreationFormular from './EditCreationFormular.vue'
|
import EditCreationFormular from './EditCreationFormular'
|
||||||
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
|
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import FigureQrCode from './FigureQrCode.vue'
|
import FigureQrCode from './FigureQrCode'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import NavBar from './NavBar.vue'
|
import NavBar from './NavBar'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
@ -46,43 +46,42 @@ describe('NavBar', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('Navbar Menu', () => {
|
describe('Navbar Menu', () => {
|
||||||
it('has a link to overview', () => {
|
|
||||||
expect(wrapper.findAll('.nav-item').at(0).find('a').attributes('href')).toBe('/')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a link to /user', () => {
|
it('has a link to /user', () => {
|
||||||
expect(wrapper.findAll('.nav-item').at(1).find('a').attributes('href')).toBe('/user')
|
expect(wrapper.findAll('.nav-item').at(0).find('a').attributes('href')).toBe('/user')
|
||||||
})
|
|
||||||
|
|
||||||
it('has a link to /creation', () => {
|
|
||||||
expect(wrapper.findAll('.nav-item').at(2).find('a').attributes('href')).toBe('/creation')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a link to /creation-confirm', () => {
|
it('has a link to /creation-confirm', () => {
|
||||||
expect(wrapper.findAll('.nav-item').at(3).find('a').attributes('href')).toBe(
|
expect(wrapper.findAll('.nav-item').at(1).find('a').attributes('href')).toBe(
|
||||||
'/creation-confirm',
|
'/creation-confirm',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a link to /contribution-links', () => {
|
it('has a link to /contribution-links', () => {
|
||||||
expect(wrapper.findAll('.nav-item').at(4).find('a').attributes('href')).toBe(
|
expect(wrapper.findAll('.nav-item').at(2).find('a').attributes('href')).toBe(
|
||||||
'/contribution-links',
|
'/contribution-links',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a link to /statistic', () => {
|
it('has a link to /statistic', () => {
|
||||||
expect(wrapper.findAll('.nav-item').at(5).find('a').attributes('href')).toBe('/statistic')
|
expect(wrapper.findAll('.nav-item').at(3).find('a').attributes('href')).toBe('/statistic')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('wallet', () => {
|
describe('wallet', () => {
|
||||||
const assignLocationSpy = jest.fn()
|
const windowLocation = window.location
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await wrapper.findAll('.nav-item').at(6).find('a').trigger('click')
|
delete window.location
|
||||||
|
window.location = ''
|
||||||
|
await wrapper.findAll('.nav-item').at(4).find('a').trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('changes window location to wallet', () => {
|
afterEach(() => {
|
||||||
expect(assignLocationSpy).toBeCalledWith('valid-token')
|
delete window.location
|
||||||
|
window.location = windowLocation
|
||||||
|
})
|
||||||
|
|
||||||
|
it('changes window location to wallet', () => {
|
||||||
|
expect(window.location).toBe('http://localhost/authenticate?token=valid-token')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('dispatches logout to store', () => {
|
it('dispatches logout to store', () => {
|
||||||
@ -92,12 +91,18 @@ describe('NavBar', () => {
|
|||||||
|
|
||||||
describe('logout', () => {
|
describe('logout', () => {
|
||||||
const windowLocationMock = jest.fn()
|
const windowLocationMock = jest.fn()
|
||||||
|
const windowLocation = window.location
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
delete window.location
|
delete window.location
|
||||||
window.location = {
|
window.location = {
|
||||||
assign: windowLocationMock,
|
assign: windowLocationMock,
|
||||||
}
|
}
|
||||||
await wrapper.findAll('.nav-item').at(7).find('a').trigger('click')
|
await wrapper.findAll('.nav-item').at(5).find('a').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete window.location
|
||||||
|
window.location = windowLocation
|
||||||
})
|
})
|
||||||
|
|
||||||
it('redirects to /logout', () => {
|
it('redirects to /logout', () => {
|
||||||
|
|||||||
@ -9,15 +9,12 @@
|
|||||||
|
|
||||||
<b-collapse id="nav-collapse" is-nav>
|
<b-collapse id="nav-collapse" is-nav>
|
||||||
<b-navbar-nav>
|
<b-navbar-nav>
|
||||||
<b-nav-item to="/">{{ $t('navbar.overview') }}</b-nav-item>
|
|
||||||
<b-nav-item to="/user">{{ $t('navbar.user_search') }}</b-nav-item>
|
<b-nav-item to="/user">{{ $t('navbar.user_search') }}</b-nav-item>
|
||||||
<b-nav-item to="/creation">{{ $t('navbar.multi_creation') }}</b-nav-item>
|
<b-nav-item class="bg-color-creation p-1" to="/creation-confirm">
|
||||||
<b-nav-item
|
{{ $t('creation') }}
|
||||||
v-show="$store.state.openCreations > 0"
|
<b-badge v-show="$store.state.openCreations > 0" variant="danger">
|
||||||
class="bg-color-creation p-1"
|
{{ $store.state.openCreations }}
|
||||||
to="/creation-confirm"
|
</b-badge>
|
||||||
>
|
|
||||||
{{ $store.state.openCreations }} {{ $t('navbar.open_creation') }}
|
|
||||||
</b-nav-item>
|
</b-nav-item>
|
||||||
<b-nav-item to="/contribution-links">
|
<b-nav-item to="/contribution-links">
|
||||||
{{ $t('navbar.automaticContributions') }}
|
{{ $t('navbar.automaticContributions') }}
|
||||||
@ -57,7 +54,4 @@ export default {
|
|||||||
height: 2rem;
|
height: 2rem;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
.bg-color-creation {
|
|
||||||
background-color: #cf1010dc;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import Overlay from './Overlay.vue'
|
import Overlay from './Overlay'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,8 @@
|
|||||||
<b-row>
|
<b-row>
|
||||||
<b-col class="col-3">{{ $t('creation_for_month') }}</b-col>
|
<b-col class="col-3">{{ $t('creation_for_month') }}</b-col>
|
||||||
<b-col class="h3">
|
<b-col class="h3">
|
||||||
{{ $d(new Date(item.date), 'month') }} {{ $d(new Date(item.date), 'year') }}
|
{{ $d(new Date(item.contributionDate), 'month') }}
|
||||||
|
{{ $d(new Date(item.contributionDate), 'year') }}
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
<b-row>
|
<b-row>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import OpenCreationsTable from './OpenCreationsTable.vue'
|
import OpenCreationsTable from './OpenCreationsTable'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="open-creations-table">
|
<div class="open-creations-table">
|
||||||
<b-table-lite :items="items" :fields="fields" caption-top striped hover stacked="md">
|
<b-table-lite
|
||||||
|
:items="items"
|
||||||
|
:fields="fields"
|
||||||
|
caption-top
|
||||||
|
striped
|
||||||
|
hover
|
||||||
|
stacked="md"
|
||||||
|
:tbody-tr-class="rowClass"
|
||||||
|
>
|
||||||
|
<template #cell(state)="row">
|
||||||
|
<b-icon :icon="getStatusIcon(row.item.state)"></b-icon>
|
||||||
|
</template>
|
||||||
<template #cell(bookmark)="row">
|
<template #cell(bookmark)="row">
|
||||||
<b-button
|
<b-button
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@ -37,6 +48,16 @@
|
|||||||
</b-button>
|
</b-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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">
|
<template #cell(deny)="row">
|
||||||
<div v-if="$store.state.moderator.id !== row.item.userId">
|
<div v-if="$store.state.moderator.id !== row.item.userId">
|
||||||
<b-button
|
<b-button
|
||||||
@ -96,9 +117,17 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { toggleRowDetails } from '../../mixins/toggleRowDetails'
|
import { toggleRowDetails } from '../../mixins/toggleRowDetails'
|
||||||
import RowDetails from '../RowDetails.vue'
|
import RowDetails from '../RowDetails'
|
||||||
import EditCreationFormular from '../EditCreationFormular.vue'
|
import EditCreationFormular from '../EditCreationFormular'
|
||||||
import ContributionMessagesList from '../ContributionMessages/ContributionMessagesList.vue'
|
import ContributionMessagesList from '../ContributionMessages/ContributionMessagesList'
|
||||||
|
|
||||||
|
const iconMap = {
|
||||||
|
IN_PROGRESS: 'question-square',
|
||||||
|
PENDING: 'bell-fill',
|
||||||
|
CONFIRMED: 'check',
|
||||||
|
DELETED: 'trash',
|
||||||
|
DENIED: 'x-circle',
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'OpenCreationsTable',
|
name: 'OpenCreationsTable',
|
||||||
@ -129,6 +158,14 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
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'
|
||||||
|
},
|
||||||
updateCreationData(data) {
|
updateCreationData(data) {
|
||||||
const row = data.row
|
const row = data.row
|
||||||
this.$emit('update-contributions', data)
|
this.$emit('update-contributions', data)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import SearchUserTable from './SearchUserTable.vue'
|
import SearchUserTable from './SearchUserTable'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
|||||||
@ -53,7 +53,6 @@
|
|||||||
<b-tab :title="$t('creation')" active :disabled="row.item.deletedAt !== null">
|
<b-tab :title="$t('creation')" active :disabled="row.item.deletedAt !== null">
|
||||||
<creation-formular
|
<creation-formular
|
||||||
v-if="!row.item.deletedAt"
|
v-if="!row.item.deletedAt"
|
||||||
type="singleCreation"
|
|
||||||
pagetype="singleCreation"
|
pagetype="singleCreation"
|
||||||
:creation="row.item.creation"
|
:creation="row.item.creation"
|
||||||
:item="row.item"
|
:item="row.item"
|
||||||
@ -92,12 +91,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import CreationFormular from '../CreationFormular.vue'
|
import CreationFormular from '../CreationFormular'
|
||||||
import ConfirmRegisterMailFormular from '../ConfirmRegisterMailFormular.vue'
|
import ConfirmRegisterMailFormular from '../ConfirmRegisterMailFormular'
|
||||||
import CreationTransactionList from '../CreationTransactionList.vue'
|
import CreationTransactionList from '../CreationTransactionList'
|
||||||
import TransactionLinkList from '../TransactionLinkList.vue'
|
import TransactionLinkList from '../TransactionLinkList'
|
||||||
import ChangeUserRoleFormular from '../ChangeUserRoleFormular.vue'
|
import ChangeUserRoleFormular from '../ChangeUserRoleFormular'
|
||||||
import DeletedUserFormular from '../DeletedUserFormular.vue'
|
import DeletedUserFormular from '../DeletedUserFormular'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SearchUserTable',
|
name: 'SearchUserTable',
|
||||||
|
|||||||
@ -1,35 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="component-select-users-table">
|
|
||||||
<b-table-lite :items="items" :fields="fields" caption-top striped hover stacked="md">
|
|
||||||
<template #cell(bookmark)="row">
|
|
||||||
<div>
|
|
||||||
<b-button
|
|
||||||
v-if="row.item.emailChecked"
|
|
||||||
variant="warning"
|
|
||||||
size="md"
|
|
||||||
@click="$emit('push-item', row.item)"
|
|
||||||
class="mr-2"
|
|
||||||
>
|
|
||||||
<b-icon icon="plus" variant="success"></b-icon>
|
|
||||||
</b-button>
|
|
||||||
<div v-else>{{ $t('e_mail') }}{{ $t('math.exclaim') }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</b-table-lite>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'SelectUsersTable',
|
|
||||||
props: {
|
|
||||||
items: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
fields: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="component-selected-users-table">
|
|
||||||
<b-table-lite :items="items" :fields="fields" caption-top striped hover stacked="md">
|
|
||||||
<template #cell(bookmark)="row">
|
|
||||||
<b-button variant="danger" size="md" @click="$emit('remove-item', row.item)" class="mr-2">
|
|
||||||
<b-icon icon="x" variant="light"></b-icon>
|
|
||||||
</b-button>
|
|
||||||
</template>
|
|
||||||
</b-table-lite>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'SelectedUsersTable',
|
|
||||||
props: {
|
|
||||||
items: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
fields: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import StatisticTable from './StatisticTable.vue'
|
import StatisticTable from './StatisticTable'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import TransactionLinkList from './TransactionLinkList.vue'
|
import TransactionLinkList from './TransactionLinkList'
|
||||||
import { listTransactionLinksAdmin } from '../graphql/listTransactionLinksAdmin.js'
|
import { listTransactionLinksAdmin } from '../graphql/listTransactionLinksAdmin.js'
|
||||||
import { toastErrorSpy } from '../../test/testSetup'
|
import { toastErrorSpy } from '../../test/testSetup'
|
||||||
|
|
||||||
@ -9,8 +9,8 @@ const apolloQueryMock = jest.fn()
|
|||||||
apolloQueryMock.mockResolvedValue({
|
apolloQueryMock.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
listTransactionLinksAdmin: {
|
listTransactionLinksAdmin: {
|
||||||
linkCount: 8,
|
count: 8,
|
||||||
linkList: [
|
links: [
|
||||||
{
|
{
|
||||||
amount: '19.99',
|
amount: '19.99',
|
||||||
code: '62ef8236ace7217fbd066c5a',
|
code: '62ef8236ace7217fbd066c5a',
|
||||||
|
|||||||
@ -42,8 +42,8 @@ export default {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
this.rows = result.data.listTransactionLinksAdmin.linkCount
|
this.rows = result.data.listTransactionLinksAdmin.count
|
||||||
this.items = result.data.listTransactionLinksAdmin.linkList
|
this.items = result.data.listTransactionLinksAdmin.links
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.toastError(error.message)
|
this.toastError(error.message)
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
import gql from 'graphql-tag'
|
|
||||||
|
|
||||||
export const adminCreateContributions = gql`
|
|
||||||
mutation ($pendingCreations: [AdminCreateContributionArgs!]!) {
|
|
||||||
adminCreateContributions(pendingCreations: $pendingCreations) {
|
|
||||||
success
|
|
||||||
successfulContribution
|
|
||||||
failedContribution
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
34
admin/src/graphql/listAllContributions.js
Normal file
34
admin/src/graphql/listAllContributions.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
export const listAllContributions = gql`
|
||||||
|
query (
|
||||||
|
$currentPage: Int = 1
|
||||||
|
$pageSize: Int = 25
|
||||||
|
$order: Order = DESC
|
||||||
|
$statusFilter: [ContributionStatus!]
|
||||||
|
) {
|
||||||
|
listAllContributions(
|
||||||
|
currentPage: $currentPage
|
||||||
|
pageSize: $pageSize
|
||||||
|
order: $order
|
||||||
|
statusFilter: $statusFilter
|
||||||
|
) {
|
||||||
|
contributionCount
|
||||||
|
contributionList {
|
||||||
|
id
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
amount
|
||||||
|
memo
|
||||||
|
createdAt
|
||||||
|
contributionDate
|
||||||
|
confirmedAt
|
||||||
|
confirmedBy
|
||||||
|
state
|
||||||
|
messagesCount
|
||||||
|
deniedAt
|
||||||
|
deniedBy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
@ -8,8 +8,8 @@ export const listTransactionLinksAdmin = gql`
|
|||||||
userId: $userId
|
userId: $userId
|
||||||
filters: { withRedeemed: true, withExpired: true, withDeleted: true }
|
filters: { withRedeemed: true, withExpired: true, withDeleted: true }
|
||||||
) {
|
) {
|
||||||
linkCount
|
count
|
||||||
linkList {
|
links {
|
||||||
id
|
id
|
||||||
amount
|
amount
|
||||||
holdAvailableAmount
|
holdAvailableAmount
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
import gql from 'graphql-tag'
|
|
||||||
|
|
||||||
export const listUnconfirmedContributions = gql`
|
|
||||||
query {
|
|
||||||
listUnconfirmedContributions {
|
|
||||||
id
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
userId
|
|
||||||
email
|
|
||||||
amount
|
|
||||||
memo
|
|
||||||
date
|
|
||||||
moderator
|
|
||||||
creation
|
|
||||||
state
|
|
||||||
messageCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
@ -7,8 +7,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import NavBar from '@/components/NavBar.vue'
|
import NavBar from '@/components/NavBar'
|
||||||
import ContentFooter from '@/components/ContentFooter.vue'
|
import ContentFooter from '@/components/ContentFooter'
|
||||||
export default {
|
export default {
|
||||||
name: 'defaultLayout',
|
name: 'defaultLayout',
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"all_emails": "Alle Nutzer",
|
"all_emails": "Alle Nutzer",
|
||||||
"back": "zurück",
|
"back": "zurück",
|
||||||
|
"chat": "Chat",
|
||||||
"contributionLink": {
|
"contributionLink": {
|
||||||
"amount": "Betrag",
|
"amount": "Betrag",
|
||||||
"changeSaved": "Änderungen gespeichert",
|
"changeSaved": "Änderungen gespeichert",
|
||||||
@ -29,10 +30,18 @@
|
|||||||
"validFrom": "Startdatum",
|
"validFrom": "Startdatum",
|
||||||
"validTo": "Enddatum"
|
"validTo": "Enddatum"
|
||||||
},
|
},
|
||||||
|
"contributions": {
|
||||||
|
"all": "Alle",
|
||||||
|
"confirms": "Bestätigt",
|
||||||
|
"deleted": "Gelöscht",
|
||||||
|
"denied": "Abgelehnt",
|
||||||
|
"open": "Offen"
|
||||||
|
},
|
||||||
|
"created": "Geschöpft",
|
||||||
|
"createdAt": "Angelegt",
|
||||||
"creation": "Schöpfung",
|
"creation": "Schöpfung",
|
||||||
"creationList": "Schöpfungsliste",
|
"creationList": "Schöpfungsliste",
|
||||||
"creation_form": {
|
"creation_form": {
|
||||||
"creation_failed": "Ausstehende Schöpfung für {email} konnte nicht erzeugt werden.",
|
|
||||||
"creation_for": "Aktives Grundeinkommen für",
|
"creation_for": "Aktives Grundeinkommen für",
|
||||||
"enter_text": "Text eintragen",
|
"enter_text": "Text eintragen",
|
||||||
"form": "Schöpfungsformular",
|
"form": "Schöpfungsformular",
|
||||||
@ -49,7 +58,6 @@
|
|||||||
"update_creation": "Schöpfung aktualisieren"
|
"update_creation": "Schöpfung aktualisieren"
|
||||||
},
|
},
|
||||||
"creation_for_month": "Schöpfung für Monat",
|
"creation_for_month": "Schöpfung für Monat",
|
||||||
"date": "Datum",
|
|
||||||
"delete": "Löschen",
|
"delete": "Löschen",
|
||||||
"deleted": "gelöscht",
|
"deleted": "gelöscht",
|
||||||
"deleted_user": "Alle gelöschten Nutzer",
|
"deleted_user": "Alle gelöschten Nutzer",
|
||||||
@ -87,23 +95,19 @@
|
|||||||
"lastname": "Nachname",
|
"lastname": "Nachname",
|
||||||
"math": {
|
"math": {
|
||||||
"equals": "=",
|
"equals": "=",
|
||||||
"exclaim": "!",
|
|
||||||
"pipe": "|",
|
"pipe": "|",
|
||||||
"plus": "+"
|
"plus": "+"
|
||||||
},
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"request": "Die Anfrage wurde gesendet."
|
"request": "Die Anfrage wurde gesendet."
|
||||||
},
|
},
|
||||||
|
"mod": "Mod",
|
||||||
"moderator": "Moderator",
|
"moderator": "Moderator",
|
||||||
"multiple_creation_text": "Bitte wähle ein oder mehrere Mitglieder aus für die du Schöpfen möchtest.",
|
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"navbar": {
|
"navbar": {
|
||||||
"automaticContributions": "Automatische Beiträge",
|
"automaticContributions": "Automatische Beiträge",
|
||||||
"logout": "Abmelden",
|
"logout": "Abmelden",
|
||||||
"multi_creation": "Mehrfachschöpfung",
|
|
||||||
"my-account": "Mein Konto",
|
"my-account": "Mein Konto",
|
||||||
"open_creation": "Offene Schöpfungen",
|
|
||||||
"overview": "Übersicht",
|
|
||||||
"statistic": "Statistik",
|
"statistic": "Statistik",
|
||||||
"user_search": "Nutzersuche"
|
"user_search": "Nutzersuche"
|
||||||
},
|
},
|
||||||
@ -132,9 +136,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redeemed": "eingelöst",
|
"redeemed": "eingelöst",
|
||||||
"remove": "Entfernen",
|
|
||||||
"removeNotSelf": "Als Admin/Moderator kannst du dich nicht selber löschen.",
|
"removeNotSelf": "Als Admin/Moderator kannst du dich nicht selber löschen.",
|
||||||
"remove_all": "alle Nutzer entfernen",
|
|
||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
"statistic": {
|
"statistic": {
|
||||||
"activeUsers": "Aktive Mitglieder",
|
"activeUsers": "Aktive Mitglieder",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"all_emails": "All users",
|
"all_emails": "All users",
|
||||||
"back": "back",
|
"back": "back",
|
||||||
|
"chat": "Chat",
|
||||||
"contributionLink": {
|
"contributionLink": {
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
"changeSaved": "Changes saved",
|
"changeSaved": "Changes saved",
|
||||||
@ -29,10 +30,18 @@
|
|||||||
"validFrom": "Start-date",
|
"validFrom": "Start-date",
|
||||||
"validTo": "End-Date"
|
"validTo": "End-Date"
|
||||||
},
|
},
|
||||||
|
"contributions": {
|
||||||
|
"all": "All",
|
||||||
|
"confirms": "Confirmed",
|
||||||
|
"deleted": "Deleted",
|
||||||
|
"denied": "Denied",
|
||||||
|
"open": "Open"
|
||||||
|
},
|
||||||
|
"created": "Confirmed",
|
||||||
|
"createdAt": "Created",
|
||||||
"creation": "Creation",
|
"creation": "Creation",
|
||||||
"creationList": "Creation list",
|
"creationList": "Creation list",
|
||||||
"creation_form": {
|
"creation_form": {
|
||||||
"creation_failed": "Could not create pending creation for {email}",
|
|
||||||
"creation_for": "Active Basic Income for",
|
"creation_for": "Active Basic Income for",
|
||||||
"enter_text": "Enter text",
|
"enter_text": "Enter text",
|
||||||
"form": "Creation form",
|
"form": "Creation form",
|
||||||
@ -49,7 +58,6 @@
|
|||||||
"update_creation": "Creation update"
|
"update_creation": "Creation update"
|
||||||
},
|
},
|
||||||
"creation_for_month": "Creation for month",
|
"creation_for_month": "Creation for month",
|
||||||
"date": "Date",
|
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"deleted": "deleted",
|
"deleted": "deleted",
|
||||||
"deleted_user": "All deleted user",
|
"deleted_user": "All deleted user",
|
||||||
@ -87,23 +95,19 @@
|
|||||||
"lastname": "Lastname",
|
"lastname": "Lastname",
|
||||||
"math": {
|
"math": {
|
||||||
"equals": "=",
|
"equals": "=",
|
||||||
"exclaim": "!",
|
|
||||||
"pipe": "|",
|
"pipe": "|",
|
||||||
"plus": "+"
|
"plus": "+"
|
||||||
},
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"request": "Request has been sent."
|
"request": "Request has been sent."
|
||||||
},
|
},
|
||||||
|
"mod": "Mod",
|
||||||
"moderator": "Moderator",
|
"moderator": "Moderator",
|
||||||
"multiple_creation_text": "Please select one or more members for which you would like to perform creations.",
|
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"navbar": {
|
"navbar": {
|
||||||
"automaticContributions": "Automatic Contributions",
|
"automaticContributions": "Automatic Contributions",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"multi_creation": "Multiple creation",
|
|
||||||
"my-account": "My Account",
|
"my-account": "My Account",
|
||||||
"open_creation": "Open creations",
|
|
||||||
"overview": "Overview",
|
|
||||||
"statistic": "Statistic",
|
"statistic": "Statistic",
|
||||||
"user_search": "User search"
|
"user_search": "User search"
|
||||||
},
|
},
|
||||||
@ -132,9 +136,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redeemed": "redeemed",
|
"redeemed": "redeemed",
|
||||||
"remove": "Remove",
|
|
||||||
"removeNotSelf": "As an admin/moderator, you cannot delete yourself.",
|
"removeNotSelf": "As an admin/moderator, you cannot delete yourself.",
|
||||||
"remove_all": "Remove all users",
|
|
||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
"statistic": {
|
"statistic": {
|
||||||
"activeUsers": "Active members",
|
"activeUsers": "Active members",
|
||||||
|
|||||||
18
admin/src/locales/index.test.js
Normal file
18
admin/src/locales/index.test.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import locales from './index.js'
|
||||||
|
|
||||||
|
describe('locales', () => {
|
||||||
|
it('should contain 2 locales', () => {
|
||||||
|
expect(locales).toHaveLength(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should contain a German locale', () => {
|
||||||
|
expect(locales).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: 'Deutsch',
|
||||||
|
code: 'de',
|
||||||
|
iso: 'de-DE',
|
||||||
|
enabled: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App'
|
||||||
|
|
||||||
// without this async calls are not working
|
// without this async calls are not working
|
||||||
import 'regenerator-runtime'
|
import 'regenerator-runtime'
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import CommunityStatistic from './CommunityStatistic.vue'
|
import CommunityStatistic from './CommunityStatistic'
|
||||||
import { communityStatistics } from '@/graphql/communityStatistics.js'
|
import { communityStatistics } from '@/graphql/communityStatistics.js'
|
||||||
import { toastErrorSpy } from '../../test/testSetup'
|
import { toastErrorSpy } from '../../test/testSetup'
|
||||||
import VueApollo from 'vue-apollo'
|
import VueApollo from 'vue-apollo'
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { communityStatistics } from '@/graphql/communityStatistics.js'
|
import { communityStatistics } from '@/graphql/communityStatistics.js'
|
||||||
import StatisticTable from '../components/Tables/StatisticTable.vue'
|
import StatisticTable from '../components/Tables/StatisticTable'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CommunityStatistic',
|
name: 'CommunityStatistic',
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ContributionLinks from './ContributionLinks.vue'
|
import ContributionLinks from './ContributionLinks'
|
||||||
import { listContributionLinks } from '@/graphql/listContributionLinks.js'
|
import { listContributionLinks } from '@/graphql/listContributionLinks.js'
|
||||||
|
import { toastErrorSpy } from '../../test/testSetup'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
const apolloQueryMock = jest.fn().mockResolvedValueOnce({
|
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
listContributionLinks: {
|
listContributionLinks: {
|
||||||
links: [
|
links: [
|
||||||
@ -47,12 +48,31 @@ describe('ContributionLinks', () => {
|
|||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calls listContributionLinks', () => {
|
describe('apollo returns', () => {
|
||||||
expect(apolloQueryMock).toBeCalledWith(
|
it('calls listContributionLinks', () => {
|
||||||
expect.objectContaining({
|
expect(apolloQueryMock).toBeCalledWith(
|
||||||
query: listContributionLinks,
|
expect.objectContaining({
|
||||||
}),
|
query: listContributionLinks,
|
||||||
)
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('query transaction with error', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
apolloQueryMock.mockRejectedValue({ message: 'OUCH!' })
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the API', () => {
|
||||||
|
expect(apolloQueryMock).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toast error', () => {
|
||||||
|
expect(toastErrorSpy).toBeCalledWith(
|
||||||
|
'listContributionLinks has no result, use default data',
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { listContributionLinks } from '@/graphql/listContributionLinks.js'
|
import { listContributionLinks } from '@/graphql/listContributionLinks.js'
|
||||||
import ContributionLink from '../components/ContributionLink/ContributionLink.vue'
|
import ContributionLink from '../components/ContributionLink/ContributionLink'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ContributionLinks',
|
name: 'ContributionLinks',
|
||||||
|
|||||||
@ -1,337 +0,0 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
|
||||||
import Creation from './Creation.vue'
|
|
||||||
import { toastErrorSpy } from '../../test/testSetup'
|
|
||||||
|
|
||||||
const localVue = global.localVue
|
|
||||||
|
|
||||||
const apolloQueryMock = jest.fn().mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
searchUsers: {
|
|
||||||
userCount: 2,
|
|
||||||
userList: [
|
|
||||||
{
|
|
||||||
userId: 1,
|
|
||||||
firstName: 'Bibi',
|
|
||||||
lastName: 'Bloxberg',
|
|
||||||
email: 'bibi@bloxberg.de',
|
|
||||||
creation: [200, 400, 600],
|
|
||||||
emailChecked: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
userId: 2,
|
|
||||||
firstName: 'Benjamin',
|
|
||||||
lastName: 'Blümchen',
|
|
||||||
email: 'benjamin@bluemchen.de',
|
|
||||||
creation: [800, 600, 400],
|
|
||||||
emailChecked: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const storeCommitMock = jest.fn()
|
|
||||||
|
|
||||||
const mocks = {
|
|
||||||
$t: jest.fn((t, options) => (options ? [t, options] : t)),
|
|
||||||
$d: jest.fn((d) => d),
|
|
||||||
$apollo: {
|
|
||||||
query: apolloQueryMock,
|
|
||||||
},
|
|
||||||
$store: {
|
|
||||||
commit: storeCommitMock,
|
|
||||||
state: {
|
|
||||||
userSelectedInMassCreation: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Creation', () => {
|
|
||||||
let wrapper
|
|
||||||
|
|
||||||
const Wrapper = () => {
|
|
||||||
return mount(Creation, { localVue, mocks })
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('mount', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a DIV element with the class.creation', () => {
|
|
||||||
expect(wrapper.find('div.creation').exists()).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('apollo returns user array', () => {
|
|
||||||
it('calls the searchUser query', () => {
|
|
||||||
expect(apolloQueryMock).toBeCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
variables: {
|
|
||||||
searchText: '',
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: 25,
|
|
||||||
filters: {
|
|
||||||
byActivated: true,
|
|
||||||
byDeleted: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has two rows in the left table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr')).toHaveLength(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has nwo rows in the right table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has correct data in first row ', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain('Bibi')
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
|
|
||||||
'Bloxberg',
|
|
||||||
)
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
|
|
||||||
'200 | 400 | 600',
|
|
||||||
)
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
|
|
||||||
'bibi@bloxberg.de',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has correct data in second row ', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).text()).toContain(
|
|
||||||
'Benjamin',
|
|
||||||
)
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).text()).toContain(
|
|
||||||
'Blümchen',
|
|
||||||
)
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).text()).toContain(
|
|
||||||
'800 | 600 | 400',
|
|
||||||
)
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).text()).toContain(
|
|
||||||
'benjamin@bluemchen.de',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('push item', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).find('button').trigger('click')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has one item in left table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr')).toHaveLength(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has one item in right table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has the correct user in left table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
|
|
||||||
'bibi@bloxberg.de',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has the correct user in right table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(1).findAll('tbody > tr').at(0).text()).toContain(
|
|
||||||
'benjamin@bluemchen.de',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('updates userSelectedInMassCreation in store', () => {
|
|
||||||
expect(storeCommitMock).toBeCalledWith('setUserSelectedInMassCreation', [
|
|
||||||
{
|
|
||||||
userId: 2,
|
|
||||||
firstName: 'Benjamin',
|
|
||||||
lastName: 'Blümchen',
|
|
||||||
email: 'benjamin@bluemchen.de',
|
|
||||||
creation: [800, 600, 400],
|
|
||||||
showDetails: false,
|
|
||||||
emailChecked: true,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('remove item', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await wrapper
|
|
||||||
.findAll('table')
|
|
||||||
.at(1)
|
|
||||||
.findAll('tbody > tr')
|
|
||||||
.at(0)
|
|
||||||
.find('button')
|
|
||||||
.trigger('click')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has two items in left table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr')).toHaveLength(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has the removed user in first row', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
|
|
||||||
'benjamin@bluemchen.de',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has no items in right table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('commits empty array as userSelectedInMassCreation', () => {
|
|
||||||
expect(storeCommitMock).toBeCalledWith('setUserSelectedInMassCreation', [])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('remove all bookmarks', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
await wrapper.find('button.btn-light').trigger('click')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has no items in right table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('commits empty array to userSelectedInMassCreation', () => {
|
|
||||||
expect(storeCommitMock).toBeCalledWith('setUserSelectedInMassCreation', [])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('calls searchUsers', () => {
|
|
||||||
expect(apolloQueryMock).toBeCalled()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('store has items in userSelectedInMassCreation', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
mocks.$store.state.userSelectedInMassCreation = [
|
|
||||||
{
|
|
||||||
userId: 2,
|
|
||||||
firstName: 'Benjamin',
|
|
||||||
lastName: 'Blümchen',
|
|
||||||
email: 'benjamin@bluemchen.de',
|
|
||||||
creation: [800, 600, 400],
|
|
||||||
showDetails: false,
|
|
||||||
emailChecked: true,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has one item in left table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr')).toHaveLength(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has one item in right table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has the stored user in second row', () => {
|
|
||||||
expect(wrapper.findAll('table').at(1).findAll('tbody > tr').at(0).text()).toContain(
|
|
||||||
'benjamin@bluemchen.de',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('failed creations', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await wrapper
|
|
||||||
.findComponent({ name: 'CreationFormular' })
|
|
||||||
.vm.$emit('toast-failed-creations', ['bibi@bloxberg.de', 'benjamin@bluemchen.de'])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('toasts two error messages', () => {
|
|
||||||
expect(toastErrorSpy).toBeCalledWith([
|
|
||||||
'creation_form.creation_failed',
|
|
||||||
{ email: 'bibi@bloxberg.de' },
|
|
||||||
])
|
|
||||||
expect(toastErrorSpy).toBeCalledWith([
|
|
||||||
'creation_form.creation_failed',
|
|
||||||
{ email: 'benjamin@bluemchen.de' },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('watchers', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('search criteria', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await wrapper.setData({ criteria: 'XX' })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('calls API when criteria changes', async () => {
|
|
||||||
expect(apolloQueryMock).toBeCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
variables: {
|
|
||||||
searchText: 'XX',
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: 25,
|
|
||||||
filters: {
|
|
||||||
byActivated: true,
|
|
||||||
byDeleted: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('reset search criteria', () => {
|
|
||||||
it('calls the API', async () => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
await wrapper.find('.test-click-clear-criteria').trigger('click')
|
|
||||||
expect(apolloQueryMock).toBeCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
variables: {
|
|
||||||
searchText: '',
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: 25,
|
|
||||||
filters: {
|
|
||||||
byActivated: true,
|
|
||||||
byDeleted: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('calls API when currentPage changes', async () => {
|
|
||||||
await wrapper.setData({ currentPage: 2 })
|
|
||||||
expect(apolloQueryMock).toBeCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
variables: {
|
|
||||||
searchText: '',
|
|
||||||
currentPage: 2,
|
|
||||||
pageSize: 25,
|
|
||||||
filters: {
|
|
||||||
byActivated: true,
|
|
||||||
byDeleted: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('apollo returns error', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
apolloQueryMock.mockRejectedValue({
|
|
||||||
message: 'Ouch',
|
|
||||||
})
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('toasts an error message', () => {
|
|
||||||
expect(toastErrorSpy).toBeCalledWith('Ouch')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,200 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="creation">
|
|
||||||
<b-row>
|
|
||||||
<b-col cols="12" lg="6">
|
|
||||||
<label>{{ $t('user_search') }}</label>
|
|
||||||
<b-input-group>
|
|
||||||
<b-form-input
|
|
||||||
type="text"
|
|
||||||
class="test-input-criteria"
|
|
||||||
v-model="criteria"
|
|
||||||
:placeholder="$t('user_search')"
|
|
||||||
></b-form-input>
|
|
||||||
|
|
||||||
<b-input-group-append class="test-click-clear-criteria" @click="criteria = ''">
|
|
||||||
<b-input-group-text class="pointer">
|
|
||||||
<b-icon icon="x" />
|
|
||||||
</b-input-group-text>
|
|
||||||
</b-input-group-append>
|
|
||||||
</b-input-group>
|
|
||||||
<select-users-table
|
|
||||||
v-if="itemsList.length > 0"
|
|
||||||
:items="itemsList"
|
|
||||||
:fields="Searchfields"
|
|
||||||
@push-item="pushItem"
|
|
||||||
/>
|
|
||||||
<b-pagination
|
|
||||||
pills
|
|
||||||
v-model="currentPage"
|
|
||||||
per-page="perPage"
|
|
||||||
:total-rows="rows"
|
|
||||||
align="center"
|
|
||||||
:hide-ellipsis="true"
|
|
||||||
></b-pagination>
|
|
||||||
</b-col>
|
|
||||||
<b-col cols="12" lg="6" class="shadow p-3 mb-5 rounded bg-info">
|
|
||||||
<div v-show="itemsMassCreation.length > 0">
|
|
||||||
<div class="text-right pr-4 mb-1">
|
|
||||||
<b-button @click="removeAllBookmarks()" variant="light">
|
|
||||||
<b-icon icon="x" scale="2" variant="danger"></b-icon>
|
|
||||||
|
|
||||||
{{ $t('remove_all') }}
|
|
||||||
</b-button>
|
|
||||||
</div>
|
|
||||||
<selected-users-table
|
|
||||||
class="shadow p-3 mb-5 bg-white rounded"
|
|
||||||
:items="itemsMassCreation"
|
|
||||||
:fields="fields"
|
|
||||||
@remove-item="removeItem"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-if="itemsMassCreation.length === 0">
|
|
||||||
{{ $t('multiple_creation_text') }}
|
|
||||||
</div>
|
|
||||||
<creation-formular
|
|
||||||
v-else
|
|
||||||
type="massCreation"
|
|
||||||
:creation="creation"
|
|
||||||
:items="itemsMassCreation"
|
|
||||||
@remove-all-bookmark="removeAllBookmarks"
|
|
||||||
@toast-failed-creations="toastFailedCreations"
|
|
||||||
/>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import CreationFormular from '../components/CreationFormular.vue'
|
|
||||||
import SelectUsersTable from '../components/Tables/SelectUsersTable.vue'
|
|
||||||
import SelectedUsersTable from '../components/Tables/SelectedUsersTable.vue'
|
|
||||||
import { searchUsers } from '../graphql/searchUsers'
|
|
||||||
import { creationMonths } from '../mixins/creationMonths'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Creation',
|
|
||||||
mixins: [creationMonths],
|
|
||||||
components: {
|
|
||||||
CreationFormular,
|
|
||||||
SelectUsersTable,
|
|
||||||
SelectedUsersTable,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showArrays: false,
|
|
||||||
itemsList: [],
|
|
||||||
itemsMassCreation: this.$store.state.userSelectedInMassCreation,
|
|
||||||
radioSelectedMass: '',
|
|
||||||
criteria: '',
|
|
||||||
rows: 0,
|
|
||||||
currentPage: 1,
|
|
||||||
perPage: 25,
|
|
||||||
now: Date.now(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async created() {
|
|
||||||
await this.getUsers()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async getUsers() {
|
|
||||||
this.$apollo
|
|
||||||
.query({
|
|
||||||
query: searchUsers,
|
|
||||||
variables: {
|
|
||||||
searchText: this.criteria,
|
|
||||||
currentPage: this.currentPage,
|
|
||||||
pageSize: this.perPage,
|
|
||||||
filters: {
|
|
||||||
byActivated: true,
|
|
||||||
byDeleted: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fetchPolicy: 'network-only',
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
this.rows = result.data.searchUsers.userCount
|
|
||||||
this.itemsList = result.data.searchUsers.userList.map((user) => {
|
|
||||||
return {
|
|
||||||
...user,
|
|
||||||
showDetails: false,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (this.itemsMassCreation.length !== 0) {
|
|
||||||
const selectedIndices = this.itemsMassCreation.map((item) => item.userId)
|
|
||||||
this.itemsList = this.itemsList.filter((item) => !selectedIndices.includes(item.userId))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.toastError(error.message)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
pushItem(selectedItem) {
|
|
||||||
this.itemsMassCreation = [
|
|
||||||
this.itemsList.find((item) => selectedItem.userId === item.userId),
|
|
||||||
...this.itemsMassCreation,
|
|
||||||
]
|
|
||||||
this.itemsList = this.itemsList.filter((item) => selectedItem.userId !== item.userId)
|
|
||||||
this.$store.commit('setUserSelectedInMassCreation', this.itemsMassCreation)
|
|
||||||
},
|
|
||||||
removeItem(selectedItem) {
|
|
||||||
this.itemsList = [
|
|
||||||
this.itemsMassCreation.find((item) => selectedItem.userId === item.userId),
|
|
||||||
...this.itemsList,
|
|
||||||
]
|
|
||||||
this.itemsMassCreation = this.itemsMassCreation.filter(
|
|
||||||
(item) => selectedItem.userId !== item.userId,
|
|
||||||
)
|
|
||||||
this.$store.commit('setUserSelectedInMassCreation', this.itemsMassCreation)
|
|
||||||
},
|
|
||||||
removeAllBookmarks() {
|
|
||||||
this.itemsMassCreation = []
|
|
||||||
this.$store.commit('setUserSelectedInMassCreation', [])
|
|
||||||
this.getUsers()
|
|
||||||
},
|
|
||||||
toastFailedCreations(failedCreations) {
|
|
||||||
failedCreations.forEach((email) =>
|
|
||||||
this.toastError(this.$t('creation_form.creation_failed', { email })),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
Searchfields() {
|
|
||||||
return [
|
|
||||||
{ key: 'bookmark', label: 'bookmark' },
|
|
||||||
{ key: 'firstName', label: this.$t('firstname') },
|
|
||||||
{ key: 'lastName', label: this.$t('lastname') },
|
|
||||||
{
|
|
||||||
key: 'creation',
|
|
||||||
label: this.creationLabel,
|
|
||||||
formatter: (value, key, item) => {
|
|
||||||
return value.join(' | ')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ key: 'email', label: this.$t('e_mail') },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
fields() {
|
|
||||||
return [
|
|
||||||
{ key: 'email', label: this.$t('e_mail') },
|
|
||||||
{ key: 'firstName', label: this.$t('firstname') },
|
|
||||||
{ key: 'lastName', label: this.$t('lastname') },
|
|
||||||
{
|
|
||||||
key: 'creation',
|
|
||||||
label: this.creationLabel,
|
|
||||||
formatter: (value, key, item) => {
|
|
||||||
return value.join(' | ')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ key: 'bookmark', label: this.$t('remove') },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
currentPage() {
|
|
||||||
this.getUsers()
|
|
||||||
},
|
|
||||||
criteria() {
|
|
||||||
this.getUsers()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import CreationConfirm from './CreationConfirm.vue'
|
import CreationConfirm from './CreationConfirm'
|
||||||
import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
|
import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
|
||||||
import { denyContribution } from '../graphql/denyContribution'
|
import { denyContribution } from '../graphql/denyContribution'
|
||||||
import { listUnconfirmedContributions } from '../graphql/listUnconfirmedContributions'
|
import { listAllContributions } from '../graphql/listAllContributions'
|
||||||
import { confirmContribution } from '../graphql/confirmContribution'
|
import { confirmContribution } from '../graphql/confirmContribution'
|
||||||
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
|
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
|
||||||
import VueApollo from 'vue-apollo'
|
import VueApollo from 'vue-apollo'
|
||||||
@ -38,50 +38,68 @@ const mocks = {
|
|||||||
|
|
||||||
const defaultData = () => {
|
const defaultData = () => {
|
||||||
return {
|
return {
|
||||||
listUnconfirmedContributions: [
|
listAllContributions: {
|
||||||
{
|
contributionCount: 2,
|
||||||
id: 1,
|
contributionList: [
|
||||||
firstName: 'Bibi',
|
{
|
||||||
lastName: 'Bloxberg',
|
id: 1,
|
||||||
userId: 99,
|
firstName: 'Bibi',
|
||||||
email: 'bibi@bloxberg.de',
|
lastName: 'Bloxberg',
|
||||||
amount: 500,
|
userId: 99,
|
||||||
memo: 'Danke für alles',
|
email: 'bibi@bloxberg.de',
|
||||||
date: new Date(),
|
amount: 500,
|
||||||
moderator: 1,
|
memo: 'Danke für alles',
|
||||||
state: 'PENDING',
|
date: new Date(),
|
||||||
creation: [500, 500, 500],
|
moderator: 1,
|
||||||
messageCount: 0,
|
state: 'PENDING',
|
||||||
},
|
creation: [500, 500, 500],
|
||||||
{
|
messagesCount: 0,
|
||||||
id: 2,
|
deniedBy: null,
|
||||||
firstName: 'Räuber',
|
deniedAt: null,
|
||||||
lastName: 'Hotzenplotz',
|
confirmedBy: null,
|
||||||
userId: 100,
|
confirmedAt: null,
|
||||||
email: 'raeuber@hotzenplotz.de',
|
contributionDate: new Date(),
|
||||||
amount: 1000000,
|
deletedBy: null,
|
||||||
memo: 'Gut Ergattert',
|
deletedAt: null,
|
||||||
date: new Date(),
|
createdAt: new Date(),
|
||||||
moderator: 1,
|
},
|
||||||
state: 'PENDING',
|
{
|
||||||
creation: [500, 500, 500],
|
id: 2,
|
||||||
messageCount: 0,
|
firstName: 'Räuber',
|
||||||
},
|
lastName: 'Hotzenplotz',
|
||||||
],
|
userId: 100,
|
||||||
|
email: 'raeuber@hotzenplotz.de',
|
||||||
|
amount: 1000000,
|
||||||
|
memo: 'Gut Ergattert',
|
||||||
|
date: new Date(),
|
||||||
|
moderator: 1,
|
||||||
|
state: 'PENDING',
|
||||||
|
creation: [500, 500, 500],
|
||||||
|
messagesCount: 0,
|
||||||
|
deniedBy: null,
|
||||||
|
deniedAt: null,
|
||||||
|
confirmedBy: null,
|
||||||
|
confirmedAt: null,
|
||||||
|
contributionDate: new Date(),
|
||||||
|
deletedBy: null,
|
||||||
|
deletedAt: null,
|
||||||
|
createdAt: new Date(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('CreationConfirm', () => {
|
describe('CreationConfirm', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
|
|
||||||
const listUnconfirmedContributionsMock = jest.fn()
|
|
||||||
const adminDeleteContributionMock = jest.fn()
|
const adminDeleteContributionMock = jest.fn()
|
||||||
const adminDenyContributionMock = jest.fn()
|
const adminDenyContributionMock = jest.fn()
|
||||||
const confirmContributionMock = jest.fn()
|
const confirmContributionMock = jest.fn()
|
||||||
|
|
||||||
mockClient.setRequestHandler(
|
mockClient.setRequestHandler(
|
||||||
listUnconfirmedContributions,
|
listAllContributions,
|
||||||
listUnconfirmedContributionsMock
|
jest
|
||||||
|
.fn()
|
||||||
.mockRejectedValueOnce({ message: 'Ouch!' })
|
.mockRejectedValueOnce({ message: 'Ouch!' })
|
||||||
.mockResolvedValue({ data: defaultData() }),
|
.mockResolvedValue({ data: defaultData() }),
|
||||||
)
|
)
|
||||||
@ -117,6 +135,10 @@ describe('CreationConfirm', () => {
|
|||||||
it('toast an error message', () => {
|
it('toast an error message', () => {
|
||||||
expect(toastErrorSpy).toBeCalledWith('Ouch!')
|
expect(toastErrorSpy).toBeCalledWith('Ouch!')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('has statusFilter ["IN_PROGRESS", "PENDING"]', () => {
|
||||||
|
expect(wrapper.vm.statusFilter).toEqual(['IN_PROGRESS', 'PENDING'])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('server response is succes', () => {
|
describe('server response is succes', () => {
|
||||||
@ -125,17 +147,7 @@ describe('CreationConfirm', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('has two pending creations', () => {
|
it('has two pending creations', () => {
|
||||||
expect(wrapper.vm.pendingCreations).toHaveLength(2)
|
expect(wrapper.find('tbody').findAll('tr')).toHaveLength(2)
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('store', () => {
|
|
||||||
it('commits resetOpenCreations to store', () => {
|
|
||||||
expect(storeCommitMock).toBeCalledWith('resetOpenCreations')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('commits setOpenCreations to store', () => {
|
|
||||||
expect(storeCommitMock).toBeCalledWith('setOpenCreations', 2)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -259,7 +271,7 @@ describe('CreationConfirm', () => {
|
|||||||
|
|
||||||
describe('deny creation', () => {
|
describe('deny creation', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await wrapper.findAll('tr').at(1).findAll('button').at(2).trigger('click')
|
await wrapper.findAll('tr').at(1).findAll('button').at(1).trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('opens the overlay', () => {
|
it('opens the overlay', () => {
|
||||||
@ -316,5 +328,94 @@ 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()
|
||||||
|
})
|
||||||
|
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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', () => {
|
||||||
|
expect(refetchSpy).toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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({
|
||||||
|
statusFilter: ['IN_PROGRESS', 'PENDING', 'CONFIRMED', 'DENIED', 'DELETED'],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('refetches contributions', () => {
|
||||||
|
expect(refetchSpy).toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('update status', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.findComponent({ name: 'OpenCreationsTable' }).vm.$emit('update-state', 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.skip('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')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,6 +1,50 @@
|
|||||||
<!-- eslint-disable @intlify/vue-i18n/no-dynamic-keys -->
|
<!-- eslint-disable @intlify/vue-i18n/no-dynamic-keys -->
|
||||||
<template>
|
<template>
|
||||||
<div class="creation-confirm">
|
<div class="creation-confirm">
|
||||||
|
<div>
|
||||||
|
<b-tabs v-model="tabIndex" content-class="mt-3" fill>
|
||||||
|
<b-tab active :title-link-attributes="{ 'data-test': 'open' }">
|
||||||
|
<template #title>
|
||||||
|
{{ $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-tabs>
|
||||||
|
</div>
|
||||||
|
<open-creations-table
|
||||||
|
class="mt-4"
|
||||||
|
:items="items"
|
||||||
|
:fields="fields"
|
||||||
|
@show-overlay="showOverlay"
|
||||||
|
@update-state="updateStatus"
|
||||||
|
@update-contributions="$apollo.queries.AllContributions.refetch()"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<b-pagination
|
||||||
|
pills
|
||||||
|
size="lg"
|
||||||
|
v-model="currentPage"
|
||||||
|
:per-page="pageSize"
|
||||||
|
:total-rows="rows"
|
||||||
|
align="center"
|
||||||
|
:hide-ellipsis="true"
|
||||||
|
></b-pagination>
|
||||||
|
|
||||||
<div v-if="overlay" id="overlay" @dblclick="overlay = false">
|
<div v-if="overlay" id="overlay" @dblclick="overlay = false">
|
||||||
<overlay :item="item" @overlay-cancel="overlay = false">
|
<overlay :item="item" @overlay-cancel="overlay = false">
|
||||||
<template #title>
|
<template #title>
|
||||||
@ -24,24 +68,24 @@
|
|||||||
</template>
|
</template>
|
||||||
</overlay>
|
</overlay>
|
||||||
</div>
|
</div>
|
||||||
<open-creations-table
|
|
||||||
class="mt-4"
|
|
||||||
:items="pendingCreations"
|
|
||||||
:fields="fields"
|
|
||||||
@show-overlay="showOverlay"
|
|
||||||
@update-state="updateState"
|
|
||||||
@update-contributions="$apollo.queries.PendingContributions.refetch()"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import Overlay from '../components/Overlay.vue'
|
import Overlay from '../components/Overlay'
|
||||||
import OpenCreationsTable from '../components/Tables/OpenCreationsTable.vue'
|
import OpenCreationsTable from '../components/Tables/OpenCreationsTable'
|
||||||
import { listUnconfirmedContributions } from '../graphql/listUnconfirmedContributions'
|
import { listAllContributions } from '../graphql/listAllContributions'
|
||||||
import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
|
import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
|
||||||
import { confirmContribution } from '../graphql/confirmContribution'
|
import { confirmContribution } from '../graphql/confirmContribution'
|
||||||
import { denyContribution } from '../graphql/denyContribution'
|
import { denyContribution } from '../graphql/denyContribution'
|
||||||
|
|
||||||
|
const FILTER_TAB_MAP = [
|
||||||
|
['IN_PROGRESS', 'PENDING'],
|
||||||
|
['CONFIRMED'],
|
||||||
|
['DENIED'],
|
||||||
|
['DELETED'],
|
||||||
|
['IN_PROGRESS', 'PENDING', 'CONFIRMED', 'DENIED', 'DELETED'],
|
||||||
|
]
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CreationConfirm',
|
name: 'CreationConfirm',
|
||||||
components: {
|
components: {
|
||||||
@ -50,10 +94,14 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
pendingCreations: [],
|
tabIndex: 0,
|
||||||
|
items: [],
|
||||||
overlay: false,
|
overlay: false,
|
||||||
item: {},
|
item: {},
|
||||||
variant: 'confirm',
|
variant: 'confirm',
|
||||||
|
rows: 0,
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 25,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -112,7 +160,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
updatePendingCreations(id) {
|
updatePendingCreations(id) {
|
||||||
this.pendingCreations = this.pendingCreations.filter((obj) => obj.id !== id)
|
this.items = this.items.filter((obj) => obj.id !== id)
|
||||||
this.$store.commit('openCreationsMinus', 1)
|
this.$store.commit('openCreationsMinus', 1)
|
||||||
},
|
},
|
||||||
showOverlay(item, variant) {
|
showOverlay(item, variant) {
|
||||||
@ -120,38 +168,155 @@ export default {
|
|||||||
this.item = item
|
this.item = item
|
||||||
this.variant = variant
|
this.variant = variant
|
||||||
},
|
},
|
||||||
updateState(id) {
|
updateStatus(id) {
|
||||||
this.pendingCreations.find((obj) => obj.id === id).messagesCount++
|
this.items.find((obj) => obj.id === id).messagesCount++
|
||||||
this.pendingCreations.find((obj) => obj.id === id).state = 'IN_PROGRESS'
|
this.items.find((obj) => obj.id === id).state = 'IN_PROGRESS'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
statusFilter() {
|
||||||
|
this.$apollo.queries.ListAllContributions.refetch()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
fields() {
|
fields() {
|
||||||
return [
|
return [
|
||||||
{ key: 'bookmark', label: this.$t('delete') },
|
[
|
||||||
{ key: 'email', label: this.$t('e_mail') },
|
{ key: 'bookmark', label: this.$t('delete') },
|
||||||
{ key: 'firstName', label: this.$t('firstname') },
|
{ key: 'deny', label: this.$t('deny') },
|
||||||
{ key: 'lastName', label: this.$t('lastname') },
|
{ key: 'email', label: this.$t('e_mail') },
|
||||||
{
|
{ key: 'firstName', label: this.$t('firstname') },
|
||||||
key: 'amount',
|
{ key: 'lastName', label: this.$t('lastname') },
|
||||||
label: this.$t('creation'),
|
{
|
||||||
formatter: (value) => {
|
key: 'amount',
|
||||||
return value + ' GDD'
|
label: this.$t('creation'),
|
||||||
|
formatter: (value) => {
|
||||||
|
return value + ' GDD'
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{ key: 'memo', label: this.$t('text'), class: 'text-break' },
|
||||||
{ key: 'memo', label: this.$t('text'), class: 'text-break' },
|
{
|
||||||
{
|
key: 'contributionDate',
|
||||||
key: 'date',
|
label: this.$t('created'),
|
||||||
label: this.$t('date'),
|
formatter: (value) => {
|
||||||
formatter: (value) => {
|
return this.$d(new Date(value), 'short')
|
||||||
return this.$d(new Date(value), 'short')
|
},
|
||||||
},
|
},
|
||||||
},
|
{ key: 'moderator', label: this.$t('moderator') },
|
||||||
{ key: 'moderator', label: this.$t('moderator') },
|
{ key: 'editCreation', label: this.$t('edit') },
|
||||||
{ key: 'editCreation', label: this.$t('edit') },
|
{ key: 'confirm', label: this.$t('save') },
|
||||||
{ key: 'deny', label: this.$t('deny') },
|
],
|
||||||
{ key: 'confirm', label: this.$t('save') },
|
[
|
||||||
]
|
{ 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.$d(new Date(value), 'short')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'createdAt',
|
||||||
|
label: this.$t('createdAt'),
|
||||||
|
formatter: (value) => {
|
||||||
|
return this.$d(new Date(value), 'short')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'confirmedAt',
|
||||||
|
label: this.$t('contributions.confirms'),
|
||||||
|
formatter: (value) => {
|
||||||
|
return this.$d(new Date(value), 'short')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ key: 'chatCreation', label: this.$t('chat') },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ key: 'reActive', label: 'reActive' },
|
||||||
|
{ 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.$d(new Date(value), 'short')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'createdAt',
|
||||||
|
label: this.$t('createdAt'),
|
||||||
|
formatter: (value) => {
|
||||||
|
return this.$d(new Date(value), 'short')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'deniedAt',
|
||||||
|
label: this.$t('contributions.denied'),
|
||||||
|
formatter: (value) => {
|
||||||
|
return this.$d(new Date(value), 'short')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ key: 'deniedBy', label: this.$t('mod') },
|
||||||
|
{ key: 'chatCreation', label: this.$t('chat') },
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{ key: 'state', label: 'state' },
|
||||||
|
{ 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.$d(new Date(value), 'short')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'createdAt',
|
||||||
|
label: this.$t('createdAt'),
|
||||||
|
formatter: (value) => {
|
||||||
|
return this.$d(new Date(value), 'short')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'confirmedAt',
|
||||||
|
label: this.$t('contributions.confirms'),
|
||||||
|
formatter: (value) => {
|
||||||
|
return this.$d(new Date(value), 'short')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ key: 'confirmedBy', label: this.$t('mod') },
|
||||||
|
{ key: 'chatCreation', label: this.$t('chat') },
|
||||||
|
],
|
||||||
|
][this.tabIndex]
|
||||||
|
},
|
||||||
|
statusFilter() {
|
||||||
|
return FILTER_TAB_MAP[this.tabIndex]
|
||||||
},
|
},
|
||||||
overlayTitle() {
|
overlayTitle() {
|
||||||
return `overlay.${this.variant}.title`
|
return `overlay.${this.variant}.title`
|
||||||
@ -182,18 +347,21 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
PendingContributions: {
|
ListAllContributions: {
|
||||||
query() {
|
query() {
|
||||||
return listUnconfirmedContributions
|
return listAllContributions
|
||||||
},
|
},
|
||||||
variables() {
|
variables() {
|
||||||
// may be at some point we need a pagination here
|
// may be at some point we need a pagination here
|
||||||
return {}
|
return {
|
||||||
|
currentPage: this.currentPage,
|
||||||
|
pageSize: this.pageSize,
|
||||||
|
statusFilter: this.statusFilter,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
update({ listUnconfirmedContributions }) {
|
update({ listAllContributions }) {
|
||||||
this.$store.commit('resetOpenCreations')
|
this.rows = listAllContributions.contributionCount
|
||||||
this.pendingCreations = listUnconfirmedContributions
|
this.items = listAllContributions.contributionList
|
||||||
this.$store.commit('setOpenCreations', listUnconfirmedContributions.length)
|
|
||||||
},
|
},
|
||||||
error({ message }) {
|
error({ message }) {
|
||||||
this.toastError(message)
|
this.toastError(message)
|
||||||
|
|||||||
@ -1,41 +1,18 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import Overview from './Overview.vue'
|
import Overview from './Overview'
|
||||||
import { listUnconfirmedContributions } from '@/graphql/listUnconfirmedContributions.js'
|
import { listAllContributions } from '../graphql/listAllContributions'
|
||||||
|
import VueApollo from 'vue-apollo'
|
||||||
|
import { createMockClient } from 'mock-apollo-client'
|
||||||
|
import { toastErrorSpy } from '../../test/testSetup'
|
||||||
|
|
||||||
|
const mockClient = createMockClient()
|
||||||
|
const apolloProvider = new VueApollo({
|
||||||
|
defaultClient: mockClient,
|
||||||
|
})
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
const apolloQueryMock = jest
|
localVue.use(VueApollo)
|
||||||
.fn()
|
|
||||||
.mockResolvedValueOnce({
|
|
||||||
data: {
|
|
||||||
listUnconfirmedContributions: [
|
|
||||||
{
|
|
||||||
pending: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pending: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pending: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
listUnconfirmedContributions: [
|
|
||||||
{
|
|
||||||
pending: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pending: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pending: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const storeCommitMock = jest.fn()
|
const storeCommitMock = jest.fn()
|
||||||
|
|
||||||
@ -43,44 +20,114 @@ const mocks = {
|
|||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
$n: jest.fn((n) => n),
|
$n: jest.fn((n) => n),
|
||||||
$d: jest.fn((d) => d),
|
$d: jest.fn((d) => d),
|
||||||
$apollo: {
|
|
||||||
query: apolloQueryMock,
|
|
||||||
},
|
|
||||||
$store: {
|
$store: {
|
||||||
commit: storeCommitMock,
|
commit: storeCommitMock,
|
||||||
state: {
|
state: {
|
||||||
openCreations: 2,
|
openCreations: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultData = () => {
|
||||||
|
return {
|
||||||
|
listAllContributions: {
|
||||||
|
contributionCount: 2,
|
||||||
|
contributionList: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
firstName: 'Bibi',
|
||||||
|
lastName: 'Bloxberg',
|
||||||
|
userId: 99,
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
amount: 500,
|
||||||
|
memo: 'Danke für alles',
|
||||||
|
date: new Date(),
|
||||||
|
moderator: 1,
|
||||||
|
state: 'PENDING',
|
||||||
|
creation: [500, 500, 500],
|
||||||
|
messagesCount: 0,
|
||||||
|
deniedBy: null,
|
||||||
|
deniedAt: null,
|
||||||
|
confirmedBy: null,
|
||||||
|
confirmedAt: null,
|
||||||
|
contributionDate: new Date(),
|
||||||
|
deletedBy: null,
|
||||||
|
deletedAt: null,
|
||||||
|
createdAt: new Date(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
firstName: 'Räuber',
|
||||||
|
lastName: 'Hotzenplotz',
|
||||||
|
userId: 100,
|
||||||
|
email: 'raeuber@hotzenplotz.de',
|
||||||
|
amount: 1000000,
|
||||||
|
memo: 'Gut Ergattert',
|
||||||
|
date: new Date(),
|
||||||
|
moderator: 1,
|
||||||
|
state: 'PENDING',
|
||||||
|
creation: [500, 500, 500],
|
||||||
|
messagesCount: 0,
|
||||||
|
deniedBy: null,
|
||||||
|
deniedAt: null,
|
||||||
|
confirmedBy: null,
|
||||||
|
confirmedAt: null,
|
||||||
|
contributionDate: new Date(),
|
||||||
|
deletedBy: null,
|
||||||
|
deletedAt: null,
|
||||||
|
createdAt: new Date(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe('Overview', () => {
|
describe('Overview', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
|
const listAllContributionsMock = jest.fn()
|
||||||
|
|
||||||
|
mockClient.setRequestHandler(
|
||||||
|
listAllContributions,
|
||||||
|
listAllContributionsMock
|
||||||
|
.mockRejectedValueOnce({ message: 'Ouch!' })
|
||||||
|
.mockResolvedValue({ data: defaultData() }),
|
||||||
|
)
|
||||||
|
|
||||||
const Wrapper = () => {
|
const Wrapper = () => {
|
||||||
return mount(Overview, { localVue, mocks })
|
return mount(Overview, { localVue, mocks, apolloProvider })
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('mount', () => {
|
describe('mount', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calls listUnconfirmedContributions', () => {
|
describe('server response for get pending creations is error', () => {
|
||||||
expect(apolloQueryMock).toBeCalledWith(
|
it('toast an error message', () => {
|
||||||
expect.objectContaining({
|
expect(toastErrorSpy).toBeCalledWith('Ouch!')
|
||||||
query: listUnconfirmedContributions,
|
})
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
|
it('calls the listAllContributions query', () => {
|
||||||
|
expect(listAllContributionsMock).toBeCalledWith({
|
||||||
|
currentPage: 1,
|
||||||
|
order: 'DESC',
|
||||||
|
pageSize: 25,
|
||||||
|
statusFilter: ['IN_PROGRESS', 'PENDING'],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('commits three pending creations to store', () => {
|
it('commits three pending creations to store', () => {
|
||||||
expect(storeCommitMock).toBeCalledWith('setOpenCreations', 3)
|
expect(storeCommitMock).toBeCalledWith('setOpenCreations', 2)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with open creations', () => {
|
describe('with open creations', () => {
|
||||||
it('renders a link to confirm creations', () => {
|
beforeEach(() => {
|
||||||
expect(wrapper.find('a[href="creation-confirm"]').text()).toContain('2')
|
mocks.$store.state.openCreations = 2
|
||||||
|
})
|
||||||
|
it('renders a link to confirm 2 creations', () => {
|
||||||
|
expect(wrapper.find('[data-test="open-creation"]').text()).toContain('2')
|
||||||
expect(wrapper.find('a[href="creation-confirm"]').exists()).toBeTruthy()
|
expect(wrapper.find('a[href="creation-confirm"]').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -91,7 +138,7 @@ describe('Overview', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('renders a link to confirm creations', () => {
|
it('renders a link to confirm creations', () => {
|
||||||
expect(wrapper.find('a[href="creation-confirm"]').text()).toContain('0')
|
expect(wrapper.find('[data-test="open-creation"]').text()).toContain('0')
|
||||||
expect(wrapper.find('a[href="creation-confirm"]').exists()).toBeTruthy()
|
expect(wrapper.find('a[href="creation-confirm"]').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -24,31 +24,40 @@
|
|||||||
>
|
>
|
||||||
<b-card-text>
|
<b-card-text>
|
||||||
<b-link to="creation-confirm">
|
<b-link to="creation-confirm">
|
||||||
<h1>{{ $store.state.openCreations }}</h1>
|
<h1 data-test="open-creation">{{ $store.state.openCreations }}</h1>
|
||||||
</b-link>
|
</b-link>
|
||||||
</b-card-text>
|
</b-card-text>
|
||||||
</b-card>
|
</b-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { listUnconfirmedContributions } from '@/graphql/listUnconfirmedContributions.js'
|
import { listAllContributions } from '../graphql/listAllContributions'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'overview',
|
name: 'overview',
|
||||||
methods: {
|
data() {
|
||||||
getPendingCreations() {
|
return {
|
||||||
this.$apollo
|
statusFilter: ['IN_PROGRESS', 'PENDING'],
|
||||||
.query({
|
}
|
||||||
query: listUnconfirmedContributions,
|
|
||||||
fetchPolicy: 'network-only',
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
this.$store.commit('setOpenCreations', result.data.listUnconfirmedContributions.length)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
created() {
|
apollo: {
|
||||||
this.getPendingCreations()
|
AllContributions: {
|
||||||
|
query() {
|
||||||
|
return listAllContributions
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
// may be at some point we need a pagination here
|
||||||
|
return {
|
||||||
|
statusFilter: this.statusFilter,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update({ listAllContributions }) {
|
||||||
|
this.$store.commit('setOpenCreations', listAllContributions.contributionCount)
|
||||||
|
},
|
||||||
|
error({ message }) {
|
||||||
|
this.toastError(message)
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import UserSearch from './UserSearch.vue'
|
import UserSearch from './UserSearch'
|
||||||
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
|
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|||||||
@ -58,7 +58,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import SearchUserTable from '../components/Tables/SearchUserTable.vue'
|
import SearchUserTable from '../components/Tables/SearchUserTable'
|
||||||
import { searchUsers } from '../graphql/searchUsers'
|
import { searchUsers } from '../graphql/searchUsers'
|
||||||
import { creationMonths } from '../mixins/creationMonths'
|
import { creationMonths } from '../mixins/creationMonths'
|
||||||
|
|
||||||
@ -72,7 +72,6 @@ export default {
|
|||||||
return {
|
return {
|
||||||
showArrays: false,
|
showArrays: false,
|
||||||
searchResult: [],
|
searchResult: [],
|
||||||
massCreation: [],
|
|
||||||
criteria: '',
|
criteria: '',
|
||||||
filters: {
|
filters: {
|
||||||
byActivated: null,
|
byActivated: null,
|
||||||
|
|||||||
@ -45,7 +45,7 @@ describe('router', () => {
|
|||||||
|
|
||||||
describe('routes', () => {
|
describe('routes', () => {
|
||||||
it('has nine routes defined', () => {
|
it('has nine routes defined', () => {
|
||||||
expect(routes).toHaveLength(9)
|
expect(routes).toHaveLength(8)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has "/overview" as default', async () => {
|
it('has "/overview" as default', async () => {
|
||||||
@ -67,13 +67,6 @@ describe('router', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('creation', () => {
|
|
||||||
it('loads the "Creation" component', async () => {
|
|
||||||
const component = await routes.find((r) => r.path === '/creation').component()
|
|
||||||
expect(component.default.name).toBe('Creation')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('creation-confirm', () => {
|
describe('creation-confirm', () => {
|
||||||
it('loads the "CreationConfirm" component', async () => {
|
it('loads the "CreationConfirm" component', async () => {
|
||||||
const component = await routes.find((r) => r.path === '/creation-confirm').component()
|
const component = await routes.find((r) => r.path === '/creation-confirm').component()
|
||||||
|
|||||||
@ -19,10 +19,6 @@ const routes = [
|
|||||||
path: '/user',
|
path: '/user',
|
||||||
component: () => import('@/pages/UserSearch.vue'),
|
component: () => import('@/pages/UserSearch.vue'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/creation',
|
|
||||||
component: () => import('@/pages/Creation.vue'),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/creation-confirm',
|
path: '/creation-confirm',
|
||||||
component: () => import('@/pages/CreationConfirm.vue'),
|
component: () => import('@/pages/CreationConfirm.vue'),
|
||||||
|
|||||||
@ -24,9 +24,6 @@ export const mutations = {
|
|||||||
moderator: (state, moderator) => {
|
moderator: (state, moderator) => {
|
||||||
state.moderator = moderator
|
state.moderator = moderator
|
||||||
},
|
},
|
||||||
setUserSelectedInMassCreation: (state, userSelectedInMassCreation) => {
|
|
||||||
state.userSelectedInMassCreation = userSelectedInMassCreation
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
|
|||||||
@ -10,7 +10,6 @@ const {
|
|||||||
resetOpenCreations,
|
resetOpenCreations,
|
||||||
setOpenCreations,
|
setOpenCreations,
|
||||||
moderator,
|
moderator,
|
||||||
setUserSelectedInMassCreation,
|
|
||||||
} = mutations
|
} = mutations
|
||||||
const { logout } = actions
|
const { logout } = actions
|
||||||
|
|
||||||
@ -65,14 +64,6 @@ describe('Vuex store', () => {
|
|||||||
expect(state.openCreations).toEqual(12)
|
expect(state.openCreations).toEqual(12)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('setUserSelectedInMassCreation', () => {
|
|
||||||
it('sets userSelectedInMassCreation to given value', () => {
|
|
||||||
const state = { userSelectedInMassCreation: [] }
|
|
||||||
setUserSelectedInMassCreation(state, [0, 1, 2])
|
|
||||||
expect(state.userSelectedInMassCreation).toEqual([0, 1, 2])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('actions', () => {
|
describe('actions', () => {
|
||||||
|
|||||||
@ -58,8 +58,4 @@ WEBHOOK_ELOPAGE_SECRET=secret
|
|||||||
# LOG_LEVEL=info
|
# LOG_LEVEL=info
|
||||||
|
|
||||||
# Federation
|
# Federation
|
||||||
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
|
FEDERATION_VALIDATE_COMMUNITY_TIMER=60000
|
||||||
# on an hash created from this topic
|
|
||||||
# FEDERATION_DHT_TOPIC=GRADIDO_HUB
|
|
||||||
# FEDERATION_DHT_SEED=64ebcb0e3ad547848fef4197c6e2332f
|
|
||||||
# FEDERATION_COMMUNITY_URL=http://localhost:4000/api
|
|
||||||
@ -55,6 +55,4 @@ EMAIL_CODE_REQUEST_TIME=$EMAIL_CODE_REQUEST_TIME
|
|||||||
WEBHOOK_ELOPAGE_SECRET=$WEBHOOK_ELOPAGE_SECRET
|
WEBHOOK_ELOPAGE_SECRET=$WEBHOOK_ELOPAGE_SECRET
|
||||||
|
|
||||||
# Federation
|
# Federation
|
||||||
FEDERATION_DHT_TOPIC=$FEDERATION_DHT_TOPIC
|
FEDERATION_VALIDATE_COMMUNITY_TIMER=$FEDERATION_VALIDATE_COMMUNITY_TIMER
|
||||||
FEDERATION_DHT_SEED=$FEDERATION_DHT_SEED
|
|
||||||
FEDERATION_COMMUNITY_URL=$FEDERATION_COMMUNITY_URL
|
|
||||||
|
|||||||
9
backend/.env.test_e2e
Normal file
9
backend/.env.test_e2e
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Server
|
||||||
|
JWT_EXPIRES_IN=1m
|
||||||
|
|
||||||
|
# Email
|
||||||
|
EMAIL=true
|
||||||
|
EMAIL_TEST_MODUS=false
|
||||||
|
EMAIL_TLS=false
|
||||||
|
# for testing password reset
|
||||||
|
EMAIL_CODE_REQUEST_TIME=1
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-backend",
|
"name": "gradido-backend",
|
||||||
"version": "1.18.1",
|
"version": "1.18.2",
|
||||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/backend",
|
"repository": "https://github.com/gradido/gradido/backend",
|
||||||
@ -15,10 +15,10 @@
|
|||||||
"lint": "eslint --max-warnings=0 --ext .js,.ts .",
|
"lint": "eslint --max-warnings=0 --ext .js,.ts .",
|
||||||
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles",
|
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles",
|
||||||
"seed": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/seeds/index.ts",
|
"seed": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/seeds/index.ts",
|
||||||
"klicktipp": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/util/klicktipp.ts"
|
"klicktipp": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/util/klicktipp.ts",
|
||||||
|
"locales": "scripts/sort.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hyperswarm/dht": "^6.2.0",
|
|
||||||
"apollo-server-express": "^2.25.2",
|
"apollo-server-express": "^2.25.2",
|
||||||
"await-semaphore": "^0.1.3",
|
"await-semaphore": "^0.1.3",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
@ -30,6 +30,7 @@
|
|||||||
"email-templates": "^10.0.1",
|
"email-templates": "^10.0.1",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"graphql": "^15.5.1",
|
"graphql": "^15.5.1",
|
||||||
|
"graphql-request": "5.0.0",
|
||||||
"i18n": "^0.15.1",
|
"i18n": "^0.15.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
|||||||
25
backend/scripts/sort.sh
Executable file
25
backend/scripts/sort.sh
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
ROOT_DIR=$(dirname "$0")/..
|
||||||
|
|
||||||
|
tmp=$(mktemp)
|
||||||
|
exit_code=0
|
||||||
|
|
||||||
|
for locale_file in $ROOT_DIR/src/locales/*.json
|
||||||
|
do
|
||||||
|
jq -f $(dirname "$0")/sort_filter.jq $locale_file > "$tmp"
|
||||||
|
if [ "$*" == "--fix" ]
|
||||||
|
then
|
||||||
|
mv "$tmp" $locale_file
|
||||||
|
else
|
||||||
|
if diff -q "$tmp" $locale_file > /dev/null ;
|
||||||
|
then
|
||||||
|
: # all good
|
||||||
|
else
|
||||||
|
exit_code=$?
|
||||||
|
echo "$(basename -- $locale_file) is not sorted by keys"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
exit $exit_code
|
||||||
13
backend/scripts/sort_filter.jq
Normal file
13
backend/scripts/sort_filter.jq
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
def walk(f):
|
||||||
|
. as $in
|
||||||
|
| if type == "object" then
|
||||||
|
reduce keys_unsorted[] as $key
|
||||||
|
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
|
||||||
|
elif type == "array" then map( walk(f) ) | f
|
||||||
|
else f
|
||||||
|
end;
|
||||||
|
|
||||||
|
def keys_sort_by(f):
|
||||||
|
to_entries | sort_by(.key|f ) | from_entries;
|
||||||
|
|
||||||
|
walk(if type == "object" then keys_sort_by(ascii_upcase) else . end)
|
||||||
@ -1,9 +1,10 @@
|
|||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
import CONFIG from '@/config/'
|
import CONFIG from '@/config/'
|
||||||
import { CustomJwtPayload } from './CustomJwtPayload'
|
import { CustomJwtPayload } from './CustomJwtPayload'
|
||||||
|
import LogError from '@/server/LogError'
|
||||||
|
|
||||||
export const decode = (token: string): CustomJwtPayload | null => {
|
export const decode = (token: string): CustomJwtPayload | null => {
|
||||||
if (!token) throw new Error('401 Unauthorized')
|
if (!token) throw new LogError('401 Unauthorized')
|
||||||
try {
|
try {
|
||||||
return <CustomJwtPayload>jwt.verify(token, CONFIG.JWT_SECRET)
|
return <CustomJwtPayload>jwt.verify(token, CONFIG.JWT_SECRET)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@ -42,7 +42,6 @@ export enum RIGHTS {
|
|||||||
DELETE_USER = 'DELETE_USER',
|
DELETE_USER = 'DELETE_USER',
|
||||||
UNDELETE_USER = 'UNDELETE_USER',
|
UNDELETE_USER = 'UNDELETE_USER',
|
||||||
ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION',
|
ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION',
|
||||||
ADMIN_CREATE_CONTRIBUTIONS = 'ADMIN_CREATE_CONTRIBUTIONS',
|
|
||||||
ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION',
|
ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION',
|
||||||
ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION',
|
ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION',
|
||||||
LIST_UNCONFIRMED_CONTRIBUTIONS = 'LIST_UNCONFIRMED_CONTRIBUTIONS',
|
LIST_UNCONFIRMED_CONTRIBUTIONS = 'LIST_UNCONFIRMED_CONTRIBUTIONS',
|
||||||
|
|||||||
@ -10,7 +10,7 @@ Decimal.set({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const constants = {
|
const constants = {
|
||||||
DB_VERSION: '0059-add_hide_amount_to_users',
|
DB_VERSION: '0060-update_communities_table',
|
||||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||||
LOG4JS_CONFIG: 'log4js-config.json',
|
LOG4JS_CONFIG: 'log4js-config.json',
|
||||||
// default log level on production should be info
|
// default log level on production should be info
|
||||||
@ -115,14 +115,8 @@ if (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const federation = {
|
const federation = {
|
||||||
FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || null,
|
FEDERATION_VALIDATE_COMMUNITY_TIMER:
|
||||||
FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
|
Number(process.env.FEDERATION_VALIDATE_COMMUNITY_TIMER) || 60000,
|
||||||
FEDERATION_COMMUNITY_URL:
|
|
||||||
process.env.FEDERATION_COMMUNITY_URL === undefined
|
|
||||||
? null
|
|
||||||
: process.env.FEDERATION_COMMUNITY_URL.endsWith('/')
|
|
||||||
? process.env.FEDERATION_COMMUNITY_URL
|
|
||||||
: process.env.FEDERATION_COMMUNITY_URL + '/',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
|
|||||||
@ -106,7 +106,7 @@ describe('sendEmailVariants', () => {
|
|||||||
'you have received a message from Bibi Bloxberg regarding your common good contribution “My contribution.”.',
|
'you have received a message from Bibi Bloxberg regarding your common good contribution “My contribution.”.',
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain(
|
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(
|
expect(result.originalMessage.html).toContain(
|
||||||
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||||
@ -424,7 +424,7 @@ describe('sendEmailVariants', () => {
|
|||||||
'Your public good contribution “My contribution.” was rejected by Bibi Bloxberg.',
|
'Your public good contribution “My contribution.” was rejected by Bibi Bloxberg.',
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain(
|
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(
|
expect(result.originalMessage.html).toContain(
|
||||||
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||||
@ -502,7 +502,7 @@ describe('sendEmailVariants', () => {
|
|||||||
'Your public good contribution “My contribution.” was deleted by Bibi Bloxberg.',
|
'Your public good contribution “My contribution.” was deleted by Bibi Bloxberg.',
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain(
|
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(
|
expect(result.originalMessage.html).toContain(
|
||||||
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||||
|
|||||||
@ -1,509 +1,212 @@
|
|||||||
import decimal from 'decimal.js-light'
|
import { EventProtocol as DbEvent } from '@entity/EventProtocol'
|
||||||
|
import Decimal from 'decimal.js-light'
|
||||||
import { EventProtocolType } from './EventProtocolType'
|
import { EventProtocolType } from './EventProtocolType'
|
||||||
|
|
||||||
export class EventBasic {
|
export const Event = (
|
||||||
type: string
|
type: EventProtocolType,
|
||||||
createdAt: Date
|
userId: number,
|
||||||
}
|
xUserId: number | null = null,
|
||||||
export class EventBasicUserId extends EventBasic {
|
xCommunityId: number | null = null,
|
||||||
userId: number
|
transactionId: number | null = null,
|
||||||
|
contributionId: number | 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.amount = amount
|
||||||
|
event.messageId = messageId
|
||||||
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EventBasicTx extends EventBasicUserId {
|
export const EVENT_CONTRIBUTION_CREATE = async (
|
||||||
transactionId: number
|
userId: number,
|
||||||
amount: decimal
|
contributionId: number,
|
||||||
}
|
amount: Decimal,
|
||||||
|
): Promise<DbEvent> =>
|
||||||
export class EventBasicTxX extends EventBasicTx {
|
Event(
|
||||||
xUserId: number
|
EventProtocolType.CONTRIBUTION_CREATE,
|
||||||
xCommunityId: number
|
userId,
|
||||||
}
|
null,
|
||||||
|
null,
|
||||||
export class EventBasicCt extends EventBasicUserId {
|
null,
|
||||||
contributionId: number
|
contributionId,
|
||||||
amount: decimal
|
amount,
|
||||||
}
|
).save()
|
||||||
|
|
||||||
export class EventBasicCtX extends EventBasicCt {
|
export const EVENT_CONTRIBUTION_DELETE = async (
|
||||||
xUserId: number
|
userId: number,
|
||||||
xCommunityId: number
|
contributionId: number,
|
||||||
}
|
amount: Decimal,
|
||||||
|
): Promise<DbEvent> =>
|
||||||
export class EventBasicRedeem extends EventBasicUserId {
|
Event(
|
||||||
transactionId?: number
|
EventProtocolType.CONTRIBUTION_DELETE,
|
||||||
contributionId?: number
|
userId,
|
||||||
}
|
null,
|
||||||
|
null,
|
||||||
export class EventBasicCtMsg extends EventBasicCt {
|
null,
|
||||||
messageId: number
|
contributionId,
|
||||||
}
|
amount,
|
||||||
|
).save()
|
||||||
export class EventVisitGradido extends EventBasic {}
|
|
||||||
export class EventRegister extends EventBasicUserId {}
|
export const EVENT_CONTRIBUTION_UPDATE = async (
|
||||||
export class EventRedeemRegister extends EventBasicRedeem {}
|
userId: number,
|
||||||
export class EventVerifyRedeem extends EventBasicRedeem {}
|
contributionId: number,
|
||||||
export class EventInactiveAccount extends EventBasicUserId {}
|
amount: Decimal,
|
||||||
export class EventSendConfirmationEmail extends EventBasicUserId {}
|
): Promise<DbEvent> =>
|
||||||
export class EventSendAccountMultiRegistrationEmail extends EventBasicUserId {}
|
Event(
|
||||||
export class EventSendForgotPasswordEmail extends EventBasicUserId {}
|
EventProtocolType.CONTRIBUTION_UPDATE,
|
||||||
export class EventSendTransactionSendEmail extends EventBasicTxX {}
|
userId,
|
||||||
export class EventSendTransactionReceiveEmail extends EventBasicTxX {}
|
null,
|
||||||
export class EventSendTransactionLinkRedeemEmail extends EventBasicTxX {}
|
null,
|
||||||
export class EventSendAddedContributionEmail extends EventBasicCt {}
|
null,
|
||||||
export class EventSendContributionConfirmEmail extends EventBasicCt {}
|
contributionId,
|
||||||
export class EventConfirmationEmail extends EventBasicUserId {}
|
amount,
|
||||||
export class EventRegisterEmailKlicktipp extends EventBasicUserId {}
|
).save()
|
||||||
export class EventLogin extends EventBasicUserId {}
|
|
||||||
export class EventLogout extends EventBasicUserId {}
|
export const EVENT_ADMIN_CONTRIBUTION_CREATE = async (
|
||||||
export class EventRedeemLogin extends EventBasicRedeem {}
|
userId: number,
|
||||||
export class EventActivateAccount extends EventBasicUserId {}
|
contributionId: number,
|
||||||
export class EventPasswordChange extends EventBasicUserId {}
|
amount: Decimal,
|
||||||
export class EventTransactionSend extends EventBasicTxX {}
|
): Promise<DbEvent> =>
|
||||||
export class EventTransactionSendRedeem extends EventBasicTxX {}
|
Event(
|
||||||
export class EventTransactionRepeateRedeem extends EventBasicTxX {}
|
EventProtocolType.ADMIN_CONTRIBUTION_CREATE,
|
||||||
export class EventTransactionCreation extends EventBasicTx {}
|
userId,
|
||||||
export class EventTransactionReceive extends EventBasicTxX {}
|
null,
|
||||||
export class EventTransactionReceiveRedeem extends EventBasicTxX {}
|
null,
|
||||||
export class EventContributionCreate extends EventBasicCt {}
|
null,
|
||||||
export class EventAdminContributionCreate extends EventBasicCt {}
|
contributionId,
|
||||||
export class EventAdminContributionDelete extends EventBasicCt {}
|
amount,
|
||||||
export class EventAdminContributionDeny extends EventBasicCt {}
|
).save()
|
||||||
export class EventAdminContributionUpdate extends EventBasicCt {}
|
|
||||||
export class EventUserCreateContributionMessage extends EventBasicCtMsg {}
|
export const EVENT_ADMIN_CONTRIBUTION_UPDATE = async (
|
||||||
export class EventAdminCreateContributionMessage extends EventBasicCtMsg {}
|
userId: number,
|
||||||
export class EventContributionDelete extends EventBasicCt {}
|
contributionId: number,
|
||||||
export class EventContributionUpdate extends EventBasicCt {}
|
amount: Decimal,
|
||||||
export class EventContributionConfirm extends EventBasicCtX {}
|
): Promise<DbEvent> =>
|
||||||
export class EventContributionDeny extends EventBasicCtX {}
|
Event(
|
||||||
export class EventContributionLinkDefine extends EventBasicCt {}
|
EventProtocolType.ADMIN_CONTRIBUTION_UPDATE,
|
||||||
export class EventContributionLinkActivateRedeem extends EventBasicCt {}
|
userId,
|
||||||
export class EventDeleteUser extends EventBasicUserId {}
|
null,
|
||||||
export class EventUndeleteUser extends EventBasicUserId {}
|
null,
|
||||||
export class EventChangeUserRole extends EventBasicUserId {}
|
null,
|
||||||
export class EventAdminUpdateContribution extends EventBasicCt {}
|
contributionId,
|
||||||
export class EventAdminDeleteContribution extends EventBasicCt {}
|
amount,
|
||||||
export class EventCreateContributionLink extends EventBasicCt {}
|
).save()
|
||||||
export class EventDeleteContributionLink extends EventBasicCt {}
|
|
||||||
export class EventUpdateContributionLink extends EventBasicCt {}
|
export const EVENT_ADMIN_CONTRIBUTION_DELETE = async (
|
||||||
|
userId: number,
|
||||||
export class Event {
|
contributionId: number,
|
||||||
public setEventBasic(): Event {
|
amount: Decimal,
|
||||||
this.type = EventProtocolType.BASIC
|
): Promise<DbEvent> =>
|
||||||
this.createdAt = new Date()
|
Event(
|
||||||
|
EventProtocolType.ADMIN_CONTRIBUTION_DELETE,
|
||||||
return this
|
userId,
|
||||||
}
|
null,
|
||||||
|
null,
|
||||||
public setEventVisitGradido(): Event {
|
null,
|
||||||
this.setEventBasic()
|
contributionId,
|
||||||
this.type = EventProtocolType.VISIT_GRADIDO
|
amount,
|
||||||
|
).save()
|
||||||
return this
|
|
||||||
}
|
export const EVENT_CONTRIBUTION_CONFIRM = async (
|
||||||
|
userId: number,
|
||||||
public setEventRegister(ev: EventRegister): Event {
|
contributionId: number,
|
||||||
this.setByBasicUser(ev.userId)
|
amount: Decimal,
|
||||||
this.type = EventProtocolType.REGISTER
|
): Promise<DbEvent> =>
|
||||||
|
Event(
|
||||||
return this
|
EventProtocolType.CONTRIBUTION_CONFIRM,
|
||||||
}
|
userId,
|
||||||
|
null,
|
||||||
public setEventRedeemRegister(ev: EventRedeemRegister): Event {
|
null,
|
||||||
this.setByBasicRedeem(ev.userId, ev.transactionId, ev.contributionId)
|
null,
|
||||||
this.type = EventProtocolType.REDEEM_REGISTER
|
contributionId,
|
||||||
|
amount,
|
||||||
return this
|
).save()
|
||||||
}
|
|
||||||
|
export const EVENT_ADMIN_CONTRIBUTION_DENY = async (
|
||||||
public setEventVerifyRedeem(ev: EventVerifyRedeem): Event {
|
userId: number,
|
||||||
this.setByBasicRedeem(ev.userId, ev.transactionId, ev.contributionId)
|
xUserId: number,
|
||||||
this.type = EventProtocolType.VERIFY_REDEEM
|
contributionId: number,
|
||||||
|
amount: Decimal,
|
||||||
return this
|
): Promise<DbEvent> =>
|
||||||
}
|
Event(
|
||||||
|
EventProtocolType.ADMIN_CONTRIBUTION_DENY,
|
||||||
public setEventInactiveAccount(ev: EventInactiveAccount): Event {
|
userId,
|
||||||
this.setByBasicUser(ev.userId)
|
xUserId,
|
||||||
this.type = EventProtocolType.INACTIVE_ACCOUNT
|
null,
|
||||||
|
null,
|
||||||
return this
|
contributionId,
|
||||||
}
|
amount,
|
||||||
|
).save()
|
||||||
public setEventSendConfirmationEmail(ev: EventSendConfirmationEmail): Event {
|
|
||||||
this.setByBasicUser(ev.userId)
|
export const EVENT_TRANSACTION_SEND = async (
|
||||||
this.type = EventProtocolType.SEND_CONFIRMATION_EMAIL
|
userId: number,
|
||||||
|
xUserId: number,
|
||||||
return this
|
transactionId: number,
|
||||||
}
|
amount: Decimal,
|
||||||
|
): Promise<DbEvent> =>
|
||||||
public setEventSendAccountMultiRegistrationEmail(
|
Event(
|
||||||
ev: EventSendAccountMultiRegistrationEmail,
|
EventProtocolType.TRANSACTION_SEND,
|
||||||
): Event {
|
userId,
|
||||||
this.setByBasicUser(ev.userId)
|
xUserId,
|
||||||
this.type = EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL
|
null,
|
||||||
|
transactionId,
|
||||||
return this
|
null,
|
||||||
}
|
amount,
|
||||||
|
).save()
|
||||||
public setEventSendForgotPasswordEmail(ev: EventSendForgotPasswordEmail): Event {
|
|
||||||
this.setByBasicUser(ev.userId)
|
export const EVENT_TRANSACTION_RECEIVE = async (
|
||||||
this.type = EventProtocolType.SEND_FORGOT_PASSWORD_EMAIL
|
userId: number,
|
||||||
|
xUserId: number,
|
||||||
return this
|
transactionId: number,
|
||||||
}
|
amount: Decimal,
|
||||||
|
): Promise<DbEvent> =>
|
||||||
public setEventSendTransactionSendEmail(ev: EventSendTransactionSendEmail): Event {
|
Event(
|
||||||
this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
|
EventProtocolType.TRANSACTION_RECEIVE,
|
||||||
this.type = EventProtocolType.SEND_TRANSACTION_SEND_EMAIL
|
userId,
|
||||||
|
xUserId,
|
||||||
return this
|
null,
|
||||||
}
|
transactionId,
|
||||||
|
null,
|
||||||
public setEventSendTransactionReceiveEmail(ev: EventSendTransactionReceiveEmail): Event {
|
amount,
|
||||||
this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
|
).save()
|
||||||
this.type = EventProtocolType.SEND_TRANSACTION_RECEIVE_EMAIL
|
|
||||||
|
export const EVENT_LOGIN = async (userId: number): Promise<DbEvent> =>
|
||||||
return this
|
Event(EventProtocolType.LOGIN, userId, null, null, null, null, null, null).save()
|
||||||
}
|
|
||||||
|
export const EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL = async (
|
||||||
public setEventSendTransactionLinkRedeemEmail(ev: EventSendTransactionLinkRedeemEmail): Event {
|
userId: number,
|
||||||
this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
|
): Promise<DbEvent> => Event(EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL, userId).save()
|
||||||
this.type = EventProtocolType.SEND_TRANSACTION_LINK_REDEEM_EMAIL
|
|
||||||
|
export const EVENT_SEND_CONFIRMATION_EMAIL = async (userId: number): Promise<DbEvent> =>
|
||||||
return this
|
Event(EventProtocolType.SEND_CONFIRMATION_EMAIL, userId).save()
|
||||||
}
|
|
||||||
|
export const EVENT_ADMIN_SEND_CONFIRMATION_EMAIL = async (userId: number): Promise<DbEvent> =>
|
||||||
public setEventSendAddedContributionEmail(ev: EventSendAddedContributionEmail): Event {
|
Event(EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL, userId).save()
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.SEND_ADDED_CONTRIBUTION_EMAIL
|
/* export const EVENT_REDEEM_REGISTER = async (
|
||||||
|
userId: number,
|
||||||
return this
|
transactionId: number | null = null,
|
||||||
}
|
contributionId: number | null = null,
|
||||||
|
): Promise<Event> =>
|
||||||
public setEventSendContributionConfirmEmail(ev: EventSendContributionConfirmEmail): Event {
|
Event(
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
EventProtocolType.REDEEM_REGISTER,
|
||||||
this.type = EventProtocolType.SEND_CONTRIBUTION_CONFIRM_EMAIL
|
userId,
|
||||||
|
null,
|
||||||
return this
|
null,
|
||||||
}
|
transactionId,
|
||||||
|
contributionId,
|
||||||
public setEventConfirmationEmail(ev: EventConfirmationEmail): Event {
|
).save()
|
||||||
this.setByBasicUser(ev.userId)
|
*/
|
||||||
this.type = EventProtocolType.CONFIRM_EMAIL
|
|
||||||
|
export const EVENT_REGISTER = async (userId: number): Promise<DbEvent> =>
|
||||||
return this
|
Event(EventProtocolType.REGISTER, userId).save()
|
||||||
}
|
|
||||||
|
export const EVENT_ACTIVATE_ACCOUNT = async (userId: number): Promise<DbEvent> =>
|
||||||
public setEventRegisterEmailKlicktipp(ev: EventRegisterEmailKlicktipp): Event {
|
Event(EventProtocolType.ACTIVATE_ACCOUNT, userId).save()
|
||||||
this.setByBasicUser(ev.userId)
|
|
||||||
this.type = EventProtocolType.REGISTER_EMAIL_KLICKTIPP
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventLogin(ev: EventLogin): Event {
|
|
||||||
this.setByBasicUser(ev.userId)
|
|
||||||
this.type = EventProtocolType.LOGIN
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventLogout(ev: EventLogout): Event {
|
|
||||||
this.setByBasicUser(ev.userId)
|
|
||||||
this.type = EventProtocolType.LOGOUT
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventRedeemLogin(ev: EventRedeemLogin): Event {
|
|
||||||
this.setByBasicRedeem(ev.userId, ev.transactionId, ev.contributionId)
|
|
||||||
this.type = EventProtocolType.REDEEM_LOGIN
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventActivateAccount(ev: EventActivateAccount): Event {
|
|
||||||
this.setByBasicUser(ev.userId)
|
|
||||||
this.type = EventProtocolType.ACTIVATE_ACCOUNT
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventPasswordChange(ev: EventPasswordChange): Event {
|
|
||||||
this.setByBasicUser(ev.userId)
|
|
||||||
this.type = EventProtocolType.PASSWORD_CHANGE
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventTransactionSend(ev: EventTransactionSend): Event {
|
|
||||||
this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
|
|
||||||
this.type = EventProtocolType.TRANSACTION_SEND
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventTransactionSendRedeem(ev: EventTransactionSendRedeem): Event {
|
|
||||||
this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
|
|
||||||
this.type = EventProtocolType.TRANSACTION_SEND_REDEEM
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventTransactionRepeateRedeem(ev: EventTransactionRepeateRedeem): Event {
|
|
||||||
this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
|
|
||||||
this.type = EventProtocolType.TRANSACTION_REPEATE_REDEEM
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventTransactionCreation(ev: EventTransactionCreation): Event {
|
|
||||||
this.setByBasicTx(ev.userId, ev.transactionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.TRANSACTION_CREATION
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventTransactionReceive(ev: EventTransactionReceive): Event {
|
|
||||||
this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
|
|
||||||
this.type = EventProtocolType.TRANSACTION_RECEIVE
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventTransactionReceiveRedeem(ev: EventTransactionReceiveRedeem): Event {
|
|
||||||
this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
|
|
||||||
this.type = EventProtocolType.TRANSACTION_RECEIVE_REDEEM
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventContributionCreate(ev: EventContributionCreate): Event {
|
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.CONTRIBUTION_CREATE
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventAdminContributionCreate(ev: EventAdminContributionCreate): Event {
|
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.ADMIN_CONTRIBUTION_CREATE
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventAdminContributionDelete(ev: EventAdminContributionDelete): Event {
|
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.ADMIN_CONTRIBUTION_DELETE
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventAdminContributionDeny(ev: EventAdminContributionDeny): Event {
|
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.ADMIN_CONTRIBUTION_DENY
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventAdminContributionUpdate(ev: EventAdminContributionUpdate): Event {
|
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.ADMIN_CONTRIBUTION_UPDATE
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventUserCreateContributionMessage(ev: EventUserCreateContributionMessage): Event {
|
|
||||||
this.setByBasicCtMsg(ev.userId, ev.contributionId, ev.amount, ev.messageId)
|
|
||||||
this.type = EventProtocolType.USER_CREATE_CONTRIBUTION_MESSAGE
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventAdminCreateContributionMessage(ev: EventAdminCreateContributionMessage): Event {
|
|
||||||
this.setByBasicCtMsg(ev.userId, ev.contributionId, ev.amount, ev.messageId)
|
|
||||||
this.type = EventProtocolType.ADMIN_CREATE_CONTRIBUTION_MESSAGE
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventContributionDelete(ev: EventContributionDelete): Event {
|
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.CONTRIBUTION_DELETE
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventContributionUpdate(ev: EventContributionUpdate): Event {
|
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.CONTRIBUTION_UPDATE
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventContributionConfirm(ev: EventContributionConfirm): Event {
|
|
||||||
this.setByBasicCtX(ev.userId, ev.contributionId, ev.amount, ev.xUserId, ev.xCommunityId)
|
|
||||||
this.type = EventProtocolType.CONTRIBUTION_CONFIRM
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventContributionDeny(ev: EventContributionDeny): Event {
|
|
||||||
this.setByBasicCtX(ev.userId, ev.contributionId, ev.amount, ev.xUserId, ev.xCommunityId)
|
|
||||||
this.type = EventProtocolType.CONTRIBUTION_DENY
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventContributionLinkDefine(ev: EventContributionLinkDefine): Event {
|
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.CONTRIBUTION_LINK_DEFINE
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventContributionLinkActivateRedeem(ev: EventContributionLinkActivateRedeem): Event {
|
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.CONTRIBUTION_LINK_ACTIVATE_REDEEM
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventDeleteUser(ev: EventDeleteUser): Event {
|
|
||||||
this.setByBasicUser(ev.userId)
|
|
||||||
this.type = EventProtocolType.DELETE_USER
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventUndeleteUser(ev: EventUndeleteUser): Event {
|
|
||||||
this.setByBasicUser(ev.userId)
|
|
||||||
this.type = EventProtocolType.UNDELETE_USER
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventChangeUserRole(ev: EventChangeUserRole): Event {
|
|
||||||
this.setByBasicUser(ev.userId)
|
|
||||||
this.type = EventProtocolType.CHANGE_USER_ROLE
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventAdminUpdateContribution(ev: EventAdminUpdateContribution): Event {
|
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.ADMIN_UPDATE_CONTRIBUTION
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventAdminDeleteContribution(ev: EventAdminDeleteContribution): Event {
|
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.ADMIN_DELETE_CONTRIBUTION
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventCreateContributionLink(ev: EventCreateContributionLink): Event {
|
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.CREATE_CONTRIBUTION_LINK
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventDeleteContributionLink(ev: EventDeleteContributionLink): Event {
|
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.DELETE_CONTRIBUTION_LINK
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventUpdateContributionLink(ev: EventUpdateContributionLink): Event {
|
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.UPDATE_CONTRIBUTION_LINK
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
setByBasicUser(userId: number): Event {
|
|
||||||
this.setEventBasic()
|
|
||||||
this.userId = userId
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
setByBasicTx(userId: number, transactionId: number, amount: decimal): Event {
|
|
||||||
this.setByBasicUser(userId)
|
|
||||||
this.transactionId = transactionId
|
|
||||||
this.amount = amount
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
setByBasicTxX(
|
|
||||||
userId: number,
|
|
||||||
transactionId: number,
|
|
||||||
amount: decimal,
|
|
||||||
xUserId: number,
|
|
||||||
xCommunityId: number,
|
|
||||||
): Event {
|
|
||||||
this.setByBasicTx(userId, transactionId, amount)
|
|
||||||
this.xUserId = xUserId
|
|
||||||
this.xCommunityId = xCommunityId
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
setByBasicCt(userId: number, contributionId: number, amount: decimal): Event {
|
|
||||||
this.setByBasicUser(userId)
|
|
||||||
this.contributionId = contributionId
|
|
||||||
this.amount = amount
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
setByBasicCtMsg(
|
|
||||||
userId: number,
|
|
||||||
contributionId: number,
|
|
||||||
amount: decimal,
|
|
||||||
messageId: number,
|
|
||||||
): Event {
|
|
||||||
this.setByBasicCt(userId, contributionId, amount)
|
|
||||||
this.messageId = messageId
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
setByBasicCtX(
|
|
||||||
userId: number,
|
|
||||||
contributionId: number,
|
|
||||||
amount: decimal,
|
|
||||||
xUserId: number,
|
|
||||||
xCommunityId: number,
|
|
||||||
): Event {
|
|
||||||
this.setByBasicCt(userId, contributionId, amount)
|
|
||||||
this.xUserId = xUserId
|
|
||||||
this.xCommunityId = xCommunityId
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
setByBasicRedeem(userId: number, transactionId?: number, contributionId?: number): Event {
|
|
||||||
this.setByBasicUser(userId)
|
|
||||||
if (transactionId) this.transactionId = transactionId
|
|
||||||
if (contributionId) this.contributionId = contributionId
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
id: number
|
|
||||||
type: string
|
|
||||||
createdAt: Date
|
|
||||||
userId: number
|
|
||||||
xUserId?: number
|
|
||||||
xCommunityId?: number
|
|
||||||
transactionId?: number
|
|
||||||
contributionId?: number
|
|
||||||
amount?: decimal
|
|
||||||
messageId?: number
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import { Event } from '@/event/Event'
|
|
||||||
import { backendLogger as logger } from '@/server/logger'
|
|
||||||
import { EventProtocol } from '@entity/EventProtocol'
|
|
||||||
|
|
||||||
export const writeEvent = async (event: Event): Promise<EventProtocol | null> => {
|
|
||||||
logger.info('writeEvent', event)
|
|
||||||
const dbEvent = new EventProtocol()
|
|
||||||
dbEvent.type = event.type
|
|
||||||
dbEvent.createdAt = event.createdAt
|
|
||||||
dbEvent.userId = event.userId
|
|
||||||
dbEvent.xUserId = event.xUserId || null
|
|
||||||
dbEvent.xCommunityId = event.xCommunityId || null
|
|
||||||
dbEvent.contributionId = event.contributionId || null
|
|
||||||
dbEvent.transactionId = event.transactionId || null
|
|
||||||
dbEvent.amount = event.amount || null
|
|
||||||
return dbEvent.save()
|
|
||||||
}
|
|
||||||
@ -1,50 +1,50 @@
|
|||||||
export enum EventProtocolType {
|
export enum EventProtocolType {
|
||||||
BASIC = 'BASIC',
|
// VISIT_GRADIDO = 'VISIT_GRADIDO',
|
||||||
VISIT_GRADIDO = 'VISIT_GRADIDO',
|
|
||||||
REGISTER = 'REGISTER',
|
REGISTER = 'REGISTER',
|
||||||
REDEEM_REGISTER = 'REDEEM_REGISTER',
|
REDEEM_REGISTER = 'REDEEM_REGISTER',
|
||||||
VERIFY_REDEEM = 'VERIFY_REDEEM',
|
// VERIFY_REDEEM = 'VERIFY_REDEEM',
|
||||||
INACTIVE_ACCOUNT = 'INACTIVE_ACCOUNT',
|
// INACTIVE_ACCOUNT = 'INACTIVE_ACCOUNT',
|
||||||
SEND_CONFIRMATION_EMAIL = 'SEND_CONFIRMATION_EMAIL',
|
SEND_CONFIRMATION_EMAIL = 'SEND_CONFIRMATION_EMAIL',
|
||||||
|
ADMIN_SEND_CONFIRMATION_EMAIL = 'ADMIN_SEND_CONFIRMATION_EMAIL',
|
||||||
SEND_ACCOUNT_MULTIREGISTRATION_EMAIL = 'SEND_ACCOUNT_MULTIREGISTRATION_EMAIL',
|
SEND_ACCOUNT_MULTIREGISTRATION_EMAIL = 'SEND_ACCOUNT_MULTIREGISTRATION_EMAIL',
|
||||||
CONFIRM_EMAIL = 'CONFIRM_EMAIL',
|
// CONFIRM_EMAIL = 'CONFIRM_EMAIL',
|
||||||
REGISTER_EMAIL_KLICKTIPP = 'REGISTER_EMAIL_KLICKTIPP',
|
// REGISTER_EMAIL_KLICKTIPP = 'REGISTER_EMAIL_KLICKTIPP',
|
||||||
LOGIN = 'LOGIN',
|
LOGIN = 'LOGIN',
|
||||||
LOGOUT = 'LOGOUT',
|
// LOGOUT = 'LOGOUT',
|
||||||
REDEEM_LOGIN = 'REDEEM_LOGIN',
|
// REDEEM_LOGIN = 'REDEEM_LOGIN',
|
||||||
ACTIVATE_ACCOUNT = 'ACTIVATE_ACCOUNT',
|
ACTIVATE_ACCOUNT = 'ACTIVATE_ACCOUNT',
|
||||||
SEND_FORGOT_PASSWORD_EMAIL = 'SEND_FORGOT_PASSWORD_EMAIL',
|
// SEND_FORGOT_PASSWORD_EMAIL = 'SEND_FORGOT_PASSWORD_EMAIL',
|
||||||
PASSWORD_CHANGE = 'PASSWORD_CHANGE',
|
// PASSWORD_CHANGE = 'PASSWORD_CHANGE',
|
||||||
SEND_TRANSACTION_SEND_EMAIL = 'SEND_TRANSACTION_SEND_EMAIL',
|
// SEND_TRANSACTION_SEND_EMAIL = 'SEND_TRANSACTION_SEND_EMAIL',
|
||||||
SEND_TRANSACTION_RECEIVE_EMAIL = 'SEND_TRANSACTION_RECEIVE_EMAIL',
|
// SEND_TRANSACTION_RECEIVE_EMAIL = 'SEND_TRANSACTION_RECEIVE_EMAIL',
|
||||||
TRANSACTION_SEND = 'TRANSACTION_SEND',
|
TRANSACTION_SEND = 'TRANSACTION_SEND',
|
||||||
TRANSACTION_SEND_REDEEM = 'TRANSACTION_SEND_REDEEM',
|
// TRANSACTION_SEND_REDEEM = 'TRANSACTION_SEND_REDEEM',
|
||||||
TRANSACTION_REPEATE_REDEEM = 'TRANSACTION_REPEATE_REDEEM',
|
// TRANSACTION_REPEATE_REDEEM = 'TRANSACTION_REPEATE_REDEEM',
|
||||||
TRANSACTION_CREATION = 'TRANSACTION_CREATION',
|
// TRANSACTION_CREATION = 'TRANSACTION_CREATION',
|
||||||
TRANSACTION_RECEIVE = 'TRANSACTION_RECEIVE',
|
TRANSACTION_RECEIVE = 'TRANSACTION_RECEIVE',
|
||||||
TRANSACTION_RECEIVE_REDEEM = 'TRANSACTION_RECEIVE_REDEEM',
|
// TRANSACTION_RECEIVE_REDEEM = 'TRANSACTION_RECEIVE_REDEEM',
|
||||||
SEND_TRANSACTION_LINK_REDEEM_EMAIL = 'SEND_TRANSACTION_LINK_REDEEM_EMAIL',
|
// SEND_TRANSACTION_LINK_REDEEM_EMAIL = 'SEND_TRANSACTION_LINK_REDEEM_EMAIL',
|
||||||
SEND_ADDED_CONTRIBUTION_EMAIL = 'SEND_ADDED_CONTRIBUTION_EMAIL',
|
// SEND_ADDED_CONTRIBUTION_EMAIL = 'SEND_ADDED_CONTRIBUTION_EMAIL',
|
||||||
SEND_CONTRIBUTION_CONFIRM_EMAIL = 'SEND_CONTRIBUTION_CONFIRM_EMAIL',
|
// SEND_CONTRIBUTION_CONFIRM_EMAIL = 'SEND_CONTRIBUTION_CONFIRM_EMAIL',
|
||||||
CONTRIBUTION_CREATE = 'CONTRIBUTION_CREATE',
|
CONTRIBUTION_CREATE = 'CONTRIBUTION_CREATE',
|
||||||
CONTRIBUTION_CONFIRM = 'CONTRIBUTION_CONFIRM',
|
CONTRIBUTION_CONFIRM = 'CONTRIBUTION_CONFIRM',
|
||||||
CONTRIBUTION_DENY = 'CONTRIBUTION_DENY',
|
// CONTRIBUTION_DENY = 'CONTRIBUTION_DENY',
|
||||||
CONTRIBUTION_LINK_DEFINE = 'CONTRIBUTION_LINK_DEFINE',
|
// CONTRIBUTION_LINK_DEFINE = 'CONTRIBUTION_LINK_DEFINE',
|
||||||
CONTRIBUTION_LINK_ACTIVATE_REDEEM = 'CONTRIBUTION_LINK_ACTIVATE_REDEEM',
|
// CONTRIBUTION_LINK_ACTIVATE_REDEEM = 'CONTRIBUTION_LINK_ACTIVATE_REDEEM',
|
||||||
CONTRIBUTION_DELETE = 'CONTRIBUTION_DELETE',
|
CONTRIBUTION_DELETE = 'CONTRIBUTION_DELETE',
|
||||||
CONTRIBUTION_UPDATE = 'CONTRIBUTION_UPDATE',
|
CONTRIBUTION_UPDATE = 'CONTRIBUTION_UPDATE',
|
||||||
ADMIN_CONTRIBUTION_CREATE = 'ADMIN_CONTRIBUTION_CREATE',
|
ADMIN_CONTRIBUTION_CREATE = 'ADMIN_CONTRIBUTION_CREATE',
|
||||||
ADMIN_CONTRIBUTION_DELETE = 'ADMIN_CONTRIBUTION_DELETE',
|
ADMIN_CONTRIBUTION_DELETE = 'ADMIN_CONTRIBUTION_DELETE',
|
||||||
ADMIN_CONTRIBUTION_DENY = 'ADMIN_CONTRIBUTION_DENY',
|
ADMIN_CONTRIBUTION_DENY = 'ADMIN_CONTRIBUTION_DENY',
|
||||||
ADMIN_CONTRIBUTION_UPDATE = 'ADMIN_CONTRIBUTION_UPDATE',
|
ADMIN_CONTRIBUTION_UPDATE = 'ADMIN_CONTRIBUTION_UPDATE',
|
||||||
USER_CREATE_CONTRIBUTION_MESSAGE = 'USER_CREATE_CONTRIBUTION_MESSAGE',
|
// USER_CREATE_CONTRIBUTION_MESSAGE = 'USER_CREATE_CONTRIBUTION_MESSAGE',
|
||||||
ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',
|
// ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',
|
||||||
DELETE_USER = 'DELETE_USER',
|
// DELETE_USER = 'DELETE_USER',
|
||||||
UNDELETE_USER = 'UNDELETE_USER',
|
// UNDELETE_USER = 'UNDELETE_USER',
|
||||||
CHANGE_USER_ROLE = 'CHANGE_USER_ROLE',
|
// CHANGE_USER_ROLE = 'CHANGE_USER_ROLE',
|
||||||
ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION',
|
// ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION',
|
||||||
ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION',
|
// ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION',
|
||||||
CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK',
|
// CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK',
|
||||||
DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK',
|
// DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK',
|
||||||
UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK',
|
// UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK',
|
||||||
}
|
}
|
||||||
|
|||||||
34
backend/src/federation/client/1_0/FederationClient.ts
Normal file
34
backend/src/federation/client/1_0/FederationClient.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { gql } from 'graphql-request'
|
||||||
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
|
import { Community as DbCommunity } from '@entity/Community'
|
||||||
|
import { GraphQLGetClient } from '../GraphQLGetClient'
|
||||||
|
import LogError from '@/server/LogError'
|
||||||
|
|
||||||
|
export async function requestGetPublicKey(dbCom: DbCommunity): Promise<string | undefined> {
|
||||||
|
let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/'
|
||||||
|
endpoint = `${endpoint}${dbCom.apiVersion}/`
|
||||||
|
logger.info(`requestGetPublicKey with endpoint='${endpoint}'...`)
|
||||||
|
|
||||||
|
const graphQLClient = GraphQLGetClient.getInstance(endpoint)
|
||||||
|
logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`)
|
||||||
|
const query = gql`
|
||||||
|
query {
|
||||||
|
getPublicKey {
|
||||||
|
publicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest(query)
|
||||||
|
logger.debug(`Response-Data:`, data, errors, extensions, headers, status)
|
||||||
|
if (data) {
|
||||||
|
logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey)
|
||||||
|
logger.info(`requestGetPublicKey processed successfully`)
|
||||||
|
return data.getPublicKey.publicKey
|
||||||
|
}
|
||||||
|
logger.warn(`requestGetPublicKey processed without response data`)
|
||||||
|
} catch (err) {
|
||||||
|
throw new LogError(`Request-Error:`, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
34
backend/src/federation/client/1_1/FederationClient.ts
Normal file
34
backend/src/federation/client/1_1/FederationClient.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { gql } from 'graphql-request'
|
||||||
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
|
import { Community as DbCommunity } from '@entity/Community'
|
||||||
|
import { GraphQLGetClient } from '../GraphQLGetClient'
|
||||||
|
import LogError from '@/server/LogError'
|
||||||
|
|
||||||
|
export async function requestGetPublicKey(dbCom: DbCommunity): Promise<string | undefined> {
|
||||||
|
let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/'
|
||||||
|
endpoint = `${endpoint}${dbCom.apiVersion}/`
|
||||||
|
logger.info(`requestGetPublicKey with endpoint='${endpoint}'...`)
|
||||||
|
|
||||||
|
const graphQLClient = GraphQLGetClient.getInstance(endpoint)
|
||||||
|
logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`)
|
||||||
|
const query = gql`
|
||||||
|
query {
|
||||||
|
getPublicKey {
|
||||||
|
publicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest(query)
|
||||||
|
logger.debug(`Response-Data:`, data, errors, extensions, headers, status)
|
||||||
|
if (data) {
|
||||||
|
logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey)
|
||||||
|
logger.info(`requestGetPublicKey processed successfully`)
|
||||||
|
return data.getPublicKey.publicKey
|
||||||
|
}
|
||||||
|
logger.warn(`requestGetPublicKey processed without response data`)
|
||||||
|
} catch (err) {
|
||||||
|
throw new LogError(`Request-Error:`, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
35
backend/src/federation/client/GraphQLGetClient.ts
Normal file
35
backend/src/federation/client/GraphQLGetClient.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { GraphQLClient } from 'graphql-request'
|
||||||
|
import { PatchedRequestInit } from 'graphql-request/dist/types'
|
||||||
|
|
||||||
|
export class GraphQLGetClient extends GraphQLClient {
|
||||||
|
private static instance: GraphQLGetClient
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Singleton's constructor should always be private to prevent direct
|
||||||
|
* construction calls with the `new` operator.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-useless-constructor
|
||||||
|
private constructor(url: string, options?: PatchedRequestInit) {
|
||||||
|
super(url, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The static method that controls the access to the singleton instance.
|
||||||
|
*
|
||||||
|
* This implementation let you subclass the Singleton class while keeping
|
||||||
|
* 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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return GraphQLGetClient.instance
|
||||||
|
}
|
||||||
|
}
|
||||||
4
backend/src/federation/enum/apiVersionType.ts
Normal file
4
backend/src/federation/enum/apiVersionType.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export enum ApiVersionType {
|
||||||
|
V1_0 = '1_0',
|
||||||
|
V1_1 = '1_1',
|
||||||
|
}
|
||||||
158
backend/src/federation/validateCommunities.test.ts
Normal file
158
backend/src/federation/validateCommunities.test.ts
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
|
import { logger } from '@test/testSetup'
|
||||||
|
import { Community as DbCommunity } from '@entity/Community'
|
||||||
|
import { testEnvironment, cleanDB } from '@test/helpers'
|
||||||
|
import { validateCommunities } from './validateCommunities'
|
||||||
|
|
||||||
|
let con: any
|
||||||
|
let testEnv: any
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
testEnv = await testEnvironment(logger)
|
||||||
|
con = testEnv.con
|
||||||
|
await cleanDB()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
// await cleanDB()
|
||||||
|
await con.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('validate Communities', () => {
|
||||||
|
/*
|
||||||
|
describe('start validation loop', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
startValidateCommunities(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs loop started', () => {
|
||||||
|
expect(logger.info).toBeCalledWith(
|
||||||
|
`Federation: startValidateCommunities loop with an interval of 0 ms...`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
describe('start validation logic without loop', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await validateCommunities()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs zero communities found', () => {
|
||||||
|
expect(logger.debug).toBeCalledWith(`Federation: found 0 dbCommunities`)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with one Community of api 1_0', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const variables1 = {
|
||||||
|
publicKey: Buffer.from('11111111111111111111111111111111'),
|
||||||
|
apiVersion: '1_0',
|
||||||
|
endPoint: 'http//localhost:5001/api/',
|
||||||
|
lastAnnouncedAt: new Date(),
|
||||||
|
}
|
||||||
|
await DbCommunity.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into(DbCommunity)
|
||||||
|
.values(variables1)
|
||||||
|
.orUpdate({
|
||||||
|
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
||||||
|
overwrite: ['end_point', 'last_announced_at'],
|
||||||
|
})
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await validateCommunities()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs one community found', () => {
|
||||||
|
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
|
||||||
|
})
|
||||||
|
it('logs requestGetPublicKey for community api 1_0 ', () => {
|
||||||
|
expect(logger.info).toBeCalledWith(
|
||||||
|
`requestGetPublicKey with endpoint='http//localhost:5001/api/1_0/'...`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('with two Communities of api 1_0 and 1_1', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const variables2 = {
|
||||||
|
publicKey: Buffer.from('11111111111111111111111111111111'),
|
||||||
|
apiVersion: '1_1',
|
||||||
|
endPoint: 'http//localhost:5001/api/',
|
||||||
|
lastAnnouncedAt: new Date(),
|
||||||
|
}
|
||||||
|
await DbCommunity.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into(DbCommunity)
|
||||||
|
.values(variables2)
|
||||||
|
.orUpdate({
|
||||||
|
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
||||||
|
overwrite: ['end_point', 'last_announced_at'],
|
||||||
|
})
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await validateCommunities()
|
||||||
|
})
|
||||||
|
it('logs two communities found', () => {
|
||||||
|
expect(logger.debug).toBeCalledWith(`Federation: found 2 dbCommunities`)
|
||||||
|
})
|
||||||
|
it('logs requestGetPublicKey for community api 1_0 ', () => {
|
||||||
|
expect(logger.info).toBeCalledWith(
|
||||||
|
`requestGetPublicKey with endpoint='http//localhost:5001/api/1_0/'...`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('logs requestGetPublicKey for community api 1_1 ', () => {
|
||||||
|
expect(logger.info).toBeCalledWith(
|
||||||
|
`requestGetPublicKey with endpoint='http//localhost:5001/api/1_1/'...`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('with three Communities of api 1_0, 1_1 and 2_0', () => {
|
||||||
|
let dbCom: DbCommunity
|
||||||
|
beforeEach(async () => {
|
||||||
|
const variables3 = {
|
||||||
|
publicKey: Buffer.from('11111111111111111111111111111111'),
|
||||||
|
apiVersion: '2_0',
|
||||||
|
endPoint: 'http//localhost:5001/api/',
|
||||||
|
lastAnnouncedAt: new Date(),
|
||||||
|
}
|
||||||
|
await DbCommunity.createQueryBuilder()
|
||||||
|
.insert()
|
||||||
|
.into(DbCommunity)
|
||||||
|
.values(variables3)
|
||||||
|
.orUpdate({
|
||||||
|
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
||||||
|
overwrite: ['end_point', 'last_announced_at'],
|
||||||
|
})
|
||||||
|
.execute()
|
||||||
|
dbCom = await DbCommunity.findOneOrFail({
|
||||||
|
where: { publicKey: variables3.publicKey, apiVersion: variables3.apiVersion },
|
||||||
|
})
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await validateCommunities()
|
||||||
|
})
|
||||||
|
it('logs three community found', () => {
|
||||||
|
expect(logger.debug).toBeCalledWith(`Federation: found 3 dbCommunities`)
|
||||||
|
})
|
||||||
|
it('logs requestGetPublicKey for community api 1_0 ', () => {
|
||||||
|
expect(logger.info).toBeCalledWith(
|
||||||
|
`requestGetPublicKey with endpoint='http//localhost:5001/api/1_0/'...`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('logs requestGetPublicKey for community api 1_1 ', () => {
|
||||||
|
expect(logger.info).toBeCalledWith(
|
||||||
|
`requestGetPublicKey with endpoint='http//localhost:5001/api/1_1/'...`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
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`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
80
backend/src/federation/validateCommunities.ts
Normal file
80
backend/src/federation/validateCommunities.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { Community as DbCommunity } from '@entity/Community'
|
||||||
|
import { IsNull } from '@dbTools/typeorm'
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
import { requestGetPublicKey as v1_0_requestGetPublicKey } from './client/1_0/FederationClient'
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
import { requestGetPublicKey as v1_1_requestGetPublicKey } from './client/1_1/FederationClient'
|
||||||
|
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> {
|
||||||
|
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()
|
||||||
|
setTimeout(run, timerInterval)
|
||||||
|
}, timerInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateCommunities(): Promise<void> {
|
||||||
|
const dbCommunities: DbCommunity[] = await DbCommunity.createQueryBuilder()
|
||||||
|
.where({ foreign: true, verifiedAt: IsNull() })
|
||||||
|
.orWhere('verified_at < last_announced_at')
|
||||||
|
.getMany()
|
||||||
|
|
||||||
|
logger.debug(`Federation: found ${dbCommunities.length} dbCommunities`)
|
||||||
|
dbCommunities.forEach(async function (dbCom) {
|
||||||
|
logger.debug(`Federation: dbCom: ${JSON.stringify(dbCom)}`)
|
||||||
|
const apiValueStrings: string[] = Object.values(ApiVersionType)
|
||||||
|
logger.debug(`suppported ApiVersions=`, apiValueStrings)
|
||||||
|
if (apiValueStrings.includes(dbCom.apiVersion)) {
|
||||||
|
logger.debug(
|
||||||
|
`Federation: validate publicKey for dbCom: ${dbCom.id} with apiVersion=${dbCom.apiVersion}`,
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
const pubKey = await invokeVersionedRequestGetPublicKey(dbCom)
|
||||||
|
logger.info(
|
||||||
|
`Federation: received publicKey=${pubKey} from endpoint=${dbCom.endPoint}/${dbCom.apiVersion}`,
|
||||||
|
)
|
||||||
|
if (pubKey && pubKey === dbCom.publicKey.toString('hex')) {
|
||||||
|
logger.info(`Federation: matching publicKey: ${pubKey}`)
|
||||||
|
DbCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() })
|
||||||
|
logger.debug(`Federation: updated dbCom: ${JSON.stringify(dbCom)}`)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn(
|
||||||
|
`Federation: dbCom: ${dbCom.id} with unsupported apiVersion=${dbCom.apiVersion}; supported versions=${apiValueStrings}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLogError(err: unknown) {
|
||||||
|
return err instanceof LogError
|
||||||
|
}
|
||||||
|
|
||||||
|
async function invokeVersionedRequestGetPublicKey(dbCom: DbCommunity): Promise<string | undefined> {
|
||||||
|
switch (dbCom.apiVersion) {
|
||||||
|
case ApiVersionType.V1_0:
|
||||||
|
return v1_0_requestGetPublicKey(dbCom)
|
||||||
|
case ApiVersionType.V1_1:
|
||||||
|
return v1_1_requestGetPublicKey(dbCom)
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES'
|
|||||||
import { RIGHTS } from '@/auth/RIGHTS'
|
import { RIGHTS } from '@/auth/RIGHTS'
|
||||||
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
|
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
|
||||||
import { User } from '@entity/User'
|
import { User } from '@entity/User'
|
||||||
|
import LogError from '@/server/LogError'
|
||||||
|
|
||||||
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
||||||
context.role = ROLE_UNAUTHORIZED // unauthorized user
|
context.role = ROLE_UNAUTHORIZED // unauthorized user
|
||||||
@ -17,13 +18,13 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
|||||||
|
|
||||||
// Do we have a token?
|
// Do we have a token?
|
||||||
if (!context.token) {
|
if (!context.token) {
|
||||||
throw new Error('401 Unauthorized')
|
throw new LogError('401 Unauthorized')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the token
|
// Decode the token
|
||||||
const decoded = decode(context.token)
|
const decoded = decode(context.token)
|
||||||
if (!decoded) {
|
if (!decoded) {
|
||||||
throw new Error('403.13 - Client certificate revoked')
|
throw new LogError('403.13 - Client certificate revoked')
|
||||||
}
|
}
|
||||||
// Set context gradidoID
|
// Set context gradidoID
|
||||||
context.gradidoID = decoded.gradidoID
|
context.gradidoID = decoded.gradidoID
|
||||||
@ -39,13 +40,13 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
|||||||
context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER
|
context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER
|
||||||
} catch {
|
} catch {
|
||||||
// in case the database query fails (user deleted)
|
// in case the database query fails (user deleted)
|
||||||
throw new Error('401 Unauthorized')
|
throw new LogError('401 Unauthorized')
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for correct rights
|
// check for correct rights
|
||||||
const missingRights = (<RIGHTS[]>rights).filter((right) => !context.role.hasRight(right))
|
const missingRights = (<RIGHTS[]>rights).filter((right) => !context.role.hasRight(right))
|
||||||
if (missingRights.length !== 0) {
|
if (missingRights.length !== 0) {
|
||||||
throw new Error('401 Unauthorized')
|
throw new LogError('401 Unauthorized')
|
||||||
}
|
}
|
||||||
|
|
||||||
// set new header token
|
// set new header token
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
import { ObjectType, Field } from 'type-graphql'
|
|
||||||
|
|
||||||
@ObjectType()
|
|
||||||
export class AdminCreateContributions {
|
|
||||||
constructor() {
|
|
||||||
this.success = false
|
|
||||||
this.successfulContribution = []
|
|
||||||
this.failedContribution = []
|
|
||||||
}
|
|
||||||
|
|
||||||
@Field(() => Boolean)
|
|
||||||
success: boolean
|
|
||||||
|
|
||||||
@Field(() => [String])
|
|
||||||
successfulContribution: string[]
|
|
||||||
|
|
||||||
@Field(() => [String])
|
|
||||||
failedContribution: string[]
|
|
||||||
}
|
|
||||||
@ -61,8 +61,8 @@ export class TransactionLink {
|
|||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class TransactionLinkResult {
|
export class TransactionLinkResult {
|
||||||
@Field(() => Int)
|
@Field(() => Int)
|
||||||
linkCount: number
|
count: number
|
||||||
|
|
||||||
@Field(() => [TransactionLink])
|
@Field(() => [TransactionLink])
|
||||||
linkList: TransactionLink[]
|
links: TransactionLink[]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -257,17 +257,13 @@ describe('Contribution Links', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [new GraphQLError('A Start-Date must be set')],
|
||||||
new GraphQLError('Start-Date is not initialized. A Start-Date must be set!'),
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith('A Start-Date must be set')
|
||||||
'Start-Date is not initialized. A Start-Date must be set!',
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns an error if missing endDate', async () => {
|
it('returns an error if missing endDate', async () => {
|
||||||
@ -282,15 +278,13 @@ describe('Contribution Links', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('End-Date is not initialized. An End-Date must be set!')],
|
errors: [new GraphQLError('An End-Date must be set')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith('An End-Date must be set')
|
||||||
'End-Date is not initialized. An End-Date must be set!',
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns an error if endDate is before startDate', async () => {
|
it('returns an error if endDate is before startDate', async () => {
|
||||||
@ -307,7 +301,7 @@ describe('Contribution Links', () => {
|
|||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [
|
||||||
new GraphQLError(`The value of validFrom must before or equals the validTo!`),
|
new GraphQLError(`The value of validFrom must before or equals the validTo`),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -315,7 +309,7 @@ describe('Contribution Links', () => {
|
|||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith(
|
||||||
`The value of validFrom must before or equals the validTo!`,
|
`The value of validFrom must before or equals the validTo`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -33,10 +33,14 @@ export class ContributionMessageResolver {
|
|||||||
try {
|
try {
|
||||||
const contribution = await DbContribution.findOne({ id: contributionId })
|
const contribution = await DbContribution.findOne({ id: contributionId })
|
||||||
if (!contribution) {
|
if (!contribution) {
|
||||||
throw new Error('Contribution not found')
|
throw new LogError('Contribution not found', contributionId)
|
||||||
}
|
}
|
||||||
if (contribution.userId !== user.id) {
|
if (contribution.userId !== user.id) {
|
||||||
throw new Error('Can not send message to contribution of another user')
|
throw new LogError(
|
||||||
|
'Can not send message to contribution of another user',
|
||||||
|
contribution.userId,
|
||||||
|
user.id,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
contributionMessage.contributionId = contributionId
|
contributionMessage.contributionId = contributionId
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
import Decimal from 'decimal.js-light'
|
import Decimal from 'decimal.js-light'
|
||||||
import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'
|
import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'
|
||||||
import { FindOperator, IsNull, In, getConnection } from '@dbTools/typeorm'
|
import { FindOperator, IsNull, getConnection } from '@dbTools/typeorm'
|
||||||
|
|
||||||
import { Contribution as DbContribution } from '@entity/Contribution'
|
import { Contribution as DbContribution } from '@entity/Contribution'
|
||||||
import { ContributionMessage } from '@entity/ContributionMessage'
|
import { ContributionMessage } from '@entity/ContributionMessage'
|
||||||
@ -8,7 +8,6 @@ import { UserContact } from '@entity/UserContact'
|
|||||||
import { User as DbUser } from '@entity/User'
|
import { User as DbUser } from '@entity/User'
|
||||||
import { Transaction as DbTransaction } from '@entity/Transaction'
|
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||||
|
|
||||||
import { AdminCreateContributions } from '@model/AdminCreateContributions'
|
|
||||||
import { AdminUpdateContribution } from '@model/AdminUpdateContribution'
|
import { AdminUpdateContribution } from '@model/AdminUpdateContribution'
|
||||||
import { Contribution, ContributionListResult } from '@model/Contribution'
|
import { Contribution, ContributionListResult } from '@model/Contribution'
|
||||||
import { Decay } from '@model/Decay'
|
import { Decay } from '@model/Decay'
|
||||||
@ -30,24 +29,21 @@ import { backendLogger as logger } from '@/server/logger'
|
|||||||
import {
|
import {
|
||||||
getCreationDates,
|
getCreationDates,
|
||||||
getUserCreation,
|
getUserCreation,
|
||||||
getUserCreations,
|
|
||||||
validateContribution,
|
validateContribution,
|
||||||
updateCreations,
|
updateCreations,
|
||||||
isValidDateString,
|
isValidDateString,
|
||||||
} from './util/creations'
|
} from './util/creations'
|
||||||
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS, FULL_CREATION_AVAILABLE } from './const/const'
|
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
|
||||||
import {
|
import {
|
||||||
Event,
|
EVENT_CONTRIBUTION_CREATE,
|
||||||
EventContributionCreate,
|
EVENT_CONTRIBUTION_DELETE,
|
||||||
EventContributionDelete,
|
EVENT_CONTRIBUTION_UPDATE,
|
||||||
EventContributionUpdate,
|
EVENT_ADMIN_CONTRIBUTION_CREATE,
|
||||||
EventContributionConfirm,
|
EVENT_ADMIN_CONTRIBUTION_UPDATE,
|
||||||
EventAdminContributionCreate,
|
EVENT_ADMIN_CONTRIBUTION_DELETE,
|
||||||
EventAdminContributionDelete,
|
EVENT_CONTRIBUTION_CONFIRM,
|
||||||
EventAdminContributionDeny,
|
EVENT_ADMIN_CONTRIBUTION_DENY,
|
||||||
EventAdminContributionUpdate,
|
|
||||||
} from '@/event/Event'
|
} from '@/event/Event'
|
||||||
import { writeEvent } from '@/event/EventProtocolEmitter'
|
|
||||||
import { calculateDecay } from '@/util/decay'
|
import { calculateDecay } from '@/util/decay'
|
||||||
import {
|
import {
|
||||||
sendContributionConfirmedEmail,
|
sendContributionConfirmedEmail,
|
||||||
@ -55,8 +51,10 @@ import {
|
|||||||
sendContributionDeniedEmail,
|
sendContributionDeniedEmail,
|
||||||
} from '@/emails/sendEmailVariants'
|
} from '@/emails/sendEmailVariants'
|
||||||
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||||
|
import LogError from '@/server/LogError'
|
||||||
|
|
||||||
import { getLastTransaction } from './util/getLastTransaction'
|
import { getLastTransaction } from './util/getLastTransaction'
|
||||||
|
import { findContributions } from './util/findContributions'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class ContributionResolver {
|
export class ContributionResolver {
|
||||||
@ -67,17 +65,12 @@ export class ContributionResolver {
|
|||||||
@Ctx() context: Context,
|
@Ctx() context: Context,
|
||||||
): Promise<UnconfirmedContribution> {
|
): Promise<UnconfirmedContribution> {
|
||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
if (memo.length > MEMO_MAX_CHARS) {
|
|
||||||
logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`)
|
|
||||||
throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memo.length < MEMO_MIN_CHARS) {
|
if (memo.length < MEMO_MIN_CHARS) {
|
||||||
logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`)
|
throw new LogError('Memo text is too short', memo.length)
|
||||||
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
|
}
|
||||||
|
if (memo.length > MEMO_MAX_CHARS) {
|
||||||
|
throw new LogError('Memo text is too long', memo.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = new Event()
|
|
||||||
|
|
||||||
const user = getUser(context)
|
const user = getUser(context)
|
||||||
const creations = await getUserCreation(user.id, clientTimezoneOffset)
|
const creations = await getUserCreation(user.id, clientTimezoneOffset)
|
||||||
@ -97,11 +90,7 @@ export class ContributionResolver {
|
|||||||
logger.trace('contribution to save', contribution)
|
logger.trace('contribution to save', contribution)
|
||||||
await DbContribution.save(contribution)
|
await DbContribution.save(contribution)
|
||||||
|
|
||||||
const eventCreateContribution = new EventContributionCreate()
|
await EVENT_CONTRIBUTION_CREATE(user.id, contribution.id, amount)
|
||||||
eventCreateContribution.userId = user.id
|
|
||||||
eventCreateContribution.amount = amount
|
|
||||||
eventCreateContribution.contributionId = contribution.id
|
|
||||||
await writeEvent(event.setEventContributionCreate(eventCreateContribution))
|
|
||||||
|
|
||||||
return new UnconfirmedContribution(contribution, user, creations)
|
return new UnconfirmedContribution(contribution, user, creations)
|
||||||
}
|
}
|
||||||
@ -112,20 +101,16 @@ export class ContributionResolver {
|
|||||||
@Arg('id', () => Int) id: number,
|
@Arg('id', () => Int) id: number,
|
||||||
@Ctx() context: Context,
|
@Ctx() context: Context,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const event = new Event()
|
|
||||||
const user = getUser(context)
|
const user = getUser(context)
|
||||||
const contribution = await DbContribution.findOne(id)
|
const contribution = await DbContribution.findOne(id)
|
||||||
if (!contribution) {
|
if (!contribution) {
|
||||||
logger.error('Contribution not found for given id')
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error('Contribution not found for given id.')
|
|
||||||
}
|
}
|
||||||
if (contribution.userId !== user.id) {
|
if (contribution.userId !== user.id) {
|
||||||
logger.error('Can not delete contribution of another user')
|
throw new LogError('Can not delete contribution of another user', contribution, user.id)
|
||||||
throw new Error('Can not delete contribution of another user')
|
|
||||||
}
|
}
|
||||||
if (contribution.confirmedAt) {
|
if (contribution.confirmedAt) {
|
||||||
logger.error('A confirmed contribution can not be deleted')
|
throw new LogError('A confirmed contribution can not be deleted', contribution)
|
||||||
throw new Error('A confirmed contribution can not be deleted')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contribution.contributionStatus = ContributionStatus.DELETED
|
contribution.contributionStatus = ContributionStatus.DELETED
|
||||||
@ -133,11 +118,7 @@ export class ContributionResolver {
|
|||||||
contribution.deletedAt = new Date()
|
contribution.deletedAt = new Date()
|
||||||
await contribution.save()
|
await contribution.save()
|
||||||
|
|
||||||
const eventDeleteContribution = new EventContributionDelete()
|
await EVENT_CONTRIBUTION_DELETE(user.id, contribution.id, contribution.amount)
|
||||||
eventDeleteContribution.userId = user.id
|
|
||||||
eventDeleteContribution.contributionId = contribution.id
|
|
||||||
eventDeleteContribution.amount = contribution.amount
|
|
||||||
await writeEvent(event.setEventContributionDelete(eventDeleteContribution))
|
|
||||||
|
|
||||||
const res = await contribution.softRemove()
|
const res = await contribution.softRemove()
|
||||||
return !!res
|
return !!res
|
||||||
@ -186,24 +167,14 @@ export class ContributionResolver {
|
|||||||
@Arg('statusFilter', () => [ContributionStatus], { nullable: true })
|
@Arg('statusFilter', () => [ContributionStatus], { nullable: true })
|
||||||
statusFilter?: ContributionStatus[],
|
statusFilter?: ContributionStatus[],
|
||||||
): Promise<ContributionListResult> {
|
): Promise<ContributionListResult> {
|
||||||
const where: {
|
const [dbContributions, count] = await findContributions(
|
||||||
contributionStatus?: FindOperator<string> | null
|
order,
|
||||||
} = {}
|
currentPage,
|
||||||
|
pageSize,
|
||||||
|
false,
|
||||||
|
statusFilter,
|
||||||
|
)
|
||||||
|
|
||||||
if (statusFilter && statusFilter.length) {
|
|
||||||
where.contributionStatus = In(statusFilter)
|
|
||||||
}
|
|
||||||
|
|
||||||
const [dbContributions, count] = await getConnection()
|
|
||||||
.createQueryBuilder()
|
|
||||||
.select('c')
|
|
||||||
.from(DbContribution, 'c')
|
|
||||||
.innerJoinAndSelect('c.user', 'u')
|
|
||||||
.where(where)
|
|
||||||
.orderBy('c.createdAt', order)
|
|
||||||
.limit(pageSize)
|
|
||||||
.offset((currentPage - 1) * pageSize)
|
|
||||||
.getManyAndCount()
|
|
||||||
return new ContributionListResult(
|
return new ContributionListResult(
|
||||||
count,
|
count,
|
||||||
dbContributions.map((contribution) => new Contribution(contribution, contribution.user)),
|
dbContributions.map((contribution) => new Contribution(contribution, contribution.user)),
|
||||||
@ -219,14 +190,11 @@ export class ContributionResolver {
|
|||||||
@Ctx() context: Context,
|
@Ctx() context: Context,
|
||||||
): Promise<UnconfirmedContribution> {
|
): Promise<UnconfirmedContribution> {
|
||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
if (memo.length > MEMO_MAX_CHARS) {
|
|
||||||
logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`)
|
|
||||||
throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memo.length < MEMO_MIN_CHARS) {
|
if (memo.length < MEMO_MIN_CHARS) {
|
||||||
logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`)
|
throw new LogError('Memo text is too short', memo.length)
|
||||||
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
|
}
|
||||||
|
if (memo.length > MEMO_MAX_CHARS) {
|
||||||
|
throw new LogError('Memo text is too long', memo.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = getUser(context)
|
const user = getUser(context)
|
||||||
@ -235,22 +203,22 @@ export class ContributionResolver {
|
|||||||
where: { id: contributionId, confirmedAt: IsNull(), deniedAt: IsNull() },
|
where: { id: contributionId, confirmedAt: IsNull(), deniedAt: IsNull() },
|
||||||
})
|
})
|
||||||
if (!contributionToUpdate) {
|
if (!contributionToUpdate) {
|
||||||
logger.error('No contribution found to given id')
|
throw new LogError('Contribution not found', contributionId)
|
||||||
throw new Error('No contribution found to given id.')
|
|
||||||
}
|
}
|
||||||
if (contributionToUpdate.userId !== user.id) {
|
if (contributionToUpdate.userId !== user.id) {
|
||||||
logger.error('user of the pending contribution and send user does not correspond')
|
throw new LogError(
|
||||||
throw new Error('user of the pending contribution and send user does not correspond')
|
'Can not update contribution of another user',
|
||||||
|
contributionToUpdate,
|
||||||
|
user.id,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
contributionToUpdate.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
contributionToUpdate.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
||||||
contributionToUpdate.contributionStatus !== ContributionStatus.PENDING
|
contributionToUpdate.contributionStatus !== ContributionStatus.PENDING
|
||||||
) {
|
) {
|
||||||
logger.error(
|
throw new LogError(
|
||||||
`Contribution can not be updated since the state is ${contributionToUpdate.contributionStatus}`,
|
'Contribution can not be updated due to status',
|
||||||
)
|
contributionToUpdate.contributionStatus,
|
||||||
throw new Error(
|
|
||||||
`Contribution can not be updated since the state is ${contributionToUpdate.contributionStatus}`,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const creationDateObj = new Date(creationDate)
|
const creationDateObj = new Date(creationDate)
|
||||||
@ -258,8 +226,7 @@ export class ContributionResolver {
|
|||||||
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
||||||
creations = updateCreations(creations, contributionToUpdate, clientTimezoneOffset)
|
creations = updateCreations(creations, contributionToUpdate, clientTimezoneOffset)
|
||||||
} else {
|
} else {
|
||||||
logger.error('Currently the month of the contribution cannot change.')
|
throw new LogError('Month of contribution can not be changed')
|
||||||
throw new Error('Currently the month of the contribution cannot change.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// all possible cases not to be true are thrown in this function
|
// all possible cases not to be true are thrown in this function
|
||||||
@ -288,13 +255,7 @@ export class ContributionResolver {
|
|||||||
contributionToUpdate.updatedAt = new Date()
|
contributionToUpdate.updatedAt = new Date()
|
||||||
DbContribution.save(contributionToUpdate)
|
DbContribution.save(contributionToUpdate)
|
||||||
|
|
||||||
const event = new Event()
|
await EVENT_CONTRIBUTION_UPDATE(user.id, contributionId, amount)
|
||||||
|
|
||||||
const eventUpdateContribution = new EventContributionUpdate()
|
|
||||||
eventUpdateContribution.userId = user.id
|
|
||||||
eventUpdateContribution.contributionId = contributionId
|
|
||||||
eventUpdateContribution.amount = amount
|
|
||||||
await writeEvent(event.setEventContributionUpdate(eventUpdateContribution))
|
|
||||||
|
|
||||||
return new UnconfirmedContribution(contributionToUpdate, user, creations)
|
return new UnconfirmedContribution(contributionToUpdate, user, creations)
|
||||||
}
|
}
|
||||||
@ -310,32 +271,26 @@ export class ContributionResolver {
|
|||||||
)
|
)
|
||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
if (!isValidDateString(creationDate)) {
|
if (!isValidDateString(creationDate)) {
|
||||||
logger.error(`invalid Date for creationDate=${creationDate}`)
|
throw new LogError('CreationDate is invalid', creationDate)
|
||||||
throw new Error(`invalid Date for creationDate=${creationDate}`)
|
|
||||||
}
|
}
|
||||||
const emailContact = await UserContact.findOne({
|
const emailContact = await UserContact.findOne({
|
||||||
where: { email },
|
where: { email },
|
||||||
withDeleted: true,
|
withDeleted: true,
|
||||||
relations: ['user'],
|
relations: ['user'],
|
||||||
})
|
})
|
||||||
if (!emailContact) {
|
if (!emailContact || !emailContact.user) {
|
||||||
logger.error(`Could not find user with email: ${email}`)
|
throw new LogError('Could not find user', email)
|
||||||
throw new Error(`Could not find user with email: ${email}`)
|
|
||||||
}
|
}
|
||||||
if (emailContact.deletedAt) {
|
if (emailContact.deletedAt || emailContact.user.deletedAt) {
|
||||||
logger.error('This emailContact was deleted. Cannot create a contribution.')
|
throw new LogError('Cannot create contribution since the user was deleted', emailContact)
|
||||||
throw new Error('This emailContact was deleted. Cannot create a contribution.')
|
|
||||||
}
|
|
||||||
if (emailContact.user.deletedAt) {
|
|
||||||
logger.error('This user was deleted. Cannot create a contribution.')
|
|
||||||
throw new Error('This user was deleted. Cannot create a contribution.')
|
|
||||||
}
|
}
|
||||||
if (!emailContact.emailChecked) {
|
if (!emailContact.emailChecked) {
|
||||||
logger.error('Contribution could not be saved, Email is not activated')
|
throw new LogError(
|
||||||
throw new Error('Contribution could not be saved, Email is not activated')
|
'Cannot create contribution since the users email is not activated',
|
||||||
|
emailContact,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = new Event()
|
|
||||||
const moderator = getUser(context)
|
const moderator = getUser(context)
|
||||||
logger.trace('moderator: ', moderator.id)
|
logger.trace('moderator: ', moderator.id)
|
||||||
const creations = await getUserCreation(emailContact.userId, clientTimezoneOffset)
|
const creations = await getUserCreation(emailContact.userId, clientTimezoneOffset)
|
||||||
@ -357,42 +312,11 @@ export class ContributionResolver {
|
|||||||
|
|
||||||
await DbContribution.save(contribution)
|
await DbContribution.save(contribution)
|
||||||
|
|
||||||
const eventAdminCreateContribution = new EventAdminContributionCreate()
|
await EVENT_ADMIN_CONTRIBUTION_CREATE(moderator.id, contribution.id, amount)
|
||||||
eventAdminCreateContribution.userId = moderator.id
|
|
||||||
eventAdminCreateContribution.amount = amount
|
|
||||||
eventAdminCreateContribution.contributionId = contribution.id
|
|
||||||
await writeEvent(event.setEventAdminContributionCreate(eventAdminCreateContribution))
|
|
||||||
|
|
||||||
return getUserCreation(emailContact.userId, clientTimezoneOffset)
|
return getUserCreation(emailContact.userId, clientTimezoneOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.ADMIN_CREATE_CONTRIBUTIONS])
|
|
||||||
@Mutation(() => AdminCreateContributions)
|
|
||||||
async adminCreateContributions(
|
|
||||||
@Arg('pendingCreations', () => [AdminCreateContributionArgs])
|
|
||||||
contributions: AdminCreateContributionArgs[],
|
|
||||||
@Ctx() context: Context,
|
|
||||||
): Promise<AdminCreateContributions> {
|
|
||||||
let success = false
|
|
||||||
const successfulContribution: string[] = []
|
|
||||||
const failedContribution: string[] = []
|
|
||||||
for (const contribution of contributions) {
|
|
||||||
await this.adminCreateContribution(contribution, context)
|
|
||||||
.then(() => {
|
|
||||||
successfulContribution.push(contribution.email)
|
|
||||||
success = true
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
failedContribution.push(contribution.email)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
success,
|
|
||||||
successfulContribution,
|
|
||||||
failedContribution,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Authorized([RIGHTS.ADMIN_UPDATE_CONTRIBUTION])
|
@Authorized([RIGHTS.ADMIN_UPDATE_CONTRIBUTION])
|
||||||
@Mutation(() => AdminUpdateContribution)
|
@Mutation(() => AdminUpdateContribution)
|
||||||
async adminUpdateContribution(
|
async adminUpdateContribution(
|
||||||
@ -405,18 +329,11 @@ export class ContributionResolver {
|
|||||||
withDeleted: true,
|
withDeleted: true,
|
||||||
relations: ['user'],
|
relations: ['user'],
|
||||||
})
|
})
|
||||||
if (!emailContact) {
|
if (!emailContact || !emailContact.user) {
|
||||||
logger.error(`Could not find UserContact with email: ${email}`)
|
throw new LogError('Could not find User', email)
|
||||||
throw new Error(`Could not find UserContact with email: ${email}`)
|
|
||||||
}
|
}
|
||||||
const user = emailContact.user
|
if (emailContact.deletedAt || emailContact.user.deletedAt) {
|
||||||
if (!user) {
|
throw new LogError('User was deleted', email)
|
||||||
logger.error(`Could not find User to emailContact: ${email}`)
|
|
||||||
throw new Error(`Could not find User to emailContact: ${email}`)
|
|
||||||
}
|
|
||||||
if (user.deletedAt) {
|
|
||||||
logger.error(`User was deleted (${email})`)
|
|
||||||
throw new Error(`User was deleted (${email})`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const moderator = getUser(context)
|
const moderator = getUser(context)
|
||||||
@ -425,28 +342,25 @@ export class ContributionResolver {
|
|||||||
where: { id, confirmedAt: IsNull(), deniedAt: IsNull() },
|
where: { id, confirmedAt: IsNull(), deniedAt: IsNull() },
|
||||||
})
|
})
|
||||||
if (!contributionToUpdate) {
|
if (!contributionToUpdate) {
|
||||||
logger.error('No contribution found to given id.')
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error('No contribution found to given id.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contributionToUpdate.userId !== user.id) {
|
if (contributionToUpdate.userId !== emailContact.user.id) {
|
||||||
logger.error('user of the pending contribution and send user does not correspond')
|
throw new LogError('User of the pending contribution and send user does not correspond')
|
||||||
throw new Error('user of the pending contribution and send user does not correspond')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contributionToUpdate.moderatorId === null) {
|
if (contributionToUpdate.moderatorId === null) {
|
||||||
logger.error('An admin is not allowed to update a user contribution.')
|
throw new LogError('An admin is not allowed to update an user contribution')
|
||||||
throw new Error('An admin is not allowed to update a user contribution.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const creationDateObj = new Date(creationDate)
|
const creationDateObj = new Date(creationDate)
|
||||||
let creations = await getUserCreation(user.id, clientTimezoneOffset)
|
let creations = await getUserCreation(emailContact.user.id, clientTimezoneOffset)
|
||||||
|
|
||||||
|
// TODO: remove this restriction
|
||||||
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
||||||
creations = updateCreations(creations, contributionToUpdate, clientTimezoneOffset)
|
creations = updateCreations(creations, contributionToUpdate, clientTimezoneOffset)
|
||||||
} else {
|
} else {
|
||||||
logger.error('Currently the month of the contribution cannot change.')
|
throw new LogError('Month of contribution can not be changed')
|
||||||
throw new Error('Currently the month of the contribution cannot change.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// all possible cases not to be true are thrown in this function
|
// all possible cases not to be true are thrown in this function
|
||||||
@ -464,53 +378,33 @@ export class ContributionResolver {
|
|||||||
result.memo = contributionToUpdate.memo
|
result.memo = contributionToUpdate.memo
|
||||||
result.date = contributionToUpdate.contributionDate
|
result.date = contributionToUpdate.contributionDate
|
||||||
|
|
||||||
result.creation = await getUserCreation(user.id, clientTimezoneOffset)
|
result.creation = await getUserCreation(emailContact.user.id, clientTimezoneOffset)
|
||||||
|
|
||||||
const event = new Event()
|
await EVENT_ADMIN_CONTRIBUTION_UPDATE(emailContact.user.id, contributionToUpdate.id, amount)
|
||||||
const eventAdminContributionUpdate = new EventAdminContributionUpdate()
|
|
||||||
eventAdminContributionUpdate.userId = user.id
|
|
||||||
eventAdminContributionUpdate.amount = amount
|
|
||||||
eventAdminContributionUpdate.contributionId = contributionToUpdate.id
|
|
||||||
await writeEvent(event.setEventAdminContributionUpdate(eventAdminContributionUpdate))
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.LIST_UNCONFIRMED_CONTRIBUTIONS])
|
@Authorized([RIGHTS.LIST_UNCONFIRMED_CONTRIBUTIONS])
|
||||||
@Query(() => [UnconfirmedContribution])
|
@Query(() => ContributionListResult) // [UnconfirmedContribution]
|
||||||
async listUnconfirmedContributions(@Ctx() context: Context): Promise<UnconfirmedContribution[]> {
|
async adminListAllContributions(
|
||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
@Args()
|
||||||
const contributions = await getConnection()
|
{ currentPage = 1, pageSize = 3, order = Order.DESC }: Paginated,
|
||||||
.createQueryBuilder()
|
@Arg('statusFilter', () => [ContributionStatus], { nullable: true })
|
||||||
.select('c')
|
statusFilter?: ContributionStatus[],
|
||||||
.from(DbContribution, 'c')
|
): Promise<ContributionListResult> {
|
||||||
.leftJoinAndSelect('c.messages', 'm')
|
const [dbContributions, count] = await findContributions(
|
||||||
.where({ confirmedAt: IsNull() })
|
order,
|
||||||
.andWhere({ deniedAt: IsNull() })
|
currentPage,
|
||||||
.getMany()
|
pageSize,
|
||||||
|
true,
|
||||||
|
statusFilter,
|
||||||
|
)
|
||||||
|
|
||||||
if (contributions.length === 0) {
|
return new ContributionListResult(
|
||||||
return []
|
count,
|
||||||
}
|
dbContributions.map((contribution) => new Contribution(contribution, contribution.user)),
|
||||||
|
)
|
||||||
const userIds = contributions.map((p) => p.userId)
|
|
||||||
const userCreations = await getUserCreations(userIds, clientTimezoneOffset)
|
|
||||||
const users = await DbUser.find({
|
|
||||||
where: { id: In(userIds) },
|
|
||||||
withDeleted: true,
|
|
||||||
relations: ['emailContact'],
|
|
||||||
})
|
|
||||||
|
|
||||||
return contributions.map((contribution) => {
|
|
||||||
const user = users.find((u) => u.id === contribution.userId)
|
|
||||||
const creation = userCreations.find((c) => c.id === contribution.userId)
|
|
||||||
|
|
||||||
return new UnconfirmedContribution(
|
|
||||||
contribution,
|
|
||||||
user,
|
|
||||||
creation ? creation.creations : FULL_CREATION_AVAILABLE,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.ADMIN_DELETE_CONTRIBUTION])
|
@Authorized([RIGHTS.ADMIN_DELETE_CONTRIBUTION])
|
||||||
@ -521,19 +415,17 @@ export class ContributionResolver {
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const contribution = await DbContribution.findOne(id)
|
const contribution = await DbContribution.findOne(id)
|
||||||
if (!contribution) {
|
if (!contribution) {
|
||||||
logger.error(`Contribution not found for given id: ${id}`)
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error('Contribution not found for given id.')
|
|
||||||
}
|
}
|
||||||
if (contribution.confirmedAt) {
|
if (contribution.confirmedAt) {
|
||||||
logger.error('A confirmed contribution can not be deleted')
|
throw new LogError('A confirmed contribution can not be deleted')
|
||||||
throw new Error('A confirmed contribution can not be deleted')
|
|
||||||
}
|
}
|
||||||
const moderator = getUser(context)
|
const moderator = getUser(context)
|
||||||
if (
|
if (
|
||||||
contribution.contributionType === ContributionType.USER &&
|
contribution.contributionType === ContributionType.USER &&
|
||||||
contribution.userId === moderator.id
|
contribution.userId === moderator.id
|
||||||
) {
|
) {
|
||||||
throw new Error('Own contribution can not be deleted as admin')
|
throw new LogError('Own contribution can not be deleted as admin')
|
||||||
}
|
}
|
||||||
const user = await DbUser.findOneOrFail(
|
const user = await DbUser.findOneOrFail(
|
||||||
{ id: contribution.userId },
|
{ id: contribution.userId },
|
||||||
@ -544,12 +436,8 @@ export class ContributionResolver {
|
|||||||
await contribution.save()
|
await contribution.save()
|
||||||
const res = await contribution.softRemove()
|
const res = await contribution.softRemove()
|
||||||
|
|
||||||
const event = new Event()
|
await EVENT_ADMIN_CONTRIBUTION_DELETE(contribution.userId, contribution.id, contribution.amount)
|
||||||
const eventAdminContributionDelete = new EventAdminContributionDelete()
|
|
||||||
eventAdminContributionDelete.userId = contribution.userId
|
|
||||||
eventAdminContributionDelete.amount = contribution.amount
|
|
||||||
eventAdminContributionDelete.contributionId = contribution.id
|
|
||||||
await writeEvent(event.setEventAdminContributionDelete(eventAdminContributionDelete))
|
|
||||||
sendContributionDeletedEmail({
|
sendContributionDeletedEmail({
|
||||||
firstName: user.firstName,
|
firstName: user.firstName,
|
||||||
lastName: user.lastName,
|
lastName: user.lastName,
|
||||||
@ -575,29 +463,24 @@ export class ContributionResolver {
|
|||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
const contribution = await DbContribution.findOne(id)
|
const contribution = await DbContribution.findOne(id)
|
||||||
if (!contribution) {
|
if (!contribution) {
|
||||||
logger.error(`Contribution not found for given id: ${id}`)
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error('Contribution not found to given id.')
|
|
||||||
}
|
}
|
||||||
if (contribution.confirmedAt) {
|
if (contribution.confirmedAt) {
|
||||||
logger.error(`Contribution already confirmd: ${id}`)
|
throw new LogError('Contribution already confirmed', id)
|
||||||
throw new Error('Contribution already confirmd.')
|
|
||||||
}
|
}
|
||||||
if (contribution.contributionStatus === 'DENIED') {
|
if (contribution.contributionStatus === 'DENIED') {
|
||||||
logger.error(`Contribution already denied: ${id}`)
|
throw new LogError('Contribution already denied', id)
|
||||||
throw new Error('Contribution already denied.')
|
|
||||||
}
|
}
|
||||||
const moderatorUser = getUser(context)
|
const moderatorUser = getUser(context)
|
||||||
if (moderatorUser.id === contribution.userId) {
|
if (moderatorUser.id === contribution.userId) {
|
||||||
logger.error('Moderator can not confirm own contribution')
|
throw new LogError('Moderator can not confirm own contribution')
|
||||||
throw new Error('Moderator can not confirm own contribution')
|
|
||||||
}
|
}
|
||||||
const user = await DbUser.findOneOrFail(
|
const user = await DbUser.findOneOrFail(
|
||||||
{ id: contribution.userId },
|
{ id: contribution.userId },
|
||||||
{ withDeleted: true, relations: ['emailContact'] },
|
{ withDeleted: true, relations: ['emailContact'] },
|
||||||
)
|
)
|
||||||
if (user.deletedAt) {
|
if (user.deletedAt) {
|
||||||
logger.error('This user was deleted. Cannot confirm a contribution.')
|
throw new LogError('Can not confirm contribution since the user was deleted')
|
||||||
throw new Error('This user was deleted. Cannot confirm a contribution.')
|
|
||||||
}
|
}
|
||||||
const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false)
|
const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false)
|
||||||
validateContribution(
|
validateContribution(
|
||||||
@ -661,18 +544,12 @@ export class ContributionResolver {
|
|||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await queryRunner.rollbackTransaction()
|
await queryRunner.rollbackTransaction()
|
||||||
logger.error('Creation was not successful', e)
|
throw new LogError('Creation was not successful', e)
|
||||||
throw new Error('Creation was not successful.')
|
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = new Event()
|
await EVENT_CONTRIBUTION_CONFIRM(user.id, contribution.id, contribution.amount)
|
||||||
const eventContributionConfirm = new EventContributionConfirm()
|
|
||||||
eventContributionConfirm.userId = user.id
|
|
||||||
eventContributionConfirm.amount = contribution.amount
|
|
||||||
eventContributionConfirm.contributionId = contribution.id
|
|
||||||
await writeEvent(event.setEventContributionConfirm(eventContributionConfirm))
|
|
||||||
} finally {
|
} finally {
|
||||||
releaseLock()
|
releaseLock()
|
||||||
}
|
}
|
||||||
@ -737,17 +614,16 @@ export class ContributionResolver {
|
|||||||
deniedBy: IsNull(),
|
deniedBy: IsNull(),
|
||||||
})
|
})
|
||||||
if (!contributionToUpdate) {
|
if (!contributionToUpdate) {
|
||||||
logger.error(`Contribution not found for given id: ${id}`)
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error(`Contribution not found for given id.`)
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
contributionToUpdate.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
contributionToUpdate.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
||||||
contributionToUpdate.contributionStatus !== ContributionStatus.PENDING
|
contributionToUpdate.contributionStatus !== ContributionStatus.PENDING
|
||||||
) {
|
) {
|
||||||
logger.error(
|
throw new LogError(
|
||||||
`Contribution state (${contributionToUpdate.contributionStatus}) is not allowed.`,
|
'Status of the contribution is not allowed',
|
||||||
|
contributionToUpdate.contributionStatus,
|
||||||
)
|
)
|
||||||
throw new Error(`State of the contribution is not allowed.`)
|
|
||||||
}
|
}
|
||||||
const moderator = getUser(context)
|
const moderator = getUser(context)
|
||||||
const user = await DbUser.findOne(
|
const user = await DbUser.findOne(
|
||||||
@ -755,10 +631,7 @@ export class ContributionResolver {
|
|||||||
{ relations: ['emailContact'] },
|
{ relations: ['emailContact'] },
|
||||||
)
|
)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
logger.error(
|
throw new LogError('Could not find User of the Contribution', contributionToUpdate.userId)
|
||||||
`Could not find User for the Contribution (userId: ${contributionToUpdate.userId}).`,
|
|
||||||
)
|
|
||||||
throw new Error('Could not find User for the Contribution.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contributionToUpdate.contributionStatus = ContributionStatus.DENIED
|
contributionToUpdate.contributionStatus = ContributionStatus.DENIED
|
||||||
@ -766,12 +639,12 @@ export class ContributionResolver {
|
|||||||
contributionToUpdate.deniedAt = new Date()
|
contributionToUpdate.deniedAt = new Date()
|
||||||
const res = await contributionToUpdate.save()
|
const res = await contributionToUpdate.save()
|
||||||
|
|
||||||
const event = new Event()
|
await EVENT_ADMIN_CONTRIBUTION_DENY(
|
||||||
const eventAdminContributionDeny = new EventAdminContributionDeny()
|
contributionToUpdate.userId,
|
||||||
eventAdminContributionDeny.userId = contributionToUpdate.userId
|
moderator.id,
|
||||||
eventAdminContributionDeny.amount = contributionToUpdate.amount
|
contributionToUpdate.id,
|
||||||
eventAdminContributionDeny.contributionId = contributionToUpdate.id
|
contributionToUpdate.amount,
|
||||||
await writeEvent(event.setEventAdminContributionDeny(eventAdminContributionDeny))
|
)
|
||||||
|
|
||||||
sendContributionDeniedEmail({
|
sendContributionDeniedEmail({
|
||||||
firstName: user.firstName,
|
firstName: user.firstName,
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { Context, getUser } from '@/server/context'
|
|||||||
import CONFIG from '@/config'
|
import CONFIG from '@/config'
|
||||||
import { apiGet, apiPost } from '@/apis/HttpRequest'
|
import { apiGet, apiPost } from '@/apis/HttpRequest'
|
||||||
import { RIGHTS } from '@/auth/RIGHTS'
|
import { RIGHTS } from '@/auth/RIGHTS'
|
||||||
|
import LogError from '@/server/LogError'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class GdtResolver {
|
export class GdtResolver {
|
||||||
@ -25,11 +26,11 @@ export class GdtResolver {
|
|||||||
`${CONFIG.GDT_API_URL}/GdtEntries/listPerEmailApi/${userEntity.emailContact.email}/${currentPage}/${pageSize}/${order}`,
|
`${CONFIG.GDT_API_URL}/GdtEntries/listPerEmailApi/${userEntity.emailContact.email}/${currentPage}/${pageSize}/${order}`,
|
||||||
)
|
)
|
||||||
if (!resultGDT.success) {
|
if (!resultGDT.success) {
|
||||||
throw new Error(resultGDT.data)
|
throw new LogError(resultGDT.data)
|
||||||
}
|
}
|
||||||
return new GdtEntryList(resultGDT.data)
|
return new GdtEntryList(resultGDT.data)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error('GDT Server is not reachable.')
|
throw new LogError('GDT Server is not reachable')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ export class GdtResolver {
|
|||||||
email: user.emailContact.email,
|
email: user.emailContact.email,
|
||||||
})
|
})
|
||||||
if (!resultGDTSum.success) {
|
if (!resultGDTSum.success) {
|
||||||
throw new Error('Call not successful')
|
throw new LogError('Call not successful')
|
||||||
}
|
}
|
||||||
return Number(resultGDTSum.data.sum) || 0
|
return Number(resultGDTSum.data.sum) || 0
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -59,7 +60,7 @@ export class GdtResolver {
|
|||||||
// load user
|
// load user
|
||||||
const resultPID = await apiGet(`${CONFIG.GDT_API_URL}/publishers/checkPidApi/${pid}`)
|
const resultPID = await apiGet(`${CONFIG.GDT_API_URL}/publishers/checkPidApi/${pid}`)
|
||||||
if (!resultPID.success) {
|
if (!resultPID.success) {
|
||||||
throw new Error(resultPID.data)
|
throw new LogError(resultPID.data)
|
||||||
}
|
}
|
||||||
return resultPID.data.pid
|
return resultPID.data.pid
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
import { randomBytes } from 'crypto'
|
import { randomBytes } from 'crypto'
|
||||||
import Decimal from 'decimal.js-light'
|
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 { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
||||||
import { User as DbUser } from '@entity/User'
|
import { User as DbUser } from '@entity/User'
|
||||||
@ -13,7 +13,6 @@ import { User } from '@model/User'
|
|||||||
import { ContributionLink } from '@model/ContributionLink'
|
import { ContributionLink } from '@model/ContributionLink'
|
||||||
import { Decay } from '@model/Decay'
|
import { Decay } from '@model/Decay'
|
||||||
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
|
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
|
||||||
import { Order } from '@enum/Order'
|
|
||||||
import { ContributionType } from '@enum/ContributionType'
|
import { ContributionType } from '@enum/ContributionType'
|
||||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||||
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||||
@ -32,8 +31,10 @@ import { getUserCreation, validateContribution } from './util/creations'
|
|||||||
import { executeTransaction } from './TransactionResolver'
|
import { executeTransaction } from './TransactionResolver'
|
||||||
import QueryLinkResult from '@union/QueryLinkResult'
|
import QueryLinkResult from '@union/QueryLinkResult'
|
||||||
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||||
|
import LogError from '@/server/LogError'
|
||||||
|
|
||||||
import { getLastTransaction } from './util/getLastTransaction'
|
import { getLastTransaction } from './util/getLastTransaction'
|
||||||
|
import transactionLinkList from './util/transactionLinkList'
|
||||||
|
|
||||||
// TODO: do not export, test it inside the resolver
|
// TODO: do not export, test it inside the resolver
|
||||||
export const transactionLinkCode = (date: Date): string => {
|
export const transactionLinkCode = (date: Date): string => {
|
||||||
@ -65,12 +66,16 @@ export class TransactionLinkResolver {
|
|||||||
const createdDate = new Date()
|
const createdDate = new Date()
|
||||||
const validUntil = transactionLinkExpireDate(createdDate)
|
const validUntil = transactionLinkExpireDate(createdDate)
|
||||||
|
|
||||||
|
if (amount.lessThanOrEqualTo(0)) {
|
||||||
|
throw new LogError('Amount must be a positive number', amount)
|
||||||
|
}
|
||||||
|
|
||||||
const holdAvailableAmount = amount.minus(calculateDecay(amount, createdDate, validUntil).decay)
|
const holdAvailableAmount = amount.minus(calculateDecay(amount, createdDate, validUntil).decay)
|
||||||
|
|
||||||
// validate amount
|
// validate amount
|
||||||
const sendBalance = await calculateBalance(user.id, holdAvailableAmount.mul(-1), createdDate)
|
const sendBalance = await calculateBalance(user.id, holdAvailableAmount.mul(-1), createdDate)
|
||||||
if (!sendBalance) {
|
if (!sendBalance) {
|
||||||
throw new Error("user hasn't enough GDD or amount is < 0")
|
throw new LogError('User has not enough GDD', user.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const transactionLink = DbTransactionLink.create()
|
const transactionLink = DbTransactionLink.create()
|
||||||
@ -81,8 +86,8 @@ export class TransactionLinkResolver {
|
|||||||
transactionLink.code = transactionLinkCode(createdDate)
|
transactionLink.code = transactionLinkCode(createdDate)
|
||||||
transactionLink.createdAt = createdDate
|
transactionLink.createdAt = createdDate
|
||||||
transactionLink.validUntil = validUntil
|
transactionLink.validUntil = validUntil
|
||||||
await DbTransactionLink.save(transactionLink).catch(() => {
|
await DbTransactionLink.save(transactionLink).catch((e) => {
|
||||||
throw new Error('Unable to save transaction link')
|
throw new LogError('Unable to save transaction link', e)
|
||||||
})
|
})
|
||||||
|
|
||||||
return new TransactionLink(transactionLink, new User(user))
|
return new TransactionLink(transactionLink, new User(user))
|
||||||
@ -98,19 +103,23 @@ export class TransactionLinkResolver {
|
|||||||
|
|
||||||
const transactionLink = await DbTransactionLink.findOne({ id })
|
const transactionLink = await DbTransactionLink.findOne({ id })
|
||||||
if (!transactionLink) {
|
if (!transactionLink) {
|
||||||
throw new Error('Transaction Link not found!')
|
throw new LogError('Transaction link not found', id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionLink.userId !== user.id) {
|
if (transactionLink.userId !== user.id) {
|
||||||
throw new Error('Transaction Link cannot be deleted!')
|
throw new LogError(
|
||||||
|
'Transaction link cannot be deleted by another user',
|
||||||
|
transactionLink.userId,
|
||||||
|
user.id,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionLink.redeemedBy) {
|
if (transactionLink.redeemedBy) {
|
||||||
throw new Error('Transaction Link already redeemed!')
|
throw new LogError('Transaction link already redeemed', transactionLink.redeemedBy)
|
||||||
}
|
}
|
||||||
|
|
||||||
await transactionLink.softRemove().catch(() => {
|
await transactionLink.softRemove().catch((e) => {
|
||||||
throw new Error('Transaction Link could not be deleted!')
|
throw new LogError('Transaction link could not be deleted', e)
|
||||||
})
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@ -136,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])
|
@Authorized([RIGHTS.REDEEM_TRANSACTION_LINK])
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async redeemTransactionLink(
|
async redeemTransactionLink(
|
||||||
@ -186,24 +171,15 @@ export class TransactionLinkResolver {
|
|||||||
.where('contributionLink.code = :code', { code: code.replace('CL-', '') })
|
.where('contributionLink.code = :code', { code: code.replace('CL-', '') })
|
||||||
.getOne()
|
.getOne()
|
||||||
if (!contributionLink) {
|
if (!contributionLink) {
|
||||||
logger.error('no contribution link found to given code:', code)
|
throw new LogError('No contribution link found to given code', code)
|
||||||
throw new Error(`No contribution link found to given code: ${code}`)
|
|
||||||
}
|
}
|
||||||
logger.info('...contribution link found with id', contributionLink.id)
|
logger.info('...contribution link found with id', contributionLink.id)
|
||||||
if (new Date(contributionLink.validFrom).getTime() > now.getTime()) {
|
if (new Date(contributionLink.validFrom).getTime() > now.getTime()) {
|
||||||
logger.error(
|
throw new LogError('Contribution link is not valid yet', contributionLink.validFrom)
|
||||||
'contribution link is not valid yet. Valid from: ',
|
|
||||||
contributionLink.validFrom,
|
|
||||||
)
|
|
||||||
throw new Error('Contribution link not valid yet')
|
|
||||||
}
|
}
|
||||||
if (contributionLink.validTo) {
|
if (contributionLink.validTo) {
|
||||||
if (new Date(contributionLink.validTo).setHours(23, 59, 59) < now.getTime()) {
|
if (new Date(contributionLink.validTo).setHours(23, 59, 59) < now.getTime()) {
|
||||||
logger.error(
|
throw new LogError('Contribution link is no longer valid', contributionLink.validTo)
|
||||||
'contribution link is no longer valid. Valid to: ',
|
|
||||||
contributionLink.validTo,
|
|
||||||
)
|
|
||||||
throw new Error('Contribution link is no longer valid')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let alreadyRedeemed: DbContribution | undefined
|
let alreadyRedeemed: DbContribution | undefined
|
||||||
@ -219,11 +195,7 @@ export class TransactionLinkResolver {
|
|||||||
})
|
})
|
||||||
.getOne()
|
.getOne()
|
||||||
if (alreadyRedeemed) {
|
if (alreadyRedeemed) {
|
||||||
logger.error(
|
throw new LogError('Contribution link already redeemed', user.id)
|
||||||
'contribution link with rule ONCE already redeemed by user with id',
|
|
||||||
user.id,
|
|
||||||
)
|
|
||||||
throw new Error('Contribution link already redeemed')
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -248,17 +220,12 @@ export class TransactionLinkResolver {
|
|||||||
)
|
)
|
||||||
.getOne()
|
.getOne()
|
||||||
if (alreadyRedeemed) {
|
if (alreadyRedeemed) {
|
||||||
logger.error(
|
throw new LogError('Contribution link already redeemed today', user.id)
|
||||||
'contribution link with rule DAILY already redeemed by user with id',
|
|
||||||
user.id,
|
|
||||||
)
|
|
||||||
throw new Error('Contribution link already redeemed today')
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
logger.error('contribution link has unknown cycle', contributionLink.cycle)
|
throw new LogError('Contribution link has unknown cycle', contributionLink.cycle)
|
||||||
throw new Error('Contribution link has unknown cycle')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,8 +275,7 @@ export class TransactionLinkResolver {
|
|||||||
logger.info('creation from contribution link commited successfuly.')
|
logger.info('creation from contribution link commited successfuly.')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await queryRunner.rollbackTransaction()
|
await queryRunner.rollbackTransaction()
|
||||||
logger.error(`Creation from contribution link was not successful: ${e}`)
|
throw new LogError('Creation from contribution link was not successful', e)
|
||||||
throw new Error(`Creation from contribution link was not successful. ${e}`)
|
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
}
|
}
|
||||||
@ -326,18 +292,18 @@ export class TransactionLinkResolver {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (user.id === linkedUser.id) {
|
if (user.id === linkedUser.id) {
|
||||||
throw new Error('Cannot redeem own transaction link.')
|
throw new LogError('Cannot redeem own transaction link', user.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: The now check should be done within the semaphore lock,
|
// TODO: The now check should be done within the semaphore lock,
|
||||||
// since the program might wait a while till it is ready to proceed
|
// since the program might wait a while till it is ready to proceed
|
||||||
// writing the transaction.
|
// writing the transaction.
|
||||||
if (transactionLink.validUntil.getTime() < now.getTime()) {
|
if (transactionLink.validUntil.getTime() < now.getTime()) {
|
||||||
throw new Error('Transaction Link is not valid anymore.')
|
throw new LogError('Transaction link is not valid anymore', transactionLink.validUntil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionLink.redeemedBy) {
|
if (transactionLink.redeemedBy) {
|
||||||
throw new Error('Transaction Link already redeemed.')
|
throw new LogError('Transaction link already redeemed', transactionLink.redeemedBy)
|
||||||
}
|
}
|
||||||
|
|
||||||
await executeTransaction(
|
await executeTransaction(
|
||||||
@ -352,43 +318,38 @@ 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])
|
@Authorized([RIGHTS.LIST_TRANSACTION_LINKS_ADMIN])
|
||||||
@Query(() => TransactionLinkResult)
|
@Query(() => TransactionLinkResult)
|
||||||
async listTransactionLinksAdmin(
|
async listTransactionLinksAdmin(
|
||||||
@Args()
|
@Args()
|
||||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
paginated: Paginated,
|
||||||
@Arg('filters', () => TransactionLinkFilters, { nullable: true })
|
@Arg('filters', () => TransactionLinkFilters, { nullable: true })
|
||||||
filters: TransactionLinkFilters,
|
filters: TransactionLinkFilters | null,
|
||||||
@Arg('userId', () => Int)
|
@Arg('userId', () => Int)
|
||||||
userId: number,
|
userId: number,
|
||||||
): Promise<TransactionLinkResult> {
|
): Promise<TransactionLinkResult> {
|
||||||
const user = await DbUser.findOneOrFail({ id: userId })
|
const user = await DbUser.findOne({ id: userId })
|
||||||
const where: {
|
if (!user) {
|
||||||
userId: number
|
throw new LogError('Could not find requested User', userId)
|
||||||
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))),
|
|
||||||
}
|
}
|
||||||
|
return transactionLinkList(paginated, filters, user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
|
import Decimal from 'decimal.js-light'
|
||||||
import { EventProtocolType } from '@/event/EventProtocolType'
|
import { EventProtocolType } from '@/event/EventProtocolType'
|
||||||
import { userFactory } from '@/seeds/factory/user'
|
import { userFactory } from '@/seeds/factory/user'
|
||||||
import {
|
import {
|
||||||
@ -118,10 +119,8 @@ describe('send coins', () => {
|
|||||||
|
|
||||||
it('logs the error thrown', async () => {
|
it('logs the error thrown', async () => {
|
||||||
// find peter to check the log
|
// find peter to check the log
|
||||||
const user = await findUserByEmail(peterData.email)
|
const user = await findUserByEmail('stephen@hawking.uk')
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith('The recipient account was deleted', user)
|
||||||
`The recipient account was deleted: recipientUser=${user}`,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -151,10 +150,8 @@ describe('send coins', () => {
|
|||||||
|
|
||||||
it('logs the error thrown', async () => {
|
it('logs the error thrown', async () => {
|
||||||
// find peter to check the log
|
// find peter to check the log
|
||||||
const user = await findUserByEmail(peterData.email)
|
const user = await findUserByEmail('garrick@ollivander.com')
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith('The recipient account is not activated', user)
|
||||||
`The recipient account is not activated: recipientUser=${user}`,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -181,37 +178,13 @@ describe('send coins', () => {
|
|||||||
}),
|
}),
|
||||||
).toEqual(
|
).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('Sender and Recipient are the same.')],
|
errors: [new GraphQLError('Sender and Recipient are the same')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith('Sender and Recipient are the same.')
|
expect(logger.error).toBeCalledWith('Sender and Recipient are the same', expect.any(Number))
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('memo text is too long', () => {
|
|
||||||
it('throws an error', async () => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
expect(
|
|
||||||
await mutate({
|
|
||||||
mutation: sendCoins,
|
|
||||||
variables: {
|
|
||||||
email: 'peter@lustig.de',
|
|
||||||
amount: 100,
|
|
||||||
memo: 'test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test t',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('memo text is too long (255 characters maximum)')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith('memo text is too long: memo.length=256 > 255')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -229,13 +202,37 @@ describe('send coins', () => {
|
|||||||
}),
|
}),
|
||||||
).toEqual(
|
).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('memo text is too short (5 characters minimum)')],
|
errors: [new GraphQLError('Memo text is too short')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith('memo text is too short: memo.length=4 < 5')
|
expect(logger.error).toBeCalledWith('Memo text is too short', 4)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('memo text is too long', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
expect(
|
||||||
|
await mutate({
|
||||||
|
mutation: sendCoins,
|
||||||
|
variables: {
|
||||||
|
email: 'peter@lustig.de',
|
||||||
|
amount: 100,
|
||||||
|
memo: 'test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test t',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('Memo text is too long')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith('Memo text is too long', 256)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -253,15 +250,13 @@ describe('send coins', () => {
|
|||||||
}),
|
}),
|
||||||
).toEqual(
|
).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError(`user hasn't enough GDD or amount is < 0`)],
|
errors: [new GraphQLError('User has not enough GDD or amount is < 0')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith('User has not enough GDD or amount is < 0', null)
|
||||||
`user hasn't enough GDD or amount is < 0 : balance=null`,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -293,6 +288,7 @@ describe('send coins', () => {
|
|||||||
|
|
||||||
describe('trying to send negative amount', () => {
|
describe('trying to send negative amount', () => {
|
||||||
it('throws an error', async () => {
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
expect(
|
expect(
|
||||||
await mutate({
|
await mutate({
|
||||||
mutation: sendCoins,
|
mutation: sendCoins,
|
||||||
@ -304,13 +300,13 @@ describe('send coins', () => {
|
|||||||
}),
|
}),
|
||||||
).toEqual(
|
).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError(`Amount to send must be positive`)],
|
errors: [new GraphQLError('Amount to send must be positive')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(`Amount to send must be positive`)
|
expect(logger.error).toBeCalledWith('Amount to send must be positive', new Decimal(-50))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -334,7 +330,7 @@ describe('send coins', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('stores the send transaction event in the database', async () => {
|
it('stores the TRANSACTION_SEND event in the database', async () => {
|
||||||
// Find the exact transaction (sent one is the one with user[1] as user)
|
// Find the exact transaction (sent one is the one with user[1] as user)
|
||||||
const transaction = await Transaction.find({
|
const transaction = await Transaction.find({
|
||||||
userId: user[1].id,
|
userId: user[1].id,
|
||||||
@ -351,7 +347,7 @@ describe('send coins', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('stores the receive event in the database', async () => {
|
it('stores the TRANSACTION_RECEIVE event in the database', async () => {
|
||||||
// Find the exact transaction (received one is the one with user[0] as user)
|
// Find the exact transaction (received one is the one with user[0] as user)
|
||||||
const transaction = await Transaction.find({
|
const transaction = await Transaction.find({
|
||||||
userId: user[0].id,
|
userId: user[0].id,
|
||||||
|
|||||||
@ -29,14 +29,14 @@ import {
|
|||||||
sendTransactionLinkRedeemedEmail,
|
sendTransactionLinkRedeemedEmail,
|
||||||
sendTransactionReceivedEmail,
|
sendTransactionReceivedEmail,
|
||||||
} from '@/emails/sendEmailVariants'
|
} from '@/emails/sendEmailVariants'
|
||||||
import { Event, EventTransactionReceive, EventTransactionSend } from '@/event/Event'
|
import { EVENT_TRANSACTION_RECEIVE, EVENT_TRANSACTION_SEND } from '@/event/Event'
|
||||||
import { writeEvent } from '@/event/EventProtocolEmitter'
|
|
||||||
|
|
||||||
import { BalanceResolver } from './BalanceResolver'
|
import { BalanceResolver } from './BalanceResolver'
|
||||||
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
|
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
|
||||||
import { findUserByEmail } from './UserResolver'
|
import { findUserByEmail } from './UserResolver'
|
||||||
|
|
||||||
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||||
|
import LogError from '@/server/LogError'
|
||||||
|
|
||||||
import { getLastTransaction } from './util/getLastTransaction'
|
import { getLastTransaction } from './util/getLastTransaction'
|
||||||
|
|
||||||
@ -55,18 +55,15 @@ export const executeTransaction = async (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (sender.id === recipient.id) {
|
if (sender.id === recipient.id) {
|
||||||
logger.error(`Sender and Recipient are the same.`)
|
throw new LogError('Sender and Recipient are the same', sender.id)
|
||||||
throw new Error('Sender and Recipient are the same.')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memo.length > MEMO_MAX_CHARS) {
|
|
||||||
logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`)
|
|
||||||
throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (memo.length < MEMO_MIN_CHARS) {
|
if (memo.length < MEMO_MIN_CHARS) {
|
||||||
logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`)
|
throw new LogError('Memo text is too short', memo.length)
|
||||||
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
|
}
|
||||||
|
|
||||||
|
if (memo.length > MEMO_MAX_CHARS) {
|
||||||
|
throw new LogError('Memo text is too long', memo.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate amount
|
// validate amount
|
||||||
@ -79,8 +76,7 @@ export const executeTransaction = async (
|
|||||||
)
|
)
|
||||||
logger.debug(`calculated Balance=${sendBalance}`)
|
logger.debug(`calculated Balance=${sendBalance}`)
|
||||||
if (!sendBalance) {
|
if (!sendBalance) {
|
||||||
logger.error(`user hasn't enough GDD or amount is < 0 : balance=${sendBalance}`)
|
throw new LogError('User has not enough GDD or amount is < 0', sendBalance)
|
||||||
throw new Error("user hasn't enough GDD or amount is < 0")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
@ -141,23 +137,22 @@ export const executeTransaction = async (
|
|||||||
await queryRunner.commitTransaction()
|
await queryRunner.commitTransaction()
|
||||||
logger.info(`commit Transaction successful...`)
|
logger.info(`commit Transaction successful...`)
|
||||||
|
|
||||||
const eventTransactionSend = new EventTransactionSend()
|
await EVENT_TRANSACTION_SEND(
|
||||||
eventTransactionSend.userId = transactionSend.userId
|
transactionSend.userId,
|
||||||
eventTransactionSend.xUserId = transactionSend.linkedUserId
|
transactionSend.linkedUserId,
|
||||||
eventTransactionSend.transactionId = transactionSend.id
|
transactionSend.id,
|
||||||
eventTransactionSend.amount = transactionSend.amount.mul(-1)
|
transactionSend.amount.mul(-1),
|
||||||
await writeEvent(new Event().setEventTransactionSend(eventTransactionSend))
|
)
|
||||||
|
|
||||||
const eventTransactionReceive = new EventTransactionReceive()
|
await EVENT_TRANSACTION_RECEIVE(
|
||||||
eventTransactionReceive.userId = transactionReceive.userId
|
transactionReceive.userId,
|
||||||
eventTransactionReceive.xUserId = transactionReceive.linkedUserId
|
transactionReceive.linkedUserId,
|
||||||
eventTransactionReceive.transactionId = transactionReceive.id
|
transactionReceive.id,
|
||||||
eventTransactionReceive.amount = transactionReceive.amount
|
transactionReceive.amount,
|
||||||
await writeEvent(new Event().setEventTransactionReceive(eventTransactionReceive))
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await queryRunner.rollbackTransaction()
|
await queryRunner.rollbackTransaction()
|
||||||
logger.error(`Transaction was not successful: ${e}`)
|
throw new LogError('Transaction was not successful', e)
|
||||||
throw new Error(`Transaction was not successful: ${e}`)
|
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
}
|
}
|
||||||
@ -316,8 +311,7 @@ export class TransactionResolver {
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
logger.info(`sendCoins(email=${email}, amount=${amount}, memo=${memo})`)
|
logger.info(`sendCoins(email=${email}, amount=${amount}, memo=${memo})`)
|
||||||
if (amount.lte(0)) {
|
if (amount.lte(0)) {
|
||||||
logger.error(`Amount to send must be positive`)
|
throw new LogError('Amount to send must be positive', amount)
|
||||||
throw new Error('Amount to send must be positive')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this is subject to replay attacks
|
// TODO this is subject to replay attacks
|
||||||
@ -326,13 +320,11 @@ export class TransactionResolver {
|
|||||||
// validate recipient user
|
// validate recipient user
|
||||||
const recipientUser = await findUserByEmail(email)
|
const recipientUser = await findUserByEmail(email)
|
||||||
if (recipientUser.deletedAt) {
|
if (recipientUser.deletedAt) {
|
||||||
logger.error(`The recipient account was deleted: recipientUser=${recipientUser}`)
|
throw new LogError('The recipient account was deleted', recipientUser)
|
||||||
throw new Error('The recipient account was deleted')
|
|
||||||
}
|
}
|
||||||
const emailContact = recipientUser.emailContact
|
const emailContact = recipientUser.emailContact
|
||||||
if (!emailContact.emailChecked) {
|
if (!emailContact.emailChecked) {
|
||||||
logger.error(`The recipient account is not activated: recipientUser=${recipientUser}`)
|
throw new LogError('The recipient account is not activated', recipientUser)
|
||||||
throw new Error('The recipient account is not activated')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await executeTransaction(amount, memo, senderUser, recipientUser)
|
await executeTransaction(amount, memo, senderUser, recipientUser)
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
setUserRole,
|
setUserRole,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
unDeleteUser,
|
unDeleteUser,
|
||||||
|
sendActivationEmail,
|
||||||
} from '@/seeds/graphql/mutations'
|
} from '@/seeds/graphql/mutations'
|
||||||
import { verifyLogin, queryOptIn, searchAdminUsers, searchUsers } from '@/seeds/graphql/queries'
|
import { verifyLogin, queryOptIn, searchAdminUsers, searchUsers } from '@/seeds/graphql/queries'
|
||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
@ -175,6 +176,19 @@ describe('UserResolver', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('stores the REGISTER event in the database', async () => {
|
||||||
|
const userConatct = await UserContact.findOneOrFail(
|
||||||
|
{ email: 'peter@lustig.de' },
|
||||||
|
{ relations: ['user'] },
|
||||||
|
)
|
||||||
|
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
type: EventProtocolType.REGISTER,
|
||||||
|
userId: userConatct.user.id,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('account activation email', () => {
|
describe('account activation email', () => {
|
||||||
@ -196,7 +210,7 @@ describe('UserResolver', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('stores the send confirmation event in the database', () => {
|
it('stores the SEND_CONFIRMATION_EMAIL event in the database', () => {
|
||||||
expect(EventProtocol.find()).resolves.toContainEqual(
|
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: EventProtocolType.SEND_CONFIRMATION_EMAIL,
|
type: EventProtocolType.SEND_CONFIRMATION_EMAIL,
|
||||||
@ -206,7 +220,7 @@ describe('UserResolver', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('email already exists', () => {
|
describe('user already exists', () => {
|
||||||
let mutation: User
|
let mutation: User
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
mutation = await mutate({ mutation: createUser, variables })
|
mutation = await mutate({ mutation: createUser, variables })
|
||||||
@ -236,6 +250,19 @@ describe('UserResolver', () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('stores the SEND_ACCOUNT_MULTIREGISTRATION_EMAIL event in the database', async () => {
|
||||||
|
const userConatct = await UserContact.findOneOrFail(
|
||||||
|
{ email: 'peter@lustig.de' },
|
||||||
|
{ relations: ['user'] },
|
||||||
|
)
|
||||||
|
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
type: EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL,
|
||||||
|
userId: userConatct.user.id,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('unknown language', () => {
|
describe('unknown language', () => {
|
||||||
@ -328,7 +355,7 @@ describe('UserResolver', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('stores the account activated event in the database', () => {
|
it('stores the ACTIVATE_ACCOUNT event in the database', () => {
|
||||||
expect(EventProtocol.find()).resolves.toContainEqual(
|
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: EventProtocolType.ACTIVATE_ACCOUNT,
|
type: EventProtocolType.ACTIVATE_ACCOUNT,
|
||||||
@ -337,7 +364,7 @@ describe('UserResolver', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('stores the redeem register event in the database', () => {
|
it('stores the REDEEM_REGISTER event in the database', () => {
|
||||||
expect(EventProtocol.find()).resolves.toContainEqual(
|
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: EventProtocolType.REDEEM_REGISTER,
|
type: EventProtocolType.REDEEM_REGISTER,
|
||||||
@ -421,7 +448,7 @@ describe('UserResolver', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('stores the redeem register event in the database', async () => {
|
it('stores the REDEEM_REGISTER event in the database', async () => {
|
||||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: EventProtocolType.REDEEM_REGISTER,
|
type: EventProtocolType.REDEEM_REGISTER,
|
||||||
@ -647,6 +674,19 @@ describe('UserResolver', () => {
|
|||||||
it('sets the token in the header', () => {
|
it('sets the token in the header', () => {
|
||||||
expect(headerPushMock).toBeCalledWith({ key: 'token', value: expect.any(String) })
|
expect(headerPushMock).toBeCalledWith({ key: 'token', value: expect.any(String) })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('stores the LOGIN event in the database', async () => {
|
||||||
|
const userConatct = await UserContact.findOneOrFail(
|
||||||
|
{ email: 'bibi@bloxberg.de' },
|
||||||
|
{ relations: ['user'] },
|
||||||
|
)
|
||||||
|
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
type: EventProtocolType.LOGIN,
|
||||||
|
userId: userConatct.user.id,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('user is in database and wrong password', () => {
|
describe('user is in database and wrong password', () => {
|
||||||
@ -887,7 +927,7 @@ describe('UserResolver', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('stores the login event in the database', () => {
|
it('stores the LOGIN event in the database', () => {
|
||||||
expect(EventProtocol.find()).resolves.toContainEqual(
|
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: EventProtocolType.LOGIN,
|
type: EventProtocolType.LOGIN,
|
||||||
@ -1668,6 +1708,157 @@ describe('UserResolver', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
describe('sendActivationEmail', () => {
|
||||||
|
describe('unauthenticated', () => {
|
||||||
|
it('returns an error', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({ mutation: sendActivationEmail, variables: { email: 'bibi@bloxberg.de' } }),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('401 Unauthorized')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('authenticated', () => {
|
||||||
|
describe('without admin rights', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
user = await userFactory(testEnv, bibiBloxberg)
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
resetToken()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an error', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({ mutation: sendActivationEmail, variables: { email: 'bibi@bloxberg.de' } }),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('401 Unauthorized')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with admin rights', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
admin = await userFactory(testEnv, peterLustig)
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
resetToken()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('user does not exist', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await expect(
|
||||||
|
mutate({ mutation: sendActivationEmail, variables: { email: 'INVALID' } }),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('No user with this credentials')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith('No user with this credentials', 'invalid')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('user is deleted', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await userFactory(testEnv, stephenHawking)
|
||||||
|
await expect(
|
||||||
|
mutate({ mutation: sendActivationEmail, variables: { email: 'stephen@hawking.uk' } }),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('User with given email contact is deleted')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'User with given email contact is deleted',
|
||||||
|
'stephen@hawking.uk',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('sendActivationEmail with success', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
user = await userFactory(testEnv, bibiBloxberg)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns true', async () => {
|
||||||
|
const result = await mutate({
|
||||||
|
mutation: sendActivationEmail,
|
||||||
|
variables: { email: 'bibi@bloxberg.de' },
|
||||||
|
})
|
||||||
|
expect(result).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
sendActivationEmail: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends an account activation email', async () => {
|
||||||
|
const userConatct = await UserContact.findOneOrFail(
|
||||||
|
{ email: 'bibi@bloxberg.de' },
|
||||||
|
{ relations: ['user'] },
|
||||||
|
)
|
||||||
|
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(
|
||||||
|
/{optin}/g,
|
||||||
|
userConatct.emailVerificationCode.toString(),
|
||||||
|
).replace(/{code}/g, '')
|
||||||
|
expect(sendAccountActivationEmail).toBeCalledWith({
|
||||||
|
firstName: 'Bibi',
|
||||||
|
lastName: 'Bloxberg',
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
language: 'de',
|
||||||
|
activationLink,
|
||||||
|
timeDurationObject: expect.objectContaining({
|
||||||
|
hours: expect.any(Number),
|
||||||
|
minutes: expect.any(Number),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('stores the ADMIN_SEND_CONFIRMATION_EMAIL event in the database', async () => {
|
||||||
|
const userConatct = await UserContact.findOneOrFail(
|
||||||
|
{ email: 'bibi@bloxberg.de' },
|
||||||
|
{ relations: ['user'] },
|
||||||
|
)
|
||||||
|
expect(EventProtocol.find()).resolves.toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
type: EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL,
|
||||||
|
userId: userConatct.user.id,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('unDelete user', () => {
|
describe('unDelete user', () => {
|
||||||
describe('unauthenticated', () => {
|
describe('unauthenticated', () => {
|
||||||
it('returns an error', async () => {
|
it('returns an error', async () => {
|
||||||
|
|||||||
@ -48,15 +48,14 @@ import { klicktippNewsletterStateMiddleware } from '@/middleware/klicktippMiddle
|
|||||||
import { klicktippSignIn } from '@/apis/KlicktippController'
|
import { klicktippSignIn } from '@/apis/KlicktippController'
|
||||||
import { RIGHTS } from '@/auth/RIGHTS'
|
import { RIGHTS } from '@/auth/RIGHTS'
|
||||||
import { hasElopageBuys } from '@/util/hasElopageBuys'
|
import { hasElopageBuys } from '@/util/hasElopageBuys'
|
||||||
import { writeEvent } from '@/event/EventProtocolEmitter'
|
|
||||||
import {
|
import {
|
||||||
Event,
|
Event,
|
||||||
EventLogin,
|
EVENT_LOGIN,
|
||||||
EventRedeemRegister,
|
EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL,
|
||||||
EventRegister,
|
EVENT_SEND_CONFIRMATION_EMAIL,
|
||||||
EventSendAccountMultiRegistrationEmail,
|
EVENT_REGISTER,
|
||||||
EventSendConfirmationEmail,
|
EVENT_ACTIVATE_ACCOUNT,
|
||||||
EventActivateAccount,
|
EVENT_ADMIN_SEND_CONFIRMATION_EMAIL,
|
||||||
} from '@/event/Event'
|
} from '@/event/Event'
|
||||||
import { getUserCreations } from './util/creations'
|
import { getUserCreations } from './util/creations'
|
||||||
import { isValidPassword } from '@/password/EncryptorUtils'
|
import { isValidPassword } from '@/password/EncryptorUtils'
|
||||||
@ -64,6 +63,7 @@ import { FULL_CREATION_AVAILABLE } from './const/const'
|
|||||||
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
|
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
|
||||||
import { PasswordEncryptionType } from '../enum/PasswordEncryptionType'
|
import { PasswordEncryptionType } from '../enum/PasswordEncryptionType'
|
||||||
import LogError from '@/server/LogError'
|
import LogError from '@/server/LogError'
|
||||||
|
import { EventProtocolType } from '@/event/EventProtocolType'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const sodium = require('sodium-native')
|
const sodium = require('sodium-native')
|
||||||
@ -177,9 +177,8 @@ export class UserResolver {
|
|||||||
key: 'token',
|
key: 'token',
|
||||||
value: encode(dbUser.gradidoID),
|
value: encode(dbUser.gradidoID),
|
||||||
})
|
})
|
||||||
const ev = new EventLogin()
|
|
||||||
ev.userId = user.id
|
await EVENT_LOGIN(user.id)
|
||||||
writeEvent(new Event().setEventLogin(ev))
|
|
||||||
logger.info(`successful Login: ${JSON.stringify(user, null, 2)}`)
|
logger.info(`successful Login: ${JSON.stringify(user, null, 2)}`)
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
@ -211,7 +210,6 @@ export class UserResolver {
|
|||||||
)
|
)
|
||||||
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
|
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
|
||||||
// default int publisher_id = 0;
|
// default int publisher_id = 0;
|
||||||
const event = new Event()
|
|
||||||
|
|
||||||
// Validate Language (no throw)
|
// Validate Language (no throw)
|
||||||
if (!language || !isLanguage(language)) {
|
if (!language || !isLanguage(language)) {
|
||||||
@ -249,9 +247,9 @@ export class UserResolver {
|
|||||||
email,
|
email,
|
||||||
language: foundUser.language, // use language of the emails owner for sending
|
language: foundUser.language, // use language of the emails owner for sending
|
||||||
})
|
})
|
||||||
const eventSendAccountMultiRegistrationEmail = new EventSendAccountMultiRegistrationEmail()
|
|
||||||
eventSendAccountMultiRegistrationEmail.userId = foundUser.id
|
await EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL(foundUser.id)
|
||||||
writeEvent(event.setEventSendConfirmationEmail(eventSendAccountMultiRegistrationEmail))
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`sendAccountMultiRegistrationEmail by ${firstName} ${lastName} to ${foundUser.firstName} ${foundUser.lastName} <${email}>`,
|
`sendAccountMultiRegistrationEmail by ${firstName} ${lastName} to ${foundUser.firstName} ${foundUser.lastName} <${email}>`,
|
||||||
)
|
)
|
||||||
@ -268,10 +266,7 @@ export class UserResolver {
|
|||||||
|
|
||||||
const gradidoID = await newGradidoID()
|
const gradidoID = await newGradidoID()
|
||||||
|
|
||||||
const eventRegister = new EventRegister()
|
const eventRegisterRedeem = Event(EventProtocolType.REDEEM_REGISTER, 0)
|
||||||
const eventRedeemRegister = new EventRedeemRegister()
|
|
||||||
const eventSendConfirmEmail = new EventSendConfirmationEmail()
|
|
||||||
|
|
||||||
let dbUser = new DbUser()
|
let dbUser = new DbUser()
|
||||||
dbUser.gradidoID = gradidoID
|
dbUser.gradidoID = gradidoID
|
||||||
dbUser.firstName = firstName
|
dbUser.firstName = firstName
|
||||||
@ -288,14 +283,14 @@ export class UserResolver {
|
|||||||
logger.info('redeemCode found contributionLink=' + contributionLink)
|
logger.info('redeemCode found contributionLink=' + contributionLink)
|
||||||
if (contributionLink) {
|
if (contributionLink) {
|
||||||
dbUser.contributionLinkId = contributionLink.id
|
dbUser.contributionLinkId = contributionLink.id
|
||||||
eventRedeemRegister.contributionId = contributionLink.id
|
eventRegisterRedeem.contributionId = contributionLink.id
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const transactionLink = await DbTransactionLink.findOne({ code: redeemCode })
|
const transactionLink = await DbTransactionLink.findOne({ code: redeemCode })
|
||||||
logger.info('redeemCode found transactionLink=' + transactionLink)
|
logger.info('redeemCode found transactionLink=' + transactionLink)
|
||||||
if (transactionLink) {
|
if (transactionLink) {
|
||||||
dbUser.referrerId = transactionLink.userId
|
dbUser.referrerId = transactionLink.userId
|
||||||
eventRedeemRegister.transactionId = transactionLink.id
|
eventRegisterRedeem.transactionId = transactionLink.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,8 +328,8 @@ export class UserResolver {
|
|||||||
timeDurationObject: getTimeDurationObject(CONFIG.EMAIL_CODE_VALID_TIME),
|
timeDurationObject: getTimeDurationObject(CONFIG.EMAIL_CODE_VALID_TIME),
|
||||||
})
|
})
|
||||||
logger.info(`sendAccountActivationEmail of ${firstName}.${lastName} to ${email}`)
|
logger.info(`sendAccountActivationEmail of ${firstName}.${lastName} to ${email}`)
|
||||||
eventSendConfirmEmail.userId = dbUser.id
|
|
||||||
writeEvent(event.setEventSendConfirmationEmail(eventSendConfirmEmail))
|
await EVENT_SEND_CONFIRMATION_EMAIL(dbUser.id)
|
||||||
|
|
||||||
if (!emailSent) {
|
if (!emailSent) {
|
||||||
logger.debug(`Account confirmation link: ${activationLink}`)
|
logger.debug(`Account confirmation link: ${activationLink}`)
|
||||||
@ -351,11 +346,10 @@ export class UserResolver {
|
|||||||
logger.info('createUser() successful...')
|
logger.info('createUser() successful...')
|
||||||
|
|
||||||
if (redeemCode) {
|
if (redeemCode) {
|
||||||
eventRedeemRegister.userId = dbUser.id
|
eventRegisterRedeem.userId = dbUser.id
|
||||||
await writeEvent(event.setEventRedeemRegister(eventRedeemRegister))
|
await eventRegisterRedeem.save()
|
||||||
} else {
|
} else {
|
||||||
eventRegister.userId = dbUser.id
|
await EVENT_REGISTER(dbUser.id)
|
||||||
await writeEvent(event.setEventRegister(eventRegister))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new User(dbUser)
|
return new User(dbUser)
|
||||||
@ -458,8 +452,6 @@ export class UserResolver {
|
|||||||
await queryRunner.connect()
|
await queryRunner.connect()
|
||||||
await queryRunner.startTransaction('REPEATABLE READ')
|
await queryRunner.startTransaction('REPEATABLE READ')
|
||||||
|
|
||||||
const event = new Event()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Save user
|
// Save user
|
||||||
await queryRunner.manager.save(user).catch((error) => {
|
await queryRunner.manager.save(user).catch((error) => {
|
||||||
@ -473,9 +465,7 @@ export class UserResolver {
|
|||||||
await queryRunner.commitTransaction()
|
await queryRunner.commitTransaction()
|
||||||
logger.info('User and UserContact data written successfully...')
|
logger.info('User and UserContact data written successfully...')
|
||||||
|
|
||||||
const eventActivateAccount = new EventActivateAccount()
|
await EVENT_ACTIVATE_ACCOUNT(user.id)
|
||||||
eventActivateAccount.userId = user.id
|
|
||||||
writeEvent(event.setEventActivateAccount(eventActivateAccount))
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await queryRunner.rollbackTransaction()
|
await queryRunner.rollbackTransaction()
|
||||||
throw new LogError('Error on writing User and User Contact data', e)
|
throw new LogError('Error on writing User and User Contact data', e)
|
||||||
@ -791,19 +781,12 @@ export class UserResolver {
|
|||||||
email = email.trim().toLowerCase()
|
email = email.trim().toLowerCase()
|
||||||
// const user = await dbUser.findOne({ id: emailContact.userId })
|
// const user = await dbUser.findOne({ id: emailContact.userId })
|
||||||
const user = await findUserByEmail(email)
|
const user = await findUserByEmail(email)
|
||||||
if (!user) {
|
if (user.deletedAt || user.emailContact.deletedAt) {
|
||||||
throw new LogError('Could not find user to given email contact', email)
|
|
||||||
}
|
|
||||||
if (user.deletedAt) {
|
|
||||||
throw new LogError('User with given email contact is deleted', email)
|
throw new LogError('User with given email contact is deleted', email)
|
||||||
}
|
}
|
||||||
const emailContact = user.emailContact
|
|
||||||
if (emailContact.deletedAt) {
|
|
||||||
throw new LogError('The given email contact for this user is deleted', email)
|
|
||||||
}
|
|
||||||
|
|
||||||
emailContact.emailResendCount++
|
user.emailContact.emailResendCount++
|
||||||
await emailContact.save()
|
await user.emailContact.save()
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const emailSent = await sendAccountActivationEmail({
|
const emailSent = await sendAccountActivationEmail({
|
||||||
@ -811,7 +794,7 @@ export class UserResolver {
|
|||||||
lastName: user.lastName,
|
lastName: user.lastName,
|
||||||
email,
|
email,
|
||||||
language: user.language,
|
language: user.language,
|
||||||
activationLink: activationLink(emailContact.emailVerificationCode),
|
activationLink: activationLink(user.emailContact.emailVerificationCode),
|
||||||
timeDurationObject: getTimeDurationObject(CONFIG.EMAIL_CODE_VALID_TIME),
|
timeDurationObject: getTimeDurationObject(CONFIG.EMAIL_CODE_VALID_TIME),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -819,10 +802,7 @@ export class UserResolver {
|
|||||||
if (!emailSent) {
|
if (!emailSent) {
|
||||||
logger.info(`Account confirmation link: ${activationLink}`)
|
logger.info(`Account confirmation link: ${activationLink}`)
|
||||||
} else {
|
} else {
|
||||||
const event = new Event()
|
await EVENT_ADMIN_SEND_CONFIRMATION_EMAIL(user.id)
|
||||||
const eventSendConfirmationEmail = new EventSendConfirmationEmail()
|
|
||||||
eventSendConfirmationEmail.userId = user.id
|
|
||||||
await writeEvent(event.setEventSendConfirmationEmail(eventSendConfirmationEmail))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import LogError from '@/server/LogError'
|
||||||
import { backendLogger as logger } from '@/server/logger'
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
import { getConnection } from '@dbTools/typeorm'
|
import { getConnection } from '@dbTools/typeorm'
|
||||||
import { Contribution } from '@entity/Contribution'
|
import { Contribution } from '@entity/Contribution'
|
||||||
@ -19,19 +20,14 @@ export const validateContribution = (
|
|||||||
const index = getCreationIndex(creationDate.getMonth(), timezoneOffset)
|
const index = getCreationIndex(creationDate.getMonth(), timezoneOffset)
|
||||||
|
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
logger.error(
|
throw new LogError('No information for available creations for the given date', creationDate)
|
||||||
'No information for available creations with the given creationDate=',
|
|
||||||
creationDate.toString(),
|
|
||||||
)
|
|
||||||
throw new Error('No information for available creations for the given date')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (amount.greaterThan(creations[index].toString())) {
|
if (amount.greaterThan(creations[index].toString())) {
|
||||||
logger.error(
|
throw new LogError(
|
||||||
`The amount (${amount} GDD) to be created exceeds the amount (${creations[index]} GDD) still available for this month.`,
|
'The amount to be created exceeds the amount still available for this month',
|
||||||
)
|
amount,
|
||||||
throw new Error(
|
creations[index],
|
||||||
`The amount (${amount} GDD) to be created exceeds the amount (${creations[index]} GDD) still available for this month.`,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,19 +122,16 @@ export const isStartEndDateValid = (
|
|||||||
endDate: string | null | undefined,
|
endDate: string | null | undefined,
|
||||||
): void => {
|
): void => {
|
||||||
if (!startDate) {
|
if (!startDate) {
|
||||||
logger.error('Start-Date is not initialized. A Start-Date must be set!')
|
throw new LogError('A Start-Date must be set')
|
||||||
throw new Error('Start-Date is not initialized. A Start-Date must be set!')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!endDate) {
|
if (!endDate) {
|
||||||
logger.error('End-Date is not initialized. An End-Date must be set!')
|
throw new LogError('An End-Date must be set')
|
||||||
throw new Error('End-Date is not initialized. An End-Date must be set!')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if endDate is before startDate
|
// check if endDate is before startDate
|
||||||
if (new Date(endDate).getTime() - new Date(startDate).getTime() < 0) {
|
if (new Date(endDate).getTime() - new Date(startDate).getTime() < 0) {
|
||||||
logger.error(`The value of validFrom must before or equals the validTo!`)
|
throw new LogError(`The value of validFrom must before or equals the validTo`)
|
||||||
throw new Error(`The value of validFrom must before or equals the validTo!`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +143,7 @@ export const updateCreations = (
|
|||||||
const index = getCreationIndex(contribution.contributionDate.getMonth(), timezoneOffset)
|
const index = getCreationIndex(contribution.contributionDate.getMonth(), timezoneOffset)
|
||||||
|
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
throw new Error('You cannot create GDD for a month older than the last three months.')
|
throw new LogError('You cannot create GDD for a month older than the last three months')
|
||||||
}
|
}
|
||||||
creations[index] = creations[index].plus(contribution.amount.toString())
|
creations[index] = creations[index].plus(contribution.amount.toString())
|
||||||
return creations
|
return creations
|
||||||
|
|||||||
25
backend/src/graphql/resolver/util/findContributions.ts
Normal file
25
backend/src/graphql/resolver/util/findContributions.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||||
|
import { Order } from '@enum/Order'
|
||||||
|
import { Contribution as DbContribution } from '@entity/Contribution'
|
||||||
|
import { In } from '@dbTools/typeorm'
|
||||||
|
|
||||||
|
export const findContributions = async (
|
||||||
|
order: Order,
|
||||||
|
currentPage: number,
|
||||||
|
pageSize: number,
|
||||||
|
withDeleted: boolean,
|
||||||
|
statusFilter?: ContributionStatus[],
|
||||||
|
): Promise<[DbContribution[], number]> =>
|
||||||
|
DbContribution.findAndCount({
|
||||||
|
where: {
|
||||||
|
...(statusFilter && statusFilter.length && { contributionStatus: In(statusFilter) }),
|
||||||
|
},
|
||||||
|
withDeleted: withDeleted,
|
||||||
|
order: {
|
||||||
|
createdAt: order,
|
||||||
|
id: order,
|
||||||
|
},
|
||||||
|
relations: ['user'],
|
||||||
|
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))),
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import createServer from './server/createServer'
|
|||||||
|
|
||||||
// config
|
// config
|
||||||
import CONFIG from './config'
|
import CONFIG from './config'
|
||||||
|
import { startValidateCommunities } from './federation/validateCommunities'
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const { app } = await createServer()
|
const { app } = await createServer()
|
||||||
@ -16,6 +17,7 @@ async function main() {
|
|||||||
console.log(`GraphIQL available at http://localhost:${CONFIG.PORT}`)
|
console.log(`GraphIQL available at http://localhost:${CONFIG.PORT}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
startValidateCommunities(Number(CONFIG.FEDERATION_VALIDATE_COMMUNITY_TIMER))
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((e) => {
|
main().catch((e) => {
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
{
|
{
|
||||||
"emails": {
|
"emails": {
|
||||||
"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“!"
|
|
||||||
},
|
|
||||||
"accountActivation": {
|
"accountActivation": {
|
||||||
"duration": "Der Link hat eine Gültigkeit von {hours} Stunden und {minutes} Minuten. Sollte die Gültigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen:",
|
"duration": "Der Link hat eine Gültigkeit von {hours} Stunden und {minutes} Minuten. Sollte die Gültigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen:",
|
||||||
"emailRegistered": "deine E-Mail-Adresse wurde soeben bei Gradido registriert.",
|
"emailRegistered": "deine E-Mail-Adresse wurde soeben bei Gradido registriert.",
|
||||||
@ -19,6 +14,11 @@
|
|||||||
"onForgottenPasswordCopyLink": "oder kopiere den obigen Link in dein Browserfenster.",
|
"onForgottenPasswordCopyLink": "oder kopiere den obigen Link in dein Browserfenster.",
|
||||||
"subject": "Gradido: Erneuter Registrierungsversuch mit deiner E-Mail"
|
"subject": "Gradido: Erneuter Registrierungsversuch mit deiner E-Mail"
|
||||||
},
|
},
|
||||||
|
"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ü „Schöpfen“ auf den Tab „Meine Beiträge“!"
|
||||||
|
},
|
||||||
"contributionConfirmed": {
|
"contributionConfirmed": {
|
||||||
"commonGoodContributionConfirmed": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde soeben von {senderFirstName} {senderLastName} bestätigt und in deinem Gradido-Konto gutgeschrieben.",
|
"commonGoodContributionConfirmed": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde soeben von {senderFirstName} {senderLastName} bestätigt und in deinem Gradido-Konto gutgeschrieben.",
|
||||||
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde bestätigt"
|
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde bestätigt"
|
||||||
@ -26,12 +26,12 @@
|
|||||||
"contributionDeleted": {
|
"contributionDeleted": {
|
||||||
"commonGoodContributionDeleted": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} gelöscht.",
|
"commonGoodContributionDeleted": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} gelöscht.",
|
||||||
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde 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": {
|
"contributionDenied": {
|
||||||
"commonGoodContributionDenied": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} abgelehnt.",
|
"commonGoodContributionDenied": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} abgelehnt.",
|
||||||
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde 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": {
|
"general": {
|
||||||
"amountGDD": "Betrag: {amountGDD} GDD",
|
"amountGDD": "Betrag: {amountGDD} GDD",
|
||||||
|
|||||||
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