mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
Merge remote-tracking branch 'origin/master' into
2956-feature-x-com-4-introduce-public-community-info-handshake
This commit is contained in:
commit
e75d2b89cf
37
.github/dependabot_backend.yml
vendored
Normal file
37
.github/dependabot_backend.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/backend"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
labels:
|
||||
- "devops"
|
||||
- "service:backend"
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: "/backend"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
labels:
|
||||
- "devops"
|
||||
- "service:docker"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
rebase-strategy: "disabled"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: "saturday"
|
||||
timezone: "Europe/Berlin"
|
||||
time: "03:00"
|
||||
labels:
|
||||
- "devops"
|
||||
3
.github/file-filters.yml
vendored
3
.github/file-filters.yml
vendored
@ -36,6 +36,9 @@ backend: &backend
|
||||
dht_node: &dht_node
|
||||
- 'dht-node/**/*'
|
||||
|
||||
dlt_connector: &dlt_connector
|
||||
- 'dlt-connector/**/*'
|
||||
|
||||
docker-compose: &docker-compose
|
||||
- 'docker-compose.*'
|
||||
|
||||
|
||||
2
.github/workflows/lint_pr.yml
vendored
2
.github/workflows/lint_pr.yml
vendored
@ -29,6 +29,8 @@ jobs:
|
||||
database
|
||||
release
|
||||
federation
|
||||
dht
|
||||
dlt
|
||||
workflow
|
||||
docker
|
||||
other
|
||||
|
||||
2
.github/workflows/test_dht_node.yml
vendored
2
.github/workflows/test_dht_node.yml
vendored
@ -51,7 +51,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Lint
|
||||
run: cd dht-node && yarn && yarn run lint
|
||||
run: cd database && yarn && cd ../dht-node && yarn && yarn run lint
|
||||
|
||||
unit_test:
|
||||
name: Unit Tests - DHT Node
|
||||
|
||||
74
.github/workflows/test_dlt_connector.yml
vendored
Normal file
74
.github/workflows/test_dlt_connector.yml
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
name: Gradido DLT Connector Test CI
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Detect File Changes - DLT Connector
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
dlt_connector: ${{ steps.changes.outputs.dlt_connector }}
|
||||
docker-compose: ${{ steps.changes.outputs.docker-compose }}
|
||||
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:
|
||||
name: Docker Build Test - DLT Connector
|
||||
if: needs.files-changed.outputs.dlt_connector == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build 'test' image
|
||||
run: |
|
||||
docker build --target test -t "gradido/dlt-connector:test" -f dlt-connector/Dockerfile .
|
||||
docker save "gradido/dlt-connector:test" > /tmp/dlt-connector.tar
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docker-dlt-connector-test
|
||||
path: /tmp/dlt-connector.tar
|
||||
|
||||
lint:
|
||||
name: Lint - DLT Connector
|
||||
if: needs.files-changed.outputs.dlt_connector == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Lint
|
||||
run: cd dlt-connector && yarn && yarn run lint
|
||||
|
||||
unit_test:
|
||||
name: Unit Tests - DLT Connector
|
||||
if: needs.files-changed.outputs.dlt_connector == 'true' || needs.files-changed.outputs.docker-compose == 'true'
|
||||
needs: [files-changed, build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Download Docker Image
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-dlt-connector-test
|
||||
path: /tmp
|
||||
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/dlt-connector.tar
|
||||
|
||||
- name: Unit tests
|
||||
run: docker run --env NODE_ENV=test --rm gradido/dlt-connector:test yarn run test
|
||||
30
.github/workflows/test_e2e.yml
vendored
30
.github/workflows/test_e2e.yml
vendored
@ -33,7 +33,6 @@ jobs:
|
||||
yarn && yarn dev_reset
|
||||
cd ../backend
|
||||
yarn && yarn seed
|
||||
cd ..
|
||||
|
||||
- name: Boot up test system | docker-compose frontends
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps frontend admin nginx
|
||||
@ -41,18 +40,35 @@ jobs:
|
||||
- name: Boot up test system | docker-compose mailserver
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mailserver
|
||||
|
||||
- name: Sleep for 15 seconds
|
||||
run: sleep 15s
|
||||
- name: End-to-end tests | prepare
|
||||
run: |
|
||||
wget --no-verbose -O /opt/cucumber-json-formatter "https://github.com/cucumber/json-formatter/releases/download/v19.0.0/cucumber-json-formatter-linux-386"
|
||||
chmod +x /opt/cucumber-json-formatter
|
||||
sudo ln -fs /opt/cucumber-json-formatter /usr/bin/cucumber-json-formatter
|
||||
cd e2e-tests/
|
||||
yarn
|
||||
|
||||
- name: End-to-end tests | run tests
|
||||
id: e2e-tests
|
||||
run: |
|
||||
cd e2e-tests/
|
||||
yarn
|
||||
yarn run cypress run
|
||||
- name: End-to-end tests | if tests failed, upload screenshots
|
||||
|
||||
- name: End-to-end tests | if tests failed, compile html report
|
||||
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
|
||||
run: |
|
||||
cd e2e-tests/
|
||||
node create-cucumber-html-report.js
|
||||
|
||||
- name: End-to-end tests | if tests failed, get pr number
|
||||
id: pr
|
||||
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
|
||||
uses: 8BitJonny/gh-get-current-pr@2.2.0
|
||||
|
||||
- name: End-to-end tests | if tests failed, upload report
|
||||
id: e2e-report
|
||||
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cypress-screenshots
|
||||
path: /home/runner/work/gradido/gradido/e2e-tests/cypress/screenshots/
|
||||
name: cypress-report-pr-#${{ steps.pr.outputs.number }}
|
||||
path: /home/runner/work/gradido/gradido/e2e-tests/cypress/reports/cucumber_html_report
|
||||
|
||||
121
CHANGELOG.md
121
CHANGELOG.md
@ -4,8 +4,129 @@ 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.23.2](https://github.com/gradido/gradido/compare/1.23.1...1.23.2)
|
||||
|
||||
- feat(admin): contribution filtering by memo [`#3174`](https://github.com/gradido/gradido/pull/3174)
|
||||
- feat(backend): after transaction creations trigger to send them to dlt-connector [`#3152`](https://github.com/gradido/gradido/pull/3152)
|
||||
|
||||
#### [1.23.1](https://github.com/gradido/gradido/compare/1.23.0...1.23.1)
|
||||
|
||||
> 10 August 2023
|
||||
|
||||
- chore(release): v1.23.1 [`#3173`](https://github.com/gradido/gradido/pull/3173)
|
||||
- feat(dlt): add dlt-connector to release-script [`#3170`](https://github.com/gradido/gradido/pull/3170)
|
||||
- fix(backend): update logfiles clearance-script [`#3168`](https://github.com/gradido/gradido/pull/3168)
|
||||
- fix(backend): too much logoutput on production [`#3160`](https://github.com/gradido/gradido/pull/3160)
|
||||
- perf(backend): enable logfile compression [`#3158`](https://github.com/gradido/gradido/pull/3158)
|
||||
- feat(other): setup dependabot for backend package updates [`#3156`](https://github.com/gradido/gradido/pull/3156)
|
||||
|
||||
#### [1.23.0](https://github.com/gradido/gradido/compare/1.22.3...1.23.0)
|
||||
|
||||
> 25 July 2023
|
||||
|
||||
- chore(release): v1.23.0 [`#3157`](https://github.com/gradido/gradido/pull/3157)
|
||||
- refactor(frontend): add contribution by link information to locales [`#3144`](https://github.com/gradido/gradido/pull/3144)
|
||||
- fix(admin): user role in admin interface [`#3153`](https://github.com/gradido/gradido/pull/3153)
|
||||
- feat(backend): 3030 feature role administration backend [`#3074`](https://github.com/gradido/gradido/pull/3074)
|
||||
- feat(other): iota-tangle-connector sending transaction [`#3132`](https://github.com/gradido/gradido/pull/3132)
|
||||
- feat(other): proper reporting for failing end-to-end tests [`#3096`](https://github.com/gradido/gradido/pull/3096)
|
||||
- fix(other): add missing volume for dlt-connector dev docker [`#3134`](https://github.com/gradido/gradido/pull/3134)
|
||||
- fix(backend): semaphore parallel redeemTransactionLink test [`#3133`](https://github.com/gradido/gradido/pull/3133)
|
||||
- feat(other): iota-tangle-connector mit Hello World Message als separates Modul [`#3118`](https://github.com/gradido/gradido/pull/3118)
|
||||
- refactor(database): fix database public key lengths [`#3026`](https://github.com/gradido/gradido/pull/3026)
|
||||
- refactor(dht): eslint dht import [`#3044`](https://github.com/gradido/gradido/pull/3044)
|
||||
|
||||
#### [1.22.3](https://github.com/gradido/gradido/compare/1.22.2...1.22.3)
|
||||
|
||||
> 6 July 2023
|
||||
|
||||
- chore(release): v1.22.3 [`#3131`](https://github.com/gradido/gradido/pull/3131)
|
||||
- fix(backend): corrected email-link [`#3129`](https://github.com/gradido/gradido/pull/3129)
|
||||
|
||||
#### [1.22.2](https://github.com/gradido/gradido/compare/1.22.1...1.22.2)
|
||||
|
||||
> 6 July 2023
|
||||
|
||||
- chore(release): v1.22.2 [`#3127`](https://github.com/gradido/gradido/pull/3127)
|
||||
- fix(backend): moderation message are completely hidden from the user [`#3123`](https://github.com/gradido/gradido/pull/3123)
|
||||
- fix(frontend): properly save username, do not allow to edit it again [`#3124`](https://github.com/gradido/gradido/pull/3124)
|
||||
- fix(frontend): fix German "Speichern" to have capital letter [`#3122`](https://github.com/gradido/gradido/pull/3122)
|
||||
|
||||
#### [1.22.1](https://github.com/gradido/gradido/compare/1.22.0...1.22.1)
|
||||
|
||||
> 4 July 2023
|
||||
|
||||
- chore(release): v1.22.1 [`#3117`](https://github.com/gradido/gradido/pull/3117)
|
||||
- fix(backend): use base url from config in email templates [`#3114`](https://github.com/gradido/gradido/pull/3114)
|
||||
- feat(frontend): test right side layout template [`#3052`](https://github.com/gradido/gradido/pull/3052)
|
||||
- feat(backend): remove iota from backend [`#3115`](https://github.com/gradido/gradido/pull/3115)
|
||||
- refactor(frontend): text juni to juli [`#3116`](https://github.com/gradido/gradido/pull/3116)
|
||||
- refactor(frontend): refactor changes incorporated [`#3113`](https://github.com/gradido/gradido/pull/3113)
|
||||
- refactor(frontend): date from deploy changed to infotext [`#3108`](https://github.com/gradido/gradido/pull/3108)
|
||||
- fix(frontend): add alias to the verifyLogin GraphQL answer. [`#3107`](https://github.com/gradido/gradido/pull/3107)
|
||||
- fix(backend): moderator message don't send email to user [`#3106`](https://github.com/gradido/gradido/pull/3106)
|
||||
|
||||
#### [1.22.0](https://github.com/gradido/gradido/compare/1.21.0...1.22.0)
|
||||
|
||||
> 30 June 2023
|
||||
|
||||
- chore(release): v1.22.0 [`#3101`](https://github.com/gradido/gradido/pull/3101)
|
||||
- fix(backend): yarn.lock after typeorm update [`#3097`](https://github.com/gradido/gradido/pull/3097)
|
||||
- feat(frontend): new style for settings page [`#3040`](https://github.com/gradido/gradido/pull/3040)
|
||||
- feat(admin): query users on contributions [`#3094`](https://github.com/gradido/gradido/pull/3094)
|
||||
- feat(backend): user query on find contributions [`#3091`](https://github.com/gradido/gradido/pull/3091)
|
||||
- fix(backend): double redeem transaction link [`#3093`](https://github.com/gradido/gradido/pull/3093)
|
||||
- feat(admin): message type admin frontend [`#3073`](https://github.com/gradido/gradido/pull/3073)
|
||||
- feat(other): end-to-end test scenarios for deleted and not registered user [`#3077`](https://github.com/gradido/gradido/pull/3077)
|
||||
- feat(database): update typeorm [`#3078`](https://github.com/gradido/gradido/pull/3078)
|
||||
- refactor(other): disable cypress test retries [`#3092`](https://github.com/gradido/gradido/pull/3092)
|
||||
- test(other): update cypress [`#3056`](https://github.com/gradido/gradido/pull/3056)
|
||||
- feat(other): add definition of cron-job for klicktipp export. [`#3051`](https://github.com/gradido/gradido/pull/3051)
|
||||
- fix(backend): forget password not for deleted users [`#3090`](https://github.com/gradido/gradido/pull/3090)
|
||||
- fix(backend): gdt server error & gdt refetch policy [`#3086`](https://github.com/gradido/gradido/pull/3086)
|
||||
- feat(backend): contribution message type moderator [`#3072`](https://github.com/gradido/gradido/pull/3072)
|
||||
- fix(other): linting in e2e tests directory does not work [`#3089`](https://github.com/gradido/gradido/pull/3089)
|
||||
- feat(other): end-to-end test feature send coins [`#3070`](https://github.com/gradido/gradido/pull/3070)
|
||||
- refactor(dht): move typescript related packages in dev Dependencies [`#3085`](https://github.com/gradido/gradido/pull/3085)
|
||||
- fix(backend): jest environment [`#3084`](https://github.com/gradido/gradido/pull/3084)
|
||||
- feat(frontend): transaction list page change style for small device [`#3081`](https://github.com/gradido/gradido/pull/3081)
|
||||
- refactor(other): refactor state to status [`#3082`](https://github.com/gradido/gradido/pull/3082)
|
||||
- feat(admin): change deny contribution btn-warning color and background-color to #e1a908 [`#3080`](https://github.com/gradido/gradido/pull/3080)
|
||||
- feat(backend): bootstrap and Hello World Test [`#3041`](https://github.com/gradido/gradido/pull/3041)
|
||||
- feat(frontend): change the info text on the start page [`#3049`](https://github.com/gradido/gradido/pull/3049)
|
||||
- refactor(dht): eslint dht n [`#3045`](https://github.com/gradido/gradido/pull/3045)
|
||||
- refactor(dht): eslint dht comments [`#3046`](https://github.com/gradido/gradido/pull/3046)
|
||||
- fix(frontend): auto logout messages autohide time 5000 [`#2959`](https://github.com/gradido/gradido/pull/2959)
|
||||
- feat(federation): introduce private key in community table [`#3024`](https://github.com/gradido/gradido/pull/3024)
|
||||
- refactor(backend): removed klicktipp middleware and replace it with a function [`#3035`](https://github.com/gradido/gradido/pull/3035)
|
||||
- feat(admin): order user search desc, fix pagination [`#3059`](https://github.com/gradido/gradido/pull/3059)
|
||||
- refactor(database): eslint database eslint comments [`#3039`](https://github.com/gradido/gradido/pull/3039)
|
||||
- refactor(database): eslint database import [`#3038`](https://github.com/gradido/gradido/pull/3038)
|
||||
- feat(backend): apply design template to HTML e-mails [`#2938`](https://github.com/gradido/gradido/pull/2938)
|
||||
- refactor(database): eslint database n [`#3037`](https://github.com/gradido/gradido/pull/3037)
|
||||
- refactor(backend): sodium native with types [`#3032`](https://github.com/gradido/gradido/pull/3032)
|
||||
- refactor(federation): expose private key to write home community [`#3023`](https://github.com/gradido/gradido/pull/3023)
|
||||
- refactor(dht): eslint dht base configuration [`#3042`](https://github.com/gradido/gradido/pull/3042)
|
||||
- refactor(federation): eslint federation base configuration [`#3047`](https://github.com/gradido/gradido/pull/3047)
|
||||
- refactor(database): eslint database base configuration [`#3036`](https://github.com/gradido/gradido/pull/3036)
|
||||
- feat(workflow): dht as package for lint pr workflow [`#3043`](https://github.com/gradido/gradido/pull/3043)
|
||||
- refactor(federation): removed (potential) duplicate db query [`#3019`](https://github.com/gradido/gradido/pull/3019)
|
||||
- refactor(federation): remove function getSeed [`#3022`](https://github.com/gradido/gradido/pull/3022)
|
||||
- refactor(federation): refactor federation to use inheritance [`#2992`](https://github.com/gradido/gradido/pull/2992)
|
||||
- refactor(backend): random-bigint types [`#3033`](https://github.com/gradido/gradido/pull/3033)
|
||||
- fix(frontend): incorrect errormessage for wrong contribution link [`#2958`](https://github.com/gradido/gradido/pull/2958)
|
||||
- refactor(federation): remove export from CommunityApi [`#3021`](https://github.com/gradido/gradido/pull/3021)
|
||||
- refactor(federation): simplify newCommunityUuid [`#3020`](https://github.com/gradido/gradido/pull/3020)
|
||||
- refactor(database): remove duplicate comments [`#3025`](https://github.com/gradido/gradido/pull/3025)
|
||||
- feat(federation): x com 3 introduce business communities [`#2955`](https://github.com/gradido/gradido/pull/2955)
|
||||
- refactor(backend): remove to do after creating issue [`#3000`](https://github.com/gradido/gradido/pull/3000)
|
||||
- refactor(backend): camelcase exception for FederationClient_XX_X [`#3016`](https://github.com/gradido/gradido/pull/3016)
|
||||
|
||||
#### [1.21.0](https://github.com/gradido/gradido/compare/1.20.0...1.21.0)
|
||||
|
||||
> 19 May 2023
|
||||
|
||||
- chore(release): v1.21.0 [`#2998`](https://github.com/gradido/gradido/pull/2998)
|
||||
- feat(frontend): preserve email after login [`#2994`](https://github.com/gradido/gradido/pull/2994)
|
||||
- feat(frontend): send coins via identifier [`#2989`](https://github.com/gradido/gradido/pull/2989)
|
||||
- feat(backend): export user events to klicktipp [`#2916`](https://github.com/gradido/gradido/pull/2916)
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Administraion Interface for Gradido",
|
||||
"main": "index.js",
|
||||
"author": "Moriz Wahl",
|
||||
"version": "1.21.0",
|
||||
"version": "1.23.2",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
|
||||
@ -21,6 +21,7 @@ const mocks = {
|
||||
moderator: {
|
||||
id: 0,
|
||||
name: 'test moderator',
|
||||
roles: ['ADMIN'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -45,7 +46,7 @@ describe('ChangeUserRoleFormular', () => {
|
||||
propsData = {
|
||||
item: {
|
||||
userId: 1,
|
||||
isAdmin: null,
|
||||
roles: [],
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
@ -61,7 +62,7 @@ describe('ChangeUserRoleFormular', () => {
|
||||
propsData = {
|
||||
item: {
|
||||
userId: 0,
|
||||
isAdmin: null,
|
||||
roles: ['ADMIN'],
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
@ -88,7 +89,7 @@ describe('ChangeUserRoleFormular', () => {
|
||||
propsData = {
|
||||
item: {
|
||||
userId: 1,
|
||||
isAdmin: null,
|
||||
roles: [],
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
@ -120,13 +121,13 @@ describe('ChangeUserRoleFormular', () => {
|
||||
beforeEach(() => {
|
||||
apolloMutateMock.mockResolvedValue({
|
||||
data: {
|
||||
setUserRole: new Date(),
|
||||
setUserRole: 'ADMIN',
|
||||
},
|
||||
})
|
||||
propsData = {
|
||||
item: {
|
||||
userId: 1,
|
||||
isAdmin: null,
|
||||
roles: ['USER'],
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
@ -134,7 +135,7 @@ describe('ChangeUserRoleFormular', () => {
|
||||
})
|
||||
|
||||
it('has selected option set to "usual user"', () => {
|
||||
expect(wrapper.find('select.role-select').element.value).toBe('user')
|
||||
expect(wrapper.find('select.role-select').element.value).toBe('USER')
|
||||
})
|
||||
|
||||
describe('change select to', () => {
|
||||
@ -149,7 +150,7 @@ describe('ChangeUserRoleFormular', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('new role', () => {
|
||||
describe('new role "MODERATOR"', () => {
|
||||
beforeEach(() => {
|
||||
rolesToSelect.at(1).setSelected()
|
||||
})
|
||||
@ -181,19 +182,267 @@ describe('ChangeUserRoleFormular', () => {
|
||||
mutation: setUserRole,
|
||||
variables: {
|
||||
userId: 1,
|
||||
isAdmin: true,
|
||||
role: 'MODERATOR',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('emits "updateIsAdmin"', () => {
|
||||
expect(wrapper.emitted('updateIsAdmin')).toEqual(
|
||||
it('emits "updateRoles" with role moderator', () => {
|
||||
expect(wrapper.emitted('updateRoles')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
{
|
||||
userId: 1,
|
||||
isAdmin: expect.any(Date),
|
||||
roles: ['MODERATOR'],
|
||||
},
|
||||
]),
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it('toasts success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm role change with error', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
apolloMutateMock.mockRejectedValue({ message: 'Oh no!' })
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Oh no!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('new role "ADMIN"', () => {
|
||||
beforeEach(() => {
|
||||
rolesToSelect.at(2).setSelected()
|
||||
})
|
||||
|
||||
it('has "change_user_role" button enabled', () => {
|
||||
expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true)
|
||||
expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(
|
||||
false,
|
||||
)
|
||||
})
|
||||
|
||||
describe('clicking the "change_user_role" button', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve(true))
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('calls the modal', () => {
|
||||
expect(wrapper.emitted('showModal'))
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('confirm role change with success', () => {
|
||||
it('calls the API', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
mutation: setUserRole,
|
||||
variables: {
|
||||
userId: 1,
|
||||
role: 'ADMIN',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('emits "updateRoles" with role moderator', () => {
|
||||
expect(wrapper.emitted('updateRoles')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
{
|
||||
userId: 1,
|
||||
roles: ['ADMIN'],
|
||||
},
|
||||
]),
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it('toasts success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm role change with error', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
apolloMutateMock.mockRejectedValue({ message: 'Oh no!' })
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Oh no!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('user has role "moderator"', () => {
|
||||
beforeEach(() => {
|
||||
apolloMutateMock.mockResolvedValue({
|
||||
data: {
|
||||
setUserRole: null,
|
||||
},
|
||||
})
|
||||
propsData = {
|
||||
item: {
|
||||
userId: 1,
|
||||
roles: ['MODERATOR'],
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
rolesToSelect = wrapper.find('select.role-select').findAll('option')
|
||||
})
|
||||
|
||||
it('has selected option set to "MODERATOR"', () => {
|
||||
expect(wrapper.find('select.role-select').element.value).toBe('MODERATOR')
|
||||
})
|
||||
|
||||
describe('change select to', () => {
|
||||
describe('same role', () => {
|
||||
it('has "change_user_role" button disabled', () => {
|
||||
expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('does not call the API', () => {
|
||||
rolesToSelect.at(1).setSelected()
|
||||
expect(apolloMutateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('new role "USER"', () => {
|
||||
beforeEach(() => {
|
||||
rolesToSelect.at(0).setSelected()
|
||||
})
|
||||
|
||||
it('has "change_user_role" button enabled', () => {
|
||||
expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true)
|
||||
expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(
|
||||
false,
|
||||
)
|
||||
})
|
||||
|
||||
describe('clicking the "change_user_role" button', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve(true))
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('calls the modal', () => {
|
||||
expect(wrapper.emitted('showModal'))
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('confirm role change with success', () => {
|
||||
it('calls the API', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
mutation: setUserRole,
|
||||
variables: {
|
||||
userId: 1,
|
||||
role: 'USER',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('emits "updateRoles"', () => {
|
||||
expect(wrapper.emitted('updateRoles')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
{
|
||||
userId: 1,
|
||||
roles: [],
|
||||
},
|
||||
]),
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it('toasts success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm role change with error', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
apolloMutateMock.mockRejectedValue({ message: 'Oh no!' })
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Oh no!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('new role "ADMIN"', () => {
|
||||
beforeEach(() => {
|
||||
rolesToSelect.at(2).setSelected()
|
||||
})
|
||||
|
||||
it('has "change_user_role" button enabled', () => {
|
||||
expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true)
|
||||
expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(
|
||||
false,
|
||||
)
|
||||
})
|
||||
|
||||
describe('clicking the "change_user_role" button', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve(true))
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('calls the modal', () => {
|
||||
expect(wrapper.emitted('showModal'))
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('confirm role change with success', () => {
|
||||
it('calls the API', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
mutation: setUserRole,
|
||||
variables: {
|
||||
userId: 1,
|
||||
role: 'ADMIN',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('emits "updateRoles"', () => {
|
||||
expect(wrapper.emitted('updateRoles')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
{
|
||||
userId: 1,
|
||||
roles: ['ADMIN'],
|
||||
},
|
||||
]),
|
||||
]),
|
||||
@ -232,7 +481,7 @@ describe('ChangeUserRoleFormular', () => {
|
||||
propsData = {
|
||||
item: {
|
||||
userId: 1,
|
||||
isAdmin: new Date(),
|
||||
roles: ['ADMIN'],
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
@ -240,7 +489,7 @@ describe('ChangeUserRoleFormular', () => {
|
||||
})
|
||||
|
||||
it('has selected option set to "admin"', () => {
|
||||
expect(wrapper.find('select.role-select').element.value).toBe('admin')
|
||||
expect(wrapper.find('select.role-select').element.value).toBe('ADMIN')
|
||||
})
|
||||
|
||||
describe('change select to', () => {
|
||||
@ -251,11 +500,12 @@ describe('ChangeUserRoleFormular', () => {
|
||||
|
||||
it('does not call the API', () => {
|
||||
rolesToSelect.at(1).setSelected()
|
||||
// TODO: Fix this
|
||||
expect(apolloMutateMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('new role', () => {
|
||||
describe('new role "USER"', () => {
|
||||
beforeEach(() => {
|
||||
rolesToSelect.at(0).setSelected()
|
||||
})
|
||||
@ -287,19 +537,90 @@ describe('ChangeUserRoleFormular', () => {
|
||||
mutation: setUserRole,
|
||||
variables: {
|
||||
userId: 1,
|
||||
isAdmin: false,
|
||||
role: 'USER',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('emits "updateIsAdmin"', () => {
|
||||
expect(wrapper.emitted('updateIsAdmin')).toEqual(
|
||||
it('emits "updateRoles"', () => {
|
||||
expect(wrapper.emitted('updateRoles')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
{
|
||||
userId: 1,
|
||||
isAdmin: null,
|
||||
roles: [],
|
||||
},
|
||||
]),
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it('toasts success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm role change with error', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
apolloMutateMock.mockRejectedValue({ message: 'Oh no!' })
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Oh no!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('new role "MODERATOR"', () => {
|
||||
beforeEach(() => {
|
||||
rolesToSelect.at(1).setSelected()
|
||||
})
|
||||
|
||||
it('has "change_user_role" button enabled', () => {
|
||||
expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true)
|
||||
expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(
|
||||
false,
|
||||
)
|
||||
})
|
||||
|
||||
describe('clicking the "change_user_role" button', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve(true))
|
||||
await wrapper.find('button').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
})
|
||||
|
||||
it('calls the modal', () => {
|
||||
expect(wrapper.emitted('showModal'))
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('confirm role change with success', () => {
|
||||
it('calls the API', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
mutation: setUserRole,
|
||||
variables: {
|
||||
userId: 1,
|
||||
role: 'MODERATOR',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('emits "updateRoles"', () => {
|
||||
expect(wrapper.emitted('updateRoles')).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.arrayContaining([
|
||||
{
|
||||
userId: 1,
|
||||
roles: ['MODERATOR'],
|
||||
},
|
||||
]),
|
||||
]),
|
||||
@ -328,5 +649,23 @@ describe('ChangeUserRoleFormular', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('authenticated user is MODERATOR', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$store.state.moderator.roles = ['MODERATOR']
|
||||
})
|
||||
|
||||
it('displays text with role', () => {
|
||||
expect(wrapper.text()).toBe('userRole.selectRoles.admin')
|
||||
})
|
||||
|
||||
it('has no role select', () => {
|
||||
expect(wrapper.find('select.role-select').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('has no button', () => {
|
||||
expect(wrapper.find('button.btn.btn-dange').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div class="change-user-role-formular">
|
||||
<div class="shadow p-3 mb-5 bg-white rounded">
|
||||
<div v-if="item.userId === $store.state.moderator.id" class="m-3 mb-4">
|
||||
<div v-if="!$store.state.moderator.roles.includes('ADMIN')" class="m-3 mb-4">
|
||||
{{ roles.find((role) => role.value === currentRole).text }}
|
||||
</div>
|
||||
<div v-else-if="item.userId === $store.state.moderator.id" class="m-3 mb-4">
|
||||
{{ $t('userRole.notChangeYourSelf') }}
|
||||
</div>
|
||||
<div v-else class="m-3">
|
||||
@ -25,8 +28,9 @@
|
||||
import { setUserRole } from '../graphql/setUserRole'
|
||||
|
||||
const rolesValues = {
|
||||
admin: 'admin',
|
||||
user: 'user',
|
||||
ADMIN: 'ADMIN',
|
||||
MODERATOR: 'MODERATOR',
|
||||
USER: 'USER',
|
||||
}
|
||||
|
||||
export default {
|
||||
@ -39,23 +43,30 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentRole: this.item.isAdmin ? rolesValues.admin : rolesValues.user,
|
||||
roleSelected: this.item.isAdmin ? rolesValues.admin : rolesValues.user,
|
||||
currentRole: this.getCurrentRole(),
|
||||
roleSelected: this.getCurrentRole(),
|
||||
roles: [
|
||||
{ value: rolesValues.user, text: this.$t('userRole.selectRoles.user') },
|
||||
{ value: rolesValues.admin, text: this.$t('userRole.selectRoles.admin') },
|
||||
{ value: rolesValues.USER, text: this.$t('userRole.selectRoles.user') },
|
||||
{ value: rolesValues.MODERATOR, text: this.$t('userRole.selectRoles.moderator') },
|
||||
{ value: rolesValues.ADMIN, text: this.$t('userRole.selectRoles.admin') },
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCurrentRole() {
|
||||
if (this.item.roles.length) return rolesValues[this.item.roles[0]]
|
||||
return rolesValues.USER
|
||||
},
|
||||
showModal() {
|
||||
this.$bvModal
|
||||
.msgBoxConfirm(
|
||||
this.$t('overlay.changeUserRole.question', {
|
||||
username: `${this.item.firstName} ${this.item.lastName}`,
|
||||
newRole:
|
||||
this.roleSelected === 'admin'
|
||||
this.roleSelected === rolesValues.ADMIN
|
||||
? this.$t('userRole.selectRoles.admin')
|
||||
: this.roleSelected === rolesValues.MODERATOR
|
||||
? this.$t('userRole.selectRoles.moderator')
|
||||
: this.$t('userRole.selectRoles.user'),
|
||||
}),
|
||||
{
|
||||
@ -77,25 +88,27 @@ export default {
|
||||
})
|
||||
},
|
||||
setUserRole(newRole, oldRole) {
|
||||
const role = this.roles.find((role) => {
|
||||
return role.value === newRole
|
||||
})
|
||||
const roleText = role.text
|
||||
const roleValue = role.value
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: setUserRole,
|
||||
variables: {
|
||||
userId: this.item.userId,
|
||||
isAdmin: newRole === rolesValues.admin,
|
||||
role: role.value,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
this.$emit('updateIsAdmin', {
|
||||
this.$emit('updateRoles', {
|
||||
userId: this.item.userId,
|
||||
isAdmin: result.data.setUserRole,
|
||||
roles: roleValue === 'USER' ? [] : [roleValue],
|
||||
})
|
||||
this.toastSuccess(
|
||||
this.$t('userRole.successfullyChangedTo', {
|
||||
role:
|
||||
result.data.setUserRole !== null
|
||||
? this.$t('userRole.selectRoles.admin')
|
||||
: this.$t('userRole.selectRoles.user'),
|
||||
role: roleText,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionMessagesFormular from './ContributionMessagesFormular'
|
||||
import { toastErrorSpy, toastSuccessSpy } from '../../../test/testSetup'
|
||||
import { adminCreateContributionMessage } from '@/graphql/adminCreateContributionMessage'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
@ -34,6 +35,7 @@ describe('ContributionMessagesFormular', () => {
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('has a DIV .contribution-messages-formular', () => {
|
||||
@ -73,13 +75,65 @@ describe('ContributionMessagesFormular', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('emitted "update-state" with data', async () => {
|
||||
expect(wrapper.emitted('update-state')).toEqual(
|
||||
it('emitted "update-status" with data', async () => {
|
||||
expect(wrapper.emitted('update-status')).toEqual(
|
||||
expect.arrayContaining([expect.arrayContaining([42])]),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('send DIALOG contribution message with success', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
await wrapper.find('button[data-test="submit-dialog"]').trigger('click')
|
||||
})
|
||||
|
||||
it('moderatorMesage has `DIALOG`', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith({
|
||||
mutation: adminCreateContributionMessage,
|
||||
variables: {
|
||||
contributionId: 42,
|
||||
message: 'text form message',
|
||||
messageType: 'DIALOG',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('toasts an success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('message.request')
|
||||
})
|
||||
})
|
||||
|
||||
describe('send MODERATOR contribution message with success', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
await wrapper.find('button[data-test="submit-moderator"]').trigger('click')
|
||||
})
|
||||
|
||||
it('moderatorMesage has `MODERATOR`', () => {
|
||||
expect(apolloMutateMock).toBeCalledWith({
|
||||
mutation: adminCreateContributionMessage,
|
||||
variables: {
|
||||
contributionId: 42,
|
||||
message: 'text form message',
|
||||
messageType: 'MODERATOR',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('toasts an success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('message.request')
|
||||
})
|
||||
})
|
||||
|
||||
describe('send contribution message with error', () => {
|
||||
beforeEach(async () => {
|
||||
apolloMutateMock.mockRejectedValue({ message: 'OUCH!' })
|
||||
@ -91,21 +145,5 @@ describe('ContributionMessagesFormular', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('OUCH!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('send contribution message with success', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper.setData({
|
||||
form: {
|
||||
text: 'text form message',
|
||||
},
|
||||
})
|
||||
wrapper = Wrapper()
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('toasts an success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('message.request')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="contribution-messages-formular">
|
||||
<div class="mt-5">
|
||||
<b-form @submit.prevent="onSubmit" @reset.prevent="onReset">
|
||||
<b-form @reset.prevent="onReset" @submit="onSubmit(messageType.DIALOG)">
|
||||
<b-form-textarea
|
||||
id="textarea"
|
||||
v-model="form.text"
|
||||
@ -12,8 +12,27 @@
|
||||
<b-col>
|
||||
<b-button type="reset" variant="danger">{{ $t('form.cancel') }}</b-button>
|
||||
</b-col>
|
||||
<b-col class="text-center">
|
||||
<b-button
|
||||
type="button"
|
||||
variant="warning"
|
||||
class="text-black"
|
||||
:disabled="disabled"
|
||||
@click.prevent="onSubmit(messageType.MODERATOR)"
|
||||
data-test="submit-moderator"
|
||||
>
|
||||
{{ $t('moderator.notice') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
|
||||
<b-col class="text-right">
|
||||
<b-button type="submit" variant="primary" :disabled="disabled">
|
||||
<b-button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
:disabled="disabled"
|
||||
@click.prevent="onSubmit(messageType.DIALOG)"
|
||||
data-test="submit-dialog"
|
||||
>
|
||||
{{ $t('form.submit') }}
|
||||
</b-button>
|
||||
</b-col>
|
||||
@ -39,10 +58,14 @@ export default {
|
||||
text: '',
|
||||
},
|
||||
loading: false,
|
||||
messageType: {
|
||||
DIALOG: 'DIALOG',
|
||||
MODERATOR: 'MODERATOR',
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit(event) {
|
||||
onSubmit(mType) {
|
||||
this.loading = true
|
||||
this.$apollo
|
||||
.mutate({
|
||||
@ -50,11 +73,12 @@ export default {
|
||||
variables: {
|
||||
contributionId: this.contributionId,
|
||||
message: this.form.text,
|
||||
messageType: mType,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
this.$emit('get-list-contribution-messages', this.contributionId)
|
||||
this.$emit('update-state', this.contributionId)
|
||||
this.$emit('update-status', this.contributionId)
|
||||
this.form.text = ''
|
||||
this.toastSuccess(this.$t('message.request'))
|
||||
this.loading = false
|
||||
|
||||
@ -1,26 +1,102 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ContributionMessagesList from './ContributionMessagesList'
|
||||
import VueApollo from 'vue-apollo'
|
||||
import { createMockClient } from 'mock-apollo-client'
|
||||
import { adminListContributionMessages } from '../../graphql/adminListContributionMessages.js'
|
||||
import { toastErrorSpy } from '../../../test/testSetup'
|
||||
|
||||
const mockClient = createMockClient()
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: mockClient,
|
||||
})
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue()
|
||||
localVue.use(VueApollo)
|
||||
|
||||
const defaultData = () => {
|
||||
return {
|
||||
adminListContributionMessages: {
|
||||
count: 4,
|
||||
messages: [
|
||||
{
|
||||
id: 43,
|
||||
message: 'A DIALOG message',
|
||||
createdAt: new Date().toString(),
|
||||
updatedAt: null,
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
userId: 1,
|
||||
isModerator: true,
|
||||
},
|
||||
{
|
||||
id: 44,
|
||||
message: 'Another DIALOG message',
|
||||
createdAt: new Date().toString(),
|
||||
updatedAt: null,
|
||||
type: 'DIALOG',
|
||||
userFirstName: 'Bibi',
|
||||
userLastName: 'Bloxberg',
|
||||
userId: 2,
|
||||
isModerator: false,
|
||||
},
|
||||
{
|
||||
id: 45,
|
||||
message: `DATE
|
||||
---
|
||||
A HISTORY message
|
||||
---
|
||||
AMOUNT`,
|
||||
createdAt: new Date().toString(),
|
||||
updatedAt: null,
|
||||
type: 'HISTORY',
|
||||
userFirstName: 'Bibi',
|
||||
userLastName: 'Bloxberg',
|
||||
userId: 2,
|
||||
isModerator: false,
|
||||
},
|
||||
{
|
||||
id: 46,
|
||||
message: 'A MODERATOR message',
|
||||
createdAt: new Date().toString(),
|
||||
updatedAt: null,
|
||||
type: 'MODERATOR',
|
||||
userFirstName: 'Peter',
|
||||
userLastName: 'Lustig',
|
||||
userId: 1,
|
||||
isModerator: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe('ContributionMessagesList', () => {
|
||||
let wrapper
|
||||
|
||||
const adminListContributionMessagessMock = jest.fn()
|
||||
|
||||
mockClient.setRequestHandler(
|
||||
adminListContributionMessages,
|
||||
adminListContributionMessagessMock
|
||||
.mockRejectedValueOnce({ message: 'Auaa!' })
|
||||
.mockResolvedValue({ data: defaultData() }),
|
||||
)
|
||||
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
contributionState: 'PENDING',
|
||||
contributionUserId: 108,
|
||||
contributionStatus: 'PENDING',
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
$n: jest.fn((n) => n),
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
$apollo: {
|
||||
query: apolloQueryMock,
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
@ -28,30 +104,34 @@ describe('ContributionMessagesList', () => {
|
||||
localVue,
|
||||
mocks,
|
||||
propsData,
|
||||
apolloProvider,
|
||||
})
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('sends query to Apollo when created', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
contributionId: propsData.contributionId,
|
||||
},
|
||||
}),
|
||||
)
|
||||
describe('server response for admin list contribution messages is error', () => {
|
||||
it('toast an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Auaa!')
|
||||
})
|
||||
})
|
||||
|
||||
it('has a DIV .contribution-messages-list', () => {
|
||||
expect(wrapper.find('div.contribution-messages-list').exists()).toBe(true)
|
||||
})
|
||||
describe('server response is succes', () => {
|
||||
it('has a DIV .contribution-messages-list', () => {
|
||||
expect(wrapper.find('div.contribution-messages-list').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('has a Component ContributionMessagesFormular', () => {
|
||||
expect(wrapper.findComponent({ name: 'ContributionMessagesFormular' }).exists()).toBe(true)
|
||||
it('has 4 messages', () => {
|
||||
expect(wrapper.findAll('div.contribution-messages-list-item')).toHaveLength(4)
|
||||
})
|
||||
|
||||
it('has a Component ContributionMessagesFormular', () => {
|
||||
expect(wrapper.findComponent({ name: 'ContributionMessagesFormular' }).exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,15 +2,17 @@
|
||||
<div class="contribution-messages-list">
|
||||
<b-container>
|
||||
<div v-for="message in messages" v-bind:key="message.id">
|
||||
<contribution-messages-list-item :message="message" />
|
||||
<contribution-messages-list-item
|
||||
:message="message"
|
||||
:contributionUserId="contributionUserId"
|
||||
/>
|
||||
</div>
|
||||
</b-container>
|
||||
|
||||
<div v-if="contributionState === 'PENDING' || contributionState === 'IN_PROGRESS'">
|
||||
<div v-if="contributionStatus === 'PENDING' || contributionStatus === 'IN_PROGRESS'">
|
||||
<contribution-messages-formular
|
||||
:contributionId="contributionId"
|
||||
@get-list-contribution-messages="getListContributionMessages"
|
||||
@update-state="updateState"
|
||||
@get-list-contribution-messages="$apollo.queries.Messages.refetch()"
|
||||
@update-status="updateStatus"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -18,7 +20,7 @@
|
||||
<script>
|
||||
import ContributionMessagesListItem from './slots/ContributionMessagesListItem'
|
||||
import ContributionMessagesFormular from '../ContributionMessages/ContributionMessagesFormular'
|
||||
import { listContributionMessages } from '../../graphql/listContributionMessages.js'
|
||||
import { adminListContributionMessages } from '../../graphql/adminListContributionMessages.js'
|
||||
|
||||
export default {
|
||||
name: 'ContributionMessagesList',
|
||||
@ -31,39 +33,43 @@ export default {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
contributionState: {
|
||||
contributionStatus: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
contributionUserId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
messages: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getListContributionMessages(id) {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: listContributionMessages,
|
||||
variables: {
|
||||
contributionId: id,
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
})
|
||||
.then((result) => {
|
||||
this.messages = result.data.listContributionMessages.messages
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
updateState(id) {
|
||||
this.$emit('update-state', id)
|
||||
apollo: {
|
||||
Messages: {
|
||||
query() {
|
||||
return adminListContributionMessages
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
contributionId: this.contributionId,
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
update({ adminListContributionMessages }) {
|
||||
this.messages = adminListContributionMessages.messages
|
||||
},
|
||||
error({ message }) {
|
||||
this.toastError(message)
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getListContributionMessages(this.contributionId)
|
||||
methods: {
|
||||
updateStatus(id) {
|
||||
this.$emit('update-status', id)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -13,11 +13,20 @@ describe('ContributionMessagesListItem', () => {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: dateMock,
|
||||
$n: numberMock,
|
||||
$store: {
|
||||
state: {
|
||||
moderator: {
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
describe('if message author has moderator role', () => {
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
contributionUserId: 108,
|
||||
state: 'PENDING',
|
||||
message: {
|
||||
id: 111,
|
||||
@ -51,27 +60,21 @@ describe('ContributionMessagesListItem', () => {
|
||||
})
|
||||
|
||||
it('has the complete user name', () => {
|
||||
expect(wrapper.find('div.text-right.is-moderator > span:nth-child(2)').text()).toBe(
|
||||
'Peter Lustig',
|
||||
)
|
||||
expect(wrapper.find('[data-test="moderator-name"]').text()).toBe('Peter Lustig')
|
||||
})
|
||||
|
||||
it('has the message creation date', () => {
|
||||
expect(wrapper.find('div.text-right.is-moderator > span:nth-child(3)').text()).toMatch(
|
||||
expect(wrapper.find('[data-test="moderator-date"]').text()).toMatch(
|
||||
'Mon Aug 29 2022 12:23:27 GMT+0000',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the moderator label', () => {
|
||||
expect(wrapper.find('div.text-right.is-moderator > small:nth-child(4)').text()).toBe(
|
||||
'moderator',
|
||||
)
|
||||
expect(wrapper.find('[data-test="moderator-label"]').text()).toBe('moderator.moderator')
|
||||
})
|
||||
|
||||
it('has the message', () => {
|
||||
expect(wrapper.find('div.text-right.is-moderator > div:nth-child(5)').text()).toBe(
|
||||
'Lorem ipsum?',
|
||||
)
|
||||
expect(wrapper.find('[data-test="moderator-message"]').text()).toBe('Lorem ipsum?')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -79,6 +82,7 @@ describe('ContributionMessagesListItem', () => {
|
||||
describe('if message author does not have moderator role', () => {
|
||||
const propsData = {
|
||||
contributionId: 42,
|
||||
contributionUserId: 108,
|
||||
state: 'PENDING',
|
||||
message: {
|
||||
id: 113,
|
||||
@ -107,23 +111,21 @@ describe('ContributionMessagesListItem', () => {
|
||||
})
|
||||
|
||||
it('has a DIV .text-left.is-not-moderator', () => {
|
||||
expect(wrapper.find('div.text-left.is-not-moderator').exists()).toBe(true)
|
||||
expect(wrapper.find('div.text-left.is-user').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('has the complete user name', () => {
|
||||
expect(wrapper.find('div.is-not-moderator.text-left > span:nth-child(2)').text()).toBe(
|
||||
'Bibi Bloxberg',
|
||||
)
|
||||
expect(wrapper.find('[data-test="user-name"]').text()).toBe('Bibi Bloxberg')
|
||||
})
|
||||
|
||||
it('has the message creation date', () => {
|
||||
expect(wrapper.find('div.is-not-moderator.text-left > span:nth-child(3)').text()).toMatch(
|
||||
expect(wrapper.find('[data-test="user-date"]').text()).toMatch(
|
||||
'Mon Aug 29 2022 12:25:34 GMT+0000',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the message', () => {
|
||||
expect(wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)').text()).toBe(
|
||||
expect(wrapper.find('[data-test="user-message"]').text()).toBe(
|
||||
'Asda sdad ad asdasd, das Ass das Das.',
|
||||
)
|
||||
})
|
||||
@ -132,6 +134,7 @@ describe('ContributionMessagesListItem', () => {
|
||||
|
||||
describe('links in contribtion message', () => {
|
||||
const propsData = {
|
||||
contributionUserId: 108,
|
||||
message: {
|
||||
id: 111,
|
||||
message: 'Lorem ipsum?',
|
||||
@ -159,7 +162,7 @@ describe('ContributionMessagesListItem', () => {
|
||||
beforeEach(() => {
|
||||
propsData.message.message = 'https://gradido.net/de/'
|
||||
wrapper = ModeratorItemWrapper()
|
||||
messageField = wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)')
|
||||
messageField = wrapper.find('[data-test="moderator-message"]')
|
||||
})
|
||||
|
||||
it('contains the link as text', () => {
|
||||
@ -176,7 +179,7 @@ describe('ContributionMessagesListItem', () => {
|
||||
propsData.message.message = `Here you find all you need to know about Gradido: https://gradido.net/de/
|
||||
and here is the link to the repository: https://github.com/gradido/gradido`
|
||||
wrapper = ModeratorItemWrapper()
|
||||
messageField = wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)')
|
||||
messageField = wrapper.find('[data-test="moderator-message"]')
|
||||
})
|
||||
|
||||
it('contains the whole text', () => {
|
||||
@ -196,6 +199,7 @@ and here is the link to the repository: https://github.com/gradido/gradido`)
|
||||
|
||||
describe('contribution message type HISTORY', () => {
|
||||
const propsData = {
|
||||
contributionUserId: 108,
|
||||
message: {
|
||||
id: 111,
|
||||
message: `Sun Nov 13 2022 13:05:48 GMT+0100 (Central European Standard Time)
|
||||
@ -227,7 +231,7 @@ This message also contains a link: https://gradido.net/de/
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
wrapper = itemWrapper()
|
||||
messageField = wrapper.find('div.is-not-moderator.text-left > div:nth-child(4)')
|
||||
messageField = wrapper
|
||||
})
|
||||
|
||||
it('renders the date', () => {
|
||||
|
||||
@ -1,17 +1,37 @@
|
||||
<template>
|
||||
<div class="contribution-messages-list-item">
|
||||
<div v-if="message.isModerator" class="text-right is-moderator">
|
||||
<div v-if="isModeratorMessage" class="text-right p-2 rounded-sm mb-3" :class="boxClass">
|
||||
<small class="ml-4" data-test="moderator-label">
|
||||
{{ $t('moderator.moderator') }}
|
||||
</small>
|
||||
<small class="ml-2" data-test="moderator-date">
|
||||
{{ $d(new Date(message.createdAt), 'short') }}
|
||||
</small>
|
||||
<span class="ml-2 mr-2" data-test="moderator-name">
|
||||
{{ message.userFirstName }} {{ message.userLastName }}
|
||||
</span>
|
||||
<b-avatar square variant="warning"></b-avatar>
|
||||
<span class="ml-2 mr-2">{{ message.userFirstName }} {{ message.userLastName }}</span>
|
||||
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
|
||||
<small class="ml-4 text-success">{{ $t('moderator') }}</small>
|
||||
<parse-message v-bind="message"></parse-message>
|
||||
|
||||
<parse-message v-bind="message" data-test="moderator-message"></parse-message>
|
||||
<small v-if="isModeratorHiddenMessage">
|
||||
<hr />
|
||||
{{ $t('moderator.request') }}
|
||||
</small>
|
||||
</div>
|
||||
<div v-else class="text-left is-not-moderator">
|
||||
<div v-else class="text-left p-2 rounded-sm mb-3" :class="boxClass">
|
||||
<b-avatar variant="info"></b-avatar>
|
||||
<span class="ml-2 mr-2">{{ message.userFirstName }} {{ message.userLastName }}</span>
|
||||
<span class="ml-2">{{ $d(new Date(message.createdAt), 'short') }}</span>
|
||||
<parse-message v-bind="message"></parse-message>
|
||||
<span class="ml-2 mr-2" data-test="user-name">
|
||||
{{ message.userFirstName }} {{ message.userLastName }}
|
||||
</span>
|
||||
<small class="ml-2" data-test="user-date">
|
||||
{{ $d(new Date(message.createdAt), 'short') }}
|
||||
</small>
|
||||
<small v-if="isHistory">
|
||||
<hr />
|
||||
{{ $t('moderator.history') }}
|
||||
<hr />
|
||||
</small>
|
||||
<parse-message v-bind="message" data-test="user-message"></parse-message>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -28,22 +48,50 @@ export default {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
contributionUserId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isModeratorMessage() {
|
||||
return this.contributionUserId !== this.message.userId
|
||||
},
|
||||
isModeratorHiddenMessage() {
|
||||
return this.message.type === 'MODERATOR'
|
||||
},
|
||||
isHistory() {
|
||||
return this.message.type === 'HISTORY'
|
||||
},
|
||||
boxClass() {
|
||||
if (this.isModeratorHiddenMessage) return 'is-moderator is-moderator-hidden-message'
|
||||
if (this.isHistory) return 'is-user is-user-history-message'
|
||||
if (this.isModeratorMessage) return 'is-moderator is-moderator-message'
|
||||
return 'is-user is-user-message'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.is-not-moderator {
|
||||
clear: both;
|
||||
width: 75%;
|
||||
margin-top: 20px;
|
||||
/* background-color: rgb(261, 204, 221); */
|
||||
}
|
||||
.is-moderator {
|
||||
clear: both;
|
||||
float: right;
|
||||
width: 75%;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
/* background-color: rgb(255, 255, 128); */
|
||||
}
|
||||
.is-moderator-message {
|
||||
background-color: rgb(228, 237, 245);
|
||||
}
|
||||
.is-moderator-hidden-message {
|
||||
background-color: rgb(217, 161, 228);
|
||||
}
|
||||
.is-user {
|
||||
clear: both;
|
||||
width: 75%;
|
||||
}
|
||||
.is-user-message {
|
||||
background-color: rgb(236, 235, 213);
|
||||
}
|
||||
.is-user-history-message {
|
||||
background-color: rgb(235, 226, 57);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -28,7 +28,7 @@ const defaultData = () => {
|
||||
memo: 'Danke für alles',
|
||||
date: new Date(),
|
||||
moderator: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -39,6 +39,7 @@ const defaultData = () => {
|
||||
deletedBy: null,
|
||||
deletedAt: null,
|
||||
createdAt: new Date(),
|
||||
moderatorId: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@ -50,7 +51,7 @@ const defaultData = () => {
|
||||
memo: 'Gut Ergattert',
|
||||
date: new Date(),
|
||||
moderator: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -61,6 +62,7 @@ const defaultData = () => {
|
||||
deletedBy: null,
|
||||
deletedAt: null,
|
||||
createdAt: new Date(),
|
||||
moderatorId: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -34,8 +34,8 @@
|
||||
{{ $t('help.transactionlist.confirmed') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('transactionlist.state') }} {{ $t('math.equals') }}
|
||||
{{ $t('help.transactionlist.state') }}
|
||||
{{ $t('transactionlist.status') }} {{ $t('math.equals') }}
|
||||
{{ $t('help.transactionlist.status') }}
|
||||
</div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
@ -78,8 +78,8 @@ export default {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'state',
|
||||
label: this.$t('transactionlist.state'),
|
||||
key: 'status',
|
||||
label: this.$t('transactionlist.status'),
|
||||
},
|
||||
{
|
||||
key: 'amount',
|
||||
|
||||
@ -131,13 +131,13 @@ describe('OpenCreationsTable', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('call updateState', () => {
|
||||
describe('call updateStatus', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.updateState(4)
|
||||
wrapper.vm.updateStatus(4)
|
||||
})
|
||||
|
||||
it('emits update-state', () => {
|
||||
expect(wrapper.vm.$root.$emit('update-state', 4)).toBeTruthy()
|
||||
it('emits update-status', () => {
|
||||
expect(wrapper.vm.$root.$emit('update-status', 4)).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
stacked="md"
|
||||
:tbody-tr-class="rowClass"
|
||||
>
|
||||
<template #cell(state)="row">
|
||||
<b-icon :icon="getStatusIcon(row.item.state)"></b-icon>
|
||||
<template #cell(status)="row">
|
||||
<b-icon :icon="getStatusIcon(row.item.status)"></b-icon>
|
||||
</template>
|
||||
<template #cell(bookmark)="row">
|
||||
<div v-if="!myself(row.item)">
|
||||
@ -39,12 +39,12 @@
|
||||
<b-button v-else @click="rowToggleDetails(row, 0)">
|
||||
<b-icon icon="chat-dots"></b-icon>
|
||||
<b-icon
|
||||
v-if="row.item.state === 'PENDING' && row.item.messagesCount > 0"
|
||||
v-if="row.item.status === 'PENDING' && row.item.messagesCount > 0"
|
||||
icon="exclamation-circle-fill"
|
||||
variant="warning"
|
||||
></b-icon>
|
||||
<b-icon
|
||||
v-if="row.item.state === 'IN_PROGRESS' && row.item.messagesCount > 0"
|
||||
v-if="row.item.status === 'IN_PROGRESS' && row.item.messagesCount > 0"
|
||||
icon="question-diamond"
|
||||
variant="warning"
|
||||
class="pl-1"
|
||||
@ -102,8 +102,9 @@
|
||||
<div v-else>
|
||||
<contribution-messages-list
|
||||
:contributionId="row.item.id"
|
||||
:contributionState="row.item.state"
|
||||
@update-state="updateState"
|
||||
:contributionStatus="row.item.status"
|
||||
:contributionUserId="row.item.userId"
|
||||
@update-status="updateStatus"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@ -154,15 +155,21 @@ export default {
|
||||
},
|
||||
rowClass(item, type) {
|
||||
if (!item || type !== 'row') return
|
||||
if (item.state === 'CONFIRMED') return 'table-success'
|
||||
if (item.state === 'DENIED') return 'table-warning'
|
||||
if (item.state === 'DELETED') return 'table-danger'
|
||||
if (item.state === 'IN_PROGRESS') return 'table-primary'
|
||||
if (item.state === 'PENDING') return 'table-primary'
|
||||
if (item.status === 'CONFIRMED') return 'table-success'
|
||||
if (item.status === 'DENIED') return 'table-warning'
|
||||
if (item.status === 'DELETED') return 'table-danger'
|
||||
if (item.status === 'IN_PROGRESS') return 'table-primary'
|
||||
if (item.status === 'PENDING') return 'table-primary'
|
||||
},
|
||||
updateState(id) {
|
||||
this.$emit('update-state', id)
|
||||
updateStatus(id) {
|
||||
this.$emit('update-status', id)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.btn-warning {
|
||||
background-color: #e1a908;
|
||||
border-color: #e1a908;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -15,6 +15,7 @@ const propsData = {
|
||||
email: 'bibi@bloxberg.de',
|
||||
creation: [200, 400, 600],
|
||||
emailChecked: true,
|
||||
roles: [],
|
||||
},
|
||||
{
|
||||
userId: 2,
|
||||
@ -23,6 +24,7 @@ const propsData = {
|
||||
email: 'benjamin@bluemchen.de',
|
||||
creation: [1000, 1000, 1000],
|
||||
emailChecked: true,
|
||||
roles: [],
|
||||
},
|
||||
{
|
||||
userId: 3,
|
||||
@ -31,6 +33,7 @@ const propsData = {
|
||||
email: 'peter@lustig.de',
|
||||
creation: [0, 0, 0],
|
||||
emailChecked: true,
|
||||
roles: ['ADMIN'],
|
||||
},
|
||||
{
|
||||
userId: 4,
|
||||
@ -39,6 +42,7 @@ const propsData = {
|
||||
email: 'new@user.ch',
|
||||
creation: [1000, 1000, 1000],
|
||||
emailChecked: false,
|
||||
roles: [],
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
@ -68,6 +72,7 @@ const mocks = {
|
||||
moderator: {
|
||||
id: 0,
|
||||
name: 'test moderator',
|
||||
roles: ['ADMIN'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -96,14 +101,14 @@ describe('SearchUserTable', () => {
|
||||
|
||||
describe('isAdmin', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.find('div.change-user-role-formular').vm.$emit('updateIsAdmin', {
|
||||
await wrapper.find('div.change-user-role-formular').vm.$emit('updateRoles', {
|
||||
userId: 1,
|
||||
isAdmin: new Date(),
|
||||
roles: ['ADMIN'],
|
||||
})
|
||||
})
|
||||
|
||||
it('emits updateIsAdmin', () => {
|
||||
expect(wrapper.emitted('updateIsAdmin')).toEqual([[1, expect.any(Date)]])
|
||||
expect(wrapper.emitted('updateRoles')).toEqual([[1, ['ADMIN']]])
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -79,9 +79,9 @@
|
||||
<transaction-link-list v-if="!row.item.deletedAt" :userId="row.item.userId" />
|
||||
</b-tab>
|
||||
<b-tab :title="$t('userRole.tabTitle')">
|
||||
<change-user-role-formular :item="row.item" @updateIsAdmin="updateIsAdmin" />
|
||||
<change-user-role-formular :item="row.item" @updateRoles="updateRoles" />
|
||||
</b-tab>
|
||||
<b-tab :title="$t('delete_user')">
|
||||
<b-tab v-if="$store.state.moderator.roles.includes('ADMIN')" :title="$t('delete_user')">
|
||||
<deleted-user-formular :item="row.item" @updateDeletedAt="updateDeletedAt" />
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
@ -127,8 +127,8 @@ export default {
|
||||
updateUserData(rowItem, newCreation) {
|
||||
rowItem.creation = newCreation
|
||||
},
|
||||
updateIsAdmin({ userId, isAdmin }) {
|
||||
this.$emit('updateIsAdmin', userId, isAdmin)
|
||||
updateRoles({ userId, roles }) {
|
||||
this.$emit('updateRoles', userId, roles)
|
||||
},
|
||||
updateDeletedAt({ userId, deletedAt }) {
|
||||
this.$emit('updateDeletedAt', userId, deletedAt)
|
||||
|
||||
47
admin/src/components/UserQuery.spec.js
Normal file
47
admin/src/components/UserQuery.spec.js
Normal file
@ -0,0 +1,47 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import UserQuery from './UserQuery'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const propsData = {
|
||||
userId: 42,
|
||||
}
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
}
|
||||
describe('TransactionLinkList', () => {
|
||||
let wrapper
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(UserQuery, { mocks, localVue, propsData })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('has div .input-group', () => {
|
||||
expect(wrapper.find('div .input-group').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('has .test-input-criteria', () => {
|
||||
expect(wrapper.find('input.test-input-criteria').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('set value', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
await wrapper.find('input.test-input-criteria').setValue('Test2')
|
||||
})
|
||||
|
||||
it('emits input', () => {
|
||||
expect(wrapper.emitted('input')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('emits input with value "Test2"', () => {
|
||||
expect(wrapper.emitted('input')).toEqual([['Test2']])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
43
admin/src/components/UserQuery.vue
Normal file
43
admin/src/components/UserQuery.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
type="text"
|
||||
class="test-input-criteria"
|
||||
v-model="currentValue"
|
||||
:placeholder="placeholderText"
|
||||
></b-form-input>
|
||||
<b-input-group-append class="test-click-clear-criteria" @click="currentValue = ''">
|
||||
<b-input-group-text class="pointer">
|
||||
<b-icon icon="x" />
|
||||
</b-input-group-text>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'UserQuery',
|
||||
props: {
|
||||
value: { type: String, default: '' },
|
||||
placeholder: { type: String, default: '' },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentValue: this.value,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
placeholderText() {
|
||||
return this.placeholder || this.$t('user_search')
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentValue() {
|
||||
if (this.value !== this.currentValue) {
|
||||
this.$emit('input', this.currentValue)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,8 +1,12 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const adminCreateContributionMessage = gql`
|
||||
mutation ($contributionId: Int!, $message: String!) {
|
||||
adminCreateContributionMessage(contributionId: $contributionId, message: $message) {
|
||||
mutation ($contributionId: Int!, $message: String!, $messageType: ContributionMessageType) {
|
||||
adminCreateContributionMessage(
|
||||
contributionId: $contributionId
|
||||
message: $message
|
||||
messageType: $messageType
|
||||
) {
|
||||
id
|
||||
message
|
||||
createdAt
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const listContributionMessages = gql`
|
||||
export const adminListContributionMessages = gql`
|
||||
query ($contributionId: Int!, $pageSize: Int = 25, $currentPage: Int = 1, $order: Order = ASC) {
|
||||
listContributionMessages(
|
||||
adminListContributionMessages(
|
||||
contributionId: $contributionId
|
||||
pageSize: $pageSize
|
||||
currentPage: $currentPage
|
||||
@ -7,6 +7,8 @@ export const adminListContributions = gql`
|
||||
$order: Order = DESC
|
||||
$statusFilter: [ContributionStatus!]
|
||||
$userId: Int
|
||||
$query: String
|
||||
$noHashtag: Boolean
|
||||
) {
|
||||
adminListContributions(
|
||||
currentPage: $currentPage
|
||||
@ -14,6 +16,8 @@ export const adminListContributions = gql`
|
||||
order: $order
|
||||
statusFilter: $statusFilter
|
||||
userId: $userId
|
||||
query: $query
|
||||
noHashtag: $noHashtag
|
||||
) {
|
||||
contributionCount
|
||||
contributionList {
|
||||
@ -26,7 +30,7 @@ export const adminListContributions = gql`
|
||||
contributionDate
|
||||
confirmedAt
|
||||
confirmedBy
|
||||
state
|
||||
status
|
||||
messagesCount
|
||||
deniedAt
|
||||
deniedBy
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const searchUsers = gql`
|
||||
query ($searchText: String!, $currentPage: Int, $pageSize: Int, $filters: SearchUsersFilters) {
|
||||
query (
|
||||
$query: String!
|
||||
$filters: SearchUsersFilters
|
||||
$currentPage: Int = 0
|
||||
$pageSize: Int = 25
|
||||
$order: Order = ASC
|
||||
) {
|
||||
searchUsers(
|
||||
searchText: $searchText
|
||||
query: $query
|
||||
filters: $filters
|
||||
currentPage: $currentPage
|
||||
pageSize: $pageSize
|
||||
filters: $filters
|
||||
order: $order
|
||||
) {
|
||||
userCount
|
||||
userList {
|
||||
@ -19,7 +26,7 @@ export const searchUsers = gql`
|
||||
hasElopage
|
||||
emailConfirmationSend
|
||||
deletedAt
|
||||
isAdmin
|
||||
roles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const setUserRole = gql`
|
||||
mutation ($userId: Int!, $isAdmin: Boolean!) {
|
||||
setUserRole(userId: $userId, isAdmin: $isAdmin)
|
||||
mutation ($userId: Int!, $role: RoleNames!) {
|
||||
setUserRole(userId: $userId, role: $role)
|
||||
}
|
||||
`
|
||||
|
||||
@ -5,7 +5,7 @@ export const verifyLogin = gql`
|
||||
verifyLogin {
|
||||
firstName
|
||||
lastName
|
||||
isAdmin
|
||||
roles
|
||||
id
|
||||
language
|
||||
}
|
||||
|
||||
@ -89,12 +89,13 @@
|
||||
"submit": "Senden"
|
||||
},
|
||||
"GDD": "GDD",
|
||||
"hashtag_symbol": "#",
|
||||
"help": {
|
||||
"help": "Hilfe",
|
||||
"transactionlist": {
|
||||
"confirmed": "Wann wurde es von einem Moderator / Admin bestätigt.",
|
||||
"periods": "Für welchen Zeitraum wurde vom Mitglied eingereicht.",
|
||||
"state": "[PENDING = eingereicht, DELETED = gelöscht, IN_PROGRESS = im Dialog mit Moderator, DENIED = abgelehnt, CONFIRMED = bestätigt]",
|
||||
"status": "[PENDING = eingereicht, DELETED = gelöscht, IN_PROGRESS = im Dialog mit Moderator, DENIED = abgelehnt, CONFIRMED = bestätigt]",
|
||||
"submitted": "Wann wurde es vom Mitglied eingereicht"
|
||||
}
|
||||
},
|
||||
@ -108,7 +109,12 @@
|
||||
"message": {
|
||||
"request": "Die Anfrage wurde gesendet."
|
||||
},
|
||||
"moderator": "Moderator",
|
||||
"moderator": {
|
||||
"history": "Die Daten wurden geändert. Dies sind die alten Daten.",
|
||||
"moderator": "Moderator",
|
||||
"notice": "Moderator Notiz",
|
||||
"request": "Diese Nachricht ist nur für die Moderatoren sichtbar!"
|
||||
},
|
||||
"name": "Name",
|
||||
"navbar": {
|
||||
"automaticContributions": "Automatische Beiträge",
|
||||
@ -119,6 +125,10 @@
|
||||
"user_search": "Nutzersuche"
|
||||
},
|
||||
"not_open_creations": "Keine offenen Schöpfungen",
|
||||
"no_filter": "Keine Filterung",
|
||||
"no_filter_tooltip": "Es wird nicht nach Hashtags gefiltert",
|
||||
"no_hashtag": "Ohne Hashtag",
|
||||
"no_hashtag_tooltip": "Zeigt nur Schöpfungen ohne Hashtag im Kommentar an",
|
||||
"open": "offen",
|
||||
"open_creations": "Offene Schöpfungen",
|
||||
"overlay": {
|
||||
@ -184,7 +194,7 @@
|
||||
"confirmed": "Bestätigt",
|
||||
"memo": "Nachricht",
|
||||
"period": "Zeitraum",
|
||||
"state": "Status",
|
||||
"status": "Status",
|
||||
"submitted": "Eingereicht",
|
||||
"title": "Alle geschöpften Transaktionen für den Nutzer"
|
||||
},
|
||||
@ -204,12 +214,14 @@
|
||||
"selectLabel": "Rolle:",
|
||||
"selectRoles": {
|
||||
"admin": "Administrator",
|
||||
"moderator": "Moderator",
|
||||
"user": "einfacher Nutzer"
|
||||
},
|
||||
"successfullyChangedTo": "Nutzer ist jetzt „{role}“.",
|
||||
"tabTitle": "Nutzer-Rolle"
|
||||
},
|
||||
"user_deleted": "Nutzer ist gelöscht.",
|
||||
"user_memo_search": "Nutzer-Kommentar-Suche",
|
||||
"user_recovered": "Nutzer ist wiederhergestellt.",
|
||||
"user_search": "Nutzer-Suche"
|
||||
}
|
||||
|
||||
@ -89,12 +89,13 @@
|
||||
"submit": "Send"
|
||||
},
|
||||
"GDD": "GDD",
|
||||
"hashtag_symbol": "#",
|
||||
"help": {
|
||||
"help": "Help",
|
||||
"transactionlist": {
|
||||
"confirmed": "When was it confirmed by a moderator / admin.",
|
||||
"periods": "For what period was it submitted by the member.",
|
||||
"state": "[PENDING = submitted, DELETED = deleted, IN_PROGRESS = in dialogue with moderator, DENIED = rejected, CONFIRMED = confirmed]",
|
||||
"status": "[PENDING = submitted, DELETED = deleted, IN_PROGRESS = in dialogue with moderator, DENIED = rejected, CONFIRMED = confirmed]",
|
||||
"submitted": "When was it submitted by the member"
|
||||
}
|
||||
},
|
||||
@ -108,7 +109,12 @@
|
||||
"message": {
|
||||
"request": "Request has been sent."
|
||||
},
|
||||
"moderator": "Moderator",
|
||||
"moderator": {
|
||||
"history": "The data has been changed. This is the old data.",
|
||||
"moderator": "Moderator",
|
||||
"notice": "Moderator note",
|
||||
"request": "This message is only visible to the moderators!"
|
||||
},
|
||||
"name": "Name",
|
||||
"navbar": {
|
||||
"automaticContributions": "Automatic Contributions",
|
||||
@ -119,6 +125,10 @@
|
||||
"user_search": "User search"
|
||||
},
|
||||
"not_open_creations": "No open creations",
|
||||
"no_filter": "No Filter",
|
||||
"no_filter_tooltip": "It is not filtered by hashtags",
|
||||
"no_hashtag": "No Hashtag",
|
||||
"no_hashtag_tooltip": "Displays only contributions without hashtag in comment",
|
||||
"open": "open",
|
||||
"open_creations": "Open creations",
|
||||
"overlay": {
|
||||
@ -184,7 +194,7 @@
|
||||
"confirmed": "Confirmed",
|
||||
"memo": "Message",
|
||||
"period": "Period",
|
||||
"state": "State",
|
||||
"status": "State",
|
||||
"submitted": "Submitted",
|
||||
"title": "All creation-transactions for the user"
|
||||
},
|
||||
@ -204,12 +214,14 @@
|
||||
"selectLabel": "Role:",
|
||||
"selectRoles": {
|
||||
"admin": "administrator",
|
||||
"moderator": "moderator",
|
||||
"user": "usual user"
|
||||
},
|
||||
"successfullyChangedTo": "User is now \"{role}\".",
|
||||
"tabTitle": "User Role"
|
||||
},
|
||||
"user_deleted": "User is deleted.",
|
||||
"user_memo_search": "User and Memo search",
|
||||
"user_recovered": "User is recovered.",
|
||||
"user_search": "User search"
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ const mocks = {
|
||||
moderator: {
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
isAdmin: '2022-08-30T07:41:31.000Z',
|
||||
roles: ['ADMIN'],
|
||||
id: 263,
|
||||
language: 'de',
|
||||
},
|
||||
@ -51,7 +51,7 @@ const defaultData = () => {
|
||||
memo: 'Danke für alles',
|
||||
date: new Date(),
|
||||
moderator: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -73,7 +73,7 @@ const defaultData = () => {
|
||||
memo: 'Gut Ergattert',
|
||||
date: new Date(),
|
||||
moderator: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -339,8 +339,10 @@ describe('CreationConfirm', () => {
|
||||
it('refetches contributions with proper filter', () => {
|
||||
expect(adminListContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
noHashtag: null,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['CONFIRMED'],
|
||||
})
|
||||
})
|
||||
@ -354,8 +356,10 @@ describe('CreationConfirm', () => {
|
||||
it('refetches contributions with proper filter', () => {
|
||||
expect(adminListContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
noHashtag: null,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['IN_PROGRESS', 'PENDING'],
|
||||
})
|
||||
})
|
||||
@ -370,8 +374,10 @@ describe('CreationConfirm', () => {
|
||||
it('refetches contributions with proper filter', () => {
|
||||
expect(adminListContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
noHashtag: null,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['DENIED'],
|
||||
})
|
||||
})
|
||||
@ -386,8 +392,10 @@ describe('CreationConfirm', () => {
|
||||
it('refetches contributions with proper filter', () => {
|
||||
expect(adminListContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
noHashtag: null,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['DELETED'],
|
||||
})
|
||||
})
|
||||
@ -402,8 +410,10 @@ describe('CreationConfirm', () => {
|
||||
it('refetches contributions with proper filter', () => {
|
||||
expect(adminListContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
noHashtag: null,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['IN_PROGRESS', 'PENDING', 'CONFIRMED', 'DENIED', 'DELETED'],
|
||||
})
|
||||
})
|
||||
@ -422,8 +432,10 @@ describe('CreationConfirm', () => {
|
||||
it('calls the API again', () => {
|
||||
expect(adminListContributionsMock).toBeCalledWith({
|
||||
currentPage: 2,
|
||||
noHashtag: null,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['IN_PROGRESS', 'PENDING', 'CONFIRMED', 'DENIED', 'DELETED'],
|
||||
})
|
||||
})
|
||||
@ -437,8 +449,10 @@ describe('CreationConfirm', () => {
|
||||
it('refetches contributions with proper filter and current page = 1', () => {
|
||||
expect(adminListContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
noHashtag: null,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['IN_PROGRESS', 'PENDING'],
|
||||
})
|
||||
})
|
||||
@ -449,14 +463,50 @@ describe('CreationConfirm', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('user query', () => {
|
||||
describe('with user query', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.findComponent({ name: 'UserQuery' }).vm.$emit('input', 'query')
|
||||
})
|
||||
|
||||
it('calls the API with query', () => {
|
||||
expect(adminListContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
noHashtag: null,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: 'query',
|
||||
statusFilter: ['IN_PROGRESS', 'PENDING'],
|
||||
})
|
||||
})
|
||||
|
||||
describe('reset query', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.findComponent({ name: 'UserQuery' }).vm.$emit('input', '')
|
||||
})
|
||||
|
||||
it('calls the API with empty query', () => {
|
||||
expect(adminListContributionsMock).toBeCalledWith({
|
||||
currentPage: 1,
|
||||
noHashtag: null,
|
||||
order: 'DESC',
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
statusFilter: ['IN_PROGRESS', 'PENDING'],
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('update status', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.findComponent({ name: 'OpenCreationsTable' }).vm.$emit('update-state', 2)
|
||||
await wrapper.findComponent({ name: 'OpenCreationsTable' }).vm.$emit('update-status', 2)
|
||||
})
|
||||
|
||||
it('updates the status', () => {
|
||||
expect(wrapper.vm.items.find((obj) => obj.id === 2).messagesCount).toBe(1)
|
||||
expect(wrapper.vm.items.find((obj) => obj.id === 2).state).toBe('IN_PROGRESS')
|
||||
expect(wrapper.vm.items.find((obj) => obj.id === 2).status).toBe('IN_PROGRESS')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
<!-- eslint-disable @intlify/vue-i18n/no-dynamic-keys -->
|
||||
<template>
|
||||
<div class="creation-confirm">
|
||||
<user-query class="mb-2 mt-2" v-model="query" :placeholder="$t('user_memo_search')" />
|
||||
<div class="mb-4">
|
||||
<b-button class="noHashtag" variant="light" @click="swapNoHashtag" v-b-tooltip="tooltipText">
|
||||
<span :style="hashtagColor">{{ $t('hashtag_symbol') }}</span>
|
||||
{{ noHashtag ? $t('no_hashtag') : $t('no_filter') }}
|
||||
</b-button>
|
||||
</div>
|
||||
<div>
|
||||
<b-tabs v-model="tabIndex" content-class="mt-3" fill>
|
||||
<b-tab active :title-link-attributes="{ 'data-test': 'open' }">
|
||||
@ -43,7 +50,7 @@
|
||||
:items="items"
|
||||
:fields="fields"
|
||||
@show-overlay="showOverlay"
|
||||
@update-state="updateStatus"
|
||||
@update-status="updateStatus"
|
||||
@update-contributions="$apollo.queries.ListAllContributions.refetch()"
|
||||
/>
|
||||
|
||||
@ -85,6 +92,7 @@
|
||||
<script>
|
||||
import Overlay from '../components/Overlay'
|
||||
import OpenCreationsTable from '../components/Tables/OpenCreationsTable'
|
||||
import UserQuery from '../components/UserQuery'
|
||||
import { adminListContributions } from '../graphql/adminListContributions'
|
||||
import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
|
||||
import { confirmContribution } from '../graphql/confirmContribution'
|
||||
@ -103,6 +111,7 @@ export default {
|
||||
components: {
|
||||
OpenCreationsTable,
|
||||
Overlay,
|
||||
UserQuery,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -114,6 +123,8 @@ export default {
|
||||
rows: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
query: '',
|
||||
noHashtag: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -122,6 +133,10 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
swapNoHashtag() {
|
||||
this.noHashtag = !!(this.noHashtag === null || this.noHashtag === false)
|
||||
this.query()
|
||||
},
|
||||
deleteCreation() {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
@ -187,13 +202,19 @@ export default {
|
||||
},
|
||||
updateStatus(id) {
|
||||
this.items.find((obj) => obj.id === id).messagesCount++
|
||||
this.items.find((obj) => obj.id === id).state = 'IN_PROGRESS'
|
||||
this.items.find((obj) => obj.id === id).status = 'IN_PROGRESS'
|
||||
},
|
||||
formatDateOrDash(value) {
|
||||
return value ? this.$d(new Date(value), 'short') : '—'
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hashtagColor() {
|
||||
return this.noHashtag ? 'color: red' : 'color: black'
|
||||
},
|
||||
tooltipText() {
|
||||
return this.noHashtag ? this.$t('no_hashtag_tooltip') : this.$t('no_filter_tooltip')
|
||||
},
|
||||
fields() {
|
||||
return [
|
||||
[
|
||||
@ -217,7 +238,7 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'moderatorId', label: this.$t('moderator') },
|
||||
{ key: 'moderatorId', label: this.$t('moderator.moderator') },
|
||||
{ key: 'editCreation', label: this.$t('chat') },
|
||||
{ key: 'confirm', label: this.$t('save') },
|
||||
],
|
||||
@ -254,7 +275,7 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'confirmedBy', label: this.$t('moderator') },
|
||||
{ key: 'confirmedBy', label: this.$t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
[
|
||||
@ -290,7 +311,7 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'deniedBy', label: this.$t('moderator') },
|
||||
{ key: 'deniedBy', label: this.$t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
[
|
||||
@ -326,12 +347,12 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'deletedBy', label: this.$t('moderator') },
|
||||
{ key: 'deletedBy', label: this.$t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
[
|
||||
// all contributions
|
||||
{ key: 'state', label: this.$t('status') },
|
||||
{ key: 'status', label: this.$t('status') },
|
||||
{ key: 'firstName', label: this.$t('firstname') },
|
||||
{ key: 'lastName', label: this.$t('lastname') },
|
||||
{
|
||||
@ -363,7 +384,7 @@ export default {
|
||||
return this.formatDateOrDash(value)
|
||||
},
|
||||
},
|
||||
{ key: 'confirmedBy', label: this.$t('moderator') },
|
||||
{ key: 'confirmedBy', label: this.$t('moderator.moderator') },
|
||||
{ key: 'chatCreation', label: this.$t('chat') },
|
||||
],
|
||||
][this.tabIndex]
|
||||
@ -409,6 +430,8 @@ export default {
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
statusFilter: this.statusFilter,
|
||||
query: this.query,
|
||||
noHashtag: this.noHashtag,
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
|
||||
@ -43,7 +43,7 @@ const defaultData = () => {
|
||||
memo: 'Danke für alles',
|
||||
date: new Date(),
|
||||
moderatorId: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
@ -65,7 +65,7 @@ const defaultData = () => {
|
||||
memo: 'Gut Ergattert',
|
||||
date: new Date(),
|
||||
moderatorId: 1,
|
||||
state: 'PENDING',
|
||||
status: 'PENDING',
|
||||
creation: [500, 500, 500],
|
||||
messagesCount: 0,
|
||||
deniedBy: null,
|
||||
|
||||
@ -10,11 +10,22 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
userCount: 4,
|
||||
userList: [
|
||||
{
|
||||
userId: 1,
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
email: 'bibi@bloxberg.de',
|
||||
creation: [200, 400, 600],
|
||||
userId: 4,
|
||||
firstName: 'New',
|
||||
lastName: 'User',
|
||||
email: 'new@user.ch',
|
||||
creation: [1000, 1000, 1000],
|
||||
emailChecked: false,
|
||||
roles: [],
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
userId: 3,
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
email: 'peter@lustig.de',
|
||||
creation: [0, 0, 0],
|
||||
roles: ['ADMIN'],
|
||||
emailChecked: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
@ -24,27 +35,20 @@ const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
lastName: 'Blümchen',
|
||||
email: 'benjamin@bluemchen.de',
|
||||
creation: [1000, 1000, 1000],
|
||||
roles: [],
|
||||
emailChecked: true,
|
||||
deletedAt: new Date(),
|
||||
},
|
||||
{
|
||||
userId: 3,
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
email: 'peter@lustig.de',
|
||||
creation: [0, 0, 0],
|
||||
userId: 1,
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
email: 'bibi@bloxberg.de',
|
||||
creation: [200, 400, 600],
|
||||
roles: [],
|
||||
emailChecked: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
userId: 4,
|
||||
firstName: 'New',
|
||||
lastName: 'User',
|
||||
email: 'new@user.ch',
|
||||
creation: [1000, 1000, 1000],
|
||||
emailChecked: false,
|
||||
deletedAt: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
@ -79,9 +83,10 @@ describe('UserSearch', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
searchText: '',
|
||||
query: '',
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filters: {
|
||||
byActivated: null,
|
||||
byDeleted: null,
|
||||
@ -100,9 +105,10 @@ describe('UserSearch', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
searchText: '',
|
||||
query: '',
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filters: {
|
||||
byActivated: false,
|
||||
byDeleted: null,
|
||||
@ -122,9 +128,10 @@ describe('UserSearch', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
searchText: '',
|
||||
query: '',
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filters: {
|
||||
byActivated: null,
|
||||
byDeleted: true,
|
||||
@ -144,9 +151,10 @@ describe('UserSearch', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
searchText: '',
|
||||
query: '',
|
||||
currentPage: 2,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filters: {
|
||||
byActivated: null,
|
||||
byDeleted: null,
|
||||
@ -166,9 +174,10 @@ describe('UserSearch', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
searchText: 'search string',
|
||||
query: 'search string',
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filters: {
|
||||
byActivated: null,
|
||||
byDeleted: null,
|
||||
@ -181,13 +190,14 @@ describe('UserSearch', () => {
|
||||
describe('reset the search field', () => {
|
||||
it('calls the API with empty criteria', async () => {
|
||||
jest.clearAllMocks()
|
||||
await wrapper.find('.test-click-clear-criteria').trigger('click')
|
||||
await wrapper.findComponent({ name: 'UserQuery' }).vm.$emit('input', '')
|
||||
expect(apolloQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
searchText: '',
|
||||
query: '',
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
order: 'DESC',
|
||||
filters: {
|
||||
byActivated: null,
|
||||
byDeleted: null,
|
||||
@ -206,10 +216,10 @@ describe('UserSearch', () => {
|
||||
it('updates user role to admin', async () => {
|
||||
await wrapper
|
||||
.findComponent({ name: 'SearchUserTable' })
|
||||
.vm.$emit('updateIsAdmin', userId, new Date())
|
||||
expect(wrapper.vm.searchResult.find((obj) => obj.userId === userId).isAdmin).toEqual(
|
||||
expect.any(Date),
|
||||
)
|
||||
.vm.$emit('updateRoles', userId, ['ADMIN'])
|
||||
expect(wrapper.vm.searchResult.find((obj) => obj.userId === userId).roles).toEqual([
|
||||
'ADMIN',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@ -217,8 +227,8 @@ describe('UserSearch', () => {
|
||||
it('updates user role to usual user', async () => {
|
||||
await wrapper
|
||||
.findComponent({ name: 'SearchUserTable' })
|
||||
.vm.$emit('updateIsAdmin', userId, null)
|
||||
expect(wrapper.vm.searchResult.find((obj) => obj.userId === userId).isAdmin).toEqual(null)
|
||||
.vm.$emit('updateRoles', userId, [])
|
||||
expect(wrapper.vm.searchResult.find((obj) => obj.userId === userId).roles).toEqual([])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -23,33 +23,19 @@
|
||||
</b-button>
|
||||
</div>
|
||||
<label>{{ $t('user_search') }}</label>
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
<user-query class="mb-4 mt-2" v-model="criteria" />
|
||||
<search-user-table
|
||||
type="PageUserSearch"
|
||||
:items="searchResult"
|
||||
:fields="fields"
|
||||
@updateIsAdmin="updateIsAdmin"
|
||||
@updateRoles="updateRoles"
|
||||
@updateDeletedAt="updateDeletedAt"
|
||||
/>
|
||||
<b-pagination
|
||||
pills
|
||||
size="lg"
|
||||
v-model="currentPage"
|
||||
per-page="perPage"
|
||||
:per-page="perPage"
|
||||
:total-rows="rows"
|
||||
align="center"
|
||||
:hide-ellipsis="true"
|
||||
@ -61,12 +47,14 @@
|
||||
import SearchUserTable from '../components/Tables/SearchUserTable'
|
||||
import { searchUsers } from '../graphql/searchUsers'
|
||||
import { creationMonths } from '../mixins/creationMonths'
|
||||
import UserQuery from '../components/UserQuery'
|
||||
|
||||
export default {
|
||||
name: 'UserSearch',
|
||||
mixins: [creationMonths],
|
||||
components: {
|
||||
SearchUserTable,
|
||||
UserQuery,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -97,10 +85,11 @@ export default {
|
||||
.query({
|
||||
query: searchUsers,
|
||||
variables: {
|
||||
searchText: this.criteria,
|
||||
query: this.criteria,
|
||||
filters: this.filters,
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.perPage,
|
||||
filters: this.filters,
|
||||
order: 'DESC',
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
})
|
||||
@ -112,8 +101,8 @@ export default {
|
||||
this.toastError(error.message)
|
||||
})
|
||||
},
|
||||
updateIsAdmin(userId, isAdmin) {
|
||||
this.searchResult.find((obj) => obj.userId === userId).isAdmin = isAdmin
|
||||
updateRoles(userId, roles) {
|
||||
this.searchResult.find((obj) => obj.userId === userId).roles = roles
|
||||
},
|
||||
updateDeletedAt(userId, deletedAt) {
|
||||
this.searchResult.find((obj) => obj.userId === userId).deletedAt = deletedAt
|
||||
|
||||
@ -13,7 +13,7 @@ const addNavigationGuards = (router, store, apollo, i18n) => {
|
||||
})
|
||||
.then((result) => {
|
||||
const moderator = result.data.verifyLogin
|
||||
if (moderator.isAdmin) {
|
||||
if (moderator.roles?.length) {
|
||||
i18n.locale = moderator.language
|
||||
store.commit('moderator', moderator)
|
||||
next({ path: '/' })
|
||||
@ -35,7 +35,7 @@ const addNavigationGuards = (router, store, apollo, i18n) => {
|
||||
!CONFIG.DEBUG_DISABLE_AUTH && // we did not disabled the auth module for debug purposes
|
||||
(!store.state.token || // we do not have a token
|
||||
!store.state.moderator || // no moderator set in store
|
||||
!store.state.moderator.isAdmin) && // user is no admin
|
||||
!store.state.moderator.roles.length) && // user is no admin
|
||||
to.path !== '/not-found' && // we are not on `not-found`
|
||||
to.path !== '/logout' // we are not on `logout`
|
||||
) {
|
||||
|
||||
@ -5,7 +5,7 @@ const storeCommitMock = jest.fn()
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
verifyLogin: {
|
||||
isAdmin: true,
|
||||
roles: ['ADMIN'],
|
||||
language: 'de',
|
||||
},
|
||||
},
|
||||
@ -52,7 +52,10 @@ describe('navigation guards', () => {
|
||||
})
|
||||
|
||||
it('commits the moderator to the store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('moderator', { isAdmin: true, language: 'de' })
|
||||
expect(storeCommitMock).toBeCalledWith('moderator', {
|
||||
roles: ['ADMIN'],
|
||||
language: 'de',
|
||||
})
|
||||
})
|
||||
|
||||
it('redirects to /', () => {
|
||||
@ -60,12 +63,48 @@ describe('navigation guards', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('with valid token and not as admin', () => {
|
||||
beforeEach(() => {
|
||||
describe('with valid token and as moderator', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
apolloQueryMock.mockResolvedValue({
|
||||
data: {
|
||||
verifyLogin: {
|
||||
isAdmin: false,
|
||||
roles: ['MODERATOR'],
|
||||
language: 'de',
|
||||
},
|
||||
},
|
||||
})
|
||||
await navGuard({ path: '/authenticate', query: { token: 'valid-token' } }, {}, next)
|
||||
})
|
||||
|
||||
it('commits the token to the store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('token', 'valid-token')
|
||||
})
|
||||
|
||||
it.skip('sets the locale', () => {
|
||||
expect(i18nLocaleMock).toBeCalledWith('de')
|
||||
})
|
||||
|
||||
it('commits the moderator to the store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('moderator', {
|
||||
roles: ['MODERATOR'],
|
||||
language: 'de',
|
||||
})
|
||||
})
|
||||
|
||||
it('redirects to /', () => {
|
||||
expect(next).toBeCalledWith({ path: '/' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('with valid token and no roles', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
apolloQueryMock.mockResolvedValue({
|
||||
data: {
|
||||
verifyLogin: {
|
||||
roles: [],
|
||||
language: 'de',
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -77,7 +116,7 @@ describe('navigation guards', () => {
|
||||
})
|
||||
|
||||
it('does not commit the moderator to the store', () => {
|
||||
expect(storeCommitMock).not.toBeCalledWith('moderator', { isAdmin: false })
|
||||
expect(storeCommitMock).not.toBeCalledWith('moderator')
|
||||
})
|
||||
|
||||
it('redirects to /not-found', async () => {
|
||||
@ -128,15 +167,22 @@ describe('navigation guards', () => {
|
||||
expect(next).toBeCalledWith({ path: '/not-found' })
|
||||
})
|
||||
|
||||
it('redirects to not found with token in store and not moderator', () => {
|
||||
it('redirects to not found with token in store and not admin or moderator', () => {
|
||||
store.state.token = 'valid token'
|
||||
navGuard({ path: '/' }, {}, next)
|
||||
expect(next).toBeCalledWith({ path: '/not-found' })
|
||||
})
|
||||
|
||||
it('does not redirect with token in store and as admin', () => {
|
||||
store.state.token = 'valid token'
|
||||
store.state.moderator = { roles: ['ADMIN'] }
|
||||
navGuard({ path: '/' }, {}, next)
|
||||
expect(next).toBeCalledWith()
|
||||
})
|
||||
|
||||
it('does not redirect with token in store and as moderator', () => {
|
||||
store.state.token = 'valid token'
|
||||
store.state.moderator = { isAdmin: true }
|
||||
store.state.moderator = { roles: ['MODERATOR'] }
|
||||
navGuard({ path: '/' }, {}, next)
|
||||
expect(next).toBeCalledWith()
|
||||
})
|
||||
|
||||
@ -21,6 +21,10 @@ KLICKTIPP_PASSWORD=secret321
|
||||
KLICKTIPP_APIKEY_DE=SomeFakeKeyDE
|
||||
KLICKTIPP_APIKEY_EN=SomeFakeKeyEN
|
||||
|
||||
# DltConnector
|
||||
DLT_CONNECTOR=true
|
||||
DLT_CONNECTOR_URL=http://localhost:6010
|
||||
|
||||
# Community
|
||||
COMMUNITY_NAME=Gradido Entwicklung
|
||||
COMMUNITY_URL=http://localhost/
|
||||
|
||||
@ -22,6 +22,10 @@ KLICKTIPP_PASSWORD=$KLICKTIPP_PASSWORD
|
||||
KLICKTIPP_APIKEY_DE=$KLICKTIPP_APIKEY_DE
|
||||
KLICKTIPP_APIKEY_EN=$KLICKTIPP_APIKEY_EN
|
||||
|
||||
# DltConnector
|
||||
DLT_CONNECTOR=$DLT_CONNECTOR
|
||||
DLT_CONNECTOR_URL=$DLT_CONNECTOR_URL
|
||||
|
||||
# Community
|
||||
COMMUNITY_NAME=$COMMUNITY_NAME
|
||||
COMMUNITY_URL=$COMMUNITY_URL
|
||||
|
||||
@ -25,10 +25,12 @@ module.exports = {
|
||||
},
|
||||
node: true,
|
||||
},
|
||||
// the parser cannot handle the split sodium import
|
||||
'import/ignore': ['sodium-native'],
|
||||
},
|
||||
rules: {
|
||||
'no-console': 'error',
|
||||
camelcase: ['error', { allow: ['FederationClient_*'] }],
|
||||
camelcase: ['error', { allow: ['FederationClient_*', 'crypto_*', 'randombytes_random'] }],
|
||||
'no-debugger': 'error',
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
@ -38,7 +40,7 @@ module.exports = {
|
||||
],
|
||||
// import
|
||||
'import/export': 'error',
|
||||
'import/no-deprecated': 'error',
|
||||
// 'import/no-deprecated': 'error',
|
||||
'import/no-empty-named-blocks': 'error',
|
||||
'import/no-extraneous-dependencies': 'error',
|
||||
'import/no-mutable-exports': 'error',
|
||||
@ -58,7 +60,10 @@ module.exports = {
|
||||
'import/no-dynamic-require': 'error',
|
||||
'import/no-internal-modules': 'off',
|
||||
'import/no-relative-packages': 'error',
|
||||
'import/no-relative-parent-imports': ['error', { ignore: ['@/*'] }],
|
||||
'import/no-relative-parent-imports': [
|
||||
'error',
|
||||
{ ignore: ['@/*', 'random-bigint', 'sodium-native'] },
|
||||
],
|
||||
'import/no-self-import': 'error',
|
||||
'import/no-unresolved': 'error',
|
||||
'import/no-useless-path-segments': 'error',
|
||||
@ -192,6 +197,9 @@ module.exports = {
|
||||
{
|
||||
files: ['*.test.ts'],
|
||||
plugins: ['jest'],
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'jest/no-disabled-tests': 'error',
|
||||
'jest/no-focused-tests': 'error',
|
||||
|
||||
5
backend/@types/random-bigint/index.d.ts
vendored
Normal file
5
backend/@types/random-bigint/index.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
declare module 'random-bigint' {
|
||||
function random(bits: number, cb?: (err: Error, num: BigInt) => void): BigInt
|
||||
export = random
|
||||
}
|
||||
8
backend/@types/sodium-native/index.d.ts
vendored
Normal file
8
backend/@types/sodium-native/index.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
export * from '@/node_modules/@types/sodium-native'
|
||||
|
||||
declare module 'sodium-native' {
|
||||
export function crypto_hash_sha512_init(state: Buffer, key?: Buffer, outlen?: Buffer): void
|
||||
export function crypto_hash_sha512_update(state: Buffer, input: Buffer): void
|
||||
export function crypto_hash_sha512_final(state: Buffer, out: Buffer): void
|
||||
}
|
||||
@ -7,7 +7,7 @@ module.exports = {
|
||||
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
lines: 89,
|
||||
lines: 90,
|
||||
},
|
||||
},
|
||||
setupFiles: ['<rootDir>/test/testSetup.ts'],
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
{
|
||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||
},
|
||||
"compress": true,
|
||||
"keepFileExt" : true,
|
||||
"fileNameSep" : "_",
|
||||
"numBackups" : 30
|
||||
@ -23,6 +24,7 @@
|
||||
{
|
||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||
},
|
||||
"compress": true,
|
||||
"keepFileExt" : true,
|
||||
"fileNameSep" : "_",
|
||||
"numBackups" : 30
|
||||
@ -36,6 +38,7 @@
|
||||
{
|
||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||
},
|
||||
"compress": true,
|
||||
"keepFileExt" : true,
|
||||
"fileNameSep" : "_",
|
||||
"numBackups" : 30
|
||||
@ -49,6 +52,7 @@
|
||||
{
|
||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||
},
|
||||
"compress": true,
|
||||
"keepFileExt" : true,
|
||||
"fileNameSep" : "_",
|
||||
"numBackups" : 30
|
||||
@ -62,6 +66,7 @@
|
||||
{
|
||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m %s"
|
||||
},
|
||||
"compress": true,
|
||||
"keepFileExt" : true,
|
||||
"fileNameSep" : "_",
|
||||
"numBackups" : 30
|
||||
@ -117,7 +122,6 @@
|
||||
"appenders":
|
||||
[
|
||||
"backend",
|
||||
"out",
|
||||
"errors"
|
||||
],
|
||||
"level": "debug",
|
||||
@ -128,7 +132,6 @@
|
||||
"appenders":
|
||||
[
|
||||
"klicktipp",
|
||||
"out",
|
||||
"errors"
|
||||
],
|
||||
"level": "debug",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-backend",
|
||||
"version": "1.21.0",
|
||||
"version": "1.23.2",
|
||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/backend",
|
||||
@ -55,6 +55,7 @@
|
||||
"@types/lodash.clonedeep": "^4.5.6",
|
||||
"@types/node": "^16.10.3",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/sodium-native": "^2.3.5",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
|
||||
174
backend/src/apis/DltConnectorClient.test.ts
Normal file
174
backend/src/apis/DltConnectorClient.test.ts
Normal file
@ -0,0 +1,174 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable security/detect-object-injection */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
import { Connection } from '@dbTools/typeorm'
|
||||
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
import { cleanDB, testEnvironment } from '@test/helpers'
|
||||
|
||||
import { CONFIG } from '@/config'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
import { DltConnectorClient } from './DltConnectorClient'
|
||||
|
||||
let con: Connection
|
||||
|
||||
let testEnv: {
|
||||
con: Connection
|
||||
}
|
||||
|
||||
// Mock the GraphQLClient
|
||||
jest.mock('graphql-request', () => {
|
||||
const originalModule = jest.requireActual('graphql-request')
|
||||
|
||||
let testCursor = 0
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
GraphQLClient: jest.fn().mockImplementation((url: string) => {
|
||||
if (url === 'invalid') {
|
||||
throw new Error('invalid url')
|
||||
}
|
||||
return {
|
||||
// why not using mockResolvedValueOnce or mockReturnValueOnce?
|
||||
// I have tried, but it didn't work and return every time the first value
|
||||
request: jest.fn().mockImplementation(() => {
|
||||
testCursor++
|
||||
if (testCursor === 4) {
|
||||
return Promise.resolve(
|
||||
// invalid, is 33 Bytes long as binary
|
||||
{
|
||||
transmitTransaction: {
|
||||
dltTransactionIdHex:
|
||||
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516212A',
|
||||
},
|
||||
},
|
||||
)
|
||||
} else if (testCursor === 5) {
|
||||
throw Error('Connection error')
|
||||
} else {
|
||||
return Promise.resolve(
|
||||
// valid, is 32 Bytes long as binary
|
||||
{
|
||||
transmitTransaction: {
|
||||
dltTransactionIdHex:
|
||||
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}),
|
||||
}
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
describe('undefined DltConnectorClient', () => {
|
||||
it('invalid url', () => {
|
||||
CONFIG.DLT_CONNECTOR_URL = 'invalid'
|
||||
CONFIG.DLT_CONNECTOR = true
|
||||
const result = DltConnectorClient.getInstance()
|
||||
expect(result).toBeUndefined()
|
||||
CONFIG.DLT_CONNECTOR_URL = 'http://dlt-connector:6010'
|
||||
})
|
||||
|
||||
it('DLT_CONNECTOR is false', () => {
|
||||
CONFIG.DLT_CONNECTOR = false
|
||||
const result = DltConnectorClient.getInstance()
|
||||
expect(result).toBeUndefined()
|
||||
CONFIG.DLT_CONNECTOR = true
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
describe.skip('transmitTransaction, without db connection', () => {
|
||||
const transaction = new DbTransaction()
|
||||
transaction.typeId = 2 // Example transaction type ID
|
||||
transaction.amount = new Decimal('10.00') // Example amount
|
||||
transaction.balanceDate = new Date() // Example creation date
|
||||
transaction.id = 1 // Example transaction ID
|
||||
|
||||
it('cannot query for transaction id', async () => {
|
||||
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
describe('transmitTransaction', () => {
|
||||
beforeAll(async () => {
|
||||
testEnv = await testEnvironment(logger)
|
||||
con = testEnv.con
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
await con.close()
|
||||
})
|
||||
|
||||
const transaction = new DbTransaction()
|
||||
transaction.typeId = 2 // Example transaction type ID
|
||||
transaction.amount = new Decimal('10.00') // Example amount
|
||||
transaction.balanceDate = new Date() // Example creation date
|
||||
transaction.id = 1 // Example transaction ID
|
||||
|
||||
// data needed to let save succeed
|
||||
transaction.memo = "I'm a dummy memo"
|
||||
transaction.userId = 1
|
||||
transaction.userGradidoID = 'dummy gradido id'
|
||||
|
||||
/*
|
||||
it.skip('cannot find transaction in db', async () => {
|
||||
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
*/
|
||||
|
||||
it('invalid transaction type', async () => {
|
||||
const localTransaction = new DbTransaction()
|
||||
localTransaction.typeId = 12
|
||||
try {
|
||||
await DltConnectorClient.getInstance()?.transmitTransaction(localTransaction)
|
||||
} catch (e) {
|
||||
expect(e).toMatchObject(
|
||||
new LogError('invalid transaction type id: ' + localTransaction.typeId.toString()),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
it.skip('should transmit the transaction and update the dltTransactionId in the database', async () => {
|
||||
await transaction.save()
|
||||
|
||||
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it.skip('invalid dltTransactionId (maximal 32 Bytes in Binary)', async () => {
|
||||
await transaction.save()
|
||||
|
||||
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
*/
|
||||
})
|
||||
|
||||
/*
|
||||
describe.skip('try transmitTransaction but graphql request failed', () => {
|
||||
it('graphql request should throw', async () => {
|
||||
const transaction = new DbTransaction()
|
||||
transaction.typeId = 2 // Example transaction type ID
|
||||
transaction.amount = new Decimal('10.00') // Example amount
|
||||
transaction.balanceDate = new Date() // Example creation date
|
||||
transaction.id = 1 // Example transaction ID
|
||||
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
})
|
||||
*/
|
||||
104
backend/src/apis/DltConnectorClient.ts
Normal file
104
backend/src/apis/DltConnectorClient.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||
import { gql, GraphQLClient } from 'graphql-request'
|
||||
|
||||
import { CONFIG } from '@/config'
|
||||
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
const sendTransaction = gql`
|
||||
mutation ($input: TransactionInput!) {
|
||||
sendTransaction(data: $input) {
|
||||
dltTransactionIdHex
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// from ChatGPT
|
||||
function getTransactionTypeString(id: TransactionTypeId): string {
|
||||
const key = Object.keys(TransactionTypeId).find(
|
||||
(key) => TransactionTypeId[key as keyof typeof TransactionTypeId] === id,
|
||||
)
|
||||
if (key === undefined) {
|
||||
throw new LogError('invalid transaction type id: ' + id.toString())
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
|
||||
// and ../federation/client/FederationClientFactory.ts
|
||||
/**
|
||||
* A Singleton class defines the `getInstance` method that lets clients access
|
||||
* the unique singleton instance.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
||||
export class DltConnectorClient {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
private static instance: DltConnectorClient
|
||||
client: GraphQLClient
|
||||
/**
|
||||
* The Singleton's constructor should always be private to prevent direct
|
||||
* construction calls with the `new` operator.
|
||||
*/
|
||||
// eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* 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(): DltConnectorClient | undefined {
|
||||
if (!CONFIG.DLT_CONNECTOR || !CONFIG.DLT_CONNECTOR_URL) {
|
||||
logger.info(`dlt-connector are disabled via config...`)
|
||||
return
|
||||
}
|
||||
if (!DltConnectorClient.instance) {
|
||||
DltConnectorClient.instance = new DltConnectorClient()
|
||||
}
|
||||
if (!DltConnectorClient.instance.client) {
|
||||
try {
|
||||
DltConnectorClient.instance.client = new GraphQLClient(CONFIG.DLT_CONNECTOR_URL, {
|
||||
method: 'GET',
|
||||
jsonSerializer: {
|
||||
parse: JSON.parse,
|
||||
stringify: JSON.stringify,
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
logger.error("couldn't connect to dlt-connector: ", e)
|
||||
return
|
||||
}
|
||||
}
|
||||
return DltConnectorClient.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* transmit transaction via dlt-connector to iota
|
||||
* and update dltTransactionId of transaction in db with iota message id
|
||||
*/
|
||||
public async transmitTransaction(transaction?: DbTransaction | null): Promise<string> {
|
||||
if (transaction) {
|
||||
const typeString = getTransactionTypeString(transaction.typeId)
|
||||
const secondsSinceEpoch = Math.round(transaction.balanceDate.getTime() / 1000)
|
||||
const amountString = transaction.amount.toString()
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const { data } = await this.client.rawRequest(sendTransaction, {
|
||||
input: {
|
||||
type: typeString,
|
||||
amount: amountString,
|
||||
createdAt: secondsSinceEpoch,
|
||||
},
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
|
||||
return data.sendTransaction.dltTransactionIdHex
|
||||
} catch (e) {
|
||||
throw new LogError('Error send sending transaction to dlt-connector: ', e)
|
||||
}
|
||||
} else {
|
||||
throw new LogError('parameter transaction not set...')
|
||||
}
|
||||
}
|
||||
}
|
||||
3
backend/src/auth/ADMIN_RIGHTS.ts
Normal file
3
backend/src/auth/ADMIN_RIGHTS.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { RIGHTS } from './RIGHTS'
|
||||
|
||||
export const ADMIN_RIGHTS = [RIGHTS.SET_USER_ROLE, RIGHTS.DELETE_USER, RIGHTS.UNDELETE_USER]
|
||||
19
backend/src/auth/MODERATOR_RIGHTS.ts
Normal file
19
backend/src/auth/MODERATOR_RIGHTS.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { RIGHTS } from './RIGHTS'
|
||||
|
||||
export const MODERATOR_RIGHTS = [
|
||||
RIGHTS.SEARCH_USERS,
|
||||
RIGHTS.ADMIN_CREATE_CONTRIBUTION,
|
||||
RIGHTS.ADMIN_UPDATE_CONTRIBUTION,
|
||||
RIGHTS.ADMIN_DELETE_CONTRIBUTION,
|
||||
RIGHTS.ADMIN_LIST_CONTRIBUTIONS,
|
||||
RIGHTS.CONFIRM_CONTRIBUTION,
|
||||
RIGHTS.SEND_ACTIVATION_EMAIL,
|
||||
RIGHTS.LIST_TRANSACTION_LINKS_ADMIN,
|
||||
RIGHTS.CREATE_CONTRIBUTION_LINK,
|
||||
RIGHTS.DELETE_CONTRIBUTION_LINK,
|
||||
RIGHTS.UPDATE_CONTRIBUTION_LINK,
|
||||
RIGHTS.ADMIN_CREATE_CONTRIBUTION_MESSAGE,
|
||||
RIGHTS.ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES,
|
||||
RIGHTS.DENY_CONTRIBUTION,
|
||||
RIGHTS.ADMIN_OPEN_CREATIONS,
|
||||
]
|
||||
@ -1,8 +1,16 @@
|
||||
export enum RIGHTS {
|
||||
// Inalienable
|
||||
LOGIN = 'LOGIN',
|
||||
COMMUNITIES = 'COMMUNITIES',
|
||||
CREATE_USER = 'CREATE_USER',
|
||||
SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL',
|
||||
SET_PASSWORD = 'SET_PASSWORD',
|
||||
QUERY_TRANSACTION_LINK = 'QUERY_TRANSACTION_LINK',
|
||||
QUERY_OPT_IN = 'QUERY_OPT_IN',
|
||||
CHECK_USERNAME = 'CHECK_USERNAME',
|
||||
// User
|
||||
VERIFY_LOGIN = 'VERIFY_LOGIN',
|
||||
BALANCE = 'BALANCE',
|
||||
COMMUNITIES = 'COMMUNITIES',
|
||||
LIST_GDT_ENTRIES = 'LIST_GDT_ENTRIES',
|
||||
EXIST_PID = 'EXIST_PID',
|
||||
UNSUBSCRIBE_NEWSLETTER = 'UNSUBSCRIBE_NEWSLETTER',
|
||||
@ -10,15 +18,10 @@ export enum RIGHTS {
|
||||
TRANSACTION_LIST = 'TRANSACTION_LIST',
|
||||
SEND_COINS = 'SEND_COINS',
|
||||
LOGOUT = 'LOGOUT',
|
||||
CREATE_USER = 'CREATE_USER',
|
||||
SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL',
|
||||
SET_PASSWORD = 'SET_PASSWORD',
|
||||
QUERY_OPT_IN = 'QUERY_OPT_IN',
|
||||
UPDATE_USER_INFOS = 'UPDATE_USER_INFOS',
|
||||
HAS_ELOPAGE = 'HAS_ELOPAGE',
|
||||
CREATE_TRANSACTION_LINK = 'CREATE_TRANSACTION_LINK',
|
||||
DELETE_TRANSACTION_LINK = 'DELETE_TRANSACTION_LINK',
|
||||
QUERY_TRANSACTION_LINK = 'QUERY_TRANSACTION_LINK',
|
||||
REDEEM_TRANSACTION_LINK = 'REDEEM_TRANSACTION_LINK',
|
||||
LIST_TRANSACTION_LINKS = 'LIST_TRANSACTION_LINKS',
|
||||
GDT_BALANCE = 'GDT_BALANCE',
|
||||
@ -34,12 +37,8 @@ export enum RIGHTS {
|
||||
LIST_ALL_CONTRIBUTION_MESSAGES = 'LIST_ALL_CONTRIBUTION_MESSAGES',
|
||||
OPEN_CREATIONS = 'OPEN_CREATIONS',
|
||||
USER = 'USER',
|
||||
CHECK_USERNAME = 'CHECK_USERNAME',
|
||||
// Admin
|
||||
// Moderator
|
||||
SEARCH_USERS = 'SEARCH_USERS',
|
||||
SET_USER_ROLE = 'SET_USER_ROLE',
|
||||
DELETE_USER = 'DELETE_USER',
|
||||
UNDELETE_USER = 'UNDELETE_USER',
|
||||
ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION',
|
||||
ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION',
|
||||
ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION',
|
||||
@ -53,4 +52,9 @@ export enum RIGHTS {
|
||||
ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',
|
||||
DENY_CONTRIBUTION = 'DENY_CONTRIBUTION',
|
||||
ADMIN_OPEN_CREATIONS = 'ADMIN_OPEN_CREATIONS',
|
||||
ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES = 'ADMIN_LIST_ALL_CONTRIBUTION_MESSAGES',
|
||||
// Admin
|
||||
SET_USER_ROLE = 'SET_USER_ROLE',
|
||||
DELETE_USER = 'DELETE_USER',
|
||||
UNDELETE_USER = 'UNDELETE_USER',
|
||||
}
|
||||
|
||||
@ -1,40 +1,24 @@
|
||||
import { INALIENABLE_RIGHTS } from './INALIENABLE_RIGHTS'
|
||||
import { RIGHTS } from './RIGHTS'
|
||||
import { Role } from './Role'
|
||||
import { RoleNames } from '@/graphql/enum/RoleNames'
|
||||
|
||||
export const ROLE_UNAUTHORIZED = new Role('unauthorized', INALIENABLE_RIGHTS)
|
||||
export const ROLE_USER = new Role('user', [
|
||||
import { ADMIN_RIGHTS } from './ADMIN_RIGHTS'
|
||||
import { INALIENABLE_RIGHTS } from './INALIENABLE_RIGHTS'
|
||||
import { MODERATOR_RIGHTS } from './MODERATOR_RIGHTS'
|
||||
import { Role } from './Role'
|
||||
import { USER_RIGHTS } from './USER_RIGHTS'
|
||||
|
||||
export const ROLE_UNAUTHORIZED = new Role(RoleNames.UNAUTHORIZED, INALIENABLE_RIGHTS)
|
||||
export const ROLE_USER = new Role(RoleNames.USER, [...INALIENABLE_RIGHTS, ...USER_RIGHTS])
|
||||
export const ROLE_MODERATOR = new Role(RoleNames.MODERATOR, [
|
||||
...INALIENABLE_RIGHTS,
|
||||
RIGHTS.VERIFY_LOGIN,
|
||||
RIGHTS.BALANCE,
|
||||
RIGHTS.LIST_GDT_ENTRIES,
|
||||
RIGHTS.EXIST_PID,
|
||||
RIGHTS.UNSUBSCRIBE_NEWSLETTER,
|
||||
RIGHTS.SUBSCRIBE_NEWSLETTER,
|
||||
RIGHTS.TRANSACTION_LIST,
|
||||
RIGHTS.SEND_COINS,
|
||||
RIGHTS.LOGOUT,
|
||||
RIGHTS.UPDATE_USER_INFOS,
|
||||
RIGHTS.HAS_ELOPAGE,
|
||||
RIGHTS.CREATE_TRANSACTION_LINK,
|
||||
RIGHTS.DELETE_TRANSACTION_LINK,
|
||||
RIGHTS.REDEEM_TRANSACTION_LINK,
|
||||
RIGHTS.LIST_TRANSACTION_LINKS,
|
||||
RIGHTS.GDT_BALANCE,
|
||||
RIGHTS.CREATE_CONTRIBUTION,
|
||||
RIGHTS.DELETE_CONTRIBUTION,
|
||||
RIGHTS.LIST_CONTRIBUTIONS,
|
||||
RIGHTS.LIST_ALL_CONTRIBUTIONS,
|
||||
RIGHTS.UPDATE_CONTRIBUTION,
|
||||
RIGHTS.SEARCH_ADMIN_USERS,
|
||||
RIGHTS.LIST_CONTRIBUTION_LINKS,
|
||||
RIGHTS.COMMUNITY_STATISTICS,
|
||||
RIGHTS.CREATE_CONTRIBUTION_MESSAGE,
|
||||
RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES,
|
||||
RIGHTS.OPEN_CREATIONS,
|
||||
RIGHTS.USER,
|
||||
...USER_RIGHTS,
|
||||
...MODERATOR_RIGHTS,
|
||||
])
|
||||
export const ROLE_ADMIN = new Role(RoleNames.ADMIN, [
|
||||
...INALIENABLE_RIGHTS,
|
||||
...USER_RIGHTS,
|
||||
...MODERATOR_RIGHTS,
|
||||
...ADMIN_RIGHTS,
|
||||
])
|
||||
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights
|
||||
|
||||
// TODO from database
|
||||
export const ROLES = [ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN]
|
||||
export const ROLES = [ROLE_UNAUTHORIZED, ROLE_USER, ROLE_MODERATOR, ROLE_ADMIN]
|
||||
|
||||
32
backend/src/auth/USER_RIGHTS.ts
Normal file
32
backend/src/auth/USER_RIGHTS.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { RIGHTS } from './RIGHTS'
|
||||
|
||||
export const USER_RIGHTS = [
|
||||
RIGHTS.VERIFY_LOGIN,
|
||||
RIGHTS.BALANCE,
|
||||
RIGHTS.LIST_GDT_ENTRIES,
|
||||
RIGHTS.EXIST_PID,
|
||||
RIGHTS.UNSUBSCRIBE_NEWSLETTER,
|
||||
RIGHTS.SUBSCRIBE_NEWSLETTER,
|
||||
RIGHTS.TRANSACTION_LIST,
|
||||
RIGHTS.SEND_COINS,
|
||||
RIGHTS.LOGOUT,
|
||||
RIGHTS.UPDATE_USER_INFOS,
|
||||
RIGHTS.HAS_ELOPAGE,
|
||||
RIGHTS.CREATE_TRANSACTION_LINK,
|
||||
RIGHTS.DELETE_TRANSACTION_LINK,
|
||||
RIGHTS.REDEEM_TRANSACTION_LINK,
|
||||
RIGHTS.LIST_TRANSACTION_LINKS,
|
||||
RIGHTS.GDT_BALANCE,
|
||||
RIGHTS.CREATE_CONTRIBUTION,
|
||||
RIGHTS.DELETE_CONTRIBUTION,
|
||||
RIGHTS.LIST_CONTRIBUTIONS,
|
||||
RIGHTS.LIST_ALL_CONTRIBUTIONS,
|
||||
RIGHTS.UPDATE_CONTRIBUTION,
|
||||
RIGHTS.SEARCH_ADMIN_USERS,
|
||||
RIGHTS.LIST_CONTRIBUTION_LINKS,
|
||||
RIGHTS.COMMUNITY_STATISTICS,
|
||||
RIGHTS.CREATE_CONTRIBUTION_MESSAGE,
|
||||
RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES,
|
||||
RIGHTS.OPEN_CREATIONS,
|
||||
RIGHTS.USER,
|
||||
]
|
||||
@ -12,14 +12,14 @@ Decimal.set({
|
||||
})
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0066-x-community-sendcoins-transactions_table',
|
||||
DB_VERSION: '0070-add_dlt_transactions_table',
|
||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||
LOG4JS_CONFIG: 'log4js-config.json',
|
||||
// default log level on production should be info
|
||||
LOG_LEVEL: process.env.LOG_LEVEL ?? 'info',
|
||||
CONFIG_VERSION: {
|
||||
DEFAULT: 'DEFAULT',
|
||||
EXPECTED: 'v15.2023-02-07',
|
||||
EXPECTED: 'v18.2023-07-10',
|
||||
CURRENT: '',
|
||||
},
|
||||
}
|
||||
@ -51,6 +51,11 @@ const klicktipp = {
|
||||
KLICKTIPP_APIKEY_EN: process.env.KLICKTIPP_APIKEY_EN ?? 'SomeFakeKeyEN',
|
||||
}
|
||||
|
||||
const dltConnector = {
|
||||
DLT_CONNECTOR: process.env.DLT_CONNECTOR === 'true' || false,
|
||||
DLT_CONNECTOR_URL: process.env.DLT_CONNECTOR_URL ?? 'http://localhost:6010',
|
||||
}
|
||||
|
||||
const community = {
|
||||
COMMUNITY_NAME: process.env.COMMUNITY_NAME ?? 'Gradido Entwicklung',
|
||||
COMMUNITY_URL: process.env.COMMUNITY_URL ?? 'http://localhost/',
|
||||
@ -126,6 +131,7 @@ export const CONFIG = {
|
||||
...server,
|
||||
...database,
|
||||
...klicktipp,
|
||||
...dltConnector,
|
||||
...community,
|
||||
...email,
|
||||
...loginServer,
|
||||
|
||||
1521
backend/src/emails/__snapshots__/sendEmailVariants.test.ts.snap
Normal file
1521
backend/src/emails/__snapshots__/sendEmailVariants.test.ts.snap
Normal file
File diff suppressed because it is too large
Load Diff
@ -94,11 +94,11 @@ describe('sendEmailTranslated', () => {
|
||||
originalMessage: expect.objectContaining({
|
||||
to: 'receiver@mail.org',
|
||||
cc: 'support@gradido.net',
|
||||
from: 'Gradido (do not answer) <info@gradido.net>',
|
||||
attachments: [],
|
||||
subject: 'Gradido: Try To Register Again With Your Email',
|
||||
html: expect.stringContaining('Gradido: Try To Register Again With Your Email'),
|
||||
text: expect.stringContaining('GRADIDO: TRY TO REGISTER AGAIN WITH YOUR EMAIL'),
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Try To Register Again With Your Email',
|
||||
html: expect.stringContaining('Try To Register Again With Your Email'),
|
||||
text: expect.stringContaining('TRY TO REGISTER AGAIN WITH YOUR EMAIL'),
|
||||
}),
|
||||
})
|
||||
})
|
||||
@ -142,11 +142,11 @@ describe('sendEmailTranslated', () => {
|
||||
originalMessage: expect.objectContaining({
|
||||
to: CONFIG.EMAIL_TEST_RECEIVER,
|
||||
cc: 'support@gradido.net',
|
||||
from: `Gradido (do not answer) <${CONFIG.EMAIL_SENDER}>`,
|
||||
attachments: [],
|
||||
subject: 'Gradido: Try To Register Again With Your Email',
|
||||
html: expect.stringContaining('Gradido: Try To Register Again With Your Email'),
|
||||
text: expect.stringContaining('GRADIDO: TRY TO REGISTER AGAIN WITH YOUR EMAIL'),
|
||||
from: `Gradido (emails.general.doNotAnswer) <${CONFIG.EMAIL_SENDER}>`,
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Try To Register Again With Your Email',
|
||||
html: expect.stringContaining('Try To Register Again With Your Email'),
|
||||
text: expect.stringContaining('TRY TO REGISTER AGAIN WITH YOUR EMAIL'),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
@ -70,7 +70,36 @@ export const sendEmailTranslated = async ({
|
||||
const resultSend = await email
|
||||
.send({
|
||||
template: path.join(__dirname, 'templates', template),
|
||||
message: receiver,
|
||||
message: {
|
||||
...receiver,
|
||||
attachments: [
|
||||
{
|
||||
filename: 'gradido-header.jpeg',
|
||||
path: path.join(__dirname, 'templates/includes/gradido-header.jpeg'),
|
||||
cid: 'gradidoheader',
|
||||
},
|
||||
{
|
||||
filename: 'facebook-icon.png',
|
||||
path: path.join(__dirname, 'templates/includes/facebook-icon.png'),
|
||||
cid: 'facebookicon',
|
||||
},
|
||||
{
|
||||
filename: 'telegram-icon.png',
|
||||
path: path.join(__dirname, 'templates/includes/telegram-icon.png'),
|
||||
cid: 'telegramicon',
|
||||
},
|
||||
{
|
||||
filename: 'twitter-icon.png',
|
||||
path: path.join(__dirname, 'templates/includes/twitter-icon.png'),
|
||||
cid: 'twittericon',
|
||||
},
|
||||
{
|
||||
filename: 'youtube-icon.png',
|
||||
path: path.join(__dirname, 'templates/includes/youtube-icon.png'),
|
||||
cid: 'youtubeicon',
|
||||
},
|
||||
],
|
||||
},
|
||||
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) => {
|
||||
|
||||
@ -34,11 +34,9 @@ let testEnv: {
|
||||
beforeAll(async () => {
|
||||
testEnv = await testEnvironment(logger, localization)
|
||||
con = testEnv.con
|
||||
// await cleanDB()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
// await cleanDB()
|
||||
await con.close()
|
||||
})
|
||||
|
||||
@ -87,8 +85,10 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has expected result', () => {
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
from: 'info@gradido.net',
|
||||
@ -97,37 +97,17 @@ describe('sendEmailVariants', () => {
|
||||
message: expect.any(String),
|
||||
originalMessage: expect.objectContaining({
|
||||
to: 'Peter Lustig <peter@lustig.de>',
|
||||
from: 'Gradido (do not answer) <info@gradido.net>',
|
||||
attachments: [],
|
||||
subject: 'Gradido: Message about your common good contribution',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Message about your common good contribution',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('GRADIDO: MESSAGE ABOUT YOUR COMMON GOOD CONTRIBUTION'),
|
||||
text: expect.stringContaining('MESSAGE ABOUT YOUR COMMON GOOD CONTRIBUTION'),
|
||||
}),
|
||||
})
|
||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'<title>Gradido: Message about your common good contribution</title>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'>Gradido: Message about your common good contribution</h1>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'you have received a message from Bibi Bloxberg regarding your common good contribution “My contribution.”.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'To view and reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||
)
|
||||
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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -163,8 +143,10 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has expected result', () => {
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
from: 'info@gradido.net',
|
||||
@ -173,41 +155,17 @@ describe('sendEmailVariants', () => {
|
||||
message: expect.any(String),
|
||||
originalMessage: expect.objectContaining({
|
||||
to: 'Peter Lustig <peter@lustig.de>',
|
||||
from: 'Gradido (do not answer) <info@gradido.net>',
|
||||
attachments: [],
|
||||
subject: 'Gradido: Email Verification',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Email Verification',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('GRADIDO: EMAIL VERIFICATION'),
|
||||
text: expect.stringContaining('EMAIL VERIFICATION'),
|
||||
}),
|
||||
})
|
||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||
expect(result.originalMessage.html).toContain('<title>Gradido: Email Verification</title>')
|
||||
expect(result.originalMessage.html).toContain('>Gradido: Email Verification</h1>')
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'Your email address has just been registered with Gradido.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'Please click on this link to complete the registration and activate your Gradido account:',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'<a href="http://localhost/checkEmail/6627633878930542284">http://localhost/checkEmail/6627633878930542284</a>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'or copy the link above into your browser window.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'The link has a validity of 23 hours and 30 minutes. If the validity of the link has already expired, you can have a new link sent to you here:',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`<a href="${CONFIG.EMAIL_LINK_FORGOTPASSWORD}">${CONFIG.EMAIL_LINK_FORGOTPASSWORD}</a>`,
|
||||
)
|
||||
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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -240,54 +198,28 @@ describe('sendEmailVariants', () => {
|
||||
})
|
||||
})
|
||||
|
||||
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: Try To Register Again With Your Email',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('GRADIDO: TRY TO REGISTER AGAIN WITH YOUR EMAIL'),
|
||||
}),
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
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 (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Try To Register Again With Your Email',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('TRY TO REGISTER AGAIN WITH YOUR EMAIL'),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'<title>Gradido: Try To Register Again With Your Email</title>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'>Gradido: Try To Register Again With Your Email</h1>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'Your email address has just been used again to register an account with Gradido.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'However, an account already exists for your email address.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'Please click on the following link if you have forgotten your password:',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`<a href="${CONFIG.EMAIL_LINK_FORGOTPASSWORD}">${CONFIG.EMAIL_LINK_FORGOTPASSWORD}</a>`,
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'or copy the link above into your browser window.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'If you are not the one who tried to register again, please contact our support:<br><a href="mailto:support@supportmail.com">support@supportmail.com</a>',
|
||||
)
|
||||
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>',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -327,8 +259,10 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has expected result', () => {
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
from: 'info@gradido.net',
|
||||
@ -337,37 +271,17 @@ describe('sendEmailVariants', () => {
|
||||
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 contribution to the common good was confirmed',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Your contribution to the common good was confirmed',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining(
|
||||
'GRADIDO: YOUR CONTRIBUTION TO THE COMMON GOOD WAS CONFIRMED',
|
||||
),
|
||||
text: expect.stringContaining('YOUR CONTRIBUTION TO THE COMMON GOOD WAS CONFIRMED'),
|
||||
}),
|
||||
})
|
||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'<title>Gradido: Your contribution to the common good was confirmed</title>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'>Gradido: Your contribution to the common good was confirmed</h1>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'Your public good contribution “My contribution.” has just been confirmed by Bibi Bloxberg and credited to your Gradido account.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Amount: 23.54 GDD')
|
||||
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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -405,7 +319,9 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('result', () => {
|
||||
it('has expected result', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
@ -415,37 +331,17 @@ describe('sendEmailVariants', () => {
|
||||
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 rejected',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Your common good contribution was rejected',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('GRADIDO: YOUR COMMON GOOD CONTRIBUTION WAS REJECTED'),
|
||||
text: expect.stringContaining('YOUR COMMON GOOD CONTRIBUTION WAS REJECTED'),
|
||||
}),
|
||||
})
|
||||
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 rejected</title>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'>Gradido: Your common good contribution was rejected</h1>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'Your public good contribution “My contribution.” was rejected by Bibi Bloxberg.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab!',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||
)
|
||||
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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -483,8 +379,10 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has expected result', () => {
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
from: 'info@gradido.net',
|
||||
@ -493,37 +391,17 @@ describe('sendEmailVariants', () => {
|
||||
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',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Your common good contribution was deleted',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('GRADIDO: YOUR COMMON GOOD CONTRIBUTION WAS DELETED'),
|
||||
text: expect.stringContaining('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 “Creation” menu in your Gradido account and click on the “My contributions” tab!',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||
)
|
||||
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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -559,8 +437,10 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has expected result', () => {
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
from: 'info@gradido.net',
|
||||
@ -569,39 +449,17 @@ describe('sendEmailVariants', () => {
|
||||
message: expect.any(String),
|
||||
originalMessage: expect.objectContaining({
|
||||
to: 'Peter Lustig <peter@lustig.de>',
|
||||
from: 'Gradido (do not answer) <info@gradido.net>',
|
||||
attachments: [],
|
||||
subject: 'Gradido: Reset password',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Reset password',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('GRADIDO: RESET PASSWORD'),
|
||||
text: expect.stringContaining('RESET PASSWORD'),
|
||||
}),
|
||||
})
|
||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||
expect(result.originalMessage.html).toContain('<title>Gradido: Reset password</title>')
|
||||
expect(result.originalMessage.html).toContain('>Gradido: Reset password</h1>')
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'You, or someone else, requested a password reset for this account.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('If it was you, please click on the link:')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'<a href="http://localhost/reset-password/3762660021544901417">http://localhost/reset-password/3762660021544901417</a>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'or copy the link above into your browser window.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'The link has a validity of 23 hours and 30 minutes. If the validity of the link has already expired, you can have a new link sent to you here:',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`<a href="${CONFIG.EMAIL_LINK_FORGOTPASSWORD}">${CONFIG.EMAIL_LINK_FORGOTPASSWORD}</a>`,
|
||||
)
|
||||
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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -643,8 +501,10 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has expected result', () => {
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
from: 'info@gradido.net',
|
||||
@ -653,36 +513,17 @@ describe('sendEmailVariants', () => {
|
||||
message: expect.any(String),
|
||||
originalMessage: expect.objectContaining({
|
||||
to: 'Peter Lustig <peter@lustig.de>',
|
||||
from: 'Gradido (do not answer) <info@gradido.net>',
|
||||
attachments: [],
|
||||
subject: 'Gradido: Bibi Bloxberg has redeemed your Gradido link',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Bibi Bloxberg has redeemed your Gradido link',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('BIBI BLOXBERG HAS REDEEMED YOUR GRADIDO LINK'),
|
||||
}),
|
||||
})
|
||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'<title>Gradido: Bibi Bloxberg has redeemed your Gradido link</title>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'>Gradido: Bibi Bloxberg has redeemed your Gradido link</h1>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'Bibi Bloxberg (bibi@bloxberg.de) has just redeemed your link.',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Amount: 17.65 GDD')
|
||||
expect(result.originalMessage.html).toContain('Message: You deserve it! 🙏🏼')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`You can find transaction details in your Gradido 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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -722,8 +563,10 @@ describe('sendEmailVariants', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('has expected result', () => {
|
||||
describe('result', () => {
|
||||
it('is the expected object', () => {
|
||||
expect(result).toMatchObject({
|
||||
envelope: {
|
||||
from: 'info@gradido.net',
|
||||
@ -732,34 +575,17 @@ describe('sendEmailVariants', () => {
|
||||
message: expect.any(String),
|
||||
originalMessage: expect.objectContaining({
|
||||
to: 'Peter Lustig <peter@lustig.de>',
|
||||
from: 'Gradido (do not answer) <info@gradido.net>',
|
||||
attachments: [],
|
||||
subject: 'Gradido: Bibi Bloxberg has sent you 37.40 Gradido',
|
||||
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
|
||||
attachments: expect.any(Array),
|
||||
subject: 'Bibi Bloxberg has sent you 37.40 Gradido',
|
||||
html: expect.any(String),
|
||||
text: expect.stringContaining('GRADIDO: BIBI BLOXBERG HAS SENT YOU 37.40 GRADIDO'),
|
||||
text: expect.stringContaining('BIBI BLOXBERG HAS SENT YOU 37.40 GRADIDO'),
|
||||
}),
|
||||
})
|
||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'<title>Gradido: Bibi Bloxberg has sent you 37.40 Gradido</title>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'>Gradido: Bibi Bloxberg has sent you 37.40 Gradido</h1>',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||
expect(result.originalMessage.html).toContain(
|
||||
'You have just received 37.40 GDD from Bibi Bloxberg (bibi@bloxberg.de).',
|
||||
)
|
||||
expect(result.originalMessage.html).toContain(
|
||||
`You can find transaction details in your Gradido 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>',
|
||||
)
|
||||
})
|
||||
|
||||
it('has the correct html as snapshot', () => {
|
||||
expect(result.originalMessage.html).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,20 +1,16 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.accountActivation.subject')
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.accountActivation.subject')
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.accountActivation.emailRegistered')
|
||||
p
|
||||
= t('emails.accountActivation.pleaseClickLink')
|
||||
br
|
||||
a(href=activationLink) #{activationLink}
|
||||
br
|
||||
= t('emails.general.orCopyLink')
|
||||
p
|
||||
= t('emails.accountActivation.duration', { hours: timeDurationObject.hours, minutes: timeDurationObject.minutes })
|
||||
br
|
||||
a(href=resendLink) #{resendLink}
|
||||
include ../greatingFormularImprint.pug
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.accountActivation.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.accountActivation.emailRegistered')
|
||||
.content
|
||||
h2= t('emails.general.completeRegistration')
|
||||
div(class="p_content")= t('emails.accountActivation.pleaseClickLink')
|
||||
a.button-3(href=activationLink) #{t('emails.accountActivation.activateAccount')}
|
||||
div(class="p_content")= t('emails.general.orCopyLink')
|
||||
|
||||
a.clink(href=activationLink) #{activationLink}
|
||||
|
||||
include ../includes/requestNewLink.pug
|
||||
|
||||
@ -1,23 +1,22 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.accountMultiRegistration.subject')
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.accountMultiRegistration.subject')
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p
|
||||
= t('emails.accountMultiRegistration.emailReused')
|
||||
br
|
||||
= t('emails.accountMultiRegistration.emailExists')
|
||||
p
|
||||
= t('emails.accountMultiRegistration.onForgottenPasswordClickLink')
|
||||
br
|
||||
a(href=resendLink) #{resendLink}
|
||||
br
|
||||
= t('emails.accountMultiRegistration.onForgottenPasswordCopyLink')
|
||||
p
|
||||
= t('emails.accountMultiRegistration.ifYouAreNotTheOne')
|
||||
br
|
||||
a(href='mailto:' + supportEmail)= supportEmail
|
||||
include ../greatingFormularImprint.pug
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.accountMultiRegistration.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p
|
||||
= t('emails.accountMultiRegistration.emailReused')
|
||||
br
|
||||
= t('emails.accountMultiRegistration.emailExists')
|
||||
.content
|
||||
h2= t('emails.resetPassword.title')
|
||||
div(class="p_content")= t('emails.accountMultiRegistration.onForgottenPasswordClickLink')
|
||||
a.button-3(href=resendLink) #{t('emails.general.reset')}
|
||||
div(class="p_content")= t('emails.general.orCopyLink')
|
||||
|
||||
a.clink(href=resendLink) #{resendLink}
|
||||
|
||||
h2(style="color: red")= t('emails.accountMultiRegistration.contactSupport')
|
||||
div(class="p_content")= t('emails.accountMultiRegistration.ifYouAreNotTheOne')
|
||||
|
||||
a.clink(href='mailto:' + supportEmail)= supportEmail
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.addedContributionMessage.subject')
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.addedContributionMessage.subject')
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.addedContributionMessage.commonGoodContributionMessage', { senderFirstName, senderLastName, contributionMemo })
|
||||
p= t('emails.addedContributionMessage.toSeeAndAnswerMessage')
|
||||
p
|
||||
= t('emails.general.linkToYourAccount')
|
||||
= " "
|
||||
a(href=overviewURL) #{overviewURL}
|
||||
p= t('emails.general.pleaseDoNotReply')
|
||||
include ../greatingFormularImprint.pug
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.addedContributionMessage.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.addedContributionMessage.commonGoodContributionMessage', { senderFirstName, senderLastName, contributionMemo })
|
||||
.content
|
||||
h2= t('emails.addedContributionMessage.readMessage')
|
||||
div(class="p_content")= t('emails.addedContributionMessage.toSeeAndAnswerMessage')
|
||||
|
||||
a.button-3(href=`${communityURL}community/contributions`) #{t('emails.general.toAccount')}
|
||||
|
||||
include ../includes/doNotReply.pug
|
||||
|
||||
@ -1,16 +1,10 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.contributionConfirmed.subject')
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.contributionConfirmed.subject')
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.contributionConfirmed.commonGoodContributionConfirmed', { senderFirstName, senderLastName, contributionMemo })
|
||||
p= t('emails.general.amountGDD', { amountGDD: contributionAmount })
|
||||
p
|
||||
= t('emails.general.linkToYourAccount')
|
||||
= " "
|
||||
a(href=overviewURL) #{overviewURL}
|
||||
p= t('emails.general.pleaseDoNotReply')
|
||||
include ../greatingFormularImprint.pug
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.contributionConfirmed.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.contributionConfirmed.commonGoodContributionConfirmed', { contributionMemo, senderFirstName, senderLastName, amountGDD: contributionAmount })
|
||||
.content
|
||||
include ../includes/contributionDetailsCTA.pug
|
||||
include ../includes/doNotReply.pug
|
||||
@ -1,16 +1,10 @@
|
||||
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
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.contributionDeleted.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.contributionDeleted.commonGoodContributionDeleted', { contributionMemo, senderFirstName, senderLastName })
|
||||
.content
|
||||
include ../includes/contributionDetailsCTA.pug
|
||||
include ../includes/doNotReply.pug
|
||||
|
||||
@ -1,16 +1,10 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.contributionDenied.subject')
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.contributionDenied.subject')
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.contributionDenied.commonGoodContributionDenied', { senderFirstName, senderLastName, contributionMemo })
|
||||
p= t('emails.contributionDenied.toSeeContributionsAndMessages')
|
||||
p
|
||||
= t('emails.general.linkToYourAccount')
|
||||
= " "
|
||||
a(href=overviewURL) #{overviewURL}
|
||||
p= t('emails.general.pleaseDoNotReply')
|
||||
include ../greatingFormularImprint.pug
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.contributionDenied.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.contributionDenied.commonGoodContributionDenied', { contributionMemo, senderFirstName, senderLastName })
|
||||
.content
|
||||
include ../includes/contributionDetailsCTA.pug
|
||||
include ../includes/doNotReply.pug
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
p(style='margin-top: 24px;')
|
||||
= t('emails.general.sincerelyYours')
|
||||
br
|
||||
= t('emails.general.yourGradidoTeam')
|
||||
p(style='margin-top: 24px;')= '—————'
|
||||
p(style='margin-top: 24px;')
|
||||
if t('general.imprintImageURL').length > 0
|
||||
div(style='position: relative; left: -22px;')
|
||||
img(src=t('general.imprintImageURL'), width='200', alt=t('general.imprintImageAlt'))
|
||||
br
|
||||
each line in t('general.imprint').split(/\n/)
|
||||
= line
|
||||
br
|
||||
a(href='mailto:' + supportEmail)= supportEmail
|
||||
br
|
||||
a(href=communityURL)= communityURL
|
||||
@ -0,0 +1,7 @@
|
||||
//-
|
||||
h2= t('emails.general.contributionDetails')
|
||||
div(class="p_content")= t('emails.contribution.toSeeContributionsAndMessages')
|
||||
a.button-3(href=`${communityURL}community/contributions`) #{t('emails.general.toAccount')}
|
||||
div(class="p_content")= t('emails.general.orCopyLink')
|
||||
|
||||
a.clink(href=`${communityURL}community/contributions`) #{`${communityURL}community/contributions`}
|
||||
1
backend/src/emails/templates/includes/doNotReply.pug
Normal file
1
backend/src/emails/templates/includes/doNotReply.pug
Normal file
@ -0,0 +1 @@
|
||||
div(class="p_content")= t('emails.general.pleaseDoNotReply')
|
||||
216
backend/src/emails/templates/includes/email.css
Normal file
216
backend/src/emails/templates/includes/email.css
Normal file
@ -0,0 +1,216 @@
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
/* @font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_c6Dpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
} */
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_cqDpp_k.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url(https://fonts.gstatic.com/s/worksans/v18/QGYsz_wNahGAdqQ43Rh_fKDp.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
BIN
backend/src/emails/templates/includes/facebook-icon.png
Normal file
BIN
backend/src/emails/templates/includes/facebook-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
73
backend/src/emails/templates/includes/footer.pug
Normal file
73
backend/src/emails/templates/includes/footer.pug
Normal file
@ -0,0 +1,73 @@
|
||||
footer
|
||||
.w-container(class="footer_01")
|
||||
.socialmedia
|
||||
a.slink(
|
||||
target="_blank"
|
||||
href="https://www.facebook.com/groups/Gradido/"
|
||||
)
|
||||
img.bi-facebook(
|
||||
alt="facebook"
|
||||
loading="lazy"
|
||||
src="cid:facebookicon"
|
||||
)
|
||||
a.slink(
|
||||
target="_blank"
|
||||
href="https://t.me/GradidoGruppe"
|
||||
)
|
||||
img.bi-telegram(
|
||||
alt="Telegram"
|
||||
loading="lazy"
|
||||
src="cid:telegramicon"
|
||||
)
|
||||
a.slink(
|
||||
target="_blank"
|
||||
href="https://twitter.com/gradido"
|
||||
)
|
||||
img.bi-twitter(
|
||||
alt="Twitter"
|
||||
loading="lazy"
|
||||
src="cid:twittericon"
|
||||
)
|
||||
a.slink(
|
||||
target="_blank"
|
||||
href="https://www.youtube.com/c/GradidoNet"
|
||||
)
|
||||
img.bi-youtube(
|
||||
alt="youtube"
|
||||
loading="lazy"
|
||||
src="cid:youtubeicon"
|
||||
)
|
||||
.line
|
||||
.footer
|
||||
div(class="footer_p1")= t("emails.footer.contactOurSupport")
|
||||
div(class="footer_p2")= t("emails.footer.supportEmail")
|
||||
img.image(
|
||||
alt="Gradido Logo"
|
||||
src="https://gdd.gradido.net/img/brand/green.png"
|
||||
)
|
||||
div
|
||||
a(
|
||||
class="terms_of_use"
|
||||
href="https://gradido.net/de/impressum/"
|
||||
target="_blank"
|
||||
)= t("emails.footer.imprint")
|
||||
br
|
||||
a(
|
||||
class="terms_of_use"
|
||||
href="https://gradido.net/de/datenschutz/"
|
||||
target="_blank"
|
||||
)= t("emails.footer.privacyPolicy")
|
||||
div(class="footer_p1")
|
||||
| Gradido-Akademie
|
||||
br
|
||||
| Institut für Wirtschaftsbionik
|
||||
br
|
||||
| Pfarrweg 2
|
||||
br
|
||||
| 74653 Künzelsau
|
||||
br
|
||||
| Deutschland
|
||||
br
|
||||
br
|
||||
br
|
||||
|
||||
BIN
backend/src/emails/templates/includes/gradido-header.jpeg
Normal file
BIN
backend/src/emails/templates/includes/gradido-header.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
6
backend/src/emails/templates/includes/greeting.pug
Normal file
6
backend/src/emails/templates/includes/greeting.pug
Normal file
@ -0,0 +1,6 @@
|
||||
//- This sets the greeting at the end of every e-mail
|
||||
.text-block
|
||||
p
|
||||
= t('emails.general.sincerelyYours')
|
||||
br
|
||||
= t('emails.general.yourGradidoTeam')
|
||||
13
backend/src/emails/templates/includes/header.pug
Normal file
13
backend/src/emails/templates/includes/header.pug
Normal file
@ -0,0 +1,13 @@
|
||||
header
|
||||
.head
|
||||
//- TODO
|
||||
//- when https://gdd.gradido.net/img/gradido-email-header.jpg is on production,
|
||||
//- replace this URL by https://gdd.gradido.net/img/brand/gradido-email-header.png
|
||||
img.head-logo(
|
||||
alt="Gradido Logo"
|
||||
loading="lazy"
|
||||
src="cid:gradidoheader"
|
||||
)
|
||||
|
||||
|
||||
|
||||
10
backend/src/emails/templates/includes/requestNewLink.pug
Normal file
10
backend/src/emails/templates/includes/requestNewLink.pug
Normal file
@ -0,0 +1,10 @@
|
||||
//-
|
||||
requestNewLink
|
||||
h2= t('emails.general.requestNewLink')
|
||||
|
||||
if timeDurationObject.minutes == 0
|
||||
div(class="p_content")= t('emails.general.linkValidity', { hours: timeDurationObject.hours })
|
||||
else
|
||||
div(class="p_content")= t('emails.general.linkValidityWithMinutes', { hours: timeDurationObject.hours, minutes: timeDurationObject.minutes })
|
||||
|
||||
a.button-4(href=resendLink) #{t('emails.general.newLink')}
|
||||
BIN
backend/src/emails/templates/includes/telegram-icon.png
Normal file
BIN
backend/src/emails/templates/includes/telegram-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
BIN
backend/src/emails/templates/includes/twitter-icon.png
Normal file
BIN
backend/src/emails/templates/includes/twitter-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
136
backend/src/emails/templates/includes/webflow.css
Normal file
136
backend/src/emails/templates/includes/webflow.css
Normal file
@ -0,0 +1,136 @@
|
||||
body{
|
||||
display: block;
|
||||
font-family: "Work Sans", sans-serif;
|
||||
font-size: 17px;
|
||||
text-align: center;
|
||||
text-align: -webkit-center;
|
||||
justify-content: center;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 15px;
|
||||
color: #383838;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.text-block {
|
||||
margin-top: 20px;
|
||||
color: #9ca0a8;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: block;
|
||||
width: 78%;
|
||||
margin: 40px 1% 40px 1%;
|
||||
padding: 20px 10% 40px 10%;
|
||||
border-radius: 24px;
|
||||
background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);
|
||||
}
|
||||
|
||||
.p_content{
|
||||
margin: 15px 0 15px 0;
|
||||
line-height: 26px;
|
||||
color: #9ca0a8;
|
||||
}
|
||||
|
||||
.clink {
|
||||
line-break: anywhere;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.button-3,
|
||||
.button-4 {
|
||||
display: inline-block;
|
||||
padding: 9px 15px;
|
||||
color: white;
|
||||
border: 0;
|
||||
line-height: inherit;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
border-radius: 20px;
|
||||
background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38);
|
||||
box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3);
|
||||
margin: 25px 0 25px 0;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.button-4 {
|
||||
background-image: radial-gradient(circle farthest-corner at 0% 0%, #616161, #c2c2c2);
|
||||
}
|
||||
|
||||
.socialmedia {
|
||||
display: flex;
|
||||
margin-top: 40px;
|
||||
max-width: 600px;
|
||||
|
||||
}
|
||||
.slink{
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.footer_p1 {
|
||||
margin-top: 30px;
|
||||
color: #9ca0a8;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.footer_p2 {
|
||||
color: #383838;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.image {
|
||||
width: 200px;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.div-block {
|
||||
display: table;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 40px;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.terms_of_use {
|
||||
color: #9ca0a8;
|
||||
}
|
||||
|
||||
.text-block-3 {
|
||||
color: #9ca0a8;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.line_image,
|
||||
.line {
|
||||
width: 100%;
|
||||
height: 13px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.line_image {
|
||||
background-image: linear-gradient(90deg, #c58d38, #c58d38 0%, #f3cd7c 35%, #dbb056 54%, #eec05f 63%, #cc9d3d);
|
||||
}
|
||||
|
||||
.line {
|
||||
background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);
|
||||
}
|
||||
BIN
backend/src/emails/templates/includes/youtube-icon.png
Normal file
BIN
backend/src/emails/templates/includes/youtube-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
26
backend/src/emails/templates/layout.pug
Normal file
26
backend/src/emails/templates/layout.pug
Normal file
@ -0,0 +1,26 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
meta(
|
||||
content="multipart/html; charset=UTF-8"
|
||||
http-equiv="content-type"
|
||||
)
|
||||
meta(
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1"
|
||||
)
|
||||
style.
|
||||
.wf-force-outline-none[tabindex="-1"]:focus{outline:none;}
|
||||
style
|
||||
include includes/email.css
|
||||
include includes/webflow.css
|
||||
|
||||
body
|
||||
div.container
|
||||
include includes/header.pug
|
||||
|
||||
.wrapper
|
||||
block content
|
||||
include includes/greeting.pug
|
||||
|
||||
include includes/footer.pug
|
||||
@ -1,20 +1,16 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.resetPassword.subject')
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.resetPassword.subject')
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.resetPassword.youOrSomeoneResetPassword')
|
||||
p
|
||||
= t('emails.resetPassword.pleaseClickLink')
|
||||
br
|
||||
a(href=resetLink) #{resetLink}
|
||||
br
|
||||
= t('emails.general.orCopyLink')
|
||||
p
|
||||
= t('emails.resetPassword.duration', { hours: timeDurationObject.hours, minutes: timeDurationObject.minutes })
|
||||
br
|
||||
a(href=resendLink) #{resendLink}
|
||||
include ../greatingFormularImprint.pug
|
||||
extends ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.resetPassword.title')
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.resetPassword.youOrSomeoneResetPassword')
|
||||
.content
|
||||
h2= t('emails.resetPassword.title')
|
||||
div(class="p_content")= t('emails.resetPassword.pleaseClickLink')
|
||||
a.button-3(href=resetLink) #{t('emails.general.reset')}
|
||||
div(class="p_content")= t('emails.general.orCopyLink')
|
||||
|
||||
a.clink(href=resetLink) #{resetLink}
|
||||
|
||||
include ../includes/requestNewLink.pug
|
||||
|
||||
@ -1,19 +1,18 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.transactionLinkRedeemed.subject', { senderFirstName, senderLastName })
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.transactionLinkRedeemed.subject', { senderFirstName, senderLastName })
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.transactionLinkRedeemed.hasRedeemedYourLink', { senderFirstName, senderLastName, senderEmail })
|
||||
p
|
||||
= t('emails.general.amountGDD', { amountGDD: transactionAmount })
|
||||
br
|
||||
= t('emails.transactionLinkRedeemed.memo', { transactionMemo })
|
||||
p
|
||||
= t('emails.general.detailsYouFindOnLinkToYourAccount')
|
||||
= " "
|
||||
a(href=overviewURL) #{overviewURL}
|
||||
p= t('emails.general.pleaseDoNotReply')
|
||||
include ../greatingFormularImprint.pug
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.transactionLinkRedeemed.title', { senderFirstName, senderLastName })
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.transactionLinkRedeemed.hasRedeemedYourLink', { senderFirstName, senderLastName, senderEmail })
|
||||
.content
|
||||
h2= t('emails.general.transactionDetails')
|
||||
div(class="p_content")= t('emails.general.amountGDD', { amountGDD: transactionAmount })
|
||||
br
|
||||
= t('emails.transactionLinkRedeemed.memo', { transactionMemo })
|
||||
br
|
||||
= t('emails.general.detailsYouFindOnLinkToYourAccount')
|
||||
|
||||
a.button-3(href=`${communityURL}transactions`) #{t('emails.general.toAccount')}
|
||||
|
||||
include ../includes/doNotReply.pug
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.transactionReceived.subject', { senderFirstName, senderLastName, transactionAmount })
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.transactionReceived.subject', { senderFirstName, senderLastName, transactionAmount })
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.transactionReceived.haveReceivedAmountGDDFrom', { transactionAmount, senderFirstName, senderLastName, senderEmail })
|
||||
p
|
||||
= t('emails.general.detailsYouFindOnLinkToYourAccount')
|
||||
= " "
|
||||
a(href=overviewURL) #{overviewURL}
|
||||
p= t('emails.general.pleaseDoNotReply')
|
||||
include ../greatingFormularImprint.pug
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
h2= t('emails.transactionReceived.title', { senderFirstName, senderLastName, transactionAmount })
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p= t('emails.transactionReceived.haveReceivedAmountGDDFrom', { transactionAmount, senderFirstName, senderLastName, senderEmail })
|
||||
.content
|
||||
h2= t('emails.general.transactionDetails')
|
||||
div(class="p_content")= t('emails.general.detailsYouFindOnLinkToYourAccount')
|
||||
|
||||
a.button-3(href=`${communityURL}transactions`) #{t('emails.general.toAccount')}
|
||||
|
||||
include ../includes/doNotReply.pug
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ export class FederationClient {
|
||||
}
|
||||
|
||||
getPublicKey = async (): Promise<string | undefined> => {
|
||||
logger.info('Federation: getPublicKey from endpoint', this.endpoint)
|
||||
logger.debug('Federation: getPublicKey from endpoint', this.endpoint)
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const { data } = await this.client.rawRequest(getPublicKey, {})
|
||||
@ -37,7 +37,7 @@ export class FederationClient {
|
||||
logger.warn('Federation: getPublicKey without response data from endpoint', this.endpoint)
|
||||
return
|
||||
}
|
||||
logger.info(
|
||||
logger.debug(
|
||||
'Federation: getPublicKey successful from endpoint',
|
||||
this.endpoint,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
|
||||
@ -64,7 +64,8 @@ describe('validate Communities', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return { data: {} } as Response<unknown> })
|
||||
return { data: {} } as Response<unknown>
|
||||
})
|
||||
const variables1 = {
|
||||
publicKey: Buffer.from(
|
||||
'1111111111111111111111111111111111111111111111111111111111111111',
|
||||
@ -168,7 +169,7 @@ describe('validate Communities', () => {
|
||||
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
|
||||
})
|
||||
it('logs requestGetPublicKey for community api 1_0 ', () => {
|
||||
expect(logger.info).toBeCalledWith(
|
||||
expect(logger.debug).toBeCalledWith(
|
||||
'Federation: getPublicKey from endpoint',
|
||||
'http//localhost:5001/api/1_0/',
|
||||
)
|
||||
@ -177,7 +178,7 @@ describe('validate Communities', () => {
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
'Federation: received not matching publicKey:',
|
||||
'somePubKey',
|
||||
expect.stringMatching('1111111111111111111111111111111111111111111111111111111111111111'),
|
||||
expect.stringMatching('11111111111111111111111111111111'),
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -189,15 +190,13 @@ describe('validate Communities', () => {
|
||||
return {
|
||||
data: {
|
||||
getPublicKey: {
|
||||
publicKey: '1111111111111111111111111111111111111111111111111111111111111111',
|
||||
publicKey: '11111111111111111111111111111111',
|
||||
},
|
||||
},
|
||||
} as Response<unknown>
|
||||
})
|
||||
const variables1 = {
|
||||
publicKey: Buffer.from(
|
||||
'1111111111111111111111111111111111111111111111111111111111111111',
|
||||
),
|
||||
publicKey: Buffer.from('11111111111111111111111111111111'),
|
||||
apiVersion: '1_0',
|
||||
endPoint: 'http//localhost:5001/api/',
|
||||
lastAnnouncedAt: new Date(),
|
||||
@ -221,15 +220,15 @@ describe('validate Communities', () => {
|
||||
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
|
||||
})
|
||||
it('logs requestGetPublicKey for community api 1_0 ', () => {
|
||||
expect(logger.info).toBeCalledWith(
|
||||
expect(logger.debug).toBeCalledWith(
|
||||
'Federation: getPublicKey from endpoint',
|
||||
'http//localhost:5001/api/1_0/',
|
||||
)
|
||||
})
|
||||
it('logs community pubKey verified', () => {
|
||||
expect(logger.info).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'Federation: verified community with:',
|
||||
expect(logger.debug).toHaveBeenNthCalledWith(
|
||||
6,
|
||||
'Federation: verified community with',
|
||||
'http//localhost:5001/api/',
|
||||
)
|
||||
})
|
||||
@ -243,15 +242,13 @@ describe('validate Communities', () => {
|
||||
return {
|
||||
data: {
|
||||
getPublicKey: {
|
||||
publicKey: '1111111111111111111111111111111111111111111111111111111111111111',
|
||||
publicKey: '11111111111111111111111111111111',
|
||||
},
|
||||
},
|
||||
} as Response<unknown>
|
||||
})
|
||||
const variables2 = {
|
||||
publicKey: Buffer.from(
|
||||
'1111111111111111111111111111111111111111111111111111111111111111',
|
||||
),
|
||||
publicKey: Buffer.from('11111111111111111111111111111111'),
|
||||
apiVersion: '1_1',
|
||||
endPoint: 'http//localhost:5001/api/',
|
||||
lastAnnouncedAt: new Date(),
|
||||
@ -275,13 +272,13 @@ describe('validate Communities', () => {
|
||||
expect(logger.debug).toBeCalledWith(`Federation: found 2 dbCommunities`)
|
||||
})
|
||||
it('logs requestGetPublicKey for community api 1_0 ', () => {
|
||||
expect(logger.info).toBeCalledWith(
|
||||
expect(logger.debug).toBeCalledWith(
|
||||
'Federation: getPublicKey from endpoint',
|
||||
'http//localhost:5001/api/1_0/',
|
||||
)
|
||||
})
|
||||
it('logs requestGetPublicKey for community api 1_1 ', () => {
|
||||
expect(logger.info).toBeCalledWith(
|
||||
expect(logger.debug).toBeCalledWith(
|
||||
'Federation: getPublicKey from endpoint',
|
||||
'http//localhost:5001/api/1_1/',
|
||||
)
|
||||
@ -291,9 +288,7 @@ describe('validate Communities', () => {
|
||||
let dbCom: DbFederatedCommunity
|
||||
beforeEach(async () => {
|
||||
const variables3 = {
|
||||
publicKey: Buffer.from(
|
||||
'1111111111111111111111111111111111111111111111111111111111111111',
|
||||
),
|
||||
publicKey: Buffer.from('11111111111111111111111111111111'),
|
||||
apiVersion: '2_0',
|
||||
endPoint: 'http//localhost:5001/api/',
|
||||
lastAnnouncedAt: new Date(),
|
||||
@ -319,13 +314,13 @@ describe('validate Communities', () => {
|
||||
expect(logger.debug).toBeCalledWith(`Federation: found 3 dbCommunities`)
|
||||
})
|
||||
it('logs requestGetPublicKey for community api 1_0 ', () => {
|
||||
expect(logger.info).toBeCalledWith(
|
||||
expect(logger.debug).toBeCalledWith(
|
||||
'Federation: getPublicKey from endpoint',
|
||||
'http//localhost:5001/api/1_0/',
|
||||
)
|
||||
})
|
||||
it('logs requestGetPublicKey for community api 1_1 ', () => {
|
||||
expect(logger.info).toBeCalledWith(
|
||||
expect(logger.debug).toBeCalledWith(
|
||||
'Federation: getPublicKey from endpoint',
|
||||
'http//localhost:5001/api/1_1/',
|
||||
)
|
||||
|
||||
@ -12,10 +12,13 @@ import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
import { ApiVersionType } from './enum/apiVersionType'
|
||||
|
||||
export function startValidateCommunities(timerInterval: number): void {
|
||||
export async function startValidateCommunities(timerInterval: number): Promise<void> {
|
||||
logger.info(
|
||||
`Federation: startValidateCommunities loop with an interval of ${timerInterval} ms...`,
|
||||
)
|
||||
// delete all foreign federated community entries to avoid increasing validation efforts and log-files
|
||||
await DbFederatedCommunity.delete({ foreign: true })
|
||||
|
||||
// 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() {
|
||||
@ -78,7 +81,7 @@ async function writeForeignCommunity(
|
||||
)}`,
|
||||
)
|
||||
} else {
|
||||
let com = await DbCommunity.findOne({ publicKey: dbCom.publicKey })
|
||||
let com = await DbCommunity.findOneBy({ publicKey: dbCom.publicKey })
|
||||
if (!com) {
|
||||
com = DbCommunity.create()
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { ArgsType, Field, Int, InputType } from 'type-graphql'
|
||||
|
||||
import { ContributionMessageType } from '@enum/ContributionMessageType'
|
||||
|
||||
@InputType()
|
||||
@ArgsType()
|
||||
export class ContributionMessageArgs {
|
||||
@ -8,4 +10,7 @@ export class ContributionMessageArgs {
|
||||
|
||||
@Field(() => String)
|
||||
message: string
|
||||
|
||||
@Field(() => ContributionMessageType, { defaultValue: ContributionMessageType.DIALOG })
|
||||
messageType: ContributionMessageType
|
||||
}
|
||||
|
||||
@ -2,6 +2,9 @@ import { ArgsType, Field, Int } from 'type-graphql'
|
||||
|
||||
@ArgsType()
|
||||
export class CreateUserArgs {
|
||||
@Field(() => String, { nullable: true })
|
||||
alias?: string | null
|
||||
|
||||
@Field(() => String)
|
||||
email: string
|
||||
|
||||
|
||||
@ -5,12 +5,12 @@ import { Order } from '@enum/Order'
|
||||
|
||||
@ArgsType()
|
||||
export class Paginated {
|
||||
@Field(() => Int, { nullable: true })
|
||||
currentPage?: number
|
||||
@Field(() => Int, { defaultValue: 1 })
|
||||
currentPage: number
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
pageSize?: number
|
||||
@Field(() => Int, { defaultValue: 3 })
|
||||
pageSize: number
|
||||
|
||||
@Field(() => Order, { nullable: true })
|
||||
order?: Order
|
||||
@Field(() => Order, { defaultValue: Order.DESC })
|
||||
order: Order
|
||||
}
|
||||
|
||||
18
backend/src/graphql/arg/SearchContributionsFilterArgs.ts
Normal file
18
backend/src/graphql/arg/SearchContributionsFilterArgs.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Field, ArgsType, Int } from 'type-graphql'
|
||||
|
||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||
|
||||
@ArgsType()
|
||||
export class SearchContributionsFilterArgs {
|
||||
@Field(() => [ContributionStatus], { nullable: true, defaultValue: null })
|
||||
statusFilter?: ContributionStatus[] | null
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
userId?: number | null
|
||||
|
||||
@Field(() => String, { nullable: true, defaultValue: '' })
|
||||
query?: string | null
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
noHashtag?: boolean | null
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
import { ArgsType, Field, Int } from 'type-graphql'
|
||||
|
||||
import { SearchUsersFilters } from '@arg/SearchUsersFilters'
|
||||
|
||||
@ArgsType()
|
||||
export class SearchUsersArgs {
|
||||
@Field(() => String)
|
||||
searchText: string
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
// eslint-disable-next-line type-graphql/invalid-nullable-input-type
|
||||
currentPage?: number
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
// eslint-disable-next-line type-graphql/invalid-nullable-input-type
|
||||
pageSize?: number
|
||||
|
||||
// eslint-disable-next-line type-graphql/wrong-decorator-signature
|
||||
@Field(() => SearchUsersFilters, { nullable: true, defaultValue: null })
|
||||
filters?: SearchUsersFilters | null
|
||||
}
|
||||
13
backend/src/graphql/arg/SetUserRoleArgs.ts
Normal file
13
backend/src/graphql/arg/SetUserRoleArgs.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { ArgsType, Field, Int, InputType } from 'type-graphql'
|
||||
|
||||
import { RoleNames } from '@enum/RoleNames'
|
||||
|
||||
@InputType()
|
||||
@ArgsType()
|
||||
export class SetUserRoleArgs {
|
||||
@Field(() => Int)
|
||||
userId: number
|
||||
|
||||
@Field(() => RoleNames, { nullable: true })
|
||||
role: RoleNames | null | undefined
|
||||
}
|
||||
@ -1,10 +1,12 @@
|
||||
import { User } from '@entity/User'
|
||||
import { AuthChecker } from 'type-graphql'
|
||||
|
||||
import { RoleNames } from '@enum/RoleNames'
|
||||
|
||||
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 { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN, ROLE_MODERATOR } from '@/auth/ROLES'
|
||||
import { Context } from '@/server/context'
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
@ -33,10 +35,23 @@ export const isAuthorized: AuthChecker<Context> = async ({ context }, rights) =>
|
||||
try {
|
||||
const user = await User.findOneOrFail({
|
||||
where: { gradidoID: decoded.gradidoID },
|
||||
relations: ['emailContact'],
|
||||
withDeleted: true,
|
||||
relations: ['emailContact', 'userRoles'],
|
||||
})
|
||||
context.user = user
|
||||
context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER
|
||||
context.role = ROLE_USER
|
||||
if (user.userRoles?.length > 0) {
|
||||
switch (user.userRoles[0].role) {
|
||||
case RoleNames.ADMIN:
|
||||
context.role = ROLE_ADMIN
|
||||
break
|
||||
case RoleNames.MODERATOR:
|
||||
context.role = ROLE_MODERATOR
|
||||
break
|
||||
default:
|
||||
context.role = ROLE_USER
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// in case the database query fails (user deleted)
|
||||
throw new LogError('401 Unauthorized')
|
||||
|
||||
@ -3,6 +3,7 @@ import { registerEnumType } from 'type-graphql'
|
||||
export enum ContributionMessageType {
|
||||
HISTORY = 'HISTORY',
|
||||
DIALOG = 'DIALOG',
|
||||
MODERATOR = 'MODERATOR', // messages for moderator communication, can only be seen by moderators
|
||||
}
|
||||
|
||||
registerEnumType(ContributionMessageType, {
|
||||
13
backend/src/graphql/enum/RoleNames.ts
Normal file
13
backend/src/graphql/enum/RoleNames.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum RoleNames {
|
||||
UNAUTHORIZED = 'UNAUTHORIZED',
|
||||
USER = 'USER',
|
||||
MODERATOR = 'MODERATOR',
|
||||
ADMIN = 'ADMIN',
|
||||
}
|
||||
|
||||
registerEnumType(RoleNames, {
|
||||
name: 'RoleNames', // this one is mandatory
|
||||
description: 'Possible role names', // this one is optional
|
||||
})
|
||||
@ -6,6 +6,7 @@ export class AdminUser {
|
||||
constructor(user: User) {
|
||||
this.firstName = user.firstName
|
||||
this.lastName = user.lastName
|
||||
this.role = user.userRoles.length > 0 ? user.userRoles[0].role : ''
|
||||
}
|
||||
|
||||
@Field(() => String)
|
||||
@ -13,6 +14,9 @@ export class AdminUser {
|
||||
|
||||
@Field(() => String)
|
||||
lastName: string
|
||||
|
||||
@Field(() => String)
|
||||
role: string
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
|
||||
@ -15,7 +15,7 @@ export class Contribution {
|
||||
this.confirmedAt = contribution.confirmedAt
|
||||
this.confirmedBy = contribution.confirmedBy
|
||||
this.contributionDate = contribution.contributionDate
|
||||
this.state = contribution.contributionStatus
|
||||
this.status = contribution.contributionStatus
|
||||
this.messagesCount = contribution.messages ? contribution.messages.length : 0
|
||||
this.deniedAt = contribution.deniedAt
|
||||
this.deniedBy = contribution.deniedBy
|
||||
@ -68,7 +68,7 @@ export class Contribution {
|
||||
messagesCount: number
|
||||
|
||||
@Field(() => String)
|
||||
state: string
|
||||
status: string
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
moderatorId: number | null
|
||||
|
||||
@ -1,25 +1,39 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field, Float, Int } from 'type-graphql'
|
||||
|
||||
import { GdtEntryType } from '@enum/GdtEntryType'
|
||||
|
||||
@ObjectType()
|
||||
export class GdtEntry {
|
||||
constructor(json: any) {
|
||||
this.id = json.id
|
||||
this.amount = json.amount
|
||||
this.date = json.date
|
||||
this.email = json.email
|
||||
this.comment = json.comment
|
||||
this.couponCode = json.coupon_code
|
||||
this.gdtEntryType = json.gdt_entry_type_id
|
||||
this.factor = json.factor
|
||||
this.amount2 = json.amount2
|
||||
this.factor2 = json.factor2
|
||||
this.gdt = json.gdt
|
||||
constructor({
|
||||
id,
|
||||
amount,
|
||||
date,
|
||||
email,
|
||||
comment,
|
||||
// eslint-disable-next-line camelcase
|
||||
coupon_code,
|
||||
// eslint-disable-next-line camelcase
|
||||
gdt_entry_type_id,
|
||||
factor,
|
||||
amount2,
|
||||
factor2,
|
||||
gdt,
|
||||
}: any) {
|
||||
this.id = id
|
||||
this.amount = amount
|
||||
this.date = date
|
||||
this.email = email
|
||||
this.comment = comment
|
||||
// eslint-disable-next-line camelcase
|
||||
this.couponCode = coupon_code
|
||||
// eslint-disable-next-line camelcase
|
||||
this.gdtEntryType = gdt_entry_type_id
|
||||
this.factor = factor
|
||||
this.amount2 = amount2
|
||||
this.factor2 = factor2
|
||||
this.gdt = gdt
|
||||
}
|
||||
|
||||
@Field(() => Int)
|
||||
|
||||
@ -1,24 +1,19 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field, Int, Float } from 'type-graphql'
|
||||
|
||||
import { GdtEntry } from './GdtEntry'
|
||||
|
||||
@ObjectType()
|
||||
export class GdtEntryList {
|
||||
constructor(json: any) {
|
||||
this.state = json.state
|
||||
this.count = json.count
|
||||
this.gdtEntries = json.gdtEntries ? json.gdtEntries.map((json: any) => new GdtEntry(json)) : []
|
||||
this.gdtSum = json.gdtSum
|
||||
this.timeUsed = json.timeUsed
|
||||
constructor(status = '', count = 0, gdtEntries = [], gdtSum = 0, timeUsed = 0) {
|
||||
this.status = status
|
||||
this.count = count
|
||||
this.gdtEntries = gdtEntries
|
||||
this.gdtSum = gdtSum
|
||||
this.timeUsed = timeUsed
|
||||
}
|
||||
|
||||
@Field(() => String)
|
||||
state: string
|
||||
status: string
|
||||
|
||||
@Field(() => Int)
|
||||
count: number
|
||||
|
||||
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