mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into logger_encryptorUtils
This commit is contained in:
commit
1a8e98cce9
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/
|
||||||
|
|||||||
98
.github/workflows/test_federation.yml
vendored
Normal file
98
.github/workflows/test_federation.yml
vendored
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
name: gradido test_federation CI
|
||||||
|
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
##############################################################################
|
||||||
|
# JOB: DOCKER BUILD TEST #####################################################
|
||||||
|
##############################################################################
|
||||||
|
build:
|
||||||
|
name: Docker Build Test
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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 }}
|
||||||
49
CHANGELOG.md
49
CHANGELOG.md
@ -4,8 +4,57 @@ 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)
|
||||||
|
|
||||||
|
> 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): community link [`#2696`](https://github.com/gradido/gradido/pull/2696)
|
||||||
|
|
||||||
|
#### [1.18.0](https://github.com/gradido/gradido/compare/1.17.1...1.18.0)
|
||||||
|
|
||||||
|
> 9 February 2023
|
||||||
|
|
||||||
|
- feat(release): version 1.18.0 [`#2690`](https://github.com/gradido/gradido/pull/2690)
|
||||||
|
- refactor(frontend): toast by automatically logged out [`#2681`](https://github.com/gradido/gradido/pull/2681)
|
||||||
|
- refactor(frontend): change text for gdd_per_link.choose-amount [`#2638`](https://github.com/gradido/gradido/pull/2638)
|
||||||
|
- fix(backend): emails for deny and delete contribution [`#2688`](https://github.com/gradido/gradido/pull/2688)
|
||||||
|
- refactor(other): remove config version from `.env.dist` [`#2686`](https://github.com/gradido/gradido/pull/2686)
|
||||||
|
- refactor(backend): use LogError on contributionMessageResolver [`#2663`](https://github.com/gradido/gradido/pull/2663)
|
||||||
|
- refactor(backend): get last transaction by only one function [`#2668`](https://github.com/gradido/gradido/pull/2668)
|
||||||
|
- refactor(other): don't rebuild modul if unit test file has been changed [`#2667`](https://github.com/gradido/gradido/pull/2667)
|
||||||
|
- refactor(backend): use LogError on contributionLinkResolver [`#2662`](https://github.com/gradido/gradido/pull/2662)
|
||||||
|
- refactor(backend): remove event protocol config switch [`#2670`](https://github.com/gradido/gradido/pull/2670)
|
||||||
|
- refactor(backend): event protocol [`#2652`](https://github.com/gradido/gradido/pull/2652)
|
||||||
|
- refactor(frontend): sidebar becomes smaller when critical phase [`#2649`](https://github.com/gradido/gradido/pull/2649)
|
||||||
|
- refactor(backend): use LogError on sendEMailTranslated [`#2656`](https://github.com/gradido/gradido/pull/2656)
|
||||||
|
- refactor(backend): log error class [`#2640`](https://github.com/gradido/gradido/pull/2640)
|
||||||
|
- feat(backend): add filterState parameter to listAllContributions query [`#2619`](https://github.com/gradido/gradido/pull/2619)
|
||||||
|
- refactor(frontend): there is no message when a month is fully created [`#2626`](https://github.com/gradido/gradido/pull/2626)
|
||||||
|
- refactor(frontend): better text alignment on send via link [`#2637`](https://github.com/gradido/gradido/pull/2637)
|
||||||
|
- refactor(frontend): text changed as indicated in the issues [`#2642`](https://github.com/gradido/gradido/pull/2642)
|
||||||
|
- refactor(frontend): when you click on create, you will be directed to the form [`#2645`](https://github.com/gradido/gradido/pull/2645)
|
||||||
|
- feat(backend): federation: separated dht-hub features in new dht-node modul [`#2510`](https://github.com/gradido/gradido/pull/2510)
|
||||||
|
- refactor(backend): refine assembly of error message in user resolver [`#2636`](https://github.com/gradido/gradido/pull/2636)
|
||||||
|
- fix(backend): unit tests creations for 31st day [`#2641`](https://github.com/gradido/gradido/pull/2641)
|
||||||
|
- fix(workflow): properly lint pr - prevent requirement to restart linting [`#2635`](https://github.com/gradido/gradido/pull/2635)
|
||||||
|
- feat(frontend): 'yes'-button shows which dialog is currently open with a different color [`#2629`](https://github.com/gradido/gradido/pull/2629)
|
||||||
|
- feat(backend): federation implement multiple apollo graphql endpoints [`#2459`](https://github.com/gradido/gradido/pull/2459)
|
||||||
|
- refactor(frontend): add legend to all contribution tab, and add tests. [`#2625`](https://github.com/gradido/gradido/pull/2625)
|
||||||
|
- feat(frontend): unit tests community page [`#2587`](https://github.com/gradido/gradido/pull/2587)
|
||||||
|
- feat(backend): deny contributions [`#2461`](https://github.com/gradido/gradido/pull/2461)
|
||||||
|
- refactor(admin): update yarn.lock file of admin. [`#2579`](https://github.com/gradido/gradido/pull/2579)
|
||||||
|
|
||||||
#### [1.17.1](https://github.com/gradido/gradido/compare/1.17.0...1.17.1)
|
#### [1.17.1](https://github.com/gradido/gradido/compare/1.17.0...1.17.1)
|
||||||
|
|
||||||
|
> 20 January 2023
|
||||||
|
|
||||||
|
- chore(release): v1.17.1 [`#2588`](https://github.com/gradido/gradido/pull/2588)
|
||||||
- refactor(frontend): change contribution memo add word-break [`#2583`](https://github.com/gradido/gradido/pull/2583)
|
- refactor(frontend): change contribution memo add word-break [`#2583`](https://github.com/gradido/gradido/pull/2583)
|
||||||
- refactor(admin): add text-break on all table memo fields [`#2584`](https://github.com/gradido/gradido/pull/2584)
|
- refactor(admin): add text-break on all table memo fields [`#2584`](https://github.com/gradido/gradido/pull/2584)
|
||||||
- fix(frontend): throw proper frontend warning errors [`#2586`](https://github.com/gradido/gradido/pull/2586)
|
- fix(frontend): throw proper frontend warning errors [`#2586`](https://github.com/gradido/gradido/pull/2586)
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
CONFIG_VERSION=v1.2022-03-18
|
|
||||||
|
|
||||||
GRAPHQL_URI=http://localhost:4000/graphql
|
GRAPHQL_URI=http://localhost:4000/graphql
|
||||||
WALLET_AUTH_URL=http://localhost/authenticate?token={token}
|
WALLET_AUTH_URL=http://localhost/authenticate?token={token}
|
||||||
WALLET_URL=http://localhost/login
|
WALLET_URL=http://localhost/login
|
||||||
|
|||||||
@ -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.17.1",
|
"version": "1.18.2",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -86,5 +86,10 @@
|
|||||||
"> 1%",
|
"> 1%",
|
||||||
"last 2 versions",
|
"last 2 versions",
|
||||||
"not ie <= 10"
|
"not ie <= 10"
|
||||||
]
|
],
|
||||||
|
"nodemonConfig": {
|
||||||
|
"ignore": [
|
||||||
|
"**/*.spec.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -46,43 +46,45 @@ 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 windowLocationMock = 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 = {
|
||||||
|
assign: windowLocationMock,
|
||||||
|
}
|
||||||
|
await wrapper.findAll('.nav-item').at(5).find('a').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete window.location
|
||||||
|
window.location = windowLocation
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('changes window location to wallet', () => {
|
it.skip('changes window location to wallet', () => {
|
||||||
expect(assignLocationSpy).toBeCalledWith('valid-token')
|
expect(windowLocationMock()).toBe('valid-token')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('dispatches logout to store', () => {
|
it('dispatches logout to store', () => {
|
||||||
@ -92,12 +94,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>
|
||||||
|
|||||||
@ -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,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
|
||||||
@ -100,6 +121,14 @@ import RowDetails from '../RowDetails.vue'
|
|||||||
import EditCreationFormular from '../EditCreationFormular.vue'
|
import EditCreationFormular from '../EditCreationFormular.vue'
|
||||||
import ContributionMessagesList from '../ContributionMessages/ContributionMessagesList.vue'
|
import ContributionMessagesList from '../ContributionMessages/ContributionMessagesList.vue'
|
||||||
|
|
||||||
|
const iconMap = {
|
||||||
|
IN_PROGRESS: 'question-square',
|
||||||
|
PENDING: 'bell-fill',
|
||||||
|
CONFIRMED: 'check',
|
||||||
|
DELETED: 'trash',
|
||||||
|
DENIED: 'x-circle',
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'OpenCreationsTable',
|
name: 'OpenCreationsTable',
|
||||||
mixins: [toggleRowDetails],
|
mixins: [toggleRowDetails],
|
||||||
@ -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,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>
|
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
@ -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,10 +1,11 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ContributionLinks from './ContributionLinks.vue'
|
import ContributionLinks from './ContributionLinks.vue'
|
||||||
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',
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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>
|
|
||||||
@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'
|
|||||||
import CreationConfirm from './CreationConfirm.vue'
|
import CreationConfirm from './CreationConfirm.vue'
|
||||||
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.vue'
|
||||||
import OpenCreationsTable from '../components/Tables/OpenCreationsTable.vue'
|
import OpenCreationsTable from '../components/Tables/OpenCreationsTable.vue'
|
||||||
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.vue'
|
||||||
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>
|
||||||
|
|||||||
@ -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'),
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
CONFIG_VERSION=v14.2022-12-22
|
|
||||||
|
|
||||||
# Server
|
# Server
|
||||||
PORT=4000
|
PORT=4000
|
||||||
JWT_SECRET=secret123
|
JWT_SECRET=secret123
|
||||||
@ -55,16 +53,9 @@ EMAIL_CODE_REQUEST_TIME=10
|
|||||||
# Webhook
|
# Webhook
|
||||||
WEBHOOK_ELOPAGE_SECRET=secret
|
WEBHOOK_ELOPAGE_SECRET=secret
|
||||||
|
|
||||||
# EventProtocol
|
|
||||||
EVENT_PROTOCOL_DISABLED=false
|
|
||||||
|
|
||||||
# SET LOG LEVEL AS NEEDED IN YOUR .ENV
|
# SET LOG LEVEL AS NEEDED IN YOUR .ENV
|
||||||
# POSSIBLE VALUES: all | trace | debug | info | warn | error | fatal
|
# POSSIBLE VALUES: all | trace | debug | info | warn | error | fatal
|
||||||
# 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
|
|
||||||
@ -54,10 +54,5 @@ EMAIL_CODE_REQUEST_TIME=$EMAIL_CODE_REQUEST_TIME
|
|||||||
# Webhook
|
# Webhook
|
||||||
WEBHOOK_ELOPAGE_SECRET=$WEBHOOK_ELOPAGE_SECRET
|
WEBHOOK_ELOPAGE_SECRET=$WEBHOOK_ELOPAGE_SECRET
|
||||||
|
|
||||||
# EventProtocol
|
|
||||||
EVENT_PROTOCOL_DISABLED=$EVENT_PROTOCOL_DISABLED
|
|
||||||
|
|
||||||
# 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.17.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",
|
||||||
@ -72,5 +73,10 @@
|
|||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.0.0",
|
||||||
"tsconfig-paths": "^3.14.0",
|
"tsconfig-paths": "^3.14.0",
|
||||||
"typescript": "^4.3.4"
|
"typescript": "^4.3.4"
|
||||||
|
},
|
||||||
|
"nodemonConfig": {
|
||||||
|
"ignore": [
|
||||||
|
"**/*.test.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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)
|
||||||
@ -10,14 +10,14 @@ 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
|
||||||
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
||||||
CONFIG_VERSION: {
|
CONFIG_VERSION: {
|
||||||
DEFAULT: 'DEFAULT',
|
DEFAULT: 'DEFAULT',
|
||||||
EXPECTED: 'v14.2022-12-22',
|
EXPECTED: 'v15.2023-02-07',
|
||||||
CURRENT: '',
|
CURRENT: '',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -99,11 +99,6 @@ const webhook = {
|
|||||||
WEBHOOK_ELOPAGE_SECRET: process.env.WEBHOOK_ELOPAGE_SECRET || 'secret',
|
WEBHOOK_ELOPAGE_SECRET: process.env.WEBHOOK_ELOPAGE_SECRET || 'secret',
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventProtocol = {
|
|
||||||
// global switch to enable writing of EventProtocol-Entries
|
|
||||||
EVENT_PROTOCOL_DISABLED: process.env.EVENT_PROTOCOL_DISABLED === 'true' || false,
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is needed by graphql-directive-auth
|
// This is needed by graphql-directive-auth
|
||||||
process.env.APP_SECRET = server.JWT_SECRET
|
process.env.APP_SECRET = server.JWT_SECRET
|
||||||
|
|
||||||
@ -120,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 = {
|
||||||
@ -139,7 +128,6 @@ const CONFIG = {
|
|||||||
...email,
|
...email,
|
||||||
...loginServer,
|
...loginServer,
|
||||||
...webhook,
|
...webhook,
|
||||||
...eventProtocol,
|
|
||||||
...federation,
|
...federation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
sendAccountMultiRegistrationEmail,
|
sendAccountMultiRegistrationEmail,
|
||||||
sendContributionConfirmedEmail,
|
sendContributionConfirmedEmail,
|
||||||
sendContributionDeniedEmail,
|
sendContributionDeniedEmail,
|
||||||
|
sendContributionDeletedEmail,
|
||||||
sendResetPasswordEmail,
|
sendResetPasswordEmail,
|
||||||
sendTransactionLinkRedeemedEmail,
|
sendTransactionLinkRedeemedEmail,
|
||||||
sendTransactionReceivedEmail,
|
sendTransactionReceivedEmail,
|
||||||
@ -438,6 +439,84 @@ describe('sendEmailVariants', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('sendContributionDeletedEmail', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
result = await sendContributionDeletedEmail({
|
||||||
|
firstName: 'Peter',
|
||||||
|
lastName: 'Lustig',
|
||||||
|
email: 'peter@lustig.de',
|
||||||
|
language: 'en',
|
||||||
|
senderFirstName: 'Bibi',
|
||||||
|
senderLastName: 'Bloxberg',
|
||||||
|
contributionMemo: 'My contribution.',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('calls "sendEmailTranslated"', () => {
|
||||||
|
it('with expected parameters', () => {
|
||||||
|
expect(sendEmailTranslated).toBeCalledWith({
|
||||||
|
receiver: {
|
||||||
|
to: 'Peter Lustig <peter@lustig.de>',
|
||||||
|
},
|
||||||
|
template: 'contributionDeleted',
|
||||||
|
locals: {
|
||||||
|
firstName: 'Peter',
|
||||||
|
lastName: 'Lustig',
|
||||||
|
locale: 'en',
|
||||||
|
senderFirstName: 'Bibi',
|
||||||
|
senderLastName: 'Bloxberg',
|
||||||
|
contributionMemo: 'My contribution.',
|
||||||
|
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has expected result', () => {
|
||||||
|
expect(result).toMatchObject({
|
||||||
|
envelope: {
|
||||||
|
from: 'info@gradido.net',
|
||||||
|
to: ['peter@lustig.de'],
|
||||||
|
},
|
||||||
|
message: expect.any(String),
|
||||||
|
originalMessage: expect.objectContaining({
|
||||||
|
to: 'Peter Lustig <peter@lustig.de>',
|
||||||
|
from: 'Gradido (do not answer) <info@gradido.net>',
|
||||||
|
attachments: [],
|
||||||
|
subject: 'Gradido: Your common good contribution was deleted',
|
||||||
|
html: expect.any(String),
|
||||||
|
text: expect.stringContaining('GRADIDO: YOUR COMMON GOOD CONTRIBUTION WAS DELETED'),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||||
|
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
'<title>Gradido: Your common good contribution was deleted</title>',
|
||||||
|
)
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
'>Gradido: Your common good contribution was deleted</h1>',
|
||||||
|
)
|
||||||
|
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
'Your public good contribution “My contribution.” was deleted by Bibi Bloxberg.',
|
||||||
|
)
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
'To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
|
||||||
|
)
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||||
|
)
|
||||||
|
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
|
||||||
|
expect(result.originalMessage.html).toContain('Kind regards,<br>your Gradido team')
|
||||||
|
expect(result.originalMessage.html).toContain('—————')
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
'<div style="position: relative; left: -22px;"><img src="https://gdd.gradido.net/img/brand/green.png" width="200" alt="Gradido-Akademie Logo"></div><br>Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><a href="mailto:support@supportmail.com">support@supportmail.com</a><br><a href="http://localhost/">http://localhost/</a>',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('sendResetPasswordEmail', () => {
|
describe('sendResetPasswordEmail', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
result = await sendResetPasswordEmail({
|
result = await sendResetPasswordEmail({
|
||||||
|
|||||||
@ -103,6 +103,32 @@ export const sendContributionConfirmedEmail = (data: {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const sendContributionDeletedEmail = (data: {
|
||||||
|
firstName: string
|
||||||
|
lastName: string
|
||||||
|
email: string
|
||||||
|
language: string
|
||||||
|
senderFirstName: string
|
||||||
|
senderLastName: string
|
||||||
|
contributionMemo: string
|
||||||
|
}): Promise<Record<string, unknown> | null> => {
|
||||||
|
return sendEmailTranslated({
|
||||||
|
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
|
||||||
|
template: 'contributionDeleted',
|
||||||
|
locals: {
|
||||||
|
firstName: data.firstName,
|
||||||
|
lastName: data.lastName,
|
||||||
|
locale: data.language,
|
||||||
|
senderFirstName: data.senderFirstName,
|
||||||
|
senderLastName: data.senderLastName,
|
||||||
|
contributionMemo: data.contributionMemo,
|
||||||
|
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const sendContributionDeniedEmail = (data: {
|
export const sendContributionDeniedEmail = (data: {
|
||||||
firstName: string
|
firstName: string
|
||||||
lastName: string
|
lastName: string
|
||||||
|
|||||||
16
backend/src/emails/templates/contributionDeleted/html.pug
Normal file
16
backend/src/emails/templates/contributionDeleted/html.pug
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
doctype html
|
||||||
|
html(lang=locale)
|
||||||
|
head
|
||||||
|
title= t('emails.contributionDeleted.subject')
|
||||||
|
body
|
||||||
|
h1(style='margin-bottom: 24px;')= t('emails.contributionDeleted.subject')
|
||||||
|
#container.col
|
||||||
|
include ../hello.pug
|
||||||
|
p= t('emails.contributionDeleted.commonGoodContributionDeleted', { senderFirstName, senderLastName, contributionMemo })
|
||||||
|
p= t('emails.contributionDeleted.toSeeContributionsAndMessages')
|
||||||
|
p
|
||||||
|
= t('emails.general.linkToYourAccount')
|
||||||
|
= " "
|
||||||
|
a(href=overviewURL) #{overviewURL}
|
||||||
|
p= t('emails.general.pleaseDoNotReply')
|
||||||
|
include ../greatingFormularImprint.pug
|
||||||
@ -0,0 +1 @@
|
|||||||
|
= t('emails.contributionDeleted.subject')
|
||||||
@ -1,517 +1,212 @@
|
|||||||
import { EventProtocol } from '@entity/EventProtocol'
|
import { EventProtocol as DbEvent } from '@entity/EventProtocol'
|
||||||
import decimal from 'decimal.js-light'
|
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 EventAdminContributionUpdate extends EventBasicCt {}
|
).save()
|
||||||
export class EventUserCreateContributionMessage extends EventBasicCtMsg {}
|
|
||||||
export class EventAdminCreateContributionMessage extends EventBasicCtMsg {}
|
export const EVENT_ADMIN_CONTRIBUTION_UPDATE = async (
|
||||||
export class EventContributionDelete extends EventBasicCt {}
|
userId: number,
|
||||||
export class EventContributionUpdate extends EventBasicCt {}
|
contributionId: number,
|
||||||
export class EventContributionConfirm extends EventBasicCtX {}
|
amount: Decimal,
|
||||||
export class EventContributionDeny extends EventBasicCtX {}
|
): Promise<DbEvent> =>
|
||||||
export class EventContributionLinkDefine extends EventBasicCt {}
|
Event(
|
||||||
export class EventContributionLinkActivateRedeem extends EventBasicCt {}
|
EventProtocolType.ADMIN_CONTRIBUTION_UPDATE,
|
||||||
export class EventDeleteUser extends EventBasicUserId {}
|
userId,
|
||||||
export class EventUndeleteUser extends EventBasicUserId {}
|
null,
|
||||||
export class EventChangeUserRole extends EventBasicUserId {}
|
null,
|
||||||
export class EventAdminUpdateContribution extends EventBasicCt {}
|
null,
|
||||||
export class EventAdminDeleteContribution extends EventBasicCt {}
|
contributionId,
|
||||||
export class EventCreateContributionLink extends EventBasicCt {}
|
amount,
|
||||||
export class EventDeleteContributionLink extends EventBasicCt {}
|
).save()
|
||||||
export class EventUpdateContributionLink extends EventBasicCt {}
|
|
||||||
|
export const EVENT_ADMIN_CONTRIBUTION_DELETE = async (
|
||||||
export class Event {
|
userId: number,
|
||||||
constructor()
|
contributionId: number,
|
||||||
constructor(event?: EventProtocol) {
|
amount: Decimal,
|
||||||
if (event) {
|
): Promise<DbEvent> =>
|
||||||
this.id = event.id
|
Event(
|
||||||
this.type = event.type
|
EventProtocolType.ADMIN_CONTRIBUTION_DELETE,
|
||||||
this.createdAt = event.createdAt
|
userId,
|
||||||
this.userId = event.userId
|
null,
|
||||||
this.xUserId = event.xUserId
|
null,
|
||||||
this.xCommunityId = event.xCommunityId
|
null,
|
||||||
this.transactionId = event.transactionId
|
contributionId,
|
||||||
this.contributionId = event.contributionId
|
amount,
|
||||||
this.amount = event.amount
|
).save()
|
||||||
}
|
|
||||||
}
|
export const EVENT_CONTRIBUTION_CONFIRM = async (
|
||||||
|
userId: number,
|
||||||
public setEventBasic(): Event {
|
contributionId: number,
|
||||||
this.type = EventProtocolType.BASIC
|
amount: Decimal,
|
||||||
this.createdAt = new Date()
|
): Promise<DbEvent> =>
|
||||||
|
Event(
|
||||||
return this
|
EventProtocolType.CONTRIBUTION_CONFIRM,
|
||||||
}
|
userId,
|
||||||
|
null,
|
||||||
public setEventVisitGradido(): Event {
|
null,
|
||||||
this.setEventBasic()
|
null,
|
||||||
this.type = EventProtocolType.VISIT_GRADIDO
|
contributionId,
|
||||||
|
amount,
|
||||||
return this
|
).save()
|
||||||
}
|
|
||||||
|
export const EVENT_ADMIN_CONTRIBUTION_DENY = async (
|
||||||
public setEventRegister(ev: EventRegister): Event {
|
userId: number,
|
||||||
this.setByBasicUser(ev.userId)
|
xUserId: number,
|
||||||
this.type = EventProtocolType.REGISTER
|
contributionId: number,
|
||||||
|
amount: Decimal,
|
||||||
return this
|
): Promise<DbEvent> =>
|
||||||
}
|
Event(
|
||||||
|
EventProtocolType.ADMIN_CONTRIBUTION_DENY,
|
||||||
public setEventRedeemRegister(ev: EventRedeemRegister): Event {
|
userId,
|
||||||
this.setByBasicRedeem(ev.userId, ev.transactionId, ev.contributionId)
|
xUserId,
|
||||||
this.type = EventProtocolType.REDEEM_REGISTER
|
null,
|
||||||
|
null,
|
||||||
return this
|
contributionId,
|
||||||
}
|
amount,
|
||||||
|
).save()
|
||||||
public setEventVerifyRedeem(ev: EventVerifyRedeem): Event {
|
|
||||||
this.setByBasicRedeem(ev.userId, ev.transactionId, ev.contributionId)
|
export const EVENT_TRANSACTION_SEND = async (
|
||||||
this.type = EventProtocolType.VERIFY_REDEEM
|
userId: number,
|
||||||
|
xUserId: number,
|
||||||
return this
|
transactionId: number,
|
||||||
}
|
amount: Decimal,
|
||||||
|
): Promise<DbEvent> =>
|
||||||
public setEventInactiveAccount(ev: EventInactiveAccount): Event {
|
Event(
|
||||||
this.setByBasicUser(ev.userId)
|
EventProtocolType.TRANSACTION_SEND,
|
||||||
this.type = EventProtocolType.INACTIVE_ACCOUNT
|
userId,
|
||||||
|
xUserId,
|
||||||
return this
|
null,
|
||||||
}
|
transactionId,
|
||||||
|
null,
|
||||||
public setEventSendConfirmationEmail(ev: EventSendConfirmationEmail): Event {
|
amount,
|
||||||
this.setByBasicUser(ev.userId)
|
).save()
|
||||||
this.type = EventProtocolType.SEND_CONFIRMATION_EMAIL
|
|
||||||
|
export const EVENT_TRANSACTION_RECEIVE = async (
|
||||||
return this
|
userId: number,
|
||||||
}
|
xUserId: number,
|
||||||
|
transactionId: number,
|
||||||
public setEventSendAccountMultiRegistrationEmail(
|
amount: Decimal,
|
||||||
ev: EventSendAccountMultiRegistrationEmail,
|
): Promise<DbEvent> =>
|
||||||
): Event {
|
Event(
|
||||||
this.setByBasicUser(ev.userId)
|
EventProtocolType.TRANSACTION_RECEIVE,
|
||||||
this.type = EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL
|
userId,
|
||||||
|
xUserId,
|
||||||
return this
|
null,
|
||||||
}
|
transactionId,
|
||||||
|
null,
|
||||||
public setEventSendForgotPasswordEmail(ev: EventSendForgotPasswordEmail): Event {
|
amount,
|
||||||
this.setByBasicUser(ev.userId)
|
).save()
|
||||||
this.type = EventProtocolType.SEND_FORGOT_PASSWORD_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 setEventSendTransactionSendEmail(ev: EventSendTransactionSendEmail): 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_SEND_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 setEventSendTransactionReceiveEmail(ev: EventSendTransactionReceiveEmail): Event {
|
Event(EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL, userId).save()
|
||||||
this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
|
|
||||||
this.type = EventProtocolType.SEND_TRANSACTION_RECEIVE_EMAIL
|
/* export const EVENT_REDEEM_REGISTER = async (
|
||||||
|
userId: number,
|
||||||
return this
|
transactionId: number | null = null,
|
||||||
}
|
contributionId: number | null = null,
|
||||||
|
): Promise<Event> =>
|
||||||
public setEventSendTransactionLinkRedeemEmail(ev: EventSendTransactionLinkRedeemEmail): Event {
|
Event(
|
||||||
this.setByBasicTxX(ev.userId, ev.transactionId, ev.amount, ev.xUserId, ev.xCommunityId)
|
EventProtocolType.REDEEM_REGISTER,
|
||||||
this.type = EventProtocolType.SEND_TRANSACTION_LINK_REDEEM_EMAIL
|
userId,
|
||||||
|
null,
|
||||||
return this
|
null,
|
||||||
}
|
transactionId,
|
||||||
|
contributionId,
|
||||||
public setEventSendAddedContributionEmail(ev: EventSendAddedContributionEmail): Event {
|
).save()
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
*/
|
||||||
this.type = EventProtocolType.SEND_ADDED_CONTRIBUTION_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 setEventSendContributionConfirmEmail(ev: EventSendContributionConfirmEmail): Event {
|
Event(EventProtocolType.ACTIVATE_ACCOUNT, userId).save()
|
||||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
|
||||||
this.type = EventProtocolType.SEND_CONTRIBUTION_CONFIRM_EMAIL
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventConfirmationEmail(ev: EventConfirmationEmail): Event {
|
|
||||||
this.setByBasicUser(ev.userId)
|
|
||||||
this.type = EventProtocolType.CONFIRM_EMAIL
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public setEventRegisterEmailKlicktipp(ev: EventRegisterEmailKlicktipp): Event {
|
|
||||||
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 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,41 +0,0 @@
|
|||||||
import { Event } from '@/event/Event'
|
|
||||||
import { backendLogger as logger } from '@/server/logger'
|
|
||||||
import { EventProtocol } from '@entity/EventProtocol'
|
|
||||||
import CONFIG from '@/config'
|
|
||||||
|
|
||||||
class EventProtocolEmitter {
|
|
||||||
/* }extends EventEmitter { */
|
|
||||||
private events: Event[]
|
|
||||||
|
|
||||||
/*
|
|
||||||
public addEvent(event: Event) {
|
|
||||||
this.events.push(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
public getEvents(): Event[] {
|
|
||||||
return this.events
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public isDisabled() {
|
|
||||||
logger.info(`EventProtocol - isDisabled=${CONFIG.EVENT_PROTOCOL_DISABLED}`)
|
|
||||||
return CONFIG.EVENT_PROTOCOL_DISABLED === true
|
|
||||||
}
|
|
||||||
|
|
||||||
public async writeEvent(event: Event): Promise<void> {
|
|
||||||
if (!eventProtocol.isDisabled()) {
|
|
||||||
logger.info(`writeEvent(${JSON.stringify(event)})`)
|
|
||||||
const dbEvent = new EventProtocol()
|
|
||||||
dbEvent.type = event.type
|
|
||||||
dbEvent.createdAt = event.createdAt
|
|
||||||
dbEvent.userId = event.userId
|
|
||||||
if (event.xUserId) dbEvent.xUserId = event.xUserId
|
|
||||||
if (event.xCommunityId) dbEvent.xCommunityId = event.xCommunityId
|
|
||||||
if (event.contributionId) dbEvent.contributionId = event.contributionId
|
|
||||||
if (event.transactionId) dbEvent.transactionId = event.transactionId
|
|
||||||
if (event.amount) dbEvent.amount = event.amount
|
|
||||||
await dbEvent.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export const eventProtocol = new EventProtocolEmitter()
|
|
||||||
@ -1,49 +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_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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,6 +15,8 @@ import { calculateDecay } from '@/util/decay'
|
|||||||
import { RIGHTS } from '@/auth/RIGHTS'
|
import { RIGHTS } from '@/auth/RIGHTS'
|
||||||
import { GdtResolver } from './GdtResolver'
|
import { GdtResolver } from './GdtResolver'
|
||||||
|
|
||||||
|
import { getLastTransaction } from './util/getLastTransaction'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class BalanceResolver {
|
export class BalanceResolver {
|
||||||
@Authorized([RIGHTS.BALANCE])
|
@Authorized([RIGHTS.BALANCE])
|
||||||
@ -32,7 +34,7 @@ export class BalanceResolver {
|
|||||||
|
|
||||||
const lastTransaction = context.lastTransaction
|
const lastTransaction = context.lastTransaction
|
||||||
? context.lastTransaction
|
? context.lastTransaction
|
||||||
: await dbTransaction.findOne({ userId: user.id }, { order: { id: 'DESC' } })
|
: await getLastTransaction(user.id)
|
||||||
|
|
||||||
logger.debug(`lastTransaction=${lastTransaction}`)
|
logger.debug(`lastTransaction=${lastTransaction}`)
|
||||||
|
|
||||||
|
|||||||
@ -313,27 +313,6 @@ describe('Contribution Links', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns an error if name is an empty string', async () => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: createContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
name: '',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('The name must be initialized')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith('The name must be initialized')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an error if name is shorter than 5 characters', async () => {
|
it('returns an error if name is shorter than 5 characters', async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
await expect(
|
await expect(
|
||||||
@ -376,27 +355,6 @@ describe('Contribution Links', () => {
|
|||||||
expect(logger.error).toBeCalledWith('The value of name is too long', 101)
|
expect(logger.error).toBeCalledWith('The value of name is too long', 101)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns an error if memo is an empty string', async () => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: createContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
memo: '',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('The memo must be initialized')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith('The memo must be initialized')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an error if memo is shorter than 5 characters', async () => {
|
it('returns an error if memo is shorter than 5 characters', async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
@ -40,20 +40,12 @@ export class ContributionLinkResolver {
|
|||||||
}: ContributionLinkArgs,
|
}: ContributionLinkArgs,
|
||||||
): Promise<ContributionLink> {
|
): Promise<ContributionLink> {
|
||||||
isStartEndDateValid(validFrom, validTo)
|
isStartEndDateValid(validFrom, validTo)
|
||||||
// TODO: this should be enforced by the schema.
|
|
||||||
if (!name) {
|
|
||||||
throw new LogError('The name must be initialized')
|
|
||||||
}
|
|
||||||
if (name.length < CONTRIBUTIONLINK_NAME_MIN_CHARS) {
|
if (name.length < CONTRIBUTIONLINK_NAME_MIN_CHARS) {
|
||||||
throw new LogError('The value of name is too short', name.length)
|
throw new LogError('The value of name is too short', name.length)
|
||||||
}
|
}
|
||||||
if (name.length > CONTRIBUTIONLINK_NAME_MAX_CHARS) {
|
if (name.length > CONTRIBUTIONLINK_NAME_MAX_CHARS) {
|
||||||
throw new LogError('The value of name is too long', name.length)
|
throw new LogError('The value of name is too long', name.length)
|
||||||
}
|
}
|
||||||
// TODO: this should be enforced by the schema.
|
|
||||||
if (!memo) {
|
|
||||||
throw new LogError('The memo must be initialized')
|
|
||||||
}
|
|
||||||
if (memo.length < MEMO_MIN_CHARS) {
|
if (memo.length < MEMO_MIN_CHARS) {
|
||||||
throw new LogError('The value of memo is too short', memo.length)
|
throw new LogError('The value of memo is too short', memo.length)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,14 +99,18 @@ describe('ContributionMessageResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('ContributionMessage was not sent successfully')],
|
errors: [
|
||||||
|
new GraphQLError(
|
||||||
|
'ContributionMessage was not sent successfully: Error: Contribution not found',
|
||||||
|
),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith(
|
||||||
'ContributionMessage was not sent successfully',
|
'ContributionMessage was not sent successfully: Error: Contribution not found',
|
||||||
new Error('Contribution not found'),
|
new Error('Contribution not found'),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -135,14 +139,18 @@ describe('ContributionMessageResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('ContributionMessage was not sent successfully')],
|
errors: [
|
||||||
|
new GraphQLError(
|
||||||
|
'ContributionMessage was not sent successfully: Error: Admin can not answer on his own contribution',
|
||||||
|
),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith(
|
||||||
'ContributionMessage was not sent successfully',
|
'ContributionMessage was not sent successfully: Error: Admin can not answer on his own contribution',
|
||||||
new Error('Admin can not answer on his own contribution'),
|
new Error('Admin can not answer on his own contribution'),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -229,14 +237,18 @@ describe('ContributionMessageResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('ContributionMessage was not sent successfully')],
|
errors: [
|
||||||
|
new GraphQLError(
|
||||||
|
'ContributionMessage was not sent successfully: Error: Contribution not found',
|
||||||
|
),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith(
|
||||||
'ContributionMessage was not sent successfully',
|
'ContributionMessage was not sent successfully: Error: Contribution not found',
|
||||||
new Error('Contribution not found'),
|
new Error('Contribution not found'),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -257,14 +269,18 @@ describe('ContributionMessageResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('ContributionMessage was not sent successfully')],
|
errors: [
|
||||||
|
new GraphQLError(
|
||||||
|
'ContributionMessage was not sent successfully: Error: Can not send message to contribution of another user',
|
||||||
|
),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith(
|
||||||
'ContributionMessage was not sent successfully',
|
'ContributionMessage was not sent successfully: Error: Can not send message to contribution of another user',
|
||||||
new Error('Can not send message to contribution of another user'),
|
new Error('Can not send message to contribution of another user'),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export class ContributionMessageResolver {
|
|||||||
await queryRunner.commitTransaction()
|
await queryRunner.commitTransaction()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await queryRunner.rollbackTransaction()
|
await queryRunner.rollbackTransaction()
|
||||||
throw new LogError('ContributionMessage was not sent successfully', e)
|
throw new LogError(`ContributionMessage was not sent successfully: ${e}`, e)
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
}
|
}
|
||||||
@ -144,7 +144,7 @@ export class ContributionMessageResolver {
|
|||||||
await queryRunner.commitTransaction()
|
await queryRunner.commitTransaction()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await queryRunner.rollbackTransaction()
|
await queryRunner.rollbackTransaction()
|
||||||
throw new LogError('ContributionMessage was not sent successfully', e)
|
throw new LogError(`ContributionMessage was not sent successfully: ${e}`, e)
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -37,24 +37,26 @@ import {
|
|||||||
} 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, FULL_CREATION_AVAILABLE } 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,
|
||||||
EventAdminContributionUpdate,
|
EVENT_ADMIN_CONTRIBUTION_DENY,
|
||||||
} from '@/event/Event'
|
} from '@/event/Event'
|
||||||
import { eventProtocol } from '@/event/EventProtocolEmitter'
|
|
||||||
import { calculateDecay } from '@/util/decay'
|
import { calculateDecay } from '@/util/decay'
|
||||||
import {
|
import {
|
||||||
sendContributionConfirmedEmail,
|
sendContributionConfirmedEmail,
|
||||||
|
sendContributionDeletedEmail,
|
||||||
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 LogError from '@/server/LogError'
|
||||||
|
|
||||||
|
import { getLastTransaction } from './util/getLastTransaction'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class ContributionResolver {
|
export class ContributionResolver {
|
||||||
@Authorized([RIGHTS.CREATE_CONTRIBUTION])
|
@Authorized([RIGHTS.CREATE_CONTRIBUTION])
|
||||||
@ -71,8 +73,6 @@ export class ContributionResolver {
|
|||||||
throw new LogError('Memo text is too long', memo.length)
|
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)
|
||||||
logger.trace('creations', creations)
|
logger.trace('creations', creations)
|
||||||
@ -91,11 +91,7 @@ export class ContributionResolver {
|
|||||||
logger.trace('contribution to save', contribution)
|
logger.trace('contribution to save', contribution)
|
||||||
await DbContribution.save(contribution)
|
await DbContribution.save(contribution)
|
||||||
|
|
||||||
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 eventProtocol.writeEvent(event.setEventContributionCreate(eventCreateContribution))
|
|
||||||
|
|
||||||
return new UnconfirmedContribution(contribution, user, creations)
|
return new UnconfirmedContribution(contribution, user, creations)
|
||||||
}
|
}
|
||||||
@ -106,7 +102,6 @@ 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) {
|
||||||
@ -124,11 +119,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 eventProtocol.writeEvent(event.setEventContributionDelete(eventDeleteContribution))
|
|
||||||
|
|
||||||
const res = await contribution.softRemove()
|
const res = await contribution.softRemove()
|
||||||
return !!res
|
return !!res
|
||||||
@ -190,6 +181,7 @@ export class ContributionResolver {
|
|||||||
.select('c')
|
.select('c')
|
||||||
.from(DbContribution, 'c')
|
.from(DbContribution, 'c')
|
||||||
.innerJoinAndSelect('c.user', 'u')
|
.innerJoinAndSelect('c.user', 'u')
|
||||||
|
.leftJoinAndSelect('c.messages', 'm')
|
||||||
.where(where)
|
.where(where)
|
||||||
.orderBy('c.createdAt', order)
|
.orderBy('c.createdAt', order)
|
||||||
.limit(pageSize)
|
.limit(pageSize)
|
||||||
@ -275,13 +267,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 eventProtocol.writeEvent(event.setEventContributionUpdate(eventUpdateContribution))
|
|
||||||
|
|
||||||
return new UnconfirmedContribution(contributionToUpdate, user, creations)
|
return new UnconfirmedContribution(contributionToUpdate, user, creations)
|
||||||
}
|
}
|
||||||
@ -317,7 +303,6 @@ export class ContributionResolver {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@ -339,13 +324,7 @@ 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 eventProtocol.writeEvent(
|
|
||||||
event.setEventAdminContributionCreate(eventAdminCreateContribution),
|
|
||||||
)
|
|
||||||
|
|
||||||
return getUserCreation(emailContact.userId, clientTimezoneOffset)
|
return getUserCreation(emailContact.userId, clientTimezoneOffset)
|
||||||
}
|
}
|
||||||
@ -440,14 +419,7 @@ export class ContributionResolver {
|
|||||||
|
|
||||||
result.creation = await getUserCreation(emailContact.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 = emailContact.user.id
|
|
||||||
eventAdminContributionUpdate.amount = amount
|
|
||||||
eventAdminContributionUpdate.contributionId = contributionToUpdate.id
|
|
||||||
await eventProtocol.writeEvent(
|
|
||||||
event.setEventAdminContributionUpdate(eventAdminContributionUpdate),
|
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -518,15 +490,9 @@ 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
|
sendContributionDeletedEmail({
|
||||||
eventAdminContributionDelete.amount = contribution.amount
|
|
||||||
eventAdminContributionDelete.contributionId = contribution.id
|
|
||||||
await eventProtocol.writeEvent(
|
|
||||||
event.setEventAdminContributionDelete(eventAdminContributionDelete),
|
|
||||||
)
|
|
||||||
sendContributionDeniedEmail({
|
|
||||||
firstName: user.firstName,
|
firstName: user.firstName,
|
||||||
lastName: user.lastName,
|
lastName: user.lastName,
|
||||||
email: user.emailContact.email,
|
email: user.emailContact.email,
|
||||||
@ -582,16 +548,11 @@ export class ContributionResolver {
|
|||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
await queryRunner.connect()
|
await queryRunner.connect()
|
||||||
await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED')
|
await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED')
|
||||||
try {
|
|
||||||
const lastTransaction = await queryRunner.manager
|
|
||||||
.createQueryBuilder()
|
|
||||||
.select('transaction')
|
|
||||||
.from(DbTransaction, 'transaction')
|
|
||||||
.where('transaction.userId = :id', { id: contribution.userId })
|
|
||||||
.orderBy('transaction.id', 'DESC')
|
|
||||||
.getOne()
|
|
||||||
logger.info('lastTransaction ID', lastTransaction ? lastTransaction.id : 'undefined')
|
|
||||||
|
|
||||||
|
const lastTransaction = await getLastTransaction(contribution.userId)
|
||||||
|
logger.info('lastTransaction ID', lastTransaction ? lastTransaction.id : 'undefined')
|
||||||
|
|
||||||
|
try {
|
||||||
let newBalance = new Decimal(0)
|
let newBalance = new Decimal(0)
|
||||||
let decay: Decay | null = null
|
let decay: Decay | null = null
|
||||||
if (lastTransaction) {
|
if (lastTransaction) {
|
||||||
@ -642,12 +603,7 @@ export class ContributionResolver {
|
|||||||
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 eventProtocol.writeEvent(event.setEventContributionConfirm(eventContributionConfirm))
|
|
||||||
} finally {
|
} finally {
|
||||||
releaseLock()
|
releaseLock()
|
||||||
}
|
}
|
||||||
@ -737,6 +693,13 @@ export class ContributionResolver {
|
|||||||
contributionToUpdate.deniedAt = new Date()
|
contributionToUpdate.deniedAt = new Date()
|
||||||
const res = await contributionToUpdate.save()
|
const res = await contributionToUpdate.save()
|
||||||
|
|
||||||
|
await EVENT_ADMIN_CONTRIBUTION_DENY(
|
||||||
|
contributionToUpdate.userId,
|
||||||
|
moderator.id,
|
||||||
|
contributionToUpdate.id,
|
||||||
|
contributionToUpdate.amount,
|
||||||
|
)
|
||||||
|
|
||||||
sendContributionDeniedEmail({
|
sendContributionDeniedEmail({
|
||||||
firstName: user.firstName,
|
firstName: user.firstName,
|
||||||
lastName: user.lastName,
|
lastName: user.lastName,
|
||||||
|
|||||||
@ -116,6 +116,11 @@ describe('TransactionLinkResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('redeemTransactionLink', () => {
|
describe('redeemTransactionLink', () => {
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
resetToken()
|
||||||
|
})
|
||||||
|
|
||||||
describe('contributionLink', () => {
|
describe('contributionLink', () => {
|
||||||
describe('input not valid', () => {
|
describe('input not valid', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@ -354,11 +359,6 @@ describe('TransactionLinkResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
/* expect(logger.error).toBeCalledWith(
|
|
||||||
'The amount to be created exceeds the amount still available for this month',
|
|
||||||
new Decimal(5),
|
|
||||||
new Decimal(0),
|
|
||||||
) */
|
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith(
|
||||||
'Creation from contribution link was not successful',
|
'Creation from contribution link was not successful',
|
||||||
new Error(
|
new Error(
|
||||||
@ -487,8 +487,7 @@ describe('TransactionLinkResolver', () => {
|
|||||||
pageSize: 5,
|
pageSize: 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: there is a test not cleaning up after itself! Fix it!
|
afterAll(async () => {
|
||||||
beforeAll(async () => {
|
|
||||||
await cleanDB()
|
await cleanDB()
|
||||||
resetToken()
|
resetToken()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -34,6 +34,8 @@ 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 LogError from '@/server/LogError'
|
||||||
|
|
||||||
|
import { getLastTransaction } from './util/getLastTransaction'
|
||||||
|
|
||||||
// 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 => {
|
||||||
const time = date.getTime().toString(16)
|
const time = date.getTime().toString(16)
|
||||||
@ -262,13 +264,7 @@ export class TransactionLinkResolver {
|
|||||||
|
|
||||||
await queryRunner.manager.insert(DbContribution, contribution)
|
await queryRunner.manager.insert(DbContribution, contribution)
|
||||||
|
|
||||||
const lastTransaction = await queryRunner.manager
|
const lastTransaction = await getLastTransaction(user.id)
|
||||||
.createQueryBuilder()
|
|
||||||
.select('transaction')
|
|
||||||
.from(DbTransaction, 'transaction')
|
|
||||||
.where('transaction.userId = :id', { id: user.id })
|
|
||||||
.orderBy('transaction.id', 'DESC')
|
|
||||||
.getOne()
|
|
||||||
let newBalance = new Decimal(0)
|
let newBalance = new Decimal(0)
|
||||||
|
|
||||||
let decay: Decay | null = null
|
let decay: Decay | null = null
|
||||||
|
|||||||
@ -330,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,
|
||||||
@ -347,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,8 +29,7 @@ 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 { eventProtocol } 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'
|
||||||
@ -39,6 +38,8 @@ import { findUserByEmail } from './UserResolver'
|
|||||||
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||||
import LogError from '@/server/LogError'
|
import LogError from '@/server/LogError'
|
||||||
|
|
||||||
|
import { getLastTransaction } from './util/getLastTransaction'
|
||||||
|
|
||||||
export const executeTransaction = async (
|
export const executeTransaction = async (
|
||||||
amount: Decimal,
|
amount: Decimal,
|
||||||
memo: string,
|
memo: string,
|
||||||
@ -136,20 +137,18 @@ 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 eventProtocol.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 eventProtocol.writeEvent(
|
|
||||||
new Event().setEventTransactionReceive(eventTransactionReceive),
|
|
||||||
)
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await queryRunner.rollbackTransaction()
|
await queryRunner.rollbackTransaction()
|
||||||
@ -204,10 +203,7 @@ export class TransactionResolver {
|
|||||||
logger.info(`transactionList(user=${user.firstName}.${user.lastName}, ${user.emailId})`)
|
logger.info(`transactionList(user=${user.firstName}.${user.lastName}, ${user.emailId})`)
|
||||||
|
|
||||||
// find current balance
|
// find current balance
|
||||||
const lastTransaction = await dbTransaction.findOne(
|
const lastTransaction = await getLastTransaction(user.id, ['contribution'])
|
||||||
{ userId: user.id },
|
|
||||||
{ order: { id: 'DESC' }, relations: ['contribution'] },
|
|
||||||
)
|
|
||||||
logger.debug(`lastTransaction=${lastTransaction}`)
|
logger.debug(`lastTransaction=${lastTransaction}`)
|
||||||
|
|
||||||
const balanceResolver = new BalanceResolver()
|
const balanceResolver = new BalanceResolver()
|
||||||
|
|||||||
@ -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 { eventProtocol } 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)
|
||||||
eventProtocol.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,11 +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)
|
||||||
eventProtocol.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}>`,
|
||||||
)
|
)
|
||||||
@ -270,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
|
||||||
@ -290,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -335,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
|
|
||||||
eventProtocol.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}`)
|
||||||
@ -353,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 eventProtocol.writeEvent(event.setEventRedeemRegister(eventRedeemRegister))
|
await eventRegisterRedeem.save()
|
||||||
} else {
|
} else {
|
||||||
eventRegister.userId = dbUser.id
|
await EVENT_REGISTER(dbUser.id)
|
||||||
await eventProtocol.writeEvent(event.setEventRegister(eventRegister))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new User(dbUser)
|
return new User(dbUser)
|
||||||
@ -460,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) => {
|
||||||
@ -475,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
|
|
||||||
eventProtocol.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)
|
||||||
@ -793,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({
|
||||||
@ -813,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),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -821,12 +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 eventProtocol.writeEvent(
|
|
||||||
event.setEventSendConfirmationEmail(eventSendConfirmationEmail),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
14
backend/src/graphql/resolver/util/getLastTransaction.ts
Normal file
14
backend/src/graphql/resolver/util/getLastTransaction.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||||
|
|
||||||
|
export const getLastTransaction = async (
|
||||||
|
userId: number,
|
||||||
|
relations?: string[],
|
||||||
|
): Promise<DbTransaction | undefined> => {
|
||||||
|
return DbTransaction.findOne(
|
||||||
|
{ userId },
|
||||||
|
{
|
||||||
|
order: { balanceDate: 'DESC', id: 'DESC' },
|
||||||
|
relations,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -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,12 +14,22 @@
|
|||||||
"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ü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
|
||||||
|
},
|
||||||
"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"
|
||||||
},
|
},
|
||||||
"contributionRejected": {
|
"contributionDeleted": {
|
||||||
"commonGoodContributionRejected": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} abgelehnt.",
|
"commonGoodContributionDeleted": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} gelöscht.",
|
||||||
|
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde gelöscht",
|
||||||
|
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
|
||||||
|
},
|
||||||
|
"contributionDenied": {
|
||||||
|
"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ü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
{
|
{
|
||||||
"emails": {
|
"emails": {
|
||||||
"addedContributionMessage": {
|
|
||||||
"commonGoodContributionMessage": "you have received a message from {senderFirstName} {senderLastName} regarding your common good contribution “{contributionMemo}”.",
|
|
||||||
"subject": "Gradido: Message about your common good contribution",
|
|
||||||
"toSeeAndAnswerMessage": "To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
|
|
||||||
},
|
|
||||||
"accountActivation": {
|
"accountActivation": {
|
||||||
"duration": "The link has a validity of {hours} hours and {minutes} minutes. If the validity of the link has already expired, you can have a new link sent to you here:",
|
"duration": "The link has a validity of {hours} hours and {minutes} minutes. If the validity of the link has already expired, you can have a new link sent to you here:",
|
||||||
"emailRegistered": "Your email address has just been registered with Gradido.",
|
"emailRegistered": "Your email address has just been registered with Gradido.",
|
||||||
@ -19,10 +14,20 @@
|
|||||||
"onForgottenPasswordCopyLink": "or copy the link above into your browser window.",
|
"onForgottenPasswordCopyLink": "or copy the link above into your browser window.",
|
||||||
"subject": "Gradido: Try To Register Again With Your Email"
|
"subject": "Gradido: Try To Register Again With Your Email"
|
||||||
},
|
},
|
||||||
|
"addedContributionMessage": {
|
||||||
|
"commonGoodContributionMessage": "you have received a message from {senderFirstName} {senderLastName} regarding your common good contribution “{contributionMemo}”.",
|
||||||
|
"subject": "Gradido: Message about your common good contribution",
|
||||||
|
"toSeeAndAnswerMessage": "To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
|
||||||
|
},
|
||||||
"contributionConfirmed": {
|
"contributionConfirmed": {
|
||||||
"commonGoodContributionConfirmed": "Your public good contribution “{contributionMemo}” has just been confirmed by {senderFirstName} {senderLastName} and credited to your Gradido account.",
|
"commonGoodContributionConfirmed": "Your public good contribution “{contributionMemo}” has just been confirmed by {senderFirstName} {senderLastName} and credited to your Gradido account.",
|
||||||
"subject": "Gradido: Your contribution to the common good was confirmed"
|
"subject": "Gradido: Your contribution to the common good was confirmed"
|
||||||
},
|
},
|
||||||
|
"contributionDeleted": {
|
||||||
|
"commonGoodContributionDeleted": "Your public good contribution “{contributionMemo}” was deleted by {senderFirstName} {senderLastName}.",
|
||||||
|
"subject": "Gradido: Your common good contribution was deleted",
|
||||||
|
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
|
||||||
|
},
|
||||||
"contributionDenied": {
|
"contributionDenied": {
|
||||||
"commonGoodContributionDenied": "Your public good contribution “{contributionMemo}” was rejected by {senderFirstName} {senderLastName}.",
|
"commonGoodContributionDenied": "Your public good contribution “{contributionMemo}” was rejected by {senderFirstName} {senderLastName}.",
|
||||||
"subject": "Gradido: Your common good contribution was rejected",
|
"subject": "Gradido: Your common good contribution was rejected",
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export const nMonthsBefore = (date: Date, months = 1): string => {
|
|||||||
export const creationFactory = async (
|
export const creationFactory = async (
|
||||||
client: ApolloServerTestClient,
|
client: ApolloServerTestClient,
|
||||||
creation: CreationInterface,
|
creation: CreationInterface,
|
||||||
): Promise<Contribution | void> => {
|
): Promise<Contribution> => {
|
||||||
const { mutate } = client
|
const { mutate } = client
|
||||||
await mutate({ mutation: login, variables: { email: creation.email, password: 'Aa12345_' } })
|
await mutate({ mutation: login, variables: { email: creation.email, password: 'Aa12345_' } })
|
||||||
|
|
||||||
@ -51,6 +51,7 @@ export const creationFactory = async (
|
|||||||
await confirmedContribution.save()
|
await confirmedContribution.save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return confirmedContribution
|
||||||
} else {
|
} else {
|
||||||
return contribution
|
return contribution
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,6 +68,12 @@ export const createUser = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const sendActivationEmail = gql`
|
||||||
|
mutation ($email: String!) {
|
||||||
|
sendActivationEmail(email: $email)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export const sendCoins = gql`
|
export const sendCoins = gql`
|
||||||
mutation ($email: String!, $amount: Decimal!, $memo: String!) {
|
mutation ($email: String!, $amount: Decimal!, $memo: String!) {
|
||||||
sendCoins(email: $email, amount: $amount, memo: $memo)
|
sendCoins(email: $email, amount: $amount, memo: $memo)
|
||||||
@ -266,6 +272,12 @@ export const deleteContribution = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const denyContribution = gql`
|
||||||
|
mutation ($id: Int!) {
|
||||||
|
denyContribution(id: $id)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export const createContributionMessage = gql`
|
export const createContributionMessage = gql`
|
||||||
mutation ($contributionId: Float!, $message: String!) {
|
mutation ($contributionId: Float!, $message: String!) {
|
||||||
createContributionMessage(contributionId: $contributionId, message: $message) {
|
createContributionMessage(contributionId: $contributionId, message: $message) {
|
||||||
|
|||||||
@ -166,6 +166,15 @@ export const listContributions = gql`
|
|||||||
id
|
id
|
||||||
amount
|
amount
|
||||||
memo
|
memo
|
||||||
|
createdAt
|
||||||
|
contributionDate
|
||||||
|
confirmedAt
|
||||||
|
confirmedBy
|
||||||
|
deletedAt
|
||||||
|
state
|
||||||
|
messagesCount
|
||||||
|
deniedAt
|
||||||
|
deniedBy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { UserInterface } from './UserInterface'
|
import { UserInterface } from './UserInterface'
|
||||||
|
|
||||||
// TODO: the generated email_contact is not deleted
|
|
||||||
export const stephenHawking: UserInterface = {
|
export const stephenHawking: UserInterface = {
|
||||||
email: 'stephen@hawking.uk',
|
email: 'stephen@hawking.uk',
|
||||||
firstName: 'Stephen',
|
firstName: 'Stephen',
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { calculateDecay } from './decay'
|
import { calculateDecay } from './decay'
|
||||||
import Decimal from 'decimal.js-light'
|
import Decimal from 'decimal.js-light'
|
||||||
import { Transaction } from '@entity/Transaction'
|
|
||||||
import { Decay } from '@model/Decay'
|
import { Decay } from '@model/Decay'
|
||||||
import { getCustomRepository } from '@dbTools/typeorm'
|
import { getCustomRepository } from '@dbTools/typeorm'
|
||||||
import { TransactionLinkRepository } from '@repository/TransactionLink'
|
import { TransactionLinkRepository } from '@repository/TransactionLink'
|
||||||
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
||||||
|
import { getLastTransaction } from '../graphql/resolver/util/getLastTransaction'
|
||||||
|
|
||||||
function isStringBoolean(value: string): boolean {
|
function isStringBoolean(value: string): boolean {
|
||||||
const lowerValue = value.toLowerCase()
|
const lowerValue = value.toLowerCase()
|
||||||
@ -20,7 +20,7 @@ async function calculateBalance(
|
|||||||
time: Date,
|
time: Date,
|
||||||
transactionLink?: dbTransactionLink | null,
|
transactionLink?: dbTransactionLink | null,
|
||||||
): Promise<{ balance: Decimal; decay: Decay; lastTransactionId: number } | null> {
|
): Promise<{ balance: Decimal; decay: Decay; lastTransactionId: number } | null> {
|
||||||
const lastTransaction = await Transaction.findOne({ userId }, { order: { id: 'DESC' } })
|
const lastTransaction = await getLastTransaction(userId)
|
||||||
if (!lastTransaction) return null
|
if (!lastTransaction) return null
|
||||||
|
|
||||||
const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time)
|
const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time)
|
||||||
|
|||||||
@ -404,6 +404,11 @@
|
|||||||
minimatch "^3.0.4"
|
minimatch "^3.0.4"
|
||||||
strip-json-comments "^3.1.1"
|
strip-json-comments "^3.1.1"
|
||||||
|
|
||||||
|
"@graphql-typed-document-node/core@^3.1.1":
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.1.tgz#076d78ce99822258cf813ecc1e7fa460fa74d052"
|
||||||
|
integrity sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==
|
||||||
|
|
||||||
"@hapi/boom@^10.0.0":
|
"@hapi/boom@^10.0.0":
|
||||||
version "10.0.0"
|
version "10.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-10.0.0.tgz#3624831d0a26b3378423b246f50eacea16e04a08"
|
resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-10.0.0.tgz#3624831d0a26b3378423b246f50eacea16e04a08"
|
||||||
@ -430,42 +435,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf"
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf"
|
||||||
integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==
|
integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==
|
||||||
|
|
||||||
"@hyperswarm/dht@^6.2.0":
|
|
||||||
version "6.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@hyperswarm/dht/-/dht-6.2.0.tgz#b2cb1218752b52fabb66f304e73448a108d1effd"
|
|
||||||
integrity sha512-AeyfRdAkfCz/J3vTC4rdpzEpT7xQ+tls87Zpzw9Py3VGUZD8hMT7pr43OOdkCBNvcln6K/5/Lxhnq5lBkzH3yw==
|
|
||||||
dependencies:
|
|
||||||
"@hyperswarm/secret-stream" "^6.0.0"
|
|
||||||
b4a "^1.3.1"
|
|
||||||
bogon "^1.0.0"
|
|
||||||
compact-encoding "^2.4.1"
|
|
||||||
compact-encoding-net "^1.0.1"
|
|
||||||
debugging-stream "^2.0.0"
|
|
||||||
dht-rpc "^6.0.0"
|
|
||||||
events "^3.3.0"
|
|
||||||
hypercore-crypto "^3.3.0"
|
|
||||||
noise-curve-ed "^1.0.2"
|
|
||||||
noise-handshake "^2.1.0"
|
|
||||||
record-cache "^1.1.1"
|
|
||||||
safety-catch "^1.0.1"
|
|
||||||
sodium-universal "^3.0.4"
|
|
||||||
udx-native "^1.1.0"
|
|
||||||
xache "^1.1.0"
|
|
||||||
|
|
||||||
"@hyperswarm/secret-stream@^6.0.0":
|
|
||||||
version "6.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@hyperswarm/secret-stream/-/secret-stream-6.0.0.tgz#67db820308cc9fed899cb8f5e9f47ae819d5a4e3"
|
|
||||||
integrity sha512-0xuyJIJDe8JYk4uWUx25qJvWqybdjKU2ZIfP1GTqd7dQxwdR0bpYrQKdLkrn5txWSK4a28ySC2AjH0G3I0gXTA==
|
|
||||||
dependencies:
|
|
||||||
b4a "^1.1.0"
|
|
||||||
hypercore-crypto "^3.3.0"
|
|
||||||
noise-curve-ed "^1.0.2"
|
|
||||||
noise-handshake "^2.1.0"
|
|
||||||
sodium-secretstream "^1.0.0"
|
|
||||||
sodium-universal "^3.0.4"
|
|
||||||
streamx "^2.10.2"
|
|
||||||
timeout-refresh "^2.0.0"
|
|
||||||
|
|
||||||
"@istanbuljs/load-nyc-config@^1.0.0":
|
"@istanbuljs/load-nyc-config@^1.0.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
|
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
|
||||||
@ -1655,11 +1624,6 @@ axios@^0.21.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects "^1.14.0"
|
follow-redirects "^1.14.0"
|
||||||
|
|
||||||
b4a@^1.0.1, b4a@^1.1.0, b4a@^1.1.1, b4a@^1.3.0, b4a@^1.3.1, b4a@^1.5.0:
|
|
||||||
version "1.5.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.5.3.tgz#56293b5607aeda3fd81c481e516e9f103fc88341"
|
|
||||||
integrity sha512-1aCQIzQJK7G0z1Una75tWMlwVAR8o+QHoAlnWc5XAxRVBESY9WsitfBgM5nPyDBP5HrhPU1Np4Pq2Y7CJQ+tVw==
|
|
||||||
|
|
||||||
babel-jest@^27.2.5:
|
babel-jest@^27.2.5:
|
||||||
version "27.2.5"
|
version "27.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.2.5.tgz#6bbbc1bb4200fe0bfd1b1fbcbe02fc62ebed16aa"
|
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.2.5.tgz#6bbbc1bb4200fe0bfd1b1fbcbe02fc62ebed16aa"
|
||||||
@ -1743,22 +1707,6 @@ binary-extensions@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||||
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
||||||
|
|
||||||
blake2b-wasm@^2.4.0:
|
|
||||||
version "2.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/blake2b-wasm/-/blake2b-wasm-2.4.0.tgz#9115649111edbbd87eb24ce7c04b427e4e2be5be"
|
|
||||||
integrity sha512-S1kwmW2ZhZFFFOghcx73+ZajEfKBqhP82JMssxtLVMxlaPea1p9uoLiUZ5WYyHn0KddwbLc+0vh4wR0KBNoT5w==
|
|
||||||
dependencies:
|
|
||||||
b4a "^1.0.1"
|
|
||||||
nanoassert "^2.0.0"
|
|
||||||
|
|
||||||
blake2b@^2.1.1:
|
|
||||||
version "2.1.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/blake2b/-/blake2b-2.1.4.tgz#817d278526ddb4cd673bfb1af16d1ad61e393ba3"
|
|
||||||
integrity sha512-AyBuuJNI64gIvwx13qiICz6H6hpmjvYS5DGkG6jbXMOT8Z3WUJ3V1X0FlhIoT1b/5JtHE3ki+xjtMvu1nn+t9A==
|
|
||||||
dependencies:
|
|
||||||
blake2b-wasm "^2.4.0"
|
|
||||||
nanoassert "^2.0.0"
|
|
||||||
|
|
||||||
bluebird@^3.7.2:
|
bluebird@^3.7.2:
|
||||||
version "3.7.2"
|
version "3.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
||||||
@ -1780,11 +1728,6 @@ body-parser@1.19.0, body-parser@^1.18.3:
|
|||||||
raw-body "2.4.0"
|
raw-body "2.4.0"
|
||||||
type-is "~1.6.17"
|
type-is "~1.6.17"
|
||||||
|
|
||||||
bogon@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/bogon/-/bogon-1.0.0.tgz#66b8cdd269f790e3aa988e157bb34d4ba75ee586"
|
|
||||||
integrity sha512-mXxtlBtnW8koqFWPUBtKJm97vBSKZRpOvxvMRVun33qQXwMNfQzq9eTcQzKzqEoNUhNqF9t8rDc/wakKCcHMTg==
|
|
||||||
|
|
||||||
boolbase@^1.0.0:
|
boolbase@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
||||||
@ -1917,13 +1860,6 @@ caniuse-lite@^1.0.30001264:
|
|||||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz"
|
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz"
|
||||||
integrity sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==
|
integrity sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==
|
||||||
|
|
||||||
chacha20-universal@^1.0.4:
|
|
||||||
version "1.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/chacha20-universal/-/chacha20-universal-1.0.4.tgz#e8a33a386500b1ce5361b811ec5e81f1797883f5"
|
|
||||||
integrity sha512-/IOxdWWNa7nRabfe7+oF+jVkGjlr2xUL4J8l/OvzZhj+c9RpMqoo3Dq+5nU1j/BflRV4BKnaQ4+4oH1yBpQG1Q==
|
|
||||||
dependencies:
|
|
||||||
nanoassert "^2.0.0"
|
|
||||||
|
|
||||||
chalk@^2.0.0:
|
chalk@^2.0.0:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||||
@ -2093,20 +2029,6 @@ commander@^6.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
|
||||||
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
|
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
|
||||||
|
|
||||||
compact-encoding-net@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/compact-encoding-net/-/compact-encoding-net-1.0.1.tgz#4da743d52721f5d0cc73a6d00556a96bc9b9fa1b"
|
|
||||||
integrity sha512-N9k1Qwg9b1ENk+TZsZhthzkuMtn3rn4ZinN75gf3/LplE+uaTCKjyaau5sK0m2NEUa/MmR77VxiGfD/Qz1ar0g==
|
|
||||||
dependencies:
|
|
||||||
compact-encoding "^2.4.1"
|
|
||||||
|
|
||||||
compact-encoding@^2.1.0, compact-encoding@^2.4.1, compact-encoding@^2.5.1:
|
|
||||||
version "2.7.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/compact-encoding/-/compact-encoding-2.7.0.tgz#e6a0df408c25cbcdf7d619c97527074478cafd06"
|
|
||||||
integrity sha512-2I0A+pYKXYwxewbLxj26tU4pJyKlFNjadzjZ+36xJ5HwTrnhD9KcMQk3McEQRl1at6jrwA8E7UjmBdsGhEAPMw==
|
|
||||||
dependencies:
|
|
||||||
b4a "^1.3.0"
|
|
||||||
|
|
||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
@ -2193,6 +2115,13 @@ cross-env@^7.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
cross-spawn "^7.0.1"
|
cross-spawn "^7.0.1"
|
||||||
|
|
||||||
|
cross-fetch@^3.1.5:
|
||||||
|
version "3.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
|
||||||
|
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
|
||||||
|
dependencies:
|
||||||
|
node-fetch "2.6.7"
|
||||||
|
|
||||||
cross-spawn@^6.0.0:
|
cross-spawn@^6.0.0:
|
||||||
version "6.0.5"
|
version "6.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
||||||
@ -2305,13 +2234,6 @@ debug@^4.3.3, debug@^4.3.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "2.1.2"
|
ms "2.1.2"
|
||||||
|
|
||||||
debugging-stream@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/debugging-stream/-/debugging-stream-2.0.0.tgz#515cad5a35299cf4b4bc0afcbd69d52c809c84ce"
|
|
||||||
integrity sha512-xwfl6wB/3xc553uwtGnSa94jFxnGOc02C0WU2Nmzwr80gzeqn1FX4VcbvoKIhe8L/lPq4BTQttAbrTN94uN8rA==
|
|
||||||
dependencies:
|
|
||||||
streamx "^2.12.4"
|
|
||||||
|
|
||||||
decimal.js-light@^2.5.1:
|
decimal.js-light@^2.5.1:
|
||||||
version "2.5.1"
|
version "2.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934"
|
resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934"
|
||||||
@ -2391,23 +2313,6 @@ detect-newline@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
|
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
|
||||||
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
|
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
|
||||||
|
|
||||||
dht-rpc@^6.0.0:
|
|
||||||
version "6.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/dht-rpc/-/dht-rpc-6.1.1.tgz#a292a22aa19b05136978d33528cb571d6e32502f"
|
|
||||||
integrity sha512-wo0nMXwn/rhxVz62V0d+l/0HuikxLQh6lkwlUIdoaUzGl9DobFj4epSScD3/lTMwKts+Ih0DFNqP+j0tYwdajQ==
|
|
||||||
dependencies:
|
|
||||||
b4a "^1.3.1"
|
|
||||||
compact-encoding "^2.1.0"
|
|
||||||
compact-encoding-net "^1.0.1"
|
|
||||||
events "^3.3.0"
|
|
||||||
fast-fifo "^1.0.0"
|
|
||||||
kademlia-routing-table "^1.0.0"
|
|
||||||
nat-sampler "^1.0.1"
|
|
||||||
sodium-universal "^3.0.4"
|
|
||||||
streamx "^2.10.3"
|
|
||||||
time-ordered-set "^1.0.2"
|
|
||||||
udx-native "^1.1.0"
|
|
||||||
|
|
||||||
dicer@0.3.0:
|
dicer@0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872"
|
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872"
|
||||||
@ -2899,11 +2804,6 @@ eventemitter3@^3.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
|
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
|
||||||
integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==
|
integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==
|
||||||
|
|
||||||
events@^3.3.0:
|
|
||||||
version "3.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
|
||||||
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
|
|
||||||
|
|
||||||
execa@^0.10.0:
|
execa@^0.10.0:
|
||||||
version "0.10.0"
|
version "0.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50"
|
resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50"
|
||||||
@ -2985,6 +2885,11 @@ express@^4.17.1:
|
|||||||
utils-merge "1.0.1"
|
utils-merge "1.0.1"
|
||||||
vary "~1.1.2"
|
vary "~1.1.2"
|
||||||
|
|
||||||
|
extract-files@^9.0.0:
|
||||||
|
version "9.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a"
|
||||||
|
integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==
|
||||||
|
|
||||||
faker@^5.5.3:
|
faker@^5.5.3:
|
||||||
version "5.5.3"
|
version "5.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/faker/-/faker-5.5.3.tgz#c57974ee484431b25205c2c8dc09fda861e51e0e"
|
resolved "https://registry.yarnpkg.com/faker/-/faker-5.5.3.tgz#c57974ee484431b25205c2c8dc09fda861e51e0e"
|
||||||
@ -3000,11 +2905,6 @@ fast-diff@^1.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
|
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
|
||||||
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
|
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
|
||||||
|
|
||||||
fast-fifo@^1.0.0:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.1.0.tgz#17d1a3646880b9891dfa0c54e69c5fef33cad779"
|
|
||||||
integrity sha512-Kl29QoNbNvn4nhDsLYjyIAaIqaJB6rBx5p3sL9VjaefJ+eMFBWVZiaoguaoZfzEKr5RhAti0UgM8703akGPJ6g==
|
|
||||||
|
|
||||||
fast-glob@^3.1.1:
|
fast-glob@^3.1.1:
|
||||||
version "3.2.7"
|
version "3.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
|
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
|
||||||
@ -3340,6 +3240,16 @@ graphql-query-complexity@^0.7.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash.get "^4.4.2"
|
lodash.get "^4.4.2"
|
||||||
|
|
||||||
|
graphql-request@5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-5.0.0.tgz#7504a807d0e11be11a3c448e900f0cc316aa18ef"
|
||||||
|
integrity sha512-SpVEnIo2J5k2+Zf76cUkdvIRaq5FMZvGQYnA4lUWYbc99m+fHh4CZYRRO/Ff4tCLQ613fzCm3SiDT64ubW5Gyw==
|
||||||
|
dependencies:
|
||||||
|
"@graphql-typed-document-node/core" "^3.1.1"
|
||||||
|
cross-fetch "^3.1.5"
|
||||||
|
extract-files "^9.0.0"
|
||||||
|
form-data "^3.0.0"
|
||||||
|
|
||||||
graphql-subscriptions@^1.0.0, graphql-subscriptions@^1.1.0:
|
graphql-subscriptions@^1.0.0, graphql-subscriptions@^1.1.0:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-1.2.1.tgz#2142b2d729661ddf967b7388f7cf1dd4cf2e061d"
|
resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-1.2.1.tgz#2142b2d729661ddf967b7388f7cf1dd4cf2e061d"
|
||||||
@ -3414,15 +3324,6 @@ he@1.2.0, he@^1.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||||
|
|
||||||
hmac-blake2b@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/hmac-blake2b/-/hmac-blake2b-2.0.0.tgz#09494e5d245d7afe45d157093080b159f7bacf15"
|
|
||||||
integrity sha512-JbGNtM1YRd8EQH/2vNTAP1oy5lJVPlBFYZfCJTu3k8sqOUm0rRIf/3+MCd5noVykETwTbun6jEOc+4Tu78ubHA==
|
|
||||||
dependencies:
|
|
||||||
nanoassert "^1.1.0"
|
|
||||||
sodium-native "^3.1.1"
|
|
||||||
sodium-universal "^3.0.0"
|
|
||||||
|
|
||||||
hosted-git-info@^2.1.4:
|
hosted-git-info@^2.1.4:
|
||||||
version "2.8.9"
|
version "2.8.9"
|
||||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||||
@ -3544,15 +3445,6 @@ human-signals@^2.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||||
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
||||||
|
|
||||||
hypercore-crypto@^3.3.0:
|
|
||||||
version "3.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/hypercore-crypto/-/hypercore-crypto-3.3.0.tgz#03ab5b44608a563e131f629f671c6f90a83c52e6"
|
|
||||||
integrity sha512-zAWbDqG7kWwS6rCxxTUeB/OeFAz3PoOmouKaoMubtDJYJsLHqXtA3wE2mLsw+E2+iYyom5zrFyBTFVYxmgwW6g==
|
|
||||||
dependencies:
|
|
||||||
b4a "^1.1.0"
|
|
||||||
compact-encoding "^2.5.1"
|
|
||||||
sodium-universal "^3.0.0"
|
|
||||||
|
|
||||||
i18n-locales@^0.0.5:
|
i18n-locales@^0.0.5:
|
||||||
version "0.0.5"
|
version "0.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/i18n-locales/-/i18n-locales-0.0.5.tgz#8f587e598ab982511d7c7db910cb45b8d93cd96a"
|
resolved "https://registry.yarnpkg.com/i18n-locales/-/i18n-locales-0.0.5.tgz#8f587e598ab982511d7c7db910cb45b8d93cd96a"
|
||||||
@ -4517,11 +4409,6 @@ jws@^3.2.2:
|
|||||||
jwa "^1.4.1"
|
jwa "^1.4.1"
|
||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
kademlia-routing-table@^1.0.0:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/kademlia-routing-table/-/kademlia-routing-table-1.0.1.tgz#6f18416f612e885a8d4df128f04c490a90d772f6"
|
|
||||||
integrity sha512-dKk19sC3/+kWhBIvOKCthxVV+JH0NrswSBq4sA4eOkkPMqQM1rRuOWte1WSKXeP8r9Nx4NuiH2gny3lMddJTpw==
|
|
||||||
|
|
||||||
keyv@^3.0.0:
|
keyv@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
|
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
|
||||||
@ -4932,26 +4819,6 @@ named-placeholders@^1.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lru-cache "^4.1.3"
|
lru-cache "^4.1.3"
|
||||||
|
|
||||||
nanoassert@^1.1.0:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/nanoassert/-/nanoassert-1.1.0.tgz#4f3152e09540fde28c76f44b19bbcd1d5a42478d"
|
|
||||||
integrity sha512-C40jQ3NzfkP53NsO8kEOFd79p4b9kDXQMwgiY1z8ZwrDZgUyom0AHwGegF4Dm99L+YoYhuaB0ceerUcXmqr1rQ==
|
|
||||||
|
|
||||||
nanoassert@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/nanoassert/-/nanoassert-2.0.0.tgz#a05f86de6c7a51618038a620f88878ed1e490c09"
|
|
||||||
integrity sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==
|
|
||||||
|
|
||||||
napi-macros@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b"
|
|
||||||
integrity sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==
|
|
||||||
|
|
||||||
nat-sampler@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/nat-sampler/-/nat-sampler-1.0.1.tgz#2b68338ea6d4c139450cd971fd00a4ac1b33d923"
|
|
||||||
integrity sha512-yQvyNN7xbqR8crTKk3U8gRgpcV1Az+vfCEijiHu9oHHsnIl8n3x+yXNHl42M6L3czGynAVoOT9TqBfS87gDdcw==
|
|
||||||
|
|
||||||
natural-compare@^1.4.0:
|
natural-compare@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||||
@ -4977,7 +4844,7 @@ nice-try@^1.0.4:
|
|||||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||||
|
|
||||||
node-fetch@^2.6.0:
|
node-fetch@2.6.7, node-fetch@^2.6.0:
|
||||||
version "2.6.7"
|
version "2.6.7"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||||
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||||
@ -4991,7 +4858,7 @@ node-fetch@^2.6.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
whatwg-url "^5.0.0"
|
whatwg-url "^5.0.0"
|
||||||
|
|
||||||
node-gyp-build@^4.3.0, node-gyp-build@^4.4.0:
|
node-gyp-build@^4.3.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40"
|
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40"
|
||||||
integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==
|
integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==
|
||||||
@ -5042,25 +4909,6 @@ nodemon@^2.0.7:
|
|||||||
undefsafe "^2.0.3"
|
undefsafe "^2.0.3"
|
||||||
update-notifier "^5.1.0"
|
update-notifier "^5.1.0"
|
||||||
|
|
||||||
noise-curve-ed@^1.0.2:
|
|
||||||
version "1.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/noise-curve-ed/-/noise-curve-ed-1.0.4.tgz#8ae83f5d2d2e31d0c9c069271ca6e462d31cd884"
|
|
||||||
integrity sha512-plUUSEOU66FZ9TaBKpk4+fgQeeS+OLlThS2o8a1TxVpMWV2v1izvEnjSpFV9gEPZl4/1yN+S5KqLubFjogqQOw==
|
|
||||||
dependencies:
|
|
||||||
b4a "^1.1.0"
|
|
||||||
nanoassert "^2.0.0"
|
|
||||||
sodium-universal "^3.0.4"
|
|
||||||
|
|
||||||
noise-handshake@^2.1.0:
|
|
||||||
version "2.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/noise-handshake/-/noise-handshake-2.2.0.tgz#24c98f502d49118770e1ec2af2894b8789f0ac7c"
|
|
||||||
integrity sha512-+0mFUc5YSnOPI+4K/7nr6XDGduITaUasPVurzrH03sk6yW+udKxP/qjEwEekRwIpnvcCKYnjiZ9HJenJv9ljZg==
|
|
||||||
dependencies:
|
|
||||||
b4a "^1.1.0"
|
|
||||||
hmac-blake2b "^2.0.0"
|
|
||||||
nanoassert "^2.0.0"
|
|
||||||
sodium-universal "^3.0.4"
|
|
||||||
|
|
||||||
nopt@~1.0.10:
|
nopt@~1.0.10:
|
||||||
version "1.0.10"
|
version "1.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
|
resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
|
||||||
@ -5666,11 +5514,6 @@ queue-microtask@^1.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||||
|
|
||||||
queue-tick@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.0.tgz#011104793a3309ae86bfeddd54e251dc94a36725"
|
|
||||||
integrity sha512-ULWhjjE8BmiICGn3G8+1L9wFpERNxkf8ysxkAer4+TFdRefDaXOCV5m92aMB9FtBVmn/8sETXLXY6BfW7hyaWQ==
|
|
||||||
|
|
||||||
railroad-diagrams@^1.0.0:
|
railroad-diagrams@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
|
resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
|
||||||
@ -5743,13 +5586,6 @@ readdirp@~3.6.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
picomatch "^2.2.1"
|
picomatch "^2.2.1"
|
||||||
|
|
||||||
record-cache@^1.1.1:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/record-cache/-/record-cache-1.2.0.tgz#e601bc4f164d58330cc00055e27aa4682291c882"
|
|
||||||
integrity sha512-kyy3HWCez2WrotaL3O4fTn0rsIdfRKOdQQcEJ9KpvmKmbffKVvwsloX063EgRUlpJIXHiDQFhJcTbZequ2uTZw==
|
|
||||||
dependencies:
|
|
||||||
b4a "^1.3.1"
|
|
||||||
|
|
||||||
reflect-metadata@^0.1.13:
|
reflect-metadata@^0.1.13:
|
||||||
version "0.1.13"
|
version "0.1.13"
|
||||||
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
|
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
|
||||||
@ -5809,7 +5645,7 @@ resolve@^1.10.0, resolve@^1.10.1, resolve@^1.20.0:
|
|||||||
is-core-module "^2.2.0"
|
is-core-module "^2.2.0"
|
||||||
path-parse "^1.0.6"
|
path-parse "^1.0.6"
|
||||||
|
|
||||||
resolve@^1.15.1, resolve@^1.17.0:
|
resolve@^1.15.1:
|
||||||
version "1.22.1"
|
version "1.22.1"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
|
||||||
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
|
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
|
||||||
@ -5886,11 +5722,6 @@ safe-identifier@^0.4.1:
|
|||||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
safety-catch@^1.0.1:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/safety-catch/-/safety-catch-1.0.2.tgz#d64cbd57fd601da91c356b6ab8902f3e449a7a4b"
|
|
||||||
integrity sha512-C1UYVZ4dtbBxEtvOcpjBaaD27nP8MlvyAQEp2fOTOEe6pfUpk1cDUxij6BR1jZup6rSyUTaBBplK7LanskrULA==
|
|
||||||
|
|
||||||
saxes@^5.0.1:
|
saxes@^5.0.1:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
|
resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
|
||||||
@ -5981,38 +5812,6 @@ sha.js@^2.4.11:
|
|||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
sha256-universal@^1.1.0:
|
|
||||||
version "1.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/sha256-universal/-/sha256-universal-1.2.1.tgz#051d92decce280cd6137d42d496eac88da942c0e"
|
|
||||||
integrity sha512-ghn3muhdn1ailCQqqceNxRgkOeZSVfSE13RQWEg6njB+itsFzGVSJv+O//2hvNXZuxVIRyNzrgsZ37SPDdGJJw==
|
|
||||||
dependencies:
|
|
||||||
b4a "^1.0.1"
|
|
||||||
sha256-wasm "^2.2.1"
|
|
||||||
|
|
||||||
sha256-wasm@^2.2.1:
|
|
||||||
version "2.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/sha256-wasm/-/sha256-wasm-2.2.2.tgz#4940b6c9ba28f3f08b700efce587ef36d4d516d4"
|
|
||||||
integrity sha512-qKSGARvao+JQlFiA+sjJZhJ/61gmW/3aNLblB2rsgIxDlDxsJPHo8a1seXj12oKtuHVgJSJJ7QEGBUYQN741lQ==
|
|
||||||
dependencies:
|
|
||||||
b4a "^1.0.1"
|
|
||||||
nanoassert "^2.0.0"
|
|
||||||
|
|
||||||
sha512-universal@^1.1.0:
|
|
||||||
version "1.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/sha512-universal/-/sha512-universal-1.2.1.tgz#829505a7586530515cc1a10b78815c99722c4df0"
|
|
||||||
integrity sha512-kehYuigMoRkIngCv7rhgruLJNNHDnitGTBdkcYbCbooL8Cidj/bS78MDxByIjcc69M915WxcQTgZetZ1JbeQTQ==
|
|
||||||
dependencies:
|
|
||||||
b4a "^1.0.1"
|
|
||||||
sha512-wasm "^2.3.1"
|
|
||||||
|
|
||||||
sha512-wasm@^2.3.1:
|
|
||||||
version "2.3.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/sha512-wasm/-/sha512-wasm-2.3.4.tgz#b86b37112ff6d1fc3740f2484a6855f17a6e1300"
|
|
||||||
integrity sha512-akWoxJPGCB3aZCrZ+fm6VIFhJ/p8idBv7AWGFng/CZIrQo51oQNsvDbTSRXWAzIiZJvpy16oIDiCCPqTe21sKg==
|
|
||||||
dependencies:
|
|
||||||
b4a "^1.0.1"
|
|
||||||
nanoassert "^2.0.0"
|
|
||||||
|
|
||||||
shebang-command@^1.2.0:
|
shebang-command@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
||||||
@ -6056,13 +5855,6 @@ signal-exit@^3.0.2, signal-exit@^3.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f"
|
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f"
|
||||||
integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==
|
integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==
|
||||||
|
|
||||||
siphash24@^1.0.1:
|
|
||||||
version "1.3.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/siphash24/-/siphash24-1.3.1.tgz#7f87fd2c5db88d8d46335a68f780f281641c8b22"
|
|
||||||
integrity sha512-moemC3ZKiTzH29nbFo3Iw8fbemWWod4vNs/WgKbQ54oEs6mE6XVlguxvinYjB+UmaE0PThgyED9fUkWvirT8hA==
|
|
||||||
dependencies:
|
|
||||||
nanoassert "^2.0.0"
|
|
||||||
|
|
||||||
sisteransi@^1.0.5:
|
sisteransi@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
|
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
|
||||||
@ -6087,50 +5879,13 @@ slick@^1.12.2:
|
|||||||
resolved "https://registry.yarnpkg.com/slick/-/slick-1.12.2.tgz#bd048ddb74de7d1ca6915faa4a57570b3550c2d7"
|
resolved "https://registry.yarnpkg.com/slick/-/slick-1.12.2.tgz#bd048ddb74de7d1ca6915faa4a57570b3550c2d7"
|
||||||
integrity sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==
|
integrity sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==
|
||||||
|
|
||||||
sodium-javascript@~0.8.0:
|
sodium-native@^3.3.0:
|
||||||
version "0.8.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/sodium-javascript/-/sodium-javascript-0.8.0.tgz#0a94d7bb58ab17be82255f3949259af59778fdbc"
|
|
||||||
integrity sha512-rEBzR5mPxPES+UjyMDvKPIXy9ImF17KOJ32nJNi9uIquWpS/nfj+h6m05J5yLJaGXjgM72LmQoUbWZVxh/rmGg==
|
|
||||||
dependencies:
|
|
||||||
blake2b "^2.1.1"
|
|
||||||
chacha20-universal "^1.0.4"
|
|
||||||
nanoassert "^2.0.0"
|
|
||||||
sha256-universal "^1.1.0"
|
|
||||||
sha512-universal "^1.1.0"
|
|
||||||
siphash24 "^1.0.1"
|
|
||||||
xsalsa20 "^1.0.0"
|
|
||||||
|
|
||||||
sodium-native@^3.1.1, sodium-native@^3.2.0, sodium-native@^3.3.0:
|
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/sodium-native/-/sodium-native-3.3.0.tgz#50ee52ac843315866cce3d0c08ab03eb78f22361"
|
resolved "https://registry.yarnpkg.com/sodium-native/-/sodium-native-3.3.0.tgz#50ee52ac843315866cce3d0c08ab03eb78f22361"
|
||||||
integrity sha512-rg6lCDM/qa3p07YGqaVD+ciAbUqm6SoO4xmlcfkbU5r1zIGrguXztLiEtaLYTV5U6k8KSIUFmnU3yQUSKmf6DA==
|
integrity sha512-rg6lCDM/qa3p07YGqaVD+ciAbUqm6SoO4xmlcfkbU5r1zIGrguXztLiEtaLYTV5U6k8KSIUFmnU3yQUSKmf6DA==
|
||||||
dependencies:
|
dependencies:
|
||||||
node-gyp-build "^4.3.0"
|
node-gyp-build "^4.3.0"
|
||||||
|
|
||||||
sodium-secretstream@^1.0.0:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/sodium-secretstream/-/sodium-secretstream-1.0.2.tgz#ae6fec16555f1a1d9fd2460b41256736d5044e13"
|
|
||||||
integrity sha512-AsWztbBHhHid+w5g28ftXA0mTrS52Dup7FYI0GR7ri1TQTlVsw0z//FNlhIqWsgtBctO/DxQosacbElCpmdcZw==
|
|
||||||
dependencies:
|
|
||||||
b4a "^1.1.1"
|
|
||||||
sodium-universal "^3.0.4"
|
|
||||||
|
|
||||||
sodium-universal@^3.0.0, sodium-universal@^3.0.4:
|
|
||||||
version "3.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/sodium-universal/-/sodium-universal-3.1.0.tgz#f2fa0384d16b7cb99b1c8551a39cc05391a3ed41"
|
|
||||||
integrity sha512-N2gxk68Kg2qZLSJ4h0NffEhp4BjgWHCHXVlDi1aG1hA3y+ZeWEmHqnpml8Hy47QzfL1xLy5nwr9LcsWAg2Ep0A==
|
|
||||||
dependencies:
|
|
||||||
blake2b "^2.1.1"
|
|
||||||
chacha20-universal "^1.0.4"
|
|
||||||
nanoassert "^2.0.0"
|
|
||||||
resolve "^1.17.0"
|
|
||||||
sha256-universal "^1.1.0"
|
|
||||||
sha512-universal "^1.1.0"
|
|
||||||
siphash24 "^1.0.1"
|
|
||||||
sodium-javascript "~0.8.0"
|
|
||||||
sodium-native "^3.2.0"
|
|
||||||
xsalsa20 "^1.0.0"
|
|
||||||
|
|
||||||
source-map-support@^0.5.6:
|
source-map-support@^0.5.6:
|
||||||
version "0.5.20"
|
version "0.5.20"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9"
|
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9"
|
||||||
@ -6216,14 +5971,6 @@ streamsearch@0.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
||||||
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
|
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
|
||||||
|
|
||||||
streamx@^2.10.2, streamx@^2.10.3, streamx@^2.12.0, streamx@^2.12.4:
|
|
||||||
version "2.12.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.12.4.tgz#0369848b20b8f79c65320735372df17cafcd9aff"
|
|
||||||
integrity sha512-K3xdIp8YSkvbdI0PrCcP0JkniN8cPCyeKlcZgRFSl1o1xKINCYM93FryvTSOY57x73pz5/AjO5B8b9BYf21wWw==
|
|
||||||
dependencies:
|
|
||||||
fast-fifo "^1.0.0"
|
|
||||||
queue-tick "^1.0.0"
|
|
||||||
|
|
||||||
string-length@^4.0.1:
|
string-length@^4.0.1:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
|
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
|
||||||
@ -6388,16 +6135,6 @@ throat@^6.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375"
|
resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375"
|
||||||
integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==
|
integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==
|
||||||
|
|
||||||
time-ordered-set@^1.0.2:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/time-ordered-set/-/time-ordered-set-1.0.2.tgz#3bd931fc048234147f8c2b8b1ebbebb0a3ecb96f"
|
|
||||||
integrity sha512-vGO99JkxvgX+u+LtOKQEpYf31Kj3i/GNwVstfnh4dyINakMgeZCpew1e3Aj+06hEslhtHEd52g7m5IV+o1K8Mw==
|
|
||||||
|
|
||||||
timeout-refresh@^2.0.0:
|
|
||||||
version "2.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/timeout-refresh/-/timeout-refresh-2.0.1.tgz#f8ec7cf1f9d93b2635b7d4388cb820c5f6c16f98"
|
|
||||||
integrity sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==
|
|
||||||
|
|
||||||
titleize@2:
|
titleize@2:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/titleize/-/titleize-2.1.0.tgz#5530de07c22147a0488887172b5bd94f5b30a48f"
|
resolved "https://registry.yarnpkg.com/titleize/-/titleize-2.1.0.tgz#5530de07c22147a0488887172b5bd94f5b30a48f"
|
||||||
@ -6622,16 +6359,6 @@ uc.micro@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
|
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
|
||||||
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
|
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
|
||||||
|
|
||||||
udx-native@^1.1.0:
|
|
||||||
version "1.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/udx-native/-/udx-native-1.2.1.tgz#a229b8bfab8c9c9eea05c7e0d68e671ab70d562d"
|
|
||||||
integrity sha512-hLoJ3rE1PuqO/A1YENG8oYNuAGltdwXofzavYwXbg2yk/qQgGBDpUQd/qtdENxkawad5cEEdJEdwvchslDl7OA==
|
|
||||||
dependencies:
|
|
||||||
b4a "^1.5.0"
|
|
||||||
napi-macros "^2.0.0"
|
|
||||||
node-gyp-build "^4.4.0"
|
|
||||||
streamx "^2.12.0"
|
|
||||||
|
|
||||||
unbox-primitive@^1.0.1:
|
unbox-primitive@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
|
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
|
||||||
@ -6936,11 +6663,6 @@ write-file-atomic@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881"
|
||||||
integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==
|
integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==
|
||||||
|
|
||||||
xache@^1.1.0:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/xache/-/xache-1.1.0.tgz#afc20dec9ff8b2260eea03f5ad9422dc0200c6e9"
|
|
||||||
integrity sha512-RQGZDHLy/uCvnIrAvaorZH/e6Dfrtxj16iVlGjkj4KD2/G/dNXNqhk5IdSucv5nSSnDK00y8Y/2csyRdHveJ+Q==
|
|
||||||
|
|
||||||
xdg-basedir@^4.0.0:
|
xdg-basedir@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
|
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
|
||||||
@ -6956,11 +6678,6 @@ xmlchars@^2.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||||
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
||||||
|
|
||||||
xsalsa20@^1.0.0:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/xsalsa20/-/xsalsa20-1.2.0.tgz#e5a05cb26f8cef723f94a559102ed50c1b44c25c"
|
|
||||||
integrity sha512-FIr/DEeoHfj7ftfylnoFt3rAIRoWXpx2AoDfrT2qD2wtp7Dp+COajvs/Icb7uHqRW9m60f5iXZwdsJJO3kvb7w==
|
|
||||||
|
|
||||||
xss@^1.0.8:
|
xss@^1.0.8:
|
||||||
version "1.0.10"
|
version "1.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.10.tgz#5cd63a9b147a755a14cb0455c7db8866120eb4d2"
|
resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.10.tgz#5cd63a9b147a755a14cb0455c7db8866120eb4d2"
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
CONFIG_VERSION=v1.2022-03-18
|
|
||||||
|
|
||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
DB_PORT=3306
|
DB_PORT=3306
|
||||||
DB_USER=root
|
DB_USER=root
|
||||||
|
|||||||
@ -16,17 +16,17 @@ export class EventProtocol extends BaseEntity {
|
|||||||
@Column({ name: 'user_id', unsigned: true, nullable: false })
|
@Column({ name: 'user_id', unsigned: true, nullable: false })
|
||||||
userId: number
|
userId: number
|
||||||
|
|
||||||
@Column({ name: 'x_user_id', unsigned: true, nullable: true })
|
@Column({ name: 'x_user_id', type: 'int', unsigned: true, nullable: true })
|
||||||
xUserId: number
|
xUserId: number | null
|
||||||
|
|
||||||
@Column({ name: 'x_community_id', unsigned: true, nullable: true })
|
@Column({ name: 'x_community_id', type: 'int', unsigned: true, nullable: true })
|
||||||
xCommunityId: number
|
xCommunityId: number | null
|
||||||
|
|
||||||
@Column({ name: 'transaction_id', unsigned: true, nullable: true })
|
@Column({ name: 'transaction_id', type: 'int', unsigned: true, nullable: true })
|
||||||
transactionId: number
|
transactionId: number | null
|
||||||
|
|
||||||
@Column({ name: 'contribution_id', unsigned: true, nullable: true })
|
@Column({ name: 'contribution_id', type: 'int', unsigned: true, nullable: true })
|
||||||
contributionId: number
|
contributionId: number | null
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: 'decimal',
|
type: 'decimal',
|
||||||
@ -35,8 +35,8 @@ export class EventProtocol extends BaseEntity {
|
|||||||
nullable: true,
|
nullable: true,
|
||||||
transformer: DecimalTransformer,
|
transformer: DecimalTransformer,
|
||||||
})
|
})
|
||||||
amount: Decimal
|
amount: Decimal | null
|
||||||
|
|
||||||
@Column({ name: 'message_id', unsigned: true, nullable: true })
|
@Column({ name: 'message_id', type: 'int', unsigned: true, nullable: true })
|
||||||
messageId: number
|
messageId: number | null
|
||||||
}
|
}
|
||||||
|
|||||||
51
database/entity/0060-update_communities_table/Community.ts
Normal file
51
database/entity/0060-update_communities_table/Community.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm'
|
||||||
|
|
||||||
|
@Entity('communities')
|
||||||
|
export class Community extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({ name: 'foreign', type: 'bool', nullable: false, default: true })
|
||||||
|
foreign: boolean
|
||||||
|
|
||||||
|
@Column({ name: 'public_key', type: 'binary', length: 64, default: null, nullable: true })
|
||||||
|
publicKey: Buffer
|
||||||
|
|
||||||
|
@Column({ name: 'api_version', length: 10, nullable: false })
|
||||||
|
apiVersion: string
|
||||||
|
|
||||||
|
@Column({ name: 'end_point', length: 255, nullable: false })
|
||||||
|
endPoint: string
|
||||||
|
|
||||||
|
@Column({ name: 'last_announced_at', type: 'datetime', nullable: true })
|
||||||
|
lastAnnouncedAt: Date
|
||||||
|
|
||||||
|
@Column({ name: 'verified_at', type: 'datetime', nullable: true })
|
||||||
|
verifiedAt: Date
|
||||||
|
|
||||||
|
@Column({ name: 'last_error_at', type: 'datetime', nullable: true })
|
||||||
|
lastErrorAt: Date
|
||||||
|
|
||||||
|
@CreateDateColumn({
|
||||||
|
name: 'created_at',
|
||||||
|
type: 'datetime',
|
||||||
|
default: () => 'CURRENT_TIMESTAMP(3)',
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
createdAt: Date
|
||||||
|
|
||||||
|
@UpdateDateColumn({
|
||||||
|
name: 'updated_at',
|
||||||
|
type: 'datetime',
|
||||||
|
onUpdate: 'CURRENT_TIMESTAMP(3)',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
updatedAt: Date | null
|
||||||
|
}
|
||||||
@ -1 +1 @@
|
|||||||
export { Community } from './0058-add_communities_table/Community'
|
export { Community } from './0060-update_communities_table/Community'
|
||||||
|
|||||||
32
database/migrations/0060-update_communities_table.ts
Normal file
32
database/migrations/0060-update_communities_table.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/* MIGRATION TO CREATE THE FEDERATION COMMUNITY TABLES
|
||||||
|
*
|
||||||
|
* This migration creates the `community` and 'communityfederation' tables in the `apollo` database (`gradido_community`).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `communities` MODIFY COLUMN `last_announced_at` datetime(3) AFTER `end_point`;',
|
||||||
|
)
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `communities` ADD COLUMN `foreign` tinyint(4) NOT NULL DEFAULT 1 AFTER `id`;',
|
||||||
|
)
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `communities` ADD COLUMN `verified_at` datetime(3) AFTER `last_announced_at`;',
|
||||||
|
)
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `communities` ADD COLUMN `last_error_at` datetime(3) AFTER `verified_at`;',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
// write downgrade logic as parameter of queryFn
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `communities` MODIFY COLUMN `last_announced_at` datetime(3) NOT NULL AFTER `end_point`;',
|
||||||
|
)
|
||||||
|
await queryFn('ALTER TABLE `communities` DROP COLUMN `foreign`;')
|
||||||
|
await queryFn('ALTER TABLE `communities` DROP COLUMN `verified_at`;')
|
||||||
|
await queryFn('ALTER TABLE `communities` DROP COLUMN `last_error_at`;')
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-database",
|
"name": "gradido-database",
|
||||||
"version": "1.17.1",
|
"version": "1.18.2",
|
||||||
"description": "Gradido Database Tool to execute database migrations",
|
"description": "Gradido Database Tool to execute database migrations",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/database",
|
"repository": "https://github.com/gradido/gradido/database",
|
||||||
|
|||||||
@ -27,7 +27,7 @@ COMMUNITY_DESCRIPTION="Gradido Development Stage1 Test Community"
|
|||||||
COMMUNITY_SUPPORT_MAIL=support@supportmail.com
|
COMMUNITY_SUPPORT_MAIL=support@supportmail.com
|
||||||
|
|
||||||
# backend
|
# backend
|
||||||
BACKEND_CONFIG_VERSION=v14.2022-12-22
|
BACKEND_CONFIG_VERSION=v15.2023-02-07
|
||||||
|
|
||||||
JWT_EXPIRES_IN=10m
|
JWT_EXPIRES_IN=10m
|
||||||
GDT_API_URL=https://gdt.gradido.net
|
GDT_API_URL=https://gdt.gradido.net
|
||||||
@ -56,9 +56,6 @@ EMAIL_CODE_REQUEST_TIME=10
|
|||||||
|
|
||||||
WEBHOOK_ELOPAGE_SECRET=secret
|
WEBHOOK_ELOPAGE_SECRET=secret
|
||||||
|
|
||||||
# EventProtocol
|
|
||||||
EVENT_PROTOCOL_DISABLED=false
|
|
||||||
|
|
||||||
# Federation
|
# Federation
|
||||||
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
|
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
|
||||||
# on an hash created from this topic
|
# on an hash created from this topic
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
CONFIG_VERSION=v1.2023-01-01
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
DB_PORT=3306
|
DB_PORT=3306
|
||||||
@ -8,9 +6,6 @@ DB_PASSWORD=
|
|||||||
DB_DATABASE=gradido_community
|
DB_DATABASE=gradido_community
|
||||||
TYPEORM_LOGGING_RELATIVE_PATH=typeorm.dht-node.log
|
TYPEORM_LOGGING_RELATIVE_PATH=typeorm.dht-node.log
|
||||||
|
|
||||||
# EventProtocol
|
|
||||||
EVENT_PROTOCOL_DISABLED=false
|
|
||||||
|
|
||||||
# SET LOG LEVEL AS NEEDED IN YOUR .ENV
|
# SET LOG LEVEL AS NEEDED IN YOUR .ENV
|
||||||
# POSSIBLE VALUES: all | trace | debug | info | warn | error | fatal
|
# POSSIBLE VALUES: all | trace | debug | info | warn | error | fatal
|
||||||
# LOG_LEVEL=info
|
# LOG_LEVEL=info
|
||||||
@ -20,3 +15,5 @@ EVENT_PROTOCOL_DISABLED=false
|
|||||||
# on an hash created from this topic
|
# on an hash created from this topic
|
||||||
FEDERATION_DHT_TOPIC=GRADIDO_HUB
|
FEDERATION_DHT_TOPIC=GRADIDO_HUB
|
||||||
# FEDERATION_DHT_SEED=64ebcb0e3ad547848fef4197c6e2332f
|
# FEDERATION_DHT_SEED=64ebcb0e3ad547848fef4197c6e2332f
|
||||||
|
# FEDERATION_COMMUNITY_URL=http://localhost
|
||||||
|
# FEDERATION_COMMUNITY_API_PORT=5000
|
||||||
|
|||||||
@ -8,10 +8,8 @@ DB_PASSWORD=$DB_PASSWORD
|
|||||||
DB_DATABASE=gradido_community
|
DB_DATABASE=gradido_community
|
||||||
TYPEORM_LOGGING_RELATIVE_PATH=$TYPEORM_LOGGING_RELATIVE_PATH
|
TYPEORM_LOGGING_RELATIVE_PATH=$TYPEORM_LOGGING_RELATIVE_PATH
|
||||||
|
|
||||||
# EventProtocol
|
|
||||||
EVENT_PROTOCOL_DISABLED=$EVENT_PROTOCOL_DISABLED
|
|
||||||
|
|
||||||
# Federation
|
# Federation
|
||||||
FEDERATION_DHT_TOPIC=$FEDERATION_DHT_TOPIC
|
FEDERATION_DHT_TOPIC=$FEDERATION_DHT_TOPIC
|
||||||
FEDERATION_DHT_SEED=$FEDERATION_DHT_SEED
|
FEDERATION_DHT_SEED=$FEDERATION_DHT_SEED
|
||||||
FEDERATION_COMMUNITY_URL=$FEDERATION_COMMUNITY_URL
|
FEDERATION_COMMUNITY_URL=$FEDERATION_COMMUNITY_URL
|
||||||
|
FEDERATION_COMMUNITY_API_PORT=$FEDERATION_COMMUNITY_API_PORT
|
||||||
|
|||||||
@ -3,13 +3,13 @@ import dotenv from 'dotenv'
|
|||||||
dotenv.config()
|
dotenv.config()
|
||||||
|
|
||||||
const constants = {
|
const constants = {
|
||||||
DB_VERSION: '0059-add_hide_amount_to_users',
|
DB_VERSION: '0060-update_communities_table',
|
||||||
LOG4JS_CONFIG: 'log4js-config.json',
|
LOG4JS_CONFIG: 'log4js-config.json',
|
||||||
// default log level on production should be info
|
// default log level on production should be info
|
||||||
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
||||||
CONFIG_VERSION: {
|
CONFIG_VERSION: {
|
||||||
DEFAULT: 'DEFAULT',
|
DEFAULT: 'DEFAULT',
|
||||||
EXPECTED: 'v1.2023-01-01',
|
EXPECTED: 'v2.2023-02-07',
|
||||||
CURRENT: '',
|
CURRENT: '',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -28,15 +28,11 @@ const database = {
|
|||||||
process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.dht-node.log',
|
process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.dht-node.log',
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventProtocol = {
|
|
||||||
// global switch to enable writing of EventProtocol-Entries
|
|
||||||
EVENT_PROTOCOL_DISABLED: process.env.EVENT_PROTOCOL_DISABLED === 'true' || false,
|
|
||||||
}
|
|
||||||
|
|
||||||
const federation = {
|
const federation = {
|
||||||
FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || 'GRADIDO_HUB',
|
FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || 'GRADIDO_HUB',
|
||||||
FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
|
FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
|
||||||
FEDERATION_COMMUNITY_URL: process.env.FEDERATION_COMMUNITY_URL || null,
|
FEDERATION_COMMUNITY_URL: process.env.FEDERATION_COMMUNITY_URL || 'http://localhost',
|
||||||
|
FEDERATION_COMMUNITY_API_PORT: process.env.FEDERATION_COMMUNITY_API_PORT || '5000',
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check config version
|
// Check config version
|
||||||
@ -55,7 +51,6 @@ const CONFIG = {
|
|||||||
...constants,
|
...constants,
|
||||||
...server,
|
...server,
|
||||||
...database,
|
...database,
|
||||||
...eventProtocol,
|
|
||||||
...federation,
|
...federation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -116,6 +116,7 @@ describe('federation', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
DHT.mockClear()
|
DHT.mockClear()
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
await cleanDB()
|
||||||
await startDHT(TEST_TOPIC)
|
await startDHT(TEST_TOPIC)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -234,18 +235,18 @@ describe('federation', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
jsonArray = [
|
jsonArray = [
|
||||||
{ api: 'v1_0', url: 'too much versions at the same time test' },
|
{ api: '1_0', url: 'too much versions at the same time test' },
|
||||||
{ api: 'v1_0', url: 'url2' },
|
{ api: '1_0', url: 'url2' },
|
||||||
{ api: 'v1_0', url: 'url3' },
|
{ api: '1_0', url: 'url3' },
|
||||||
{ api: 'v1_0', url: 'url4' },
|
{ api: '1_0', url: 'url4' },
|
||||||
{ api: 'v1_0', url: 'url5' },
|
{ api: '1_0', url: 'url5' },
|
||||||
]
|
]
|
||||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the received data', () => {
|
it('logs the received data', () => {
|
||||||
expect(logger.info).toBeCalledWith(
|
expect(logger.info).toBeCalledWith(
|
||||||
'data: [{"api":"v1_0","url":"too much versions at the same time test"},{"api":"v1_0","url":"url2"},{"api":"v1_0","url":"url3"},{"api":"v1_0","url":"url4"},{"api":"v1_0","url":"url5"}]',
|
'data: [{"api":"1_0","url":"too much versions at the same time test"},{"api":"1_0","url":"url2"},{"api":"1_0","url":"url3"},{"api":"1_0","url":"url4"},{"api":"1_0","url":"url5"}]',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -266,17 +267,17 @@ describe('federation', () => {
|
|||||||
jsonArray = [
|
jsonArray = [
|
||||||
{
|
{
|
||||||
wrong: 'wrong but tolerated property test',
|
wrong: 'wrong but tolerated property test',
|
||||||
api: 'v1_0',
|
api: '1_0',
|
||||||
url: 'url1',
|
url: 'url1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
api: 'v2_0',
|
api: '2_0',
|
||||||
url: 'url2',
|
url: 'url2',
|
||||||
wrong: 'wrong but tolerated property test',
|
wrong: 'wrong but tolerated property test',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||||
result = await DbCommunity.find()
|
result = await DbCommunity.find({ foreign: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@ -287,13 +288,14 @@ describe('federation', () => {
|
|||||||
expect(result).toHaveLength(2)
|
expect(result).toHaveLength(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has an entry for api version v1_0', () => {
|
it('has an entry for api version 1_0', () => {
|
||||||
expect(result).toEqual(
|
expect(result).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
|
foreign: true,
|
||||||
publicKey: expect.any(Buffer),
|
publicKey: expect.any(Buffer),
|
||||||
apiVersion: 'v1_0',
|
apiVersion: '1_0',
|
||||||
endPoint: 'url1',
|
endPoint: 'url1',
|
||||||
lastAnnouncedAt: expect.any(Date),
|
lastAnnouncedAt: expect.any(Date),
|
||||||
createdAt: expect.any(Date),
|
createdAt: expect.any(Date),
|
||||||
@ -303,13 +305,14 @@ describe('federation', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has an entry for api version v2_0', () => {
|
it('has an entry for api version 2_0', () => {
|
||||||
expect(result).toEqual(
|
expect(result).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
|
foreign: true,
|
||||||
publicKey: expect.any(Buffer),
|
publicKey: expect.any(Buffer),
|
||||||
apiVersion: 'v2_0',
|
apiVersion: '2_0',
|
||||||
endPoint: 'url2',
|
endPoint: 'url2',
|
||||||
lastAnnouncedAt: expect.any(Date),
|
lastAnnouncedAt: expect.any(Date),
|
||||||
createdAt: expect.any(Date),
|
createdAt: expect.any(Date),
|
||||||
@ -535,7 +538,7 @@ describe('federation', () => {
|
|||||||
{ api: 'toolong api', url: 'some valid url' },
|
{ api: 'toolong api', url: 'some valid url' },
|
||||||
]
|
]
|
||||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||||
result = await DbCommunity.find()
|
result = await DbCommunity.find({ foreign: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@ -551,6 +554,7 @@ describe('federation', () => {
|
|||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
|
foreign: true,
|
||||||
publicKey: expect.any(Buffer),
|
publicKey: expect.any(Buffer),
|
||||||
apiVersion: 'valid api',
|
apiVersion: 'valid api',
|
||||||
endPoint:
|
endPoint:
|
||||||
@ -588,7 +592,7 @@ describe('federation', () => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||||
result = await DbCommunity.find()
|
result = await DbCommunity.find({ foreign: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@ -604,6 +608,7 @@ describe('federation', () => {
|
|||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
|
foreign: true,
|
||||||
publicKey: expect.any(Buffer),
|
publicKey: expect.any(Buffer),
|
||||||
apiVersion: 'valid api1',
|
apiVersion: 'valid api1',
|
||||||
endPoint:
|
endPoint:
|
||||||
@ -621,6 +626,7 @@ describe('federation', () => {
|
|||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
|
foreign: true,
|
||||||
publicKey: expect.any(Buffer),
|
publicKey: expect.any(Buffer),
|
||||||
apiVersion: 'valid api2',
|
apiVersion: 'valid api2',
|
||||||
endPoint:
|
endPoint:
|
||||||
@ -638,6 +644,7 @@ describe('federation', () => {
|
|||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
|
foreign: true,
|
||||||
publicKey: expect.any(Buffer),
|
publicKey: expect.any(Buffer),
|
||||||
apiVersion: 'valid api3',
|
apiVersion: 'valid api3',
|
||||||
endPoint:
|
endPoint:
|
||||||
@ -655,6 +662,7 @@ describe('federation', () => {
|
|||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
|
foreign: true,
|
||||||
publicKey: expect.any(Buffer),
|
publicKey: expect.any(Buffer),
|
||||||
apiVersion: 'valid api4',
|
apiVersion: 'valid api4',
|
||||||
endPoint:
|
endPoint:
|
||||||
@ -710,17 +718,17 @@ describe('federation', () => {
|
|||||||
Buffer.from(
|
Buffer.from(
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
{
|
{
|
||||||
api: 'v1_0',
|
api: '1_0',
|
||||||
url: 'http://localhost:4000/api/v1_0',
|
url: 'http://localhost:5001/api/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
api: 'v2_0',
|
api: '2_0',
|
||||||
url: 'http://localhost:4000/api/v2_0',
|
url: 'http://localhost:5002/api/',
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
result = await DbCommunity.find()
|
result = await DbCommunity.find({ foreign: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@ -736,9 +744,10 @@ describe('federation', () => {
|
|||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
|
foreign: true,
|
||||||
publicKey: expect.any(Buffer),
|
publicKey: expect.any(Buffer),
|
||||||
apiVersion: 'v1_0',
|
apiVersion: '1_0',
|
||||||
endPoint: 'http://localhost:4000/api/v1_0',
|
endPoint: 'http://localhost:5001/api/',
|
||||||
lastAnnouncedAt: expect.any(Date),
|
lastAnnouncedAt: expect.any(Date),
|
||||||
createdAt: expect.any(Date),
|
createdAt: expect.any(Date),
|
||||||
updatedAt: null,
|
updatedAt: null,
|
||||||
@ -747,14 +756,15 @@ describe('federation', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has an entry for api version v2_0', () => {
|
it('has an entry for api version 2_0', () => {
|
||||||
expect(result).toEqual(
|
expect(result).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
|
foreign: true,
|
||||||
publicKey: expect.any(Buffer),
|
publicKey: expect.any(Buffer),
|
||||||
apiVersion: 'v2_0',
|
apiVersion: '2_0',
|
||||||
endPoint: 'http://localhost:4000/api/v2_0',
|
endPoint: 'http://localhost:5002/api/',
|
||||||
lastAnnouncedAt: expect.any(Date),
|
lastAnnouncedAt: expect.any(Date),
|
||||||
createdAt: expect.any(Date),
|
createdAt: expect.any(Date),
|
||||||
updatedAt: null,
|
updatedAt: null,
|
||||||
@ -775,16 +785,16 @@ describe('federation', () => {
|
|||||||
Buffer.from(
|
Buffer.from(
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
{
|
{
|
||||||
api: 'v1_0',
|
api: '1_0',
|
||||||
url: 'http://localhost:4000/api/v1_0',
|
url: 'http://localhost:5001/api/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
api: 'v1_1',
|
api: '1_1',
|
||||||
url: 'http://localhost:4000/api/v1_1',
|
url: 'http://localhost:5002/api/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
api: 'v2_0',
|
api: '2_0',
|
||||||
url: 'http://localhost:4000/api/v2_0',
|
url: 'http://localhost:5003/api/',
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -15,9 +15,9 @@ const ERRORTIME = 240000
|
|||||||
const ANNOUNCETIME = 30000
|
const ANNOUNCETIME = 30000
|
||||||
|
|
||||||
enum ApiVersionType {
|
enum ApiVersionType {
|
||||||
V1_0 = 'v1_0',
|
V1_0 = '1_0',
|
||||||
V1_1 = 'v1_1',
|
V1_1 = '1_1',
|
||||||
V2_0 = 'v2_0',
|
V2_0 = '2_0',
|
||||||
}
|
}
|
||||||
type CommunityApi = {
|
type CommunityApi = {
|
||||||
api: string
|
api: string
|
||||||
@ -31,13 +31,7 @@ export const startDHT = async (topic: string): Promise<void> => {
|
|||||||
logger.info(`keyPairDHT: publicKey=${keyPair.publicKey.toString('hex')}`)
|
logger.info(`keyPairDHT: publicKey=${keyPair.publicKey.toString('hex')}`)
|
||||||
logger.debug(`keyPairDHT: secretKey=${keyPair.secretKey.toString('hex')}`)
|
logger.debug(`keyPairDHT: secretKey=${keyPair.secretKey.toString('hex')}`)
|
||||||
|
|
||||||
const ownApiVersions = Object.values(ApiVersionType).map(function (apiEnum) {
|
const ownApiVersions = writeHomeCommunityEnries(keyPair.publicKey)
|
||||||
const comApi: CommunityApi = {
|
|
||||||
api: apiEnum,
|
|
||||||
url: CONFIG.FEDERATION_COMMUNITY_URL + apiEnum,
|
|
||||||
}
|
|
||||||
return comApi
|
|
||||||
})
|
|
||||||
logger.debug(`ApiList: ${JSON.stringify(ownApiVersions)}`)
|
logger.debug(`ApiList: ${JSON.stringify(ownApiVersions)}`)
|
||||||
|
|
||||||
const node = new DHT({ keyPair })
|
const node = new DHT({ keyPair })
|
||||||
@ -184,3 +178,34 @@ export const startDHT = async (topic: string): Promise<void> => {
|
|||||||
logger.error('DHT unexpected error:', err)
|
logger.error('DHT unexpected error:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function writeHomeCommunityEnries(pubKey: any): Promise<CommunityApi[]> {
|
||||||
|
const homeApiVersions: CommunityApi[] = Object.values(ApiVersionType).map(function (apiEnum) {
|
||||||
|
const port =
|
||||||
|
Number.parseInt(CONFIG.FEDERATION_COMMUNITY_API_PORT) + Number(apiEnum.replace('_', ''))
|
||||||
|
const comApi: CommunityApi = {
|
||||||
|
api: apiEnum,
|
||||||
|
url: CONFIG.FEDERATION_COMMUNITY_URL + ':' + port.toString() + '/api/',
|
||||||
|
}
|
||||||
|
return comApi
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
// first remove privious existing homeCommunity entries
|
||||||
|
DbCommunity.createQueryBuilder().delete().where({ foreign: false }).execute()
|
||||||
|
|
||||||
|
homeApiVersions.forEach(async function (homeApi) {
|
||||||
|
const homeCom = new DbCommunity()
|
||||||
|
homeCom.foreign = false
|
||||||
|
homeCom.apiVersion = homeApi.api
|
||||||
|
homeCom.endPoint = homeApi.url
|
||||||
|
homeCom.publicKey = pubKey.toString('hex')
|
||||||
|
|
||||||
|
// this will NOT update the updatedAt column, to distingue between a normal update and the last announcement
|
||||||
|
await DbCommunity.insert(homeCom)
|
||||||
|
logger.info(`federation home-community inserted successfully: ${JSON.stringify(homeCom)}`)
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Federation: Error writing HomeCommunity-Entries: ${err}`)
|
||||||
|
}
|
||||||
|
return homeApiVersions
|
||||||
|
}
|
||||||
|
|||||||
@ -84,6 +84,29 @@ services:
|
|||||||
- ./dht-node:/app
|
- ./dht-node:/app
|
||||||
- ./database:/database
|
- ./database:/database
|
||||||
|
|
||||||
|
########################################################
|
||||||
|
# FEDERATION ###########################################
|
||||||
|
########################################################
|
||||||
|
federation:
|
||||||
|
# name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there
|
||||||
|
image: gradido/federation:local-development
|
||||||
|
build:
|
||||||
|
target: development
|
||||||
|
networks:
|
||||||
|
- external-net
|
||||||
|
- internal-net
|
||||||
|
environment:
|
||||||
|
- NODE_ENV="development"
|
||||||
|
volumes:
|
||||||
|
# This makes sure the docker container has its own node modules.
|
||||||
|
# Therefore it is possible to have a different node version on the host machine
|
||||||
|
- federation_node_modules:/app/node_modules
|
||||||
|
- federation_database_node_modules:/database/node_modules
|
||||||
|
- federation_database_build:/database/build
|
||||||
|
# bind the local folder to the docker to allow live reload
|
||||||
|
- ./federation:/app
|
||||||
|
- ./database:/database
|
||||||
|
|
||||||
########################################################
|
########################################################
|
||||||
# DATABASE ##############################################
|
# DATABASE ##############################################
|
||||||
########################################################
|
########################################################
|
||||||
@ -155,5 +178,8 @@ volumes:
|
|||||||
dht_node_modules:
|
dht_node_modules:
|
||||||
dht_database_node_modules:
|
dht_database_node_modules:
|
||||||
dht_database_build:
|
dht_database_build:
|
||||||
|
federation_node_modules:
|
||||||
|
federation_database_node_modules:
|
||||||
|
federation_database_build:
|
||||||
database_node_modules:
|
database_node_modules:
|
||||||
database_build:
|
database_build:
|
||||||
@ -36,6 +36,21 @@ services:
|
|||||||
- NODE_ENV="test"
|
- NODE_ENV="test"
|
||||||
- DB_HOST=mariadb
|
- DB_HOST=mariadb
|
||||||
|
|
||||||
|
########################################################
|
||||||
|
# FEDERATION ###########################################
|
||||||
|
########################################################
|
||||||
|
federation:
|
||||||
|
# name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there
|
||||||
|
image: gradido/federation:test
|
||||||
|
build:
|
||||||
|
target: test
|
||||||
|
networks:
|
||||||
|
- external-net
|
||||||
|
- internal-net
|
||||||
|
environment:
|
||||||
|
- NODE_ENV="test"
|
||||||
|
- DB_HOST=mariadb
|
||||||
|
|
||||||
########################################################
|
########################################################
|
||||||
# DATABASE #############################################
|
# DATABASE #############################################
|
||||||
########################################################
|
########################################################
|
||||||
|
|||||||
@ -147,6 +147,42 @@ services:
|
|||||||
# <host_machine_directory>:<container_directory> – mirror bidirectional path in local context with path in Docker container
|
# <host_machine_directory>:<container_directory> – mirror bidirectional path in local context with path in Docker container
|
||||||
- ./logs/dht-node:/logs/dht-node
|
- ./logs/dht-node:/logs/dht-node
|
||||||
|
|
||||||
|
########################################################
|
||||||
|
# FEDERATION ###########################################
|
||||||
|
########################################################
|
||||||
|
federation:
|
||||||
|
# name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there
|
||||||
|
image: gradido/federation:local-production
|
||||||
|
build:
|
||||||
|
# since we have to include the entities from ./database we cannot define the context as ./federation
|
||||||
|
# this might blow build image size to the moon ?!
|
||||||
|
context: ./
|
||||||
|
dockerfile: ./federation/Dockerfile
|
||||||
|
target: production
|
||||||
|
networks:
|
||||||
|
- internal-net
|
||||||
|
- external-net
|
||||||
|
ports:
|
||||||
|
- 5010:5010
|
||||||
|
depends_on:
|
||||||
|
- mariadb
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
# Envs used in Dockerfile
|
||||||
|
# - DOCKER_WORKDIR="/app"
|
||||||
|
- PORT=5010
|
||||||
|
- BUILD_DATE
|
||||||
|
- BUILD_VERSION
|
||||||
|
- BUILD_COMMIT
|
||||||
|
- NODE_ENV="production"
|
||||||
|
- DB_HOST=mariadb
|
||||||
|
# Application only envs
|
||||||
|
#env_file:
|
||||||
|
# - ./frontend/.env
|
||||||
|
volumes:
|
||||||
|
# <host_machine_directory>:<container_directory> – mirror bidirectional path in local context with path in Docker container
|
||||||
|
- ./logs/federation:/logs/federation
|
||||||
|
|
||||||
########################################################
|
########################################################
|
||||||
# DATABASE #############################################
|
# DATABASE #############################################
|
||||||
########################################################
|
########################################################
|
||||||
|
|||||||
26
e2e-tests/.eslintrc.js
Normal file
26
e2e-tests/.eslintrc.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
cypress: true,
|
||||||
|
},
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['cypress', 'prettier', '@typescript-eslint' /*, 'jest' */],
|
||||||
|
extends: [
|
||||||
|
'standard',
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
],
|
||||||
|
// add your custom rules here
|
||||||
|
rules: {
|
||||||
|
'no-console': ['error'],
|
||||||
|
'no-debugger': 'error',
|
||||||
|
'prettier/prettier': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
htmlWhitespaceSensitivity: 'ignore',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
5
e2e-tests/.gitignore
vendored
Normal file
5
e2e-tests/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
node_modules/
|
||||||
|
cypress/screenshots/
|
||||||
|
cypress/videos/
|
||||||
|
cucumber-messages.ndjson
|
||||||
|
|
||||||
9
e2e-tests/.prettierrc.js
Normal file
9
e2e-tests/.prettierrc.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module.exports = {
|
||||||
|
semi: false,
|
||||||
|
printWidth: 100,
|
||||||
|
singleQuote: true,
|
||||||
|
trailingComma: "all",
|
||||||
|
tabWidth: 2,
|
||||||
|
bracketSpacing: true,
|
||||||
|
endOfLine: "auto",
|
||||||
|
};
|
||||||
@ -11,7 +11,7 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
FROM cypress/base:16.14.2-slim
|
FROM cypress/base:16.14.2-slim
|
||||||
|
|
||||||
ARG DOCKER_WORKDIR=/tests/
|
ARG DOCKER_WORKDIR="/tests"
|
||||||
WORKDIR $DOCKER_WORKDIR
|
WORKDIR $DOCKER_WORKDIR
|
||||||
|
|
||||||
# install dependencies
|
# install dependencies
|
||||||
@ -1,7 +1,73 @@
|
|||||||
# Gradido end-to-end tests
|
# Gradido End-to-End Testing with [Cypress](https://www.cypress.io/) (CI-ready via Docker)
|
||||||
|
|
||||||
This is still WIP.
|
A setup to show-case Cypress as an end-to-end testing tool for Gradido running in a Docker container.
|
||||||
|
The tests are organized in feature files written in Gherkin syntax.
|
||||||
|
|
||||||
For automated end-to-end testing one of the frameworks Cypress or Playwright will be utilized.
|
|
||||||
|
|
||||||
For more details on how to run them, see the subfolders' README instructions.
|
## Features under test
|
||||||
|
|
||||||
|
So far these features are initially tested
|
||||||
|
- [User authentication](https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.feature)
|
||||||
|
- [User profile - change password](https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/UserProfile.ChangePassword.feature)
|
||||||
|
- [User registration]((https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/User.Registration.feature)) (WIP)
|
||||||
|
|
||||||
|
|
||||||
|
## Precondition
|
||||||
|
|
||||||
|
Before running the tests, change to the repo's root directory (gradido).
|
||||||
|
|
||||||
|
### Boot up the system under test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
### Seed the database
|
||||||
|
|
||||||
|
The database has to be seeded upfront to every test run.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# change to the backend directory
|
||||||
|
cd /path/to/gradido/gradido/backend
|
||||||
|
|
||||||
|
# install all dependencies
|
||||||
|
yarn
|
||||||
|
|
||||||
|
# seed the database (everytime before running the tests)
|
||||||
|
yarn seed
|
||||||
|
```
|
||||||
|
|
||||||
|
## Execute the test
|
||||||
|
|
||||||
|
This setup will be integrated in the Gradido Github Actions to automatically support the CI/CD process.
|
||||||
|
For now the test setup can only be used locally in two modes.
|
||||||
|
|
||||||
|
### Run Cypress directly from the code
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# change to the tests directory
|
||||||
|
cd /path/to/gradido/e2e-tests/
|
||||||
|
|
||||||
|
# install all dependencies
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
# a) run the tests on command line
|
||||||
|
yarn cypress run
|
||||||
|
|
||||||
|
# b) open the Cypress GUI to run the tests in interactive mode
|
||||||
|
yarn cypress open
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Run Cyprss from a separate Docker container
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# change to the cypress directory
|
||||||
|
cd /path/to/gradido/e2e-tests/
|
||||||
|
|
||||||
|
# build a Docker image from the Dockerfile
|
||||||
|
docker build -t gradido_e2e-tests-cypress .
|
||||||
|
|
||||||
|
# run the Docker image and execute the given tests
|
||||||
|
docker run -it --network=host gradido_e2e-tests-cypress yarn cypress-e2e
|
||||||
|
```
|
||||||
|
|||||||
79
e2e-tests/cypress.config.ts
Normal file
79
e2e-tests/cypress.config.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { defineConfig } from 'cypress'
|
||||||
|
import { addCucumberPreprocessorPlugin } from '@badeball/cypress-cucumber-preprocessor'
|
||||||
|
import browserify from '@badeball/cypress-cucumber-preprocessor/browserify'
|
||||||
|
|
||||||
|
let resetPasswordLink: string
|
||||||
|
|
||||||
|
async function setupNodeEvents(
|
||||||
|
on: Cypress.PluginEvents,
|
||||||
|
config: Cypress.PluginConfigOptions
|
||||||
|
): Promise<Cypress.PluginConfigOptions> {
|
||||||
|
await addCucumberPreprocessorPlugin(on, config)
|
||||||
|
|
||||||
|
on(
|
||||||
|
'file:preprocessor',
|
||||||
|
browserify(config, {
|
||||||
|
typescript: require.resolve('typescript'),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
on('task', {
|
||||||
|
setResetPasswordLink: (val) => {
|
||||||
|
return (resetPasswordLink = val)
|
||||||
|
},
|
||||||
|
getResetPasswordLink: () => {
|
||||||
|
return resetPasswordLink
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
on('after:run', (results) => {
|
||||||
|
if (results) {
|
||||||
|
// results will be undefined in interactive mode
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(results.status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
specPattern: '**/*.feature',
|
||||||
|
excludeSpecPattern: '*.js',
|
||||||
|
experimentalSessionAndOrigin: true,
|
||||||
|
baseUrl: 'http://localhost:3000',
|
||||||
|
chromeWebSecurity: false,
|
||||||
|
defaultCommandTimeout: 10000,
|
||||||
|
supportFile: 'cypress/support/index.ts',
|
||||||
|
viewportHeight: 720,
|
||||||
|
viewportWidth: 1280,
|
||||||
|
video: false,
|
||||||
|
retries: {
|
||||||
|
runMode: 2,
|
||||||
|
openMode: 0,
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
backendURL: 'http://localhost:4000',
|
||||||
|
mailserverURL: 'http://localhost:1080',
|
||||||
|
loginQuery: `query ($email: String!, $password: String!, $publisherId: Int) {
|
||||||
|
login(email: $email, password: $password, publisherId: $publisherId) {
|
||||||
|
email
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
language
|
||||||
|
klickTipp {
|
||||||
|
newsletterState
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
hasElopage
|
||||||
|
publisherId
|
||||||
|
isAdmin
|
||||||
|
creation
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
setupNodeEvents,
|
||||||
|
},
|
||||||
|
})
|
||||||
4
e2e-tests/cypress/.gitignore
vendored
4
e2e-tests/cypress/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
tests/node_modules/
|
|
||||||
tests/cypress/screenshots/
|
|
||||||
tests/cypress/videos/
|
|
||||||
tests/cucumber-messages.ndjson
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
# Gradido End-to-End Testing with [Cypress](https://www.cypress.io/) (CI-ready via Docker)
|
|
||||||
|
|
||||||
A setup to show-case Cypress as an end-to-end testing tool for Gradido running in a Docker container.
|
|
||||||
The tests are organized in feature files written in Gherkin syntax.
|
|
||||||
|
|
||||||
|
|
||||||
## Features under test
|
|
||||||
|
|
||||||
So far these features are initially tested
|
|
||||||
- [User authentication](https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/User.Authentication.feature)
|
|
||||||
- [User profile - change password](https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/UserProfile.ChangePassword.feature)
|
|
||||||
- [User registration]((https://github.com/gradido/gradido/blob/master/e2e-tests/cypress/tests/cypress/e2e/User.Registration.feature)) (WIP)
|
|
||||||
|
|
||||||
|
|
||||||
## Precondition
|
|
||||||
|
|
||||||
Before running the tests, change to the repo's root directory (gradido).
|
|
||||||
|
|
||||||
### Boot up the system under test
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose up
|
|
||||||
```
|
|
||||||
|
|
||||||
### Seed the database
|
|
||||||
|
|
||||||
The database has to be seeded upfront to every test run.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# change to the backend directory
|
|
||||||
cd /path/to/gradido/gradido/backend
|
|
||||||
|
|
||||||
# install all dependencies
|
|
||||||
yarn
|
|
||||||
|
|
||||||
# seed the database (everytime before running the tests)
|
|
||||||
yarn seed
|
|
||||||
```
|
|
||||||
|
|
||||||
## Execute the test
|
|
||||||
|
|
||||||
This setup will be integrated in the Gradido Github Actions to automatically support the CI/CD process.
|
|
||||||
For now the test setup can only be used locally in two modes.
|
|
||||||
|
|
||||||
### Run Cypress directly from the code
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# change to the tests directory
|
|
||||||
cd /path/to/gradido/e2e-tests/cypress/tests
|
|
||||||
|
|
||||||
# install all dependencies
|
|
||||||
yarn install
|
|
||||||
|
|
||||||
# a) run the tests on command line
|
|
||||||
yarn cypress run
|
|
||||||
|
|
||||||
# b) open the Cypress GUI to run the tests in interactive mode
|
|
||||||
yarn cypress open
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Run Cyprss from a separate Docker container
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# change to the cypress directory
|
|
||||||
cd /path/to/gradido/e2e-tests/cypress/
|
|
||||||
|
|
||||||
# build a Docker image from the Dockerfile
|
|
||||||
docker build -t gradido_e2e-tests-cypress .
|
|
||||||
|
|
||||||
# run the Docker image and execute the given tests
|
|
||||||
docker run -it --network=host gradido_e2e-tests-cypress yarn cypress-e2e
|
|
||||||
```
|
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
Feature: User Authentication - reset password
|
||||||
|
As a user
|
||||||
|
I want to reset my password from the sign in page
|
||||||
|
|
||||||
|
# TODO for these pre-conditions utilize seeding or API check, if user exists in test system
|
||||||
|
# Background:
|
||||||
|
# Given the following "users" are in the database:
|
||||||
|
# | email | password | name |
|
||||||
|
# | bibi@bloxberg.de | Aa12345_ | Bibi Bloxberg |
|
||||||
|
|
||||||
|
Scenario: Reset password from signin page successfully
|
||||||
|
Given the user navigates to page "/login"
|
||||||
|
And the user navigates to the forgot password page
|
||||||
|
When the user enters the e-mail address "bibi@bloxberg.de"
|
||||||
|
And the user submits the e-mail form
|
||||||
|
Then the user receives an e-mail containing the password reset link
|
||||||
|
When the user opens the password reset link in the browser
|
||||||
|
And the user enters the password "12345Aa_"
|
||||||
|
And the user repeats the password "12345Aa_"
|
||||||
|
And the user submits the password form
|
||||||
|
And the user clicks the sign in button
|
||||||
|
Then the user submits the credentials "bibi@bloxberg.de" "Aa12345_"
|
||||||
|
And the user cannot login
|
||||||
|
But the user submits the credentials "bibi@bloxberg.de" "12345Aa_"
|
||||||
|
And the user is logged in with username "Bibi Bloxberg"
|
||||||
@ -11,7 +11,7 @@ Feature: User authentication
|
|||||||
# | bibi@bloxberg.de | Aa12345_ | Bibi Bloxberg |
|
# | bibi@bloxberg.de | Aa12345_ | Bibi Bloxberg |
|
||||||
|
|
||||||
Scenario: Log in successfully
|
Scenario: Log in successfully
|
||||||
Given the browser navigates to page "/login"
|
Given the user navigates to page "/login"
|
||||||
When the user submits the credentials "bibi@bloxberg.de" "Aa12345_"
|
When the user submits the credentials "bibi@bloxberg.de" "Aa12345_"
|
||||||
Then the user is logged in with username "Bibi Bloxberg"
|
Then the user is logged in with username "Bibi Bloxberg"
|
||||||
|
|
||||||
@ -4,7 +4,7 @@ Feature: User registration
|
|||||||
|
|
||||||
@skip
|
@skip
|
||||||
Scenario: Register successfully
|
Scenario: Register successfully
|
||||||
Given the browser navigates to page "/register"
|
Given the user navigates to page "/register"
|
||||||
When the user fills name and email "Regina" "Register" "regina@register.com"
|
When the user fills name and email "Regina" "Register" "regina@register.com"
|
||||||
And the user agrees to the privacy policy
|
And the user agrees to the privacy policy
|
||||||
And the user submits the registration form
|
And the user submits the registration form
|
||||||
@ -12,7 +12,7 @@ Feature: User profile - change password
|
|||||||
Given the user is logged in as "bibi@bloxberg.de" "Aa12345_"
|
Given the user is logged in as "bibi@bloxberg.de" "Aa12345_"
|
||||||
|
|
||||||
Scenario: Change password successfully
|
Scenario: Change password successfully
|
||||||
Given the browser navigates to page "/profile"
|
Given the user navigates to page "/profile"
|
||||||
And the user opens the change password menu
|
And the user opens the change password menu
|
||||||
When the user fills the password form with:
|
When the user fills the password form with:
|
||||||
| Old password | Aa12345_ |
|
| Old password | Aa12345_ |
|
||||||
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