Merge branch 'master' into eslint-disable-tests-typesafer

This commit is contained in:
Ulf Gebhardt 2023-05-03 11:24:56 +02:00 committed by GitHub
commit 239f42cfa4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
174 changed files with 1429 additions and 787 deletions

View File

@ -30,10 +30,13 @@
admin: &admin
- 'admin/**/*'
backend: &backend
- 'backend/**/*'
dht_node: &dht_node
- 'dht-node/**/*'
docker: &docker
docker-compose: &docker-compose
- 'docker-compose.*'
federation: &federation
@ -42,5 +45,8 @@ federation: &federation
frontend: &frontend
- 'frontend/**/*'
mariadb: &mariadb
- 'mariadb/**/*'
nginx: &nginx
- 'nginx/**/*'

View File

@ -1,195 +0,0 @@
name: gradido test CI
on: push
jobs:
##############################################################################
# JOB: DOCKER BUILD TEST BACKEND #############################################
##############################################################################
build_test_backend:
name: Docker Build Test - Backend
runs-on: ubuntu-latest
#needs: [nothing]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# BACKEND ################################################################
##########################################################################
- name: Backend | Build `test` image
run: |
docker build -f ./backend/Dockerfile --target test -t "gradido/backend:test" .
docker save "gradido/backend:test" > /tmp/backend.tar
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: docker-backend-test
path: /tmp/backend.tar
##############################################################################
# JOB: DOCKER BUILD TEST DATABASE UP #########################################
##############################################################################
build_test_database_up:
name: Docker Build Test - Database up
runs-on: ubuntu-latest
#needs: [nothing]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# DATABASE UP ############################################################
##########################################################################
- name: Database | Build `test_up` image
run: |
docker build --target test_up -t "gradido/database:test_up" database/
docker save "gradido/database:test_up" > /tmp/database_up.tar
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: docker-database-test_up
path: /tmp/database_up.tar
##############################################################################
# JOB: DOCKER BUILD TEST MARIADB #############################################
##############################################################################
build_test_mariadb:
name: Docker Build Test - MariaDB
runs-on: ubuntu-latest
#needs: [nothing]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# BUILD MARIADB DOCKER IMAGE #############################################
##########################################################################
- name: mariadb | Build `test` image
run: |
docker build --target mariadb_server -t "gradido/mariadb:test" -f ./mariadb/Dockerfile ./
docker save "gradido/mariadb:test" > /tmp/mariadb.tar
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: docker-mariadb-test
path: /tmp/mariadb.tar
##############################################################################
# JOB: LINT BACKEND ##########################################################
##############################################################################
lint_backend:
name: Lint - Backend
runs-on: ubuntu-latest
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# LINT BACKEND ###########################################################
##########################################################################
- name: backend | Lint
run: cd database && yarn && 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 ######################################################
##############################################################################
lint_database_up:
name: Lint - Database Up
runs-on: ubuntu-latest
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# LINT DATABASE ##########################################################
##########################################################################
- name: Database | Lint
run: cd database && yarn && yarn run lint
##############################################################################
# JOB: UNIT TEST BACKEND ####################################################
##############################################################################
unit_test_backend:
name: Unit tests - Backend
runs-on: ubuntu-latest
needs: [build_test_mariadb]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# DOWNLOAD DOCKER IMAGES #################################################
##########################################################################
- name: Download Docker Image (Mariadb)
uses: actions/download-artifact@v3
with:
name: docker-mariadb-test
path: /tmp
- name: Load Docker Image
run: docker load < /tmp/mariadb.tar
##########################################################################
# UNIT TESTS BACKEND #####################################################
##########################################################################
- name: backend | 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: backend | docker-compose database
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database
- name: backend Unit tests | test
run: cd database && yarn && yarn build && cd ../backend && yarn && yarn test
##########################################################################
# DATABASE MIGRATION TEST UP + RESET #####################################
##########################################################################
database_migration_test:
name: Database Migration Test - Up + Reset
runs-on: ubuntu-latest
#needs: [nothing]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# DOCKER COMPOSE DATABASE UP + RESET #####################################
##########################################################################
- name: database | docker-compose
run: docker-compose -f docker-compose.yml up --detach mariadb
- name: database | up
run: docker-compose -f docker-compose.yml run -T database yarn up
- name: database | reset
run: docker-compose -f docker-compose.yml run -T database yarn reset

81
.github/workflows/test_backend.yml vendored Normal file
View File

@ -0,0 +1,81 @@
name: Gradido Backend Test CI
on: push
jobs:
files-changed:
name: Detect File Changes - Backend
runs-on: ubuntu-latest
outputs:
backend: ${{ steps.changes.outputs.backend }}
database: ${{ steps.changes.outputs.database }}
docker-compose: ${{ steps.changes.outputs.docker-compose }}
mariadb: ${{ steps.changes.outputs.mariadb }}
steps:
- uses: actions/checkout@v3.3.0
- name: Check for frontend file changes
uses: dorny/paths-filter@v2.11.1
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
list-files: shell
build_test:
if: needs.files-changed.outputs.backend == 'true'
name: Docker Build Test - Backend
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Backend | Build 'test' image
run: docker build -f ./backend/Dockerfile --target test -t "gradido/backend:test" .
unit_test:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.database == 'true' || needs.files-changed.outputs.docker-compose == 'true' || needs.files-changed.outputs.mariadb == 'true'
name: Unit tests - Backend
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Backend | 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: Backend | docker-compose database
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database
- name: Backend | Unit tests
run: cd database && yarn && yarn build && cd ../backend && yarn && yarn test
lint:
if: needs.files-changed.outputs.backend == 'true'
name: Lint - Backend
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Backend | Lint
run: cd database && yarn && cd ../backend && yarn && yarn run lint
locales:
if: needs.files-changed.outputs.backend == 'true'
name: Locales - Backend
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Backend | Locales
run: cd backend && yarn && yarn locales

64
.github/workflows/test_database.yml vendored Normal file
View File

@ -0,0 +1,64 @@
name: Gradido Database Test CI
on: push
jobs:
files-changed:
name: Detect File Changes - Database
runs-on: ubuntu-latest
outputs:
database: ${{ steps.changes.outputs.database }}
docker-compose: ${{ steps.changes.outputs.docker-compose }}
mariadb: ${{ steps.changes.outputs.mariadb }}
steps:
- uses: actions/checkout@v3.3.0
- name: Check for frontend file changes
uses: dorny/paths-filter@v2.11.1
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
list-files: shell
build:
if: needs.files-changed.outputs.database == 'true'
name: Docker Build Test - Database up
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Database | Build 'test_up' image
run: docker build --target test_up -t "gradido/database:test_up" database/
database_migration_test:
if: needs.files-changed.outputs.database == 'true' || needs.files-changed.outputs.docker-compose == 'true' || needs.files-changed.outputs.mariadb == 'true'
name: Database Migration Test - Up + Reset
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Database | docker-compose
run: docker-compose -f docker-compose.yml up --detach mariadb
- name: Database | up
run: docker-compose -f docker-compose.yml run -T database yarn up
- name: Database | reset
run: docker-compose -f docker-compose.yml run -T database yarn reset
lint:
if: needs.files-changed.outputs.database == 'true'
name: Lint - Database Up
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Database | Lint
run: cd database && yarn && yarn run lint

View File

@ -3,14 +3,13 @@ name: Gradido DHT Node Test CI
on: push
jobs:
# only (but most important) job from this workflow required for pull requests
# check results serve as run conditions for all other jobs here
files-changed:
name: Detect File Changes - DHT Node
runs-on: ubuntu-latest
outputs:
database: ${{ steps.changes.outputs.database }}
dht_node: ${{ steps.changes.outputs.dht_node }}
docker: ${{ steps.changes.outputs.docker }}
docker-compose: ${{ steps.changes.outputs.docker-compose }}
steps:
- uses: actions/checkout@v3.3.0
@ -22,12 +21,9 @@ jobs:
filters: .github/file-filters.yml
list-files: shell
##############################################################################
# JOB: DOCKER BUILD TEST #####################################################
##############################################################################
build:
name: Docker Build Test - DHT Node
if: needs.files-changed.outputs.dht_node == 'true' || needs.files-changed.outputs.docker == 'true'
if: needs.files-changed.outputs.dht_node == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
@ -45,9 +41,6 @@ jobs:
name: docker-dht-node-test
path: /tmp/dht-node.tar
##############################################################################
# JOB: LINT ##################################################################
##############################################################################
lint:
name: Lint - DHT Node
if: needs.files-changed.outputs.dht_node == 'true'
@ -60,12 +53,9 @@ jobs:
- name: Lint
run: cd dht-node && yarn && yarn run lint
##############################################################################
# JOB: UNIT TEST #############################################################
##############################################################################
unit_test:
name: Unit Tests - DHT Node
if: needs.files-changed.outputs.dht_node == 'true' || needs.files-changed.outputs.docker == 'true'
if: needs.files-changed.outputs.database == 'true' || needs.files-changed.outputs.dht_node == 'true' || needs.files-changed.outputs.docker-compose == 'true' || needs.files-changed.outputs.mariadb == 'true'
needs: [files-changed, build]
runs-on: ubuntu-latest
steps:

View File

@ -3,13 +3,11 @@ name: Gradido Federation Test CI
on: push
jobs:
# only (but most important) job from this workflow required for pull requests
# check results serve as run conditions for all other jobs here
files-changed:
name: Detect File Changes - Federation
runs-on: ubuntu-latest
outputs:
docker: ${{ steps.changes.outputs.docker }}
docker-compose: ${{ steps.changes.outputs.docker-compose }}
federation: ${{ steps.changes.outputs.federation }}
steps:
- uses: actions/checkout@v3.3.0
@ -22,12 +20,9 @@ jobs:
filters: .github/file-filters.yml
list-files: shell
##############################################################################
# JOB: DOCKER BUILD TEST #####################################################
##############################################################################
build:
name: Docker Build Test - Federation
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.federation == 'true'
if: needs.files-changed.outputs.federation == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
@ -45,9 +40,6 @@ jobs:
name: docker-federation-test
path: /tmp/federation.tar
##############################################################################
# JOB: LINT ##################################################################
##############################################################################
lint:
name: Lint - Federation
if: needs.files-changed.outputs.federation == 'true'
@ -60,12 +52,9 @@ jobs:
- name: Lint
run: cd federation && yarn && yarn run lint
##############################################################################
# JOB: UNIT TEST #############################################################
##############################################################################
unit_test:
name: Unit Tests - Federation
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.federation == 'true'
if: needs.files-changed.outputs.database == 'true' || needs.files-changed.outputs.docker-compose == 'true' || needs.files-changed.outputs.federation == 'true' || needs.files-changed.outputs.mariadb == 'true'
needs: [files-changed, build]
runs-on: ubuntu-latest
steps:

32
.github/workflows/test_mariadb.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: Gradido MariaDB Test CI
on: push
jobs:
files-changed:
name: Detect File Changes - MariaDB
runs-on: ubuntu-latest
outputs:
mariadb: ${{ steps.changes.outputs.mariadb }}
steps:
- uses: actions/checkout@v3.3.0
- name: Check for frontend file changes
uses: dorny/paths-filter@v2.11.1
id: changes
with:
token: ${{ github.token }}
filters: .github/file-filters.yml
list-files: shell
build_test:
if: needs.files-changed.outputs.mariadb == 'true'
name: Docker Build Test - MariaDB
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: MariaDB | Build 'test' image
run: docker build --target mariadb_server -t "gradido/mariadb:test" -f ./mariadb/Dockerfile ./

View File

@ -4,8 +4,72 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [1.20.0](https://github.com/gradido/gradido/compare/1.19.1...1.20.0)
- fix(backend): no await for emails [`#2918`](https://github.com/gradido/gradido/pull/2918)
- fix(frontend): no receiver on send by link [`#2933`](https://github.com/gradido/gradido/pull/2933)
- fix(admin): pagination set currentPage by switch tabs [`#2902`](https://github.com/gradido/gradido/pull/2902)
- fix(federation): correct export of the community url [`#2931`](https://github.com/gradido/gradido/pull/2931)
- fix(frontend): displayed decay duration [`#2927`](https://github.com/gradido/gradido/pull/2927)
- fix(frontend): community tab navigation [`#2928`](https://github.com/gradido/gradido/pull/2928)
- fix(frontend): moderator id missing [`#2925`](https://github.com/gradido/gradido/pull/2925)
- fix(frontend): reset button send coins [`#2924`](https://github.com/gradido/gradido/pull/2924)
- refactor(backend): eslint plugin import export style [`#2908`](https://github.com/gradido/gradido/pull/2908)
- fix(backend): vscode intellisense fixes [`#2919`](https://github.com/gradido/gradido/pull/2919)
- refactor(backend): eslint import-no-cycle enabled [`#2905`](https://github.com/gradido/gradido/pull/2905)
- docs(backend): alias rules and conventions [`#2881`](https://github.com/gradido/gradido/pull/2881)
- refactor(backend): get transaction list [`#2923`](https://github.com/gradido/gradido/pull/2923)
- feat(backend): previous balance in transaction [`#2914`](https://github.com/gradido/gradido/pull/2914)
- refactor(backend): eslint update packages [`#2829`](https://github.com/gradido/gradido/pull/2829)
- feat(frontend): send coins via gradido ID [`#2837`](https://github.com/gradido/gradido/pull/2837)
- refactor(database): cleanup database [`#2808`](https://github.com/gradido/gradido/pull/2808)
- refactor(backend): eslint plugin n + fixes [`#2828`](https://github.com/gradido/gradido/pull/2828)
- feat(frontend): add link to download QR-Code [`#2889`](https://github.com/gradido/gradido/pull/2889)
- fix(other): delete node_modules and /tmp/yarn--* on start.sh [`#2904`](https://github.com/gradido/gradido/pull/2904)
- fix(admin): add confirmation modal for user role change and user (un)deletion [`#2880`](https://github.com/gradido/gradido/pull/2880)
- fix(admin): admin open contribution edit button [`#2811`](https://github.com/gradido/gradido/pull/2811)
- fix(backend): import order [`#2911`](https://github.com/gradido/gradido/pull/2911)
- fix(other): use default values for undefined .env database values [`#2910`](https://github.com/gradido/gradido/pull/2910)
- refactor(backend): eslint plugin import + fixes [`#2827`](https://github.com/gradido/gradido/pull/2827)
- refactor(backend): missing event tests [`#2900`](https://github.com/gradido/gradido/pull/2900)
- refactor(backend): unify event names [`#2799`](https://github.com/gradido/gradido/pull/2799)
- feat(backend): events for users [`#2797`](https://github.com/gradido/gradido/pull/2797)
- fix(backend): subscription get's email and languages from user [`#2885`](https://github.com/gradido/gradido/pull/2885)
- refactor(backend): eslint-plugin-jest + fixes [`#2816`](https://github.com/gradido/gradido/pull/2816)
- fix(frontend): fr change ` and ´ to ' [`#2888`](https://github.com/gradido/gradido/pull/2888)
- feat(backend): events for transaction links [`#2792`](https://github.com/gradido/gradido/pull/2792)
- feat(backend): events for contributions [`#2784`](https://github.com/gradido/gradido/pull/2784)
- feat(backend): events for contribution messages [`#2783`](https://github.com/gradido/gradido/pull/2783)
- refactor(backend): upgrade coverage to 85% [`#2884`](https://github.com/gradido/gradido/pull/2884)
- refactor(backend): add klicktipp-api library [`#2883`](https://github.com/gradido/gradido/pull/2883)
- feat(backend): events for contribution links [`#2780`](https://github.com/gradido/gradido/pull/2780)
- refactor(backend): separate events in own files [`#2777`](https://github.com/gradido/gradido/pull/2777)
- refactor(backend): rename `evenProtocolType` to `eventType` [`#2776`](https://github.com/gradido/gradido/pull/2776)
- refactor(other): add yarn installAll on gradido folder [`#2703`](https://github.com/gradido/gradido/pull/2703)
- docs(federation): describe the technical federation architecture [`#2716`](https://github.com/gradido/gradido/pull/2716)
- refactor(admin): admin list contributions for creation transaction list query [`#2791`](https://github.com/gradido/gradido/pull/2791)
- refactor(workflow): separate workflow with file filter for nginx testing [`#2871`](https://github.com/gradido/gradido/pull/2871)
- feat(backend): read communities data from database [`#2807`](https://github.com/gradido/gradido/pull/2807)
- feat(federation): implement a graphql endpoint to answer getpublickey-request [`#2651`](https://github.com/gradido/gradido/pull/2651)
- refactor(workflow): add file filters to dht node and federation workflows [`#2838`](https://github.com/gradido/gradido/pull/2838)
- refactor(workflow): separate test workflow for end-to-end tests [`#2836`](https://github.com/gradido/gradido/pull/2836)
- refactor(workflow): separate test workflow for frontend [`#2835`](https://github.com/gradido/gradido/pull/2835)
- feat(federation): add federation modul to deployment scripts [`#2733`](https://github.com/gradido/gradido/pull/2733)
- refactor(database): event table [`#2720`](https://github.com/gradido/gradido/pull/2720)
- refactor(workflow): separate test workflow with file change filters for admin interface [`#2734`](https://github.com/gradido/gradido/pull/2734)
- fix(admin): fix translation in menu (english) [`#2814`](https://github.com/gradido/gradido/pull/2814)
- refactor(workflow): configure jest to directly check coverage to schlanken the test workflows [`#2790`](https://github.com/gradido/gradido/pull/2790)
- fix(database): removed commands from package.json not working [`#2805`](https://github.com/gradido/gradido/pull/2805)
- fix(other): repair UserProfile.ChangePassword.feature scenario [`#2782`](https://github.com/gradido/gradido/pull/2782)
- feat(backend): test double redeem transaction links [`#2788`](https://github.com/gradido/gradido/pull/2788)
- feat(backend): admin open creations query [`#2813`](https://github.com/gradido/gradido/pull/2813)
- refactor(backend): eslint-plugin-type-graphql + fixes [`#2745`](https://github.com/gradido/gradido/pull/2745)
#### [1.19.1](https://github.com/gradido/gradido/compare/1.19.0...1.19.1)
> 10 March 2023
- chore(other): upgrade version to 1.19.1 [`#2812`](https://github.com/gradido/gradido/pull/2812)
- fix(frontend): admin question clickable [`#2810`](https://github.com/gradido/gradido/pull/2810)
- refactor(frontend): change b-img to b-icon send [`#2809`](https://github.com/gradido/gradido/pull/2809)
- fix(admin): update openCreation in case of tab open. [`#2806`](https://github.com/gradido/gradido/pull/2806)

View File

@ -3,7 +3,7 @@
"description": "Administraion Interface for Gradido",
"main": "index.js",
"author": "Moriz Wahl",
"version": "1.19.1",
"version": "1.20.0",
"license": "Apache-2.0",
"private": false,
"scripts": {
@ -33,6 +33,7 @@
"bootstrap": "4.3.1",
"bootstrap-vue": "^2.21.2",
"core-js": "^3.6.5",
"date-fns": "^2.29.3",
"dotenv-webpack": "^7.0.3",
"express": "^4.17.1",
"graphql": "^15.6.1",

View File

@ -0,0 +1,183 @@
import { mount } from '@vue/test-utils'
import FederationVisualizeItem from './FederationVisualizeItem.vue'
const localVue = global.localVue
const today = new Date()
const createdDate = new Date()
createdDate.setDate(createdDate.getDate() - 3)
let propsData = {
item: {
id: 7590,
foreign: false,
publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7',
url: 'http://localhost/api/2_0',
lastAnnouncedAt: createdDate,
verifiedAt: today,
lastErrorAt: null,
createdAt: createdDate,
updatedAt: null,
},
}
const mocks = {
$i18n: {
locale: 'en',
},
}
describe('FederationVisualizeItem', () => {
let wrapper
const Wrapper = () => {
return mount(FederationVisualizeItem, { localVue, mocks, propsData })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component', () => {
expect(wrapper.find('div.federation-visualize-item').exists()).toBe(true)
})
describe('rendering item properties', () => {
it('has the url', () => {
expect(wrapper.find('.row > div:nth-child(2) > div').text()).toBe(
'http://localhost/api/2_0',
)
})
it('has the public key', () => {
expect(wrapper.find('.row > div:nth-child(2) > small').text()).toContain(
'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7'.substring(0, 26),
)
})
describe('verified item', () => {
it('has the check icon', () => {
expect(wrapper.find('svg.bi-check').exists()).toBe(true)
})
it('has the text variant "success"', () => {
expect(wrapper.find('.text-success').exists()).toBe(true)
})
})
describe('not verified item', () => {
beforeEach(() => {
propsData = {
item: {
id: 7590,
foreign: false,
publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7',
url: 'http://localhost/api/2_0',
lastAnnouncedAt: createdDate,
verifiedAt: null,
lastErrorAt: null,
createdAt: createdDate,
updatedAt: null,
},
}
wrapper = Wrapper()
})
it('has the x-circle icon', () => {
expect(wrapper.find('svg.bi-x-circle').exists()).toBe(true)
})
it('has the text variant "danger"', () => {
expect(wrapper.find('.text-danger').exists()).toBe(true)
})
})
// describe('with different locales (de, en, fr, es, nl)', () => {
describe('lastAnnouncedAt', () => {
it('computes the time string for different locales (de, en, fr, es, nl)', () => {
wrapper.vm.$i18n.locale = 'de'
wrapper = Wrapper()
expect(wrapper.vm.lastAnnouncedAt).toBe('vor 3 Tagen')
wrapper.vm.$i18n.locale = 'fr'
wrapper = Wrapper()
expect(wrapper.vm.lastAnnouncedAt).toBe('il y a 3 jours')
wrapper.vm.$i18n.locale = 'es'
wrapper = Wrapper()
expect(wrapper.vm.lastAnnouncedAt).toBe('hace 3 días')
wrapper.vm.$i18n.locale = 'nl'
wrapper = Wrapper()
expect(wrapper.vm.lastAnnouncedAt).toBe('3 dagen geleden')
})
describe('lastAnnouncedAt == null', () => {
beforeEach(() => {
propsData = {
item: {
id: 7590,
foreign: false,
publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7',
url: 'http://localhost/api/2_0',
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: createdDate,
updatedAt: null,
},
}
wrapper = Wrapper()
})
it('computes empty string', async () => {
expect(wrapper.vm.lastAnnouncedAt).toBe('')
})
})
})
describe('createdAt', () => {
it('computes the time string for different locales (de, en, fr, es, nl)', () => {
wrapper.vm.$i18n.locale = 'de'
wrapper = Wrapper()
expect(wrapper.vm.createdAt).toBe('vor 3 Tagen')
wrapper.vm.$i18n.locale = 'fr'
wrapper = Wrapper()
expect(wrapper.vm.createdAt).toBe('il y a 3 jours')
wrapper.vm.$i18n.locale = 'es'
wrapper = Wrapper()
expect(wrapper.vm.createdAt).toBe('hace 3 días')
wrapper.vm.$i18n.locale = 'nl'
wrapper = Wrapper()
expect(wrapper.vm.createdAt).toBe('3 dagen geleden')
})
describe('createdAt == null', () => {
beforeEach(() => {
propsData = {
item: {
id: 7590,
foreign: false,
publicKey: 'eaf6a426b24fd54f8fbae11c17700fc595080ca25159579c63d38dbc64284ba7',
url: 'http://localhost/api/2_0',
lastAnnouncedAt: createdDate,
verifiedAt: null,
lastErrorAt: null,
createdAt: null,
updatedAt: null,
},
}
wrapper = Wrapper()
})
it('computes empty string', async () => {
expect(wrapper.vm.createdAt).toBe('')
})
})
})
})
})
})

View File

@ -0,0 +1,63 @@
<template>
<div class="federation-visualize-item">
<b-row>
<b-col cols="1"><b-icon :icon="icon" :variant="variant" class="mr-4"></b-icon></b-col>
<b-col>
<div>{{ item.url }}</div>
<small>{{ `${item.publicKey.substring(0, 26)}` }}</small>
</b-col>
<b-col cols="2">{{ lastAnnouncedAt }}</b-col>
<b-col cols="2">{{ createdAt }}</b-col>
</b-row>
</div>
</template>
<script>
import { formatDistanceToNow } from 'date-fns'
import { de, en, fr, es, nl } from 'date-fns/locale'
const locales = { en, de, es, fr, nl }
export default {
name: 'FederationVisualizeItem',
props: {
item: { type: Object },
},
data() {
return {
formatDistanceToNow,
locale: this.$i18n.locale,
}
},
computed: {
verified() {
return new Date(this.item.verifiedAt) >= new Date(this.item.lastAnnouncedAt)
},
icon() {
return this.verified ? 'check' : 'x-circle'
},
variant() {
return this.verified ? 'success' : 'danger'
},
lastAnnouncedAt() {
if (this.item.lastAnnouncedAt) {
return formatDistanceToNow(new Date(this.item.lastAnnouncedAt), {
includeSecond: true,
addSuffix: true,
locale: locales[this.locale],
})
}
return ''
},
createdAt() {
if (this.item.createdAt) {
return formatDistanceToNow(new Date(this.item.createdAt), {
includeSecond: true,
addSuffix: true,
locale: locales[this.locale],
})
}
return ''
},
},
}
</script>

View File

@ -62,8 +62,12 @@ describe('NavBar', () => {
)
})
it('has a link to /federation', () => {
expect(wrapper.findAll('.nav-item').at(3).find('a').attributes('href')).toBe('/federation')
})
it('has a link to /statistic', () => {
expect(wrapper.findAll('.nav-item').at(3).find('a').attributes('href')).toBe('/statistic')
expect(wrapper.findAll('.nav-item').at(4).find('a').attributes('href')).toBe('/statistic')
})
})
@ -72,7 +76,7 @@ describe('NavBar', () => {
beforeEach(async () => {
delete window.location
window.location = ''
await wrapper.findAll('.nav-item').at(4).find('a').trigger('click')
await wrapper.findAll('.nav-item').at(5).find('a').trigger('click')
})
afterEach(() => {
@ -97,7 +101,7 @@ describe('NavBar', () => {
window.location = {
assign: windowLocationMock,
}
await wrapper.findAll('.nav-item').at(5).find('a').trigger('click')
await wrapper.findAll('.nav-item').at(6).find('a').trigger('click')
})
afterEach(() => {

View File

@ -1,6 +1,6 @@
<template>
<div class="component-nabvar">
<b-navbar toggleable="md" type="dark" variant="success">
<b-navbar toggleable="lg" type="dark" class="bg-dark">
<b-navbar-brand class="mb-2" to="/">
<img src="img/brand/gradido_logo_w.png" class="navbar-brand-img pl-2" alt="..." />
</b-navbar-brand>
@ -19,6 +19,9 @@
<b-nav-item to="/contribution-links">
{{ $t('navbar.automaticContributions') }}
</b-nav-item>
<b-nav-item to="/federation">
{{ $t('navbar.instances') }}
</b-nav-item>
<b-nav-item to="/statistic">{{ $t('navbar.statistic') }}</b-nav-item>
<b-nav-item @click="wallet">{{ $t('navbar.my-account') }}</b-nav-item>
<b-nav-item @click="logout">{{ $t('navbar.logout') }}</b-nav-item>

View File

@ -0,0 +1,17 @@
import gql from 'graphql-tag'
export const getCommunities = gql`
query {
getCommunities {
id
foreign
publicKey
url
lastAnnouncedAt
verifiedAt
lastErrorAt
createdAt
updatedAt
}
}
`

View File

@ -68,6 +68,13 @@
"error": "Fehler",
"expired": "abgelaufen",
"e_mail": "E-Mail",
"federation": {
"createdAt": "Erstellt am",
"gradidoInstances": "Gradido Instanzen",
"lastAnnouncedAt": "letzte Bekanntgabe",
"url": "Url",
"verified": "Verifiziert"
},
"firstname": "Vorname",
"footer": {
"app_version": "App version {version}",
@ -105,6 +112,7 @@
"name": "Name",
"navbar": {
"automaticContributions": "Automatische Beiträge",
"instances": "Instanzen",
"logout": "Abmelden",
"my-account": "Mein Konto",
"statistic": "Statistik",

View File

@ -68,6 +68,13 @@
"error": "Error",
"expired": "expired",
"e_mail": "E-mail",
"federation": {
"createdAt": "Created At ",
"gradidoInstances": "Gradido Instances",
"lastAnnouncedAt": "Last Announced",
"url": "Url",
"verified": "Verified"
},
"firstname": "Firstname",
"footer": {
"app_version": "App version {version}",
@ -105,6 +112,7 @@
"name": "Name",
"navbar": {
"automaticContributions": "Automatic Contributions",
"instances": "Instances",
"logout": "Logout",
"my-account": "My Account",
"statistic": "Statistic",

View File

@ -39,7 +39,7 @@ const mocks = {
const defaultData = () => {
return {
adminListContributions: {
contributionCount: 2,
contributionCount: 30,
contributionList: [
{
id: 1,
@ -407,6 +407,44 @@ describe('CreationConfirm', () => {
statusFilter: ['IN_PROGRESS', 'PENDING', 'CONFIRMED', 'DENIED', 'DELETED'],
})
})
describe('change pagination', () => {
it('has pagination buttons', () => {
expect(wrapper.findComponent({ name: 'BPagination' }).exists()).toBe(true)
})
describe('next page', () => {
beforeEach(() => {
jest.clearAllMocks()
wrapper.findComponent({ name: 'BPagination' }).vm.$emit('input', 2)
})
it('calls the API again', () => {
expect(adminListContributionsMock).toBeCalledWith({
currentPage: 2,
order: 'DESC',
pageSize: 25,
statusFilter: ['IN_PROGRESS', 'PENDING', 'CONFIRMED', 'DENIED', 'DELETED'],
})
})
describe('click tab "open" again', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.find('a[data-test="open"]').trigger('click')
})
it('refetches contributions with proper filter and current page = 1', () => {
expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1,
order: 'DESC',
pageSize: 25,
statusFilter: ['IN_PROGRESS', 'PENDING'],
})
})
})
})
})
})
})
})

View File

@ -116,6 +116,11 @@ export default {
pageSize: 25,
}
},
watch: {
tabIndex() {
this.currentPage = 1
},
},
methods: {
deleteCreation() {
this.$apollo

View File

@ -0,0 +1,125 @@
import { mount } from '@vue/test-utils'
import FederationVisualize from './FederationVisualize'
import VueApollo from 'vue-apollo'
import { createMockClient } from 'mock-apollo-client'
import { getCommunities } from '@/graphql/getCommunities'
import { toastErrorSpy } from '../../test/testSetup'
const mockClient = createMockClient()
const apolloProvider = new VueApollo({
defaultClient: mockClient,
})
const localVue = global.localVue
localVue.use(VueApollo)
const mocks = {
$t: (key) => key,
$d: jest.fn((d) => d),
$i18n: {
locale: 'en',
t: (key) => key,
},
}
const defaultData = () => {
return {
getCommunities: [
{
id: 1776,
foreign: true,
publicKey: 'c7ca9e742421bb167b8666cb78f90b40c665b8f35db8f001988d44dbb3ce8527',
url: 'http://localhost/api/2_0',
lastAnnouncedAt: '2023-04-07T12:27:24.037Z',
verifiedAt: null,
lastErrorAt: null,
createdAt: '2023-04-07T11:45:06.254Z',
updatedAt: null,
__typename: 'Community',
},
{
id: 1775,
foreign: true,
publicKey: 'c7ca9e742421bb167b8666cb78f90b40c665b8f35db8f001988d44dbb3ce8527',
url: 'http://localhost/api/1_1',
lastAnnouncedAt: '2023-04-07T12:27:24.023Z',
verifiedAt: null,
lastErrorAt: null,
createdAt: '2023-04-07T11:45:06.234Z',
updatedAt: null,
__typename: 'Community',
},
{
id: 1774,
foreign: true,
publicKey: 'c7ca9e742421bb167b8666cb78f90b40c665b8f35db8f001988d44dbb3ce8527',
url: 'http://localhost/api/1_0',
lastAnnouncedAt: '2023-04-07T12:27:24.009Z',
verifiedAt: null,
lastErrorAt: null,
createdAt: '2023-04-07T11:45:06.218Z',
updatedAt: null,
__typename: 'Community',
},
],
}
}
describe('FederationVisualize', () => {
let wrapper
const getCommunitiesMock = jest.fn()
mockClient.setRequestHandler(
getCommunities,
getCommunitiesMock
.mockRejectedValueOnce({ message: 'Ouch!' })
.mockResolvedValue({ data: defaultData() }),
)
const Wrapper = () => {
return mount(FederationVisualize, { localVue, mocks, apolloProvider })
}
describe('mount', () => {
beforeEach(() => {
jest.clearAllMocks()
wrapper = Wrapper()
})
describe('server error', () => {
it('toast error', () => {
expect(toastErrorSpy).toBeCalledWith('Ouch!')
})
})
describe('sever success', () => {
it('sends query to Apollo when created', () => {
expect(getCommunitiesMock).toBeCalled()
})
it('has a DIV element with the class "federation-visualize"', () => {
expect(wrapper.find('div.federation-visualize').exists()).toBe(true)
})
it('has a refresh button', () => {
expect(wrapper.find('[data-test="federation-communities-refresh-btn"]').exists()).toBe(true)
})
it('renders 3 community list items', () => {
expect(wrapper.findAll('.list-group-item').length).toBe(3)
})
describe('cklicking the refresh button', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.find('[data-test="federation-communities-refresh-btn"]').trigger('click')
})
it('calls the API', async () => {
expect(getCommunitiesMock).toBeCalled()
})
})
})
})
})

View File

@ -0,0 +1,69 @@
<template>
<div class="federation-visualize">
<div class="d-flex justify-content-between align-items-center mb-3">
<span class="h2">{{ $t('federation.gradidoInstances') }}</span>
<b-button>
<b-icon
icon="arrow-clockwise"
font-scale="2"
:animation="animation"
@click="$apollo.queries.GetCommunities.refresh()"
data-test="federation-communities-refresh-btn"
></b-icon>
</b-button>
</div>
<b-list-group>
<b-row>
<b-col cols="1" class="ml-1">{{ $t('federation.verified') }}</b-col>
<b-col class="ml-3">{{ $t('federation.url') }}</b-col>
<b-col cols="2">{{ $t('federation.lastAnnouncedAt') }}</b-col>
<b-col cols="2">{{ $t('federation.createdAt') }}</b-col>
</b-row>
<b-list-group-item
v-for="item in communities"
:key="item.id"
:variant="!item.foreign ? 'primary' : 'warning'"
>
<federation-visualize-item :item="item" />
</b-list-group-item>
</b-list-group>
</div>
</template>
<script>
import { getCommunities } from '@/graphql/getCommunities'
import FederationVisualizeItem from '../components/Fedaration/FederationVisualizeItem.vue'
export default {
name: 'FederationVisualize',
components: {
FederationVisualizeItem,
},
data() {
return {
oldPublicKey: '',
communities: [],
icon: '',
}
},
computed: {
animation() {
return this.$apollo.queries.GetCommunities.loading ? 'spin' : ''
},
},
apollo: {
GetCommunities: {
fetchPolicy: 'network-only',
query() {
return getCommunities
},
update({ getCommunities }) {
this.communities = getCommunities
},
error({ message }) {
this.toastError(message)
},
},
},
}
</script>

View File

@ -45,7 +45,7 @@ describe('router', () => {
describe('routes', () => {
it('has nine routes defined', () => {
expect(routes).toHaveLength(8)
expect(routes).toHaveLength(9)
})
it('has "/overview" as default', async () => {
@ -88,6 +88,13 @@ describe('router', () => {
})
})
describe('federation', () => {
it('loads the "FederationVisualize" page', async () => {
const component = await routes.find((r) => r.path === '/federation').component()
expect(component.default.name).toBe('FederationVisualize')
})
})
describe('not found page', () => {
it('renders the "NotFound" component', async () => {
const component = await routes.find((r) => r.path === '*').component()

View File

@ -31,6 +31,10 @@ const routes = [
path: '*',
component: () => import('@/components/NotFoundPage.vue'),
},
{
path: '/federation',
component: () => import('@/pages/FederationVisualize.vue'),
},
]
export default routes

View File

@ -5038,6 +5038,11 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
date-fns@^2.29.3:
version "2.29.3"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==
de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"

View File

@ -5,7 +5,7 @@ module.exports = {
node: true,
},
parser: '@typescript-eslint/parser',
plugins: ['prettier', '@typescript-eslint', 'type-graphql', 'import', 'n'],
plugins: ['prettier', '@typescript-eslint', 'type-graphql', 'import', 'n', 'promise'],
extends: [
'standard',
'eslint:recommended',
@ -19,7 +19,7 @@ module.exports = {
},
'import/resolver': {
typescript: {
project: ['**/tsconfig.json'],
project: ['./tsconfig.json', '**/tsconfig.json'],
},
node: true,
},
@ -51,7 +51,7 @@ module.exports = {
'import/named': 'error',
'import/namespace': 'error',
'import/no-absolute-path': 'error',
'import/no-cycle': 'off',
'import/no-cycle': 'error',
'import/no-dynamic-require': 'error',
'import/no-internal-modules': 'off',
'import/no-relative-packages': 'error',
@ -67,7 +67,7 @@ module.exports = {
'import/group-exports': 'off',
'import/newline-after-import': 'error',
'import/no-anonymous-default-export': 'error',
'import/no-default-export': 'off',
'import/no-default-export': 'error',
'import/no-duplicates': 'error',
'import/no-named-default': 'error',
'import/no-namespace': 'error',
@ -96,7 +96,7 @@ module.exports = {
distinctGroup: true,
},
],
'import/prefer-default-export': 'off', // TODO
'import/prefer-default-export': 'off',
// n
'n/handle-callback-err': 'error',
'n/no-callback-literal': 'error',
@ -136,6 +136,21 @@ module.exports = {
'n/prefer-global/url-search-params': 'error',
'n/prefer-promises/dns': 'error',
'n/prefer-promises/fs': 'error',
// promise
'promise/catch-or-return': 'error',
'promise/no-return-wrap': 'error',
'promise/param-names': 'error',
'promise/always-return': 'error',
'promise/no-native': 'off',
'promise/no-nesting': 'warn',
'promise/no-promise-in-callback': 'warn',
'promise/no-callback-in-promise': 'warn',
'promise/avoid-new': 'warn',
'promise/no-new-statics': 'error',
'promise/no-return-in-finally': 'warn',
'promise/valid-params': 'warn',
'promise/prefer-await-to-callbacks': 'error',
'promise/no-multiple-resolved': 'error',
},
overrides: [
// only for ts files
@ -156,8 +171,8 @@ module.exports = {
'import/unambiguous': 'off',
},
parserOptions: {
tsconfigRootDir: './',
project: ['**/tsconfig.json'],
tsconfigRootDir: __dirname,
project: ['./tsconfig.json', '**/tsconfig.json'],
// this is to properly reference the referenced project database without requirement of compiling it
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
},

View File

@ -1,9 +1,14 @@
module.exports = {
semi: false,
printWidth: 100,
singleQuote: true,
trailingComma: "all",
tabWidth: 2,
useTabs: false,
semi: false,
singleQuote: true,
quoteProps: "as-needed",
jsxSingleQuote: true,
trailingComma: "all",
bracketSpacing: true,
bracketSameLine: false,
arrowParens: "always",
endOfLine: "auto",
};

View File

@ -8,7 +8,7 @@
"pattern": "yyyy-MM-dd",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
},
"keepFileExt" : true,
"fileNameSep" : "_",
@ -21,7 +21,7 @@
"pattern": "yyyy-MM-dd",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
},
"keepFileExt" : true,
"fileNameSep" : "_",
@ -34,7 +34,7 @@
"pattern": "yyyy-MM-dd",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
},
"keepFileExt" : true,
"fileNameSep" : "_",
@ -47,7 +47,7 @@
"pattern": "yyyy-MM-dd",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
},
"keepFileExt" : true,
"fileNameSep" : "_",
@ -60,7 +60,7 @@
"pattern": "yyyy-MM-dd",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m %s"
},
"keepFileExt" : true,
"fileNameSep" : "_",
@ -77,7 +77,7 @@
"type": "stdout",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
}
},
"apolloOut":
@ -85,7 +85,7 @@
"type": "stdout",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
}
}
},

View File

@ -1,6 +1,6 @@
{
"name": "gradido-backend",
"version": "1.19.1",
"version": "1.20.0",
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/backend",

View File

@ -1,47 +1,44 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import axios from 'axios'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const apiPost = async (url: string, payload: unknown): Promise<any> => {
logger.trace('POST', url, payload)
return axios
.post(url, payload)
.then((result) => {
logger.trace('POST-Response', result)
if (result.status !== 200) {
throw new LogError('HTTP Status Error', result.status)
}
if (result.data.state !== 'success') {
throw new Error(result.data.msg)
}
return { success: true, data: result.data }
})
.catch((error) => {
return { success: false, data: error.message }
})
try {
const result = await axios.post(url, payload)
logger.trace('POST-Response', result)
if (result.status !== 200) {
throw new LogError('HTTP Status Error', result.status)
}
if (result.data.state !== 'success') {
throw new LogError(result.data.msg)
}
return { success: true, data: result.data }
} catch (error: any) {
return { success: false, data: error.message }
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const apiGet = async (url: string): Promise<any> => {
logger.trace('GET: url=' + url)
return axios
.get(url)
.then((result) => {
logger.trace('GET-Response', result)
if (result.status !== 200) {
throw new LogError('HTTP Status Error', result.status)
}
if (!['success', 'warning'].includes(result.data.state)) {
throw new Error(result.data.msg)
}
return { success: true, data: result.data }
})
.catch((error) => {
return { success: false, data: error.message }
})
try {
const result = await axios.get(url)
logger.trace('GET-Response', result)
if (result.status !== 200) {
throw new LogError('HTTP Status Error', result.status)
}
if (!['success', 'warning'].includes(result.data.state)) {
throw new LogError(result.data.msg)
}
return { success: true, data: result.data }
} catch (error: any) {
return { success: false, data: error.message }
}
}

View File

@ -4,7 +4,8 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import CONFIG from '@/config'
import { CONFIG } from '@/config'
// eslint-disable-next-line import/no-relative-parent-imports
import KlicktippConnector from 'klicktipp-api'

View File

@ -1,7 +1,7 @@
import { verify, sign } from 'jsonwebtoken'
import CONFIG from '@/config/'
import LogError from '@/server/LogError'
import { CONFIG } from '@/config/'
import { LogError } from '@/server/LogError'
import { CustomJwtPayload } from './CustomJwtPayload'

View File

@ -1,4 +1,4 @@
import CONFIG from './index'
import { CONFIG } from './index'
describe('config/index', () => {
describe('decay start block', () => {

View File

@ -76,7 +76,7 @@ const email = {
EMAIL_SENDER: process.env.EMAIL_SENDER || 'info@gradido.net',
EMAIL_PASSWORD: process.env.EMAIL_PASSWORD || '',
EMAIL_SMTP_URL: process.env.EMAIL_SMTP_URL || 'mailserver',
EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '1025',
EMAIL_SMTP_PORT: Number(process.env.EMAIL_SMTP_PORT) || 1025,
// eslint-disable-next-line no-unneeded-ternary
EMAIL_TLS: process.env.EMAIL_TLS === 'false' ? false : true,
EMAIL_LINK_VERIFICATION:
@ -121,7 +121,7 @@ const federation = {
Number(process.env.FEDERATION_VALIDATE_COMMUNITY_TIMER) || 60000,
}
const CONFIG = {
export const CONFIG = {
...constants,
...server,
...database,
@ -132,5 +132,3 @@ const CONFIG = {
...webhook,
...federation,
}
export default CONFIG

View File

@ -3,13 +3,13 @@ import { createTransport } from 'nodemailer'
import { logger, i18n } from '@test/testSetup'
import CONFIG from '@/config'
import { CONFIG } from '@/config'
import { sendEmailTranslated } from './sendEmailTranslated'
CONFIG.EMAIL = false
CONFIG.EMAIL_SMTP_URL = 'EMAIL_SMTP_URL'
CONFIG.EMAIL_SMTP_PORT = '1234'
CONFIG.EMAIL_SMTP_PORT = 1234
CONFIG.EMAIL_USERNAME = 'user'
CONFIG.EMAIL_PASSWORD = 'pwd'
CONFIG.EMAIL_TLS = true
@ -30,7 +30,7 @@ jest.mock('nodemailer', () => {
})
describe('sendEmailTranslated', () => {
let result: Record<string, unknown> | null
let result: Record<string, unknown> | boolean | null
describe('config email is false', () => {
beforeEach(async () => {

View File

@ -1,49 +1,52 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import path from 'path'
import Email from 'email-templates'
import i18n from 'i18n'
import { createTransport } from 'nodemailer'
import CONFIG from '@/config'
import LogError from '@/server/LogError'
import { CONFIG } from '@/config'
import { backendLogger as logger } from '@/server/logger'
export const sendEmailTranslated = async (params: {
export const sendEmailTranslated = async ({
receiver,
template,
locals,
}: {
receiver: {
to: string
cc?: string
}
template: string
locals: Record<string, unknown>
}): Promise<Record<string, unknown> | null> => {
let resultSend: Record<string, unknown> | null = null
}): Promise<Record<string, unknown> | boolean | null> => {
// TODO: test the calling order of 'i18n.setLocale' for example: language of logging 'en', language of email receiver 'es', reset language of current user 'de'
// because language of receiver can differ from language of current user who triggers the sending
const rememberLocaleToRestore = i18n.getLocale()
i18n.setLocale('en') // for logging
logger.info(
`send Email: language=${params.locals.locale} to=${params.receiver.to}` +
(params.receiver.cc ? `, cc=${params.receiver.cc}` : '') +
`, subject=${i18n.__('emails.' + params.template + '.subject')}`,
)
if (!CONFIG.EMAIL) {
logger.info(`Emails are disabled via config...`)
return null
}
// because language of receiver can differ from language of current user who triggers the sending
// const rememberLocaleToRestore = i18n.getLocale()
i18n.setLocale('en') // for logging
logger.info(
`send Email: language=${locals.locale as string} to=${receiver.to}` +
(receiver.cc ? `, cc=${receiver.cc}` : '') +
`, subject=${i18n.__('emails.' + template + '.subject')}`,
)
if (CONFIG.EMAIL_TEST_MODUS) {
logger.info(
`Testmodus=ON: change receiver from ${params.receiver.to} to ${CONFIG.EMAIL_TEST_RECEIVER}`,
`Testmodus=ON: change receiver from ${receiver.to} to ${CONFIG.EMAIL_TEST_RECEIVER}`,
)
params.receiver.to = CONFIG.EMAIL_TEST_RECEIVER
receiver.to = CONFIG.EMAIL_TEST_RECEIVER
}
const transport = createTransport({
host: CONFIG.EMAIL_SMTP_URL,
port: Number(CONFIG.EMAIL_SMTP_PORT),
port: CONFIG.EMAIL_SMTP_PORT,
secure: false, // true for 465, false for other ports
requireTLS: CONFIG.EMAIL_TLS,
auth: {
@ -52,7 +55,7 @@ export const sendEmailTranslated = async (params: {
},
})
i18n.setLocale(params.locals.locale as string) // for email
i18n.setLocale(locals.locale as string) // for email
// TESTING: see 'README.md'
const email = new Email({
@ -64,23 +67,16 @@ export const sendEmailTranslated = async (params: {
// i18n, // is only needed if you don't install i18n
})
// ATTENTION: await is needed, because otherwise on send the email gets send in the language of the current user, because below the language gets reset
await email
const resultSend = await email
.send({
template: path.join(__dirname, 'templates', params.template),
message: params.receiver,
locals: params.locals, // the 'locale' in here seems not to be used by 'email-template', because it doesn't work if the language isn't set before by 'i18n.setLocale'
})
.then((result: Record<string, unknown>) => {
resultSend = result
logger.info('Send email successfully !!!')
logger.info('Result: ', result)
template: path.join(__dirname, 'templates', template),
message: receiver,
locals, // the 'locale' in here seems not to be used by 'email-template', because it doesn't work if the language isn't set before by 'i18n.setLocale'
})
.catch((error: unknown) => {
throw new LogError('Error sending notification email', error)
logger.error('Error sending notification email', error)
return false
})
i18n.setLocale(rememberLocaleToRestore)
return resultSend
}

View File

@ -9,7 +9,7 @@ import { Decimal } from 'decimal.js-light'
import { testEnvironment } from '@test/helpers'
import { logger, i18n as localization } from '@test/testSetup'
import CONFIG from '@/config'
import { CONFIG } from '@/config'
import { sendEmailTranslated } from './sendEmailTranslated'
import {

View File

@ -1,6 +1,6 @@
import { Decimal } from 'decimal.js-light'
import CONFIG from '@/config'
import { CONFIG } from '@/config'
import { decimalSeparatorByLanguage } from '@/util/utilities'
import { sendEmailTranslated } from './sendEmailTranslated'
@ -13,7 +13,7 @@ export const sendAddedContributionMessageEmail = (data: {
senderFirstName: string
senderLastName: string
contributionMemo: string
}): Promise<Record<string, unknown> | null> => {
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: {
to: `${data.firstName} ${data.lastName} <${data.email}>`,
@ -40,7 +40,7 @@ export const sendAccountActivationEmail = (data: {
language: string
activationLink: string
timeDurationObject: Record<string, unknown>
}): Promise<Record<string, unknown> | null> => {
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'accountActivation',
@ -62,7 +62,7 @@ export const sendAccountMultiRegistrationEmail = (data: {
lastName: string
email: string
language: string
}): Promise<Record<string, unknown> | null> => {
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'accountMultiRegistration',
@ -86,7 +86,7 @@ export const sendContributionConfirmedEmail = (data: {
senderLastName: string
contributionMemo: string
contributionAmount: Decimal
}): Promise<Record<string, unknown> | null> => {
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionConfirmed',
@ -113,7 +113,7 @@ export const sendContributionDeletedEmail = (data: {
senderFirstName: string
senderLastName: string
contributionMemo: string
}): Promise<Record<string, unknown> | null> => {
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionDeleted',
@ -139,7 +139,7 @@ export const sendContributionDeniedEmail = (data: {
senderFirstName: string
senderLastName: string
contributionMemo: string
}): Promise<Record<string, unknown> | null> => {
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionDenied',
@ -164,7 +164,7 @@ export const sendResetPasswordEmail = (data: {
language: string
resetLink: string
timeDurationObject: Record<string, unknown>
}): Promise<Record<string, unknown> | null> => {
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'resetPassword',
@ -191,7 +191,7 @@ export const sendTransactionLinkRedeemedEmail = (data: {
senderEmail: string
transactionMemo: string
transactionAmount: Decimal
}): Promise<Record<string, unknown> | null> => {
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'transactionLinkRedeemed',
@ -220,7 +220,7 @@ export const sendTransactionReceivedEmail = (data: {
senderLastName: string
senderEmail: string
transactionAmount: Decimal
}): Promise<Record<string, unknown> | null> => {
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'transactionReceived',

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_CONFIRM = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_CREATE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_DELETE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_DENY = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_LINK_CREATE = async (
moderator: DbUser,

View File

@ -2,7 +2,8 @@ import { ContributionLink as DbContributionLink } from '@entity/ContributionLink
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_LINK_DELETE = async (
moderator: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE = async (
moderator: DbUser,

View File

@ -3,7 +3,8 @@ import { ContributionMessage as DbContributionMessage } from '@entity/Contributi
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_CONTRIBUTION_UPDATE = async (
user: DbUser,

View File

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

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_USER_ROLE_SET = async (
user: DbUser,

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_ADMIN_USER_UNDELETE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_CONTRIBUTION_CREATE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_CONTRIBUTION_DELETE = async (
user: DbUser,

View File

@ -5,7 +5,8 @@ import { Transaction as DbTransaction } from '@entity/Transaction'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_CONTRIBUTION_LINK_REDEEM = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { ContributionMessage as DbContributionMessage } from '@entity/Contributi
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_CONTRIBUTION_MESSAGE_CREATE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_CONTRIBUTION_UPDATE = async (
user: DbUser,

View File

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

View File

@ -1,7 +1,8 @@
import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_EMAIL_ADMIN_CONFIRMATION = async (
user: DbUser,

View File

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

View File

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

View File

@ -3,7 +3,8 @@ import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_TRANSACTION_LINK_CREATE = async (
user: DbUser,

View File

@ -2,7 +2,8 @@ import { Event as DbEvent } from '@entity/Event'
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { User as DbUser } from '@entity/User'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_TRANSACTION_LINK_DELETE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_TRANSACTION_LINK_REDEEM = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Transaction as DbTransaction } from '@entity/Transaction'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_TRANSACTION_RECEIVE = async (
user: DbUser,

View File

@ -3,7 +3,8 @@ import { Transaction as DbTransaction } from '@entity/Transaction'
import { User as DbUser } from '@entity/User'
import { Decimal } from 'decimal.js-light'
import { Event, EventType } from './Event'
import { Event } from './Event'
import { EventType } from './EventType'
export const EVENT_TRANSACTION_SEND = async (
user: DbUser,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,37 +34,3 @@ export const Event = (
event.amount = amount
return event
}
export { EventType }
export { EVENT_ADMIN_CONTRIBUTION_CONFIRM } from './EVENT_ADMIN_CONTRIBUTION_CONFIRM'
export { EVENT_ADMIN_CONTRIBUTION_CREATE } from './EVENT_ADMIN_CONTRIBUTION_CREATE'
export { EVENT_ADMIN_CONTRIBUTION_DELETE } from './EVENT_ADMIN_CONTRIBUTION_DELETE'
export { EVENT_ADMIN_CONTRIBUTION_DENY } from './EVENT_ADMIN_CONTRIBUTION_DENY'
export { EVENT_ADMIN_CONTRIBUTION_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_UPDATE'
export { EVENT_ADMIN_CONTRIBUTION_LINK_CREATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_CREATE'
export { EVENT_ADMIN_CONTRIBUTION_LINK_DELETE } from './EVENT_ADMIN_CONTRIBUTION_LINK_DELETE'
export { EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE'
export { EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE'
export { EVENT_ADMIN_USER_DELETE } from './EVENT_ADMIN_USER_DELETE'
export { EVENT_ADMIN_USER_UNDELETE } from './EVENT_ADMIN_USER_UNDELETE'
export { EVENT_ADMIN_USER_ROLE_SET } from './EVENT_ADMIN_USER_ROLE_SET'
export { EVENT_CONTRIBUTION_CREATE } from './EVENT_CONTRIBUTION_CREATE'
export { EVENT_CONTRIBUTION_DELETE } from './EVENT_CONTRIBUTION_DELETE'
export { EVENT_CONTRIBUTION_UPDATE } from './EVENT_CONTRIBUTION_UPDATE'
export { EVENT_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_CONTRIBUTION_MESSAGE_CREATE'
export { EVENT_CONTRIBUTION_LINK_REDEEM } from './EVENT_CONTRIBUTION_LINK_REDEEM'
export { EVENT_EMAIL_ACCOUNT_MULTIREGISTRATION } from './EVENT_EMAIL_ACCOUNT_MULTIREGISTRATION'
export { EVENT_EMAIL_ADMIN_CONFIRMATION } from './EVENT_EMAIL_ADMIN_CONFIRMATION'
export { EVENT_EMAIL_CONFIRMATION } from './EVENT_EMAIL_CONFIRMATION'
export { EVENT_EMAIL_FORGOT_PASSWORD } from './EVENT_EMAIL_FORGOT_PASSWORD'
export { EVENT_TRANSACTION_SEND } from './EVENT_TRANSACTION_SEND'
export { EVENT_TRANSACTION_RECEIVE } from './EVENT_TRANSACTION_RECEIVE'
export { EVENT_TRANSACTION_LINK_CREATE } from './EVENT_TRANSACTION_LINK_CREATE'
export { EVENT_TRANSACTION_LINK_DELETE } from './EVENT_TRANSACTION_LINK_DELETE'
export { EVENT_TRANSACTION_LINK_REDEEM } from './EVENT_TRANSACTION_LINK_REDEEM'
export { EVENT_USER_ACTIVATE_ACCOUNT } from './EVENT_USER_ACTIVATE_ACCOUNT'
export { EVENT_USER_INFO_UPDATE } from './EVENT_USER_INFO_UPDATE'
export { EVENT_USER_LOGIN } from './EVENT_USER_LOGIN'
export { EVENT_USER_LOGOUT } from './EVENT_USER_LOGOUT'
export { EVENT_USER_REGISTER } from './EVENT_USER_REGISTER'

View File

@ -0,0 +1,33 @@
export { EventType } from './EventType'
export { Event } from './Event'
export { EVENT_ADMIN_CONTRIBUTION_CONFIRM } from './EVENT_ADMIN_CONTRIBUTION_CONFIRM'
export { EVENT_ADMIN_CONTRIBUTION_CREATE } from './EVENT_ADMIN_CONTRIBUTION_CREATE'
export { EVENT_ADMIN_CONTRIBUTION_DELETE } from './EVENT_ADMIN_CONTRIBUTION_DELETE'
export { EVENT_ADMIN_CONTRIBUTION_DENY } from './EVENT_ADMIN_CONTRIBUTION_DENY'
export { EVENT_ADMIN_CONTRIBUTION_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_UPDATE'
export { EVENT_ADMIN_CONTRIBUTION_LINK_CREATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_CREATE'
export { EVENT_ADMIN_CONTRIBUTION_LINK_DELETE } from './EVENT_ADMIN_CONTRIBUTION_LINK_DELETE'
export { EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE'
export { EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE'
export { EVENT_ADMIN_USER_DELETE } from './EVENT_ADMIN_USER_DELETE'
export { EVENT_ADMIN_USER_UNDELETE } from './EVENT_ADMIN_USER_UNDELETE'
export { EVENT_ADMIN_USER_ROLE_SET } from './EVENT_ADMIN_USER_ROLE_SET'
export { EVENT_CONTRIBUTION_CREATE } from './EVENT_CONTRIBUTION_CREATE'
export { EVENT_CONTRIBUTION_DELETE } from './EVENT_CONTRIBUTION_DELETE'
export { EVENT_CONTRIBUTION_UPDATE } from './EVENT_CONTRIBUTION_UPDATE'
export { EVENT_CONTRIBUTION_MESSAGE_CREATE } from './EVENT_CONTRIBUTION_MESSAGE_CREATE'
export { EVENT_CONTRIBUTION_LINK_REDEEM } from './EVENT_CONTRIBUTION_LINK_REDEEM'
export { EVENT_EMAIL_ACCOUNT_MULTIREGISTRATION } from './EVENT_EMAIL_ACCOUNT_MULTIREGISTRATION'
export { EVENT_EMAIL_ADMIN_CONFIRMATION } from './EVENT_EMAIL_ADMIN_CONFIRMATION'
export { EVENT_EMAIL_CONFIRMATION } from './EVENT_EMAIL_CONFIRMATION'
export { EVENT_EMAIL_FORGOT_PASSWORD } from './EVENT_EMAIL_FORGOT_PASSWORD'
export { EVENT_TRANSACTION_SEND } from './EVENT_TRANSACTION_SEND'
export { EVENT_TRANSACTION_RECEIVE } from './EVENT_TRANSACTION_RECEIVE'
export { EVENT_TRANSACTION_LINK_CREATE } from './EVENT_TRANSACTION_LINK_CREATE'
export { EVENT_TRANSACTION_LINK_DELETE } from './EVENT_TRANSACTION_LINK_DELETE'
export { EVENT_TRANSACTION_LINK_REDEEM } from './EVENT_TRANSACTION_LINK_REDEEM'
export { EVENT_USER_ACTIVATE_ACCOUNT } from './EVENT_USER_ACTIVATE_ACCOUNT'
export { EVENT_USER_INFO_UPDATE } from './EVENT_USER_INFO_UPDATE'
export { EVENT_USER_LOGIN } from './EVENT_USER_LOGIN'
export { EVENT_USER_LOGOUT } from './EVENT_USER_LOGOUT'
export { EVENT_USER_REGISTER } from './EVENT_USER_REGISTER'

View File

@ -5,7 +5,7 @@ import { Community as DbCommunity } from '@entity/Community'
import { gql } from 'graphql-request'
import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
export async function requestGetPublicKey(dbCom: DbCommunity): Promise<string | undefined> {

View File

@ -5,7 +5,7 @@ import { Community as DbCommunity } from '@entity/Community'
import { gql } from 'graphql-request'
import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
export async function requestGetPublicKey(dbCom: DbCommunity): Promise<string | undefined> {

View File

@ -1,7 +1,7 @@
import { IsNull } from '@dbTools/typeorm'
import { Community as DbCommunity } from '@entity/Community'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
// eslint-disable-next-line camelcase

View File

@ -3,7 +3,7 @@ import { ArgsType, Field, InputType } from 'type-graphql'
@InputType()
@ArgsType()
export default class AdminCreateContributionArgs {
export class AdminCreateContributionArgs {
@Field(() => String)
email: string

View File

@ -2,7 +2,7 @@ import { Decimal } from 'decimal.js-light'
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType()
export default class AdminUpdateContributionArgs {
export class AdminUpdateContributionArgs {
@Field(() => Int)
id: number

View File

@ -3,7 +3,7 @@ import { ArgsType, Field, InputType } from 'type-graphql'
@InputType()
@ArgsType()
export default class ContributionArgs {
export class ContributionArgs {
@Field(() => Decimal)
amount: Decimal

View File

@ -2,7 +2,7 @@ import { Decimal } from 'decimal.js-light'
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType()
export default class ContributionLinkArgs {
export class ContributionLinkArgs {
@Field(() => Decimal)
amount: Decimal

View File

@ -2,7 +2,7 @@ import { ArgsType, Field, Int, InputType } from 'type-graphql'
@InputType()
@ArgsType()
export default class ContributionMessageArgs {
export class ContributionMessageArgs {
@Field(() => Int)
contributionId: number

View File

@ -1,7 +1,7 @@
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType()
export default class CreateUserArgs {
export class CreateUserArgs {
@Field(() => String)
email: string

View File

@ -4,7 +4,7 @@ import { ArgsType, Field, Int } from 'type-graphql'
import { Order } from '@enum/Order'
@ArgsType()
export default class Paginated {
export class Paginated {
@Field(() => Int, { nullable: true })
currentPage?: number

View File

@ -1,9 +1,9 @@
import { ArgsType, Field, Int } from 'type-graphql'
import SearchUsersFilters from '@arg/SearchUsersFilters'
import { SearchUsersFilters } from '@arg/SearchUsersFilters'
@ArgsType()
export default class SearchUsersArgs {
export class SearchUsersArgs {
@Field(() => String)
searchText: string

View File

@ -1,7 +1,7 @@
import { Field, InputType } from 'type-graphql'
@InputType()
export default class SearchUsersFilters {
export class SearchUsersFilters {
@Field(() => Boolean, { nullable: true, defaultValue: null })
byActivated?: boolean | null

View File

@ -2,7 +2,7 @@ import { Decimal } from 'decimal.js-light'
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export default class TransactionLinkArgs {
export class TransactionLinkArgs {
@Field(() => Decimal)
amount: Decimal

View File

@ -2,7 +2,7 @@
import { Field, InputType } from 'type-graphql'
@InputType()
export default class TransactionLinkFilters {
export class TransactionLinkFilters {
@Field(() => Boolean, { nullable: true })
withDeleted?: boolean

View File

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

View File

@ -1,7 +1,7 @@
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType()
export default class UnsecureLoginArgs {
export class UnsecureLoginArgs {
@Field(() => String)
email: string

View File

@ -1,7 +1,7 @@
import { ArgsType, Field, Int } from 'type-graphql'
@ArgsType()
export default class UpdateUserInfosArgs {
export class UpdateUserInfosArgs {
@Field({ nullable: true })
firstName?: string

View File

@ -10,9 +10,9 @@ import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
import { decode, encode } from '@/auth/JWT'
import { RIGHTS } from '@/auth/RIGHTS'
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
export const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
context.role = ROLE_UNAUTHORIZED // unauthorized user
// is rights an inalienable right?
@ -56,5 +56,3 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
context.setHeaders.push({ key: 'token', value: encode(decoded.gradidoID) })
return true
}
export default isAuthorized

View File

@ -8,9 +8,7 @@ export class Community {
this.foreign = dbCom.foreign
this.publicKey = dbCom.publicKey.toString()
this.url =
(dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/') +
'api/' +
dbCom.apiVersion
(dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/') + dbCom.apiVersion
this.lastAnnouncedAt = dbCom.lastAnnouncedAt
this.verifiedAt = dbCom.verifiedAt
this.lastErrorAt = dbCom.lastErrorAt

View File

@ -2,7 +2,7 @@ import { ContributionLink as dbContributionLink } from '@entity/ContributionLink
import { Decimal } from 'decimal.js-light'
import { ObjectType, Field, Int } from 'type-graphql'
import CONFIG from '@/config'
import { CONFIG } from '@/config'
@ObjectType()
export class ContributionLink {

View File

@ -47,6 +47,10 @@ export class Transaction {
this.linkId = transaction.contribution
? transaction.contribution.contributionLinkId
: transaction.transactionLinkId || null
this.previousBalance =
(transaction.previousTransaction &&
transaction.previousTransaction.balance.toDecimalPlaces(2, Decimal.ROUND_DOWN)) ||
new Decimal(0)
}
@Field(() => Int)
@ -70,6 +74,9 @@ export class Transaction {
@Field(() => Date)
balanceDate: Date
@Field(() => Decimal)
previousBalance: Decimal
@Field(() => Decay)
decay: Decay

View File

@ -2,7 +2,7 @@ import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
import { Decimal } from 'decimal.js-light'
import { ObjectType, Field, Int } from 'type-graphql'
import CONFIG from '@/config'
import { CONFIG } from '@/config'
import { User } from './User'

View File

@ -34,6 +34,7 @@ describe('CommunityResolver', () => {
let foreignCom1: DbCommunity
let foreignCom2: DbCommunity
let foreignCom3: DbCommunity
describe('with empty list', () => {
it('returns no community entry', async () => {
// const result: Community[] = await query({ query: getCommunities })
@ -54,7 +55,7 @@ describe('CommunityResolver', () => {
homeCom1.foreign = false
homeCom1.publicKey = Buffer.from('publicKey-HomeCommunity')
homeCom1.apiVersion = '1_0'
homeCom1.endPoint = 'http://localhost'
homeCom1.endPoint = 'http://localhost/api'
homeCom1.createdAt = new Date()
await DbCommunity.insert(homeCom1)
@ -62,7 +63,7 @@ describe('CommunityResolver', () => {
homeCom2.foreign = false
homeCom2.publicKey = Buffer.from('publicKey-HomeCommunity')
homeCom2.apiVersion = '1_1'
homeCom2.endPoint = 'http://localhost'
homeCom2.endPoint = 'http://localhost/api'
homeCom2.createdAt = new Date()
await DbCommunity.insert(homeCom2)
@ -70,24 +71,24 @@ describe('CommunityResolver', () => {
homeCom3.foreign = false
homeCom3.publicKey = Buffer.from('publicKey-HomeCommunity')
homeCom3.apiVersion = '2_0'
homeCom3.endPoint = 'http://localhost'
homeCom3.endPoint = 'http://localhost/api'
homeCom3.createdAt = new Date()
await DbCommunity.insert(homeCom3)
})
it('returns three home-community entries', async () => {
it('returns 3 home-community entries', async () => {
await expect(query({ query: getCommunities })).resolves.toMatchObject({
data: {
getCommunities: [
{
id: 1,
foreign: homeCom1.foreign,
id: 3,
foreign: homeCom3.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
url: expect.stringMatching('http://localhost/api/1_0'),
url: expect.stringMatching('http://localhost/api/2_0'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: homeCom1.createdAt.toISOString(),
createdAt: homeCom3.createdAt.toISOString(),
updatedAt: null,
},
{
@ -102,14 +103,14 @@ describe('CommunityResolver', () => {
updatedAt: null,
},
{
id: 3,
foreign: homeCom3.foreign,
id: 1,
foreign: homeCom1.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
url: expect.stringMatching('http://localhost/api/2_0'),
url: expect.stringMatching('http://localhost/api/1_0'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: homeCom3.createdAt.toISOString(),
createdAt: homeCom1.createdAt.toISOString(),
updatedAt: null,
},
],
@ -126,7 +127,7 @@ describe('CommunityResolver', () => {
foreignCom1.foreign = true
foreignCom1.publicKey = Buffer.from('publicKey-ForeignCommunity')
foreignCom1.apiVersion = '1_0'
foreignCom1.endPoint = 'http://remotehost'
foreignCom1.endPoint = 'http://remotehost/api'
foreignCom1.createdAt = new Date()
await DbCommunity.insert(foreignCom1)
@ -134,7 +135,7 @@ describe('CommunityResolver', () => {
foreignCom2.foreign = true
foreignCom2.publicKey = Buffer.from('publicKey-ForeignCommunity')
foreignCom2.apiVersion = '1_1'
foreignCom2.endPoint = 'http://remotehost'
foreignCom2.endPoint = 'http://remotehost/api'
foreignCom2.createdAt = new Date()
await DbCommunity.insert(foreignCom2)
@ -142,24 +143,24 @@ describe('CommunityResolver', () => {
foreignCom3.foreign = true
foreignCom3.publicKey = Buffer.from('publicKey-ForeignCommunity')
foreignCom3.apiVersion = '1_2'
foreignCom3.endPoint = 'http://remotehost'
foreignCom3.endPoint = 'http://remotehost/api'
foreignCom3.createdAt = new Date()
await DbCommunity.insert(foreignCom3)
})
it('returns 3x home and 3x foreign-community entries', async () => {
it('returns 3 home community and 3 foreign community entries', async () => {
await expect(query({ query: getCommunities })).resolves.toMatchObject({
data: {
getCommunities: [
{
id: 1,
foreign: homeCom1.foreign,
id: 3,
foreign: homeCom3.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
url: expect.stringMatching('http://localhost/api/1_0'),
url: expect.stringMatching('http://localhost/api/2_0'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: homeCom1.createdAt.toISOString(),
createdAt: homeCom3.createdAt.toISOString(),
updatedAt: null,
},
{
@ -174,25 +175,25 @@ describe('CommunityResolver', () => {
updatedAt: null,
},
{
id: 3,
foreign: homeCom3.foreign,
id: 1,
foreign: homeCom1.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
url: expect.stringMatching('http://localhost/api/2_0'),
url: expect.stringMatching('http://localhost/api/1_0'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: homeCom3.createdAt.toISOString(),
createdAt: homeCom1.createdAt.toISOString(),
updatedAt: null,
},
{
id: 4,
foreign: foreignCom1.foreign,
id: 6,
foreign: foreignCom3.foreign,
publicKey: expect.stringMatching('publicKey-ForeignCommunity'),
url: expect.stringMatching('http://remotehost/api/1_0'),
url: expect.stringMatching('http://remotehost/api/1_2'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: foreignCom1.createdAt.toISOString(),
createdAt: foreignCom3.createdAt.toISOString(),
updatedAt: null,
},
{
@ -207,14 +208,14 @@ describe('CommunityResolver', () => {
updatedAt: null,
},
{
id: 6,
foreign: foreignCom3.foreign,
id: 4,
foreign: foreignCom1.foreign,
publicKey: expect.stringMatching('publicKey-ForeignCommunity'),
url: expect.stringMatching('http://remotehost/api/1_2'),
url: expect.stringMatching('http://remotehost/api/1_0'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: foreignCom3.createdAt.toISOString(),
createdAt: foreignCom1.createdAt.toISOString(),
updatedAt: null,
},
],

View File

@ -11,7 +11,11 @@ export class CommunityResolver {
@Query(() => [Community])
async getCommunities(): Promise<Community[]> {
const dbCommunities: DbCommunity[] = await DbCommunity.find({
order: { foreign: 'ASC', publicKey: 'ASC', apiVersion: 'ASC' },
order: {
foreign: 'ASC',
createdAt: 'DESC',
lastAnnouncedAt: 'DESC',
},
})
return dbCommunities.map((dbCom: DbCommunity) => new Community(dbCom))
}

View File

@ -10,7 +10,7 @@ import { GraphQLError } from 'graphql'
import { cleanDB, testEnvironment, resetToken } from '@test/helpers'
import { logger } from '@test/testSetup'
import { EventType } from '@/event/Event'
import { EventType } from '@/event/Events'
import { userFactory } from '@/seeds/factory/user'
import {
login,

View File

@ -3,9 +3,8 @@ import { ContributionLink as DbContributionLink } from '@entity/ContributionLink
import { Decimal } from 'decimal.js-light'
import { Resolver, Args, Arg, Authorized, Mutation, Query, Int, Ctx } from 'type-graphql'
// TODO: this is a strange construct
import ContributionLinkArgs from '@arg/ContributionLinkArgs'
import Paginated from '@arg/Paginated'
import { ContributionLinkArgs } from '@arg/ContributionLinkArgs'
import { Paginated } from '@arg/Paginated'
import { Order } from '@enum/Order'
import { ContributionLink } from '@model/ContributionLink'
import { ContributionLinkList } from '@model/ContributionLinkList'
@ -15,9 +14,9 @@ import {
EVENT_ADMIN_CONTRIBUTION_LINK_CREATE,
EVENT_ADMIN_CONTRIBUTION_LINK_DELETE,
EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE,
} from '@/event/Event'
} from '@/event/Events'
import { Context, getUser } from '@/server/context'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
import {
CONTRIBUTIONLINK_NAME_MAX_CHARS,

View File

@ -12,7 +12,7 @@ import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
import { logger, i18n as localization } from '@test/testSetup'
import { sendAddedContributionMessageEmail } from '@/emails/sendEmailVariants'
import { EventType } from '@/event/Event'
import { EventType } from '@/event/Events'
import { userFactory } from '@/seeds/factory/user'
import {
adminCreateContributionMessage,

View File

@ -6,8 +6,8 @@ import { User as DbUser } from '@entity/User'
import { UserContact as DbUserContact } from '@entity/UserContact'
import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'
import ContributionMessageArgs from '@arg/ContributionMessageArgs'
import Paginated from '@arg/Paginated'
import { ContributionMessageArgs } from '@arg/ContributionMessageArgs'
import { Paginated } from '@arg/Paginated'
import { ContributionStatus } from '@enum/ContributionStatus'
import { ContributionMessageType } from '@enum/MessageType'
import { Order } from '@enum/Order'
@ -18,9 +18,9 @@ import { sendAddedContributionMessageEmail } from '@/emails/sendEmailVariants'
import {
EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE,
EVENT_CONTRIBUTION_MESSAGE_CREATE,
} from '@/event/Event'
} from '@/event/Events'
import { Context, getUser } from '@/server/context'
import LogError from '@/server/LogError'
import { LogError } from '@/server/LogError'
@Resolver()
export class ContributionMessageResolver {
@ -146,7 +146,7 @@ export class ContributionMessageResolver {
await queryRunner.manager.update(DbContribution, { id: contributionId }, contribution)
}
await sendAddedContributionMessageEmail({
void sendAddedContributionMessageEmail({
firstName: contribution.user.firstName,
lastName: contribution.user.lastName,
email: contribution.user.emailContact.email,

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