Merge branch 'master' into 2481-fix-admin-no-confirmation-when-changing-user-role

This commit is contained in:
mahula 2023-03-23 21:28:00 +01:00 committed by GitHub
commit 3d1eb2b939
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 1189 additions and 542 deletions

View File

@ -40,4 +40,7 @@ federation: &federation
- '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,55 +80,53 @@ 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()
}) })
it('sends query to Apollo when created', () => { describe('server error', () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({
variables: {
currentPage: 1,
pageSize: 10,
order: 'DESC',
userId: 1,
},
}),
)
})
it('has two values for the transaction', () => {
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', () => { it('toast error', () => {
expect(toastErrorSpy).toBeCalledWith('OUCH!') expect(toastErrorSpy).toBeCalledWith('Ouch!')
}) })
}) })
describe('watch currentPage', () => { describe('sever success', () => {
beforeEach(async () => { it('sends query to Apollo when created', () => {
jest.clearAllMocks() expect(adminListContributionsMock).toBeCalledWith({
await wrapper.setData({ currentPage: 2 }) currentPage: 1,
pageSize: 10,
order: 'DESC',
userId: 1,
})
}) })
it('returns the string in normal order if reversed property is not true', () => { it('has two values for the transaction', () => {
expect(wrapper.vm.currentPage).toBe(2) expect(wrapper.find('tbody').findAll('tr').length).toBe(2)
})
describe('watch currentPage', () => {
beforeEach(async () => {
jest.clearAllMocks()
await wrapper.setData({ currentPage: 2 })
})
it('returns the string in normal order if reversed property is not true', () => {
expect(wrapper.vm.currentPage).toBe(2)
})
}) })
}) })
}) })

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() {
currentPage: this.currentPage, return {
pageSize: this.perPage, currentPage: this.currentPage,
order: 'DESC', pageSize: this.perPage,
userId: parseInt(this.userId), order: 'DESC',
}, userId: parseInt(this.userId),
}) }
.then((result) => { },
this.rows = result.data.creationTransactionList.contributionCount update({ adminListContributions }) {
this.items = result.data.creationTransactionList.contributionList this.rows = adminListContributions.contributionCount
}) this.items = adminListContributions.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

@ -43,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: '0063-event_link_fields',
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,24 @@
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,
null,
null,
amount,
).save()

View File

@ -0,0 +1,24 @@
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,
null,
null,
amount,
).save()

View File

@ -0,0 +1,24 @@
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,
null,
null,
amount,
).save()

View File

@ -0,0 +1,24 @@
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,
null,
null,
amount,
).save()

View File

@ -0,0 +1,23 @@
import Decimal from 'decimal.js-light'
import { User as DbUser } from '@entity/User'
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { Event as DbEvent } from '@entity/Event'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_LINK_CREATE = async (
moderator: DbUser,
contributionLink: DbContributionLink,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_LINK_CREATE,
{ id: 0 } as DbUser,
moderator,
null,
null,
null,
null,
null,
contributionLink,
amount,
).save()

View File

@ -0,0 +1,20 @@
import { User as DbUser } from '@entity/User'
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { Event as DbEvent } from '@entity/Event'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_LINK_DELETE = async (
moderator: DbUser,
contributionLink: DbContributionLink,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_LINK_DELETE,
{ id: 0 } as DbUser,
moderator,
null,
null,
null,
null,
null,
contributionLink,
).save()

View File

@ -0,0 +1,23 @@
import Decimal from 'decimal.js-light'
import { User as DbUser } from '@entity/User'
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { Event as DbEvent } from '@entity/Event'
import { Event, EventType } from './Event'
export const EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE = async (
moderator: DbUser,
contributionLink: DbContributionLink,
amount: Decimal,
): Promise<DbEvent> =>
Event(
EventType.ADMIN_CONTRIBUTION_LINK_UPDATE,
{ id: 0 } as DbUser,
moderator,
null,
null,
null,
null,
null,
contributionLink,
amount,
).save()

View File

@ -0,0 +1,24 @@
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,
null,
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,23 @@
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,
null,
null,
amount,
).save()

View File

@ -0,0 +1,23 @@
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,
null,
null,
amount,
).save()

View File

@ -0,0 +1,23 @@
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,
null,
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,24 @@
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,
null,
null,
amount,
).save()

View File

@ -0,0 +1,24 @@
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,
null,
null,
amount,
).save()

View File

@ -1,19 +1,23 @@
import { Event as DbEvent } from '@entity/Event' import { Event as DbEvent } from '@entity/Event'
import { User as DbUser } from '@entity/User' import { User as DbUser } from '@entity/User'
import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage'
import { Contribution as DbContribution } from '@entity/Contribution'
import { Transaction as DbTransaction } from '@entity/Transaction' import { Transaction as DbTransaction } from '@entity/Transaction'
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { Contribution as DbContribution } from '@entity/Contribution'
import { ContributionMessage as DbContributionMessage } from '@entity/ContributionMessage'
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
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,
involvedTransaction: DbTransaction | null = null, involvedTransaction: DbTransaction | null = null,
involvedContribution: DbContribution | null = null, involvedContribution: DbContribution | null = null,
involvedContributionMessage: DbContributionMessage | null = null, involvedContributionMessage: DbContributionMessage | null = null,
involvedTransactionLink: DbTransactionLink | null = null,
involvedContributionLink: DbContributionLink | null = null,
amount: Decimal | null = null, amount: Decimal | null = null,
): DbEvent => { ): DbEvent => {
const event = new DbEvent() const event = new DbEvent()
@ -24,194 +28,30 @@ export const Event = (
event.involvedTransaction = involvedTransaction event.involvedTransaction = involvedTransaction
event.involvedContribution = involvedContribution event.involvedContribution = involvedContribution
event.involvedContributionMessage = involvedContributionMessage event.involvedContributionMessage = involvedContributionMessage
event.involvedTransactionLink = involvedTransactionLink
event.involvedContributionLink = involvedContributionLink
event.amount = amount event.amount = amount
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_CONTRIBUTION_LINK_CREATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_CREATE'
user, export { EVENT_ADMIN_CONTRIBUTION_LINK_DELETE } from './EVENT_ADMIN_CONTRIBUTION_LINK_DELETE'
user, export { EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE } from './EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE'
null, export { EVENT_ADMIN_SEND_CONFIRMATION_EMAIL } from './EVENT_ADMIN_SEND_CONFIRMATION_EMAIL'
null, export { EVENT_CONTRIBUTION_CREATE } from './EVENT_CONTRIBUTION_CREATE'
contribution, export { EVENT_CONTRIBUTION_DELETE } from './EVENT_CONTRIBUTION_DELETE'
null, export { EVENT_CONTRIBUTION_UPDATE } from './EVENT_CONTRIBUTION_UPDATE'
amount, export { EVENT_LOGIN } from './EVENT_LOGIN'
).save() export { EVENT_REGISTER } from './EVENT_REGISTER'
export { EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL } from './EVENT_SEND_ACCOUNT_MULTIREGISTRATION_EMAIL'
export const EVENT_CONTRIBUTION_UPDATE = async ( export { EVENT_SEND_CONFIRMATION_EMAIL } from './EVENT_SEND_CONFIRMATION_EMAIL'
user: DbUser, export { EVENT_TRANSACTION_SEND } from './EVENT_TRANSACTION_SEND'
contribution: DbContribution, export { EVENT_TRANSACTION_RECEIVE } from './EVENT_TRANSACTION_RECEIVE'
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,50 +1,47 @@
export enum EventProtocolType { export enum EventType {
// VISIT_GRADIDO = 'VISIT_GRADIDO',
REGISTER = 'REGISTER',
REDEEM_REGISTER = 'REDEEM_REGISTER',
// VERIFY_REDEEM = 'VERIFY_REDEEM',
// INACTIVE_ACCOUNT = 'INACTIVE_ACCOUNT',
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_CONTRIBUTION_LINK_CREATE = 'ADMIN_CONTRIBUTION_LINK_CREATE',
ADMIN_CONTRIBUTION_LINK_DELETE = 'ADMIN_CONTRIBUTION_LINK_DELETE',
ADMIN_CONTRIBUTION_LINK_UPDATE = 'ADMIN_CONTRIBUTION_LINK_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_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',
// UNDELETE_USER = 'UNDELETE_USER', // UNDELETE_USER = 'UNDELETE_USER',
// CHANGE_USER_ROLE = 'CHANGE_USER_ROLE', // CHANGE_USER_ROLE = 'CHANGE_USER_ROLE',
// ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION',
// ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION',
// CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK',
// DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK',
// UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK',
} }

View File

@ -19,6 +19,8 @@ import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { peterLustig } from '@/seeds/users/peter-lustig' import { peterLustig } from '@/seeds/users/peter-lustig'
import { userFactory } from '@/seeds/factory/user' import { userFactory } from '@/seeds/factory/user'
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { EventType } from '@/event/Event'
import { Event as DbEvent } from '@entity/Event'
let mutate: any, query: any, con: any let mutate: any, query: any, con: any
let testEnv: any let testEnv: any
@ -249,6 +251,18 @@ describe('Contribution Links', () => {
) )
}) })
it('stores the ADMIN_CONTRIBUTION_LINK_CREATE event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({
type: EventType.ADMIN_CONTRIBUTION_LINK_CREATE,
affectedUserId: 0,
actingUserId: expect.any(Number),
involvedContributionLinkId: expect.any(Number),
amount: expect.decimalEqual(200),
}),
)
})
it('returns an error if missing startDate', async () => { it('returns an error if missing startDate', async () => {
jest.clearAllMocks() jest.clearAllMocks()
await expect( await expect(
@ -531,6 +545,18 @@ describe('Contribution Links', () => {
}), }),
) )
}) })
it('stores the ADMIN_CONTRIBUTION_LINK_UPDATE event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({
type: EventType.ADMIN_CONTRIBUTION_LINK_UPDATE,
affectedUserId: 0,
actingUserId: expect.any(Number),
involvedContributionLinkId: expect.any(Number),
amount: expect.decimalEqual(400),
}),
)
})
}) })
}) })
@ -558,18 +584,29 @@ describe('Contribution Links', () => {
linkId = links.data.listContributionLinks.links[0].id linkId = links.data.listContributionLinks.links[0].id
}) })
it('returns a date string', async () => { it('returns true', async () => {
await expect( await expect(
mutate({ mutation: deleteContributionLink, variables: { id: linkId } }), mutate({ mutation: deleteContributionLink, variables: { id: linkId } }),
).resolves.toEqual( ).resolves.toEqual(
expect.objectContaining({ expect.objectContaining({
data: { data: {
deleteContributionLink: expect.any(String), deleteContributionLink: true,
}, },
}), }),
) )
}) })
it('stores the ADMIN_CONTRIBUTION_LINK_DELETE event in the database', async () => {
await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({
type: EventType.ADMIN_CONTRIBUTION_LINK_DELETE,
affectedUserId: 0,
actingUserId: expect.any(Number),
involvedContributionLinkId: linkId,
}),
)
})
it('does not list this contribution link anymore', async () => { it('does not list this contribution link anymore', async () => {
await expect(query({ query: listContributionLinks })).resolves.toEqual( await expect(query({ query: listContributionLinks })).resolves.toEqual(
expect.objectContaining({ expect.objectContaining({

View File

@ -1,5 +1,5 @@
import Decimal from 'decimal.js-light' import Decimal from 'decimal.js-light'
import { Resolver, Args, Arg, Authorized, Mutation, Query, Int } from 'type-graphql' import { Resolver, Args, Arg, Authorized, Mutation, Query, Int, Ctx } from 'type-graphql'
import { MoreThan, IsNull } from '@dbTools/typeorm' import { MoreThan, IsNull } from '@dbTools/typeorm'
import { import {
@ -12,7 +12,6 @@ import { isStartEndDateValid } from './util/creations'
import { ContributionLinkList } from '@model/ContributionLinkList' import { ContributionLinkList } from '@model/ContributionLinkList'
import { ContributionLink } from '@model/ContributionLink' import { ContributionLink } from '@model/ContributionLink'
import ContributionLinkArgs from '@arg/ContributionLinkArgs' import ContributionLinkArgs from '@arg/ContributionLinkArgs'
import { backendLogger as logger } from '@/server/logger'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { Order } from '@enum/Order' import { Order } from '@enum/Order'
@ -21,6 +20,12 @@ import Paginated from '@arg/Paginated'
// TODO: this is a strange construct // TODO: this is a strange construct
import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver' import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver'
import LogError from '@/server/LogError' import LogError from '@/server/LogError'
import { Context, getUser } from '@/server/context'
import {
EVENT_ADMIN_CONTRIBUTION_LINK_CREATE,
EVENT_ADMIN_CONTRIBUTION_LINK_DELETE,
EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE,
} from '@/event/Event'
@Resolver() @Resolver()
export class ContributionLinkResolver { export class ContributionLinkResolver {
@ -38,6 +43,7 @@ export class ContributionLinkResolver {
maxAmountPerMonth = null, maxAmountPerMonth = null,
maxPerCycle, maxPerCycle,
}: ContributionLinkArgs, }: ContributionLinkArgs,
@Ctx() context: Context,
): Promise<ContributionLink> { ): Promise<ContributionLink> {
isStartEndDateValid(validFrom, validTo) isStartEndDateValid(validFrom, validTo)
if (name.length < CONTRIBUTIONLINK_NAME_MIN_CHARS) { if (name.length < CONTRIBUTIONLINK_NAME_MIN_CHARS) {
@ -68,7 +74,8 @@ export class ContributionLinkResolver {
dbContributionLink.maxAmountPerMonth = maxAmountPerMonth dbContributionLink.maxAmountPerMonth = maxAmountPerMonth
dbContributionLink.maxPerCycle = maxPerCycle dbContributionLink.maxPerCycle = maxPerCycle
await dbContributionLink.save() await dbContributionLink.save()
logger.debug(`createContributionLink successful!`) await EVENT_ADMIN_CONTRIBUTION_LINK_CREATE(getUser(context), dbContributionLink, amount)
return new ContributionLink(dbContributionLink) return new ContributionLink(dbContributionLink)
} }
@ -91,16 +98,19 @@ export class ContributionLinkResolver {
} }
@Authorized([RIGHTS.DELETE_CONTRIBUTION_LINK]) @Authorized([RIGHTS.DELETE_CONTRIBUTION_LINK])
@Mutation(() => Date, { nullable: true }) @Mutation(() => Boolean)
async deleteContributionLink(@Arg('id', () => Int) id: number): Promise<Date | null> { async deleteContributionLink(
const contributionLink = await DbContributionLink.findOne(id) @Arg('id', () => Int) id: number,
if (!contributionLink) { @Ctx() context: Context,
): Promise<boolean> {
const dbContributionLink = await DbContributionLink.findOne(id)
if (!dbContributionLink) {
throw new LogError('Contribution Link not found', id) throw new LogError('Contribution Link not found', id)
} }
await contributionLink.softRemove() await dbContributionLink.softRemove()
logger.debug(`deleteContributionLink successful!`) await EVENT_ADMIN_CONTRIBUTION_LINK_DELETE(getUser(context), dbContributionLink)
const newContributionLink = await DbContributionLink.findOne({ id }, { withDeleted: true })
return newContributionLink ? newContributionLink.deletedAt : null return true
} }
@Authorized([RIGHTS.UPDATE_CONTRIBUTION_LINK]) @Authorized([RIGHTS.UPDATE_CONTRIBUTION_LINK])
@ -118,6 +128,7 @@ export class ContributionLinkResolver {
maxPerCycle, maxPerCycle,
}: ContributionLinkArgs, }: ContributionLinkArgs,
@Arg('id', () => Int) id: number, @Arg('id', () => Int) id: number,
@Ctx() context: Context,
): Promise<ContributionLink> { ): Promise<ContributionLink> {
const dbContributionLink = await DbContributionLink.findOne(id) const dbContributionLink = await DbContributionLink.findOne(id)
if (!dbContributionLink) { if (!dbContributionLink) {
@ -132,7 +143,8 @@ export class ContributionLinkResolver {
dbContributionLink.maxAmountPerMonth = maxAmountPerMonth dbContributionLink.maxAmountPerMonth = maxAmountPerMonth
dbContributionLink.maxPerCycle = maxPerCycle dbContributionLink.maxPerCycle = maxPerCycle
await dbContributionLink.save() await dbContributionLink.save()
logger.debug(`updateContributionLink successful!`) await EVENT_ADMIN_CONTRIBUTION_LINK_UPDATE(getUser(context), dbContributionLink, amount)
return new ContributionLink(dbContributionLink) return new ContributionLink(dbContributionLink)
} }
} }

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,
@ -587,7 +587,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,
@ -818,7 +818,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,
@ -946,7 +946,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,
@ -2036,7 +2036,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),
@ -2262,7 +2262,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,
@ -2304,7 +2304,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),
@ -2390,7 +2390,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,
@ -2548,7 +2548,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,
}), }),
) )
}) })
@ -2580,7 +2580,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,
}), }),
) )
}) })
@ -2669,12 +2669,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({
@ -2699,7 +2699,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({
@ -2723,9 +2723,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({
@ -2890,9 +2890,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,
@ -384,23 +382,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,
@ -555,40 +555,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,10 +376,10 @@ 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, involvedContributionLinkId: link.id,
}), }),
) )
}) })
@ -461,10 +461,10 @@ 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, involvedTransactionLinkId: 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

@ -20,9 +20,7 @@ import { getConnection, getCustomRepository, IsNull, Not } from '@dbTools/typeor
import { User as DbUser } from '@entity/User' import { User as DbUser } from '@entity/User'
import { UserContact as DbUserContact } from '@entity/UserContact' import { UserContact as DbUserContact } from '@entity/UserContact'
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink' import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
import { Transaction as DbTransaction } from '@entity/Transaction'
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { Contribution as DbContribution } from '@entity/Contribution'
import { UserRepository } from '@repository/User' import { UserRepository } from '@repository/User'
import { User } from '@model/User' import { User } from '@model/User'
@ -56,6 +54,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 +68,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 +271,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,
) )
@ -293,16 +291,14 @@ export class UserResolver {
logger.info('redeemCode found contributionLink', contributionLink) logger.info('redeemCode found contributionLink', contributionLink)
if (contributionLink) { if (contributionLink) {
dbUser.contributionLinkId = contributionLink.id dbUser.contributionLinkId = contributionLink.id
// TODO this is so wrong eventRegisterRedeem.involvedContributionLink = contributionLink
eventRegisterRedeem.involvedContribution = { id: contributionLink.id } as DbContribution
} }
} else { } else {
const transactionLink = await DbTransactionLink.findOne({ code: redeemCode }) const transactionLink = await DbTransactionLink.findOne({ code: redeemCode })
logger.info('redeemCode found transactionLink', transactionLink) logger.info('redeemCode found transactionLink', transactionLink)
if (transactionLink) { if (transactionLink) {
dbUser.referrerId = transactionLink.userId dbUser.referrerId = transactionLink.userId
// TODO this is so wrong eventRegisterRedeem.involvedTransactionLink = transactionLink
eventRegisterRedeem.involvedTransaction = { id: transactionLink.id } as DbTransaction
} }
} }
} }

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

@ -220,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

@ -0,0 +1,99 @@
import { Contribution } from '../Contribution'
import { ContributionMessage } from '../ContributionMessage'
import { User } from '../User'
import { Transaction } from '../Transaction'
import Decimal from 'decimal.js-light'
import {
BaseEntity,
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
ManyToOne,
JoinColumn,
} from 'typeorm'
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
import { TransactionLink } from '../TransactionLink'
import { ContributionLink } from '../ContributionLink'
@Entity('events')
export class Event extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ length: 100, nullable: false, collation: 'utf8mb4_unicode_ci' })
type: string
@CreateDateColumn({
name: 'created_at',
type: 'datetime',
default: () => 'CURRENT_TIMESTAMP(3)',
nullable: false,
})
createdAt: Date
@Column({ name: 'affected_user_id', unsigned: true, nullable: false })
affectedUserId: number
@ManyToOne(() => User)
@JoinColumn({ name: 'affected_user_id', referencedColumnName: 'id' })
affectedUser: User
@Column({ name: 'acting_user_id', unsigned: true, nullable: false })
actingUserId: number
@ManyToOne(() => User)
@JoinColumn({ name: 'acting_user_id', referencedColumnName: 'id' })
actingUser: User
@Column({ name: 'involved_user_id', type: 'int', unsigned: true, nullable: true })
involvedUserId: number | null
@ManyToOne(() => User)
@JoinColumn({ name: 'involved_user_id', referencedColumnName: 'id' })
involvedUser: User | null
@Column({ name: 'involved_transaction_id', type: 'int', unsigned: true, nullable: true })
involvedTransactionId: number | null
@ManyToOne(() => Transaction)
@JoinColumn({ name: 'involved_transaction_id', referencedColumnName: 'id' })
involvedTransaction: Transaction | null
@Column({ name: 'involved_contribution_id', type: 'int', unsigned: true, nullable: true })
involvedContributionId: number | null
@ManyToOne(() => Contribution)
@JoinColumn({ name: 'involved_contribution_id', referencedColumnName: 'id' })
involvedContribution: Contribution | null
@Column({ name: 'involved_contribution_message_id', type: 'int', unsigned: true, nullable: true })
involvedContributionMessageId: number | null
@ManyToOne(() => ContributionMessage)
@JoinColumn({ name: 'involved_contribution_message_id', referencedColumnName: 'id' })
involvedContributionMessage: ContributionMessage | null
@Column({ name: 'involved_transaction_link_id', type: 'int', unsigned: true, nullable: true })
involvedTransactionLinkId: number | null
@ManyToOne(() => TransactionLink)
@JoinColumn({ name: 'involved_transaction_link_id', referencedColumnName: 'id' })
involvedTransactionLink: TransactionLink | null
@Column({ name: 'involved_contribution_link_id', type: 'int', unsigned: true, nullable: true })
involvedContributionLinkId: number | null
@ManyToOne(() => ContributionLink)
@JoinColumn({ name: 'involved_contribution_link_id', referencedColumnName: 'id' })
involvedContributionLink: ContributionLink | null
@Column({
type: 'decimal',
precision: 40,
scale: 20,
nullable: true,
transformer: DecimalTransformer,
})
amount: Decimal | null
}

View File

@ -1 +1 @@
export { Event } from './0061-event_refactoring/Event' export { Event } from './0063-event_link_fields/Event'

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

@ -0,0 +1,29 @@
/* MIGRATION TO ADD LINK ID FIELDS TO EVENT TABLE
*
* This migration add two fields to store a TransactionLinkId and a ContributionLinkId
* in the event table. Furthermore the event `REDEEM_REGISTER` is rewritten to use the
* new fields.
*/
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
'ALTER TABLE `events` ADD COLUMN `involved_transaction_link_id` int(10) unsigned DEFAULT NULL AFTER `involved_contribution_message_id`;',
)
await queryFn(
'ALTER TABLE `events` ADD COLUMN `involved_contribution_link_id` int(10) unsigned DEFAULT NULL AFTER `involved_transaction_link_id`;',
)
await queryFn(
'UPDATE `events` SET `involved_transaction_link_id` = `involved_transaction_id`, `involved_transaction_id` = NULL, `involved_contribution_link_id` = `involved_contribution_id`, `involved_contribution_id` = NULL WHERE `type` = "REDEEM_REGISTER";',
)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
'UPDATE `events` SET `involved_transaction_id` = `involved_transaction_link_id`, `involved_contribution_id` = `involved_contribution_link_id` WHERE `type` = "REDEEM_REGISTER";',
)
await queryFn('ALTER TABLE `events` DROP COLUMN `involved_contribution_link_id`;')
await queryFn('ALTER TABLE `events` DROP COLUMN `involved_transaction_link_id`;')
}

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: '0063-event_link_fields',
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: '0063-event_link_fields',
// 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

@ -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",