Merge branch 'master' into 2803-open-contribution-edit-button

This commit is contained in:
Moriz Wahl 2023-03-23 13:17:02 +01:00
commit 2ac94e1b47
54 changed files with 1155 additions and 653 deletions

View File

@ -41,3 +41,6 @@ federation: &federation
frontend: &frontend frontend: &frontend
- 'frontend/**/*' - 'frontend/**/*'
nginx: &nginx
- 'nginx/**/*'

32
.github/workflows/test-nginx.yml vendored Normal file
View File

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

View File

@ -81,31 +81,6 @@ jobs:
name: docker-mariadb-test name: docker-mariadb-test
path: /tmp/mariadb.tar path: /tmp/mariadb.tar
##############################################################################
# JOB: DOCKER BUILD TEST NGINX ###############################################
##############################################################################
build_test_nginx:
name: Docker Build Test - Nginx
runs-on: ubuntu-latest
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v3
##########################################################################
# BUILD NGINX DOCKER IMAGE ###############################################
##########################################################################
- name: nginx | Build `test` image
run: |
docker build -t "gradido/nginx:test" nginx/
docker save "gradido/nginx:test" > /tmp/nginx.tar
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: docker-nginx-test
path: /tmp/nginx.tar
############################################################################## ##############################################################################
# JOB: LINT BACKEND ########################################################## # JOB: LINT BACKEND ##########################################################
############################################################################## ##############################################################################

View File

@ -76,7 +76,11 @@ git clone git@github.com:gradido/gradido.git
git submodule update --recursive --init git submodule update --recursive --init
``` ```
### 2. Run docker-compose ### 2. Install modules
You can go in each under folder (admin, frontend, database, backend, ...) and call ``yarn`` in each folder or you can call ``yarn installAll``.
### 3. Run docker-compose
Run docker-compose to bring up the development environment Run docker-compose to bring up the development environment

View File

@ -1,43 +1,75 @@
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import CreationTransactionList from './CreationTransactionList' import CreationTransactionList from './CreationTransactionList'
import { toastErrorSpy } from '../../test/testSetup' import { toastErrorSpy } from '../../test/testSetup'
import VueApollo from 'vue-apollo'
import { createMockClient } from 'mock-apollo-client'
import { adminListContributions } from '../graphql/adminListContributions'
const mockClient = createMockClient()
const apolloProvider = new VueApollo({
defaultClient: mockClient,
})
const localVue = global.localVue const localVue = global.localVue
localVue.use(VueApollo)
const apolloQueryMock = jest.fn().mockResolvedValue({ const defaultData = () => {
data: { return {
creationTransactionList: { adminListContributions: {
contributionCount: 2, contributionCount: 2,
contributionList: [ contributionList: [
{ {
id: 1, id: 1,
amount: 5.8, firstName: 'Bibi',
createdAt: '2022-09-21T11:09:51.000Z', lastName: 'Bloxberg',
confirmedAt: null, userId: 99,
contributionDate: '2022-08-01T00:00:00.000Z', email: 'bibi@bloxberg.de',
memo: 'für deine Hilfe, Fräulein Rottenmeier', amount: 500,
memo: 'Danke für alles',
date: new Date(),
moderator: 1,
state: 'PENDING', state: 'PENDING',
creation: [500, 500, 500],
messagesCount: 0,
deniedBy: null,
deniedAt: null,
confirmedBy: null,
confirmedAt: null,
contributionDate: new Date(),
deletedBy: null,
deletedAt: null,
createdAt: new Date(),
}, },
{ {
id: 2, id: 2,
amount: '47', firstName: 'Räuber',
createdAt: '2022-09-21T11:09:28.000Z', lastName: 'Hotzenplotz',
confirmedAt: '2022-09-21T11:09:28.000Z', userId: 100,
contributionDate: '2022-08-01T00:00:00.000Z', email: 'raeuber@hotzenplotz.de',
memo: 'für deine Hilfe, Frau Holle', amount: 1000000,
state: 'CONFIRMED', memo: 'Gut Ergattert',
date: new Date(),
moderator: 1,
state: 'PENDING',
creation: [500, 500, 500],
messagesCount: 0,
deniedBy: null,
deniedAt: null,
confirmedBy: null,
confirmedAt: new Date(),
contributionDate: new Date(),
deletedBy: null,
deletedAt: null,
createdAt: new Date(),
}, },
], ],
}, },
}, }
}) }
const mocks = { const mocks = {
$d: jest.fn((t) => t), $d: jest.fn((t) => t),
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$apollo: {
query: apolloQueryMock,
},
} }
const propsData = { const propsData = {
@ -48,47 +80,44 @@ const propsData = {
describe('CreationTransactionList', () => { describe('CreationTransactionList', () => {
let wrapper let wrapper
const adminListContributionsMock = jest.fn()
mockClient.setRequestHandler(
adminListContributions,
adminListContributionsMock
.mockRejectedValueOnce({ message: 'Ouch!' })
.mockResolvedValue({ data: defaultData() }),
)
const Wrapper = () => { const Wrapper = () => {
return mount(CreationTransactionList, { localVue, mocks, propsData }) return mount(CreationTransactionList, { localVue, mocks, propsData, apolloProvider })
} }
describe('mount', () => { describe('mount', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks()
wrapper = Wrapper() wrapper = Wrapper()
}) })
describe('server error', () => {
it('toast error', () => {
expect(toastErrorSpy).toBeCalledWith('Ouch!')
})
})
describe('sever success', () => {
it('sends query to Apollo when created', () => { it('sends query to Apollo when created', () => {
expect(apolloQueryMock).toBeCalledWith( expect(adminListContributionsMock).toBeCalledWith({
expect.objectContaining({
variables: {
currentPage: 1, currentPage: 1,
pageSize: 10, pageSize: 10,
order: 'DESC', order: 'DESC',
userId: 1, userId: 1,
}, })
}),
)
}) })
it('has two values for the transaction', () => { it('has two values for the transaction', () => {
expect(wrapper.find('tbody').findAll('tr').length).toBe(2) expect(wrapper.find('tbody').findAll('tr').length).toBe(2)
}) })
describe('query transaction with error', () => {
beforeEach(() => {
apolloQueryMock.mockRejectedValue({ message: 'OUCH!' })
wrapper = Wrapper()
})
it('calls the API', () => {
expect(apolloQueryMock).toBeCalled()
})
it('toast error', () => {
expect(toastErrorSpy).toBeCalledWith('OUCH!')
})
})
describe('watch currentPage', () => { describe('watch currentPage', () => {
beforeEach(async () => { beforeEach(async () => {
jest.clearAllMocks() jest.clearAllMocks()
@ -101,3 +130,4 @@ describe('CreationTransactionList', () => {
}) })
}) })
}) })
})

View File

@ -42,7 +42,7 @@
</div> </div>
</template> </template>
<script> <script>
import { creationTransactionList } from '../graphql/creationTransactionList' import { adminListContributions } from '../graphql/adminListContributions'
export default { export default {
name: 'CreationTransactionList', name: 'CreationTransactionList',
props: { props: {
@ -92,33 +92,26 @@ export default {
], ],
} }
}, },
methods: { apollo: {
getTransactions() { AdminListContributions: {
this.$apollo query() {
.query({ return adminListContributions
query: creationTransactionList, },
variables: { variables() {
return {
currentPage: this.currentPage, currentPage: this.currentPage,
pageSize: this.perPage, pageSize: this.perPage,
order: 'DESC', order: 'DESC',
userId: parseInt(this.userId), userId: parseInt(this.userId),
}
}, },
}) update({ adminListContributions }) {
.then((result) => { this.rows = adminListContributions.contributionCount
this.rows = result.data.creationTransactionList.contributionCount this.items = adminListContributions.contributionList
this.items = result.data.creationTransactionList.contributionList
})
.catch((error) => {
this.toastError(error.message)
})
}, },
error({ message }) {
this.toastError(message)
}, },
created() {
this.getTransactions()
},
watch: {
currentPage() {
this.getTransactions()
}, },
}, },
} }

View File

@ -1,17 +1,19 @@
import gql from 'graphql-tag' import gql from 'graphql-tag'
export const adminListAllContributions = gql` export const adminListContributions = gql`
query ( query (
$currentPage: Int = 1 $currentPage: Int = 1
$pageSize: Int = 25 $pageSize: Int = 25
$order: Order = DESC $order: Order = DESC
$statusFilter: [ContributionStatus!] $statusFilter: [ContributionStatus!]
$userId: Int
) { ) {
adminListAllContributions( adminListContributions(
currentPage: $currentPage currentPage: $currentPage
pageSize: $pageSize pageSize: $pageSize
order: $order order: $order
statusFilter: $statusFilter statusFilter: $statusFilter
userId: $userId
) { ) {
contributionCount contributionCount
contributionList { contributionList {

View File

@ -1,23 +0,0 @@
import gql from 'graphql-tag'
export const creationTransactionList = gql`
query ($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC, $userId: Int!) {
creationTransactionList(
currentPage: $currentPage
pageSize: $pageSize
order: $order
userId: $userId
) {
contributionCount
contributionList {
id
amount
createdAt
confirmedAt
contributionDate
memo
state
}
}
}
`

View File

@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'
import CreationConfirm from './CreationConfirm' import CreationConfirm from './CreationConfirm'
import { adminDeleteContribution } from '../graphql/adminDeleteContribution' import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
import { denyContribution } from '../graphql/denyContribution' import { denyContribution } from '../graphql/denyContribution'
import { adminListAllContributions } from '../graphql/adminListAllContributions' import { adminListContributions } from '../graphql/adminListContributions'
import { confirmContribution } from '../graphql/confirmContribution' import { confirmContribution } from '../graphql/confirmContribution'
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup' import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
import VueApollo from 'vue-apollo' import VueApollo from 'vue-apollo'
@ -38,7 +38,7 @@ const mocks = {
const defaultData = () => { const defaultData = () => {
return { return {
adminListAllContributions: { adminListContributions: {
contributionCount: 2, contributionCount: 2,
contributionList: [ contributionList: [
{ {
@ -92,14 +92,14 @@ const defaultData = () => {
describe('CreationConfirm', () => { describe('CreationConfirm', () => {
let wrapper let wrapper
const adminListAllContributionsMock = jest.fn() const adminListContributionsMock = jest.fn()
const adminDeleteContributionMock = jest.fn() const adminDeleteContributionMock = jest.fn()
const adminDenyContributionMock = jest.fn() const adminDenyContributionMock = jest.fn()
const confirmContributionMock = jest.fn() const confirmContributionMock = jest.fn()
mockClient.setRequestHandler( mockClient.setRequestHandler(
adminListAllContributions, adminListContributions,
adminListAllContributionsMock adminListContributionsMock
.mockRejectedValueOnce({ message: 'Ouch!' }) .mockRejectedValueOnce({ message: 'Ouch!' })
.mockResolvedValue({ data: defaultData() }), .mockResolvedValue({ data: defaultData() }),
) )
@ -337,7 +337,7 @@ describe('CreationConfirm', () => {
}) })
it('refetches contributions with proper filter', () => { it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({ expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1, currentPage: 1,
order: 'DESC', order: 'DESC',
pageSize: 25, pageSize: 25,
@ -352,7 +352,7 @@ describe('CreationConfirm', () => {
}) })
it('refetches contributions with proper filter', () => { it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({ expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1, currentPage: 1,
order: 'DESC', order: 'DESC',
pageSize: 25, pageSize: 25,
@ -368,7 +368,7 @@ describe('CreationConfirm', () => {
}) })
it('refetches contributions with proper filter', () => { it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({ expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1, currentPage: 1,
order: 'DESC', order: 'DESC',
pageSize: 25, pageSize: 25,
@ -384,7 +384,7 @@ describe('CreationConfirm', () => {
}) })
it('refetches contributions with proper filter', () => { it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({ expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1, currentPage: 1,
order: 'DESC', order: 'DESC',
pageSize: 25, pageSize: 25,
@ -400,7 +400,7 @@ describe('CreationConfirm', () => {
}) })
it('refetches contributions with proper filter', () => { it('refetches contributions with proper filter', () => {
expect(adminListAllContributionsMock).toBeCalledWith({ expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1, currentPage: 1,
order: 'DESC', order: 'DESC',
pageSize: 25, pageSize: 25,

View File

@ -85,7 +85,7 @@
<script> <script>
import Overlay from '../components/Overlay' import Overlay from '../components/Overlay'
import OpenCreationsTable from '../components/Tables/OpenCreationsTable' import OpenCreationsTable from '../components/Tables/OpenCreationsTable'
import { adminListAllContributions } from '../graphql/adminListAllContributions' import { adminListContributions } from '../graphql/adminListContributions'
import { adminDeleteContribution } from '../graphql/adminDeleteContribution' import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
import { confirmContribution } from '../graphql/confirmContribution' import { confirmContribution } from '../graphql/confirmContribution'
import { denyContribution } from '../graphql/denyContribution' import { denyContribution } from '../graphql/denyContribution'
@ -397,7 +397,7 @@ export default {
apollo: { apollo: {
ListAllContributions: { ListAllContributions: {
query() { query() {
return adminListAllContributions return adminListContributions
}, },
variables() { variables() {
return { return {
@ -407,11 +407,11 @@ export default {
} }
}, },
fetchPolicy: 'no-cache', fetchPolicy: 'no-cache',
update({ adminListAllContributions }) { update({ adminListContributions }) {
this.rows = adminListAllContributions.contributionCount this.rows = adminListContributions.contributionCount
this.items = adminListAllContributions.contributionList this.items = adminListContributions.contributionList
if (this.statusFilter === FILTER_TAB_MAP[0]) { if (this.statusFilter === FILTER_TAB_MAP[0]) {
this.$store.commit('setOpenCreations', adminListAllContributions.contributionCount) this.$store.commit('setOpenCreations', adminListContributions.contributionCount)
} }
}, },
error({ message }) { error({ message }) {

View File

@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import Overview from './Overview' import Overview from './Overview'
import { adminListAllContributions } from '../graphql/adminListAllContributions' import { adminListContributions } from '../graphql/adminListContributions'
import VueApollo from 'vue-apollo' import VueApollo from 'vue-apollo'
import { createMockClient } from 'mock-apollo-client' import { createMockClient } from 'mock-apollo-client'
import { toastErrorSpy } from '../../test/testSetup' import { toastErrorSpy } from '../../test/testSetup'
@ -30,7 +30,7 @@ const mocks = {
const defaultData = () => { const defaultData = () => {
return { return {
adminListAllContributions: { adminListContributions: {
contributionCount: 2, contributionCount: 2,
contributionList: [ contributionList: [
{ {
@ -84,11 +84,11 @@ const defaultData = () => {
describe('Overview', () => { describe('Overview', () => {
let wrapper let wrapper
const adminListAllContributionsMock = jest.fn() const adminListContributionsMock = jest.fn()
mockClient.setRequestHandler( mockClient.setRequestHandler(
adminListAllContributions, adminListContributions,
adminListAllContributionsMock adminListContributionsMock
.mockRejectedValueOnce({ message: 'Ouch!' }) .mockRejectedValueOnce({ message: 'Ouch!' })
.mockResolvedValue({ data: defaultData() }), .mockResolvedValue({ data: defaultData() }),
) )
@ -109,8 +109,8 @@ describe('Overview', () => {
}) })
}) })
it('calls the adminListAllContributions query', () => { it('calls the adminListContributions query', () => {
expect(adminListAllContributionsMock).toBeCalledWith({ expect(adminListContributionsMock).toBeCalledWith({
currentPage: 1, currentPage: 1,
order: 'DESC', order: 'DESC',
pageSize: 25, pageSize: 25,

View File

@ -31,7 +31,7 @@
</div> </div>
</template> </template>
<script> <script>
import { adminListAllContributions } from '../graphql/adminListAllContributions' import { adminListContributions } from '../graphql/adminListContributions'
export default { export default {
name: 'overview', name: 'overview',
@ -43,7 +43,7 @@ export default {
apollo: { apollo: {
AllContributions: { AllContributions: {
query() { query() {
return adminListAllContributions return adminListContributions
}, },
variables() { variables() {
// may be at some point we need a pagination here // may be at some point we need a pagination here
@ -51,8 +51,8 @@ export default {
statusFilter: this.statusFilter, statusFilter: this.statusFilter,
} }
}, },
update({ adminListAllContributions }) { update({ adminListContributions }) {
this.$store.commit('setOpenCreations', adminListAllContributions.contributionCount) this.$store.commit('setOpenCreations', adminListContributions.contributionCount)
}, },
error({ message }) { error({ message }) {
this.toastError(message) this.toastError(message)

View File

@ -6,7 +6,7 @@ module.exports = {
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
coverageThreshold: { coverageThreshold: {
global: { global: {
lines: 80, lines: 81,
}, },
}, },
setupFiles: ['<rootDir>/test/testSetup.ts'], setupFiles: ['<rootDir>/test/testSetup.ts'],

View File

@ -2,7 +2,6 @@ import { RIGHTS } from './RIGHTS'
export const INALIENABLE_RIGHTS = [ export const INALIENABLE_RIGHTS = [
RIGHTS.LOGIN, RIGHTS.LOGIN,
RIGHTS.GET_COMMUNITY_INFO,
RIGHTS.COMMUNITIES, RIGHTS.COMMUNITIES,
RIGHTS.CREATE_USER, RIGHTS.CREATE_USER,
RIGHTS.SEND_RESET_PASSWORD_EMAIL, RIGHTS.SEND_RESET_PASSWORD_EMAIL,

View File

@ -2,7 +2,6 @@ export enum RIGHTS {
LOGIN = 'LOGIN', LOGIN = 'LOGIN',
VERIFY_LOGIN = 'VERIFY_LOGIN', VERIFY_LOGIN = 'VERIFY_LOGIN',
BALANCE = 'BALANCE', BALANCE = 'BALANCE',
GET_COMMUNITY_INFO = 'GET_COMMUNITY_INFO',
COMMUNITIES = 'COMMUNITIES', COMMUNITIES = 'COMMUNITIES',
LIST_GDT_ENTRIES = 'LIST_GDT_ENTRIES', LIST_GDT_ENTRIES = 'LIST_GDT_ENTRIES',
EXIST_PID = 'EXIST_PID', EXIST_PID = 'EXIST_PID',
@ -44,10 +43,9 @@ export enum RIGHTS {
ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION', ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION',
ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION', ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION',
ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION', ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION',
LIST_UNCONFIRMED_CONTRIBUTIONS = 'LIST_UNCONFIRMED_CONTRIBUTIONS', ADMIN_LIST_CONTRIBUTIONS = 'ADMIN_LIST_CONTRIBUTIONS',
CONFIRM_CONTRIBUTION = 'CONFIRM_CONTRIBUTION', CONFIRM_CONTRIBUTION = 'CONFIRM_CONTRIBUTION',
SEND_ACTIVATION_EMAIL = 'SEND_ACTIVATION_EMAIL', SEND_ACTIVATION_EMAIL = 'SEND_ACTIVATION_EMAIL',
CREATION_TRANSACTION_LIST = 'CREATION_TRANSACTION_LIST',
LIST_TRANSACTION_LINKS_ADMIN = 'LIST_TRANSACTION_LINKS_ADMIN', LIST_TRANSACTION_LINKS_ADMIN = 'LIST_TRANSACTION_LINKS_ADMIN',
CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK', CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK',
DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK', DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK',

View File

@ -10,7 +10,7 @@ Decimal.set({
}) })
const constants = { const constants = {
DB_VERSION: '0061-event_refactoring', DB_VERSION: '0062-event_contribution_confirm',
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
LOG4JS_CONFIG: 'log4js-config.json', LOG4JS_CONFIG: 'log4js-config.json',
// default log level on production should be info // default log level on production should be info

View File

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

View File

@ -0,0 +1,22 @@
import Decimal from 'decimal.js-light'
import { User as DbUser } from '@entity/User'
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_CONFIRM = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_CONFIRM,
user,
moderator,
null,
null,
contribution,
null,
amount,
).save()

View File

@ -0,0 +1,22 @@
import Decimal from 'decimal.js-light'
import { User as DbUser } from '@entity/User'
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_CREATE = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_CREATE,
user,
moderator,
null,
null,
contribution,
null,
amount,
).save()

View File

@ -0,0 +1,22 @@
import Decimal from 'decimal.js-light'
import { User as DbUser } from '@entity/User'
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_DELETE = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_DELETE,
user,
moderator,
null,
null,
contribution,
null,
amount,
).save()

View File

@ -0,0 +1,22 @@
import Decimal from 'decimal.js-light'
import { User as DbUser } from '@entity/User'
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_DENY = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_DENY,
user,
moderator,
null,
null,
contribution,
null,
amount,
).save()

View File

@ -0,0 +1,22 @@
import Decimal from 'decimal.js-light'
import { User as DbUser } from '@entity/User'
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_UPDATE = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_UPDATE,
user,
moderator,
null,
null,
contribution,
null,
amount,
).save()

View File

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

View File

@ -0,0 +1,12 @@
import Decimal from 'decimal.js-light'
import { User as DbUser } from '@entity/User'
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { Event, EventType } from './Event'
export const EVENT_CONTRIBUTION_CREATE = async (
user: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(EventType.CONTRIBUTION_CREATE, user, user, null, null, contribution, null, amount).save()

View File

@ -0,0 +1,12 @@
import Decimal from 'decimal.js-light'
import { User as DbUser } from '@entity/User'
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { Event, EventType } from './Event'
export const EVENT_CONTRIBUTION_DELETE = async (
user: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(EventType.CONTRIBUTION_DELETE, user, user, null, null, contribution, null, amount).save()

View File

@ -0,0 +1,12 @@
import Decimal from 'decimal.js-light'
import { User as DbUser } from '@entity/User'
import { Contribution as DbContribution } from '@entity/Contribution'
import { Event as DbEvent } from '@entity/Event'
import { Event, EventType } from './Event'
export const EVENT_CONTRIBUTION_UPDATE = async (
user: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(EventType.CONTRIBUTION_UPDATE, user, user, null, null, contribution, null, amount).save()

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,22 @@
import Decimal from 'decimal.js-light'
import { User as DbUser } from '@entity/User'
import { Transaction as DbTransaction } from '@entity/Transaction'
import { Event as DbEvent } from '@entity/Event'
import { Event, EventType } from './Event'
export const EVENT_TRANSACTION_RECEIVE = async (
user: DbUser,
involvedUser: DbUser,
transaction: DbTransaction,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.TRANSACTION_RECEIVE,
user,
involvedUser,
involvedUser,
transaction,
null,
null,
amount,
).save()

View File

@ -0,0 +1,22 @@
import Decimal from 'decimal.js-light'
import { User as DbUser } from '@entity/User'
import { Transaction as DbTransaction } from '@entity/Transaction'
import { Event as DbEvent } from '@entity/Event'
import { Event, EventType } from './Event'
export const EVENT_TRANSACTION_SEND = async (
user: DbUser,
involvedUser: DbUser,
transaction: DbTransaction,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.TRANSACTION_SEND,
user,
user,
involvedUser,
transaction,
null,
null,
amount,
).save()

View File

@ -4,10 +4,10 @@ import { ContributionMessage as DbContributionMessage } from '@entity/Contributi
import { Contribution as DbContribution } from '@entity/Contribution' import { Contribution as DbContribution } from '@entity/Contribution'
import { Transaction as DbTransaction } from '@entity/Transaction' import { Transaction as DbTransaction } from '@entity/Transaction'
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
import { EventProtocolType } from './EventProtocolType' import { EventType } from './Event'
export const Event = ( export const Event = (
type: EventProtocolType, type: EventType,
affectedUser: DbUser, affectedUser: DbUser,
actingUser: DbUser, actingUser: DbUser,
involvedUser: DbUser | null = null, involvedUser: DbUser | null = null,
@ -28,190 +28,21 @@ export const Event = (
return event return event
} }
export const EVENT_CONTRIBUTION_CREATE = async ( export { EventType } from './EventType'
user: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.CONTRIBUTION_CREATE,
user,
user,
null,
null,
contribution,
null,
amount,
).save()
export const EVENT_CONTRIBUTION_DELETE = async ( export { EVENT_ACTIVATE_ACCOUNT } from './EVENT_ACTIVATE_ACCOUNT'
user: DbUser, export { EVENT_ADMIN_CONTRIBUTION_CONFIRM } from './EVENT_ADMIN_CONTRIBUTION_CONFIRM'
contribution: DbContribution, export { EVENT_ADMIN_CONTRIBUTION_CREATE } from './EVENT_ADMIN_CONTRIBUTION_CREATE'
amount: Decimal, export { EVENT_ADMIN_CONTRIBUTION_DELETE } from './EVENT_ADMIN_CONTRIBUTION_DELETE'
): Promise<DbEvent> => export { EVENT_ADMIN_CONTRIBUTION_DENY } from './EVENT_ADMIN_CONTRIBUTION_DENY'
Event( export { EVENT_ADMIN_CONTRIBUTION_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_UPDATE'
EventProtocolType.CONTRIBUTION_DELETE, export { EVENT_ADMIN_SEND_CONFIRMATION_EMAIL } from './EVENT_ADMIN_SEND_CONFIRMATION_EMAIL'
user, export { EVENT_CONTRIBUTION_CREATE } from './EVENT_CONTRIBUTION_CREATE'
user, export { EVENT_CONTRIBUTION_DELETE } from './EVENT_CONTRIBUTION_DELETE'
null, export { EVENT_CONTRIBUTION_UPDATE } from './EVENT_CONTRIBUTION_UPDATE'
null, export { EVENT_LOGIN } from './EVENT_LOGIN'
contribution, export { EVENT_REGISTER } from './EVENT_REGISTER'
null, export { EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL } from './EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL'
amount, export { EVENT_SEND_CONFIRMATION_EMAIL } from './EVENT_SEND_CONFIRMATION_EMAIL'
).save() export { EVENT_TRANSACTION_SEND } from './EVENT_TRANSACTION_SEND'
export { EVENT_TRANSACTION_RECEIVE } from './EVENT_TRANSACTION_RECEIVE'
export const EVENT_CONTRIBUTION_UPDATE = async (
user: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.CONTRIBUTION_UPDATE,
user,
user,
null,
null,
contribution,
null,
amount,
).save()
export const EVENT_ADMIN_CONTRIBUTION_CREATE = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.ADMIN_CONTRIBUTION_CREATE,
user,
moderator,
null,
null,
contribution,
null,
amount,
).save()
export const EVENT_ADMIN_CONTRIBUTION_UPDATE = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.ADMIN_CONTRIBUTION_UPDATE,
user,
moderator,
null,
null,
contribution,
null,
amount,
).save()
export const EVENT_ADMIN_CONTRIBUTION_DELETE = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.ADMIN_CONTRIBUTION_DELETE,
user,
moderator,
null,
null,
contribution,
null,
amount,
).save()
export const EVENT_CONTRIBUTION_CONFIRM = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.CONTRIBUTION_CONFIRM,
user,
moderator,
null,
null,
contribution,
null,
amount,
).save()
export const EVENT_ADMIN_CONTRIBUTION_DENY = async (
user: DbUser,
moderator: DbUser,
contribution: DbContribution,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.ADMIN_CONTRIBUTION_DENY,
user,
moderator,
null,
null,
contribution,
null,
amount,
).save()
export const EVENT_TRANSACTION_SEND = async (
user: DbUser,
involvedUser: DbUser,
transaction: DbTransaction,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.TRANSACTION_SEND,
user,
user,
involvedUser,
transaction,
null,
null,
amount,
).save()
export const EVENT_TRANSACTION_RECEIVE = async (
user: DbUser,
involvedUser: DbUser,
transaction: DbTransaction,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventProtocolType.TRANSACTION_RECEIVE,
user,
involvedUser,
involvedUser,
transaction,
null,
null,
amount,
).save()
export const EVENT_LOGIN = async (user: DbUser): Promise<DbEvent> =>
Event(EventProtocolType.LOGIN, user, user).save()
export const EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL = async (user: DbUser): Promise<DbEvent> =>
Event(EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL, user, { id: 0 } as DbUser).save()
export const EVENT_SEND_CONFIRMATION_EMAIL = async (user: DbUser): Promise<DbEvent> =>
Event(EventProtocolType.SEND_CONFIRMATION_EMAIL, user, user).save()
export const EVENT_ADMIN_SEND_CONFIRMATION_EMAIL = async (
user: DbUser,
moderator: DbUser,
): Promise<DbEvent> =>
Event(EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL, user, moderator).save()
export const EVENT_REGISTER = async (user: DbUser): Promise<DbEvent> =>
Event(EventProtocolType.REGISTER, user, user).save()
export const EVENT_ACTIVATE_ACCOUNT = async (user: DbUser): Promise<DbEvent> =>
Event(EventProtocolType.ACTIVATE_ACCOUNT, user, user).save()

View File

@ -1,42 +1,48 @@
export enum EventProtocolType { export { EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL } from './EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL'
// VISIT_GRADIDO = 'VISIT_GRADIDO', export { EVENT_SEND_CONFIRMATION_EMAIL } from './EVENT_SEND_CONFIRMATION_EMAIL'
REGISTER = 'REGISTER', export { EVENT_TRANSACTION_SEND } from './EVENT_TRANSACTION_SEND'
REDEEM_REGISTER = 'REDEEM_REGISTER', export { EVENT_TRANSACTION_RECEIVE } from './EVENT_TRANSACTION_RECEIVE'
// VERIFY_REDEEM = 'VERIFY_REDEEM',
// INACTIVE_ACCOUNT = 'INACTIVE_ACCOUNT', export enum EventType {
SEND_CONFIRMATION_EMAIL = 'SEND_CONFIRMATION_EMAIL',
ADMIN_SEND_CONFIRMATION_EMAIL = 'ADMIN_SEND_CONFIRMATION_EMAIL',
SEND_ACCOUNT_MULTIREGISTRATION_EMAIL = 'SEND_ACCOUNT_MULTIREGISTRATION_EMAIL',
// CONFIRM_EMAIL = 'CONFIRM_EMAIL',
// REGISTER_EMAIL_KLICKTIPP = 'REGISTER_EMAIL_KLICKTIPP',
LOGIN = 'LOGIN',
// LOGOUT = 'LOGOUT',
// REDEEM_LOGIN = 'REDEEM_LOGIN',
ACTIVATE_ACCOUNT = 'ACTIVATE_ACCOUNT', ACTIVATE_ACCOUNT = 'ACTIVATE_ACCOUNT',
// SEND_FORGOT_PASSWORD_EMAIL = 'SEND_FORGOT_PASSWORD_EMAIL', // TODO CONTRIBUTION_CONFIRM = 'CONTRIBUTION_CONFIRM',
// PASSWORD_CHANGE = 'PASSWORD_CHANGE', ADMIN_CONTRIBUTION_CONFIRM = 'ADMIN_CONTRIBUTION_CONFIRM',
// SEND_TRANSACTION_SEND_EMAIL = 'SEND_TRANSACTION_SEND_EMAIL',
// SEND_TRANSACTION_RECEIVE_EMAIL = 'SEND_TRANSACTION_RECEIVE_EMAIL',
TRANSACTION_SEND = 'TRANSACTION_SEND',
// TRANSACTION_SEND_REDEEM = 'TRANSACTION_SEND_REDEEM',
// TRANSACTION_REPEATE_REDEEM = 'TRANSACTION_REPEATE_REDEEM',
// TRANSACTION_CREATION = 'TRANSACTION_CREATION',
TRANSACTION_RECEIVE = 'TRANSACTION_RECEIVE',
// TRANSACTION_RECEIVE_REDEEM = 'TRANSACTION_RECEIVE_REDEEM',
// SEND_TRANSACTION_LINK_REDEEM_EMAIL = 'SEND_TRANSACTION_LINK_REDEEM_EMAIL',
// SEND_ADDED_CONTRIBUTION_EMAIL = 'SEND_ADDED_CONTRIBUTION_EMAIL',
// SEND_CONTRIBUTION_CONFIRM_EMAIL = 'SEND_CONTRIBUTION_CONFIRM_EMAIL',
CONTRIBUTION_CREATE = 'CONTRIBUTION_CREATE',
CONTRIBUTION_CONFIRM = 'CONTRIBUTION_CONFIRM',
// CONTRIBUTION_DENY = 'CONTRIBUTION_DENY',
// CONTRIBUTION_LINK_DEFINE = 'CONTRIBUTION_LINK_DEFINE',
// CONTRIBUTION_LINK_ACTIVATE_REDEEM = 'CONTRIBUTION_LINK_ACTIVATE_REDEEM',
CONTRIBUTION_DELETE = 'CONTRIBUTION_DELETE',
CONTRIBUTION_UPDATE = 'CONTRIBUTION_UPDATE',
ADMIN_CONTRIBUTION_CREATE = 'ADMIN_CONTRIBUTION_CREATE', ADMIN_CONTRIBUTION_CREATE = 'ADMIN_CONTRIBUTION_CREATE',
ADMIN_CONTRIBUTION_DELETE = 'ADMIN_CONTRIBUTION_DELETE', ADMIN_CONTRIBUTION_DELETE = 'ADMIN_CONTRIBUTION_DELETE',
ADMIN_CONTRIBUTION_DENY = 'ADMIN_CONTRIBUTION_DENY', ADMIN_CONTRIBUTION_DENY = 'ADMIN_CONTRIBUTION_DENY',
ADMIN_CONTRIBUTION_UPDATE = 'ADMIN_CONTRIBUTION_UPDATE', ADMIN_CONTRIBUTION_UPDATE = 'ADMIN_CONTRIBUTION_UPDATE',
ADMIN_SEND_CONFIRMATION_EMAIL = 'ADMIN_SEND_CONFIRMATION_EMAIL',
CONTRIBUTION_CREATE = 'CONTRIBUTION_CREATE',
CONTRIBUTION_DELETE = 'CONTRIBUTION_DELETE',
CONTRIBUTION_UPDATE = 'CONTRIBUTION_UPDATE',
LOGIN = 'LOGIN',
REGISTER = 'REGISTER',
REDEEM_REGISTER = 'REDEEM_REGISTER',
SEND_ACCOUNT_MULTIREGISTRATION_EMAIL = 'SEND_ACCOUNT_MULTIREGISTRATION_EMAIL',
SEND_CONFIRMATION_EMAIL = 'SEND_CONFIRMATION_EMAIL',
TRANSACTION_SEND = 'TRANSACTION_SEND',
TRANSACTION_RECEIVE = 'TRANSACTION_RECEIVE',
// VISIT_GRADIDO = 'VISIT_GRADIDO',
// VERIFY_REDEEM = 'VERIFY_REDEEM',
// INACTIVE_ACCOUNT = 'INACTIVE_ACCOUNT',
// CONFIRM_EMAIL = 'CONFIRM_EMAIL',
// REGISTER_EMAIL_KLICKTIPP = 'REGISTER_EMAIL_KLICKTIPP',
// LOGOUT = 'LOGOUT',
// REDEEM_LOGIN = 'REDEEM_LOGIN',
// SEND_FORGOT_PASSWORD_EMAIL = 'SEND_FORGOT_PASSWORD_EMAIL',
// PASSWORD_CHANGE = 'PASSWORD_CHANGE',
// SEND_TRANSACTION_SEND_EMAIL = 'SEND_TRANSACTION_SEND_EMAIL',
// SEND_TRANSACTION_RECEIVE_EMAIL = 'SEND_TRANSACTION_RECEIVE_EMAIL',
// TRANSACTION_SEND_REDEEM = 'TRANSACTION_SEND_REDEEM',
// TRANSACTION_REPEATE_REDEEM = 'TRANSACTION_REPEATE_REDEEM',
// TRANSACTION_CREATION = 'TRANSACTION_CREATION',
// TRANSACTION_RECEIVE_REDEEM = 'TRANSACTION_RECEIVE_REDEEM',
// SEND_TRANSACTION_LINK_REDEEM_EMAIL = 'SEND_TRANSACTION_LINK_REDEEM_EMAIL',
// SEND_ADDED_CONTRIBUTION_EMAIL = 'SEND_ADDED_CONTRIBUTION_EMAIL',
// SEND_CONTRIBUTION_CONFIRM_EMAIL = 'SEND_CONTRIBUTION_CONFIRM_EMAIL',
// CONTRIBUTION_DENY = 'CONTRIBUTION_DENY',
// CONTRIBUTION_LINK_DEFINE = 'CONTRIBUTION_LINK_DEFINE',
// CONTRIBUTION_LINK_ACTIVATE_REDEEM = 'CONTRIBUTION_LINK_ACTIVATE_REDEEM',
// USER_CREATE_CONTRIBUTION_MESSAGE = 'USER_CREATE_CONTRIBUTION_MESSAGE', // USER_CREATE_CONTRIBUTION_MESSAGE = 'USER_CREATE_CONTRIBUTION_MESSAGE',
// ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE', // ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',
// DELETE_USER = 'DELETE_USER', // DELETE_USER = 'DELETE_USER',

View File

@ -1,33 +1,47 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field, Int } from 'type-graphql' import { ObjectType, Field, Int } from 'type-graphql'
import { Community as DbCommunity } from '@entity/Community'
@ObjectType() @ObjectType()
export class Community { export class Community {
constructor(json?: any) { constructor(dbCom: DbCommunity) {
if (json) { this.id = dbCom.id
this.id = Number(json.id) this.foreign = dbCom.foreign
this.name = json.name this.publicKey = dbCom.publicKey.toString()
this.url = json.url this.url =
this.description = json.description (dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/') +
this.registerUrl = json.registerUrl 'api/' +
} dbCom.apiVersion
this.lastAnnouncedAt = dbCom.lastAnnouncedAt
this.verifiedAt = dbCom.verifiedAt
this.lastErrorAt = dbCom.lastErrorAt
this.createdAt = dbCom.createdAt
this.updatedAt = dbCom.updatedAt
} }
@Field(() => Int) @Field(() => Int)
id: number id: number
@Field(() => Boolean)
foreign: boolean
@Field(() => String) @Field(() => String)
name: string publicKey: string
@Field(() => String) @Field(() => String)
url: string url: string
@Field(() => String) @Field(() => Date, { nullable: true })
description: string lastAnnouncedAt: Date | null
@Field(() => String) @Field(() => Date, { nullable: true })
registerUrl: string verifiedAt: Date | null
@Field(() => Date, { nullable: true })
lastErrorAt: Date | null
@Field(() => Date, { nullable: true })
createdAt: Date | null
@Field(() => Date, { nullable: true })
updatedAt: Date | null
} }

View File

@ -1,24 +1,25 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/unbound-method */ /* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { createTestClient } from 'apollo-server-testing' import { getCommunities } from '@/seeds/graphql/queries'
import createServer from '@/server/createServer' import { Community as DbCommunity } from '@entity/Community'
import CONFIG from '@/config' import { testEnvironment } from '@test/helpers'
jest.mock('@/config')
let query: any let query: any
// to do: We need a setup for the tests that closes the connection // to do: We need a setup for the tests that closes the connection
let con: any let con: any
let testEnv: any
beforeAll(async () => { beforeAll(async () => {
const server = await createServer({}) testEnv = await testEnvironment()
con = server.con query = testEnv.query
query = createTestClient(server.apollo).query con = testEnv.con
await DbCommunity.clear()
}) })
afterAll(async () => { afterAll(async () => {
@ -26,74 +27,90 @@ afterAll(async () => {
}) })
describe('CommunityResolver', () => { describe('CommunityResolver', () => {
const getCommunityInfoQuery = ` describe('getCommunities', () => {
query { let homeCom1: DbCommunity
getCommunityInfo { let homeCom2: DbCommunity
name let homeCom3: DbCommunity
description let foreignCom1: DbCommunity
url let foreignCom2: DbCommunity
registerUrl let foreignCom3: DbCommunity
} describe('with empty list', () => {
} it('returns no community entry', async () => {
` // const result: Community[] = await query({ query: getCommunities })
// expect(result.length).toEqual(0)
const communities = ` await expect(query({ query: getCommunities })).resolves.toMatchObject({
query {
communities {
id
name
url
description
registerUrl
}
}
`
describe('getCommunityInfo', () => {
it('returns the default values', async () => {
await expect(query({ query: getCommunityInfoQuery })).resolves.toMatchObject({
data: { data: {
getCommunityInfo: { getCommunities: [],
name: 'Gradido Entwicklung',
description: 'Die lokale Entwicklungsumgebung von Gradido.',
url: 'http://localhost/',
registerUrl: 'http://localhost/register',
},
}, },
}) })
}) })
}) })
describe('communities', () => { describe('only home-communities entries', () => {
describe('PRODUCTION = false', () => { beforeEach(async () => {
beforeEach(() => { jest.clearAllMocks()
CONFIG.PRODUCTION = false
homeCom1 = DbCommunity.create()
homeCom1.foreign = false
homeCom1.publicKey = Buffer.from('publicKey-HomeCommunity')
homeCom1.apiVersion = '1_0'
homeCom1.endPoint = 'http://localhost'
homeCom1.createdAt = new Date()
await DbCommunity.insert(homeCom1)
homeCom2 = DbCommunity.create()
homeCom2.foreign = false
homeCom2.publicKey = Buffer.from('publicKey-HomeCommunity')
homeCom2.apiVersion = '1_1'
homeCom2.endPoint = 'http://localhost'
homeCom2.createdAt = new Date()
await DbCommunity.insert(homeCom2)
homeCom3 = DbCommunity.create()
homeCom3.foreign = false
homeCom3.publicKey = Buffer.from('publicKey-HomeCommunity')
homeCom3.apiVersion = '2_0'
homeCom3.endPoint = 'http://localhost'
homeCom3.createdAt = new Date()
await DbCommunity.insert(homeCom3)
}) })
it('returns three communities', async () => { it('returns three home-community entries', async () => {
await expect(query({ query: communities })).resolves.toMatchObject({ await expect(query({ query: getCommunities })).resolves.toMatchObject({
data: { data: {
communities: [ getCommunities: [
{ {
id: 1, id: 1,
name: 'Gradido Entwicklung', foreign: homeCom1.foreign,
description: 'Die lokale Entwicklungsumgebung von Gradido.', publicKey: expect.stringMatching('publicKey-HomeCommunity'),
url: 'http://localhost/', url: expect.stringMatching('http://localhost/api/1_0'),
registerUrl: 'http://localhost/register-community', lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: homeCom1.createdAt.toISOString(),
updatedAt: null,
}, },
{ {
id: 2, id: 2,
name: 'Gradido Staging', foreign: homeCom2.foreign,
description: 'Der Testserver der Gradido-Akademie.', publicKey: expect.stringMatching('publicKey-HomeCommunity'),
url: 'https://stage1.gradido.net/', url: expect.stringMatching('http://localhost/api/1_1'),
registerUrl: 'https://stage1.gradido.net/register-community', lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: homeCom2.createdAt.toISOString(),
updatedAt: null,
}, },
{ {
id: 3, id: 3,
name: 'Gradido-Akademie', foreign: homeCom3.foreign,
description: 'Freies Institut für Wirtschaftsbionik.', publicKey: expect.stringMatching('publicKey-HomeCommunity'),
url: 'https://gradido.net', url: expect.stringMatching('http://localhost/api/2_0'),
registerUrl: 'https://gdd1.gradido.com/register-community', lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: homeCom3.createdAt.toISOString(),
updatedAt: null,
}, },
], ],
}, },
@ -101,21 +118,104 @@ describe('CommunityResolver', () => {
}) })
}) })
describe('PRODUCTION = true', () => { describe('plus foreign-communities entries', () => {
beforeEach(() => { beforeEach(async () => {
CONFIG.PRODUCTION = true jest.clearAllMocks()
foreignCom1 = DbCommunity.create()
foreignCom1.foreign = true
foreignCom1.publicKey = Buffer.from('publicKey-ForeignCommunity')
foreignCom1.apiVersion = '1_0'
foreignCom1.endPoint = 'http://remotehost'
foreignCom1.createdAt = new Date()
await DbCommunity.insert(foreignCom1)
foreignCom2 = DbCommunity.create()
foreignCom2.foreign = true
foreignCom2.publicKey = Buffer.from('publicKey-ForeignCommunity')
foreignCom2.apiVersion = '1_1'
foreignCom2.endPoint = 'http://remotehost'
foreignCom2.createdAt = new Date()
await DbCommunity.insert(foreignCom2)
foreignCom3 = DbCommunity.create()
foreignCom3.foreign = true
foreignCom3.publicKey = Buffer.from('publicKey-ForeignCommunity')
foreignCom3.apiVersion = '1_2'
foreignCom3.endPoint = 'http://remotehost'
foreignCom3.createdAt = new Date()
await DbCommunity.insert(foreignCom3)
}) })
it('returns one community', async () => { it('returns 3x home and 3x foreign-community entries', async () => {
await expect(query({ query: communities })).resolves.toMatchObject({ await expect(query({ query: getCommunities })).resolves.toMatchObject({
data: { data: {
communities: [ getCommunities: [
{
id: 1,
foreign: homeCom1.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
url: expect.stringMatching('http://localhost/api/1_0'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: homeCom1.createdAt.toISOString(),
updatedAt: null,
},
{
id: 2,
foreign: homeCom2.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
url: expect.stringMatching('http://localhost/api/1_1'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: homeCom2.createdAt.toISOString(),
updatedAt: null,
},
{ {
id: 3, id: 3,
name: 'Gradido-Akademie', foreign: homeCom3.foreign,
description: 'Freies Institut für Wirtschaftsbionik.', publicKey: expect.stringMatching('publicKey-HomeCommunity'),
url: 'https://gradido.net', url: expect.stringMatching('http://localhost/api/2_0'),
registerUrl: 'https://gdd1.gradido.com/register-community', lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: homeCom3.createdAt.toISOString(),
updatedAt: null,
},
{
id: 4,
foreign: foreignCom1.foreign,
publicKey: expect.stringMatching('publicKey-ForeignCommunity'),
url: expect.stringMatching('http://remotehost/api/1_0'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: foreignCom1.createdAt.toISOString(),
updatedAt: null,
},
{
id: 5,
foreign: foreignCom2.foreign,
publicKey: expect.stringMatching('publicKey-ForeignCommunity'),
url: expect.stringMatching('http://remotehost/api/1_1'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: foreignCom2.createdAt.toISOString(),
updatedAt: null,
},
{
id: 6,
foreign: foreignCom3.foreign,
publicKey: expect.stringMatching('publicKey-ForeignCommunity'),
url: expect.stringMatching('http://remotehost/api/1_2'),
lastAnnouncedAt: null,
verifiedAt: null,
lastErrorAt: null,
createdAt: foreignCom3.createdAt.toISOString(),
updatedAt: null,
}, },
], ],
}, },

View File

@ -1,58 +1,18 @@
import { Resolver, Query, Authorized } from 'type-graphql' import { Resolver, Query, Authorized } from 'type-graphql'
import { Community } from '@model/Community' import { Community } from '@model/Community'
import { Community as DbCommunity } from '@entity/Community'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
import CONFIG from '@/config'
@Resolver() @Resolver()
export class CommunityResolver { export class CommunityResolver {
@Authorized([RIGHTS.GET_COMMUNITY_INFO])
@Query(() => Community)
getCommunityInfo(): Community {
return new Community({
name: CONFIG.COMMUNITY_NAME,
description: CONFIG.COMMUNITY_DESCRIPTION,
url: CONFIG.COMMUNITY_URL,
registerUrl: CONFIG.COMMUNITY_REGISTER_URL,
})
}
@Authorized([RIGHTS.COMMUNITIES]) @Authorized([RIGHTS.COMMUNITIES])
@Query(() => [Community]) @Query(() => [Community])
communities(): Community[] { async getCommunities(): Promise<Community[]> {
if (CONFIG.PRODUCTION) const dbCommunities: DbCommunity[] = await DbCommunity.find({
return [ order: { foreign: 'ASC', publicKey: 'ASC', apiVersion: 'ASC' },
new Community({ })
id: 3, return dbCommunities.map((dbCom: DbCommunity) => new Community(dbCom))
name: 'Gradido-Akademie',
description: 'Freies Institut für Wirtschaftsbionik.',
url: 'https://gradido.net',
registerUrl: 'https://gdd1.gradido.com/register-community',
}),
]
return [
new Community({
id: 1,
name: 'Gradido Entwicklung',
description: 'Die lokale Entwicklungsumgebung von Gradido.',
url: 'http://localhost/',
registerUrl: 'http://localhost/register-community',
}),
new Community({
id: 2,
name: 'Gradido Staging',
description: 'Der Testserver der Gradido-Akademie.',
url: 'https://stage1.gradido.net/',
registerUrl: 'https://stage1.gradido.net/register-community',
}),
new Community({
id: 3,
name: 'Gradido-Akademie',
description: 'Freies Institut für Wirtschaftsbionik.',
url: 'https://gradido.net',
registerUrl: 'https://gdd1.gradido.com/register-community',
}),
]
} }
} }

View File

@ -27,7 +27,7 @@ import {
import { import {
listAllContributions, listAllContributions,
listContributions, listContributions,
adminListAllContributions, adminListContributions,
} from '@/seeds/graphql/queries' } from '@/seeds/graphql/queries'
import { import {
sendContributionConfirmedEmail, sendContributionConfirmedEmail,
@ -50,7 +50,7 @@ import { Event as DbEvent } from '@entity/Event'
import { Contribution } from '@entity/Contribution' import { Contribution } from '@entity/Contribution'
import { Transaction as DbTransaction } from '@entity/Transaction' import { Transaction as DbTransaction } from '@entity/Transaction'
import { User } from '@entity/User' import { User } from '@entity/User'
import { EventProtocolType } from '@/event/EventProtocolType' import { EventType } from '@/event/Event'
import { logger, i18n as localization } from '@test/testSetup' import { logger, i18n as localization } from '@test/testSetup'
import { UserInputError } from 'apollo-server-express' import { UserInputError } from 'apollo-server-express'
import { raeuberHotzenplotz } from '@/seeds/users/raeuber-hotzenplotz' import { raeuberHotzenplotz } from '@/seeds/users/raeuber-hotzenplotz'
@ -281,7 +281,7 @@ describe('ContributionResolver', () => {
it('stores the CONTRIBUTION_CREATE event in the database', async () => { it('stores the CONTRIBUTION_CREATE event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.CONTRIBUTION_CREATE, type: EventType.CONTRIBUTION_CREATE,
affectedUserId: bibi.id, affectedUserId: bibi.id,
actingUserId: bibi.id, actingUserId: bibi.id,
involvedContributionId: pendingContribution.data.createContribution.id, involvedContributionId: pendingContribution.data.createContribution.id,
@ -586,7 +586,7 @@ describe('ContributionResolver', () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.CONTRIBUTION_UPDATE, type: EventType.CONTRIBUTION_UPDATE,
affectedUserId: bibi.id, affectedUserId: bibi.id,
actingUserId: bibi.id, actingUserId: bibi.id,
involvedContributionId: pendingContribution.data.createContribution.id, involvedContributionId: pendingContribution.data.createContribution.id,
@ -817,7 +817,7 @@ describe('ContributionResolver', () => {
it('stores the ADMIN_CONTRIBUTION_DENY event in the database', async () => { it('stores the ADMIN_CONTRIBUTION_DENY event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.ADMIN_CONTRIBUTION_DENY, type: EventType.ADMIN_CONTRIBUTION_DENY,
affectedUserId: bibi.id, affectedUserId: bibi.id,
actingUserId: admin.id, actingUserId: admin.id,
involvedContributionId: contributionToDeny.data.createContribution.id, involvedContributionId: contributionToDeny.data.createContribution.id,
@ -945,7 +945,7 @@ describe('ContributionResolver', () => {
it('stores the CONTRIBUTION_DELETE event in the database', async () => { it('stores the CONTRIBUTION_DELETE event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.CONTRIBUTION_DELETE, type: EventType.CONTRIBUTION_DELETE,
affectedUserId: bibi.id, affectedUserId: bibi.id,
actingUserId: bibi.id, actingUserId: bibi.id,
involvedContributionId: contributionToDelete.data.createContribution.id, involvedContributionId: contributionToDelete.data.createContribution.id,
@ -2033,7 +2033,7 @@ describe('ContributionResolver', () => {
it('stores the ADMIN_CONTRIBUTION_CREATE event in the database', async () => { it('stores the ADMIN_CONTRIBUTION_CREATE event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.ADMIN_CONTRIBUTION_CREATE, type: EventType.ADMIN_CONTRIBUTION_CREATE,
affectedUserId: bibi.id, affectedUserId: bibi.id,
actingUserId: admin.id, actingUserId: admin.id,
amount: expect.decimalEqual(200), amount: expect.decimalEqual(200),
@ -2252,7 +2252,7 @@ describe('ContributionResolver', () => {
it('stores the ADMIN_CONTRIBUTION_UPDATE event in the database', async () => { it('stores the ADMIN_CONTRIBUTION_UPDATE event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE, type: EventType.ADMIN_CONTRIBUTION_UPDATE,
affectedUserId: creation?.userId, affectedUserId: creation?.userId,
actingUserId: admin.id, actingUserId: admin.id,
amount: 300, amount: 300,
@ -2292,7 +2292,7 @@ describe('ContributionResolver', () => {
it('stores the ADMIN_CONTRIBUTION_UPDATE event in the database', async () => { it('stores the ADMIN_CONTRIBUTION_UPDATE event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE, type: EventType.ADMIN_CONTRIBUTION_UPDATE,
affectedUserId: creation?.userId, affectedUserId: creation?.userId,
actingUserId: admin.id, actingUserId: admin.id,
amount: expect.decimalEqual(200), amount: expect.decimalEqual(200),
@ -2378,7 +2378,7 @@ describe('ContributionResolver', () => {
it('stores the ADMIN_CONTRIBUTION_DELETE event in the database', async () => { it('stores the ADMIN_CONTRIBUTION_DELETE event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.ADMIN_CONTRIBUTION_DELETE, type: EventType.ADMIN_CONTRIBUTION_DELETE,
affectedUserId: creation?.userId, affectedUserId: creation?.userId,
actingUserId: admin.id, actingUserId: admin.id,
involvedContributionId: creation?.id, involvedContributionId: creation?.id,
@ -2536,7 +2536,7 @@ describe('ContributionResolver', () => {
it('stores the CONTRIBUTION_CONFIRM event in the database', async () => { it('stores the CONTRIBUTION_CONFIRM event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.CONTRIBUTION_CONFIRM, type: EventType.ADMIN_CONTRIBUTION_CONFIRM,
}), }),
) )
}) })
@ -2568,7 +2568,7 @@ describe('ContributionResolver', () => {
it('stores the SEND_CONFIRMATION_EMAIL event in the database', async () => { it('stores the SEND_CONFIRMATION_EMAIL event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.SEND_CONFIRMATION_EMAIL, type: EventType.SEND_CONFIRMATION_EMAIL,
}), }),
) )
}) })
@ -2657,12 +2657,12 @@ describe('ContributionResolver', () => {
}) })
}) })
describe('adminListAllContribution', () => { describe('adminListContributions', () => {
describe('unauthenticated', () => { describe('unauthenticated', () => {
it('returns an error', async () => { it('returns an error', async () => {
await expect( await expect(
query({ query({
query: adminListAllContributions, query: adminListContributions,
}), }),
).resolves.toEqual( ).resolves.toEqual(
expect.objectContaining({ expect.objectContaining({
@ -2687,7 +2687,7 @@ describe('ContributionResolver', () => {
it('returns an error', async () => { it('returns an error', async () => {
await expect( await expect(
query({ query({
query: adminListAllContributions, query: adminListContributions,
}), }),
).resolves.toEqual( ).resolves.toEqual(
expect.objectContaining({ expect.objectContaining({
@ -2711,9 +2711,9 @@ describe('ContributionResolver', () => {
it('returns 17 creations in total', async () => { it('returns 17 creations in total', async () => {
const { const {
data: { adminListAllContributions: contributionListObject }, data: { adminListContributions: contributionListObject },
}: { data: { adminListAllContributions: ContributionListResult } } = await query({ }: { data: { adminListContributions: ContributionListResult } } = await query({
query: adminListAllContributions, query: adminListContributions,
}) })
expect(contributionListObject.contributionList).toHaveLength(17) expect(contributionListObject.contributionList).toHaveLength(17)
expect(contributionListObject).toMatchObject({ expect(contributionListObject).toMatchObject({
@ -2878,9 +2878,9 @@ describe('ContributionResolver', () => {
it('returns two pending creations with page size set to 2', async () => { it('returns two pending creations with page size set to 2', async () => {
const { const {
data: { adminListAllContributions: contributionListObject }, data: { adminListContributions: contributionListObject },
}: { data: { adminListAllContributions: ContributionListResult } } = await query({ }: { data: { adminListContributions: ContributionListResult } } = await query({
query: adminListAllContributions, query: adminListContributions,
variables: { variables: {
currentPage: 1, currentPage: 1,
pageSize: 2, pageSize: 2,

View File

@ -42,7 +42,7 @@ import {
EVENT_ADMIN_CONTRIBUTION_CREATE, EVENT_ADMIN_CONTRIBUTION_CREATE,
EVENT_ADMIN_CONTRIBUTION_UPDATE, EVENT_ADMIN_CONTRIBUTION_UPDATE,
EVENT_ADMIN_CONTRIBUTION_DELETE, EVENT_ADMIN_CONTRIBUTION_DELETE,
EVENT_CONTRIBUTION_CONFIRM, EVENT_ADMIN_CONTRIBUTION_CONFIRM,
EVENT_ADMIN_CONTRIBUTION_DENY, EVENT_ADMIN_CONTRIBUTION_DENY,
} from '@/event/Event' } from '@/event/Event'
import { calculateDecay } from '@/util/decay' import { calculateDecay } from '@/util/decay'
@ -136,15 +136,15 @@ export class ContributionResolver {
): Promise<ContributionListResult> { ): Promise<ContributionListResult> {
const user = getUser(context) const user = getUser(context)
const [dbContributions, count] = await findContributions( const [dbContributions, count] = await findContributions({
order, order,
currentPage, currentPage,
pageSize, pageSize,
true, withDeleted: true,
['messages'], relations: ['messages'],
user.id, userId: user.id,
statusFilter, statusFilter,
) })
return new ContributionListResult( return new ContributionListResult(
count, count,
dbContributions.map((contribution) => new Contribution(contribution, user)), dbContributions.map((contribution) => new Contribution(contribution, user)),
@ -159,15 +159,13 @@ export class ContributionResolver {
@Arg('statusFilter', () => [ContributionStatus], { nullable: true }) @Arg('statusFilter', () => [ContributionStatus], { nullable: true })
statusFilter?: ContributionStatus[] | null, statusFilter?: ContributionStatus[] | null,
): Promise<ContributionListResult> { ): Promise<ContributionListResult> {
const [dbContributions, count] = await findContributions( const [dbContributions, count] = await findContributions({
order, order,
currentPage, currentPage,
pageSize, pageSize,
false, relations: ['user'],
['user'],
undefined,
statusFilter, statusFilter,
) })
return new ContributionListResult( return new ContributionListResult(
count, count,
@ -369,23 +367,25 @@ export class ContributionResolver {
return result return result
} }
@Authorized([RIGHTS.LIST_UNCONFIRMED_CONTRIBUTIONS]) @Authorized([RIGHTS.ADMIN_LIST_CONTRIBUTIONS])
@Query(() => ContributionListResult) // [UnconfirmedContribution] @Query(() => ContributionListResult)
async adminListAllContributions( async adminListContributions(
@Args() @Args()
{ currentPage = 1, pageSize = 3, order = Order.DESC }: Paginated, { currentPage = 1, pageSize = 3, order = Order.DESC }: Paginated,
@Arg('statusFilter', () => [ContributionStatus], { nullable: true }) @Arg('statusFilter', () => [ContributionStatus], { nullable: true })
statusFilter?: ContributionStatus[] | null, statusFilter?: ContributionStatus[] | null,
@Arg('userId', () => Int, { nullable: true })
userId?: number | null,
): Promise<ContributionListResult> { ): Promise<ContributionListResult> {
const [dbContributions, count] = await findContributions( const [dbContributions, count] = await findContributions({
order, order,
currentPage, currentPage,
pageSize, pageSize,
true, withDeleted: true,
['user', 'messages'], userId,
undefined, relations: ['user', 'messages'],
statusFilter, statusFilter,
) })
return new ContributionListResult( return new ContributionListResult(
count, count,
@ -540,40 +540,13 @@ export class ContributionResolver {
await queryRunner.release() await queryRunner.release()
} }
await EVENT_CONTRIBUTION_CONFIRM(user, moderatorUser, contribution, contribution.amount) await EVENT_ADMIN_CONTRIBUTION_CONFIRM(user, moderatorUser, contribution, contribution.amount)
} finally { } finally {
releaseLock() releaseLock()
} }
return true return true
} }
@Authorized([RIGHTS.CREATION_TRANSACTION_LIST])
@Query(() => ContributionListResult)
async creationTransactionList(
@Args()
{ currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
@Arg('userId', () => Int) userId: number,
): Promise<ContributionListResult> {
const offset = (currentPage - 1) * pageSize
const [contributionResult, count] = await getConnection()
.createQueryBuilder()
.select('c')
.from(DbContribution, 'c')
.leftJoinAndSelect('c.user', 'u')
.where(`user_id = ${userId}`)
.withDeleted()
.limit(pageSize)
.offset(offset)
.orderBy('c.created_at', order)
.getManyAndCount()
return new ContributionListResult(
count,
contributionResult.map((contribution) => new Contribution(contribution, contribution.user)),
)
// return userTransactions.map((t) => new Transaction(t, new User(user), communityUser))
}
@Authorized([RIGHTS.OPEN_CREATIONS]) @Authorized([RIGHTS.OPEN_CREATIONS])
@Query(() => [OpenCreation]) @Query(() => [OpenCreation])
async openCreations(@Ctx() context: Context): Promise<OpenCreation[]> { async openCreations(@Ctx() context: Context): Promise<OpenCreation[]> {

View File

@ -6,7 +6,7 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
import { EventProtocolType } from '@/event/EventProtocolType' import { EventType } from '@/event/Event'
import { userFactory } from '@/seeds/factory/user' import { userFactory } from '@/seeds/factory/user'
import { import {
confirmContribution, confirmContribution,
@ -343,7 +343,7 @@ describe('send coins', () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.TRANSACTION_SEND, type: EventType.TRANSACTION_SEND,
affectedUserId: user[1].id, affectedUserId: user[1].id,
actingUserId: user[1].id, actingUserId: user[1].id,
involvedUserId: user[0].id, involvedUserId: user[0].id,
@ -361,7 +361,7 @@ describe('send coins', () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.TRANSACTION_RECEIVE, type: EventType.TRANSACTION_RECEIVE,
affectedUserId: user[0].id, affectedUserId: user[0].id,
actingUserId: user[1].id, actingUserId: user[1].id,
involvedUserId: user[1].id, involvedUserId: user[1].id,

View File

@ -39,7 +39,7 @@ import { contributionLinkFactory } from '@/seeds/factory/contributionLink'
import { transactionLinkFactory } from '@/seeds/factory/transactionLink' import { transactionLinkFactory } from '@/seeds/factory/transactionLink'
import { ContributionLink } from '@model/ContributionLink' import { ContributionLink } from '@model/ContributionLink'
import { TransactionLink } from '@entity/TransactionLink' import { TransactionLink } from '@entity/TransactionLink'
import { EventProtocolType } from '@/event/EventProtocolType' import { EventType } from '@/event/Event'
import { Event as DbEvent } from '@entity/Event' import { Event as DbEvent } from '@entity/Event'
import { validate as validateUUID, version as versionUUID } from 'uuid' import { validate as validateUUID, version as versionUUID } from 'uuid'
import { peterLustig } from '@/seeds/users/peter-lustig' import { peterLustig } from '@/seeds/users/peter-lustig'
@ -189,7 +189,7 @@ describe('UserResolver', () => {
) )
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.REGISTER, type: EventType.REGISTER,
affectedUserId: userConatct.user.id, affectedUserId: userConatct.user.id,
actingUserId: userConatct.user.id, actingUserId: userConatct.user.id,
}), }),
@ -219,7 +219,7 @@ describe('UserResolver', () => {
it('stores the SEND_CONFIRMATION_EMAIL event in the database', async () => { it('stores the SEND_CONFIRMATION_EMAIL event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.SEND_CONFIRMATION_EMAIL, type: EventType.SEND_CONFIRMATION_EMAIL,
affectedUserId: user[0].id, affectedUserId: user[0].id,
actingUserId: user[0].id, actingUserId: user[0].id,
}), }),
@ -265,7 +265,7 @@ describe('UserResolver', () => {
) )
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL, type: EventType.SEND_ACCOUNT_MULTIREGISTRATION_EMAIL,
affectedUserId: userConatct.user.id, affectedUserId: userConatct.user.id,
actingUserId: 0, actingUserId: 0,
}), }),
@ -366,7 +366,7 @@ describe('UserResolver', () => {
it('stores the ACTIVATE_ACCOUNT event in the database', async () => { it('stores the ACTIVATE_ACCOUNT event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.ACTIVATE_ACCOUNT, type: EventType.ACTIVATE_ACCOUNT,
affectedUserId: user[0].id, affectedUserId: user[0].id,
actingUserId: user[0].id, actingUserId: user[0].id,
}), }),
@ -376,7 +376,7 @@ describe('UserResolver', () => {
it('stores the REDEEM_REGISTER event in the database', async () => { it('stores the REDEEM_REGISTER event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.REDEEM_REGISTER, type: EventType.REDEEM_REGISTER,
affectedUserId: result.data.createUser.id, affectedUserId: result.data.createUser.id,
actingUserId: result.data.createUser.id, actingUserId: result.data.createUser.id,
involvedContributionId: link.id, involvedContributionId: link.id,
@ -461,7 +461,7 @@ describe('UserResolver', () => {
it('stores the REDEEM_REGISTER event in the database', async () => { it('stores the REDEEM_REGISTER event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.REDEEM_REGISTER, type: EventType.REDEEM_REGISTER,
affectedUserId: newUser.data.createUser.id, affectedUserId: newUser.data.createUser.id,
actingUserId: newUser.data.createUser.id, actingUserId: newUser.data.createUser.id,
involvedTransactionId: transactionLink.id, involvedTransactionId: transactionLink.id,
@ -694,7 +694,7 @@ describe('UserResolver', () => {
) )
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.LOGIN, type: EventType.LOGIN,
affectedUserId: userConatct.user.id, affectedUserId: userConatct.user.id,
actingUserId: userConatct.user.id, actingUserId: userConatct.user.id,
}), }),
@ -943,7 +943,7 @@ describe('UserResolver', () => {
it('stores the LOGIN event in the database', async () => { it('stores the LOGIN event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.LOGIN, type: EventType.LOGIN,
affectedUserId: user[0].id, affectedUserId: user[0].id,
actingUserId: user[0].id, actingUserId: user[0].id,
}), }),
@ -1863,7 +1863,7 @@ describe('UserResolver', () => {
) )
await expect(DbEvent.find()).resolves.toContainEqual( await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({ expect.objectContaining({
type: EventProtocolType.ADMIN_SEND_CONFIRMATION_EMAIL, type: EventType.ADMIN_SEND_CONFIRMATION_EMAIL,
affectedUserId: userConatct.user.id, affectedUserId: userConatct.user.id,
actingUserId: admin.id, actingUserId: admin.id,
}), }),

View File

@ -56,6 +56,7 @@ import { RIGHTS } from '@/auth/RIGHTS'
import { hasElopageBuys } from '@/util/hasElopageBuys' import { hasElopageBuys } from '@/util/hasElopageBuys'
import { import {
Event, Event,
EventType,
EVENT_LOGIN, EVENT_LOGIN,
EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL, EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL,
EVENT_SEND_CONFIRMATION_EMAIL, EVENT_SEND_CONFIRMATION_EMAIL,
@ -69,7 +70,6 @@ import { FULL_CREATION_AVAILABLE } from './const/const'
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor' import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
import { PasswordEncryptionType } from '../enum/PasswordEncryptionType' import { PasswordEncryptionType } from '../enum/PasswordEncryptionType'
import LogError from '@/server/LogError' import LogError from '@/server/LogError'
import { EventProtocolType } from '@/event/EventProtocolType'
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const sodium = require('sodium-native') const sodium = require('sodium-native')
@ -273,7 +273,7 @@ export class UserResolver {
const gradidoID = await newGradidoID() const gradidoID = await newGradidoID()
const eventRegisterRedeem = Event( const eventRegisterRedeem = Event(
EventProtocolType.REDEEM_REGISTER, EventType.REDEEM_REGISTER,
{ id: 0 } as DbUser, { id: 0 } as DbUser,
{ id: 0 } as DbUser, { id: 0 } as DbUser,
) )

View File

@ -3,21 +3,30 @@ import { Order } from '@enum/Order'
import { Contribution as DbContribution } from '@entity/Contribution' import { Contribution as DbContribution } from '@entity/Contribution'
import { In } from '@dbTools/typeorm' import { In } from '@dbTools/typeorm'
interface FindContributionsOptions {
order: Order
currentPage: number
pageSize: number
withDeleted?: boolean
relations?: string[]
userId?: number | null
statusFilter?: ContributionStatus[] | null
}
export const findContributions = async ( export const findContributions = async (
order: Order, options: FindContributionsOptions,
currentPage: number, ): Promise<[DbContribution[], number]> => {
pageSize: number, const { order, currentPage, pageSize, withDeleted, relations, userId, statusFilter } = {
withDeleted: boolean, withDeleted: false,
relations: string[], relations: [],
userId?: number, ...options,
statusFilter?: ContributionStatus[] | null, }
): Promise<[DbContribution[], number]> => return DbContribution.findAndCount({
DbContribution.findAndCount({
where: { where: {
...(statusFilter && statusFilter.length && { contributionStatus: In(statusFilter) }), ...(statusFilter && statusFilter.length && { contributionStatus: In(statusFilter) }),
...(userId && { userId }), ...(userId && { userId }),
}, },
withDeleted: withDeleted, withDeleted,
order: { order: {
createdAt: order, createdAt: order,
id: order, id: order,
@ -26,3 +35,4 @@ export const findContributions = async (
skip: (currentPage - 1) * pageSize, skip: (currentPage - 1) * pageSize,
take: pageSize, take: pageSize,
}) })
}

View File

@ -133,6 +133,22 @@ export const communities = gql`
} }
` `
export const getCommunities = gql`
query {
getCommunities {
id
foreign
publicKey
url
lastAnnouncedAt
verifiedAt
lastErrorAt
createdAt
updatedAt
}
}
`
export const queryTransactionLink = gql` export const queryTransactionLink = gql`
query ($code: String!) { query ($code: String!) {
queryTransactionLink(code: $code) { queryTransactionLink(code: $code) {
@ -204,18 +220,20 @@ query ($currentPage: Int = 1, $pageSize: Int = 5, $order: Order = DESC, $statusF
` `
// from admin interface // from admin interface
export const adminListAllContributions = gql` export const adminListContributions = gql`
query ( query (
$currentPage: Int = 1 $currentPage: Int = 1
$pageSize: Int = 25 $pageSize: Int = 25
$order: Order = DESC $order: Order = DESC
$statusFilter: [ContributionStatus!] $statusFilter: [ContributionStatus!]
$userId: Int
) { ) {
adminListAllContributions( adminListContributions(
currentPage: $currentPage currentPage: $currentPage
pageSize: $pageSize pageSize: $pageSize
order: $order order: $order
statusFilter: $statusFilter statusFilter: $statusFilter
userId: $userId
) { ) {
contributionCount contributionCount
contributionList { contributionList {

View File

@ -25,13 +25,13 @@ export class Community extends BaseEntity {
endPoint: string endPoint: string
@Column({ name: 'last_announced_at', type: 'datetime', nullable: true }) @Column({ name: 'last_announced_at', type: 'datetime', nullable: true })
lastAnnouncedAt: Date lastAnnouncedAt: Date | null
@Column({ name: 'verified_at', type: 'datetime', nullable: true }) @Column({ name: 'verified_at', type: 'datetime', nullable: true })
verifiedAt: Date verifiedAt: Date | null
@Column({ name: 'last_error_at', type: 'datetime', nullable: true }) @Column({ name: 'last_error_at', type: 'datetime', nullable: true })
lastErrorAt: Date lastErrorAt: Date | null
@CreateDateColumn({ @CreateDateColumn({
name: 'created_at', name: 'created_at',

View File

@ -0,0 +1,20 @@
/* MIGRATION TO RENAME CONTRIBUTION_CONFIRM EVENT
*
* This migration renames the CONTRIBUTION_CONFIRM Event
* to ADMIN_CONTRIBUTION_CONFIRM
*/
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
'UPDATE `events` SET `type` = "ADMIN_CONTRIBUTION_CONFIRM" WHERE `type` = "CONTRIBUTION_CONFIRM";',
)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
'UPDATE `events` SET `type` = "CONTRIBUTION_CONFIRM" WHERE `type` = "ADMIN_CONTRIBUTION_CONFIRM";',
)
}

View File

@ -3,7 +3,7 @@ import dotenv from 'dotenv'
dotenv.config() dotenv.config()
const constants = { const constants = {
DB_VERSION: '0061-event_refactoring', DB_VERSION: '0062-event_contribution_confirm',
LOG4JS_CONFIG: 'log4js-config.json', LOG4JS_CONFIG: 'log4js-config.json',
// default log level on production should be info // default log level on production should be info
LOG_LEVEL: process.env.LOG_LEVEL || 'info', LOG_LEVEL: process.env.LOG_LEVEL || 'info',

View File

@ -4,29 +4,6 @@ This document contains the concept and technical details for the *federation* of
But meanwhile the usage of a DHT like HyperSwarm promises more coverage of the gradido requirements out of the box. More details about HyperSwarm can be found here [@hyperswarm/dht](https://github.com/hyperswarm/dht). But meanwhile the usage of a DHT like HyperSwarm promises more coverage of the gradido requirements out of the box. More details about HyperSwarm can be found here [@hyperswarm/dht](https://github.com/hyperswarm/dht).
## ActivityPub (deprecated)
The activity pub defines a server-to-server federation protocol to share information between decentralized instances and will be the main komponent for the gradido community federation.
At first we asume a *gradido community* as an *ActivityPub user*. A user is represented by "*actors*" via the users's accounts on servers. User's accounts on different servers corrsponds to different actors, which means community accounts on different servers corrsponds to different communities.
Every community (actor) has an:
* inbox: to get messages from the world
* outbox: to send messages to others
and are simple endpoints or just URLs, which are described in the *ActivityStream* of each *ActivityPub community*.
### Open Decision:
It has to be decided, if the Federation will work with an internal or with external ActivityPub-Server, as shown in the picture below:
![FederationActivityPub](./image/FederationActivityPub.png " ")
The Variant A with an internal server contains the benefit to be as independent as possible from third party service providers and will not cause additional hosting costs. But this solution will cause the additional efforts of impementing an ActivityPub-Server in the gradido application and the responsibility for this component.
The Varaint B with an external server contains the benefit to reduce the implementation efforts and the responsibility for an own ActivitPub-Server. But it will cause an additional dependency to a third party service provider and the growing hosting costs.
## HyperSwarm ## HyperSwarm
The decision to switch from ActivityPub to HyperSwarm base on the arguments, that the *hyperswarm/dht* library will satify the most federation requirements out of the box. It is now to design the business requirements of the [gradido community communication](../BusinessRequirements/CommunityVerwaltung.md#UC-createCommunity) in a technical conception. The decision to switch from ActivityPub to HyperSwarm base on the arguments, that the *hyperswarm/dht* library will satify the most federation requirements out of the box. It is now to design the business requirements of the [gradido community communication](../BusinessRequirements/CommunityVerwaltung.md#UC-createCommunity) in a technical conception.
@ -41,12 +18,30 @@ To enable such a relationship between an existing community and a new community
2. Authentication 2. Authentication
3. Autorized Communication 3. Autorized Communication
### Overview ### Overview of Federation-Handshake
At first the following diagramm gives an overview of the three stages and shows the handshake between an existing community-A and a new created community-B including the data exchange for buildup such a federated, authenticated and autorized relationship. At first the following diagramm gives an overview of the three stages and shows the handshake between an existing community-A and a new created community-B including the data exchange for buildup such a federated, authenticated and autorized relationship.
![FederationHyperSwarm.png](./image/FederationHyperSwarm.png) ![FederationHyperSwarm.png](./image/FederationHyperSwarm.png)
### Technical Architecture
The previous described handshake will be done by several technical modules of the gradido system. The following picture gives an overview about the modules and how the communicate with each other.
![img](./image/TechnicalOverview_V1-19.svg)
As soon as a Gradido Community is up and running the DHT-Modul first writes the home-community-entries in the database and starts with the federation via HyperSwarm. Each community, which is configured with the configuration key GRADIDO_HUB to listen on the correct topic of the DHT will be part of the Gradido-Net-Federation. That means each DHT-Modul of each community will receive the publicKey of all connected communities. The DHT-Modul will open for each received publicKey a communication-socket with the associated community DHT-Modul. Over this open socket the connected communities exchange the data "api-version" and "url" for later direct communication between both communities. The exchanged api-version info and urls will be written in the own database.
The background of this exchanged data base on the supported api-versions a community will support with its own federation-modules. Each running federation-module in a community will support exact one graphql api-version of a cross-community-communication. To reach a dedicated federation-module with the correct api-version during a cross-community-communication the url for this federation-module must be known by both communities. As shown in the picture above the graphql-client with api-version V1_0 in the left community will interact with the federation-module with api-version V1_0 on the right community. During the lifecycle of the gradido-application it will be necessary to extent the features and interfaces for the cross-community-communication. To keep a backwards compatibilty and not to force each community to always upgrade their running software version on the last api-version at the same time, it will be necessary to support several api-versions in parallel. The different running api-version modules are responsible to convert and treat the exchanged data in a correct way to ensure konsistent data in the local database of the community.
The up and running Backend-Module contains a validation logic to verify the community entries from the own DHT-Module. For each announced but unverified community-entry the GraphQL-Client is used to invoke a getPublicKey-Request. Depending on the containing api-version the matching GraphQL-Client is used and the getPublicKey-Request will be send to the given URL.
As soon as the federation-module of the associated community received the getPublicKey-request the own publicKey is read from database and send back in the response.
The GraphQL-Client will read the publicKey of the other community from the returned response data and compare it with the data of the community-entry, which caused the getPublicKey-Request. If they match the community-entry will be updated by inserting the current timestamp in the verifiedAt-field of this community-entry.
This federation and verification logic will work the whole time and can be monitored by observing the communities-table changes. The Admin-UI will contain a Page to have a look on the current state of the communities table content.
### Prerequisits ### Prerequisits
Before starting in describing the details of the federation handshake, some prerequisits have to be defined. Before starting in describing the details of the federation handshake, some prerequisits have to be defined.
@ -235,7 +230,6 @@ For the first federation release the *DHT-Node* will be part of the *apollo serv
| communityApiVersion.apiversion | keep existing value | | communityApiVersion.apiversion | keep existing value |
| communityApiVersion.validFrom | exchangedData.API.validFrom | | communityApiVersion.validFrom | exchangedData.API.validFrom |
| communityApiVersion.verifiedAt | keep existing value | | communityApiVersion.verifiedAt | keep existing value |
*
3. After all received data is stored successfully, the *DHT-Node* starts the *stage2 - Authentication* of the federation handshake 3. After all received data is stored successfully, the *DHT-Node* starts the *stage2 - Authentication* of the federation handshake
### Stage2 - Authentication ### Stage2 - Authentication
@ -284,8 +278,6 @@ As soon the *openConnection* request is invoked:
3. check if the decrypted `parameter.signedAndEncryptedURL` is equals the selected url from the previous selected CommunityFederationEntry 3. check if the decrypted `parameter.signedAndEncryptedURL` is equals the selected url from the previous selected CommunityFederationEntry
1. if not then break the further processing of this request by only writing an error-log event. There will be no answer to the invoker community, because this community will only go on with a `openConnectionRedirect`-request from this community. 1. if not then break the further processing of this request by only writing an error-log event. There will be no answer to the invoker community, because this community will only go on with a `openConnectionRedirect`-request from this community.
2. if yes then verify the signature of `parameter.signedAndEncryptedURL` with the `cf.pubKey` read in step 2 before 2. if yes then verify the signature of `parameter.signedAndEncryptedURL` with the `cf.pubKey` read in step 2 before
3.
4.
### Stage3 - Autorized Business Communication ### Stage3 - Autorized Business Communication

View File

@ -0,0 +1,282 @@
<mxfile host="65bd71144e">
<diagram id="RqE3izjX3TYt3HTUOB95" name="Seite-1">
<mxGraphModel dx="3343" dy="773" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="2336" pageHeight="1654" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="Community&amp;nbsp; &quot;Gradido-Akademie&quot;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;verticalAlign=top;fontSize=16;fontStyle=1;gradientColor=#7ea6e0;" parent="1" vertex="1">
<mxGeometry x="-158.26" y="80" width="870" height="800" as="geometry"/>
</mxCell>
<mxCell id="3" value="Gradido - technical Infrastructure-Overview&lt;br&gt;&lt;font style=&quot;font-size: 12px&quot;&gt;State of 02.2023&lt;/font&gt;" style="text;html=1;strokeColor=#82b366;fillColor=#d5e8d4;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;labelBorderColor=none;gradientColor=#97d077;" parent="1" vertex="1">
<mxGeometry x="-1160" y="20" width="1880" height="40" as="geometry"/>
</mxCell>
<mxCell id="4" value="&lt;b&gt;Backend-Modul&lt;/b&gt;&lt;br&gt;GraphQL-API" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="-118.25999999999999" y="269" width="540" height="101" as="geometry"/>
</mxCell>
<mxCell id="7" value="CommunityServer DB" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=15;fontStyle=1;gradientColor=#7ea6e0;" parent="1" vertex="1">
<mxGeometry x="211.74" y="760" width="150" height="80" as="geometry"/>
</mxCell>
<mxCell id="8" value="" style="endArrow=classic;startArrow=classic;html=1;fontSize=15;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;fillColor=#dae8fc;gradientColor=#7ea6e0;strokeColor=#6c8ebf;" parent="1" source="7" target="4" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="191.74" y="590" as="sourcePoint"/>
<mxPoint x="241.74" y="540" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="15" value="&lt;b&gt;Layer 1:&lt;/b&gt;" style="text;html=1;strokeColor=#6c8ebf;fillColor=#dae8fc;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;glass=0;labelBackgroundColor=none;fontSize=15;opacity=0;gradientColor=#7ea6e0;" parent="1" vertex="1">
<mxGeometry x="-148.26" y="735" width="70" height="20" as="geometry"/>
</mxCell>
<mxCell id="16" value="" style="endArrow=none;dashed=1;html=1;fontSize=15;fontColor=#000000;entryX=1.002;entryY=0.801;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" target="2" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="-158.26" y="720" as="sourcePoint"/>
<mxPoint x="603.74" y="720" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="17" value="&lt;b&gt;Layer 2:&lt;/b&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;glass=0;labelBackgroundColor=none;fontSize=15;fontColor=#000000;opacity=0;" parent="1" vertex="1">
<mxGeometry x="-148.26" y="240" width="70" height="20" as="geometry"/>
</mxCell>
<mxCell id="21" value="&quot;&lt;b&gt;GDT-Server&lt;/b&gt;&quot; &lt;br&gt;base on cakephp + mySQL" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#fff2cc;strokeColor=#d6b656;gradientColor=#ffd966;" parent="1" vertex="1">
<mxGeometry x="491.74" y="269" width="210" height="60" as="geometry"/>
</mxCell>
<mxCell id="22" value="GDT-Server DB" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=15;fontStyle=1;gradientColor=#ffd966;" parent="1" vertex="1">
<mxGeometry x="521.74" y="760" width="150" height="80" as="geometry"/>
</mxCell>
<mxCell id="23" value="" style="endArrow=classic;startArrow=classic;html=1;fontSize=15;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="22" target="21" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="381.74" y="590" as="sourcePoint"/>
<mxPoint x="586.6200000000001" y="370" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="24" value="" style="endArrow=classic;startArrow=classic;html=1;fontSize=15;fontColor=#000000;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" target="21" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="421.74" y="299" as="sourcePoint"/>
<mxPoint x="401.74" y="479" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="25" value="&lt;font style=&quot;font-size: 12px&quot;&gt;json-&lt;br&gt;ajax-&lt;br&gt;request&lt;/font&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=15;fontColor=#000000;labelBackgroundColor=none;" parent="24" vertex="1" connectable="0">
<mxGeometry x="-0.3429" relative="1" as="geometry">
<mxPoint x="10" y="-28" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="30" value="" style="endArrow=none;dashed=1;html=1;fontSize=15;fontColor=#000000;entryX=1.002;entryY=0.401;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0;exitY=0.4;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="-159.9999999999999" y="230" as="sourcePoint"/>
<mxPoint x="711.7399999999999" y="230.79999999999995" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="31" value="&lt;b&gt;Layer 3:&lt;/b&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;glass=0;labelBackgroundColor=none;fontSize=15;fontColor=#000000;opacity=0;" parent="1" vertex="1">
<mxGeometry x="-148.26" y="90" width="70" height="20" as="geometry"/>
</mxCell>
<mxCell id="32" value="&quot;&lt;b&gt;Elopage&lt;/b&gt;&quot; &lt;br&gt;external Service-Portal" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="471.74" y="120" width="210" height="60" as="geometry"/>
</mxCell>
<mxCell id="33" value="&quot;&lt;b&gt;User-UI&lt;/b&gt;&quot;" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;" parent="1" vertex="1">
<mxGeometry x="-48.25999999999999" y="120" width="200" height="60" as="geometry"/>
</mxCell>
<mxCell id="34" value="" style="endArrow=classic;startArrow=classic;html=1;fontSize=15;fontColor=#000000;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.315;exitY=-0.05;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="4" target="33" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="351.74" y="420" as="sourcePoint"/>
<mxPoint x="401.74" y="370" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="35" value="" style="endArrow=classic;startArrow=classic;html=1;fontSize=15;fontColor=#000000;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.395;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="21" target="32" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="576.24" y="310" as="sourcePoint"/>
<mxPoint x="576.24" y="180" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="36" value="&lt;span style=&quot;font-size: 12px&quot;&gt;graphql&lt;/span&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=15;fontColor=#000000;labelBackgroundColor=none;" parent="1" vertex="1" connectable="0">
<mxGeometry x="191.74" y="216" as="geometry">
<mxPoint x="-169" y="-4" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="37" value="&lt;font style=&quot;font-size: 12px&quot;&gt;json-&lt;br&gt;request&lt;/font&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=15;fontColor=#000000;labelBackgroundColor=none;" parent="1" vertex="1" connectable="0">
<mxGeometry x="551.74" y="216" as="geometry">
<mxPoint x="-2" y="-15" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="39" style="edgeStyle=none;html=1;entryX=0.767;entryY=-0.017;entryDx=0;entryDy=0;startArrow=classic;startFill=1;entryPerimeter=0;" parent="1" source="38" target="4" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="40" value="graphql" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];labelBackgroundColor=none;" parent="39" vertex="1" connectable="0">
<mxGeometry x="-0.4305" y="1" relative="1" as="geometry">
<mxPoint x="23" y="-1" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="38" value="&quot;&lt;b&gt;Admin-UI&lt;/b&gt;&quot;" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;" parent="1" vertex="1">
<mxGeometry x="191.74" y="120" width="210" height="60" as="geometry"/>
</mxCell>
<mxCell id="42" style="edgeStyle=none;html=1;entryX=0.145;entryY=0;entryDx=0;entryDy=4.35;entryPerimeter=0;startArrow=classic;startFill=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;fillColor=#dae8fc;gradientColor=#7ea6e0;strokeColor=#6c8ebf;" parent="1" source="41" target="7" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="41" value="&lt;b&gt;DHT-Modul&lt;/b&gt;&lt;br&gt;HyperSwarm" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;" parent="1" vertex="1">
<mxGeometry x="-118.25999999999999" y="640" width="190" height="60" as="geometry"/>
</mxCell>
<mxCell id="102" style="edgeStyle=none;html=1;fontSize=16;startArrow=classic;startFill=1;fillColor=#dae8fc;gradientColor=#7ea6e0;strokeColor=#6c8ebf;" parent="1" source="43" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="260" y="760" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="43" value="&lt;b&gt;Federation-Modul&lt;/b&gt;&lt;br&gt;GraphQL-API V2_0" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;dashed=1;" parent="1" vertex="1">
<mxGeometry x="-118.25999999999999" y="380" width="190" height="60" as="geometry"/>
</mxCell>
<mxCell id="101" style="edgeStyle=none;html=1;fontSize=16;startArrow=classic;startFill=1;fillColor=#dae8fc;gradientColor=#7ea6e0;strokeColor=#6c8ebf;" parent="1" source="44" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="260" y="760" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="44" value="&lt;b&gt;Federation-Modul&lt;/b&gt;&lt;br&gt;GraphQL-API V1_x" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;dashed=1;" parent="1" vertex="1">
<mxGeometry x="-88.25999999999999" y="430" width="190" height="60" as="geometry"/>
</mxCell>
<mxCell id="100" style="edgeStyle=none;html=1;entryX=0.335;entryY=0.013;entryDx=0;entryDy=0;entryPerimeter=0;fontSize=16;startArrow=classic;startFill=1;exitX=0.78;exitY=0.667;exitDx=0;exitDy=0;exitPerimeter=0;fillColor=#dae8fc;gradientColor=#7ea6e0;strokeColor=#6c8ebf;" parent="1" source="45" target="7" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="45" value="&lt;b&gt;Federation-Modul&lt;/b&gt;&lt;br&gt;GraphQL-API V1_1" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;dashed=1;" parent="1" vertex="1">
<mxGeometry x="-58.25999999999999" y="480" width="190" height="60" as="geometry"/>
</mxCell>
<mxCell id="47" style="edgeStyle=none;html=1;entryX=0.333;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;startArrow=classic;startFill=1;exitX=0.996;exitY=0.633;exitDx=0;exitDy=0;exitPerimeter=0;fillColor=#dae8fc;gradientColor=#7ea6e0;strokeColor=#6c8ebf;" parent="1" source="46" target="7" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="46" value="&lt;b&gt;Federation-Modul&lt;/b&gt;&lt;br&gt;GraphQL-API V1_0" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;" parent="1" vertex="1">
<mxGeometry x="-28.25999999999999" y="530" width="190" height="60" as="geometry"/>
</mxCell>
<mxCell id="48" value="GraphQL-Client V1_0" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;" parent="1" vertex="1">
<mxGeometry x="-108.25999999999999" y="289" width="180" height="31" as="geometry"/>
</mxCell>
<mxCell id="49" value="GraphQL-Client V1_0" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;" parent="1" vertex="1">
<mxGeometry x="-98.25999999999999" y="299" width="180" height="31" as="geometry"/>
</mxCell>
<mxCell id="50" value="GraphQL-Client V1_0" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;" parent="1" vertex="1">
<mxGeometry x="-88.25999999999999" y="309" width="180" height="31" as="geometry"/>
</mxCell>
<mxCell id="51" value="GraphQL-Client V1_0" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;" parent="1" vertex="1">
<mxGeometry x="-78.25999999999999" y="319" width="180" height="31" as="geometry"/>
</mxCell>
<mxCell id="52" value="Community &quot;GallischesDorf-TBB&quot;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;verticalAlign=top;fontStyle=1;fontSize=16;gradientColor=#97d077;" parent="1" vertex="1">
<mxGeometry x="-1148.26" y="80" width="628.26" height="800" as="geometry"/>
</mxCell>
<mxCell id="54" value="&lt;b&gt;Backend-Modul&lt;/b&gt;&lt;br&gt;GraphQL-API" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#d5e8d4;strokeColor=#82b366;gradientColor=#97d077;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="-1108.26" y="269" width="568.26" height="101" as="geometry"/>
</mxCell>
<mxCell id="55" value="CommunityServer DB" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=15;fontStyle=1;gradientColor=#97d077;" parent="1" vertex="1">
<mxGeometry x="-1048.26" y="770" width="150" height="80" as="geometry"/>
</mxCell>
<mxCell id="56" value="" style="endArrow=classic;startArrow=classic;html=1;fontSize=15;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;" parent="1" source="55" target="54" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="-798.26" y="590" as="sourcePoint"/>
<mxPoint x="-748.26" y="540" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="57" value="&lt;b&gt;Layer 1:&lt;/b&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;glass=0;labelBackgroundColor=none;fontSize=15;fontColor=#000000;opacity=0;" parent="1" vertex="1">
<mxGeometry x="-1138.26" y="735" width="70" height="20" as="geometry"/>
</mxCell>
<mxCell id="58" value="" style="endArrow=none;dashed=1;html=1;fontSize=15;fontColor=#000000;entryX=1.002;entryY=0.801;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" target="52" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="-1148.26" y="720" as="sourcePoint"/>
<mxPoint x="-386.26" y="720" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="59" value="&lt;b&gt;Layer 2:&lt;/b&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;glass=0;labelBackgroundColor=none;fontSize=15;fontColor=#000000;opacity=0;" parent="1" vertex="1">
<mxGeometry x="-1138.26" y="240" width="70" height="20" as="geometry"/>
</mxCell>
<mxCell id="65" value="" style="endArrow=none;dashed=1;html=1;fontSize=15;fontColor=#000000;exitX=0;exitY=0.4;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="-1149.9999999999998" y="230" as="sourcePoint"/>
<mxPoint x="-520" y="231" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="66" value="&lt;b&gt;Layer 3:&lt;/b&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;glass=0;labelBackgroundColor=none;fontSize=15;fontColor=#000000;opacity=0;" parent="1" vertex="1">
<mxGeometry x="-1138.26" y="90" width="70" height="20" as="geometry"/>
</mxCell>
<mxCell id="68" value="&quot;&lt;b&gt;User-UI&lt;/b&gt;&quot;" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#d5e8d4;strokeColor=#82b366;gradientColor=#97d077;" parent="1" vertex="1">
<mxGeometry x="-1030" y="120" width="200" height="60" as="geometry"/>
</mxCell>
<mxCell id="69" value="" style="endArrow=classic;startArrow=classic;html=1;fontSize=15;fontColor=#000000;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.315;exitY=-0.05;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="54" target="68" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="-638.26" y="420" as="sourcePoint"/>
<mxPoint x="-588.26" y="370" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="71" value="&lt;span style=&quot;font-size: 12px&quot;&gt;graphql&lt;/span&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=15;fontColor=#000000;labelBackgroundColor=none;" parent="1" vertex="1" connectable="0">
<mxGeometry x="-798.26" y="216" as="geometry">
<mxPoint x="-169" y="-4" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="73" style="edgeStyle=none;html=1;entryX=0.767;entryY=-0.017;entryDx=0;entryDy=0;startArrow=classic;startFill=1;entryPerimeter=0;" parent="1" source="75" target="54" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="74" value="graphql" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];labelBackgroundColor=none;" parent="73" vertex="1" connectable="0">
<mxGeometry x="-0.4305" y="1" relative="1" as="geometry">
<mxPoint x="23" y="-1" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="75" value="&quot;&lt;b&gt;Admin-UI&lt;/b&gt;&quot;" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#d5e8d4;strokeColor=#82b366;gradientColor=#97d077;" parent="1" vertex="1">
<mxGeometry x="-775" y="120" width="210" height="60" as="geometry"/>
</mxCell>
<mxCell id="76" style="edgeStyle=none;html=1;entryX=0.855;entryY=0;entryDx=0;entryDy=4.35;entryPerimeter=0;startArrow=classic;startFill=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;" parent="1" source="77" target="55" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="90" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;" parent="1" source="77" target="41" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="91" value="&amp;nbsp;DHT-Socket Communication&amp;nbsp;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=16;" parent="90" vertex="1" connectable="0">
<mxGeometry x="-0.1307" y="-1" relative="1" as="geometry">
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="77" value="&lt;b&gt;DHT-Modul&lt;/b&gt;&lt;br&gt;HyperSwarm" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#d5e8d4;strokeColor=#82b366;gradientColor=#97d077;" parent="1" vertex="1">
<mxGeometry x="-745" y="640" width="190" height="60" as="geometry"/>
</mxCell>
<mxCell id="87" style="edgeStyle=none;html=1;entryX=0.655;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;startArrow=classic;startFill=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;" parent="1" source="80" target="55" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="80" value="&lt;b&gt;Federation-Modul&lt;/b&gt;&lt;br&gt;GraphQL-API V1_1" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#d5e8d4;strokeColor=#82b366;gradientColor=#97d077;dashed=1;" parent="1" vertex="1">
<mxGeometry x="-760" y="500" width="190" height="60" as="geometry"/>
</mxCell>
<mxCell id="81" style="edgeStyle=none;html=1;entryX=0.655;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;startArrow=classic;startFill=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;" parent="1" source="82" target="55" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="82" value="&lt;b&gt;Federation-Modul&lt;/b&gt;&lt;br&gt;GraphQL-API V1_0" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#d5e8d4;strokeColor=#82b366;gradientColor=#97d077;" parent="1" vertex="1">
<mxGeometry x="-730" y="550" width="190" height="60" as="geometry"/>
</mxCell>
<mxCell id="98" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fontSize=16;startArrow=classic;startFill=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="85" target="45" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="99" value="graphQL-Handshake" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=16;" parent="98" vertex="1" connectable="0">
<mxGeometry x="-0.5775" relative="1" as="geometry">
<mxPoint x="28" y="-14" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="85" value="GraphQL-Client V1_1" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#d5e8d4;strokeColor=#82b366;gradientColor=#97d077;" parent="1" vertex="1">
<mxGeometry x="-745" y="289" width="180" height="31" as="geometry"/>
</mxCell>
<mxCell id="96" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fontSize=16;startArrow=classic;startFill=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="86" target="46" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="97" value="graphQL-Handshake" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=16;" parent="96" vertex="1" connectable="0">
<mxGeometry x="-0.5322" y="-1" relative="1" as="geometry">
<mxPoint x="-11" y="16" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="86" value="GraphQL-Client V1_0" style="rounded=1;whiteSpace=wrap;html=1;fontSize=16;align=center;fillColor=#d5e8d4;strokeColor=#82b366;gradientColor=#97d077;" parent="1" vertex="1">
<mxGeometry x="-730" y="314" width="180" height="31" as="geometry"/>
</mxCell>
<mxCell id="92" style="edgeStyle=none;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;fontSize=16;startArrow=classic;startFill=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="51" target="82" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="94" value="graphQL-Handshake" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=16;" parent="92" vertex="1" connectable="0">
<mxGeometry x="0.1267" y="2" relative="1" as="geometry">
<mxPoint x="-93" y="56" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="93" style="edgeStyle=none;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;fontSize=16;startArrow=classic;startFill=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="50" target="80" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="95" value="graphQL-Handshake" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=16;" parent="93" vertex="1" connectable="0">
<mxGeometry x="-0.0706" relative="1" as="geometry">
<mxPoint x="63" y="-60" as="offset"/>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -11,7 +11,7 @@ Decimal.set({
*/ */
const constants = { const constants = {
DB_VERSION: '0061-event_refactoring', DB_VERSION: '0062-event_contribution_confirm',
// DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 // DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
LOG4JS_CONFIG: 'log4js-config.json', LOG4JS_CONFIG: 'log4js-config.json',
// default log level on production should be info // default log level on production should be info

View File

@ -0,0 +1,53 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { createTestClient } from 'apollo-server-testing'
import createServer from '@/server/createServer'
import { Community as DbCommunity } from '@entity/Community'
let query: any
// to do: We need a setup for the tests that closes the connection
let con: any
beforeAll(async () => {
const server = await createServer()
con = server.con
query = createTestClient(server.apollo).query
DbCommunity.clear()
})
afterAll(async () => {
await con.close()
})
describe('PublicKeyResolver', () => {
const getPublicKeyQuery = `
query {
getPublicKey
{
publicKey
}
}
`
describe('getPublicKey', () => {
beforeEach(async () => {
const homeCom = new DbCommunity()
homeCom.foreign = false
homeCom.apiVersion = '1_0'
homeCom.endPoint = 'endpoint-url'
homeCom.publicKey = Buffer.from('homeCommunity-publicKey')
await DbCommunity.insert(homeCom)
})
it('returns homeCommunity-publicKey', async () => {
await expect(query({ query: getPublicKeyQuery })).resolves.toMatchObject({
data: {
getPublicKey: {
publicKey: expect.stringMatching('homeCommunity-publicKey'),
},
},
})
})
})
})

View File

@ -9,12 +9,12 @@ import { GetPublicKeyResult } from '../model/GetPublicKeyResult'
export class PublicKeyResolver { export class PublicKeyResolver {
@Query(() => GetPublicKeyResult) @Query(() => GetPublicKeyResult)
async getPublicKey(): Promise<GetPublicKeyResult> { async getPublicKey(): Promise<GetPublicKeyResult> {
logger.info(`getPublicKey()...`) logger.debug(`getPublicKey() via apiVersion=1_0 ...`)
const homeCom = await DbCommunity.findOneOrFail({ const homeCom = await DbCommunity.findOneOrFail({
foreign: false, foreign: false,
apiVersion: '1_0', apiVersion: '1_0',
}) })
logger.info(`getPublicKey()... with publicKey=${homeCom.publicKey}`) logger.info(`getPublicKey()-1_0... return publicKey=${homeCom.publicKey}`)
return new GetPublicKeyResult(homeCom.publicKey.toString()) return new GetPublicKeyResult(homeCom.publicKey.toString())
} }
} }

View File

@ -7,7 +7,8 @@
"author": "Ulf Gebhardt <ulf.gebhardt@webcraft-media.de>", "author": "Ulf Gebhardt <ulf.gebhardt@webcraft-media.de>",
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": { "scripts": {
"release": "scripts/release.sh" "release": "scripts/release.sh",
"installAll": "yarn && cd database && yarn && cd ../frontend && yarn && cd ../admin && yarn && cd ../backend && yarn && cd ../e2e-tests && yarn && cd ../federation && yarn && cd ../dht-node && yarn && cd .."
}, },
"dependencies": { "dependencies": {
"auto-changelog": "^2.4.0", "auto-changelog": "^2.4.0",