mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge remote-tracking branch 'origin/master' into
2501-feature-federation-implement-a-graphql-client-to-request-getpublickey
This commit is contained in:
commit
8cc62c1949
5
.github/workflows/test.yml
vendored
5
.github/workflows/test.yml
vendored
@ -35,7 +35,6 @@ jobs:
|
||||
build_test_admin:
|
||||
name: Docker Build Test - Admin Interface
|
||||
runs-on: ubuntu-latest
|
||||
#needs: [nothing]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -437,7 +436,7 @@ jobs:
|
||||
report_name: Coverage Frontend
|
||||
type: lcov
|
||||
result_path: ./coverage/lcov.info
|
||||
min_coverage: 92
|
||||
min_coverage: 95
|
||||
token: ${{ github.token }}
|
||||
|
||||
##############################################################################
|
||||
@ -479,7 +478,7 @@ jobs:
|
||||
report_name: Coverage Admin Interface
|
||||
type: lcov
|
||||
result_path: ./coverage/lcov.info
|
||||
min_coverage: 95
|
||||
min_coverage: 96
|
||||
token: ${{ github.token }}
|
||||
|
||||
##############################################################################
|
||||
|
||||
68
CHANGELOG.md
68
CHANGELOG.md
@ -4,8 +4,76 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [1.17.1](https://github.com/gradido/gradido/compare/1.17.0...1.17.1)
|
||||
|
||||
- refactor(frontend): change contribution memo add word-break [`#2583`](https://github.com/gradido/gradido/pull/2583)
|
||||
- refactor(admin): add text-break on all table memo fields [`#2584`](https://github.com/gradido/gradido/pull/2584)
|
||||
- fix(frontend): throw proper frontend warning errors [`#2586`](https://github.com/gradido/gradido/pull/2586)
|
||||
- refactor(frontend): equalize en with fr languages. [`#2585`](https://github.com/gradido/gradido/pull/2585)
|
||||
- refactor(frontend): forgot password unit tests [`#2582`](https://github.com/gradido/gradido/pull/2582)
|
||||
- fix(frontend): fix min value for hours input [`#2581`](https://github.com/gradido/gradido/pull/2581)
|
||||
- fix(frontend): change dropdown placement calender no-flip true [`#2580`](https://github.com/gradido/gradido/pull/2580)
|
||||
- refactor(frontend): link send result style [`#2577`](https://github.com/gradido/gradido/pull/2577)
|
||||
- refactor(frontend): remove vertical scrolling & small fixes [`#2578`](https://github.com/gradido/gradido/pull/2578)
|
||||
- refactor(frontend): tyle mobile device auth template [`#2576`](https://github.com/gradido/gradido/pull/2576)
|
||||
|
||||
#### [1.17.0](https://github.com/gradido/gradido/compare/1.16.0...1.17.0)
|
||||
|
||||
> 18 January 2023
|
||||
|
||||
- chore(release): v1.17.0 [`#2575`](https://github.com/gradido/gradido/pull/2575)
|
||||
- fix(frontend): submit contribution text [`#2573`](https://github.com/gradido/gradido/pull/2573)
|
||||
- fix(backend): admin cannot delete confirmed contribution [`#2571`](https://github.com/gradido/gradido/pull/2571)
|
||||
- fix(frontend): english locales - horas -> hours [`#2572`](https://github.com/gradido/gradido/pull/2572)
|
||||
- fix(frontend): mobil divices datepicker add props dropleft [`#2570`](https://github.com/gradido/gradido/pull/2570)
|
||||
- fix(frontend): pagination [`#2569`](https://github.com/gradido/gradido/pull/2569)
|
||||
- fix(frontend): add a watch on gdt prop to assure propper loading when mounted [`#2568`](https://github.com/gradido/gradido/pull/2568)
|
||||
- refactor(frontend): creation step in quarter hour set [`#2566`](https://github.com/gradido/gradido/pull/2566)
|
||||
- fix(frontend): tunneled email on right side last transactions [`#2561`](https://github.com/gradido/gradido/pull/2561)
|
||||
- feat(frontend): test transaction page [`#2555`](https://github.com/gradido/gradido/pull/2555)
|
||||
- refactor(backend): statistics with field resolvers [`#2553`](https://github.com/gradido/gradido/pull/2553)
|
||||
- fix(frontend): normalized amount transaction if processed again [`#2550`](https://github.com/gradido/gradido/pull/2550)
|
||||
- fix(backend): semaphore deadlock [`#2551`](https://github.com/gradido/gradido/pull/2551)
|
||||
- fix(frontend): mobile design [`#2552`](https://github.com/gradido/gradido/pull/2552)
|
||||
- refactor(frontend): slots for right sidebar and header [`#2548`](https://github.com/gradido/gradido/pull/2548)
|
||||
- fix(frontend): creation menu highlighted on all submenus [`#2527`](https://github.com/gradido/gradido/pull/2527)
|
||||
- refactor(frontend): computed hours for open creations [`#2545`](https://github.com/gradido/gradido/pull/2545)
|
||||
- feat(other): add description for daily backup cronjob [`#2532`](https://github.com/gradido/gradido/pull/2532)
|
||||
- fix(frontend): editing transaction does not work [`#2543`](https://github.com/gradido/gradido/pull/2543)
|
||||
- refactor(frontend): remove open creations from store [`#2541`](https://github.com/gradido/gradido/pull/2541)
|
||||
- feat(other): vscode extensions [`#2524`](https://github.com/gradido/gradido/pull/2524)
|
||||
- fix(backend): remove jest from dependecies [`#2533`](https://github.com/gradido/gradido/pull/2533)
|
||||
- fix(frontend): initials without space [`#2546`](https://github.com/gradido/gradido/pull/2546)
|
||||
- fix(backend): fix backend not confirmable [`#2539`](https://github.com/gradido/gradido/pull/2539)
|
||||
- fix(frontend): send gdd and send link gdd is running [`#2534`](https://github.com/gradido/gradido/pull/2534)
|
||||
- fix(other): update browser list [`#2540`](https://github.com/gradido/gradido/pull/2540)
|
||||
- test(backend): increase backend coverage to 78% [`#2542`](https://github.com/gradido/gradido/pull/2542)
|
||||
- fix(frontend): pagination gdt [`#2525`](https://github.com/gradido/gradido/pull/2525)
|
||||
- fix(frontend): leaves are over the user symbol [`#2526`](https://github.com/gradido/gradido/pull/2526)
|
||||
- fix(frontend): input-email label and placeholder are displayed correctly per language [`#2528`](https://github.com/gradido/gradido/pull/2528)
|
||||
- feat(backend): add hideAmountGDD & hideAmountGDT to users table. [`#2506`](https://github.com/gradido/gradido/pull/2506)
|
||||
- fix(frontend): avatar initials always has 2 letters [`#2530`](https://github.com/gradido/gradido/pull/2530)
|
||||
- refactor(backend): seed contributions as user [`#2460`](https://github.com/gradido/gradido/pull/2460)
|
||||
- fix(backend): fix logger middleware [`#2503`](https://github.com/gradido/gradido/pull/2503)
|
||||
- fix(backend): fix email text [`#2523`](https://github.com/gradido/gradido/pull/2523)
|
||||
- feat(other): new scopes for lint pr [`#2489`](https://github.com/gradido/gradido/pull/2489)
|
||||
- fix(backend): fix config - some typos [`#2477`](https://github.com/gradido/gradido/pull/2477)
|
||||
- style(frontend): new Design [`#2297`](https://github.com/gradido/gradido/pull/2297)
|
||||
- refactor(other): adjust some texts and translations [`#2504`](https://github.com/gradido/gradido/pull/2504)
|
||||
- test(other): fix tests breaking with the new year [`#2505`](https://github.com/gradido/gradido/pull/2505)
|
||||
- feat(backend): federation implement exchange of api versions persist in table [`#2427`](https://github.com/gradido/gradido/pull/2427)
|
||||
- feat(backend): semaphore to lock transaction table [`#2458`](https://github.com/gradido/gradido/pull/2458)
|
||||
- feat(backend): design html emails and adjust texts [`#2472`](https://github.com/gradido/gradido/pull/2472)
|
||||
- feat(backend): test semaphore [`#2468`](https://github.com/gradido/gradido/pull/2468)
|
||||
- fix(admin): reduce triggers of success toast on deleted user form to exactly one [`#2471`](https://github.com/gradido/gradido/pull/2471)
|
||||
- refactor(other): build nginx docker image in workflow independent of other builds [`#2470`](https://github.com/gradido/gradido/pull/2470)
|
||||
- feat(backend): setup unit tests for federation [`#2465`](https://github.com/gradido/gradido/pull/2465)
|
||||
|
||||
#### [1.16.0](https://github.com/gradido/gradido/compare/1.15.0...1.16.0)
|
||||
|
||||
> 15 December 2022
|
||||
|
||||
- feat(release): version 1.16.0 [`#2467`](https://github.com/gradido/gradido/pull/2467)
|
||||
- refactor(backend): cleaning user related old password junk [`#2426`](https://github.com/gradido/gradido/pull/2426)
|
||||
- fix(database): consistent transaction table [`#2453`](https://github.com/gradido/gradido/pull/2453)
|
||||
- refactor(backend): dissolve admin resolver [`#2416`](https://github.com/gradido/gradido/pull/2416)
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Administraion Interface for Gradido",
|
||||
"main": "index.js",
|
||||
"author": "Moriz Wahl",
|
||||
"version": "1.16.0",
|
||||
"version": "1.17.1",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
|
||||
@ -88,7 +88,7 @@ export default {
|
||||
return `${value} GDD`
|
||||
},
|
||||
},
|
||||
{ key: 'memo', label: this.$t('transactionlist.memo') },
|
||||
{ key: 'memo', label: this.$t('transactionlist.memo'), class: 'text-break' },
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
@ -5,6 +5,7 @@ const localVue = global.localVue
|
||||
|
||||
const apolloMutateMock = jest.fn().mockResolvedValue({})
|
||||
const apolloQueryMock = jest.fn().mockResolvedValue({})
|
||||
const toggleDetailsMock = jest.fn()
|
||||
|
||||
const propsData = {
|
||||
items: [
|
||||
@ -57,7 +58,7 @@ const propsData = {
|
||||
return value + ' GDD'
|
||||
},
|
||||
},
|
||||
{ key: 'memo', label: 'text' },
|
||||
{ key: 'memo', label: 'text', class: 'text-break' },
|
||||
{
|
||||
key: 'date',
|
||||
label: 'date',
|
||||
@ -138,5 +139,50 @@ describe('OpenCreationsTable', () => {
|
||||
expect(wrapper.vm.items[0].creation).toEqual([444, 555, 666])
|
||||
})
|
||||
})
|
||||
|
||||
describe('call updateState', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.updateState(4)
|
||||
})
|
||||
|
||||
it('emits update-state', () => {
|
||||
expect(wrapper.vm.$root.$emit('update-state', 4)).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('call updateCreationData', () => {
|
||||
const date = new Date()
|
||||
beforeEach(() => {
|
||||
wrapper.vm.updateCreationData({
|
||||
amount: Number(80.0),
|
||||
date: date,
|
||||
memo: 'Test memo',
|
||||
row: {
|
||||
item: {},
|
||||
detailsShowing: false,
|
||||
toggleDetails: toggleDetailsMock,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('emits update-state', () => {
|
||||
expect(
|
||||
wrapper.vm.$emit('update-contributions', {
|
||||
amount: Number(80.0),
|
||||
date: date,
|
||||
memo: 'Test memo',
|
||||
row: {
|
||||
item: {},
|
||||
detailsShowing: false,
|
||||
toggleDetails: toggleDetailsMock,
|
||||
},
|
||||
}),
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
it('calls toggleDetails', () => {
|
||||
expect(toggleDetailsMock).toBeCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
@click="$emit('remove-creation', row.item)"
|
||||
class="mr-2"
|
||||
>
|
||||
<b-icon icon="x" variant="light"></b-icon>
|
||||
<b-icon icon="trash" variant="light"></b-icon>
|
||||
</b-button>
|
||||
</template>
|
||||
<template #cell(editCreation)="row">
|
||||
@ -49,6 +49,18 @@
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell(deny)="row">
|
||||
<div v-if="$store.state.moderator.id !== row.item.userId">
|
||||
<b-button
|
||||
variant="danger"
|
||||
size="md"
|
||||
@click="$emit('deny-creation', row.item)"
|
||||
class="mr-2"
|
||||
>
|
||||
<b-icon icon="x" variant="light"></b-icon>
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #row-details="row">
|
||||
<row-details
|
||||
:row="row"
|
||||
|
||||
@ -67,7 +67,7 @@ export default {
|
||||
return `${value} GDD`
|
||||
},
|
||||
},
|
||||
{ key: 'memo', label: this.$t('transactionlist.memo') },
|
||||
{ key: 'memo', label: this.$t('transactionlist.memo'), class: 'text-break' },
|
||||
{
|
||||
key: 'validUntil',
|
||||
label: this.$t('transactionlink.valid_until'),
|
||||
|
||||
7
admin/src/graphql/denyContribution.js
Normal file
7
admin/src/graphql/denyContribution.js
Normal file
@ -0,0 +1,7 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const denyContribution = gql`
|
||||
mutation ($id: Int!) {
|
||||
denyContribution(id: $id)
|
||||
}
|
||||
`
|
||||
@ -35,6 +35,7 @@
|
||||
"creation_failed": "Ausstehende Schöpfung für {email} konnte nicht erzeugt werden.",
|
||||
"creation_for": "Aktives Grundeinkommen für",
|
||||
"deleteNow": "Möchtest du diesen Beitrag zur Gemeinschaft wirklich löschen?",
|
||||
"denyNow": "Möchtest du diesen Beitrag zur Gemeinschaft wirklich ablehnen?",
|
||||
"enter_text": "Text eintragen",
|
||||
"form": "Schöpfungsformular",
|
||||
"min_characters": "Mindestens 10 Zeichen eingeben",
|
||||
@ -45,6 +46,7 @@
|
||||
"toasted": "Offene Schöpfung ({value} GDD) für {email} wurde gespeichert und liegt zur Bestätigung bereit",
|
||||
"toasted_created": "Schöpfung wurde erfolgreich gespeichert",
|
||||
"toasted_delete": "Offene Schöpfung wurde gelöscht",
|
||||
"toasted_denied": "Offene Schöpfung wurde abgelehnt",
|
||||
"toasted_update": "`Offene Schöpfung {value} GDD) für {email} wurde geändert und liegt zur Bestätigung bereit",
|
||||
"update_creation": "Schöpfung aktualisieren"
|
||||
},
|
||||
@ -54,6 +56,7 @@
|
||||
"deleted": "gelöscht",
|
||||
"deleted_user": "Alle gelöschten Nutzer",
|
||||
"delete_user": "Nutzer löschen",
|
||||
"deny": "Ablehnen",
|
||||
"edit": "Bearbeiten",
|
||||
"enabled": "aktiviert",
|
||||
"error": "Fehler",
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
"creation_failed": "Could not create pending creation for {email}",
|
||||
"creation_for": "Active Basic Income for",
|
||||
"deleteNow": "Do you really want to delete this contribution to the community?",
|
||||
"denyNow": "Do you really want to reject this contribution to the community?",
|
||||
"enter_text": "Enter text",
|
||||
"form": "Creation form",
|
||||
"min_characters": "Enter at least 10 characters",
|
||||
@ -45,6 +46,7 @@
|
||||
"toasted": "Open creation ({value} GDD) for {email} has been saved and is ready for confirmation.",
|
||||
"toasted_created": "Creation has been successfully saved",
|
||||
"toasted_delete": "Open creation has been deleted",
|
||||
"toasted_denied": "Open creation has been denied",
|
||||
"toasted_update": "Open creation {value} GDD) for {email} has been changed and is ready for confirmation.",
|
||||
"update_creation": "Creation update"
|
||||
},
|
||||
@ -54,6 +56,7 @@
|
||||
"deleted": "deleted",
|
||||
"deleted_user": "All deleted user",
|
||||
"delete_user": "Delete user",
|
||||
"deny": "Reject",
|
||||
"edit": "Edit",
|
||||
"enabled": "enabled",
|
||||
"error": "Error",
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import CreationConfirm from './CreationConfirm.vue'
|
||||
import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
|
||||
import { denyContribution } from '../graphql/denyContribution'
|
||||
import { listUnconfirmedContributions } from '../graphql/listUnconfirmedContributions'
|
||||
import { confirmContribution } from '../graphql/confirmContribution'
|
||||
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
|
||||
@ -75,6 +76,7 @@ describe('CreationConfirm', () => {
|
||||
|
||||
const listUnconfirmedContributionsMock = jest.fn()
|
||||
const adminDeleteContributionMock = jest.fn()
|
||||
const adminDenyContributionMock = jest.fn()
|
||||
const confirmContributionMock = jest.fn()
|
||||
|
||||
mockClient.setRequestHandler(
|
||||
@ -89,6 +91,13 @@ describe('CreationConfirm', () => {
|
||||
adminDeleteContributionMock.mockResolvedValue({ data: { adminDeleteContribution: true } }),
|
||||
)
|
||||
|
||||
mockClient.setRequestHandler(
|
||||
denyContribution,
|
||||
adminDenyContributionMock
|
||||
.mockRejectedValueOnce({ message: 'Ouch!' })
|
||||
.mockResolvedValue({ data: { denyContribution: true } }),
|
||||
)
|
||||
|
||||
mockClient.setRequestHandler(
|
||||
confirmContribution,
|
||||
confirmContributionMock.mockResolvedValue({ data: { confirmContribution: true } }),
|
||||
@ -243,5 +252,59 @@ describe('CreationConfirm', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('deny creation with error', () => {
|
||||
let spy
|
||||
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve('some value'))
|
||||
await wrapper.findAll('tr').at(1).findAll('button').at(0).trigger('click')
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Ouchhh!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('deny creation with success', () => {
|
||||
let spy
|
||||
|
||||
describe('admin confirms deny', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve('some value'))
|
||||
await wrapper.findAll('tr').at(1).findAll('button').at(3).trigger('click')
|
||||
})
|
||||
|
||||
it('opens a modal', () => {
|
||||
expect(spy).toBeCalled()
|
||||
})
|
||||
|
||||
it('calls the adminDeleteContribution mutation', () => {
|
||||
expect(adminDenyContributionMock).toBeCalledWith({ id: 1 })
|
||||
})
|
||||
|
||||
it('commits openCreationsMinus to store', () => {
|
||||
expect(storeCommitMock).toBeCalledWith('openCreationsMinus', 1)
|
||||
})
|
||||
|
||||
it('toasts a success message', () => {
|
||||
expect(toastSuccessSpy).toBeCalledWith('creation_form.toasted_denied')
|
||||
})
|
||||
})
|
||||
|
||||
describe('admin cancels deny', () => {
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
|
||||
spy.mockImplementation(() => Promise.resolve(false))
|
||||
await wrapper.findAll('tr').at(1).findAll('button').at(3).trigger('click')
|
||||
})
|
||||
|
||||
it('does not call the adminDeleteContribution mutation', () => {
|
||||
expect(adminDenyContributionMock).not.toBeCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
class="mt-4"
|
||||
:items="pendingCreations"
|
||||
:fields="fields"
|
||||
@deny-creation="denyCreation"
|
||||
@remove-creation="removeCreation"
|
||||
@show-overlay="showOverlay"
|
||||
@update-state="updateState"
|
||||
@ -20,6 +21,7 @@ import OpenCreationsTable from '../components/Tables/OpenCreationsTable.vue'
|
||||
import { listUnconfirmedContributions } from '../graphql/listUnconfirmedContributions'
|
||||
import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
|
||||
import { confirmContribution } from '../graphql/confirmContribution'
|
||||
import { denyContribution } from '../graphql/denyContribution'
|
||||
|
||||
export default {
|
||||
name: 'CreationConfirm',
|
||||
@ -35,6 +37,26 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
denyCreation(item) {
|
||||
this.$bvModal.msgBoxConfirm(this.$t('creation_form.denyNow')).then(async (value) => {
|
||||
if (value) {
|
||||
await this.$apollo
|
||||
.mutate({
|
||||
mutation: denyContribution,
|
||||
variables: {
|
||||
id: item.id,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
this.updatePendingCreations(item.id)
|
||||
this.toastSuccess(this.$t('creation_form.toasted_denied'))
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toastError(error.message)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
removeCreation(item) {
|
||||
this.$bvModal.msgBoxConfirm(this.$t('creation_form.deleteNow')).then(async (value) => {
|
||||
if (value)
|
||||
@ -99,7 +121,7 @@ export default {
|
||||
return value + ' GDD'
|
||||
},
|
||||
},
|
||||
{ key: 'memo', label: this.$t('text') },
|
||||
{ key: 'memo', label: this.$t('text'), class: 'text-break' },
|
||||
{
|
||||
key: 'date',
|
||||
label: this.$t('date'),
|
||||
@ -110,6 +132,7 @@ export default {
|
||||
{ key: 'moderator', label: this.$t('moderator') },
|
||||
{ key: 'editCreation', label: this.$t('edit') },
|
||||
{ key: 'confirm', label: this.$t('save') },
|
||||
{ key: 'deny', label: this.$t('deny') },
|
||||
]
|
||||
},
|
||||
},
|
||||
|
||||
@ -4141,9 +4141,9 @@ caniuse-api@^3.0.0:
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001271:
|
||||
version "1.0.30001442"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz"
|
||||
integrity sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==
|
||||
version "1.0.30001445"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001445.tgz"
|
||||
integrity sha512-8sdQIdMztYmzfTMO6KfLny878Ln9c2M0fc7EH60IjlP4Dc4PiCy7K2Vl3ITmWgOyPgVQKa5x+UP/KqFsxj4mBg==
|
||||
|
||||
capture-exit@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-backend",
|
||||
"version": "1.16.0",
|
||||
"version": "1.17.1",
|
||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/backend",
|
||||
|
||||
@ -54,4 +54,5 @@ export enum RIGHTS {
|
||||
DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK',
|
||||
UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK',
|
||||
ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',
|
||||
DENY_CONTRIBUTION = 'DENY_CONTRIBUTION',
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
sendAccountActivationEmail,
|
||||
sendAccountMultiRegistrationEmail,
|
||||
sendContributionConfirmedEmail,
|
||||
sendContributionRejectedEmail,
|
||||
sendContributionDeniedEmail,
|
||||
sendResetPasswordEmail,
|
||||
sendTransactionLinkRedeemedEmail,
|
||||
sendTransactionReceivedEmail,
|
||||
@ -360,9 +360,9 @@ describe('sendEmailVariants', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('sendContributionRejectedEmail', () => {
|
||||
describe('sendContributionDeniedEmail', () => {
|
||||
beforeAll(async () => {
|
||||
result = await sendContributionRejectedEmail({
|
||||
result = await sendContributionDeniedEmail({
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
email: 'peter@lustig.de',
|
||||
@ -379,7 +379,7 @@ describe('sendEmailVariants', () => {
|
||||
receiver: {
|
||||
to: 'Peter Lustig <peter@lustig.de>',
|
||||
},
|
||||
template: 'contributionRejected',
|
||||
template: 'contributionDenied',
|
||||
locals: {
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
|
||||
@ -103,7 +103,7 @@ export const sendContributionConfirmedEmail = (data: {
|
||||
})
|
||||
}
|
||||
|
||||
export const sendContributionRejectedEmail = (data: {
|
||||
export const sendContributionDeniedEmail = (data: {
|
||||
firstName: string
|
||||
lastName: string
|
||||
email: string
|
||||
@ -114,7 +114,7 @@ export const sendContributionRejectedEmail = (data: {
|
||||
}): Promise<Record<string, unknown> | null> => {
|
||||
return sendEmailTranslated({
|
||||
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
|
||||
template: 'contributionRejected',
|
||||
template: 'contributionDenied',
|
||||
locals: {
|
||||
firstName: data.firstName,
|
||||
lastName: data.lastName,
|
||||
|
||||
16
backend/src/emails/templates/contributionDenied/html.pug
Normal file
16
backend/src/emails/templates/contributionDenied/html.pug
Normal file
@ -0,0 +1,16 @@
|
||||
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
|
||||
@ -0,0 +1 @@
|
||||
= t('emails.contributionDenied.subject')
|
||||
@ -1,16 +0,0 @@
|
||||
doctype html
|
||||
html(lang=locale)
|
||||
head
|
||||
title= t('emails.contributionRejected.subject')
|
||||
body
|
||||
h1(style='margin-bottom: 24px;')= t('emails.contributionRejected.subject')
|
||||
#container.col
|
||||
include ../hello.pug
|
||||
p= t('emails.contributionRejected.commonGoodContributionRejected', { senderFirstName, senderLastName, contributionMemo })
|
||||
p= t('emails.contributionRejected.toSeeContributionsAndMessages')
|
||||
p
|
||||
= t('emails.general.linkToYourAccount')
|
||||
= " "
|
||||
a(href=overviewURL) #{overviewURL}
|
||||
p= t('emails.general.pleaseDoNotReply')
|
||||
include ../greatingFormularImprint.pug
|
||||
@ -1 +0,0 @@
|
||||
= t('emails.contributionRejected.subject')
|
||||
@ -7,6 +7,7 @@ class EventProtocolEmitter {
|
||||
/* }extends EventEmitter { */
|
||||
private events: Event[]
|
||||
|
||||
/*
|
||||
public addEvent(event: Event) {
|
||||
this.events.push(event)
|
||||
}
|
||||
@ -14,6 +15,7 @@ class EventProtocolEmitter {
|
||||
public getEvents(): Event[] {
|
||||
return this.events
|
||||
}
|
||||
*/
|
||||
|
||||
public isDisabled() {
|
||||
logger.info(`EventProtocol - isDisabled=${CONFIG.EVENT_PROTOCOL_DISABLED}`)
|
||||
|
||||
@ -1 +0,0 @@
|
||||
declare module '@hyperswarm/dht'
|
||||
@ -1,798 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import { startDHT } from './index'
|
||||
import DHT from '@hyperswarm/dht'
|
||||
import CONFIG from '@/config'
|
||||
import { logger } from '@test/testSetup'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
import { testEnvironment, cleanDB } from '@test/helpers'
|
||||
|
||||
CONFIG.FEDERATION_DHT_SEED = '64ebcb0e3ad547848fef4197c6e2332f'
|
||||
|
||||
jest.mock('@hyperswarm/dht')
|
||||
|
||||
const TEST_TOPIC = 'gradido_test_topic'
|
||||
|
||||
const keyPairMock = {
|
||||
publicKey: Buffer.from('publicKey'),
|
||||
secretKey: Buffer.from('secretKey'),
|
||||
}
|
||||
|
||||
const serverListenSpy = jest.fn()
|
||||
|
||||
const serverEventMocks: { [key: string]: any } = {}
|
||||
|
||||
const serverOnMock = jest.fn().mockImplementation((key: string, callback) => {
|
||||
serverEventMocks[key] = callback
|
||||
})
|
||||
|
||||
const nodeCreateServerMock = jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
on: serverOnMock,
|
||||
listen: serverListenSpy,
|
||||
}
|
||||
})
|
||||
|
||||
const nodeAnnounceMock = jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
finished: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
const lookupResultMock = {
|
||||
token: Buffer.from(TEST_TOPIC),
|
||||
from: {
|
||||
id: Buffer.from('somone'),
|
||||
host: '188.95.53.5',
|
||||
port: 63561,
|
||||
},
|
||||
to: { id: null, host: '83.53.31.27', port: 55723 },
|
||||
peers: [
|
||||
{
|
||||
publicKey: Buffer.from('some-public-key'),
|
||||
relayAddresses: [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const nodeLookupMock = jest.fn().mockResolvedValue([lookupResultMock])
|
||||
|
||||
const socketEventMocks: { [key: string]: any } = {}
|
||||
|
||||
const socketOnMock = jest.fn().mockImplementation((key: string, callback) => {
|
||||
socketEventMocks[key] = callback
|
||||
})
|
||||
|
||||
const socketWriteMock = jest.fn()
|
||||
|
||||
const nodeConnectMock = jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
on: socketOnMock,
|
||||
once: socketOnMock,
|
||||
write: socketWriteMock,
|
||||
}
|
||||
})
|
||||
|
||||
DHT.hash.mockImplementation(() => {
|
||||
return Buffer.from(TEST_TOPIC)
|
||||
})
|
||||
|
||||
DHT.keyPair.mockImplementation(() => {
|
||||
return keyPairMock
|
||||
})
|
||||
|
||||
DHT.mockImplementation(() => {
|
||||
return {
|
||||
createServer: nodeCreateServerMock,
|
||||
announce: nodeAnnounceMock,
|
||||
lookup: nodeLookupMock,
|
||||
connect: nodeConnectMock,
|
||||
}
|
||||
})
|
||||
|
||||
let con: any
|
||||
let testEnv: any
|
||||
|
||||
beforeAll(async () => {
|
||||
testEnv = await testEnvironment(logger)
|
||||
con = testEnv.con
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
await con.close()
|
||||
})
|
||||
|
||||
describe('federation', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers()
|
||||
})
|
||||
|
||||
describe('call startDHT', () => {
|
||||
const hashSpy = jest.spyOn(DHT, 'hash')
|
||||
const keyPairSpy = jest.spyOn(DHT, 'keyPair')
|
||||
beforeEach(async () => {
|
||||
DHT.mockClear()
|
||||
jest.clearAllMocks()
|
||||
await startDHT(TEST_TOPIC)
|
||||
})
|
||||
|
||||
it('calls DHT.hash', () => {
|
||||
expect(hashSpy).toBeCalledWith(Buffer.from(TEST_TOPIC))
|
||||
})
|
||||
|
||||
it('creates a key pair', () => {
|
||||
expect(keyPairSpy).toBeCalledWith(expect.any(Buffer))
|
||||
})
|
||||
|
||||
it('initializes a new DHT object', () => {
|
||||
expect(DHT).toBeCalledWith({ keyPair: keyPairMock })
|
||||
})
|
||||
|
||||
describe('DHT node', () => {
|
||||
it('creates a server', () => {
|
||||
expect(nodeCreateServerMock).toBeCalled()
|
||||
})
|
||||
|
||||
it('listens on the server', () => {
|
||||
expect(serverListenSpy).toBeCalled()
|
||||
})
|
||||
|
||||
describe('timers', () => {
|
||||
beforeEach(() => {
|
||||
jest.runOnlyPendingTimers()
|
||||
})
|
||||
|
||||
it('announces on topic', () => {
|
||||
expect(nodeAnnounceMock).toBeCalledWith(Buffer.from(TEST_TOPIC), keyPairMock)
|
||||
})
|
||||
|
||||
it('looks up on topic', () => {
|
||||
expect(nodeLookupMock).toBeCalledWith(Buffer.from(TEST_TOPIC))
|
||||
})
|
||||
})
|
||||
|
||||
describe('server connection event', () => {
|
||||
beforeEach(() => {
|
||||
serverEventMocks.connection({
|
||||
remotePublicKey: Buffer.from('another-public-key'),
|
||||
on: socketOnMock,
|
||||
})
|
||||
})
|
||||
|
||||
it('can be triggered', () => {
|
||||
expect(socketOnMock).toBeCalled()
|
||||
})
|
||||
|
||||
describe('socket events', () => {
|
||||
describe('on data', () => {
|
||||
describe('with receiving simply a string', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
socketEventMocks.data(Buffer.from('no-json string'))
|
||||
})
|
||||
|
||||
it('logs the received data', () => {
|
||||
expect(logger.info).toBeCalledWith('data: no-json string')
|
||||
})
|
||||
|
||||
it('logs an error of unexpected data format and structure', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Error on receiving data from socket:',
|
||||
new SyntaxError('Unexpected token o in JSON at position 1'),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving array of strings', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
const strArray: string[] = ['invalid type test', 'api', 'url']
|
||||
socketEventMocks.data(Buffer.from(strArray.toString()))
|
||||
})
|
||||
|
||||
it('logs the received data', () => {
|
||||
expect(logger.info).toBeCalledWith('data: invalid type test,api,url')
|
||||
})
|
||||
|
||||
it('logs an error of unexpected data format and structure', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Error on receiving data from socket:',
|
||||
new SyntaxError('Unexpected token i in JSON at position 0'),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving array of string-arrays', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const strArray: string[][] = [
|
||||
[`api`, `url`, `invalid type in array test`],
|
||||
[`wrong`, `api`, `url`],
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(strArray.toString()))
|
||||
})
|
||||
|
||||
it('logs the received data', () => {
|
||||
expect(logger.info).toBeCalledWith(
|
||||
'data: api,url,invalid type in array test,wrong,api,url',
|
||||
)
|
||||
})
|
||||
|
||||
it('logs an error of unexpected data format and structure', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Error on receiving data from socket:',
|
||||
new SyntaxError('Unexpected token a in JSON at position 0'),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving JSON-Array with too much entries', () => {
|
||||
let jsonArray: { api: string; url: string }[]
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
jsonArray = [
|
||||
{ api: 'v1_0', url: 'too much versions at the same time test' },
|
||||
{ api: 'v1_0', url: 'url2' },
|
||||
{ api: 'v1_0', url: 'url3' },
|
||||
{ api: 'v1_0', url: 'url4' },
|
||||
{ api: 'v1_0', url: 'url5' },
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
})
|
||||
|
||||
it('logs the received data', () => {
|
||||
expect(logger.info).toBeCalledWith(
|
||||
'data: [{"api":"v1_0","url":"too much versions at the same time test"},{"api":"v1_0","url":"url2"},{"api":"v1_0","url":"url3"},{"api":"v1_0","url":"url4"},{"api":"v1_0","url":"url5"}]',
|
||||
)
|
||||
})
|
||||
|
||||
it('logs a warning of too much apiVersion-Definitions', () => {
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
`received totaly wrong or too much apiVersions-Definition JSON-String: ${JSON.stringify(
|
||||
jsonArray,
|
||||
)}`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving wrong but tolerated property data', () => {
|
||||
let jsonArray: any[]
|
||||
let result: DbCommunity[] = []
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
jsonArray = [
|
||||
{
|
||||
wrong: 'wrong but tolerated property test',
|
||||
api: 'v1_0',
|
||||
url: 'url1',
|
||||
},
|
||||
{
|
||||
api: 'v2_0',
|
||||
url: 'url2',
|
||||
wrong: 'wrong but tolerated property test',
|
||||
},
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
result = await DbCommunity.find()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
it('has two Communty entries in database', () => {
|
||||
expect(result).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('has an entry for api version v1_0', () => {
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
publicKey: expect.any(Buffer),
|
||||
apiVersion: 'v1_0',
|
||||
endPoint: 'url1',
|
||||
lastAnnouncedAt: expect.any(Date),
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: null,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it('has an entry for api version v2_0', () => {
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
publicKey: expect.any(Buffer),
|
||||
apiVersion: 'v2_0',
|
||||
endPoint: 'url2',
|
||||
lastAnnouncedAt: expect.any(Date),
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: null,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving data but missing api property', () => {
|
||||
let jsonArray: any[]
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
jsonArray = [
|
||||
{ test1: 'missing api proterty test', url: 'any url definition as string' },
|
||||
{ api: 'some api', test2: 'missing url property test' },
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
})
|
||||
|
||||
it('logs the received data', () => {
|
||||
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
|
||||
})
|
||||
|
||||
it('logs a warning of invalid apiVersion-Definition', () => {
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
`received invalid apiVersion-Definition: ${JSON.stringify(jsonArray[0])}`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving data but missing url property', () => {
|
||||
let jsonArray: any[]
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
jsonArray = [
|
||||
{ api: 'some api', test2: 'missing url property test' },
|
||||
{ test1: 'missing api proterty test', url: 'any url definition as string' },
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
})
|
||||
|
||||
it('logs the received data', () => {
|
||||
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
|
||||
})
|
||||
|
||||
it('logs a warning of invalid apiVersion-Definition', () => {
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
`received invalid apiVersion-Definition: ${JSON.stringify(jsonArray[0])}`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving data but wrong type of api property', () => {
|
||||
let jsonArray: any[]
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
jsonArray = [
|
||||
{ api: 1, url: 'wrong property type tests' },
|
||||
{ api: 'urltyptest', url: 2 },
|
||||
{ api: 1, url: 2 },
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
})
|
||||
|
||||
it('logs the received data', () => {
|
||||
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
|
||||
})
|
||||
|
||||
it('logs a warning of invalid apiVersion-Definition', () => {
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
`received invalid apiVersion-Definition: ${JSON.stringify(jsonArray[0])}`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving data but wrong type of url property', () => {
|
||||
let jsonArray: any[]
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
jsonArray = [
|
||||
{ api: 'urltyptest', url: 2 },
|
||||
{ api: 1, url: 'wrong property type tests' },
|
||||
{ api: 1, url: 2 },
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
})
|
||||
|
||||
it('logs the received data', () => {
|
||||
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
|
||||
})
|
||||
|
||||
it('logs a warning of invalid apiVersion-Definition', () => {
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
`received invalid apiVersion-Definition: ${JSON.stringify(jsonArray[0])}`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving data but wrong type of both properties', () => {
|
||||
let jsonArray: any[]
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
jsonArray = [
|
||||
{ api: 1, url: 2 },
|
||||
{ api: 'urltyptest', url: 2 },
|
||||
{ api: 1, url: 'wrong property type tests' },
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
})
|
||||
|
||||
it('logs the received data', () => {
|
||||
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
|
||||
})
|
||||
|
||||
it('logs a warning of invalid apiVersion-Definition', () => {
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
`received invalid apiVersion-Definition: ${JSON.stringify(jsonArray[0])}`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving data but too long api string', () => {
|
||||
let jsonArray: any[]
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
jsonArray = [
|
||||
{ api: 'toolong api', url: 'some valid url' },
|
||||
{
|
||||
api: 'valid api',
|
||||
url: 'this is a too long url definition with exact one character more than the allowed two hundert and fiftyfive characters. and here begins the fill characters with no sense of content menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmic',
|
||||
},
|
||||
{
|
||||
api: 'toolong api',
|
||||
url: 'this is a too long url definition with exact one character more than the allowed two hundert and fiftyfive characters. and here begins the fill characters with no sense of content menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmic',
|
||||
},
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
})
|
||||
|
||||
it('logs the received data', () => {
|
||||
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
|
||||
})
|
||||
|
||||
it('logs a warning of invalid apiVersion-Definition', () => {
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
`received apiVersion with content longer than max length: ${JSON.stringify(
|
||||
jsonArray[0],
|
||||
)}`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving data but too long url string', () => {
|
||||
let jsonArray: any[]
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
jsonArray = [
|
||||
{
|
||||
api: 'api',
|
||||
url: 'this is a too long url definition with exact one character more than the allowed two hundert and fiftyfive characters. and here begins the fill characters with no sense of content menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmic',
|
||||
},
|
||||
{ api: 'toolong api', url: 'some valid url' },
|
||||
{
|
||||
api: 'toolong api',
|
||||
url: 'this is a too long url definition with exact one character more than the allowed two hundert and fiftyfive characters. and here begins the fill characters with no sense of content menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmic',
|
||||
},
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
})
|
||||
|
||||
it('logs the received data', () => {
|
||||
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
|
||||
})
|
||||
|
||||
it('logs a warning of invalid apiVersion-Definition', () => {
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
`received apiVersion with content longer than max length: ${JSON.stringify(
|
||||
jsonArray[0],
|
||||
)}`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving data but both properties with too long strings', () => {
|
||||
let jsonArray: any[]
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
jsonArray = [
|
||||
{
|
||||
api: 'toolong api',
|
||||
url: 'this is a too long url definition with exact one character more than the allowed two hundert and fiftyfive characters. and here begins the fill characters with no sense of content menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmic',
|
||||
},
|
||||
{
|
||||
api: 'api',
|
||||
url: 'this is a too long url definition with exact one character more than the allowed two hundert and fiftyfive characters. and here begins the fill characters with no sense of content menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmic',
|
||||
},
|
||||
{ api: 'toolong api', url: 'some valid url' },
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
})
|
||||
|
||||
it('logs the received data', () => {
|
||||
expect(logger.info).toBeCalledWith(`data: ${JSON.stringify(jsonArray)}`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving data of exact max allowed properties length', () => {
|
||||
let jsonArray: any[]
|
||||
let result: DbCommunity[] = []
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
jsonArray = [
|
||||
{
|
||||
api: 'valid api',
|
||||
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
|
||||
},
|
||||
{
|
||||
api: 'api',
|
||||
url: 'this is a too long url definition with exact one character more than the allowed two hundert and fiftyfive characters. and here begins the fill characters with no sense of content menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmic',
|
||||
},
|
||||
{ api: 'toolong api', url: 'some valid url' },
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
result = await DbCommunity.find()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
it('has one Communty entry in database', () => {
|
||||
expect(result).toHaveLength(1)
|
||||
})
|
||||
|
||||
it(`has an entry with max content length for api and url`, () => {
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
publicKey: expect.any(Buffer),
|
||||
apiVersion: 'valid api',
|
||||
endPoint:
|
||||
'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
|
||||
lastAnnouncedAt: expect.any(Date),
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: null,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving data of exact max allowed buffer length', () => {
|
||||
let jsonArray: any[]
|
||||
let result: DbCommunity[] = []
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
jsonArray = [
|
||||
{
|
||||
api: 'valid api1',
|
||||
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
|
||||
},
|
||||
{
|
||||
api: 'valid api2',
|
||||
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
|
||||
},
|
||||
{
|
||||
api: 'valid api3',
|
||||
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
|
||||
},
|
||||
{
|
||||
api: 'valid api4',
|
||||
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
|
||||
},
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
result = await DbCommunity.find()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
it('has five Communty entries in database', () => {
|
||||
expect(result).toHaveLength(4)
|
||||
})
|
||||
|
||||
it(`has an entry 'valid api1' with max content length for api and url`, () => {
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
publicKey: expect.any(Buffer),
|
||||
apiVersion: 'valid api1',
|
||||
endPoint:
|
||||
'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
|
||||
lastAnnouncedAt: expect.any(Date),
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: null,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it(`has an entry 'valid api2' with max content length for api and url`, () => {
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
publicKey: expect.any(Buffer),
|
||||
apiVersion: 'valid api2',
|
||||
endPoint:
|
||||
'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
|
||||
lastAnnouncedAt: expect.any(Date),
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: null,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it(`has an entry 'valid api3' with max content length for api and url`, () => {
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
publicKey: expect.any(Buffer),
|
||||
apiVersion: 'valid api3',
|
||||
endPoint:
|
||||
'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
|
||||
lastAnnouncedAt: expect.any(Date),
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: null,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it(`has an entry 'valid api4' with max content length for api and url`, () => {
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
publicKey: expect.any(Buffer),
|
||||
apiVersion: 'valid api4',
|
||||
endPoint:
|
||||
'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
|
||||
lastAnnouncedAt: expect.any(Date),
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: null,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with receiving data longer than max allowed buffer length', () => {
|
||||
let jsonArray: any[]
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
jsonArray = [
|
||||
{
|
||||
api: 'Xvalid api1',
|
||||
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
|
||||
},
|
||||
{
|
||||
api: 'valid api2',
|
||||
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
|
||||
},
|
||||
{
|
||||
api: 'valid api3',
|
||||
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
|
||||
},
|
||||
{
|
||||
api: 'valid api4',
|
||||
url: 'this is a valid url definition with exact the max allowed length of two hundert and fiftyfive characters. and here begins the fill characters with no sense of content kuhwarmiga menschhabicheinhungerdassichnichtweiswoichheutnachtschlafensollsofriertesmich',
|
||||
},
|
||||
]
|
||||
await socketEventMocks.data(Buffer.from(JSON.stringify(jsonArray)))
|
||||
})
|
||||
|
||||
it('logs the received data', () => {
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
`received more than max allowed length of data buffer: ${
|
||||
JSON.stringify(jsonArray).length
|
||||
} against 1141 max allowed`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with proper data', () => {
|
||||
let result: DbCommunity[] = []
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
await socketEventMocks.data(
|
||||
Buffer.from(
|
||||
JSON.stringify([
|
||||
{
|
||||
api: 'v1_0',
|
||||
url: 'http://localhost:4000/api/v1_0',
|
||||
},
|
||||
{
|
||||
api: 'v2_0',
|
||||
url: 'http://localhost:4000/api/v2_0',
|
||||
},
|
||||
]),
|
||||
),
|
||||
)
|
||||
result = await DbCommunity.find()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
it('has two Communty entries in database', () => {
|
||||
expect(result).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('has an entry for api version v1_0', () => {
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
publicKey: expect.any(Buffer),
|
||||
apiVersion: 'v1_0',
|
||||
endPoint: 'http://localhost:4000/api/v1_0',
|
||||
lastAnnouncedAt: expect.any(Date),
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: null,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
it('has an entry for api version v2_0', () => {
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
publicKey: expect.any(Buffer),
|
||||
apiVersion: 'v2_0',
|
||||
endPoint: 'http://localhost:4000/api/v2_0',
|
||||
lastAnnouncedAt: expect.any(Date),
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: null,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('on open', () => {
|
||||
beforeEach(() => {
|
||||
socketEventMocks.open()
|
||||
})
|
||||
|
||||
it.skip('calls socket write with own api versions', () => {
|
||||
expect(socketWriteMock).toBeCalledWith(
|
||||
Buffer.from(
|
||||
JSON.stringify([
|
||||
{
|
||||
api: 'v1_0',
|
||||
url: 'http://localhost:4000/api/v1_0',
|
||||
},
|
||||
{
|
||||
api: 'v1_1',
|
||||
url: 'http://localhost:4000/api/v1_1',
|
||||
},
|
||||
{
|
||||
api: 'v2_0',
|
||||
url: 'http://localhost:4000/api/v2_0',
|
||||
},
|
||||
]),
|
||||
),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,185 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import DHT from '@hyperswarm/dht'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import CONFIG from '@/config'
|
||||
import { Community as DbCommunity } from '@entity/Community'
|
||||
|
||||
const KEY_SECRET_SEEDBYTES = 32
|
||||
const getSeed = (): Buffer | null =>
|
||||
CONFIG.FEDERATION_DHT_SEED ? Buffer.alloc(KEY_SECRET_SEEDBYTES, CONFIG.FEDERATION_DHT_SEED) : null
|
||||
|
||||
const POLLTIME = 20000
|
||||
const SUCCESSTIME = 120000
|
||||
const ERRORTIME = 240000
|
||||
const ANNOUNCETIME = 30000
|
||||
|
||||
enum ApiVersionType {
|
||||
V1_0 = 'v1_0',
|
||||
V1_1 = 'v1_1',
|
||||
V2_0 = 'v2_0',
|
||||
}
|
||||
type CommunityApi = {
|
||||
api: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export const startDHT = async (topic: string): Promise<void> => {
|
||||
try {
|
||||
const TOPIC = DHT.hash(Buffer.from(topic))
|
||||
const keyPair = DHT.keyPair(getSeed())
|
||||
logger.info(`keyPairDHT: publicKey=${keyPair.publicKey.toString('hex')}`)
|
||||
logger.debug(`keyPairDHT: secretKey=${keyPair.secretKey.toString('hex')}`)
|
||||
|
||||
const ownApiVersions = Object.values(ApiVersionType).map(function (apiEnum) {
|
||||
const comApi: CommunityApi = {
|
||||
api: apiEnum,
|
||||
url: CONFIG.FEDERATION_COMMUNITY_URL + apiEnum,
|
||||
}
|
||||
return comApi
|
||||
})
|
||||
logger.debug(`ApiList: ${JSON.stringify(ownApiVersions)}`)
|
||||
|
||||
const node = new DHT({ keyPair })
|
||||
|
||||
const server = node.createServer()
|
||||
|
||||
server.on('connection', function (socket: any) {
|
||||
logger.info(`server on... with Remote public key: ${socket.remotePublicKey.toString('hex')}`)
|
||||
|
||||
socket.on('data', async (data: Buffer) => {
|
||||
try {
|
||||
if (data.length > 1141) {
|
||||
logger.warn(
|
||||
`received more than max allowed length of data buffer: ${data.length} against 1141 max allowed`,
|
||||
)
|
||||
return
|
||||
}
|
||||
logger.info(`data: ${data.toString('ascii')}`)
|
||||
const recApiVersions: CommunityApi[] = JSON.parse(data.toString('ascii'))
|
||||
|
||||
// TODO better to introduce the validation by https://github.com/typestack/class-validato
|
||||
if (recApiVersions && Array.isArray(recApiVersions) && recApiVersions.length < 5) {
|
||||
for (const recApiVersion of recApiVersions) {
|
||||
if (
|
||||
!recApiVersion.api ||
|
||||
typeof recApiVersion.api !== 'string' ||
|
||||
!recApiVersion.url ||
|
||||
typeof recApiVersion.url !== 'string'
|
||||
) {
|
||||
logger.warn(
|
||||
`received invalid apiVersion-Definition: ${JSON.stringify(recApiVersion)}`,
|
||||
)
|
||||
// in a forEach-loop use return instead of continue
|
||||
return
|
||||
}
|
||||
// TODO better to introduce the validation on entity-Level by https://github.com/typestack/class-validator
|
||||
if (recApiVersion.api.length > 10 || recApiVersion.url.length > 255) {
|
||||
logger.warn(
|
||||
`received apiVersion with content longer than max length: ${JSON.stringify(
|
||||
recApiVersion,
|
||||
)}`,
|
||||
)
|
||||
// in a forEach-loop use return instead of continue
|
||||
return
|
||||
}
|
||||
|
||||
const variables = {
|
||||
apiVersion: recApiVersion.api,
|
||||
endPoint: recApiVersion.url,
|
||||
publicKey: socket.remotePublicKey.toString('hex'),
|
||||
lastAnnouncedAt: new Date(),
|
||||
}
|
||||
logger.debug(`upsert with variables=${JSON.stringify(variables)}`)
|
||||
// this will NOT update the updatedAt column, to distingue between a normal update and the last announcement
|
||||
await DbCommunity.createQueryBuilder()
|
||||
.insert()
|
||||
.into(DbCommunity)
|
||||
.values(variables)
|
||||
.orUpdate({
|
||||
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
||||
overwrite: ['end_point', 'last_announced_at'],
|
||||
})
|
||||
.execute()
|
||||
logger.info(`federation community upserted successfully...`)
|
||||
}
|
||||
} else {
|
||||
logger.warn(
|
||||
`received totaly wrong or too much apiVersions-Definition JSON-String: ${JSON.stringify(
|
||||
recApiVersions,
|
||||
)}`,
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Error on receiving data from socket:', e)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
await server.listen()
|
||||
|
||||
setInterval(async () => {
|
||||
logger.info(`Announcing on topic: ${TOPIC.toString('hex')}`)
|
||||
await node.announce(TOPIC, keyPair).finished()
|
||||
}, ANNOUNCETIME)
|
||||
|
||||
let successfulRequests: string[] = []
|
||||
let errorfulRequests: string[] = []
|
||||
|
||||
setInterval(async () => {
|
||||
logger.info('Refreshing successful nodes')
|
||||
successfulRequests = []
|
||||
}, SUCCESSTIME)
|
||||
|
||||
setInterval(async () => {
|
||||
logger.info('Refreshing errorful nodes')
|
||||
errorfulRequests = []
|
||||
}, ERRORTIME)
|
||||
|
||||
setInterval(async () => {
|
||||
const result = await node.lookup(TOPIC)
|
||||
|
||||
const collectedPubKeys: string[] = []
|
||||
|
||||
for await (const data of result) {
|
||||
data.peers.forEach((peer: any) => {
|
||||
const pubKey = peer.publicKey.toString('hex')
|
||||
if (
|
||||
pubKey !== keyPair.publicKey.toString('hex') &&
|
||||
!successfulRequests.includes(pubKey) &&
|
||||
!errorfulRequests.includes(pubKey) &&
|
||||
!collectedPubKeys.includes(pubKey)
|
||||
) {
|
||||
collectedPubKeys.push(peer.publicKey.toString('hex'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Found new peers: ${collectedPubKeys}`)
|
||||
|
||||
collectedPubKeys.forEach((remotePubKey) => {
|
||||
const socket = node.connect(Buffer.from(remotePubKey, 'hex'))
|
||||
|
||||
// socket.once("connect", function () {
|
||||
// console.log("client side emitted connect");
|
||||
// });
|
||||
|
||||
// socket.once("end", function () {
|
||||
// console.log("client side ended");
|
||||
// });
|
||||
|
||||
socket.once('error', (err: any) => {
|
||||
errorfulRequests.push(remotePubKey)
|
||||
logger.error(`error on peer ${remotePubKey}: ${err.message}`)
|
||||
})
|
||||
|
||||
socket.on('open', function () {
|
||||
socket.write(Buffer.from(JSON.stringify(ownApiVersions)))
|
||||
successfulRequests.push(remotePubKey)
|
||||
})
|
||||
})
|
||||
}, POLLTIME)
|
||||
} catch (err) {
|
||||
logger.error('DHT unexpected error:', err)
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
import { registerEnumType } from 'type-graphql'
|
||||
|
||||
export enum TransactionType {
|
||||
CREATION = 'creation',
|
||||
SEND = 'send',
|
||||
RECIEVE = 'receive',
|
||||
}
|
||||
|
||||
registerEnumType(TransactionType, {
|
||||
name: 'TransactionType', // this one is mandatory
|
||||
description: 'Name of the Type of the transaction', // this one is optional
|
||||
})
|
||||
@ -18,6 +18,8 @@ export class Contribution {
|
||||
this.contributionDate = contribution.contributionDate
|
||||
this.state = contribution.contributionStatus
|
||||
this.messagesCount = contribution.messages ? contribution.messages.length : 0
|
||||
this.deniedAt = contribution.deniedAt
|
||||
this.deniedBy = contribution.deniedBy
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
@ -47,6 +49,12 @@ export class Contribution {
|
||||
@Field(() => Number, { nullable: true })
|
||||
confirmedBy: number | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
deniedAt: Date | null
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
deniedBy: number | null
|
||||
|
||||
@Field(() => Date)
|
||||
contributionDate: Date
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||
import { bobBaumeister } from '@/seeds/users/bob-baumeister'
|
||||
import { stephenHawking } from '@/seeds/users/stephen-hawking'
|
||||
import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
|
||||
import {
|
||||
@ -26,7 +27,13 @@ import {
|
||||
sendContributionConfirmedEmail,
|
||||
// sendContributionRejectedEmail,
|
||||
} from '@/emails/sendEmailVariants'
|
||||
import { cleanDB, resetToken, testEnvironment, contributionDateFormatter } from '@test/helpers'
|
||||
import {
|
||||
cleanDB,
|
||||
resetToken,
|
||||
testEnvironment,
|
||||
contributionDateFormatter,
|
||||
resetEntity,
|
||||
} from '@test/helpers'
|
||||
import { GraphQLError } from 'graphql'
|
||||
import { userFactory } from '@/seeds/factory/user'
|
||||
import { creationFactory } from '@/seeds/factory/creation'
|
||||
@ -1818,6 +1825,49 @@ describe('ContributionResolver', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('creation already confirmed', () => {
|
||||
it('throws an error', async () => {
|
||||
await userFactory(testEnv, bobBaumeister)
|
||||
await query({
|
||||
query: login,
|
||||
variables: { email: 'bob@baumeister.de', password: 'Aa12345_' },
|
||||
})
|
||||
const {
|
||||
data: { createContribution: confirmedContribution },
|
||||
} = await mutate({
|
||||
mutation: createContribution,
|
||||
variables: {
|
||||
amount: 100.0,
|
||||
memo: 'Confirmed Contribution',
|
||||
creationDate: contributionDateFormatter(new Date()),
|
||||
},
|
||||
})
|
||||
await query({
|
||||
query: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
await mutate({
|
||||
mutation: confirmContribution,
|
||||
variables: {
|
||||
id: confirmedContribution.id ? confirmedContribution.id : -1,
|
||||
},
|
||||
})
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminDeleteContribution,
|
||||
variables: {
|
||||
id: confirmedContribution.id ? confirmedContribution.id : -1,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('A confirmed contribution can not be deleted')],
|
||||
}),
|
||||
)
|
||||
await resetEntity(DbTransaction)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirmContribution', () => {
|
||||
|
||||
@ -50,7 +50,7 @@ import { eventProtocol } from '@/event/EventProtocolEmitter'
|
||||
import { calculateDecay } from '@/util/decay'
|
||||
import {
|
||||
sendContributionConfirmedEmail,
|
||||
sendContributionRejectedEmail,
|
||||
sendContributionDeniedEmail,
|
||||
} from '@/emails/sendEmailVariants'
|
||||
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||
|
||||
@ -217,7 +217,7 @@ export class ContributionResolver {
|
||||
const user = getUser(context)
|
||||
|
||||
const contributionToUpdate = await DbContribution.findOne({
|
||||
where: { id: contributionId, confirmedAt: IsNull() },
|
||||
where: { id: contributionId, confirmedAt: IsNull(), deniedAt: IsNull() },
|
||||
})
|
||||
if (!contributionToUpdate) {
|
||||
logger.error('No contribution found to given id')
|
||||
@ -409,7 +409,7 @@ export class ContributionResolver {
|
||||
const moderator = getUser(context)
|
||||
|
||||
const contributionToUpdate = await DbContribution.findOne({
|
||||
where: { id, confirmedAt: IsNull() },
|
||||
where: { id, confirmedAt: IsNull(), deniedAt: IsNull() },
|
||||
})
|
||||
if (!contributionToUpdate) {
|
||||
logger.error('No contribution found to given id.')
|
||||
@ -475,6 +475,7 @@ export class ContributionResolver {
|
||||
.from(DbContribution, 'c')
|
||||
.leftJoinAndSelect('c.messages', 'm')
|
||||
.where({ confirmedAt: IsNull() })
|
||||
.andWhere({ deniedAt: IsNull() })
|
||||
.getMany()
|
||||
|
||||
if (contributions.length === 0) {
|
||||
@ -512,6 +513,10 @@ export class ContributionResolver {
|
||||
logger.error(`Contribution not found for given id: ${id}`)
|
||||
throw new Error('Contribution not found for given id.')
|
||||
}
|
||||
if (contribution.confirmedAt) {
|
||||
logger.error('A confirmed contribution can not be deleted')
|
||||
throw new Error('A confirmed contribution can not be deleted')
|
||||
}
|
||||
const moderator = getUser(context)
|
||||
if (
|
||||
contribution.contributionType === ContributionType.USER &&
|
||||
@ -536,7 +541,7 @@ export class ContributionResolver {
|
||||
await eventProtocol.writeEvent(
|
||||
event.setEventAdminContributionDelete(eventAdminContributionDelete),
|
||||
)
|
||||
sendContributionRejectedEmail({
|
||||
sendContributionDeniedEmail({
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
email: user.emailContact.email,
|
||||
@ -568,6 +573,10 @@ export class ContributionResolver {
|
||||
logger.error(`Contribution already confirmd: ${id}`)
|
||||
throw new Error('Contribution already confirmd.')
|
||||
}
|
||||
if (contribution.contributionStatus === 'DENIED') {
|
||||
logger.error(`Contribution already denied: ${id}`)
|
||||
throw new Error('Contribution already denied.')
|
||||
}
|
||||
const moderatorUser = getUser(context)
|
||||
if (moderatorUser.id === contribution.userId) {
|
||||
logger.error('Moderator can not confirm own contribution')
|
||||
@ -680,6 +689,7 @@ export class ContributionResolver {
|
||||
.from(DbContribution, 'c')
|
||||
.leftJoinAndSelect('c.user', 'u')
|
||||
.where(`user_id = ${userId}`)
|
||||
.withDeleted()
|
||||
.limit(pageSize)
|
||||
.offset(offset)
|
||||
.orderBy('c.created_at', order)
|
||||
@ -710,4 +720,58 @@ export class ContributionResolver {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.DENY_CONTRIBUTION])
|
||||
@Mutation(() => Boolean)
|
||||
async denyContribution(
|
||||
@Arg('id', () => Int) id: number,
|
||||
@Ctx() context: Context,
|
||||
): Promise<boolean> {
|
||||
const contributionToUpdate = await DbContribution.findOne({
|
||||
id,
|
||||
confirmedAt: IsNull(),
|
||||
deniedBy: IsNull(),
|
||||
})
|
||||
if (!contributionToUpdate) {
|
||||
logger.error(`Contribution not found for given id: ${id}`)
|
||||
throw new Error(`Contribution not found for given id.`)
|
||||
}
|
||||
if (
|
||||
contributionToUpdate.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
||||
contributionToUpdate.contributionStatus !== ContributionStatus.PENDING
|
||||
) {
|
||||
logger.error(
|
||||
`Contribution state (${contributionToUpdate.contributionStatus}) is not allowed.`,
|
||||
)
|
||||
throw new Error(`State of the contribution is not allowed.`)
|
||||
}
|
||||
const moderator = getUser(context)
|
||||
const user = await DbUser.findOne(
|
||||
{ id: contributionToUpdate.userId },
|
||||
{ relations: ['emailContact'] },
|
||||
)
|
||||
if (!user) {
|
||||
logger.error(
|
||||
`Could not find User for the Contribution (userId: ${contributionToUpdate.userId}).`,
|
||||
)
|
||||
throw new Error('Could not find User for the Contribution.')
|
||||
}
|
||||
|
||||
contributionToUpdate.contributionStatus = ContributionStatus.DENIED
|
||||
contributionToUpdate.deniedBy = moderator.id
|
||||
contributionToUpdate.deniedAt = new Date()
|
||||
const res = await contributionToUpdate.save()
|
||||
|
||||
sendContributionDeniedEmail({
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
email: user.emailContact.email,
|
||||
language: user.language,
|
||||
senderFirstName: moderator.firstName,
|
||||
senderLastName: moderator.lastName,
|
||||
contributionMemo: contributionToUpdate.memo,
|
||||
})
|
||||
|
||||
return !!res
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import createServer from './server/createServer'
|
||||
import { startDHT } from '@/federation/index'
|
||||
|
||||
// config
|
||||
import CONFIG from './config'
|
||||
@ -18,21 +17,6 @@ async function main() {
|
||||
console.log(`GraphIQL available at http://localhost:${CONFIG.PORT}`)
|
||||
}
|
||||
})
|
||||
startValidateCommunities(Number(CONFIG.FEDERATION_VALIDATE_COMMUNITY_TIMER))
|
||||
|
||||
// start DHT hyperswarm when DHT_TOPIC is set in .env
|
||||
if (CONFIG.FEDERATION_DHT_TOPIC) {
|
||||
if (CONFIG.FEDERATION_COMMUNITY_URL === null) {
|
||||
throw Error(`Config-Error: missing configuration of property FEDERATION_COMMUNITY_URL`)
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`starting Federation on ${CONFIG.FEDERATION_DHT_TOPIC} ${
|
||||
CONFIG.FEDERATION_DHT_SEED ? 'with seed...' : 'without seed...'
|
||||
}`,
|
||||
)
|
||||
await startDHT(CONFIG.FEDERATION_DHT_TOPIC) // con,
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
|
||||
@ -23,8 +23,8 @@
|
||||
"commonGoodContributionConfirmed": "Your public good contribution “{contributionMemo}” has just been confirmed by {senderFirstName} {senderLastName} and credited to your Gradido account.",
|
||||
"subject": "Gradido: Your contribution to the common good was confirmed"
|
||||
},
|
||||
"contributionRejected": {
|
||||
"commonGoodContributionRejected": "Your public good contribution “{contributionMemo}” was rejected by {senderFirstName} {senderLastName}.",
|
||||
"contributionDenied": {
|
||||
"commonGoodContributionDenied": "Your public good contribution “{contributionMemo}” was rejected by {senderFirstName} {senderLastName}.",
|
||||
"subject": "Gradido: Your common good contribution was rejected",
|
||||
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
|
||||
},
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-database",
|
||||
"version": "1.16.0",
|
||||
"version": "1.17.1",
|
||||
"description": "Gradido Database Tool to execute database migrations",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/database",
|
||||
|
||||
3
federation/.eslintignore
Normal file
3
federation/.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
**/*.min.js
|
||||
build
|
||||
28
federation/.eslintrc.js
Normal file
28
federation/.eslintrc.js
Normal file
@ -0,0 +1,28 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
// jest: true,
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['prettier', '@typescript-eslint' /*, 'jest' */],
|
||||
extends: [
|
||||
'standard',
|
||||
'eslint:recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
],
|
||||
// add your custom rules here
|
||||
rules: {
|
||||
'no-console': ['error'],
|
||||
'no-debugger': 'error',
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
htmlWhitespaceSensitivity: 'ignore',
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
8
federation/.gitignore
vendored
Normal file
8
federation/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/node_modules/
|
||||
/.env
|
||||
/.env.bak
|
||||
/build/
|
||||
package-json.lock
|
||||
coverage
|
||||
# emacs
|
||||
*~
|
||||
146
federation/log4js-config.json
Normal file
146
federation/log4js-config.json
Normal file
@ -0,0 +1,146 @@
|
||||
{
|
||||
"appenders":
|
||||
{
|
||||
"access":
|
||||
{
|
||||
"type": "dateFile",
|
||||
"filename": "../logs/federation/access-%p.log",
|
||||
"pattern": "yyyy-MM-dd",
|
||||
"layout":
|
||||
{
|
||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||
},
|
||||
"keepFileExt" : true,
|
||||
"fileNameSep" : "_",
|
||||
"numBackups" : 30
|
||||
},
|
||||
"apollo":
|
||||
{
|
||||
"type": "dateFile",
|
||||
"filename": "../logs/federation/apollo-%p.log",
|
||||
"pattern": "yyyy-MM-dd",
|
||||
"layout":
|
||||
{
|
||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||
},
|
||||
"keepFileExt" : true,
|
||||
"fileNameSep" : "_",
|
||||
"numBackups" : 30
|
||||
},
|
||||
"backend":
|
||||
{
|
||||
"type": "dateFile",
|
||||
"filename": "../logs/federation/backend-%p.log",
|
||||
"pattern": "yyyy-MM-dd",
|
||||
"layout":
|
||||
{
|
||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||
},
|
||||
"keepFileExt" : true,
|
||||
"fileNameSep" : "_",
|
||||
"numBackups" : 30
|
||||
},
|
||||
"federation":
|
||||
{
|
||||
"type": "dateFile",
|
||||
"filename": "../logs/federation/apiversion-%v-%p.log",
|
||||
"pattern": "yyyy-MM-dd",
|
||||
"layout":
|
||||
{
|
||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||
},
|
||||
"keepFileExt" : true,
|
||||
"fileNameSep" : "_",
|
||||
"numBackups" : 30
|
||||
},
|
||||
"errorFile":
|
||||
{
|
||||
"type": "dateFile",
|
||||
"filename": "../logs/federation/errors-%p.log",
|
||||
"pattern": "yyyy-MM-dd",
|
||||
"layout":
|
||||
{
|
||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||
},
|
||||
"keepFileExt" : true,
|
||||
"fileNameSep" : "_",
|
||||
"numBackups" : 30
|
||||
},
|
||||
"errors":
|
||||
{
|
||||
"type": "logLevelFilter",
|
||||
"level": "error",
|
||||
"appender": "errorFile"
|
||||
},
|
||||
"out":
|
||||
{
|
||||
"type": "stdout",
|
||||
"layout":
|
||||
{
|
||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||
}
|
||||
},
|
||||
"apolloOut":
|
||||
{
|
||||
"type": "stdout",
|
||||
"layout":
|
||||
{
|
||||
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
|
||||
}
|
||||
}
|
||||
},
|
||||
"categories":
|
||||
{
|
||||
"default":
|
||||
{
|
||||
"appenders":
|
||||
[
|
||||
"out",
|
||||
"errors"
|
||||
],
|
||||
"level": "debug",
|
||||
"enableCallStack": true
|
||||
},
|
||||
"apollo":
|
||||
{
|
||||
"appenders":
|
||||
[
|
||||
"apollo",
|
||||
"apolloOut",
|
||||
"errors"
|
||||
],
|
||||
"level": "debug",
|
||||
"enableCallStack": true
|
||||
},
|
||||
"backend":
|
||||
{
|
||||
"appenders":
|
||||
[
|
||||
"backend",
|
||||
"out",
|
||||
"errors"
|
||||
],
|
||||
"level": "debug",
|
||||
"enableCallStack": true
|
||||
},
|
||||
"federation":
|
||||
{
|
||||
"appenders":
|
||||
[
|
||||
"federation",
|
||||
"out",
|
||||
"errors"
|
||||
],
|
||||
"level": "debug",
|
||||
"enableCallStack": true
|
||||
},
|
||||
"http":
|
||||
{
|
||||
"appenders":
|
||||
[
|
||||
"access"
|
||||
],
|
||||
"level": "info"
|
||||
}
|
||||
}
|
||||
}
|
||||
49
federation/package.json
Normal file
49
federation/package.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "gradido-federation",
|
||||
"version": "1.0.0",
|
||||
"description": "Gradido federation module providing Gradido-Hub-Federation and versioned API for inter community communication",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/federation",
|
||||
"author": "Claus-Peter Huebner",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"build": "tsc --build",
|
||||
"clean": "tsc --build --clean",
|
||||
"start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js",
|
||||
"dev": "cross-env TZ=UTC nodemon -w src --ext ts --exec ts-node -r dotenv/config -r tsconfig-paths/register src/index.ts",
|
||||
"lint": "eslint --max-warnings=0 --ext .js,.ts ."
|
||||
},
|
||||
"dependencies": {
|
||||
"apollo-server-express": "^2.25.2",
|
||||
"class-validator": "^0.13.2",
|
||||
"cors": "2.8.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"decimal.js-light": "^2.5.1",
|
||||
"dotenv": "10.0.0",
|
||||
"express": "4.17.1",
|
||||
"graphql": "15.5.1",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"log4js": "^6.7.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.1.1",
|
||||
"type-graphql": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.0",
|
||||
"@typescript-eslint/parser": "^4.28.0",
|
||||
"@types/lodash.clonedeep": "^4.5.6",
|
||||
"@types/node": "^16.10.3",
|
||||
"eslint": "^7.29.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-plugin-import": "^2.23.4",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"prettier": "^2.3.1",
|
||||
"typescript": "^4.3.4",
|
||||
"nodemon": "^2.0.7"
|
||||
}
|
||||
}
|
||||
94
federation/src/config/index.ts
Normal file
94
federation/src/config/index.ts
Normal file
@ -0,0 +1,94 @@
|
||||
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
|
||||
import dotenv from 'dotenv'
|
||||
dotenv.config()
|
||||
/*
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
Decimal.set({
|
||||
precision: 25,
|
||||
rounding: Decimal.ROUND_HALF_UP,
|
||||
})
|
||||
*/
|
||||
|
||||
const constants = {
|
||||
DB_VERSION: '0059-add_hide_amount_to_users',
|
||||
// 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: 'v1.2023-01-09',
|
||||
CURRENT: '',
|
||||
},
|
||||
}
|
||||
|
||||
const server = {
|
||||
PORT: process.env.PORT || 5000,
|
||||
// JWT_SECRET: process.env.JWT_SECRET || 'secret123',
|
||||
// JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '10m',
|
||||
GRAPHIQL: process.env.GRAPHIQL === 'true' || false,
|
||||
// GDT_API_URL: process.env.GDT_API_URL || 'https://gdt.gradido.net',
|
||||
PRODUCTION: process.env.NODE_ENV === 'production' || false,
|
||||
}
|
||||
const database = {
|
||||
DB_HOST: process.env.DB_HOST || 'localhost',
|
||||
DB_PORT: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 3306,
|
||||
DB_USER: process.env.DB_USER || 'root',
|
||||
DB_PASSWORD: process.env.DB_PASSWORD || '',
|
||||
DB_DATABASE: process.env.DB_DATABASE || 'gradido_community',
|
||||
TYPEORM_LOGGING_RELATIVE_PATH:
|
||||
process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.backend.log',
|
||||
}
|
||||
/*
|
||||
const community = {
|
||||
COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung',
|
||||
COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/',
|
||||
COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/register',
|
||||
COMMUNITY_REDEEM_URL: process.env.COMMUNITY_REDEEM_URL || 'http://localhost/redeem/{code}',
|
||||
COMMUNITY_REDEEM_CONTRIBUTION_URL:
|
||||
process.env.COMMUNITY_REDEEM_CONTRIBUTION_URL || 'http://localhost/redeem/CL-{code}',
|
||||
COMMUNITY_DESCRIPTION:
|
||||
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.',
|
||||
}
|
||||
*/
|
||||
// const eventProtocol = {
|
||||
// global switch to enable writing of EventProtocol-Entries
|
||||
// EVENT_PROTOCOL_DISABLED: process.env.EVENT_PROTOCOL_DISABLED === 'true' || false,
|
||||
// }
|
||||
|
||||
// This is needed by graphql-directive-auth
|
||||
// process.env.APP_SECRET = server.JWT_SECRET
|
||||
|
||||
// Check config version
|
||||
constants.CONFIG_VERSION.CURRENT =
|
||||
process.env.CONFIG_VERSION || constants.CONFIG_VERSION.DEFAULT
|
||||
if (
|
||||
![
|
||||
constants.CONFIG_VERSION.EXPECTED,
|
||||
constants.CONFIG_VERSION.DEFAULT,
|
||||
].includes(constants.CONFIG_VERSION.CURRENT)
|
||||
) {
|
||||
throw new Error(
|
||||
`Fatal: Config Version incorrect - expected "${constants.CONFIG_VERSION.EXPECTED}" or "${constants.CONFIG_VERSION.DEFAULT}", but found "${constants.CONFIG_VERSION.CURRENT}"`
|
||||
)
|
||||
}
|
||||
|
||||
const federation = {
|
||||
// FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || null,
|
||||
// FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
|
||||
FEDERATION_PORT: process.env.FEDERATION_PORT || 5000,
|
||||
FEDERATION_API: process.env.FEDERATION_API || '1_0',
|
||||
FEDERATION_COMMUNITY_URL: process.env.FEDERATION_COMMUNITY_URL || null,
|
||||
}
|
||||
|
||||
const CONFIG = {
|
||||
...constants,
|
||||
...server,
|
||||
...database,
|
||||
// ...community,
|
||||
// ...eventProtocol,
|
||||
...federation,
|
||||
}
|
||||
|
||||
export default CONFIG
|
||||
12
federation/src/graphql/api/1_0/resolver/Test2Resolver.ts
Normal file
12
federation/src/graphql/api/1_0/resolver/Test2Resolver.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Query, Resolver } from 'type-graphql'
|
||||
import { federationLogger as logger } from '@/server/logger'
|
||||
import { GetTestApiResult } from '../../GetTestApiResult'
|
||||
|
||||
@Resolver()
|
||||
export class Test2Resolver {
|
||||
@Query(() => GetTestApiResult)
|
||||
async test2(): Promise<GetTestApiResult> {
|
||||
logger.info(`test api 2 1_0`)
|
||||
return new GetTestApiResult('1_0')
|
||||
}
|
||||
}
|
||||
12
federation/src/graphql/api/1_0/resolver/TestResolver.ts
Normal file
12
federation/src/graphql/api/1_0/resolver/TestResolver.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Field, ObjectType, Query, Resolver } from 'type-graphql'
|
||||
import { federationLogger as logger } from '@/server/logger'
|
||||
import { GetTestApiResult } from '../../GetTestApiResult'
|
||||
|
||||
@Resolver()
|
||||
export class TestResolver {
|
||||
@Query(() => GetTestApiResult)
|
||||
async test(): Promise<GetTestApiResult> {
|
||||
logger.info(`test api 1_0`)
|
||||
return new GetTestApiResult('1_0')
|
||||
}
|
||||
}
|
||||
12
federation/src/graphql/api/1_1/resolver/TestResolver.ts
Normal file
12
federation/src/graphql/api/1_1/resolver/TestResolver.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Field, ObjectType, Query, Resolver } from 'type-graphql'
|
||||
import { federationLogger as logger } from '@/server/logger'
|
||||
import { GetTestApiResult } from '../../GetTestApiResult'
|
||||
|
||||
@Resolver()
|
||||
export class TestResolver {
|
||||
@Query(() => GetTestApiResult)
|
||||
async test(): Promise<GetTestApiResult> {
|
||||
logger.info(`test api 1_1`)
|
||||
return new GetTestApiResult('1_1')
|
||||
}
|
||||
}
|
||||
12
federation/src/graphql/api/2_0/resolver/TestResolver.ts
Normal file
12
federation/src/graphql/api/2_0/resolver/TestResolver.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Field, ObjectType, Query, Resolver } from 'type-graphql'
|
||||
import { federationLogger as logger } from '@/server/logger'
|
||||
import { GetTestApiResult } from '../../GetTestApiResult'
|
||||
|
||||
@Resolver()
|
||||
export class TestResolver {
|
||||
@Query(() => GetTestApiResult)
|
||||
async test(): Promise<GetTestApiResult> {
|
||||
logger.info(`test api 2_0`)
|
||||
return new GetTestApiResult('2_0')
|
||||
}
|
||||
}
|
||||
11
federation/src/graphql/api/GetTestApiResult.ts
Normal file
11
federation/src/graphql/api/GetTestApiResult.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Field, ObjectType } from 'type-graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class GetTestApiResult {
|
||||
constructor(apiVersion: string) {
|
||||
this.api = apiVersion
|
||||
}
|
||||
|
||||
@Field(() => String)
|
||||
api: string
|
||||
}
|
||||
12
federation/src/graphql/api/schema.ts
Normal file
12
federation/src/graphql/api/schema.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import path from 'path'
|
||||
// config
|
||||
import CONFIG from '../../config'
|
||||
import { federationLogger as logger } from '@/server/logger'
|
||||
|
||||
export const getApiResolvers = (): string => {
|
||||
logger.info(`getApiResolvers...${CONFIG.FEDERATION_API}`)
|
||||
return path.join(
|
||||
__dirname,
|
||||
`./${CONFIG.FEDERATION_API}/resolver/*Resolver.{ts,js}`
|
||||
)
|
||||
}
|
||||
23
federation/src/graphql/scalar/Decimal.ts
Normal file
23
federation/src/graphql/scalar/Decimal.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { GraphQLScalarType, Kind } from 'graphql'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
export default new GraphQLScalarType({
|
||||
name: 'Decimal',
|
||||
description: 'The `Decimal` scalar type to represent currency values',
|
||||
|
||||
serialize(value: Decimal) {
|
||||
return value.toString()
|
||||
},
|
||||
|
||||
parseValue(value) {
|
||||
return new Decimal(value)
|
||||
},
|
||||
|
||||
parseLiteral(ast) {
|
||||
if (ast.kind !== Kind.STRING) {
|
||||
throw new TypeError(`${String(ast)} is not a valid decimal value.`)
|
||||
}
|
||||
|
||||
return new Decimal(ast.value)
|
||||
},
|
||||
})
|
||||
17
federation/src/graphql/schema.ts
Normal file
17
federation/src/graphql/schema.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { GraphQLSchema } from 'graphql'
|
||||
import { buildSchema } from 'type-graphql'
|
||||
|
||||
// import isAuthorized from './directive/isAuthorized'
|
||||
import DecimalScalar from './scalar/Decimal'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { getApiResolvers } from './api/schema'
|
||||
|
||||
const schema = async (): Promise<GraphQLSchema> => {
|
||||
return await buildSchema({
|
||||
resolvers: [getApiResolvers()],
|
||||
// authChecker: isAuthorized,
|
||||
scalarsMap: [{ type: Decimal, scalar: DecimalScalar }],
|
||||
})
|
||||
}
|
||||
|
||||
export default schema
|
||||
33
federation/src/index.ts
Normal file
33
federation/src/index.ts
Normal file
@ -0,0 +1,33 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import createServer from './server/createServer'
|
||||
|
||||
// config
|
||||
import CONFIG from './config'
|
||||
|
||||
async function main() {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`FEDERATION_PORT=${CONFIG.FEDERATION_PORT}`)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`FEDERATION_API=${CONFIG.FEDERATION_API}`)
|
||||
const { app } = await createServer()
|
||||
|
||||
app.listen(CONFIG.FEDERATION_PORT, () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`Server is running at http://localhost:${CONFIG.FEDERATION_PORT}`
|
||||
)
|
||||
if (CONFIG.GRAPHIQL) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`GraphIQL available at http://localhost:${CONFIG.FEDERATION_PORT}`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
})
|
||||
8
federation/src/server/cors.ts
Normal file
8
federation/src/server/cors.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import cors from 'cors'
|
||||
|
||||
const corsOptions = {
|
||||
origin: '*',
|
||||
exposedHeaders: ['token'],
|
||||
}
|
||||
|
||||
export default cors(corsOptions)
|
||||
91
federation/src/server/createServer.ts
Normal file
91
federation/src/server/createServer.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { ApolloServer } from 'apollo-server-express'
|
||||
import express, { Express } from 'express'
|
||||
|
||||
// database
|
||||
import connection from '@/typeorm/connection'
|
||||
import { checkDBVersion } from '@/typeorm/DBVersion'
|
||||
|
||||
// server
|
||||
import cors from './cors'
|
||||
// import serverContext from './context'
|
||||
import plugins from './plugins'
|
||||
|
||||
// config
|
||||
import CONFIG from '@/config'
|
||||
|
||||
// graphql
|
||||
import schema from '@/graphql/schema'
|
||||
|
||||
// webhooks
|
||||
// import { elopageWebhook } from '@/webhook/elopage'
|
||||
import { Connection } from '@dbTools/typeorm'
|
||||
|
||||
import { apolloLogger } from './logger'
|
||||
import { Logger } from 'log4js'
|
||||
|
||||
// i18n
|
||||
// import { i18n } from './localization'
|
||||
|
||||
// TODO implement
|
||||
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
|
||||
|
||||
type ServerDef = { apollo: ApolloServer; app: Express; con: Connection }
|
||||
|
||||
const createServer = async (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// context: any = serverContext,
|
||||
logger: Logger = apolloLogger
|
||||
// localization: i18n.I18n = i18n,
|
||||
): Promise<ServerDef> => {
|
||||
logger.addContext('user', 'unknown')
|
||||
logger.debug('createServer...')
|
||||
|
||||
// open mysql connection
|
||||
const con = await connection()
|
||||
if (!con || !con.isConnected) {
|
||||
logger.fatal(`Couldn't open connection to database!`)
|
||||
throw new Error(`Fatal: Couldn't open connection to database`)
|
||||
}
|
||||
|
||||
// check for correct database version
|
||||
const dbVersion = await checkDBVersion(CONFIG.DB_VERSION)
|
||||
if (!dbVersion) {
|
||||
logger.fatal('Fatal: Database Version incorrect')
|
||||
throw new Error('Fatal: Database Version incorrect')
|
||||
}
|
||||
|
||||
// Express Server
|
||||
const app = express()
|
||||
|
||||
// cors
|
||||
app.use(cors)
|
||||
|
||||
// bodyparser json
|
||||
app.use(express.json())
|
||||
// bodyparser urlencoded for elopage
|
||||
app.use(express.urlencoded({ extended: true }))
|
||||
|
||||
// i18n
|
||||
// app.use(localization.init)
|
||||
|
||||
// Elopage Webhook
|
||||
// app.post('/hook/elopage/' + CONFIG.WEBHOOK_ELOPAGE_SECRET, elopageWebhook)
|
||||
|
||||
// Apollo Server
|
||||
const apollo = new ApolloServer({
|
||||
schema: await schema(),
|
||||
// playground: CONFIG.GRAPHIQL,
|
||||
// introspection: CONFIG.GRAPHIQL,
|
||||
// context,
|
||||
plugins,
|
||||
logger,
|
||||
})
|
||||
apollo.applyMiddleware({ app, path: '/' })
|
||||
logger.debug('createServer...successful')
|
||||
|
||||
return { apollo, app, con }
|
||||
}
|
||||
|
||||
export default createServer
|
||||
43
federation/src/server/logger.ts
Normal file
43
federation/src/server/logger.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import log4js from 'log4js'
|
||||
import CONFIG from '@/config'
|
||||
|
||||
import { readFileSync } from 'fs'
|
||||
|
||||
const options = JSON.parse(readFileSync(CONFIG.LOG4JS_CONFIG, 'utf-8'))
|
||||
|
||||
options.categories.backend.level = CONFIG.LOG_LEVEL
|
||||
options.categories.apollo.level = CONFIG.LOG_LEVEL
|
||||
let filename: string = options.appenders.federation.filename
|
||||
options.appenders.federation.filename = filename
|
||||
.replace('%v', CONFIG.FEDERATION_API)
|
||||
.replace('%p', CONFIG.FEDERATION_PORT.toString())
|
||||
filename = options.appenders.access.filename
|
||||
options.appenders.access.filename = filename.replace(
|
||||
'%p',
|
||||
CONFIG.FEDERATION_PORT.toString()
|
||||
)
|
||||
filename = options.appenders.apollo.filename
|
||||
options.appenders.apollo.filename = filename.replace(
|
||||
'%p',
|
||||
CONFIG.FEDERATION_PORT.toString()
|
||||
)
|
||||
filename = options.appenders.backend.filename
|
||||
options.appenders.backend.filename = filename.replace(
|
||||
'%p',
|
||||
CONFIG.FEDERATION_PORT.toString()
|
||||
)
|
||||
filename = options.appenders.errorFile.filename
|
||||
options.appenders.errorFile.filename = filename.replace(
|
||||
'%p',
|
||||
CONFIG.FEDERATION_PORT.toString()
|
||||
)
|
||||
|
||||
log4js.configure(options)
|
||||
|
||||
const apolloLogger = log4js.getLogger('apollo')
|
||||
// const backendLogger = log4js.getLogger('backend')
|
||||
const federationLogger = log4js.getLogger('federation')
|
||||
|
||||
// backendLogger.addContext('user', 'unknown')
|
||||
|
||||
export { apolloLogger, federationLogger }
|
||||
68
federation/src/server/plugins.ts
Normal file
68
federation/src/server/plugins.ts
Normal file
@ -0,0 +1,68 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import clonedeep from 'lodash.clonedeep'
|
||||
|
||||
const setHeadersPlugin = {
|
||||
requestDidStart() {
|
||||
return {
|
||||
willSendResponse(requestContext: any) {
|
||||
const { setHeaders = [] } = requestContext.context
|
||||
setHeaders.forEach(({ key, value }: { [key: string]: string }) => {
|
||||
if (requestContext.response.http.headers.get(key)) {
|
||||
requestContext.response.http.headers.set(key, value)
|
||||
} else {
|
||||
requestContext.response.http.headers.append(key, value)
|
||||
}
|
||||
})
|
||||
return requestContext
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const filterVariables = (variables: any) => {
|
||||
const vars = clonedeep(variables)
|
||||
if (vars.password) vars.password = '***'
|
||||
if (vars.passwordNew) vars.passwordNew = '***'
|
||||
return vars
|
||||
}
|
||||
|
||||
const logPlugin = {
|
||||
requestDidStart(requestContext: any) {
|
||||
const { logger } = requestContext
|
||||
const { query, mutation, variables, operationName } = requestContext.request
|
||||
if (operationName !== 'IntrospectionQuery') {
|
||||
logger.info(`Request:
|
||||
${mutation || query}variables: ${JSON.stringify(
|
||||
filterVariables(variables),
|
||||
null,
|
||||
2
|
||||
)}`)
|
||||
}
|
||||
return {
|
||||
willSendResponse(requestContext: any) {
|
||||
if (operationName !== 'IntrospectionQuery') {
|
||||
if (requestContext.context.user)
|
||||
logger.info(`User ID: ${requestContext.context.user.id}`)
|
||||
if (requestContext.response.data) {
|
||||
logger.info('Response Success!')
|
||||
logger.trace(`Response-Data:
|
||||
${JSON.stringify(requestContext.response.data, null, 2)}`)
|
||||
}
|
||||
if (requestContext.response.errors)
|
||||
logger.error(`Response-Errors:
|
||||
${JSON.stringify(requestContext.response.errors, null, 2)}`)
|
||||
}
|
||||
return requestContext
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const plugins =
|
||||
process.env.NODE_ENV === 'development'
|
||||
? [setHeadersPlugin]
|
||||
: [setHeadersPlugin, logPlugin]
|
||||
|
||||
export default plugins
|
||||
27
federation/src/typeorm/DBVersion.ts
Normal file
27
federation/src/typeorm/DBVersion.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Migration } from '@entity/Migration'
|
||||
import { federationLogger as logger } from '@/server/logger'
|
||||
|
||||
const getDBVersion = async (): Promise<string | null> => {
|
||||
try {
|
||||
const dbVersion = await Migration.findOne({ order: { version: 'DESC' } })
|
||||
return dbVersion ? dbVersion.fileName : null
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const checkDBVersion = async (DB_VERSION: string): Promise<boolean> => {
|
||||
const dbVersion = await getDBVersion()
|
||||
if (!dbVersion || dbVersion.indexOf(DB_VERSION) === -1) {
|
||||
logger.error(
|
||||
`Wrong database version detected - the backend requires '${DB_VERSION}' but found '${
|
||||
dbVersion || 'None'
|
||||
}`
|
||||
)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export { checkDBVersion, getDBVersion }
|
||||
34
federation/src/typeorm/connection.ts
Normal file
34
federation/src/typeorm/connection.ts
Normal file
@ -0,0 +1,34 @@
|
||||
// TODO This is super weird - since the entities are defined in another project they have their own globals.
|
||||
// We cannot use our connection here, but must use the external typeorm installation
|
||||
import { Connection, createConnection, FileLogger } from '@dbTools/typeorm'
|
||||
import CONFIG from '@/config'
|
||||
import { entities } from '@entity/index'
|
||||
|
||||
const connection = async (): Promise<Connection | null> => {
|
||||
try {
|
||||
return createConnection({
|
||||
name: 'default',
|
||||
type: 'mysql',
|
||||
host: CONFIG.DB_HOST,
|
||||
port: CONFIG.DB_PORT,
|
||||
username: CONFIG.DB_USER,
|
||||
password: CONFIG.DB_PASSWORD,
|
||||
database: CONFIG.DB_DATABASE,
|
||||
entities,
|
||||
synchronize: false,
|
||||
logging: true,
|
||||
logger: new FileLogger('all', {
|
||||
logPath: CONFIG.TYPEORM_LOGGING_RELATIVE_PATH,
|
||||
}),
|
||||
extra: {
|
||||
charset: 'utf8mb4_unicode_ci',
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export default connection
|
||||
90
federation/tsconfig.json
Normal file
90
federation/tsconfig.json
Normal file
@ -0,0 +1,90 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./build", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
"strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */
|
||||
"paths": { /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
"@/*": ["src/*"],
|
||||
// "@arg/*": ["src/graphql/arg/*"],
|
||||
// "@enum/*": ["src/graphql/enum/*"],
|
||||
// "@model/*": ["src/graphql/model/*"],
|
||||
"@repository/*": ["src/typeorm/repository/*"],
|
||||
// "@test/*": ["test/*"],
|
||||
/* external */
|
||||
"@typeorm/*": ["../backend/src/typeorm/*", "../../backend/src/typeorm/*"],
|
||||
"@dbTools/*": ["../database/src/*", "../../database/build/src/*"],
|
||||
"@entity/*": ["../database/entity/*", "../../database/build/entity/*"]
|
||||
},
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
"typeRoots": ["src/dht_node/@types", "node_modules/@types"], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../database/tsconfig.json",
|
||||
// add 'prepend' if you want to include the referenced project in your output file
|
||||
// "prepend": true
|
||||
}
|
||||
]
|
||||
}
|
||||
3247
federation/yarn.lock
Normal file
3247
federation/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bootstrap-vue-gradido-wallet",
|
||||
"version": "1.16.0",
|
||||
"version": "1.17.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node run/server.js",
|
||||
@ -66,6 +66,7 @@
|
||||
"vuex-persistedstate": "^4.0.0-beta.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apollo/client": "^3.7.4",
|
||||
"@intlify/eslint-plugin-vue-i18n": "^1.4.0",
|
||||
"@vue/cli-plugin-babel": "^3.7.0",
|
||||
"@vue/cli-plugin-eslint": "^3.7.0",
|
||||
@ -76,6 +77,7 @@
|
||||
"babel-plugin-transform-require-context": "^0.1.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv-webpack": "^7.0.3",
|
||||
"mock-apollo-client": "^1.2.1",
|
||||
"postcss": "^8.4.8",
|
||||
"postcss-html": "^1.3.0",
|
||||
"postcss-scss": "^4.0.3",
|
||||
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
font-family: 'WorkSans', sans-serif !important;
|
||||
}
|
||||
.appContent {
|
||||
min-width: 360px;
|
||||
min-width: 330px;
|
||||
max-width: 1320px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
"date": "01 janvier 2023",
|
||||
"text": "Compte Gradido 2023 : nouveau design et communautés décentralisées",
|
||||
"url": "https://gradido.net/fr/gradido-konto-2023-neues-design-und-dezentrale-communities/",
|
||||
"extra": "Ce sont souvent les personnes les plus discrètes qui créent silencieusement, avec application et passion, les bases de grands développements. Notre site Développeur ont effectué ces derniers mois un travail préparatoire formidable qui sera mis à profit en 2023."
|
||||
"extra": "Ce sont souvent les personnes les plus discrètes qui créent silencieusement, avec application et passion, les bases de grands développements. Nos développeurs ont effectués ces derniers mois un travail préparatoire formidable qui sera mis à profit en 2023."
|
||||
},
|
||||
{
|
||||
"locale": "es",
|
||||
|
||||
@ -53,6 +53,7 @@ export default {
|
||||
|
||||
.auth-header {
|
||||
font-family: 'Open Sans', sans-serif !important;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.sheet-img {
|
||||
@ -61,6 +62,17 @@ export default {
|
||||
max-width: 64%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.auth-header {
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.auth-header {
|
||||
height: 70px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 450px) {
|
||||
.sheet-img {
|
||||
top: -15px;
|
||||
|
||||
@ -1,17 +1,31 @@
|
||||
<template>
|
||||
<div class="clipboard-copy">
|
||||
<div v-if="canCopyLink" size="lg" class="mb-5">
|
||||
<div class="d-flex">
|
||||
<div>
|
||||
<label>{{ $t('gdd_per_link.copy-link') }}</label>
|
||||
<div class="pointer text-center bg-secondary gradido-border-radius p-4" @click="copyLink">
|
||||
{{ link }}
|
||||
<div v-if="canCopyLink" class="mb-5">
|
||||
<div>
|
||||
<label>{{ $t('gdd_per_link.copy-link') }}</label>
|
||||
<div
|
||||
class="pointer text-center bg-secondary gradido-border-radius p-3"
|
||||
@click="copyLink"
|
||||
data-test="copyLink"
|
||||
>
|
||||
{{ link }}
|
||||
<div>
|
||||
<b-button class="p-4">
|
||||
<b-icon icon="link45deg"></b-icon>
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-5">
|
||||
<label>{{ $t('gdd_per_link.copy-link-with-text') }}</label>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<label>{{ $t('gdd_per_link.copy-link-with-text') }}</label>
|
||||
<div
|
||||
class="pointer text-center bg-secondary gradido-border-radius p-3"
|
||||
data-test="copyLinkWithText"
|
||||
@click="copyLinkWithText"
|
||||
>
|
||||
{{ linkText }}
|
||||
<div>
|
||||
<b-button @click="copyLinkWithText" class="p-4">
|
||||
<b-button class="p-4">
|
||||
<b-icon icon="link45deg"></b-icon>
|
||||
</b-button>
|
||||
</div>
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
:label-no-date-selected="$t('contribution.noDateSelected')"
|
||||
required
|
||||
:disabled="this.form.id !== null"
|
||||
:no-flip="true"
|
||||
>
|
||||
<template #nav-prev-year><span></span></template>
|
||||
<template #nav-next-year><span></span></template>
|
||||
@ -34,12 +35,12 @@
|
||||
v-model="form.hours"
|
||||
:name="$t('form.hours')"
|
||||
:label="$t('form.hours')"
|
||||
placeholder="0.5"
|
||||
placeholder="0.25"
|
||||
:rules="{
|
||||
required: true,
|
||||
min: 0.5,
|
||||
min: 0.25,
|
||||
max: validMaxTime,
|
||||
gddCreationTime: [0.5, validMaxTime],
|
||||
gddCreationTime: [0.25, validMaxTime],
|
||||
}"
|
||||
:validMaxTime="validMaxTime"
|
||||
@updateAmount="updateAmount"
|
||||
|
||||
@ -16,20 +16,27 @@
|
||||
<b-avatar v-else :icon="icon" :variant="variant" size="3em"></b-avatar>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<div v-if="firstName" class="mr-3 font-weight-bold">{{ firstName }} {{ lastName }}</div>
|
||||
<div v-if="firstName" class="mr-3 font-weight-bold">
|
||||
{{ firstName }} {{ lastName }}
|
||||
<b-icon :icon="icon" :variant="variant"></b-icon>
|
||||
</div>
|
||||
<div class="small">
|
||||
{{ $d(new Date(contributionDate), 'monthAndYear') }}
|
||||
</div>
|
||||
<div class="mt-3 font-weight-bold">{{ $t('contributionText') }}</div>
|
||||
<div class="mb-3">{{ memo }}</div>
|
||||
<div class="mb-3 text-break word-break">{{ memo }}</div>
|
||||
<div v-if="state === 'IN_PROGRESS'" class="text-205">
|
||||
{{ $t('contribution.alert.answerQuestion') }}
|
||||
</div>
|
||||
</b-col>
|
||||
<b-col cols="12" lg="3" offset="3" offset-md="0" offset-lg="0">
|
||||
<b-col cols="9" lg="3" offset="3" offset-md="0" offset-lg="0">
|
||||
<div class="small">
|
||||
{{ $t('creation') }} {{ $t('(') }}{{ amount / 20 }} {{ $t('h') }}{{ $t(')') }}
|
||||
</div>
|
||||
<div v-if="state === 'DENIED' && allContribution" class="font-weight-bold">
|
||||
<b-icon icon="x-circle" variant="danger"></b-icon>
|
||||
{{ $t('contribution.alert.denied') }}
|
||||
</div>
|
||||
<div v-if="state === 'DELETED'" class="small">
|
||||
{{ $t('contribution.deleted') }}
|
||||
</div>
|
||||
@ -143,6 +150,14 @@ export default {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
deniedBy: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
deniedAt: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
state: {
|
||||
type: String,
|
||||
required: false,
|
||||
@ -172,12 +187,14 @@ export default {
|
||||
computed: {
|
||||
icon() {
|
||||
if (this.deletedAt) return 'trash'
|
||||
if (this.deniedAt) return 'x-circle'
|
||||
if (this.confirmedAt) return 'check'
|
||||
if (this.state === 'IN_PROGRESS') return 'question-circle'
|
||||
return 'bell-fill'
|
||||
},
|
||||
variant() {
|
||||
if (this.deletedAt) return 'danger'
|
||||
if (this.deniedAt) return 'warning'
|
||||
if (this.confirmedAt) return 'success'
|
||||
if (this.state === 'IN_PROGRESS') return 'f5'
|
||||
return 'primary'
|
||||
|
||||
@ -10,6 +10,12 @@ const mocks = {
|
||||
$tc: jest.fn((tc) => tc),
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
$store: {
|
||||
state: {
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
:memo="memo"
|
||||
:validUntil="validUntil"
|
||||
></clipboard-copy>
|
||||
<label>{{ $t('qrCode') }}</label>
|
||||
<div class="text-center">
|
||||
<div><figure-qr-code :link="link" /></div>
|
||||
<div>
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
:placeholder="placeholder"
|
||||
type="number"
|
||||
:state="validated ? valid : false"
|
||||
step="0.5"
|
||||
step="0.25"
|
||||
min="0"
|
||||
:max="validMaxTime"
|
||||
class="bg-248"
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
rows="4"
|
||||
max-rows="4"
|
||||
:disabled="disabled"
|
||||
no-resize
|
||||
></b-form-textarea>
|
||||
<b-form-invalid-feedback v-bind="ariaMsg">
|
||||
{{ errors[0] }}
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
<div>
|
||||
<div data-test="navbar-item-username">{{ username.username }}</div>
|
||||
|
||||
<div class="text-right" data-test="navbar-item-email">
|
||||
<div data-test="navbar-item-email">
|
||||
{{ $store.state.email }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -55,7 +55,7 @@ describe('ContributionInfo', () => {
|
||||
expect(listItems.at(2).text()).toBe('contribution.alert.confirm')
|
||||
|
||||
expect(listItems.at(3).find('svg').attributes('aria-label')).toEqual('x circle')
|
||||
expect(listItems.at(3).text()).toBe('contribution.alert.rejected')
|
||||
expect(listItems.at(3).text()).toBe('contribution.alert.denied')
|
||||
})
|
||||
})
|
||||
|
||||
@ -78,8 +78,14 @@ describe('ContributionInfo', () => {
|
||||
expect(listItems.at(0).find('svg').attributes('aria-label')).toEqual('bell fill')
|
||||
expect(listItems.at(0).text()).toBe('contribution.alert.pending')
|
||||
|
||||
expect(listItems.at(1).find('svg').attributes('aria-label')).toEqual('check')
|
||||
expect(listItems.at(1).text()).toBe('contribution.alert.confirm')
|
||||
expect(listItems.at(1).find('svg').attributes('aria-label')).toEqual('question square')
|
||||
expect(listItems.at(1).text()).toBe('contribution.alert.in_progress')
|
||||
|
||||
expect(listItems.at(2).find('svg').attributes('aria-label')).toEqual('check')
|
||||
expect(listItems.at(2).text()).toBe('contribution.alert.confirm')
|
||||
|
||||
expect(listItems.at(3).find('svg').attributes('aria-label')).toEqual('x circle')
|
||||
expect(listItems.at(3).text()).toBe('contribution.alert.denied')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -19,8 +19,8 @@
|
||||
{{ $t('contribution.alert.confirm') }}
|
||||
</li>
|
||||
<li>
|
||||
<b-icon icon="x-circle" variant="danger"></b-icon>
|
||||
{{ $t('contribution.alert.rejected') }}
|
||||
<b-icon icon="x-circle" variant="warning"></b-icon>
|
||||
{{ $t('contribution.alert.denied') }}
|
||||
</li>
|
||||
<li>
|
||||
<b-icon icon="trash" variant="danger"></b-icon>
|
||||
@ -38,10 +38,18 @@
|
||||
<b-icon icon="bell-fill" variant="primary"></b-icon>
|
||||
{{ $t('contribution.alert.pending') }}
|
||||
</li>
|
||||
<li>
|
||||
<b-icon icon="question-square" variant="warning"></b-icon>
|
||||
{{ $t('contribution.alert.in_progress') }}
|
||||
</li>
|
||||
<li>
|
||||
<b-icon icon="check" variant="success"></b-icon>
|
||||
{{ $t('contribution.alert.confirm') }}
|
||||
</li>
|
||||
<li>
|
||||
<b-icon icon="x-circle" variant="warning"></b-icon>
|
||||
{{ $t('contribution.alert.denied') }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="hash === '#edit'" show fade variant="secondary" class="text-dark">
|
||||
|
||||
@ -17,6 +17,12 @@ const mocks = {
|
||||
$apollo: {
|
||||
query: apolloQueryMock,
|
||||
},
|
||||
$store: {
|
||||
state: {
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
@ -102,10 +108,10 @@ describe('TransactionLinkSummary', () => {
|
||||
|
||||
describe('click on transaction links', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper.find('div.transaction-slot-link').trigger('click')
|
||||
wrapper.find('div.row').trigger('click')
|
||||
})
|
||||
|
||||
it.skip('calls the API to get the list transaction links', () => {
|
||||
it('calls the API to get the list transaction links', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith({
|
||||
query: listTransactionLinks,
|
||||
variables: {
|
||||
@ -115,14 +121,14 @@ describe('TransactionLinkSummary', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it.skip('has four transactionLinks', () => {
|
||||
it('has four transactionLinks', () => {
|
||||
expect(wrapper.vm.transactionLinks).toHaveLength(4)
|
||||
})
|
||||
|
||||
describe('close transaction link details', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
wrapper.find('div.transaction-slot-link').trigger('click')
|
||||
wrapper.find('div.row').trigger('click')
|
||||
})
|
||||
|
||||
it('does not call the API', () => {
|
||||
@ -136,10 +142,10 @@ describe('TransactionLinkSummary', () => {
|
||||
describe('reopen transaction link details', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
wrapper.find('div.transaction-slot-link').trigger('click')
|
||||
wrapper.find('div.row').trigger('click')
|
||||
})
|
||||
|
||||
it.skip('calls the API to get the list transaction links', () => {
|
||||
it('calls the API to get the list transaction links', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith({
|
||||
query: listTransactionLinks,
|
||||
variables: {
|
||||
@ -149,7 +155,7 @@ describe('TransactionLinkSummary', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it.skip('has four transactionLinks', () => {
|
||||
it('has four transactionLinks', () => {
|
||||
expect(wrapper.vm.transactionLinks).toHaveLength(4)
|
||||
})
|
||||
})
|
||||
@ -215,7 +221,7 @@ describe('TransactionLinkSummary', () => {
|
||||
})
|
||||
|
||||
it('has eight transactionLinks', () => {
|
||||
expect(wrapper.vm.transactionLinks).toHaveLength(4)
|
||||
expect(wrapper.vm.transactionLinks).toHaveLength(8)
|
||||
})
|
||||
|
||||
it('loads more transaction links', () => {
|
||||
@ -230,19 +236,19 @@ describe('TransactionLinkSummary', () => {
|
||||
|
||||
describe('close transaction link list', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper.find('div.transaction-slot-link').trigger('click')
|
||||
wrapper.find('div.row').trigger('click')
|
||||
})
|
||||
describe('reopen transaction link list', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
wrapper.find('div.transaction-slot-link').trigger('click')
|
||||
wrapper.find('div.row').trigger('click')
|
||||
})
|
||||
|
||||
it.skip('calls the API once', () => {
|
||||
it('calls the API once', () => {
|
||||
expect(apolloQueryMock).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it.skip('calls the API with current page one', () => {
|
||||
it('calls the API with current page one', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith({
|
||||
query: listTransactionLinks,
|
||||
variables: {
|
||||
@ -289,10 +295,10 @@ describe('TransactionLinkSummary', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe.skip('loads transaction links with error', () => {
|
||||
describe('loads transaction links with error', () => {
|
||||
beforeEach(() => {
|
||||
apolloQueryMock.mockRejectedValue({ message: 'OUCH!' })
|
||||
wrapper.find('div.transaction-slot-link').trigger('click')
|
||||
wrapper.find('div.row').trigger('click')
|
||||
})
|
||||
|
||||
it('toasts an error message', () => {
|
||||
|
||||
@ -183,6 +183,8 @@ export const listContributions = gql`
|
||||
deletedAt
|
||||
state
|
||||
messagesCount
|
||||
deniedAt
|
||||
deniedBy
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -202,6 +204,10 @@ export const listAllContributions = gql`
|
||||
contributionDate
|
||||
confirmedAt
|
||||
confirmedBy
|
||||
state
|
||||
messagesCount
|
||||
deniedAt
|
||||
deniedBy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<div v-if="skeleton">
|
||||
<skeleton-overview />
|
||||
</div>
|
||||
<div v-else class="mx--3 mx-lg-0">
|
||||
<div v-else class="mx-lg-0">
|
||||
<!-- navbar -->
|
||||
<b-row>
|
||||
<b-col>
|
||||
@ -125,14 +125,13 @@
|
||||
:transactions="transactions"
|
||||
:transactionCount="transactionCount"
|
||||
:transactionLinkCount="transactionLinkCount"
|
||||
@set-tunneled-email="setTunneledEmail"
|
||||
>
|
||||
<template #transactions>
|
||||
<last-transactions
|
||||
:transactions="transactions"
|
||||
:transactionCount="transactionCount"
|
||||
:transactionLinkCount="transactionLinkCount"
|
||||
v-on="$listeners"
|
||||
@set-tunneled-email="setTunneledEmail"
|
||||
/>
|
||||
</template>
|
||||
<template #community>
|
||||
@ -167,14 +166,13 @@
|
||||
:transactions="transactions"
|
||||
:transactionCount="transactionCount"
|
||||
:transactionLinkCount="transactionLinkCount"
|
||||
@set-tunneled-email="setTunneledEmail"
|
||||
>
|
||||
<template #transactions>
|
||||
<last-transactions
|
||||
:transactions="transactions"
|
||||
:transactionCount="transactionCount"
|
||||
:transactionLinkCount="transactionLinkCount"
|
||||
v-on="$listeners"
|
||||
@set-tunneled-email="setTunneledEmail"
|
||||
/>
|
||||
</template>
|
||||
<template #community>
|
||||
|
||||
@ -30,21 +30,22 @@
|
||||
"noOpenContributionLinkText": "Zur Zeit gibt es keine automatischen Schöpfungen.",
|
||||
"openContributionLinks": "Öffentliche Beitrags-Linkliste",
|
||||
"openContributionLinkText": "Folgende {count} automatische Schöpfungen werden zur Zeit durch die Gemeinschaft „{name}“ bereitgestellt.",
|
||||
"submitContribution": "schreiben"
|
||||
"submitContribution": "Schreiben"
|
||||
},
|
||||
"communityInfo": "Gemeinschaft Information",
|
||||
"contact": "Kontakt",
|
||||
"contribution": {
|
||||
"activity": "Tätigkeit",
|
||||
"alert": {
|
||||
"answerQuestion": "Bitte beantworte die Rückfrage!",
|
||||
"answerQuestion": "Bitte beantworte diese Rückfrage.",
|
||||
"answerQuestionToast": "Du hast eine Rückfrage auf einen Beitrag. Bitte antworte auf diese.",
|
||||
"communityNoteList": "Hier findest du alle eingereichten und bestätigten Beiträge von allen Mitgliedern aus dieser Gemeinschaft.",
|
||||
"confirm": "bestätigt",
|
||||
"deleted": "gelöscht",
|
||||
"denied": "abgelehnt",
|
||||
"in_progress": "Es gibt eine Rückfrage der Moderatoren.",
|
||||
"myContributionNoteList": "Eingereichte Beiträge, die noch nicht bestätigt wurden, kannst du jederzeit bearbeiten oder löschen.",
|
||||
"pending": "Eingereicht und wartet auf Bestätigung",
|
||||
"rejected": "abgelehnt"
|
||||
"pending": "Eingereicht und wartet auf Bestätigung"
|
||||
},
|
||||
"delete": "Beitrag löschen! Bist du sicher?",
|
||||
"deleted": "Der Beitrag wurde gelöscht! Wird aber sichtbar bleiben.",
|
||||
|
||||
@ -30,21 +30,22 @@
|
||||
"noOpenContributionLinkText": "Currently there are no automatic creations.",
|
||||
"openContributionLinks": "Open contribution-link list",
|
||||
"openContributionLinkText": "The following {count} automatic creations are currently provided by the \"{name}\" community.",
|
||||
"submitContribution": "writing"
|
||||
"submitContribution": "Contribute"
|
||||
},
|
||||
"communityInfo": "Community Information",
|
||||
"contact": "Contact",
|
||||
"contribution": {
|
||||
"activity": "Activity",
|
||||
"alert": {
|
||||
"answerQuestion": "Please answer the question",
|
||||
"answerQuestion": "Please answer the question.",
|
||||
"answerQuestionToast": "You have a question about a post. Please reply to it.",
|
||||
"communityNoteList": "Here you will find all submitted and confirmed contributions from all members of this community.",
|
||||
"confirm": "confirmed",
|
||||
"deleted": "deleted",
|
||||
"denied": "rejected",
|
||||
"in_progress": "There is a question from the moderators.",
|
||||
"myContributionNoteList": "You can edit or delete entries that have not yet been confirmed at any time.",
|
||||
"pending": "Submitted and waiting for confirmation",
|
||||
"rejected": "deleted"
|
||||
"pending": "Submitted and waiting for confirmation"
|
||||
},
|
||||
"delete": "Delete Contribution! Are you sure?",
|
||||
"deleted": "The contribution has been deleted! But it will remain visible.",
|
||||
@ -126,7 +127,7 @@
|
||||
"firstname": "Firstname",
|
||||
"from": "from",
|
||||
"generate_now": "Generate now",
|
||||
"hours": "Horas",
|
||||
"hours": "Hours",
|
||||
"lastname": "Lastname",
|
||||
"memo": "Message",
|
||||
"message": "Message",
|
||||
|
||||
@ -39,10 +39,10 @@
|
||||
"answerQuestion": "Por favor, contesta las preguntas",
|
||||
"communityNoteList": "Aquí encontrarás todas las contribuciones enviadas y confirmadas de todos los miembros de esta comunidad.",
|
||||
"confirm": "confirmado",
|
||||
"denied": "rechazado",
|
||||
"in_progress": "Hay una pregunta de los moderatores.",
|
||||
"myContributionNoteList": "Puedes editar o eliminar las contribuciones enviadas que aún no han sido confirmadas en cualquier momento.",
|
||||
"pending": "Enviado y a la espera de confirmación",
|
||||
"rejected": "rechazado"
|
||||
"pending": "Enviado y a la espera de confirmación"
|
||||
},
|
||||
"date": "Contribución para:",
|
||||
"delete": "Eliminar la contribución. ¿Estás seguro?",
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
{
|
||||
"(": "(",
|
||||
")": ")",
|
||||
"100": "100%",
|
||||
"1000thanks": "1000 mercis d'être avec nous!",
|
||||
"125": "125%",
|
||||
@ -20,29 +22,29 @@
|
||||
"community": {
|
||||
"choose-another-community": "Choisissez une autre communauté",
|
||||
"community": "Communauté",
|
||||
"communityMember": "Vous etes un membre actif",
|
||||
"continue-to-registration": "Continuez l´inscription",
|
||||
"current-community": "Communauté actuelle",
|
||||
"members": "Membres",
|
||||
"moderator": "Modérateur",
|
||||
"moderators": "Modérateurs",
|
||||
"myContributions": "Mes contributions aux biens communs",
|
||||
"myContributions": "Mes contributions",
|
||||
"noOpenContributionLinkText": "Currently there are no automatic creations.",
|
||||
"openContributionLinks": "liste de liens de contribution publique",
|
||||
"openContributionLinkText": "Les {count} créations automatiques suivantes sont actuellement fournies par la communauté \"{name}\".",
|
||||
"other-communities": "Autres communautés",
|
||||
"statistic": "Statistiques",
|
||||
"submitContribution": "écrire",
|
||||
"switch-to-this-community": "Passer à cette communauté"
|
||||
"submitContribution": "Contribuer"
|
||||
},
|
||||
"communityInfo": "Information communauté^^",
|
||||
"contact": "Contact",
|
||||
"contribution": {
|
||||
"activity": "Activité",
|
||||
"alert": {
|
||||
"answerQuestion": "S'il te plais répond à la question",
|
||||
"communityNoteList": "Vous trouverez ci-contre toutes les contributions versées et certifiées de tous les membres de cette communauté.",
|
||||
"confirm": " Approuvé",
|
||||
"confirm": "Approuvé",
|
||||
"deleted": "Supprimé",
|
||||
"denied": "supprimé",
|
||||
"in_progress": "Il y a une question du modérateur.",
|
||||
"myContributionNoteList": "À tout moment vous pouvez éditer ou supprimer les données qui n´ont pas été confirmées.",
|
||||
"pending": "Inscription en attente de validation",
|
||||
"rejected": "supprimé"
|
||||
"pending": "Inscription en attente de validation"
|
||||
},
|
||||
"date": "Contribution pour:",
|
||||
"delete": "Supprimer la contribution! Êtes-vous sûr?",
|
||||
@ -65,6 +67,8 @@
|
||||
"thanksYouWith": "vous remercie avec",
|
||||
"unique": "(unique)"
|
||||
},
|
||||
"contributionText": "Texte de la contribution",
|
||||
"creation": "Création",
|
||||
"decay": {
|
||||
"before_startblock_transaction": "Cette transaction n´est pas péremptoire.",
|
||||
"calculation_decay": "Calcul de la décroissance",
|
||||
@ -84,6 +88,7 @@
|
||||
}
|
||||
},
|
||||
"delete": "Supprimer",
|
||||
"edit": "modifier",
|
||||
"em-dash": "—",
|
||||
"error": {
|
||||
"email-already-sent": "Nous vous avons déjà envoyé un email il y a moins de 10 minutes.",
|
||||
@ -121,7 +126,7 @@
|
||||
"firstname": " Prénom",
|
||||
"from": "de",
|
||||
"generate_now": "Produire maintenant",
|
||||
"hours": "Uren",
|
||||
"hours": "Heures",
|
||||
"lastname": "Nom",
|
||||
"memo": "Note",
|
||||
"message": "Message",
|
||||
@ -147,7 +152,8 @@
|
||||
"to": "à",
|
||||
"to1": "à",
|
||||
"validation": {
|
||||
"gddSendAmount": "L´espace {_field_} doit comprendre un nombre entre {min} et {max} avec un maximum de deux chiffres après la virgule",
|
||||
"gddCreationTime": "Le champ {_field_} doit comprendre un nombre entre {min} et {max} avec un maximum de une décimale.",
|
||||
"gddSendAmount": "Le champ {_field_} doit comprendre un nombre entre {min} et {max} avec un maximum de deux chiffres après la virgule",
|
||||
"is-not": "Vous ne pouvez pas vous envoyer de Gradido à vous-même",
|
||||
"usernmae-regex": "Le nom d´utilisateur doit commencer par une lettre, suivi d´au moins deux caractères alphanumériques.",
|
||||
"usernmae-unique": "Ce nom d´utilisateur est déjà pris."
|
||||
@ -155,13 +161,13 @@
|
||||
"your_amount": "Votre montant"
|
||||
},
|
||||
"GDD": "GDD",
|
||||
"gddKonto": "Compte GDD",
|
||||
"gdd_per_link": {
|
||||
"choose-amount": "Sélectionnez le montant que vous souhaitez envoyer via lien. Vous pouvez également joindre un message. Cliquez sur ‘créer maintenant’ pour établir un lien que vous pourrez partager.",
|
||||
"copy-link": "Copier le lien",
|
||||
"copy-link-with-text": "Copier le lien et le texte",
|
||||
"created": "Le lien a été créé!",
|
||||
"credit-your-gradido": "Pour l´accréditation du Gradido, cliquer sur le lien!",
|
||||
"decay-14-day": "Perte sur 14 jours",
|
||||
"delete-the-link": "Supprimer le lien?",
|
||||
"deleted": "Le lien a été supprimé!",
|
||||
"expiredOn": "A expiré le",
|
||||
@ -188,6 +194,7 @@
|
||||
"validUntil": "Valide jusqu´au",
|
||||
"validUntilDate": "Le lien est valide jusqu´au {date}."
|
||||
},
|
||||
"GDT": "GDT",
|
||||
"gdt": {
|
||||
"calculation": "Calcul de Gradido Transform",
|
||||
"contribution": "Contribution",
|
||||
@ -199,22 +206,25 @@
|
||||
"funding": "Aux contributions au financement",
|
||||
"gdt": "Gradido Transform",
|
||||
"gdt-received": "Gradido Transform (GDT) perçu",
|
||||
"gdtKonto": "Compte GDT",
|
||||
"no-transactions": "Vous ne possédez pas encore Gradido Transform (GDT).",
|
||||
"not-reachable": "Le Serveur GDT n´est pas accessible.",
|
||||
"publisher": "Un membre que vous avez référé a apporté un contribution",
|
||||
"raise": "Augmentation",
|
||||
"recruited-member": "Membre invité"
|
||||
},
|
||||
"h": "h",
|
||||
"language": "Langage",
|
||||
"link-load": "Enregistrer le dernier lien | Enregistrer les derniers {n} liens | Enregistrer plus de {n} liens",
|
||||
"login": "Connexion",
|
||||
"math": {
|
||||
"aprox": "~",
|
||||
"asterisk": "*",
|
||||
"equal": "=",
|
||||
"minus": "−",
|
||||
"pipe": "|"
|
||||
},
|
||||
"maxReached": "Max. atteint",
|
||||
"member": "Membre",
|
||||
"message": {
|
||||
"activateEmail": "Votre compte n´a pas encore été activé. Veuillez vérifier vos emails et cliquer sur le lien d´activation ou faites la demande d´un nouveau lien en utilisant la page qui permet de générer un nouveau mot de passe.",
|
||||
"checkEmail": "Votre email a bien été vérifié. Vous pouvez vous enregistrer maintenant.",
|
||||
@ -231,13 +241,22 @@
|
||||
"community": "Communauté",
|
||||
"info": "Information",
|
||||
"logout": "Déconnexion",
|
||||
"members_area": "Partie réservée aux membres",
|
||||
"overview": "Aperçu",
|
||||
"profile": "Mon profile",
|
||||
"send": "Envoyer",
|
||||
"settings": "Configuration",
|
||||
"support": "Aide",
|
||||
"transactions": "Transactions"
|
||||
},
|
||||
"openHours": "Heures ouverte",
|
||||
"pageTitle": {
|
||||
"community": "Ma communauté",
|
||||
"gdt": "Vos transactions GDT",
|
||||
"information": "{community}",
|
||||
"overview": "Bienvenue {name}",
|
||||
"send": "Envoyé Gradidos",
|
||||
"settings": "Configuration",
|
||||
"transactions": "Vos transactions"
|
||||
},
|
||||
"qrCode": "QR Code",
|
||||
"send_gdd": "Envoyer GDD",
|
||||
"send_per_link": "Envoyer GDD via lien",
|
||||
@ -248,6 +267,8 @@
|
||||
"warningText": "Êtes-vous toujours connecté?"
|
||||
},
|
||||
"settings": {
|
||||
"hideAmountGDD": "Votre montant GDD est caché.",
|
||||
"hideAmountGDT": "Votre montant GDT est caché.",
|
||||
"language": {
|
||||
"changeLanguage": "Changer la langue",
|
||||
"de": "Deutsch",
|
||||
@ -280,7 +301,9 @@
|
||||
"text": "Sauvegardez votre nouveau mot de passe maintenant, que vous pourrez utiliser pour vous connecter à votre compte Gradido dans le futur."
|
||||
},
|
||||
"subtitle": "Si vous avez oublié votre mot de passe, vous pouvez le réinitialiser ici."
|
||||
}
|
||||
},
|
||||
"showAmountGDD": "Votre montant GDD est visible.",
|
||||
"showAmountGDT": "Votre montant GDT est visible."
|
||||
},
|
||||
"signin": "S´identifier",
|
||||
"signup": "S´inscrire",
|
||||
@ -302,11 +325,8 @@
|
||||
"uppercase": "Une lettre majuscule requise."
|
||||
}
|
||||
},
|
||||
"statistic": {
|
||||
"totalGradidoAvailable": "GDD total en circulation",
|
||||
"totalGradidoCreated": "GDD total puisé",
|
||||
"totalGradidoDecayed": "Total de GDD écoulé"
|
||||
},
|
||||
"status": "Statu",
|
||||
"submitted": "Envoyé",
|
||||
"success": "Avec succès",
|
||||
"time": {
|
||||
"days": "Jours",
|
||||
@ -318,8 +338,7 @@
|
||||
"years": "Année"
|
||||
},
|
||||
"transaction": {
|
||||
"gdd-text": "Transactions Gradido",
|
||||
"gdt-text": "Transactions de GradidoTransform",
|
||||
"lastTransactions": "Dernières transactions",
|
||||
"nullTransactions": "Vous n´avez pas encore de transaction effectuée sur votre compte.",
|
||||
"receiverDeleted": "Le compte du destinataire n´existe plus",
|
||||
"receiverNotFound": "Destinataire inconnu",
|
||||
|
||||
@ -39,10 +39,10 @@
|
||||
"answerQuestion": "Please answer the question",
|
||||
"communityNoteList": "Hier vind je alle ingediende en bevestigde bijdragen van alle leden uit deze gemeenschap.",
|
||||
"confirm": "bevestigt",
|
||||
"denied": "afgewezen",
|
||||
"in_progress": "There is a question from the moderators.",
|
||||
"myContributionNoteList": "Ingediende bijdragen, die nog niet bevestigd zijn, kun je op elk moment wijzigen of verwijderen.",
|
||||
"pending": "Ingediend en wacht op bevestiging",
|
||||
"rejected": "afgewezen"
|
||||
"pending": "Ingediend en wacht op bevestiging"
|
||||
},
|
||||
"date": "Bijdrage voor:",
|
||||
"delete": "Bijdrage verwijderen! Weet je het zeker?",
|
||||
|
||||
@ -34,10 +34,10 @@
|
||||
"alert": {
|
||||
"communityNoteList": "Burada, bu topluluğun tüm üyelerinden gönderilen ve onaylanan bütün faydalı hizmetleri bulacaksın.",
|
||||
"confirm": "onaylandı",
|
||||
"denied": "reddedildi",
|
||||
"myContributionNoteList": "Bildirmiş olduğun henüz onaylanmamış olan faaliyetleri istediğin zaman düzenleyebilir veya silebilirsin.",
|
||||
"myContributionNoteSupport": "Yakın zamanda moderatörlerle aranda bir diyalog olasılığı olacak. Şu anda herhangi bir sorun yaşıyorsan, lütfen destek hattına başvur.",
|
||||
"pending": "Gönderildi ve onay bekleniyor",
|
||||
"rejected": "reddedildi"
|
||||
"pending": "Gönderildi ve onay bekleniyor"
|
||||
},
|
||||
"date": "Hizmet:",
|
||||
"delete": "Hizmeti sil! Emin misin?",
|
||||
|
||||
@ -24,15 +24,7 @@ export const copyLinks = {
|
||||
},
|
||||
copyLinkWithText() {
|
||||
navigator.clipboard
|
||||
.writeText(
|
||||
`${this.link}
|
||||
${this.$store.state.firstName} ${this.$t('transaction-link.send_you')} ${this.amount} Gradido.
|
||||
"${this.memo}"
|
||||
${this.$t('gdd_per_link.credit-your-gradido')} ${this.$t('gdd_per_link.validUntilDate', {
|
||||
date: this.$d(new Date(this.validUntil), 'short'),
|
||||
})}
|
||||
${this.$t('gdd_per_link.link-hint')}`,
|
||||
)
|
||||
.writeText(this.linkText)
|
||||
.then(() => {
|
||||
this.toastSuccess(this.$t('gdd_per_link.link-and-text-copied'))
|
||||
})
|
||||
@ -42,4 +34,15 @@ ${this.$t('gdd_per_link.link-hint')}`,
|
||||
})
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
linkText() {
|
||||
return `${this.link}
|
||||
${this.$store.state.firstName} ${this.$t('transaction-link.send_you')} ${this.amount} Gradido.
|
||||
"${this.memo}"
|
||||
${this.$t('gdd_per_link.credit-your-gradido')} ${this.$t('gdd_per_link.validUntilDate', {
|
||||
date: this.$d(new Date(this.validUntil), 'short'),
|
||||
})}
|
||||
${this.$t('gdd_per_link.link-hint')}`
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,58 +1,59 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Community from './Community'
|
||||
import { toastErrorSpy, toastSuccessSpy } from '@test/testSetup'
|
||||
import { toastErrorSpy, toastSuccessSpy, toastInfoSpy } from '@test/testSetup'
|
||||
import { createContribution, updateContribution, deleteContribution } from '@/graphql/mutations'
|
||||
import { listContributions, listAllContributions } from '@/graphql/queries'
|
||||
import { listContributions, listAllContributions, openCreations } from '@/graphql/queries'
|
||||
import { createMockClient } from 'mock-apollo-client'
|
||||
import VueApollo from 'vue-apollo'
|
||||
|
||||
const mockClient = createMockClient()
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: mockClient,
|
||||
})
|
||||
|
||||
const localVue = global.localVue
|
||||
localVue.use(VueApollo)
|
||||
|
||||
const mockStoreDispach = jest.fn()
|
||||
const apolloQueryMock = jest.fn()
|
||||
const apolloMutationMock = jest.fn()
|
||||
const apolloRefetchMock = jest.fn()
|
||||
const routerPushMock = jest.fn()
|
||||
|
||||
describe('Community', () => {
|
||||
let wrapper
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
$apollo: {
|
||||
query: apolloQueryMock,
|
||||
mutate: apolloMutationMock,
|
||||
queries: {
|
||||
OpenCreations: {
|
||||
refetch: apolloRefetchMock,
|
||||
mockClient.setRequestHandler(
|
||||
openCreations,
|
||||
jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce({ message: 'Open Creations failed!' })
|
||||
.mockResolvedValue({
|
||||
data: {
|
||||
openCreations: [
|
||||
{
|
||||
month: 0,
|
||||
year: 2023,
|
||||
amount: '1000',
|
||||
},
|
||||
{
|
||||
month: 1,
|
||||
year: 2023,
|
||||
amount: '1000',
|
||||
},
|
||||
{
|
||||
month: 2,
|
||||
year: 2023,
|
||||
amount: '1000',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
$store: {
|
||||
dispatch: mockStoreDispach,
|
||||
state: {
|
||||
creation: ['1000', '1000', '1000'],
|
||||
},
|
||||
},
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
$router: {
|
||||
push: jest.fn(),
|
||||
},
|
||||
$route: {
|
||||
hash: 'my',
|
||||
},
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(Community, {
|
||||
localVue,
|
||||
mocks,
|
||||
})
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
apolloQueryMock.mockResolvedValue({
|
||||
mockClient.setRequestHandler(
|
||||
listContributions,
|
||||
jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce({ message: 'List Contributions failed!' })
|
||||
.mockResolvedValue({
|
||||
data: {
|
||||
listContributions: {
|
||||
contributionList: [
|
||||
@ -64,10 +65,40 @@ describe('Community', () => {
|
||||
deletedAt: null,
|
||||
confirmedBy: null,
|
||||
confirmedAt: null,
|
||||
firstName: 'Bibi',
|
||||
contributionDate: '2022-07-15T08:47:06.000Z',
|
||||
lastName: 'Bloxberg',
|
||||
state: 'IN_PROGRESS',
|
||||
messagesCount: 0,
|
||||
},
|
||||
{
|
||||
id: 1550,
|
||||
amount: '200',
|
||||
memo: 'Fleisig, fleisig am Arbeiten gewesen',
|
||||
createdAt: '2022-07-15T08:47:06.000Z',
|
||||
deletedAt: null,
|
||||
confirmedBy: null,
|
||||
confirmedAt: null,
|
||||
firstName: 'Bibi',
|
||||
contributionDate: '2022-06-15T08:47:06.000Z',
|
||||
lastName: 'Bloxberg',
|
||||
state: 'CONFIRMED',
|
||||
messagesCount: 0,
|
||||
},
|
||||
],
|
||||
contributionCount: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
mockClient.setRequestHandler(
|
||||
listAllContributions,
|
||||
jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce({ message: 'List All Contributions failed!' })
|
||||
.mockResolvedValue({
|
||||
data: {
|
||||
listAllContributions: {
|
||||
contributionList: [
|
||||
{
|
||||
@ -75,29 +106,137 @@ describe('Community', () => {
|
||||
amount: '200',
|
||||
memo: 'Fleisig, fleisig am Arbeiten mein lieber Freund, 50 Zeichen sind viel',
|
||||
createdAt: '2022-07-15T08:47:06.000Z',
|
||||
contributionDate: '2022-07-15T08:47:06.000Z',
|
||||
deletedAt: null,
|
||||
confirmedBy: null,
|
||||
confirmedAt: null,
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
},
|
||||
{
|
||||
id: 1550,
|
||||
amount: '200',
|
||||
memo: 'Fleisig, fleisig am Arbeiten gewesen',
|
||||
createdAt: '2022-07-15T08:47:06.000Z',
|
||||
deletedAt: null,
|
||||
confirmedBy: null,
|
||||
confirmedAt: null,
|
||||
firstName: 'Bibi',
|
||||
contributionDate: '2022-06-15T08:47:06.000Z',
|
||||
lastName: 'Bloxberg',
|
||||
messagesCount: 0,
|
||||
},
|
||||
{
|
||||
id: 1556,
|
||||
amount: '400',
|
||||
memo: 'Ein anderer lieber Freund ist auch sehr felißig am Arbeiten!!!!',
|
||||
createdAt: '2022-07-16T08:47:06.000Z',
|
||||
contributionDate: '2022-07-16T08:47:06.000Z',
|
||||
deletedAt: null,
|
||||
confirmedBy: null,
|
||||
confirmedAt: null,
|
||||
firstName: 'Bob',
|
||||
lastName: 'der Baumeister',
|
||||
},
|
||||
],
|
||||
contributionCount: 2,
|
||||
contributionCount: 3,
|
||||
},
|
||||
},
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
mockClient.setRequestHandler(
|
||||
createContribution,
|
||||
jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce({ message: 'Create Contribution failed!' })
|
||||
.mockResolvedValue({
|
||||
data: {
|
||||
createContribution: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
mockClient.setRequestHandler(
|
||||
updateContribution,
|
||||
jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce({ message: 'Update Contribution failed!' })
|
||||
.mockResolvedValue({
|
||||
data: {
|
||||
updateContribution: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
mockClient.setRequestHandler(
|
||||
deleteContribution,
|
||||
jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce({ message: 'Delete Contribution failed!' })
|
||||
.mockResolvedValue({
|
||||
data: {
|
||||
deleteContribution: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$d: jest.fn((d) => d),
|
||||
$store: {
|
||||
dispatch: mockStoreDispach,
|
||||
state: {
|
||||
user: {
|
||||
firstName: 'Bibi',
|
||||
lastName: 'Bloxberg',
|
||||
},
|
||||
},
|
||||
},
|
||||
$i18n: {
|
||||
locale: 'en',
|
||||
},
|
||||
$router: {
|
||||
push: routerPushMock,
|
||||
},
|
||||
$route: {
|
||||
hash: '#edit',
|
||||
},
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(Community, {
|
||||
localVue,
|
||||
mocks,
|
||||
apolloProvider,
|
||||
})
|
||||
}
|
||||
|
||||
let apolloMutateSpy
|
||||
let refetchContributionsSpy
|
||||
let refetchAllContributionsSpy
|
||||
let refetchOpenCreationsSpy
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
wrapper = Wrapper()
|
||||
apolloMutateSpy = jest.spyOn(wrapper.vm.$apollo, 'mutate')
|
||||
refetchContributionsSpy = jest.spyOn(wrapper.vm.$apollo.queries.ListContributions, 'refetch')
|
||||
refetchAllContributionsSpy = jest.spyOn(
|
||||
wrapper.vm.$apollo.queries.ListAllContributions,
|
||||
'refetch',
|
||||
)
|
||||
refetchOpenCreationsSpy = jest.spyOn(wrapper.vm.$apollo.queries.OpenCreations, 'refetch')
|
||||
})
|
||||
|
||||
it('has a DIV .community-page', () => {
|
||||
expect(wrapper.find('div.community-page').exists()).toBe(true)
|
||||
describe('server response for queries is error', () => {
|
||||
it('toasts three errors', () => {
|
||||
expect(toastErrorSpy).toBeCalledTimes(3)
|
||||
expect(toastErrorSpy).toBeCalledWith('Open Creations failed!')
|
||||
expect(toastErrorSpy).toBeCalledWith('List Contributions failed!')
|
||||
expect(toastErrorSpy).toBeCalledWith('List All Contributions failed!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('tabs', () => {
|
||||
@ -105,60 +244,49 @@ describe('Community', () => {
|
||||
expect(wrapper.findAll('div[role="tabpanel"]')).toHaveLength(3)
|
||||
})
|
||||
|
||||
it.todo('check for correct tabIndex if state is "IN_PROGRESS" or not')
|
||||
it('check for correct tabIndex if state is "IN_PROGRESS" or not', () => {
|
||||
expect(routerPushMock).toBeCalledWith({ path: '/community#my' })
|
||||
})
|
||||
|
||||
it('toasts an info', () => {
|
||||
expect(toastInfoSpy).toBeCalledWith('contribution.alert.answerQuestionToast')
|
||||
})
|
||||
})
|
||||
|
||||
describe('API calls after creation', () => {
|
||||
it('has a DIV .community-page', () => {
|
||||
expect(wrapper.find('div.community-page').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('emits update transactions', () => {
|
||||
expect(wrapper.emitted('update-transactions')).toEqual([[0]])
|
||||
})
|
||||
|
||||
it('queries list of own contributions', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith({
|
||||
fetchPolicy: 'no-cache',
|
||||
query: listContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('queries list of all contributions', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith({
|
||||
fetchPolicy: 'no-cache',
|
||||
query: listAllContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('server response is error', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
apolloQueryMock.mockRejectedValue({ message: 'Ups' })
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('toasts two errors', () => {
|
||||
expect(toastErrorSpy).toBeCalledTimes(2)
|
||||
expect(toastErrorSpy).toBeCalledWith('Ups')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('set contrubtion', () => {
|
||||
describe('save contrubtion', () => {
|
||||
describe('with error', () => {
|
||||
const now = new Date().toISOString()
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({
|
||||
form: {
|
||||
id: null,
|
||||
date: now,
|
||||
memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...',
|
||||
amount: '200',
|
||||
},
|
||||
})
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('toasts the error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Create Contribution failed!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with success', () => {
|
||||
const now = new Date().toISOString()
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
apolloMutationMock.mockResolvedValue({
|
||||
data: {
|
||||
createContribution: true,
|
||||
},
|
||||
})
|
||||
await wrapper.setData({
|
||||
form: {
|
||||
id: null,
|
||||
@ -171,7 +299,7 @@ describe('Community', () => {
|
||||
})
|
||||
|
||||
it('calls the create contribution mutation', () => {
|
||||
expect(apolloMutationMock).toBeCalledWith({
|
||||
expect(apolloMutateSpy).toBeCalledWith({
|
||||
fetchPolicy: 'no-cache',
|
||||
mutation: createContribution,
|
||||
variables: {
|
||||
@ -187,62 +315,49 @@ describe('Community', () => {
|
||||
})
|
||||
|
||||
it('updates the contribution list', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith({
|
||||
fetchPolicy: 'no-cache',
|
||||
query: listContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
},
|
||||
})
|
||||
expect(refetchContributionsSpy).toBeCalled()
|
||||
})
|
||||
|
||||
it('verifies the login (to get the new creations available)', () => {
|
||||
expect(apolloRefetchMock).toBeCalled()
|
||||
it('updates the all contribution list', () => {
|
||||
expect(refetchAllContributionsSpy).toBeCalled()
|
||||
})
|
||||
|
||||
it('set all data to the default values)', () => {
|
||||
it('updates the open creations', () => {
|
||||
expect(refetchOpenCreationsSpy).toBeCalled()
|
||||
})
|
||||
|
||||
it('sets all data to the default values)', () => {
|
||||
expect(wrapper.vm.form.id).toBe(null)
|
||||
expect(wrapper.vm.form.date).toBe('')
|
||||
expect(wrapper.vm.form.memo).toBe('')
|
||||
expect(wrapper.vm.form.amount).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with error', () => {
|
||||
const now = new Date().toISOString()
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
apolloMutationMock.mockRejectedValue({
|
||||
message: 'Ouch!',
|
||||
})
|
||||
await wrapper.setData({
|
||||
form: {
|
||||
id: null,
|
||||
date: now,
|
||||
memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...',
|
||||
amount: '200',
|
||||
},
|
||||
})
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('toasts the error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Ouch!')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('update contrubtion', () => {
|
||||
describe('with error', () => {
|
||||
const now = new Date().toISOString()
|
||||
beforeEach(async () => {
|
||||
await wrapper
|
||||
.findComponent({ name: 'ContributionForm' })
|
||||
.vm.$emit('update-contribution', {
|
||||
id: 2,
|
||||
date: now,
|
||||
memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...',
|
||||
amount: '400',
|
||||
})
|
||||
})
|
||||
|
||||
it('toasts the error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Update Contribution failed!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with success', () => {
|
||||
const now = new Date().toISOString()
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
apolloMutationMock.mockResolvedValue({
|
||||
data: {
|
||||
updateContribution: true,
|
||||
},
|
||||
})
|
||||
await wrapper
|
||||
.findComponent({ name: 'ContributionForm' })
|
||||
.vm.$emit('update-contribution', {
|
||||
@ -254,7 +369,7 @@ describe('Community', () => {
|
||||
})
|
||||
|
||||
it('calls the update contribution mutation', () => {
|
||||
expect(apolloMutationMock).toBeCalledWith({
|
||||
expect(apolloMutateSpy).toBeCalledWith({
|
||||
fetchPolicy: 'no-cache',
|
||||
mutation: updateContribution,
|
||||
variables: {
|
||||
@ -271,40 +386,15 @@ describe('Community', () => {
|
||||
})
|
||||
|
||||
it('updates the contribution list', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith({
|
||||
fetchPolicy: 'no-cache',
|
||||
query: listContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
},
|
||||
})
|
||||
expect(refetchContributionsSpy).toBeCalled()
|
||||
})
|
||||
|
||||
it('verifies the login (to get the new creations available)', () => {
|
||||
expect(apolloRefetchMock).toBeCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('with error', () => {
|
||||
const now = new Date().toISOString()
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
apolloMutationMock.mockRejectedValue({
|
||||
message: 'Oh No!',
|
||||
})
|
||||
await wrapper
|
||||
.findComponent({ name: 'ContributionForm' })
|
||||
.vm.$emit('update-contribution', {
|
||||
id: 2,
|
||||
date: now,
|
||||
memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...',
|
||||
amount: '400',
|
||||
})
|
||||
it('updates the all contribution list', () => {
|
||||
expect(refetchAllContributionsSpy).toBeCalled()
|
||||
})
|
||||
|
||||
it('toasts the error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Oh No!')
|
||||
it('updates the open creations', () => {
|
||||
expect(refetchOpenCreationsSpy).toBeCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -314,22 +404,28 @@ describe('Community', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({ tabIndex: 1 })
|
||||
contributionListComponent = await wrapper.findComponent({ name: 'ContributionList' })
|
||||
contributionListComponent = wrapper.findComponent({ name: 'ContributionList' })
|
||||
})
|
||||
|
||||
describe('with error', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
contributionListComponent.vm.$emit('delete-contribution', { id: 2 })
|
||||
})
|
||||
|
||||
it('toasts the error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Delete Contribution failed!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with success', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
apolloMutationMock.mockResolvedValue({
|
||||
data: {
|
||||
deleteContribution: true,
|
||||
},
|
||||
})
|
||||
contributionListComponent.vm.$emit('delete-contribution', { id: 2 })
|
||||
await contributionListComponent.vm.$emit('delete-contribution', { id: 2 })
|
||||
})
|
||||
|
||||
it('calls the API', () => {
|
||||
expect(apolloMutationMock).toBeCalledWith({
|
||||
expect(apolloMutateSpy).toBeCalledWith({
|
||||
fetchPolicy: 'no-cache',
|
||||
mutation: deleteContribution,
|
||||
variables: {
|
||||
@ -343,37 +439,20 @@ describe('Community', () => {
|
||||
})
|
||||
|
||||
it('updates the contribution list', () => {
|
||||
expect(apolloQueryMock).toBeCalledWith({
|
||||
fetchPolicy: 'no-cache',
|
||||
query: listContributions,
|
||||
variables: {
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
},
|
||||
})
|
||||
expect(refetchContributionsSpy).toBeCalled()
|
||||
})
|
||||
|
||||
it('verifies the login (to get the new creations available)', () => {
|
||||
expect(apolloRefetchMock).toBeCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('with error', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
apolloMutationMock.mockRejectedValue({
|
||||
message: 'Oh my god!',
|
||||
})
|
||||
contributionListComponent.vm.$emit('delete-contribution', { id: 2 })
|
||||
it('updates the all contribution list', () => {
|
||||
expect(refetchAllContributionsSpy).toBeCalled()
|
||||
})
|
||||
|
||||
it('toasts the error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('Oh my god!')
|
||||
it('updates the open creations', () => {
|
||||
expect(refetchOpenCreationsSpy).toBeCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe.skip('update contribution form', () => {
|
||||
describe('update contribution form', () => {
|
||||
const now = new Date().toISOString()
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({ tabIndex: 1 })
|
||||
@ -391,12 +470,58 @@ describe('Community', () => {
|
||||
expect(wrapper.vm.form.id).toBe(2)
|
||||
expect(wrapper.vm.form.date).toBe(now)
|
||||
expect(wrapper.vm.form.memo).toBe('Mein Beitrag zur Gemeinschaft für diesen Monat ...')
|
||||
expect(wrapper.vm.form.amount).toBe('400')
|
||||
expect(wrapper.vm.form.amount).toBe('400.00')
|
||||
})
|
||||
|
||||
it('sets tab index back to 0', () => {
|
||||
expect(wrapper.vm.tabIndex).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('update list all contributions', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
await wrapper.setData({ tabIndex: 2 })
|
||||
await wrapper
|
||||
.findAllComponents({ name: 'ContributionList' })
|
||||
.at(1)
|
||||
.vm.$emit('update-list-contributions', {
|
||||
currentPage: 2,
|
||||
pageSize: 5,
|
||||
})
|
||||
})
|
||||
|
||||
it('updates page size and current page', () => {
|
||||
expect(wrapper.vm.pageSizeAll).toBe(5)
|
||||
expect(wrapper.vm.currentPageAll).toBe(2)
|
||||
})
|
||||
|
||||
it('updates the all contribution list', () => {
|
||||
expect(refetchAllContributionsSpy).toBeCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('update list contributions', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
await wrapper.setData({ tabIndex: 1 })
|
||||
await wrapper
|
||||
.findAllComponents({ name: 'ContributionList' })
|
||||
.at(0)
|
||||
.vm.$emit('update-list-contributions', {
|
||||
currentPage: 2,
|
||||
pageSize: 5,
|
||||
})
|
||||
})
|
||||
|
||||
it('updates page size and current page', () => {
|
||||
expect(wrapper.vm.pageSize).toBe(5)
|
||||
expect(wrapper.vm.currentPage).toBe(2)
|
||||
})
|
||||
|
||||
it('updates the all contribution list', () => {
|
||||
expect(refetchContributionsSpy).toBeCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
/>
|
||||
<div class="mb-3"></div>
|
||||
<contribution-form
|
||||
@set-contribution="setContribution"
|
||||
@set-contribution="saveContribution"
|
||||
@update-contribution="updateContribution"
|
||||
v-model="form"
|
||||
:isThisMonth="isThisMonth"
|
||||
@ -70,6 +70,7 @@ export default {
|
||||
itemsAll: [],
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
currentPageAll: 1,
|
||||
pageSizeAll: 25,
|
||||
contributionCount: 0,
|
||||
contributionCountAll: 0,
|
||||
@ -107,6 +108,51 @@ export default {
|
||||
this.toastError(message)
|
||||
},
|
||||
},
|
||||
ListAllContributions: {
|
||||
query() {
|
||||
return listAllContributions
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
variables() {
|
||||
return {
|
||||
currentPage: this.currentPageAll,
|
||||
pageSize: this.pageSizeAll,
|
||||
}
|
||||
},
|
||||
update({ listAllContributions }) {
|
||||
this.contributionCountAll = listAllContributions.contributionCount
|
||||
this.itemsAll = listAllContributions.contributionList
|
||||
},
|
||||
error({ message }) {
|
||||
this.toastError(message)
|
||||
},
|
||||
},
|
||||
ListContributions: {
|
||||
query() {
|
||||
return listContributions
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
variables() {
|
||||
return {
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
}
|
||||
},
|
||||
update({ listContributions }) {
|
||||
this.contributionCount = listContributions.contributionCount
|
||||
this.items = listContributions.contributionList
|
||||
if (this.items.find((item) => item.state === 'IN_PROGRESS')) {
|
||||
this.tabIndex = 1
|
||||
if (this.$route.hash !== '#my') {
|
||||
this.$router.push({ path: '/community#my' })
|
||||
}
|
||||
this.toastInfo(this.$t('contribution.alert.answerQuestionToast'))
|
||||
}
|
||||
},
|
||||
error({ message }) {
|
||||
this.toastError(message)
|
||||
},
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
@ -160,7 +206,12 @@ export default {
|
||||
this.$root.$emit('bv::toggle::collapse', value.id)
|
||||
})
|
||||
},
|
||||
setContribution(data) {
|
||||
refetchData() {
|
||||
this.$apollo.queries.ListAllContributions.refetch()
|
||||
this.$apollo.queries.ListContributions.refetch()
|
||||
this.$apollo.queries.OpenCreations.refetch()
|
||||
},
|
||||
saveContribution(data) {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
fetchPolicy: 'no-cache',
|
||||
@ -173,15 +224,7 @@ export default {
|
||||
})
|
||||
.then((result) => {
|
||||
this.toastSuccess(this.$t('contribution.submitted'))
|
||||
this.updateListContributions({
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
})
|
||||
this.updateListAllContributions({
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
})
|
||||
this.$apollo.queries.OpenCreations.refetch()
|
||||
this.refetchData()
|
||||
})
|
||||
.catch((err) => {
|
||||
this.toastError(err.message)
|
||||
@ -201,15 +244,7 @@ export default {
|
||||
})
|
||||
.then((result) => {
|
||||
this.toastSuccess(this.$t('contribution.updated'))
|
||||
this.updateListContributions({
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
})
|
||||
this.updateListAllContributions({
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
})
|
||||
this.$apollo.queries.OpenCreations.refetch()
|
||||
this.refetchData()
|
||||
})
|
||||
.catch((err) => {
|
||||
this.toastError(err.message)
|
||||
@ -226,68 +261,21 @@ export default {
|
||||
})
|
||||
.then((result) => {
|
||||
this.toastSuccess(this.$t('contribution.deleted'))
|
||||
this.updateListContributions({
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
})
|
||||
this.updateListAllContributions({
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
})
|
||||
this.$apollo.queries.OpenCreations.refetch()
|
||||
this.refetchData()
|
||||
})
|
||||
.catch((err) => {
|
||||
this.toastError(err.message)
|
||||
})
|
||||
},
|
||||
updateListAllContributions(pagination) {
|
||||
this.$apollo
|
||||
.query({
|
||||
fetchPolicy: 'no-cache',
|
||||
query: listAllContributions,
|
||||
variables: {
|
||||
currentPage: pagination.currentPage,
|
||||
pageSize: pagination.pageSize,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
const {
|
||||
data: { listAllContributions },
|
||||
} = result
|
||||
this.contributionCountAll = listAllContributions.contributionCount
|
||||
this.itemsAll = listAllContributions.contributionList
|
||||
})
|
||||
.catch((err) => {
|
||||
this.toastError(err.message)
|
||||
})
|
||||
this.currentPageAll = pagination.currentPage
|
||||
this.pageSizeAll = pagination.pageSize
|
||||
this.$apollo.queries.ListAllContributions.refetch()
|
||||
},
|
||||
updateListContributions(pagination) {
|
||||
this.$apollo
|
||||
.query({
|
||||
fetchPolicy: 'no-cache',
|
||||
query: listContributions,
|
||||
variables: {
|
||||
currentPage: pagination.currentPage,
|
||||
pageSize: pagination.pageSize,
|
||||
},
|
||||
})
|
||||
.then((result) => {
|
||||
const {
|
||||
data: { listContributions },
|
||||
} = result
|
||||
this.contributionCount = listContributions.contributionCount
|
||||
this.items = listContributions.contributionList
|
||||
if (this.items.find((item) => item.state === 'IN_PROGRESS')) {
|
||||
this.tabIndex = 1
|
||||
if (this.$route.hash !== '#my') {
|
||||
this.$router.push({ path: '/community#my' })
|
||||
}
|
||||
this.toastInfo('Du hast eine Rückfrage auf eine Contribution. Bitte beantworte diese!')
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
this.toastError(err.message)
|
||||
})
|
||||
this.currentPage = pagination.currentPage
|
||||
this.pageSize = pagination.pageSize
|
||||
this.$apollo.queries.ListContributions.refetch()
|
||||
},
|
||||
updateContributionForm(item) {
|
||||
this.form.id = item.id
|
||||
@ -306,16 +294,7 @@ export default {
|
||||
this.items.find((item) => item.id === id).state = 'PENDING'
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.updateListContributions({
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
})
|
||||
this.updateListAllContributions({
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
})
|
||||
this.updateTransactions(0)
|
||||
this.tabIndex = 1
|
||||
this.$router.push({ path: '/community#my' })
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { mount, RouterLinkStub } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import flushPromises from 'flush-promises'
|
||||
import { toastErrorSpy } from '@test/testSetup'
|
||||
import ForgotPassword from './ForgotPassword'
|
||||
@ -7,43 +7,28 @@ const mockAPIcall = jest.fn()
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const mockRouterPush = jest.fn()
|
||||
|
||||
const stubs = {
|
||||
RouterLink: RouterLinkStub,
|
||||
}
|
||||
|
||||
const createMockObject = (comingFrom) => {
|
||||
return {
|
||||
localVue,
|
||||
mocks: {
|
||||
$t: jest.fn((t) => t),
|
||||
$router: {
|
||||
push: mockRouterPush,
|
||||
},
|
||||
$apollo: {
|
||||
mutate: mockAPIcall,
|
||||
},
|
||||
$route: {
|
||||
params: {
|
||||
comingFrom,
|
||||
},
|
||||
},
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$apollo: {
|
||||
mutate: mockAPIcall,
|
||||
},
|
||||
$route: {
|
||||
params: {
|
||||
comingFrom: '',
|
||||
},
|
||||
stubs,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
describe('ForgotPassword', () => {
|
||||
let wrapper
|
||||
|
||||
const Wrapper = (functionN) => {
|
||||
return mount(ForgotPassword, functionN)
|
||||
const Wrapper = () => {
|
||||
return mount(ForgotPassword, { localVue, mocks })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper(createMockObject())
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders the component', () => {
|
||||
@ -110,12 +95,6 @@ describe('ForgotPassword', () => {
|
||||
expect(wrapper.find('.test-message-button').attributes('href')).toBe('/login')
|
||||
})
|
||||
|
||||
it.skip('click redirects to "/login"', async () => {
|
||||
// wrapper.find('.test-message-button').trigger('click')
|
||||
// await wrapper.vm.$nextTick()
|
||||
expect(mockRouterPush).toBeCalledWith('/login')
|
||||
})
|
||||
|
||||
it('toasts a standard error message', () => {
|
||||
expect(toastErrorSpy).toBeCalledWith('error.email-already-sent')
|
||||
})
|
||||
@ -144,13 +123,20 @@ describe('ForgotPassword', () => {
|
||||
it('button link redirects to "/login"', () => {
|
||||
expect(wrapper.find('.test-message-button').attributes('href')).toBe('/login')
|
||||
})
|
||||
|
||||
it.skip('click redirects to "/login"', () => {
|
||||
// expect(mockRouterPush).toBeCalledWith('/login')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('route has coming from ', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$route.params.comingFrom = 'coming from'
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('changes subtitle', () => {
|
||||
expect(wrapper.vm.subtitle).toBe('settings.password.resend_subtitle')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -254,7 +254,7 @@ describe('Send', () => {
|
||||
describe('copy link with success', () => {
|
||||
beforeEach(async () => {
|
||||
navigatorClipboardMock.mockResolvedValue()
|
||||
await wrapper.find('.pointer').trigger('click')
|
||||
await wrapper.find('div[data-test="copyLink"]').trigger('click')
|
||||
})
|
||||
|
||||
it('should call clipboard.writeText', () => {
|
||||
@ -270,7 +270,7 @@ describe('Send', () => {
|
||||
describe('copy link with error', () => {
|
||||
beforeEach(async () => {
|
||||
navigatorClipboardMock.mockRejectedValue()
|
||||
await wrapper.find('.clipboard-copy').find('.btn-secondary').trigger('click')
|
||||
await wrapper.find('div[data-test="copyLink"]').trigger('click')
|
||||
})
|
||||
|
||||
it('toasts error message', () => {
|
||||
@ -292,7 +292,7 @@ describe('Send', () => {
|
||||
describe('copy link and text with success', () => {
|
||||
beforeEach(async () => {
|
||||
navigatorClipboardMock.mockResolvedValue()
|
||||
await wrapper.findAll('button').at(0).trigger('click')
|
||||
await wrapper.find('div[data-test="copyLinkWithText"]').trigger('click')
|
||||
})
|
||||
|
||||
it('should call clipboard.writeText', () => {
|
||||
@ -312,7 +312,7 @@ describe('Send', () => {
|
||||
describe('copy link and text with error', () => {
|
||||
beforeEach(async () => {
|
||||
navigatorClipboardMock.mockRejectedValue()
|
||||
await wrapper.findAll('button').at(0).trigger('click')
|
||||
await wrapper.find('div[data-test="copyLinkWithText"]').trigger('click')
|
||||
})
|
||||
|
||||
it('toasts error message', () => {
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
:transactionLinkCount="transactionLinkCount"
|
||||
:transactions="transactions"
|
||||
:showPagination="true"
|
||||
:pageSize="pageSize"
|
||||
@update-transactions="updateTransactions"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
@ -90,6 +91,11 @@ export default {
|
||||
this.updateGdt()
|
||||
}
|
||||
},
|
||||
gdt() {
|
||||
if (this.gdt) {
|
||||
this.updateGdt()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -22,6 +22,7 @@ import { loadFilters } from '@/filters/amount'
|
||||
import { toasters } from '@/mixins/toaster'
|
||||
export const toastErrorSpy = jest.spyOn(toasters.methods, 'toastError')
|
||||
export const toastSuccessSpy = jest.spyOn(toasters.methods, 'toastSuccess')
|
||||
export const toastInfoSpy = jest.spyOn(toasters.methods, 'toastInfo')
|
||||
|
||||
Object.keys(rules).forEach((rule) => {
|
||||
extend(rule, {
|
||||
@ -70,6 +71,6 @@ console.warn = (m) => {
|
||||
}
|
||||
|
||||
// throw errors for vue warnings to force the programmers to take care about warnings
|
||||
Vue.config.warnHandler = (w) => {
|
||||
Vue.config.warnHandler = async (w) => {
|
||||
throw new Error(w)
|
||||
}
|
||||
|
||||
@ -2,6 +2,25 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@apollo/client@^3.7.4":
|
||||
version "3.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.7.4.tgz#28c3fb7f89365ccaf185bc8b51860041f37629b3"
|
||||
integrity sha512-bgiCKRmLSBImX4JRrw8NjqGo0AQE/mowCdHX1PJp2r5zIXrJx0UeaAYmx1qJY69Oz/KR7SKlLt4xK+bOP1jx7A==
|
||||
dependencies:
|
||||
"@graphql-typed-document-node/core" "^3.1.1"
|
||||
"@wry/context" "^0.7.0"
|
||||
"@wry/equality" "^0.5.0"
|
||||
"@wry/trie" "^0.3.0"
|
||||
graphql-tag "^2.12.6"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
optimism "^0.16.1"
|
||||
prop-types "^15.7.2"
|
||||
response-iterator "^0.2.6"
|
||||
symbol-observable "^4.0.0"
|
||||
ts-invariant "^0.10.3"
|
||||
tslib "^2.3.0"
|
||||
zen-observable-ts "^1.2.5"
|
||||
|
||||
"@babel/code-frame@7.12.11":
|
||||
version "7.12.11"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
|
||||
@ -1904,6 +1923,11 @@
|
||||
minimatch "^3.0.4"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@graphql-typed-document-node/core@^3.1.1":
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.1.tgz#076d78ce99822258cf813ecc1e7fa460fa74d052"
|
||||
integrity sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==
|
||||
|
||||
"@hapi/address@2.x.x":
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
|
||||
@ -3159,6 +3183,13 @@
|
||||
"@types/node" ">=6"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@wry/context@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.7.0.tgz#be88e22c0ddf62aeb0ae9f95c3d90932c619a5c8"
|
||||
integrity sha512-LcDAiYWRtwAoSOArfk7cuYvFXytxfVrdX7yxoUmK7pPITLk5jYh2F8knCwS7LjgYL8u1eidPlKKV6Ikqq0ODqQ==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@wry/equality@^0.1.2":
|
||||
version "0.1.11"
|
||||
resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.11.tgz#35cb156e4a96695aa81a9ecc4d03787bc17f1790"
|
||||
@ -3166,6 +3197,20 @@
|
||||
dependencies:
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@wry/equality@^0.5.0":
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.5.3.tgz#fafebc69561aa2d40340da89fa7dc4b1f6fb7831"
|
||||
integrity sha512-avR+UXdSrsF2v8vIqIgmeTY0UR91UT+IyablCyKe/uk22uOJ8fusKZnH9JH9e1/EtLeNJBtagNmL3eJdnOV53g==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@wry/trie@^0.3.0":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@wry/trie/-/trie-0.3.2.tgz#a06f235dc184bd26396ba456711f69f8c35097e6"
|
||||
integrity sha512-yRTyhWSls2OY/pYLfwff867r8ekooZ4UI+/gxot5Wj8EFwSf2rG+n+Mo/6LoLQm1TKA4GRj2+LCpbfS937dClQ==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@xtuc/ieee754@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
|
||||
@ -7453,6 +7498,13 @@ graceful-fs@^4.2.4:
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
|
||||
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
|
||||
|
||||
graphql-tag@^2.12.6:
|
||||
version "2.12.6"
|
||||
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1"
|
||||
integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
graphql-tag@^2.4.2:
|
||||
version "2.12.5"
|
||||
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.5.tgz#5cff974a67b417747d05c8d9f5f3cb4495d0db8f"
|
||||
@ -7636,6 +7688,13 @@ hmac-drbg@^1.0.1:
|
||||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hoist-non-react-statics@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
dependencies:
|
||||
react-is "^16.7.0"
|
||||
|
||||
homedir-polyfill@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
|
||||
@ -9915,7 +9974,7 @@ loglevel@^1.6.8:
|
||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
|
||||
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.2.0:
|
||||
loose-envify@^1.0.0, loose-envify@^1.2.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
@ -10260,6 +10319,11 @@ mkdirp@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
mock-apollo-client@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/mock-apollo-client/-/mock-apollo-client-1.2.1.tgz#e3bfdc3ff73b1fea28fa7e91ec82e43ba8cbfa39"
|
||||
integrity sha512-QYQ6Hxo+t7hard1bcHHbsHxlNQYTQsaMNsm2Psh/NbwLMi2R4tGzplJKt97MUWuARHMq3GHB4PTLj/gxej4Caw==
|
||||
|
||||
moment@^2.19.2:
|
||||
version "2.29.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||
@ -10767,6 +10831,14 @@ optimism@^0.10.0:
|
||||
dependencies:
|
||||
"@wry/context" "^0.4.0"
|
||||
|
||||
optimism@^0.16.1:
|
||||
version "0.16.2"
|
||||
resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.16.2.tgz#519b0c78b3b30954baed0defe5143de7776bf081"
|
||||
integrity sha512-zWNbgWj+3vLEjZNIh/okkY2EUfX+vB9TJopzIZwT1xxaMqC5hRLLraePod4c5n4He08xuXNH+zhKFFCu390wiQ==
|
||||
dependencies:
|
||||
"@wry/context" "^0.7.0"
|
||||
"@wry/trie" "^0.3.0"
|
||||
|
||||
optionator@^0.8.1, optionator@^0.8.2:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
|
||||
@ -11721,6 +11793,15 @@ prompts@^2.0.1:
|
||||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
prop-types@^15.7.2:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
dependencies:
|
||||
loose-envify "^1.4.0"
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.13.1"
|
||||
|
||||
proto-list@~1.2.1:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
|
||||
@ -11900,7 +11981,7 @@ raw-body@2.4.0:
|
||||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
react-is@^16.8.4:
|
||||
react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.4:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
@ -12269,6 +12350,11 @@ resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.1
|
||||
is-core-module "^2.2.0"
|
||||
path-parse "^1.0.6"
|
||||
|
||||
response-iterator@^0.2.6:
|
||||
version "0.2.6"
|
||||
resolved "https://registry.yarnpkg.com/response-iterator/-/response-iterator-0.2.6.tgz#249005fb14d2e4eeb478a3f735a28fd8b4c9f3da"
|
||||
integrity sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw==
|
||||
|
||||
restore-cursor@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
|
||||
@ -13406,6 +13492,11 @@ symbol-observable@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
|
||||
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
|
||||
|
||||
symbol-observable@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205"
|
||||
integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==
|
||||
|
||||
symbol-tree@^3.2.2, symbol-tree@^3.2.4:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
|
||||
@ -13704,6 +13795,13 @@ tryer@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
|
||||
integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
|
||||
|
||||
ts-invariant@^0.10.3:
|
||||
version "0.10.3"
|
||||
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.10.3.tgz#3e048ff96e91459ffca01304dbc7f61c1f642f6c"
|
||||
integrity sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
ts-invariant@^0.4.0:
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.4.4.tgz#97a523518688f93aafad01b0e80eb803eb2abd86"
|
||||
@ -13757,6 +13855,11 @@ tslib@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
|
||||
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
|
||||
|
||||
tslib@^2.3.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
|
||||
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
|
||||
|
||||
tsutils@^3.17.1:
|
||||
version "3.21.0"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
|
||||
@ -14919,7 +15022,14 @@ zen-observable-ts@^0.8.21:
|
||||
tslib "^1.9.3"
|
||||
zen-observable "^0.8.0"
|
||||
|
||||
zen-observable@^0.8.0:
|
||||
zen-observable-ts@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz#6c6d9ea3d3a842812c6e9519209365a122ba8b58"
|
||||
integrity sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==
|
||||
dependencies:
|
||||
zen-observable "0.8.15"
|
||||
|
||||
zen-observable@0.8.15, zen-observable@^0.8.0:
|
||||
version "0.8.15"
|
||||
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
|
||||
integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido",
|
||||
"version": "1.16.0",
|
||||
"version": "1.17.1",
|
||||
"description": "Gradido",
|
||||
"main": "index.js",
|
||||
"repository": "git@github.com:gradido/gradido.git",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user