mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
Merge branch 'dlt_connector_direct_usage' into dlt_inspector_as_submodule
This commit is contained in:
commit
03ed61bea6
1
.bun-version
Normal file
1
.bun-version
Normal file
@ -0,0 +1 @@
|
||||
1.3.0
|
||||
15
.github/workflows/lint.yml
vendored
15
.github/workflows/lint.yml
vendored
@ -12,6 +12,7 @@ jobs:
|
||||
backend: ${{ steps.backend.outputs.success }}
|
||||
database: ${{ steps.database.outputs.success }}
|
||||
dht-node: ${{ steps.dht-node.outputs.success }}
|
||||
dlt-connector: ${{ steps.dlt-connector.outputs.success }}
|
||||
federation: ${{ steps.federation.outputs.success }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -57,6 +58,12 @@ jobs:
|
||||
cd ./dht-node
|
||||
biome ci .
|
||||
echo "success=$([ $? -eq 0 ] && echo true || echo false)" >> $GITHUB_OUTPUT
|
||||
- name: Lint - DLT Connector
|
||||
id: dlt-connector
|
||||
run: |
|
||||
cd ./dlt-connector
|
||||
biome ci .
|
||||
echo "success=$([ $? -eq 0 ] && echo true || echo false)" >> $GITHUB_OUTPUT
|
||||
- name: Lint - Federation
|
||||
id: federation
|
||||
run: |
|
||||
@ -112,6 +119,14 @@ jobs:
|
||||
- name: Check result from previous step
|
||||
run: if [ "${{ needs.lint.outputs.dht-node }}" != "true" ]; then exit 1; fi
|
||||
|
||||
lint_dlt_connector:
|
||||
name: Lint - DLT Connector
|
||||
needs: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check result from previous step
|
||||
run: if [ "${{ needs.lint.outputs.dlt-connector }}" != "true" ]; then exit 1; fi
|
||||
|
||||
lint_federation:
|
||||
name: Lint - Federation
|
||||
needs: lint
|
||||
|
||||
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@ -403,10 +403,8 @@ jobs:
|
||||
##########################################################################
|
||||
# Push release tag to GitHub #############################################
|
||||
##########################################################################
|
||||
- name: yarn install
|
||||
run: yarn install
|
||||
- name: generate changelog
|
||||
run: yarn auto-changelog --commit-limit 0 --latest-version ${{ env.VERSION }} --unreleased-only
|
||||
run: npx auto-changelog --commit-limit 0 --latest-version ${{ env.VERSION }} --unreleased-only
|
||||
- name: package-version-to-git-release
|
||||
continue-on-error: true # Will fail if tag exists
|
||||
id: create_release
|
||||
|
||||
7
.github/workflows/test_admin_interface.yml
vendored
7
.github/workflows/test_admin_interface.yml
vendored
@ -54,6 +54,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
@ -93,6 +95,9 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Admin Interface | Locales
|
||||
run: cd admin && yarn locales
|
||||
run: cd admin && bun locales
|
||||
9
.github/workflows/test_backend.yml
vendored
9
.github/workflows/test_backend.yml
vendored
@ -56,6 +56,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
@ -81,6 +83,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
@ -98,6 +102,9 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Backend | Locales
|
||||
run: cd backend && yarn locales
|
||||
run: cd backend && bun locales
|
||||
8
.github/workflows/test_config.yml
vendored
8
.github/workflows/test_config.yml
vendored
@ -28,16 +28,18 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: bun install --filter config-schema --frozen-lockfile
|
||||
|
||||
- name: typecheck
|
||||
run: cd config-schema && yarn typecheck
|
||||
run: cd config-schema && bun run typecheck
|
||||
|
||||
- name: unit tests
|
||||
run: cd config-schema && yarn test
|
||||
run: cd config-schema && bun run test
|
||||
|
||||
|
||||
4
.github/workflows/test_core.yml
vendored
4
.github/workflows/test_core.yml
vendored
@ -29,9 +29,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
|
||||
4
.github/workflows/test_database.yml
vendored
4
.github/workflows/test_database.yml
vendored
@ -53,6 +53,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
@ -76,6 +78,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
|
||||
2
.github/workflows/test_dht_node.yml
vendored
2
.github/workflows/test_dht_node.yml
vendored
@ -53,6 +53,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
|
||||
52
.github/workflows/test_dlt_connector.yml
vendored
52
.github/workflows/test_dlt_connector.yml
vendored
@ -28,29 +28,20 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: create .env
|
||||
run: |
|
||||
cd dlt-connector
|
||||
cat <<EOF > .env
|
||||
GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET=$(openssl rand -hex 16)
|
||||
GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY=$(openssl rand -hex 16)
|
||||
HOME_COMMUNITY_SEED=$(openssl rand -hex 32)
|
||||
HIERO_OPERATOR_KEY=$(openssl rand -hex 32)
|
||||
HIERO_OPERATOR_ID="0.0.2"
|
||||
EOF
|
||||
|
||||
- name: Build 'test' image
|
||||
run: |
|
||||
docker build --target test -t "gradido/dlt-connector:test" -f dlt-connector/Dockerfile .
|
||||
docker save "gradido/dlt-connector:test" > /tmp/dlt-connector.tar
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docker-dlt-connector-test
|
||||
path: /tmp/dlt-connector.tar
|
||||
|
||||
lint:
|
||||
name: Lint - DLT Connector
|
||||
if: needs.files-changed.outputs.dlt_connector == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Lint
|
||||
run: cd dlt-connector && yarn && yarn run lint
|
||||
run: docker build --target production -t "gradido/dlt-connector:productionTest" -f dlt-connector/Dockerfile .
|
||||
|
||||
unit_test:
|
||||
name: Unit Tests - DLT Connector
|
||||
@ -60,6 +51,21 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: cd dlt-connector && bun install --frozen-lockfile
|
||||
|
||||
- name: typecheck && unit test
|
||||
run: |
|
||||
export GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET=$(openssl rand -hex 16)
|
||||
export GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY=$(openssl rand -hex 16)
|
||||
export HOME_COMMUNITY_SEED=$(openssl rand -hex 32)
|
||||
export HIERO_OPERATOR_KEY=$(openssl rand -hex 32)
|
||||
export HIERO_OPERATOR_ID="0.0.2"
|
||||
cd dlt-connector && bun typecheck && bun test
|
||||
|
||||
- name: DLT-Connector | Unit tests
|
||||
run: cd dlt-connector && yarn && yarn test
|
||||
|
||||
8
.github/workflows/test_e2e.yml
vendored
8
.github/workflows/test_e2e.yml
vendored
@ -17,6 +17,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: Boot up test system | docker-compose mariadb mailserver
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach mariadb mailserver
|
||||
@ -70,7 +72,7 @@ jobs:
|
||||
id: e2e-tests
|
||||
run: |
|
||||
cd e2e-tests/
|
||||
yarn run cypress run
|
||||
bun cypress run
|
||||
|
||||
- name: End-to-end tests | if tests failed, compile html report
|
||||
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
|
||||
@ -120,6 +122,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: Boot up test system | docker-compose mariadb mailserver
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach mariadb mailserver
|
||||
@ -203,6 +207,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: Boot up test system | docker-compose mariadb mailserver
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach mariadb mailserver
|
||||
|
||||
2
.github/workflows/test_federation.yml
vendored
2
.github/workflows/test_federation.yml
vendored
@ -53,6 +53,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
|
||||
13
.github/workflows/test_frontend.yml
vendored
13
.github/workflows/test_frontend.yml
vendored
@ -52,12 +52,14 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: bun install --filter frontend --frozen-lockfile
|
||||
|
||||
- name: Frontend | Unit tests
|
||||
run: cd frontend && yarn test
|
||||
run: cd frontend && bun run test
|
||||
|
||||
lint:
|
||||
if: needs.files-changed.outputs.config == 'true' || needs.files-changed.outputs.frontend == 'true'
|
||||
@ -77,7 +79,9 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
bun install --filter frontend --frozen-lockfile
|
||||
@ -106,6 +110,9 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Frontend | Locales
|
||||
run: cd frontend && yarn locales
|
||||
run: cd frontend && bun run locales
|
||||
|
||||
6
.github/workflows/test_shared.yml
vendored
6
.github/workflows/test_shared.yml
vendored
@ -30,13 +30,15 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: bun install --filter shared --frozen-lockfile
|
||||
|
||||
- name: typecheck
|
||||
run: cd shared && yarn typecheck
|
||||
run: cd shared && bun run typecheck
|
||||
|
||||
- name: unit tests
|
||||
run: cd shared && yarn test
|
||||
run: cd shared && bun run test
|
||||
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -12,7 +12,7 @@ messages.pot
|
||||
nbproject
|
||||
.metadata
|
||||
/out/*
|
||||
.env
|
||||
/.env
|
||||
package-lock.json
|
||||
/deployment/bare_metal/.env
|
||||
/deployment/bare_metal/nginx/sites-available/gradido.conf
|
||||
|
||||
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
@ -9,7 +9,7 @@
|
||||
"request": "launch",
|
||||
"name": "Database Debug Tests",
|
||||
"stopOnEntry": true,
|
||||
"runtimeExecutable": "yarn",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"test"
|
||||
@ -25,7 +25,7 @@
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "DHT-Node Debug Tests",
|
||||
"runtimeExecutable": "yarn",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"test:debug"
|
||||
@ -42,7 +42,7 @@
|
||||
"request": "launch",
|
||||
"name": "DHT-Node Debug",
|
||||
"stopOnEntry": true,
|
||||
"runtimeExecutable": "yarn",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev"
|
||||
@ -58,7 +58,7 @@
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Federation Debug Tests",
|
||||
"runtimeExecutable": "yarn",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"test:debug"
|
||||
@ -75,7 +75,7 @@
|
||||
"request": "launch",
|
||||
"name": "Federation Debug",
|
||||
"stopOnEntry": true,
|
||||
"runtimeExecutable": "yarn",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev"
|
||||
@ -92,7 +92,7 @@
|
||||
"request": "launch",
|
||||
"name": "Backend Debug",
|
||||
"stopOnEntry": true,
|
||||
"runtimeExecutable": "yarn",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev"
|
||||
|
||||
33
CHANGELOG.md
33
CHANGELOG.md
@ -4,8 +4,41 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [v2.7.0](https://github.com/gradido/gradido/compare/v2.7.0...v2.7.0)
|
||||
|
||||
- fixes [`414ff8a`](https://github.com/gradido/gradido/commit/414ff8ac5a7477109f80123ccca5c4c8ed4511b2)
|
||||
|
||||
#### [v2.7.0](https://github.com/gradido/gradido/compare/2.6.1...v2.7.0)
|
||||
|
||||
> 15 October 2025
|
||||
|
||||
- feat(frontend): gradido id under avatar instead of email [`#3543`](https://github.com/gradido/gradido/pull/3543)
|
||||
- refactor(backend): add and use template function updateAllDefinedAndChanged in update user and community [`#3546`](https://github.com/gradido/gradido/pull/3546)
|
||||
- fix(backend): check for openai thread timeout [`#3549`](https://github.com/gradido/gradido/pull/3549)
|
||||
- feat(frontend): paste community and user in recipient field of gdd send dialog [`#3542`](https://github.com/gradido/gradido/pull/3542)
|
||||
- fix(backend): allow reading gmsApiKey admins only [`#3547`](https://github.com/gradido/gradido/pull/3547)
|
||||
- feat(frontend): make link send confirmation dialog more accurate [`#3548`](https://github.com/gradido/gradido/pull/3548)
|
||||
- fix(federation): fix bug [`#3545`](https://github.com/gradido/gradido/pull/3545)
|
||||
- feat(frontend): modify frontend for cross community redeem link disbursement [`#3537`](https://github.com/gradido/gradido/pull/3537)
|
||||
- fix(frontend): decimal comma to point on contribution form [`#3539`](https://github.com/gradido/gradido/pull/3539)
|
||||
- feat(backend): introduce security in disbursement handshake [`#3523`](https://github.com/gradido/gradido/pull/3523)
|
||||
- feat(frontend): update texts for overview and contribution message [`#3532`](https://github.com/gradido/gradido/pull/3532)
|
||||
- feat(frontend): add link to gdd in overview [`#3531`](https://github.com/gradido/gradido/pull/3531)
|
||||
- feat(other): add infos for using logging in tests [`#3530`](https://github.com/gradido/gradido/pull/3530)
|
||||
- feat(frontend): increase memo [`#3527`](https://github.com/gradido/gradido/pull/3527)
|
||||
- feat(admin): add hiero topic id like gms api key [`#3524`](https://github.com/gradido/gradido/pull/3524)
|
||||
- fix(other): publish only on release [`#3529`](https://github.com/gradido/gradido/pull/3529)
|
||||
|
||||
#### [2.6.1](https://github.com/gradido/gradido/compare/2.3.1...2.6.1)
|
||||
|
||||
> 14 August 2025
|
||||
|
||||
- fix(other): remove mariadb from publish workflow [`#3528`](https://github.com/gradido/gradido/pull/3528)
|
||||
- fix(database): docker setup [`#3526`](https://github.com/gradido/gradido/pull/3526)
|
||||
- fix(other): fix problems with bun and e2e [`#3525`](https://github.com/gradido/gradido/pull/3525)
|
||||
- feat(backend): introduce security in x com tx handshake [`#3520`](https://github.com/gradido/gradido/pull/3520)
|
||||
- feat(backend): openid connect routes [`#3518`](https://github.com/gradido/gradido/pull/3518)
|
||||
- chore(release): v2.6.1 beta [`#3521`](https://github.com/gradido/gradido/pull/3521)
|
||||
- refactor(frontend): transaction and contribution form [`#3519`](https://github.com/gradido/gradido/pull/3519)
|
||||
- fix(federation): fix some attack vectors in communities handshake [`#3517`](https://github.com/gradido/gradido/pull/3517)
|
||||
- fix(other): start sh when called from webhook [`#3515`](https://github.com/gradido/gradido/pull/3515)
|
||||
|
||||
@ -48,8 +48,6 @@ RUN bun install --global turbo
|
||||
# Add bun's global bin directory to PATH
|
||||
ENV PATH="/root/.bun/bin:${PATH}"
|
||||
|
||||
#RUN yarn global add turbo
|
||||
|
||||
# Settings
|
||||
## Expose Container Port
|
||||
EXPOSE ${BACKEND_PORT}
|
||||
@ -85,10 +83,6 @@ COPY --chown=app:app ./ ./
|
||||
# yarn install
|
||||
RUN bun install --frozen-lockfile --non-interactive
|
||||
|
||||
# try with bun, use yarn if problems occur
|
||||
# go into admin folder and use yarn to install local dependencies which need to use nohoist for @vee-validate/i18n which isn't supported by bun
|
||||
#RUN bun install --frozen-lockfile
|
||||
|
||||
|
||||
##################################################################################
|
||||
# TEST ###########################################################################
|
||||
@ -136,7 +130,7 @@ WORKDIR ${DOCKER_WORKDIR}
|
||||
# Copy only the build artifacts from the previous build stage
|
||||
COPY --chown=app:app --from=build /app/node_modules ./node_modules
|
||||
COPY --chown=app:app --from=build /app/package.json ./package.json
|
||||
COPY --chown=app:app --from=build /app/yarn.lock ./yarn.lock
|
||||
COPY --chown=app:app --from=build /app/bun.lock ./bun.lock
|
||||
COPY --chown=app:app --from=build /app/turbo.json ./turbo.json
|
||||
# and Turbo cache to prevent rebuilding
|
||||
COPY --chown=app:app --from=build /tmp/turbo ./tmp/turbo
|
||||
|
||||
29
README.md
29
README.md
@ -88,13 +88,6 @@ bun install
|
||||
bun install --global turbo@^2
|
||||
```
|
||||
|
||||
If this does not work, try to use [yarn](https://classic.yarnpkg.com/en/docs/install) instead
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
yarn global add turbo@^2
|
||||
```
|
||||
|
||||
- **Development Mode (Hot-Reload)**:
|
||||
Launches Gradido with hot-reloading for fast iteration.
|
||||
|
||||
@ -124,10 +117,6 @@ The installation of dockers depends on your selected product package from the [d
|
||||
* In case the docker desktop will not start correctly because of previous docker installations, then please clean the used directories of previous docker installation - `C:\Users` - before you retry starting docker desktop. For further problems executing docker desktop please take a look in this description "[logs and trouble shooting](https://docs.docker.com/desktop/windows/troubleshoot/)"
|
||||
* In case your docker desktop installation causes high memory consumption per vmmem process, then please take a look at this description "[vmmen process consuming too much memory (Docker Desktop)](https://dev.to/tallesl/vmmen-process-consuming-too-much-memory-docker-desktop-273p)"
|
||||
|
||||
### yarn
|
||||
|
||||
For the Gradido build process the yarn package manager will be used. Please download and install [yarn for windows](https://phoenixnap.com/kb/yarn-windows) by following the instructions there.
|
||||
|
||||
|
||||
### ⚡ Workspaces and Bun Compatibility
|
||||
The project now uses **Workspaces**, and work is ongoing to make all modules **Bun-compatible**. You can currently use `bun install`, but not all modules are fully Bun-compatible yet.
|
||||
@ -143,12 +132,10 @@ To install dependencies with Bun:
|
||||
bun install
|
||||
```
|
||||
|
||||
Note that some modules are still not fully compatible with Bun. Therefore, continue using **Yarn** for development if you run into any issues.
|
||||
|
||||
### EMFILE: too many open files
|
||||
With
|
||||
```bash
|
||||
yarn docker_dev
|
||||
bun docker_dev
|
||||
```
|
||||
or also
|
||||
```bash
|
||||
@ -161,11 +148,11 @@ which you are working on in dev mode and the rest in production mode.
|
||||
|
||||
For example if you are only working on the frontend, you can start the frontend in dev mode and the rest in production mode:
|
||||
```bash
|
||||
yarn docker_dev frontend
|
||||
bun docker_dev frontend
|
||||
```
|
||||
and in another bash
|
||||
```bash
|
||||
yarn docker backend admin database nginx --no-deps
|
||||
bun docker backend admin database nginx --no-deps
|
||||
```
|
||||
or local with turbo
|
||||
```bash
|
||||
@ -218,10 +205,10 @@ Currently Modules `frontend`, `admin`, `share` and `core` running the tests in p
|
||||
`database`, `backend`, `dht-node` and `federation` are running the tests still serially.
|
||||
|
||||
### Clear
|
||||
In root folder calling `yarn clear` will clear all turbo caches, node_modules and build folders of all workspaces for a clean rebuild.
|
||||
In root folder calling `bun clear` will clear all turbo caches, node_modules and build folders of all workspaces for a clean rebuild.
|
||||
|
||||
```bash
|
||||
yarn clear
|
||||
bun clear
|
||||
```
|
||||
|
||||
|
||||
@ -254,13 +241,13 @@ To generate the Changelog and set a new Version you should use the following com
|
||||
|
||||
```bash
|
||||
git fetch --all
|
||||
yarn release
|
||||
bun release
|
||||
```
|
||||
|
||||
The first command `git fetch --all` will make sure you have all tags previously defined which is required to generate a correct changelog. The second command `yarn release` will execute the changelog tool and set version numbers in the main package and sub-packages. It is required to do `yarn install` before you can use this command.
|
||||
The first command `git fetch --all` will make sure you have all tags previously defined which is required to generate a correct changelog. The second command `bun release` will execute the changelog tool and set version numbers in the main package and sub-packages. It is required to do `bun install` before you can use this command.
|
||||
After generating a new version you should commit the changes. This will be the CHANGELOG.md and several package.json files. This commit will be omitted in the changelog.
|
||||
|
||||
Note: The Changelog will be regenerated with all tags on release on the external builder tool, but will not be checked in there. The Changelog on the github release will therefore always be correct, on the repo it might be incorrect due to missing tags when executing the `yarn release` command.
|
||||
Note: The Changelog will be regenerated with all tags on release on the external builder tool, but will not be checked in there. The Changelog on the github release will therefore always be correct, on the repo it might be incorrect due to missing tags when executing the `bun release` command.
|
||||
|
||||
## How the different .env work on deploy
|
||||
|
||||
|
||||
@ -57,7 +57,9 @@ WORKDIR ${DOCKER_WORKDIR}
|
||||
FROM base as bun-base
|
||||
|
||||
RUN apk update && apk add --no-cache curl tar bash
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
COPY .bun-version .bun-version
|
||||
RUN BUN_VERSION=$(cat .bun-version) && \
|
||||
curl -fsSL https://bun.com/install | bash -s "bun-v${BUN_VERSION}"
|
||||
# Add bun's global bin directory to PATH
|
||||
ENV PATH="/root/.bun/bin:${PATH}"
|
||||
|
||||
|
||||
@ -1,26 +1,48 @@
|
||||
# admin
|
||||
|
||||
## Project setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
yarn serve
|
||||
turbo dev
|
||||
```
|
||||
or from root folder:
|
||||
|
||||
```
|
||||
turbo admin#dev
|
||||
```
|
||||
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
yarn build
|
||||
turbo build
|
||||
```
|
||||
or from root folder:
|
||||
|
||||
```
|
||||
turbo admin#build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
yarn lint
|
||||
turbo lint
|
||||
```
|
||||
or from root folder:
|
||||
|
||||
```
|
||||
turbo admin#lint
|
||||
```
|
||||
|
||||
### Unit tests
|
||||
```
|
||||
yarn test
|
||||
turbo test
|
||||
```
|
||||
For filtering out single tests:
|
||||
```
|
||||
turbo test -- <test_name>
|
||||
```
|
||||
Everything after -- will be passed to vitest.
|
||||
|
||||
or from root folder:
|
||||
|
||||
```
|
||||
turbo admin#test
|
||||
```
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Administration Interface for Gradido",
|
||||
"main": "index.js",
|
||||
"author": "Gradido Academy - https://www.gradido.net",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@ -61,6 +61,7 @@
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"config-schema": "*",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^10.0.0",
|
||||
"dotenv-webpack": "^7.0.3",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
@ -71,7 +72,7 @@
|
||||
"eslint-plugin-prettier": "^5.2.3",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "8.7.1",
|
||||
"joi": "^17.13.3",
|
||||
"joi": "17.13.3",
|
||||
"jsdom": "^25.0.0",
|
||||
"lightningcss": "^1.30.1",
|
||||
"mock-apollo-client": "^1.2.1",
|
||||
|
||||
@ -15,7 +15,11 @@
|
||||
</div>
|
||||
<div ref="chatContainer" class="messages-scroll-container">
|
||||
<TransitionGroup class="messages" tag="div" name="chat">
|
||||
<div v-for="(message, index) in messages" :key="index" :class="['message', message.role]">
|
||||
<div
|
||||
v-for="(message, index) in messages"
|
||||
:key="index"
|
||||
:class="['message', message.role, { 'message-error': message.isError }]"
|
||||
>
|
||||
<div class="message-content position-relative inner-container">
|
||||
<span v-html="formatMessage(message)"></span>
|
||||
<b-button
|
||||
@ -25,7 +29,7 @@
|
||||
:title="$t('copy-to-clipboard')"
|
||||
@click="copyToClipboard(message.content)"
|
||||
>
|
||||
<IBiClipboard></IBiClipboard>
|
||||
<IBiCopy></IBiCopy>
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -170,7 +174,18 @@ const sendMessage = () => {
|
||||
onMounted(async () => {
|
||||
if (messages.value.length === 0) {
|
||||
loading.value = true
|
||||
await resumeChatRefetch()
|
||||
try {
|
||||
await resumeChatRefetch()
|
||||
} catch (error) {
|
||||
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
|
||||
toastError(`Error loading chat: ${error.graphQLErrors[0].message}`)
|
||||
return
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(JSON.stringify(error, null, 2))
|
||||
toastError(`Error loading chat: ${error}`)
|
||||
}
|
||||
}
|
||||
const messagesFromServer = resumeChatResult.value.resumeChat
|
||||
if (messagesFromServer && messagesFromServer.length > 0) {
|
||||
threadId.value = messagesFromServer[0].threadId
|
||||
@ -279,6 +294,17 @@ onMounted(async () => {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.message.error .message-content {
|
||||
background-color: #f1e5e5;
|
||||
color: rgb(194 12 12);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
|
||||
@ -26,6 +26,7 @@ fragment AiChatMessageFields on ChatGptMessage {
|
||||
content
|
||||
role
|
||||
threadId
|
||||
isError
|
||||
}
|
||||
|
||||
fragment UserCommonFields on User {
|
||||
|
||||
@ -54,8 +54,9 @@ WORKDIR ${DOCKER_WORKDIR}
|
||||
FROM base as bun-base
|
||||
|
||||
RUN apt update && apt install -y --no-install-recommends ca-certificates curl bash unzip
|
||||
#RUN apk update && apk add --no-cache curl tar bash
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
COPY .bun-version .bun-version
|
||||
RUN BUN_VERSION=$(cat .bun-version) && \
|
||||
curl -fsSL https://bun.com/install | bash -s "bun-v${BUN_VERSION}"
|
||||
# Add bun's global bin directory to PATH
|
||||
ENV PATH="/root/.bun/bin:${PATH}"
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
## Seed DB
|
||||
|
||||
```bash
|
||||
yarn seed
|
||||
turbo seed
|
||||
```
|
||||
|
||||
Deletes all data in database. Then seeds data in database.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"private": false,
|
||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||
"repository": "https://github.com/gradido/gradido/backend",
|
||||
@ -41,6 +41,7 @@
|
||||
"@swc/cli": "^0.7.3",
|
||||
"@swc/core": "^1.11.24",
|
||||
"@swc/helpers": "^0.5.17",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/email-templates": "^10.0.4",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/faker": "^5.5.9",
|
||||
@ -75,7 +76,7 @@
|
||||
"helmet": "^5.1.1",
|
||||
"i18n": "^0.15.1",
|
||||
"jest": "27.2.4",
|
||||
"joi": "^17.13.3",
|
||||
"joi": "17.13.3",
|
||||
"jose": "^4.14.4",
|
||||
"klicktipp-api": "^1.0.2",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
|
||||
@ -4,7 +4,7 @@ import { DltConnectorClient } from './DltConnectorClient'
|
||||
|
||||
describe('undefined DltConnectorClient', () => {
|
||||
it('invalid url', () => {
|
||||
CONFIG.DLT_CONNECTOR_URL = 'invalid'
|
||||
CONFIG.DLT_CONNECTOR_URL = ''
|
||||
CONFIG.DLT_CONNECTOR = true
|
||||
const result = DltConnectorClient.getInstance()
|
||||
expect(result).toBeUndefined()
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
UserLoggingView,
|
||||
} from 'database'
|
||||
import { TransactionDraft } from './model/TransactionDraft'
|
||||
import { CONFIG } from '@/config'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.dltConnector`)
|
||||
// will be undefined if dlt connect is disabled
|
||||
@ -44,25 +45,39 @@ async function checkDltConnectorResult(dltTransaction: DbDltTransaction, clientR
|
||||
return dltTransaction
|
||||
}
|
||||
|
||||
async function executeDltTransaction(draft: TransactionDraft | null, typeId: DltTransactionType, persist = true): Promise<DbDltTransaction | null> {
|
||||
if (draft && dltConnectorClient) {
|
||||
const clientResponse = dltConnectorClient.sendTransaction(draft)
|
||||
let dltTransaction = new DbDltTransaction()
|
||||
dltTransaction.typeId = typeId
|
||||
dltTransaction = await checkDltConnectorResult(dltTransaction, clientResponse)
|
||||
if (persist) {
|
||||
return await dltTransaction.save()
|
||||
}
|
||||
return dltTransaction
|
||||
}
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
|
||||
/**
|
||||
* send register address transaction via dlt-connector to hiero
|
||||
* and update dltTransactionId of transaction in db with hiero transaction id
|
||||
*/
|
||||
export async function registerAddressTransaction(user: DbUser, community: DbCommunity): Promise<DbDltTransaction | null> {
|
||||
if (!CONFIG.DLT_CONNECTOR) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
if (!user.id) {
|
||||
logger.error(`missing id for user: ${user.gradidoID}, please call registerAddressTransaction after user.save()`)
|
||||
return null
|
||||
}
|
||||
// return null if some data where missing and log error
|
||||
const draft = TransactionDraft.createRegisterAddress(user, community)
|
||||
if (draft && dltConnectorClient) {
|
||||
const clientResponse = dltConnectorClient.sendTransaction(draft)
|
||||
let dltTransaction = new DbDltTransaction()
|
||||
dltTransaction.typeId = DltTransactionType.REGISTER_ADDRESS
|
||||
const dltTransaction = await executeDltTransaction(draft, DltTransactionType.REGISTER_ADDRESS, false)
|
||||
if (dltTransaction) {
|
||||
if (user.id) {
|
||||
dltTransaction.userId = user.id
|
||||
}
|
||||
dltTransaction = await checkDltConnectorResult(dltTransaction, clientResponse)
|
||||
return await dltTransaction.save()
|
||||
}
|
||||
return null
|
||||
@ -73,20 +88,16 @@ export async function contributionTransaction(
|
||||
signingUser: DbUser,
|
||||
createdAt: Date,
|
||||
): Promise<DbDltTransaction | null> {
|
||||
if (!CONFIG.DLT_CONNECTOR) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
const homeCommunity = await getHomeCommunity()
|
||||
if (!homeCommunity) {
|
||||
logger.error('home community not found')
|
||||
return null
|
||||
}
|
||||
const draft = TransactionDraft.createContribution(contribution, createdAt, signingUser, homeCommunity)
|
||||
if (draft && dltConnectorClient) {
|
||||
const clientResponse = dltConnectorClient.sendTransaction(draft)
|
||||
let dltTransaction = new DbDltTransaction()
|
||||
dltTransaction.typeId = DltTransactionType.CREATION
|
||||
dltTransaction = await checkDltConnectorResult(dltTransaction, clientResponse)
|
||||
return await dltTransaction.save()
|
||||
}
|
||||
return null
|
||||
return await executeDltTransaction(draft, DltTransactionType.CREATION)
|
||||
}
|
||||
|
||||
export async function transferTransaction(
|
||||
@ -96,6 +107,9 @@ export async function transferTransaction(
|
||||
memo: string,
|
||||
createdAt: Date
|
||||
): Promise<DbDltTransaction | null> {
|
||||
if (!CONFIG.DLT_CONNECTOR) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
// load community if not already loaded, maybe they are remote communities
|
||||
if (!senderUser.community) {
|
||||
senderUser.community = await getCommunityByUuid(senderUser.communityUuid)
|
||||
@ -106,35 +120,27 @@ export async function transferTransaction(
|
||||
logger.info(`sender user: ${new UserLoggingView(senderUser)}`)
|
||||
logger.info(`recipient user: ${new UserLoggingView(recipientUser)}`)
|
||||
const draft = TransactionDraft.createTransfer(senderUser, recipientUser, amount, memo, createdAt)
|
||||
if (draft && dltConnectorClient) {
|
||||
const clientResponse = dltConnectorClient.sendTransaction(draft)
|
||||
let dltTransaction = new DbDltTransaction()
|
||||
dltTransaction.typeId = DltTransactionType.TRANSFER
|
||||
dltTransaction = await checkDltConnectorResult(dltTransaction, clientResponse)
|
||||
return await dltTransaction.save()
|
||||
}
|
||||
return null
|
||||
return await executeDltTransaction(draft, DltTransactionType.TRANSFER)
|
||||
}
|
||||
|
||||
export async function deferredTransferTransaction(senderUser: DbUser, transactionLink: DbTransactionLink)
|
||||
: Promise<DbDltTransaction | null> {
|
||||
if (!CONFIG.DLT_CONNECTOR) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
// load community if not already loaded
|
||||
if (!senderUser.community) {
|
||||
senderUser.community = await getCommunityByUuid(senderUser.communityUuid)
|
||||
}
|
||||
const draft = TransactionDraft.createDeferredTransfer(senderUser, transactionLink)
|
||||
if (draft && dltConnectorClient) {
|
||||
const clientResponse = dltConnectorClient.sendTransaction(draft)
|
||||
let dltTransaction = new DbDltTransaction()
|
||||
dltTransaction.typeId = DltTransactionType.DEFERRED_TRANSFER
|
||||
dltTransaction = await checkDltConnectorResult(dltTransaction, clientResponse)
|
||||
return await dltTransaction.save()
|
||||
}
|
||||
return null
|
||||
return await executeDltTransaction(draft, DltTransactionType.DEFERRED_TRANSFER)
|
||||
}
|
||||
|
||||
export async function redeemDeferredTransferTransaction(transactionLink: DbTransactionLink, amount: string, createdAt: Date, recipientUser: DbUser)
|
||||
: Promise<DbDltTransaction | null> {
|
||||
if (!CONFIG.DLT_CONNECTOR) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
// load user and communities if not already loaded
|
||||
if (!transactionLink.user) {
|
||||
logger.debug('load sender user')
|
||||
@ -151,14 +157,7 @@ export async function redeemDeferredTransferTransaction(transactionLink: DbTrans
|
||||
logger.debug(`sender: ${new UserLoggingView(transactionLink.user)}`)
|
||||
logger.debug(`recipient: ${new UserLoggingView(recipientUser)}`)
|
||||
const draft = TransactionDraft.redeemDeferredTransfer(transactionLink, amount, createdAt, recipientUser)
|
||||
if (draft && dltConnectorClient) {
|
||||
const clientResponse = dltConnectorClient.sendTransaction(draft)
|
||||
let dltTransaction = new DbDltTransaction()
|
||||
dltTransaction.typeId = DltTransactionType.REDEEM_DEFERRED_TRANSFER
|
||||
dltTransaction = await checkDltConnectorResult(dltTransaction, clientResponse)
|
||||
return await dltTransaction.save()
|
||||
}
|
||||
return null
|
||||
return await executeDltTransaction(draft, DltTransactionType.REDEEM_DEFERRED_TRANSFER)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -11,6 +11,8 @@ import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { getLogger } from 'log4js'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.apis.openai.OpenaiClient`)
|
||||
// this is the time after when openai is deleting an inactive thread
|
||||
const OPENAI_AI_THREAD_DEFAULT_TIMEOUT_DAYS = 60
|
||||
|
||||
/**
|
||||
* The `OpenaiClient` class is a singleton that provides an interface to interact with the OpenAI API.
|
||||
@ -87,21 +89,35 @@ export class OpenaiClient {
|
||||
logger.warn(`No openai thread found for user: ${user.id}`)
|
||||
return []
|
||||
}
|
||||
const threadMessages = (
|
||||
await this.openai.beta.threads.messages.list(openaiThreadEntity.id, { order: 'desc' })
|
||||
).getPaginatedItems()
|
||||
if (openaiThreadEntity.updatedAt < new Date(Date.now() - OPENAI_AI_THREAD_DEFAULT_TIMEOUT_DAYS * 24 * 60 * 60 * 1000)) {
|
||||
logger.info(`Openai thread for user: ${user.id} is older than ${OPENAI_AI_THREAD_DEFAULT_TIMEOUT_DAYS} days, deleting...`)
|
||||
// let run async, because it could need some time, but we don't need to wait, because we create a new one nevertheless
|
||||
void this.deleteThread(openaiThreadEntity.id)
|
||||
return []
|
||||
}
|
||||
try {
|
||||
const threadMessages = (
|
||||
await this.openai.beta.threads.messages.list(openaiThreadEntity.id, { order: 'desc' })
|
||||
).getPaginatedItems()
|
||||
|
||||
logger.info(`Resumed thread: ${openaiThreadEntity.id}`)
|
||||
return threadMessages
|
||||
.map(
|
||||
(message) =>
|
||||
new MessageModel(
|
||||
this.messageContentToString(message),
|
||||
message.role,
|
||||
openaiThreadEntity.id,
|
||||
),
|
||||
)
|
||||
.reverse()
|
||||
logger.info(`Resumed thread: ${openaiThreadEntity.id}`)
|
||||
return threadMessages
|
||||
.map(
|
||||
(message) =>
|
||||
new MessageModel(
|
||||
this.messageContentToString(message),
|
||||
message.role,
|
||||
openaiThreadEntity.id,
|
||||
),
|
||||
)
|
||||
.reverse()
|
||||
} catch (e) {
|
||||
if(e instanceof Error && e.toString().includes('No thread found with id')) {
|
||||
logger.info(`Thread not found: ${openaiThreadEntity.id}`)
|
||||
return []
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteThread(threadId: string): Promise<boolean> {
|
||||
@ -124,6 +140,7 @@ export class OpenaiClient {
|
||||
}
|
||||
|
||||
public async runAndGetLastNewMessage(threadId: string): Promise<MessageModel> {
|
||||
const updateOpenAiThreadResolver = OpenaiThreads.update({ id: threadId }, { updatedAt: new Date() })
|
||||
const run = await this.openai.beta.threads.runs.createAndPoll(threadId, {
|
||||
assistant_id: CONFIG.OPENAI_ASSISTANT_ID,
|
||||
})
|
||||
@ -138,6 +155,7 @@ export class OpenaiClient {
|
||||
logger.warn(`No message in thread: ${threadId}, run: ${run.id}`, messagesPage.data)
|
||||
return new MessageModel('No Answer', 'assistant')
|
||||
}
|
||||
await updateOpenAiThreadResolver
|
||||
return new MessageModel(this.messageContentToString(message), 'assistant')
|
||||
}
|
||||
|
||||
|
||||
@ -35,7 +35,6 @@ export enum RIGHTS {
|
||||
UPDATE_CONTRIBUTION = 'UPDATE_CONTRIBUTION',
|
||||
LIST_CONTRIBUTION_LINKS = 'LIST_CONTRIBUTION_LINKS',
|
||||
COMMUNITY_STATISTICS = 'COMMUNITY_STATISTICS',
|
||||
COMMUNITY_STATUS = 'COMMUNITY_STATUS',
|
||||
SEARCH_ADMIN_USERS = 'SEARCH_ADMIN_USERS',
|
||||
CREATE_CONTRIBUTION_MESSAGE = 'CREATE_CONTRIBUTION_MESSAGE',
|
||||
LIST_ALL_CONTRIBUTION_MESSAGES = 'LIST_ALL_CONTRIBUTION_MESSAGES',
|
||||
|
||||
@ -26,7 +26,6 @@ export const USER_RIGHTS = [
|
||||
RIGHTS.SEARCH_ADMIN_USERS,
|
||||
RIGHTS.LIST_CONTRIBUTION_LINKS,
|
||||
RIGHTS.COMMUNITY_STATISTICS,
|
||||
RIGHTS.COMMUNITY_STATUS,
|
||||
RIGHTS.CREATE_CONTRIBUTION_MESSAGE,
|
||||
RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES,
|
||||
RIGHTS.OPEN_CREATIONS,
|
||||
|
||||
@ -1,78 +1,110 @@
|
||||
import { CommunityLoggingView, Community as DbCommunity, FederatedCommunity as DbFederatedCommunity, FederatedCommunityLoggingView, getHomeCommunity } from 'database'
|
||||
import { validate as validateUUID, version as versionUUID } from 'uuid'
|
||||
import {
|
||||
CommunityHandshakeState as DbCommunityHandshakeState,
|
||||
CommunityHandshakeStateLoggingView,
|
||||
FederatedCommunity as DbFederatedCommunity,
|
||||
findPendingCommunityHandshake,
|
||||
getHomeCommunityWithFederatedCommunityOrFail,
|
||||
CommunityHandshakeStateType,
|
||||
getCommunityByPublicKeyOrFail,
|
||||
} from 'database'
|
||||
import { randombytes_random } from 'sodium-native'
|
||||
import { CONFIG as CONFIG_CORE } from 'core'
|
||||
|
||||
import { AuthenticationClient as V1_0_AuthenticationClient } from '@/federation/client/1_0/AuthenticationClient'
|
||||
import { ensureUrlEndsWithSlash } from 'core'
|
||||
import { ensureUrlEndsWithSlash, getFederatedCommunityWithApiOrFail } from 'core'
|
||||
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { encryptAndSign, OpenConnectionJwtPayloadType } from 'shared'
|
||||
import { communityAuthenticatedSchema, encryptAndSign, OpenConnectionJwtPayloadType } from 'shared'
|
||||
import { getLogger } from 'log4js'
|
||||
import { AuthenticationClientFactory } from './client/AuthenticationClientFactory'
|
||||
import { EncryptedTransferArgs } from 'core'
|
||||
import { CommunityHandshakeStateLogic } from 'core'
|
||||
import { Ed25519PublicKey } from 'shared'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.authenticateCommunities`)
|
||||
const createLogger = (functionName: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.authenticateCommunities.${functionName}`)
|
||||
|
||||
export enum StartCommunityAuthenticationResult {
|
||||
ALREADY_AUTHENTICATED = 'already authenticated',
|
||||
ALREADY_IN_PROGRESS = 'already in progress',
|
||||
SUCCESSFULLY_STARTED = 'successfully started',
|
||||
}
|
||||
|
||||
export async function startCommunityAuthentication(
|
||||
fedComB: DbFederatedCommunity,
|
||||
): Promise<void> {
|
||||
const methodLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.authenticateCommunities.startCommunityAuthentication`)
|
||||
): Promise<StartCommunityAuthenticationResult> {
|
||||
const methodLogger = createLogger('startCommunityAuthentication')
|
||||
const handshakeID = randombytes_random().toString()
|
||||
const fedComBPublicKey = new Ed25519PublicKey(fedComB.publicKey)
|
||||
methodLogger.addContext('handshakeID', handshakeID)
|
||||
methodLogger.debug(`startCommunityAuthentication()...`, {
|
||||
fedComB: new FederatedCommunityLoggingView(fedComB),
|
||||
})
|
||||
const homeComA = await getHomeCommunity()
|
||||
methodLogger.debug('homeComA', new CommunityLoggingView(homeComA!))
|
||||
const homeFedComA = await DbFederatedCommunity.findOneByOrFail({
|
||||
foreign: false,
|
||||
apiVersion: CONFIG_CORE.FEDERATION_BACKEND_SEND_ON_API,
|
||||
})
|
||||
methodLogger.debug('homeFedComA', new FederatedCommunityLoggingView(homeFedComA))
|
||||
const comB = await DbCommunity.findOneByOrFail({ publicKey: fedComB.publicKey })
|
||||
methodLogger.debug('started with comB:', new CommunityLoggingView(comB))
|
||||
methodLogger.debug(`start with public key ${fedComBPublicKey.asHex()}`)
|
||||
const homeComA = await getHomeCommunityWithFederatedCommunityOrFail(fedComB.apiVersion)
|
||||
// methodLogger.debug('homeComA', new CommunityLoggingView(homeComA))
|
||||
const homeFedComA = getFederatedCommunityWithApiOrFail(homeComA, fedComB.apiVersion)
|
||||
|
||||
const comB = await getCommunityByPublicKeyOrFail(fedComBPublicKey)
|
||||
// methodLogger.debug('started with comB:', new CommunityLoggingView(comB))
|
||||
// check if communityUuid is not a valid v4Uuid
|
||||
try {
|
||||
if (
|
||||
comB &&
|
||||
((comB.communityUuid === null && comB.authenticatedAt === null) ||
|
||||
(comB.communityUuid !== null &&
|
||||
(!validateUUID(comB.communityUuid) ||
|
||||
versionUUID(comB.communityUuid!) !== 4)))
|
||||
) {
|
||||
methodLogger.debug('comB.uuid is null or is a not valid v4Uuid...', comB.communityUuid || 'null', comB.authenticatedAt || 'null')
|
||||
const client = AuthenticationClientFactory.getInstance(fedComB)
|
||||
|
||||
if (client instanceof V1_0_AuthenticationClient) {
|
||||
if (!comB.publicJwtKey) {
|
||||
throw new Error('Public JWT key still not exist for comB ' + comB.name)
|
||||
}
|
||||
//create JWT with url in payload encrypted by foreignCom.publicJwtKey and signed with homeCom.privateJwtKey
|
||||
const payload = new OpenConnectionJwtPayloadType(handshakeID,
|
||||
ensureUrlEndsWithSlash(homeFedComA.endPoint).concat(homeFedComA.apiVersion),
|
||||
)
|
||||
methodLogger.debug('payload', payload)
|
||||
const jws = await encryptAndSign(payload, homeComA!.privateJwtKey!, comB.publicJwtKey!)
|
||||
methodLogger.debug('jws', jws)
|
||||
// prepare the args for the client invocation
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = homeComA!.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = handshakeID
|
||||
methodLogger.debug('before client.openConnection() args:', args)
|
||||
const result = await client.openConnection(args)
|
||||
if (result) {
|
||||
methodLogger.debug(`successful initiated at community:`, fedComB.endPoint)
|
||||
} else {
|
||||
methodLogger.error(`can't initiate at community:`, fedComB.endPoint)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
methodLogger.debug(`comB.communityUuid is already a valid v4Uuid ${ comB.communityUuid || 'null' } and was authenticated at ${ comB.authenticatedAt || 'null'}`)
|
||||
}
|
||||
} catch (err) {
|
||||
methodLogger.error(`Error:`, err)
|
||||
|
||||
// communityAuthenticatedSchema.safeParse return true
|
||||
// - if communityUuid is a valid v4Uuid and
|
||||
// - if authenticatedAt is a valid date
|
||||
if (communityAuthenticatedSchema.safeParse(comB).success) {
|
||||
methodLogger.debug(`comB.communityUuid is already a valid v4Uuid ${ comB.communityUuid || 'null' } and was authenticated at ${ comB.authenticatedAt || 'null'}`)
|
||||
return StartCommunityAuthenticationResult.ALREADY_AUTHENTICATED
|
||||
}
|
||||
methodLogger.removeContext('handshakeID')
|
||||
/*methodLogger.debug('comB.uuid is null or is a not valid v4Uuid...',
|
||||
comB.communityUuid || 'null', comB.authenticatedAt || 'null'
|
||||
)*/
|
||||
|
||||
// check if a authentication is already in progress
|
||||
const existingState = await findPendingCommunityHandshake(fedComBPublicKey, fedComB.apiVersion)
|
||||
if (existingState) {
|
||||
const stateLogic = new CommunityHandshakeStateLogic(existingState)
|
||||
// retry on timeout or failure
|
||||
if (!(await stateLogic.isTimeoutUpdate())) {
|
||||
// authentication with community and api version is still in progress and it is not timeout yet
|
||||
methodLogger.debug('existingState, so we exit here', new CommunityHandshakeStateLoggingView(existingState))
|
||||
return StartCommunityAuthenticationResult.ALREADY_IN_PROGRESS
|
||||
}
|
||||
}
|
||||
|
||||
const client = AuthenticationClientFactory.getInstance(fedComB)
|
||||
|
||||
if (client instanceof V1_0_AuthenticationClient) {
|
||||
if (!comB.publicJwtKey) {
|
||||
throw new Error(`Public JWT key still not exist for comB ${comB.name}`)
|
||||
}
|
||||
const state = new DbCommunityHandshakeState()
|
||||
state.publicKey = fedComBPublicKey.asBuffer()
|
||||
state.apiVersion = fedComB.apiVersion
|
||||
state.status = CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION
|
||||
state.handshakeId = parseInt(handshakeID)
|
||||
await state.save()
|
||||
methodLogger.debug('[START_COMMUNITY_AUTHENTICATION] community handshake state created')
|
||||
|
||||
//create JWT with url in payload encrypted by foreignCom.publicJwtKey and signed with homeCom.privateJwtKey
|
||||
const payload = new OpenConnectionJwtPayloadType(handshakeID,
|
||||
ensureUrlEndsWithSlash(homeFedComA.endPoint).concat(homeFedComA.apiVersion),
|
||||
)
|
||||
// methodLogger.debug('payload', payload)
|
||||
const jws = await encryptAndSign(payload, homeComA!.privateJwtKey!, comB.publicJwtKey!)
|
||||
// methodLogger.debug('jws', jws)
|
||||
// prepare the args for the client invocation
|
||||
const args = new EncryptedTransferArgs()
|
||||
const homeComAPublicKey = new Ed25519PublicKey(homeComA!.publicKey)
|
||||
args.publicKey = homeComAPublicKey.asHex()
|
||||
args.jwt = jws
|
||||
args.handshakeID = handshakeID
|
||||
// methodLogger.debug('before client.openConnection() args:', args)
|
||||
const result = await client.openConnection(args)
|
||||
if (result) {
|
||||
methodLogger.debug(`successful initiated at community:`, fedComB.endPoint)
|
||||
} else {
|
||||
const errorMsg = `can't initiate at community: ${fedComB.endPoint}`
|
||||
methodLogger.error(errorMsg)
|
||||
state.status = CommunityHandshakeStateType.FAILED
|
||||
state.lastError = errorMsg
|
||||
}
|
||||
await state.save()
|
||||
}
|
||||
return StartCommunityAuthenticationResult.SUCCESSFULLY_STARTED
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import { FederationClient as V1_0_FederationClient } from '@/federation/client/1
|
||||
import { FederationClient as V1_1_FederationClient } from '@/federation/client/1_1/FederationClient'
|
||||
import { ApiVersionType, ensureUrlEndsWithSlash } from 'core'
|
||||
|
||||
export type FederationClient = V1_0_FederationClient | V1_1_FederationClient
|
||||
type FederationClient = V1_0_FederationClient | V1_1_FederationClient
|
||||
|
||||
interface FederationClientInstance {
|
||||
id: number
|
||||
|
||||
@ -103,7 +103,7 @@ describe('validate Communities', () => {
|
||||
return {
|
||||
data: {
|
||||
getPublicKey: {
|
||||
publicKey: 'somePubKey',
|
||||
publicKey: '2222222222222222222222222222222222222222222222222222222222222222',
|
||||
},
|
||||
},
|
||||
} as Response<unknown>
|
||||
@ -170,8 +170,8 @@ describe('validate Communities', () => {
|
||||
it('logs not matching publicKeys', () => {
|
||||
expect(logger.debug).toBeCalledWith(
|
||||
'received not matching publicKey:',
|
||||
'somePubKey',
|
||||
expect.stringMatching('11111111111111111111111111111111'),
|
||||
'2222222222222222222222222222222222222222222222222222222222222222',
|
||||
expect.stringMatching('1111111111111111111111111111111100000000000000000000000000000000'),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import {
|
||||
Community as DbCommunity,
|
||||
FederatedCommunity as DbFederatedCommunity,
|
||||
FederatedCommunityLoggingView,
|
||||
getHomeCommunity,
|
||||
} from 'database'
|
||||
import { IsNull } from 'typeorm'
|
||||
@ -11,7 +10,7 @@ import { FederationClient as V1_0_FederationClient } from '@/federation/client/1
|
||||
import { PublicCommunityInfo } from '@/federation/client/1_0/model/PublicCommunityInfo'
|
||||
import { FederationClientFactory } from '@/federation/client/FederationClientFactory'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { createKeyPair } from 'shared'
|
||||
import { createKeyPair, Ed25519PublicKey } from 'shared'
|
||||
import { getLogger } from 'log4js'
|
||||
import { startCommunityAuthentication } from './authenticateCommunities'
|
||||
import { PublicCommunityInfoLoggingView } from './client/1_0/logging/PublicCommunityInfoLogging.view'
|
||||
@ -45,27 +44,31 @@ export async function validateCommunities(): Promise<void> {
|
||||
|
||||
logger.debug(`found ${dbFederatedCommunities.length} dbCommunities`)
|
||||
for (const dbFedComB of dbFederatedCommunities) {
|
||||
logger.debug('dbFedComB', new FederatedCommunityLoggingView(dbFedComB))
|
||||
logger.debug(`verify federation community: ${dbFedComB.endPoint}${dbFedComB.apiVersion}`)
|
||||
const apiValueStrings: string[] = Object.values(ApiVersionType)
|
||||
logger.debug(`suppported ApiVersions=`, apiValueStrings)
|
||||
if (!apiValueStrings.includes(dbFedComB.apiVersion)) {
|
||||
logger.debug('dbFedComB with unsupported apiVersion', dbFedComB.endPoint, dbFedComB.apiVersion)
|
||||
logger.debug(`supported ApiVersions=`, apiValueStrings)
|
||||
continue
|
||||
}
|
||||
try {
|
||||
const client = FederationClientFactory.getInstance(dbFedComB)
|
||||
|
||||
if (client instanceof V1_0_FederationClient) {
|
||||
const pubKey = await client.getPublicKey()
|
||||
if (pubKey && pubKey === dbFedComB.publicKey.toString('hex')) {
|
||||
// throw if key isn't valid hex with length 64
|
||||
const clientPublicKey = new Ed25519PublicKey(await client.getPublicKey())
|
||||
// throw if key isn't valid hex with length 64
|
||||
const fedComBPublicKey = new Ed25519PublicKey(dbFedComB.publicKey)
|
||||
if (clientPublicKey.isSame(fedComBPublicKey)) {
|
||||
await DbFederatedCommunity.update({ id: dbFedComB.id }, { verifiedAt: new Date() })
|
||||
logger.debug(`verified dbFedComB with:`, dbFedComB.endPoint)
|
||||
// logger.debug(`verified dbFedComB with:`, dbFedComB.endPoint)
|
||||
const pubComInfo = await client.getPublicCommunityInfo()
|
||||
if (pubComInfo) {
|
||||
await writeForeignCommunity(dbFedComB, pubComInfo)
|
||||
logger.debug(`wrote response of getPublicCommunityInfo in dbFedComB ${dbFedComB.endPoint}`)
|
||||
try {
|
||||
await startCommunityAuthentication(dbFedComB)
|
||||
const result = await startCommunityAuthentication(dbFedComB)
|
||||
logger.info(`${dbFedComB.endPoint}${dbFedComB.apiVersion} verified, authentication state: ${result}`)
|
||||
} catch (err) {
|
||||
logger.warn(`Warning: Authentication of community ${dbFedComB.endPoint} still ongoing:`, err)
|
||||
}
|
||||
@ -73,7 +76,7 @@ export async function validateCommunities(): Promise<void> {
|
||||
logger.debug('missing result of getPublicCommunityInfo')
|
||||
}
|
||||
} else {
|
||||
logger.debug('received not matching publicKey:', pubKey, dbFedComB.publicKey.toString('hex'))
|
||||
logger.debug('received not matching publicKey:', clientPublicKey.asHex(), fedComBPublicKey.asHex())
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@ -10,10 +10,14 @@ export class ChatGptMessage {
|
||||
@Field()
|
||||
role: string
|
||||
|
||||
@Field()
|
||||
threadId: string
|
||||
@Field({ nullable: true })
|
||||
threadId?: string
|
||||
|
||||
public constructor(data: Partial<Message>) {
|
||||
@Field()
|
||||
isError: boolean
|
||||
|
||||
public constructor(data: Partial<Message>, isError: boolean = false) {
|
||||
Object.assign(this, data)
|
||||
this.isError = isError
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ export class Community {
|
||||
this.creationDate = dbCom.creationDate
|
||||
this.uuid = dbCom.communityUuid
|
||||
this.authenticatedAt = dbCom.authenticatedAt
|
||||
// this.gmsApiKey = dbCom.gmsApiKey //
|
||||
this.hieroTopicId = dbCom.hieroTopicId
|
||||
}
|
||||
|
||||
@ -40,10 +39,6 @@ export class Community {
|
||||
@Field(() => Date, { nullable: true })
|
||||
authenticatedAt: Date | null
|
||||
|
||||
// gms api key should only seen by admins, they can use AdminCommunityView
|
||||
// @Field(() => String, { nullable: true })
|
||||
// gmsApiKey: string | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
hieroTopicId: string | null
|
||||
}
|
||||
|
||||
@ -15,10 +15,10 @@ export class AiChatResolver {
|
||||
async resumeChat(@Ctx() context: Context): Promise<ChatGptMessage[]> {
|
||||
const openaiClient = OpenaiClient.getInstance()
|
||||
if (!openaiClient) {
|
||||
return Promise.resolve([new ChatGptMessage({ content: 'OpenAI API is not enabled' })])
|
||||
return Promise.resolve([new ChatGptMessage({ content: 'OpenAI API is not enabled', role: 'assistant' }, true)])
|
||||
}
|
||||
if (!context.user) {
|
||||
return Promise.resolve([new ChatGptMessage({ content: 'User not found' })])
|
||||
return Promise.resolve([new ChatGptMessage({ content: 'User not found', role: 'assistant' }, true)])
|
||||
}
|
||||
const messages = await openaiClient.resumeThread(context.user)
|
||||
return messages.map((message) => new ChatGptMessage(message))
|
||||
@ -42,10 +42,10 @@ export class AiChatResolver {
|
||||
): Promise<ChatGptMessage> {
|
||||
const openaiClient = OpenaiClient.getInstance()
|
||||
if (!openaiClient) {
|
||||
return Promise.resolve(new ChatGptMessage({ content: 'OpenAI API is not enabled' }))
|
||||
return Promise.resolve(new ChatGptMessage({ content: 'OpenAI API is not enabled', role: 'assistant' }, true))
|
||||
}
|
||||
if (!context.user) {
|
||||
return Promise.resolve(new ChatGptMessage({ content: 'User not found' }))
|
||||
return Promise.resolve(new ChatGptMessage({ content: 'User not found', role: 'assistant' }, true))
|
||||
}
|
||||
const messageObj = new Message(message)
|
||||
if (!threadId || threadId.length === 0) {
|
||||
|
||||
@ -48,6 +48,9 @@ beforeAll(async () => {
|
||||
query = testEnv.query
|
||||
con = testEnv.con
|
||||
await cleanDB()
|
||||
// reset id auto increment
|
||||
await DbCommunity.clear()
|
||||
await DbFederatedCommunity.clear()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
@ -4,7 +4,6 @@ import {
|
||||
getHomeCommunity
|
||||
} from 'database'
|
||||
import { Arg, Args, Authorized, Mutation, Query, Resolver } from 'type-graphql'
|
||||
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { EditCommunityInput } from '@input/EditCommunityInput'
|
||||
import { AdminCommunityView } from '@model/AdminCommunityView'
|
||||
@ -19,6 +18,7 @@ import {
|
||||
getCommunityByIdentifier,
|
||||
getCommunityByUuid,
|
||||
} from './util/communities'
|
||||
import { updateAllDefinedAndChanged } from 'shared'
|
||||
|
||||
import { CONFIG } from '@/config'
|
||||
|
||||
@ -78,24 +78,22 @@ export class CommunityResolver {
|
||||
if (homeCom.foreign) {
|
||||
throw new LogError('Error: Only the HomeCommunity could be modified!')
|
||||
}
|
||||
|
||||
if (
|
||||
homeCom.gmsApiKey !== gmsApiKey ||
|
||||
homeCom.location !== location ||
|
||||
homeCom.hieroTopicId !== hieroTopicId
|
||||
) {
|
||||
// TODO: think about this, it is really expected to delete gmsApiKey if no new one is given?
|
||||
homeCom.gmsApiKey = gmsApiKey ?? null
|
||||
if (location) {
|
||||
homeCom.location = Location2Point(location)
|
||||
}
|
||||
// update only with new value, don't overwrite existing value with null or undefined!
|
||||
if (hieroTopicId) {
|
||||
homeCom.hieroTopicId = hieroTopicId
|
||||
let updated = false
|
||||
// if location is undefined, it should not be changed
|
||||
// if location is null, it should be set to null
|
||||
if (typeof location !== 'undefined') {
|
||||
const newLocation = location ? Location2Point(location) : null
|
||||
if (newLocation !== homeCom.location) {
|
||||
homeCom.location = newLocation
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
if (updateAllDefinedAndChanged(homeCom, { gmsApiKey, hieroTopicId })) {
|
||||
updated = true
|
||||
}
|
||||
if (updated) {
|
||||
await DbCommunity.save(homeCom)
|
||||
}
|
||||
|
||||
return new AdminCommunityView(homeCom)
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,11 +37,14 @@ import { TRANSACTIONS_LOCK } from 'database'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { getLogger } from 'config-schema/test/testSetup'
|
||||
import { transactionLinkCode } from './TransactionLinkResolver'
|
||||
import { CONFIG } from '@/config'
|
||||
|
||||
const logErrorLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
|
||||
|
||||
jest.mock('@/password/EncryptorUtils')
|
||||
|
||||
CONFIG.DLT_CONNECTOR = false
|
||||
|
||||
// mock semaphore to allow use fake timers
|
||||
jest.mock('database/src/util/TRANSACTIONS_LOCK')
|
||||
TRANSACTIONS_LOCK.acquire = jest.fn().mockResolvedValue(jest.fn())
|
||||
|
||||
@ -15,7 +15,8 @@ import { QueryLinkResult } from '@union/QueryLinkResult'
|
||||
import { Decay, interpretEncryptedTransferArgs, TransactionTypeId } from 'core'
|
||||
import {
|
||||
AppDatabase, Contribution as DbContribution,
|
||||
ContributionLink as DbContributionLink, FederatedCommunity as DbFederatedCommunity,
|
||||
ContributionLink as DbContributionLink,
|
||||
FederatedCommunity as DbFederatedCommunity,
|
||||
DltTransaction as DbDltTransaction,
|
||||
Transaction as DbTransaction,
|
||||
TransactionLink as DbTransactionLink,
|
||||
@ -39,8 +40,16 @@ import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
|
||||
import { calculateBalance } from '@/util/validate'
|
||||
import { fullName } from 'core'
|
||||
import { TRANSACTION_LINK_LOCK, TRANSACTIONS_LOCK } from 'database'
|
||||
import { calculateDecay, decode, DisburseJwtPayloadType, encode, encryptAndSign, RedeemJwtPayloadType, verify } from 'shared'
|
||||
|
||||
import {
|
||||
calculateDecay,
|
||||
compoundInterest,
|
||||
decode,
|
||||
DisburseJwtPayloadType,
|
||||
encode,
|
||||
encryptAndSign,
|
||||
RedeemJwtPayloadType,
|
||||
verify
|
||||
} from 'shared'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { DisbursementClient as V1_0_DisbursementClient } from '@/federation/client/1_0/DisbursementClient'
|
||||
import { DisbursementClientFactory } from '@/federation/client/DisbursementClientFactory'
|
||||
@ -93,7 +102,7 @@ export class TransactionLinkResolver {
|
||||
const createdDate = new Date()
|
||||
const validUntil = transactionLinkExpireDate(createdDate)
|
||||
|
||||
const holdAvailableAmount = amount.minus(calculateDecay(amount, createdDate, validUntil).decay)
|
||||
const holdAvailableAmount = compoundInterest(amount, CODE_VALID_DAYS_DURATION * 24 * 60 * 60)
|
||||
|
||||
// validate amount
|
||||
const sendBalance = await calculateBalance(user.id, holdAvailableAmount.mul(-1), createdDate)
|
||||
@ -387,8 +396,8 @@ export class TransactionLinkResolver {
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
const releaseLinkLock = await TRANSACTION_LINK_LOCK.acquire()
|
||||
const now = new Date()
|
||||
const releaseLinkLock = await TRANSACTION_LINK_LOCK.acquire()
|
||||
try {
|
||||
const transactionLink = await DbTransactionLink.findOne({ where: { code } })
|
||||
if (!transactionLink) {
|
||||
|
||||
@ -33,10 +33,13 @@ import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
|
||||
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||
import { stephenHawking } from '@/seeds/users/stephen-hawking'
|
||||
import { getLogger } from 'config-schema/test/testSetup'
|
||||
import { CONFIG } from '@/config'
|
||||
|
||||
jest.mock('@/password/EncryptorUtils')
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
|
||||
CONFIG.DLT_CONNECTOR = false
|
||||
CONFIG.EMAIL = false
|
||||
|
||||
let mutate: ApolloServerTestClient['mutate']
|
||||
let query: ApolloServerTestClient['query']
|
||||
@ -434,50 +437,6 @@ describe('send coins', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('sendTransactionsToDltConnector', () => {
|
||||
let transaction: Transaction[]
|
||||
let dltTransactions: DltTransaction[]
|
||||
beforeAll(async () => {
|
||||
// Find the previous created transactions of sendCoin mutation
|
||||
transaction = await Transaction.find({
|
||||
where: { memo: 'unrepeatable memo' },
|
||||
order: { balanceDate: 'ASC', id: 'ASC' },
|
||||
})
|
||||
|
||||
// and read aslong as all async created dlt-transactions are finished
|
||||
do {
|
||||
dltTransactions = await DltTransaction.find({
|
||||
where: { transactionId: In([transaction[0].id, transaction[1].id]) },
|
||||
// relations: ['transaction'],
|
||||
// order: { createdAt: 'ASC', id: 'ASC' },
|
||||
})
|
||||
} while (transaction.length > dltTransactions.length)
|
||||
})
|
||||
|
||||
it('has wait till sendTransactionsToDltConnector created all dlt-transactions', () => {
|
||||
expect(dltTransactions).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
transactionId: transaction[0].id,
|
||||
messageId: null,
|
||||
verified: false,
|
||||
createdAt: expect.any(Date),
|
||||
verifiedAt: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
transactionId: transaction[1].id,
|
||||
messageId: null,
|
||||
verified: false,
|
||||
createdAt: expect.any(Date),
|
||||
verifiedAt: null,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('send coins via gradido ID', () => {
|
||||
it('sends the coins', async () => {
|
||||
|
||||
@ -58,6 +58,8 @@ export const executeTransaction = async (
|
||||
logger: Logger,
|
||||
transactionLink?: dbTransactionLink | null,
|
||||
): Promise<boolean> => {
|
||||
// acquire lock
|
||||
const releaseLock = await TRANSACTIONS_LOCK.acquire()
|
||||
const receivedCallDate = new Date()
|
||||
let dltTransactionPromise: Promise<DbDltTransaction | null> = Promise.resolve(null)
|
||||
if (!transactionLink) {
|
||||
@ -66,11 +68,8 @@ export const executeTransaction = async (
|
||||
dltTransactionPromise = redeemDeferredTransferTransaction(transactionLink, amount.toString(), receivedCallDate, recipient)
|
||||
}
|
||||
|
||||
// acquire lock
|
||||
const releaseLock = await TRANSACTIONS_LOCK.acquire()
|
||||
|
||||
try {
|
||||
logger.info('executeTransaction', memo)
|
||||
logger.info('executeTransaction', amount, memo, sender, recipient)
|
||||
|
||||
if (await countOpenPendingTransactions([sender.gradidoID, recipient.gradidoID]) > 0) {
|
||||
throw new LogError(
|
||||
@ -89,7 +88,7 @@ export const executeTransaction = async (
|
||||
receivedCallDate,
|
||||
transactionLink,
|
||||
)
|
||||
logger.debug(`calculated balance=${sendBalance?.balance.toString()} decay=${sendBalance?.decay.decay.toString()} lastTransactionId=${sendBalance?.lastTransactionId}`)
|
||||
logger.debug(`calculated Balance=${sendBalance}`)
|
||||
if (!sendBalance) {
|
||||
throw new LogError('User has not enough GDD or amount is < 0', sendBalance)
|
||||
}
|
||||
@ -148,7 +147,7 @@ export const executeTransaction = async (
|
||||
// Save linked transaction id for send
|
||||
transactionSend.linkedTransactionId = transactionReceive.id
|
||||
await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend)
|
||||
logger.debug('send Transaction updated', new TransactionLoggingView(transactionSend).toJSON())
|
||||
logger.debug('send Transaction updated', transactionSend)
|
||||
|
||||
if (transactionLink) {
|
||||
logger.info('transactionLink', transactionLink)
|
||||
@ -162,8 +161,10 @@ export const executeTransaction = async (
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction()
|
||||
logger.info(`commit Transaction successful...`)
|
||||
|
||||
await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount)
|
||||
|
||||
await EVENT_TRANSACTION_RECEIVE(
|
||||
recipient,
|
||||
sender,
|
||||
@ -185,9 +186,6 @@ export const executeTransaction = async (
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
|
||||
// notify dlt-connector loop for new work
|
||||
// InterruptiveSleepManager.getInstance().interrupt(TRANSMIT_TO_IOTA_INTERRUPTIVE_SLEEP_KEY)
|
||||
await sendTransactionReceivedEmail({
|
||||
firstName: recipient.firstName,
|
||||
lastName: recipient.lastName,
|
||||
@ -464,7 +462,7 @@ export class TransactionResolver {
|
||||
recipientCommunityIdentifier,
|
||||
)
|
||||
if (!recipientUser) {
|
||||
throw new LogError('The recipient user was not found', { recipientIdentifier, recipientCommunityIdentifier })
|
||||
throw new LogError('The recipient user was not found', recipientUser)
|
||||
}
|
||||
logger.addContext('to', recipientUser?.id)
|
||||
if (recipientUser.foreign) {
|
||||
|
||||
@ -117,6 +117,7 @@ beforeAll(async () => {
|
||||
query = testEnv.query
|
||||
con = testEnv.con
|
||||
CONFIG.HUMHUB_ACTIVE = false
|
||||
CONFIG.DLT_CONNECTOR = false
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ import {
|
||||
Root,
|
||||
} from 'type-graphql'
|
||||
import { IRestResponse } from 'typed-rest-client'
|
||||
import { EntityNotFoundError, In, Point } from 'typeorm'
|
||||
import { EntityManager, EntityNotFoundError, In, Point } from 'typeorm'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { UserArgs } from '@arg//UserArgs'
|
||||
@ -105,6 +105,7 @@ import { sendUserToGms } from './util/sendUserToGms'
|
||||
import { syncHumhub } from './util/syncHumhub'
|
||||
import { validateAlias } from 'core'
|
||||
import { registerAddressTransaction } from '@/apis/dltConnector'
|
||||
import { updateAllDefinedAndChanged } from 'shared'
|
||||
|
||||
const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl']
|
||||
const DEFAULT_LANGUAGE = 'de'
|
||||
@ -739,18 +740,22 @@ export class UserResolver {
|
||||
user.humhubPublishName as PublishNameType,
|
||||
)
|
||||
|
||||
// try {
|
||||
if (firstName) {
|
||||
user.firstName = firstName
|
||||
}
|
||||
|
||||
if (lastName) {
|
||||
user.lastName = lastName
|
||||
}
|
||||
let updated = updateAllDefinedAndChanged(user, {
|
||||
firstName,
|
||||
lastName,
|
||||
hideAmountGDD,
|
||||
hideAmountGDT,
|
||||
humhubAllowed,
|
||||
gmsAllowed,
|
||||
gmsPublishName: gmsPublishName?.valueOf(),
|
||||
humhubPublishName: humhubPublishName?.valueOf(),
|
||||
gmsPublishLocation: gmsPublishLocation?.valueOf(),
|
||||
})
|
||||
|
||||
// currently alias can only be set, not updated
|
||||
if (alias && !user.alias && (await validateAlias(alias))) {
|
||||
user.alias = alias
|
||||
updated = true
|
||||
}
|
||||
|
||||
if (language) {
|
||||
@ -760,6 +765,7 @@ export class UserResolver {
|
||||
}
|
||||
user.language = language
|
||||
i18n.setLocale(language)
|
||||
updated = true
|
||||
}
|
||||
|
||||
if (password && passwordNew) {
|
||||
@ -780,55 +786,28 @@ export class UserResolver {
|
||||
// Save new password hash and newly encrypted private key
|
||||
user.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
|
||||
user.password = await encryptPassword(user, passwordNew)
|
||||
updated = true
|
||||
}
|
||||
|
||||
// Save hideAmountGDD value
|
||||
if (hideAmountGDD !== undefined) {
|
||||
user.hideAmountGDD = hideAmountGDD
|
||||
}
|
||||
// Save hideAmountGDT value
|
||||
if (hideAmountGDT !== undefined) {
|
||||
user.hideAmountGDT = hideAmountGDT
|
||||
}
|
||||
if (humhubAllowed !== undefined) {
|
||||
user.humhubAllowed = humhubAllowed
|
||||
}
|
||||
if (gmsAllowed !== undefined) {
|
||||
user.gmsAllowed = gmsAllowed
|
||||
}
|
||||
if (gmsPublishName !== null && gmsPublishName !== undefined) {
|
||||
user.gmsPublishName = gmsPublishName
|
||||
}
|
||||
if (humhubPublishName !== null && humhubPublishName !== undefined) {
|
||||
user.humhubPublishName = humhubPublishName
|
||||
}
|
||||
if (gmsLocation) {
|
||||
user.location = Location2Point(gmsLocation)
|
||||
updated = true
|
||||
}
|
||||
if (gmsPublishLocation !== null && gmsPublishLocation !== undefined) {
|
||||
user.gmsPublishLocation = gmsPublishLocation
|
||||
|
||||
// early exit if no update was made
|
||||
if (!updated) {
|
||||
return true
|
||||
}
|
||||
// } catch (err) {
|
||||
// console.log('error:', err)
|
||||
// }
|
||||
const queryRunner = db.getDataSource().createQueryRunner()
|
||||
await queryRunner.connect()
|
||||
await queryRunner.startTransaction('REPEATABLE READ')
|
||||
|
||||
try {
|
||||
await queryRunner.manager.save(user).catch((error) => {
|
||||
throw new LogError('Error saving user', error)
|
||||
})
|
||||
|
||||
await queryRunner.commitTransaction()
|
||||
logger.debug('writing User data successful...', new UserLoggingView(user))
|
||||
} catch (e) {
|
||||
await queryRunner.rollbackTransaction()
|
||||
throw new LogError('Error on writing updated user data', e)
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
await DbUser.save(user)
|
||||
} catch (error) {
|
||||
const errorMessage = 'Error saving user'
|
||||
logger.error(errorMessage, error)
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
logger.info('updateUserInfos() successfully finished...')
|
||||
logger.debug('writing User data successful...', new UserLoggingView(user))
|
||||
await EVENT_USER_INFO_UPDATE(user)
|
||||
|
||||
// validate if user settings are changed with relevance to update gms-user
|
||||
|
||||
@ -21,9 +21,14 @@ import {
|
||||
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||
import { bobBaumeister } from '@/seeds/users/bob-baumeister'
|
||||
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||
import { CONFIG } from '@/config'
|
||||
import { TRANSACTIONS_LOCK } from 'database'
|
||||
|
||||
jest.mock('@/password/EncryptorUtils')
|
||||
|
||||
CONFIG.DLT_CONNECTOR = false
|
||||
CONFIG.EMAIL = false
|
||||
|
||||
let mutate: ApolloServerTestClient['mutate']
|
||||
let con: DataSource
|
||||
let testEnv: {
|
||||
@ -44,7 +49,43 @@ afterAll(async () => {
|
||||
await con.destroy()
|
||||
})
|
||||
|
||||
type RunOrder = { [key: number]: { start: number, end: number } }
|
||||
async function fakeWork(runOrder: RunOrder, index: number) {
|
||||
const releaseLock = await TRANSACTIONS_LOCK.acquire()
|
||||
const startDate = new Date()
|
||||
await new Promise((resolve) => setTimeout(resolve, Math.random() * 50))
|
||||
const endDate = new Date()
|
||||
runOrder[index] = { start: startDate.getTime(), end: endDate.getTime() }
|
||||
releaseLock()
|
||||
}
|
||||
|
||||
describe('semaphore', () => {
|
||||
it("didn't should run in parallel", async () => {
|
||||
const runOrder: RunOrder = {}
|
||||
await Promise.all([
|
||||
fakeWork(runOrder, 1),
|
||||
fakeWork(runOrder, 2),
|
||||
fakeWork(runOrder, 3),
|
||||
fakeWork(runOrder, 4),
|
||||
fakeWork(runOrder, 5),
|
||||
])
|
||||
expect(runOrder[1].start).toBeLessThan(runOrder[1].end)
|
||||
expect(runOrder[1].start).toBeLessThan(runOrder[2].start)
|
||||
expect(runOrder[2].start).toBeLessThan(runOrder[2].end)
|
||||
expect(runOrder[2].start).toBeLessThan(runOrder[3].start)
|
||||
expect(runOrder[3].start).toBeLessThan(runOrder[3].end)
|
||||
expect(runOrder[3].start).toBeLessThan(runOrder[4].start)
|
||||
expect(runOrder[4].start).toBeLessThan(runOrder[4].end)
|
||||
expect(runOrder[4].start).toBeLessThan(runOrder[5].start)
|
||||
expect(runOrder[5].start).toBeLessThan(runOrder[5].end)
|
||||
expect(runOrder[1].end).toBeLessThan(runOrder[2].end)
|
||||
expect(runOrder[2].end).toBeLessThan(runOrder[3].end)
|
||||
expect(runOrder[3].end).toBeLessThan(runOrder[4].end)
|
||||
expect(runOrder[4].end).toBeLessThan(runOrder[5].end)
|
||||
})
|
||||
})
|
||||
|
||||
describe('semaphore fullstack', () => {
|
||||
let contributionLinkCode = ''
|
||||
let bobsTransactionLinkCode = ''
|
||||
let bibisTransactionLinkCode = ''
|
||||
|
||||
@ -1,799 +0,0 @@
|
||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||
import { Community, DltTransaction, Transaction } from 'database'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
// import { GraphQLClient } from 'graphql-request'
|
||||
// import { Response } from 'graphql-request/dist/types'
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { Response } from 'graphql-request/dist/types'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { cleanDB, testEnvironment } from '@test/helpers'
|
||||
import { i18n as localization } from '@test/testSetup'
|
||||
|
||||
import { CONFIG } from '@/config'
|
||||
import { TransactionTypeId } from 'core'
|
||||
import { creations } from '@/seeds/creation'
|
||||
import { creationFactory } from '@/seeds/factory/creation'
|
||||
import { userFactory } from '@/seeds/factory/user'
|
||||
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||
import { bobBaumeister } from '@/seeds/users/bob-baumeister'
|
||||
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||
import { raeuberHotzenplotz } from '@/seeds/users/raeuber-hotzenplotz'
|
||||
import { getLogger } from 'config-schema/test/testSetup'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
|
||||
import { sendTransactionsToDltConnector } from './sendTransactionsToDltConnector'
|
||||
|
||||
jest.mock('@/password/EncryptorUtils')
|
||||
|
||||
const logger = getLogger(
|
||||
`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.sendTransactionsToDltConnector`,
|
||||
)
|
||||
|
||||
/*
|
||||
// Mock the GraphQLClient
|
||||
jest.mock('graphql-request', () => {
|
||||
const originalModule = jest.requireActual('graphql-request')
|
||||
|
||||
let testCursor = 0
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
GraphQLClient: jest.fn().mockImplementation((url: string) => {
|
||||
if (url === 'invalid') {
|
||||
throw new Error('invalid url')
|
||||
}
|
||||
return {
|
||||
// why not using mockResolvedValueOnce or mockReturnValueOnce?
|
||||
// I have tried, but it didn't work and return every time the first value
|
||||
request: jest.fn().mockImplementation(() => {
|
||||
testCursor++
|
||||
if (testCursor === 4) {
|
||||
return Promise.resolve(
|
||||
// invalid, is 33 Bytes long as binary
|
||||
{
|
||||
transmitTransaction: {
|
||||
dltTransactionIdHex:
|
||||
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516212A',
|
||||
},
|
||||
},
|
||||
)
|
||||
} else if (testCursor === 5) {
|
||||
throw Error('Connection error')
|
||||
} else {
|
||||
return Promise.resolve(
|
||||
// valid, is 32 Bytes long as binary
|
||||
{
|
||||
transmitTransaction: {
|
||||
dltTransactionIdHex:
|
||||
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}),
|
||||
}
|
||||
}),
|
||||
}
|
||||
})
|
||||
let mutate: ApolloServerTestClient['mutate'],
|
||||
query: ApolloServerTestClient['query'],
|
||||
con: Connection
|
||||
let testEnv: {
|
||||
mutate: ApolloServerTestClient['mutate']
|
||||
query: ApolloServerTestClient['query']
|
||||
con: Connection
|
||||
}
|
||||
*/
|
||||
|
||||
async function createHomeCommunity(): Promise<Community> {
|
||||
const homeCommunity = Community.create()
|
||||
homeCommunity.foreign = false
|
||||
homeCommunity.communityUuid = uuidv4()
|
||||
homeCommunity.url = 'localhost'
|
||||
homeCommunity.publicKey = Buffer.from('0x6e6a6c6d6feffe', 'hex')
|
||||
await Community.save(homeCommunity)
|
||||
return homeCommunity
|
||||
}
|
||||
|
||||
async function createTxCREATION1(verified: boolean): Promise<Transaction> {
|
||||
let tx = Transaction.create()
|
||||
tx.amount = new Decimal(1000)
|
||||
tx.balance = new Decimal(100)
|
||||
tx.balanceDate = new Date('01.01.2023 00:00:00')
|
||||
tx.memo = 'txCREATION1'
|
||||
tx.typeId = TransactionTypeId.CREATION
|
||||
tx.userGradidoID = 'txCREATION1.userGradidoID'
|
||||
tx.userId = 1
|
||||
tx.userName = 'txCREATION 1'
|
||||
tx = await Transaction.save(tx)
|
||||
|
||||
if (verified) {
|
||||
const dlttx = DltTransaction.create()
|
||||
dlttx.createdAt = new Date('01.01.2023 00:00:10')
|
||||
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c1'
|
||||
dlttx.transactionId = tx.id
|
||||
dlttx.verified = true
|
||||
dlttx.verifiedAt = new Date('01.01.2023 00:01:10')
|
||||
await DltTransaction.save(dlttx)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
async function createTxCREATION2(verified: boolean): Promise<Transaction> {
|
||||
let tx = Transaction.create()
|
||||
tx.amount = new Decimal(1000)
|
||||
tx.balance = new Decimal(200)
|
||||
tx.balanceDate = new Date('02.01.2023 00:00:00')
|
||||
tx.memo = 'txCREATION2'
|
||||
tx.typeId = TransactionTypeId.CREATION
|
||||
tx.userGradidoID = 'txCREATION2.userGradidoID'
|
||||
tx.userId = 2
|
||||
tx.userName = 'txCREATION 2'
|
||||
tx = await Transaction.save(tx)
|
||||
|
||||
if (verified) {
|
||||
const dlttx = DltTransaction.create()
|
||||
dlttx.createdAt = new Date('02.01.2023 00:00:10')
|
||||
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c2'
|
||||
dlttx.transactionId = tx.id
|
||||
dlttx.verified = true
|
||||
dlttx.verifiedAt = new Date('02.01.2023 00:01:10')
|
||||
await DltTransaction.save(dlttx)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
async function createTxCREATION3(verified: boolean): Promise<Transaction> {
|
||||
let tx = Transaction.create()
|
||||
tx.amount = new Decimal(1000)
|
||||
tx.balance = new Decimal(300)
|
||||
tx.balanceDate = new Date('03.01.2023 00:00:00')
|
||||
tx.memo = 'txCREATION3'
|
||||
tx.typeId = TransactionTypeId.CREATION
|
||||
tx.userGradidoID = 'txCREATION3.userGradidoID'
|
||||
tx.userId = 3
|
||||
tx.userName = 'txCREATION 3'
|
||||
tx = await Transaction.save(tx)
|
||||
|
||||
if (verified) {
|
||||
const dlttx = DltTransaction.create()
|
||||
dlttx.createdAt = new Date('03.01.2023 00:00:10')
|
||||
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c3'
|
||||
dlttx.transactionId = tx.id
|
||||
dlttx.verified = true
|
||||
dlttx.verifiedAt = new Date('03.01.2023 00:01:10')
|
||||
await DltTransaction.save(dlttx)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
async function createTxSend1ToReceive2(verified: boolean): Promise<Transaction> {
|
||||
let tx = Transaction.create()
|
||||
tx.amount = new Decimal(100)
|
||||
tx.balance = new Decimal(1000)
|
||||
tx.balanceDate = new Date('11.01.2023 00:00:00')
|
||||
tx.memo = 'txSEND1 to txRECEIVE2'
|
||||
tx.typeId = TransactionTypeId.SEND
|
||||
tx.userGradidoID = 'txSEND1.userGradidoID'
|
||||
tx.userId = 1
|
||||
tx.userName = 'txSEND 1'
|
||||
tx.linkedUserGradidoID = 'txRECEIVE2.linkedUserGradidoID'
|
||||
tx.linkedUserId = 2
|
||||
tx.linkedUserName = 'txRECEIVE 2'
|
||||
tx = await Transaction.save(tx)
|
||||
|
||||
if (verified) {
|
||||
const dlttx = DltTransaction.create()
|
||||
dlttx.createdAt = new Date('11.01.2023 00:00:10')
|
||||
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516a1'
|
||||
dlttx.transactionId = tx.id
|
||||
dlttx.verified = true
|
||||
dlttx.verifiedAt = new Date('11.01.2023 00:01:10')
|
||||
await DltTransaction.save(dlttx)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
async function createTxReceive2FromSend1(verified: boolean): Promise<Transaction> {
|
||||
let tx = Transaction.create()
|
||||
tx.amount = new Decimal(100)
|
||||
tx.balance = new Decimal(1300)
|
||||
tx.balanceDate = new Date('11.01.2023 00:00:00')
|
||||
tx.memo = 'txSEND1 to txRECEIVE2'
|
||||
tx.typeId = TransactionTypeId.RECEIVE
|
||||
tx.userGradidoID = 'txRECEIVE2.linkedUserGradidoID'
|
||||
tx.userId = 2
|
||||
tx.userName = 'txRECEIVE 2'
|
||||
tx.linkedUserGradidoID = 'txSEND1.userGradidoID'
|
||||
tx.linkedUserId = 1
|
||||
tx.linkedUserName = 'txSEND 1'
|
||||
tx = await Transaction.save(tx)
|
||||
|
||||
if (verified) {
|
||||
const dlttx = DltTransaction.create()
|
||||
dlttx.createdAt = new Date('11.01.2023 00:00:10')
|
||||
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516b2'
|
||||
dlttx.transactionId = tx.id
|
||||
dlttx.verified = true
|
||||
dlttx.verifiedAt = new Date('11.01.2023 00:01:10')
|
||||
await DltTransaction.save(dlttx)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
/*
|
||||
async function createTxSend2ToReceive3(verified: boolean): Promise<Transaction> {
|
||||
let tx = Transaction.create()
|
||||
tx.amount = new Decimal(200)
|
||||
tx.balance = new Decimal(1100)
|
||||
tx.balanceDate = new Date('23.01.2023 00:00:00')
|
||||
tx.memo = 'txSEND2 to txRECEIVE3'
|
||||
tx.typeId = TransactionTypeId.SEND
|
||||
tx.userGradidoID = 'txSEND2.userGradidoID'
|
||||
tx.userId = 2
|
||||
tx.userName = 'txSEND 2'
|
||||
tx.linkedUserGradidoID = 'txRECEIVE3.linkedUserGradidoID'
|
||||
tx.linkedUserId = 3
|
||||
tx.linkedUserName = 'txRECEIVE 3'
|
||||
tx = await Transaction.save(tx)
|
||||
|
||||
if (verified) {
|
||||
const dlttx = DltTransaction.create()
|
||||
dlttx.createdAt = new Date('23.01.2023 00:00:10')
|
||||
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516a2'
|
||||
dlttx.transactionId = tx.id
|
||||
dlttx.verified = true
|
||||
dlttx.verifiedAt = new Date('23.01.2023 00:01:10')
|
||||
await DltTransaction.save(dlttx)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
async function createTxReceive3FromSend2(verified: boolean): Promise<Transaction> {
|
||||
let tx = Transaction.create()
|
||||
tx.amount = new Decimal(200)
|
||||
tx.balance = new Decimal(1500)
|
||||
tx.balanceDate = new Date('23.01.2023 00:00:00')
|
||||
tx.memo = 'txSEND2 to txRECEIVE3'
|
||||
tx.typeId = TransactionTypeId.RECEIVE
|
||||
tx.userGradidoID = 'txRECEIVE3.linkedUserGradidoID'
|
||||
tx.userId = 3
|
||||
tx.userName = 'txRECEIVE 3'
|
||||
tx.linkedUserGradidoID = 'txSEND2.userGradidoID'
|
||||
tx.linkedUserId = 2
|
||||
tx.linkedUserName = 'txSEND 2'
|
||||
tx = await Transaction.save(tx)
|
||||
|
||||
if (verified) {
|
||||
const dlttx = DltTransaction.create()
|
||||
dlttx.createdAt = new Date('23.01.2023 00:00:10')
|
||||
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516b3'
|
||||
dlttx.transactionId = tx.id
|
||||
dlttx.verified = true
|
||||
dlttx.verifiedAt = new Date('23.01.2023 00:01:10')
|
||||
await DltTransaction.save(dlttx)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
async function createTxSend3ToReceive1(verified: boolean): Promise<Transaction> {
|
||||
let tx = Transaction.create()
|
||||
tx.amount = new Decimal(300)
|
||||
tx.balance = new Decimal(1200)
|
||||
tx.balanceDate = new Date('31.01.2023 00:00:00')
|
||||
tx.memo = 'txSEND3 to txRECEIVE1'
|
||||
tx.typeId = TransactionTypeId.SEND
|
||||
tx.userGradidoID = 'txSEND3.userGradidoID'
|
||||
tx.userId = 3
|
||||
tx.userName = 'txSEND 3'
|
||||
tx.linkedUserGradidoID = 'txRECEIVE1.linkedUserGradidoID'
|
||||
tx.linkedUserId = 1
|
||||
tx.linkedUserName = 'txRECEIVE 1'
|
||||
tx = await Transaction.save(tx)
|
||||
|
||||
if (verified) {
|
||||
const dlttx = DltTransaction.create()
|
||||
dlttx.createdAt = new Date('31.01.2023 00:00:10')
|
||||
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516a3'
|
||||
dlttx.transactionId = tx.id
|
||||
dlttx.verified = true
|
||||
dlttx.verifiedAt = new Date('31.01.2023 00:01:10')
|
||||
await DltTransaction.save(dlttx)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
async function createTxReceive1FromSend3(verified: boolean): Promise<Transaction> {
|
||||
let tx = Transaction.create()
|
||||
tx.amount = new Decimal(300)
|
||||
tx.balance = new Decimal(1300)
|
||||
tx.balanceDate = new Date('31.01.2023 00:00:00')
|
||||
tx.memo = 'txSEND3 to txRECEIVE1'
|
||||
tx.typeId = TransactionTypeId.RECEIVE
|
||||
tx.userGradidoID = 'txRECEIVE1.linkedUserGradidoID'
|
||||
tx.userId = 1
|
||||
tx.userName = 'txRECEIVE 1'
|
||||
tx.linkedUserGradidoID = 'txSEND3.userGradidoID'
|
||||
tx.linkedUserId = 3
|
||||
tx.linkedUserName = 'txSEND 3'
|
||||
tx = await Transaction.save(tx)
|
||||
|
||||
if (verified) {
|
||||
const dlttx = DltTransaction.create()
|
||||
dlttx.createdAt = new Date('31.01.2023 00:00:10')
|
||||
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516b1'
|
||||
dlttx.transactionId = tx.id
|
||||
dlttx.verified = true
|
||||
dlttx.verifiedAt = new Date('31.01.2023 00:01:10')
|
||||
await DltTransaction.save(dlttx)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
*/
|
||||
|
||||
let con: DataSource
|
||||
let testEnv: {
|
||||
mutate: ApolloServerTestClient['mutate']
|
||||
query: ApolloServerTestClient['query']
|
||||
con: DataSource
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
testEnv = await testEnvironment(logger, localization)
|
||||
con = testEnv.con
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDB()
|
||||
await con.destroy()
|
||||
})
|
||||
|
||||
describe('create and send Transactions to DltConnector', () => {
|
||||
let txCREATION1: Transaction
|
||||
let txCREATION2: Transaction
|
||||
let txCREATION3: Transaction
|
||||
let txSEND1to2: Transaction
|
||||
let txRECEIVE2From1: Transaction
|
||||
// let txSEND2To3: Transaction
|
||||
// let txRECEIVE3From2: Transaction
|
||||
// let txSEND3To1: Transaction
|
||||
// let txRECEIVE1From3: Transaction
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanDB()
|
||||
})
|
||||
|
||||
describe('with 3 creations but inactive dlt-connector', () => {
|
||||
it('found 3 dlt-transactions', async () => {
|
||||
txCREATION1 = await createTxCREATION1(false)
|
||||
txCREATION2 = await createTxCREATION2(false)
|
||||
txCREATION3 = await createTxCREATION3(false)
|
||||
await createHomeCommunity()
|
||||
|
||||
CONFIG.DLT_CONNECTOR = false
|
||||
await sendTransactionsToDltConnector()
|
||||
expect(logger.info).toBeCalledWith('sendTransactionsToDltConnector...')
|
||||
|
||||
// Find the previous created transactions of sendCoin mutation
|
||||
const transactions = await Transaction.find({
|
||||
// where: { memo: 'unrepeatable memo' },
|
||||
order: { balanceDate: 'ASC', id: 'ASC' },
|
||||
})
|
||||
|
||||
const dltTransactions = await DltTransaction.find({
|
||||
// where: { transactionId: In([transaction[0].id, transaction[1].id]) },
|
||||
// relations: ['transaction'],
|
||||
order: { createdAt: 'ASC', id: 'ASC' },
|
||||
})
|
||||
|
||||
expect(dltTransactions).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
transactionId: transactions[0].id,
|
||||
messageId: null,
|
||||
verified: false,
|
||||
createdAt: expect.any(Date),
|
||||
verifiedAt: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
transactionId: transactions[1].id,
|
||||
messageId: null,
|
||||
verified: false,
|
||||
createdAt: expect.any(Date),
|
||||
verifiedAt: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
transactionId: transactions[2].id,
|
||||
messageId: null,
|
||||
verified: false,
|
||||
createdAt: expect.any(Date),
|
||||
verifiedAt: null,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
|
||||
expect(logger.info).nthCalledWith(2, 'sending to DltConnector currently not configured...')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with 3 creations and active dlt-connector', () => {
|
||||
it('found 3 dlt-transactions', async () => {
|
||||
await userFactory(testEnv, bibiBloxberg)
|
||||
await userFactory(testEnv, peterLustig)
|
||||
await userFactory(testEnv, raeuberHotzenplotz)
|
||||
await userFactory(testEnv, bobBaumeister)
|
||||
let count = 0
|
||||
for (const creation of creations) {
|
||||
await creationFactory(testEnv, creation)
|
||||
count++
|
||||
// we need only 3 for testing
|
||||
if (count >= 3) {
|
||||
break
|
||||
}
|
||||
}
|
||||
await createHomeCommunity()
|
||||
|
||||
CONFIG.DLT_CONNECTOR = true
|
||||
|
||||
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
|
||||
return {
|
||||
data: {
|
||||
sendTransaction: { succeed: true },
|
||||
},
|
||||
} as Response<unknown>
|
||||
})
|
||||
|
||||
await sendTransactionsToDltConnector()
|
||||
|
||||
expect(logger.info).toBeCalledWith('sendTransactionsToDltConnector...')
|
||||
|
||||
// Find the previous created transactions of sendCoin mutation
|
||||
const transactions = await Transaction.find({
|
||||
// where: { memo: 'unrepeatable memo' },
|
||||
order: { balanceDate: 'ASC', id: 'ASC' },
|
||||
})
|
||||
|
||||
const dltTransactions = await DltTransaction.find({
|
||||
// where: { transactionId: In([transaction[0].id, transaction[1].id]) },
|
||||
// relations: ['transaction'],
|
||||
order: { createdAt: 'ASC', id: 'ASC' },
|
||||
})
|
||||
|
||||
expect(dltTransactions).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
transactionId: transactions[0].id,
|
||||
messageId: 'sended',
|
||||
verified: false,
|
||||
createdAt: expect.any(Date),
|
||||
verifiedAt: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
transactionId: transactions[1].id,
|
||||
messageId: 'sended',
|
||||
verified: false,
|
||||
createdAt: expect.any(Date),
|
||||
verifiedAt: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
transactionId: transactions[2].id,
|
||||
messageId: 'sended',
|
||||
verified: false,
|
||||
createdAt: expect.any(Date),
|
||||
verifiedAt: null,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with 3 verified creations, 1 sendCoins and active dlt-connector', () => {
|
||||
it('found 3 dlt-transactions', async () => {
|
||||
txCREATION1 = await createTxCREATION1(true)
|
||||
txCREATION2 = await createTxCREATION2(true)
|
||||
txCREATION3 = await createTxCREATION3(true)
|
||||
await createHomeCommunity()
|
||||
|
||||
txSEND1to2 = await createTxSend1ToReceive2(false)
|
||||
txRECEIVE2From1 = await createTxReceive2FromSend1(false)
|
||||
|
||||
/*
|
||||
txSEND2To3 = await createTxSend2ToReceive3()
|
||||
txRECEIVE3From2 = await createTxReceive3FromSend2()
|
||||
txSEND3To1 = await createTxSend3ToReceive1()
|
||||
txRECEIVE1From3 = await createTxReceive1FromSend3()
|
||||
*/
|
||||
|
||||
CONFIG.DLT_CONNECTOR = true
|
||||
|
||||
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
|
||||
return {
|
||||
data: {
|
||||
sendTransaction: { succeed: true },
|
||||
},
|
||||
} as Response<unknown>
|
||||
})
|
||||
|
||||
await sendTransactionsToDltConnector()
|
||||
|
||||
expect(logger.info).toBeCalledWith('sendTransactionsToDltConnector...')
|
||||
|
||||
// Find the previous created transactions of sendCoin mutation
|
||||
/*
|
||||
const transactions = await Transaction.find({
|
||||
// where: { memo: 'unrepeatable memo' },
|
||||
order: { balanceDate: 'ASC', id: 'ASC' },
|
||||
})
|
||||
*/
|
||||
|
||||
const dltTransactions = await DltTransaction.find({
|
||||
// where: { transactionId: In([transaction[0].id, transaction[1].id]) },
|
||||
// relations: ['transaction'],
|
||||
order: { createdAt: 'ASC', id: 'ASC' },
|
||||
})
|
||||
|
||||
expect(dltTransactions).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
transactionId: txCREATION1.id,
|
||||
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c1',
|
||||
verified: true,
|
||||
createdAt: new Date('01.01.2023 00:00:10'),
|
||||
verifiedAt: new Date('01.01.2023 00:01:10'),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
transactionId: txCREATION2.id,
|
||||
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c2',
|
||||
verified: true,
|
||||
createdAt: new Date('02.01.2023 00:00:10'),
|
||||
verifiedAt: new Date('02.01.2023 00:01:10'),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
transactionId: txCREATION3.id,
|
||||
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c3',
|
||||
verified: true,
|
||||
createdAt: new Date('03.01.2023 00:00:10'),
|
||||
verifiedAt: new Date('03.01.2023 00:01:10'),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
transactionId: txSEND1to2.id,
|
||||
messageId: 'sended',
|
||||
verified: false,
|
||||
createdAt: expect.any(Date),
|
||||
verifiedAt: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
transactionId: txRECEIVE2From1.id,
|
||||
messageId: 'sended',
|
||||
verified: false,
|
||||
createdAt: expect.any(Date),
|
||||
verifiedAt: null,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
})
|
||||
/*
|
||||
describe('with one Community of api 1_0 and not matching pubKey', () => {
|
||||
beforeEach(async () => {
|
||||
|
||||
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
|
||||
|
||||
return {
|
||||
data: {
|
||||
getPublicKey: {
|
||||
publicKey: 'somePubKey',
|
||||
},
|
||||
},
|
||||
} as Response<unknown>
|
||||
})
|
||||
const variables1 = {
|
||||
publicKey: Buffer.from('11111111111111111111111111111111'),
|
||||
apiVersion: '1_0',
|
||||
endPoint: 'http//localhost:5001/api/',
|
||||
lastAnnouncedAt: new Date(),
|
||||
}
|
||||
await DbFederatedCommunity.createQueryBuilder()
|
||||
.insert()
|
||||
.into(DbFederatedCommunity)
|
||||
.values(variables1)
|
||||
.orUpdate({
|
||||
|
||||
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
||||
overwrite: ['end_point', 'last_announced_at'],
|
||||
})
|
||||
.execute()
|
||||
|
||||
jest.clearAllMocks()
|
||||
// await validateCommunities()
|
||||
})
|
||||
|
||||
it('logs one community found', () => {
|
||||
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
|
||||
})
|
||||
it('logs requestGetPublicKey for community api 1_0 ', () => {
|
||||
expect(logger.info).toBeCalledWith(
|
||||
'Federation: getPublicKey from endpoint',
|
||||
'http//localhost:5001/api/1_0/',
|
||||
)
|
||||
})
|
||||
it('logs not matching publicKeys', () => {
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
'Federation: received not matching publicKey:',
|
||||
'somePubKey',
|
||||
expect.stringMatching('11111111111111111111111111111111'),
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('with one Community of api 1_0 and matching pubKey', () => {
|
||||
beforeEach(async () => {
|
||||
|
||||
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
|
||||
|
||||
return {
|
||||
data: {
|
||||
getPublicKey: {
|
||||
publicKey: '11111111111111111111111111111111',
|
||||
},
|
||||
},
|
||||
} as Response<unknown>
|
||||
})
|
||||
const variables1 = {
|
||||
publicKey: Buffer.from('11111111111111111111111111111111'),
|
||||
apiVersion: '1_0',
|
||||
endPoint: 'http//localhost:5001/api/',
|
||||
lastAnnouncedAt: new Date(),
|
||||
}
|
||||
await DbFederatedCommunity.createQueryBuilder()
|
||||
.insert()
|
||||
.into(DbFederatedCommunity)
|
||||
.values(variables1)
|
||||
.orUpdate({
|
||||
|
||||
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
||||
overwrite: ['end_point', 'last_announced_at'],
|
||||
})
|
||||
.execute()
|
||||
await DbFederatedCommunity.update({}, { verifiedAt: null })
|
||||
jest.clearAllMocks()
|
||||
// await validateCommunities()
|
||||
})
|
||||
|
||||
it('logs one community found', () => {
|
||||
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
|
||||
})
|
||||
it('logs requestGetPublicKey for community api 1_0 ', () => {
|
||||
expect(logger.info).toBeCalledWith(
|
||||
'Federation: getPublicKey from endpoint',
|
||||
'http//localhost:5001/api/1_0/',
|
||||
)
|
||||
})
|
||||
it('logs community pubKey verified', () => {
|
||||
expect(logger.info).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'Federation: verified community with',
|
||||
'http//localhost:5001/api/',
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('with two Communities of api 1_0 and 1_1', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
|
||||
|
||||
return {
|
||||
data: {
|
||||
getPublicKey: {
|
||||
publicKey: '11111111111111111111111111111111',
|
||||
},
|
||||
},
|
||||
} as Response<unknown>
|
||||
})
|
||||
const variables2 = {
|
||||
publicKey: Buffer.from('11111111111111111111111111111111'),
|
||||
apiVersion: '1_1',
|
||||
endPoint: 'http//localhost:5001/api/',
|
||||
lastAnnouncedAt: new Date(),
|
||||
}
|
||||
await DbFederatedCommunity.createQueryBuilder()
|
||||
.insert()
|
||||
.into(DbFederatedCommunity)
|
||||
.values(variables2)
|
||||
.orUpdate({
|
||||
|
||||
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
||||
overwrite: ['end_point', 'last_announced_at'],
|
||||
})
|
||||
.execute()
|
||||
|
||||
await DbFederatedCommunity.update({}, { verifiedAt: null })
|
||||
jest.clearAllMocks()
|
||||
// await validateCommunities()
|
||||
})
|
||||
it('logs two communities found', () => {
|
||||
expect(logger.debug).toBeCalledWith(`Federation: found 2 dbCommunities`)
|
||||
})
|
||||
it('logs requestGetPublicKey for community api 1_0 ', () => {
|
||||
expect(logger.info).toBeCalledWith(
|
||||
'Federation: getPublicKey from endpoint',
|
||||
'http//localhost:5001/api/1_0/',
|
||||
)
|
||||
})
|
||||
it('logs requestGetPublicKey for community api 1_1 ', () => {
|
||||
expect(logger.info).toBeCalledWith(
|
||||
'Federation: getPublicKey from endpoint',
|
||||
'http//localhost:5001/api/1_1/',
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('with three Communities of api 1_0, 1_1 and 2_0', () => {
|
||||
let dbCom: DbFederatedCommunity
|
||||
beforeEach(async () => {
|
||||
const variables3 = {
|
||||
publicKey: Buffer.from('11111111111111111111111111111111'),
|
||||
apiVersion: '2_0',
|
||||
endPoint: 'http//localhost:5001/api/',
|
||||
lastAnnouncedAt: new Date(),
|
||||
}
|
||||
await DbFederatedCommunity.createQueryBuilder()
|
||||
.insert()
|
||||
.into(DbFederatedCommunity)
|
||||
.values(variables3)
|
||||
.orUpdate({
|
||||
|
||||
conflict_target: ['id', 'publicKey', 'apiVersion'],
|
||||
overwrite: ['end_point', 'last_announced_at'],
|
||||
})
|
||||
.execute()
|
||||
dbCom = await DbFederatedCommunity.findOneOrFail({
|
||||
where: { publicKey: variables3.publicKey, apiVersion: variables3.apiVersion },
|
||||
})
|
||||
await DbFederatedCommunity.update({}, { verifiedAt: null })
|
||||
jest.clearAllMocks()
|
||||
// await validateCommunities()
|
||||
})
|
||||
it('logs three community found', () => {
|
||||
expect(logger.debug).toBeCalledWith(`Federation: found 3 dbCommunities`)
|
||||
})
|
||||
it('logs requestGetPublicKey for community api 1_0 ', () => {
|
||||
expect(logger.info).toBeCalledWith(
|
||||
'Federation: getPublicKey from endpoint',
|
||||
'http//localhost:5001/api/1_0/',
|
||||
)
|
||||
})
|
||||
it('logs requestGetPublicKey for community api 1_1 ', () => {
|
||||
expect(logger.info).toBeCalledWith(
|
||||
'Federation: getPublicKey from endpoint',
|
||||
'http//localhost:5001/api/1_1/',
|
||||
)
|
||||
})
|
||||
it('logs unsupported api for community with api 2_0 ', () => {
|
||||
expect(logger.warn).toBeCalledWith(
|
||||
'Federation: dbCom with unsupported apiVersion',
|
||||
dbCom.endPoint,
|
||||
'2_0',
|
||||
)
|
||||
})
|
||||
})
|
||||
*/
|
||||
})
|
||||
})
|
||||
@ -1,7 +1,6 @@
|
||||
import 'reflect-metadata'
|
||||
import 'source-map-support/register'
|
||||
import { getLogger } from 'log4js'
|
||||
import { sendTransactionsToDltConnector } from './apis/dltConnector/sendTransactionsToDltConnector'
|
||||
import { CONFIG } from './config'
|
||||
import { startValidateCommunities } from './federation/validateCommunities'
|
||||
import { createServer } from './server/createServer'
|
||||
@ -21,11 +20,7 @@ async function main() {
|
||||
console.log(`GraphIQL available at http://localhost:${CONFIG.PORT}`)
|
||||
}
|
||||
})
|
||||
// task is running the whole time for transmitting transaction via dlt-connector to iota
|
||||
// can be notified with InterruptiveSleepManager.getInstance().interrupt(TRANSMIT_TO_IOTA_INTERRUPTIVE_SLEEP_KEY)
|
||||
// that a new transaction or user was stored in db
|
||||
// void sendTransactionsToDltConnector()
|
||||
void startValidateCommunities(Number(CONFIG.FEDERATION_VALIDATE_COMMUNITY_TIMER))
|
||||
await startValidateCommunities(Number(CONFIG.FEDERATION_VALIDATE_COMMUNITY_TIMER))
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import { delay } from 'core'
|
||||
|
||||
/**
|
||||
* Sleep, that can be interrupted
|
||||
* call sleep only for msSteps and than check if interrupt was called
|
||||
*/
|
||||
export class InterruptiveSleep {
|
||||
private interruptSleep = false
|
||||
private msSteps = 10
|
||||
|
||||
constructor(msSteps: number) {
|
||||
this.msSteps = msSteps
|
||||
}
|
||||
|
||||
public interrupt(): void {
|
||||
this.interruptSleep = true
|
||||
}
|
||||
|
||||
public async sleep(ms: number): Promise<void> {
|
||||
let waited = 0
|
||||
this.interruptSleep = false
|
||||
while (waited < ms && !this.interruptSleep) {
|
||||
await delay(this.msSteps)
|
||||
waited += this.msSteps
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
import { InterruptiveSleep } from './InterruptiveSleep'
|
||||
|
||||
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
|
||||
// and ../federation/client/FederationClientFactory.ts
|
||||
/**
|
||||
* Managing Instances of interruptive sleep it is inspired from conditions from c++ multithreading
|
||||
* It is used for separate worker threads which will go to sleep after they haven't anything todo left,
|
||||
* but with this Manager and InterruptiveSleep Object it sleeps only stepSize and check if something interrupted his sleep,
|
||||
* so he can check for new work
|
||||
*/
|
||||
export const TRANSMIT_TO_IOTA_INTERRUPTIVE_SLEEP_KEY = 'transmitToIota'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
||||
export class InterruptiveSleepManager {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
private static instance: InterruptiveSleepManager
|
||||
private interruptiveSleep: Map<string, InterruptiveSleep> = new Map<string, InterruptiveSleep>()
|
||||
private stepSizeMilliseconds = 10
|
||||
|
||||
/**
|
||||
* The Singleton's constructor should always be private to prevent direct
|
||||
* construction calls with the `new` operator.
|
||||
*/
|
||||
// eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* The static method that controls the access to the singleton instance.
|
||||
*
|
||||
* This implementation let you subclass the Singleton class while keeping
|
||||
* just one instance of each subclass around.
|
||||
*/
|
||||
public static getInstance(): InterruptiveSleepManager {
|
||||
if (!InterruptiveSleepManager.instance) {
|
||||
InterruptiveSleepManager.instance = new InterruptiveSleepManager()
|
||||
}
|
||||
return InterruptiveSleepManager.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* only for new created InterruptiveSleepManager Entries!
|
||||
* @param step size in ms in which new! InterruptiveSleepManager check if they where triggered
|
||||
*/
|
||||
public setStepSize(ms: number) {
|
||||
this.stepSizeMilliseconds = ms
|
||||
}
|
||||
|
||||
public interrupt(key: string): void {
|
||||
const interruptiveSleep = this.interruptiveSleep.get(key)
|
||||
if (interruptiveSleep) {
|
||||
interruptiveSleep.interrupt()
|
||||
}
|
||||
}
|
||||
|
||||
public sleep(key: string, ms: number): Promise<void> {
|
||||
if (!this.interruptiveSleep.has(key)) {
|
||||
this.interruptiveSleep.set(key, new InterruptiveSleep(this.stepSizeMilliseconds))
|
||||
}
|
||||
const interruptiveSleep = this.interruptiveSleep.get(key)
|
||||
if (!interruptiveSleep) {
|
||||
throw new LogError('map entry not exist after setting it')
|
||||
}
|
||||
return interruptiveSleep.sleep(ms)
|
||||
}
|
||||
}
|
||||
@ -39,13 +39,11 @@ export const testEnvironment = async (testLogger = getLogger('apollo'), testI18n
|
||||
}
|
||||
|
||||
export const resetEntity = async (entity: any) => {
|
||||
// delete data and reset autoincrement!
|
||||
await entity.clear()
|
||||
/*const items = await entity.find({ withDeleted: true })
|
||||
const items = await entity.find({ withDeleted: true })
|
||||
if (items.length > 0) {
|
||||
const ids = items.map((e: any) => e.id)
|
||||
await entity.delete(ids)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
export const resetToken = () => {
|
||||
|
||||
2
bunfig.toml
Normal file
2
bunfig.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[install]
|
||||
linker = "hoisted"
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "config-schema",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"description": "Gradido Config for validate config",
|
||||
"main": "./build/index.js",
|
||||
"types": "./src/index.ts",
|
||||
@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.2",
|
||||
"joi": "^17.13.3",
|
||||
"joi": "17.13.3",
|
||||
"log4js": "^6.9.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"yoctocolors-cjs": "^2.1.2",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "core",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"description": "Gradido Core Code, High-Level Shared Code, with dependencies on other modules",
|
||||
"main": "./build/index.js",
|
||||
"types": "./src/index.ts",
|
||||
@ -28,6 +28,7 @@
|
||||
"database": "*",
|
||||
"esbuild": "^0.25.2",
|
||||
"i18n": "^0.15.1",
|
||||
"joi": "^17.13.3",
|
||||
"jose": "^4.14.4",
|
||||
"log4js": "^6.9.1",
|
||||
"shared": "*",
|
||||
@ -37,10 +38,12 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.0.0",
|
||||
"@types/i18n": "^0.13.4",
|
||||
"@types/minimatch": "6.0.0",
|
||||
"@types/node": "^17.0.21",
|
||||
"@types/sodium-native": "^2.3.5",
|
||||
"config-schema": "*",
|
||||
"decimal.js-light": "^2.5.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"graphql-request": "5.0.0",
|
||||
"jest": "27.2.4",
|
||||
"type-graphql": "^1.1.1",
|
||||
|
||||
@ -1,42 +1,41 @@
|
||||
import { EncryptedTransferArgs } from '../model/EncryptedTransferArgs'
|
||||
import { JwtPayloadType } from 'shared'
|
||||
import { Ed25519PublicKey, JwtPayloadType } from 'shared'
|
||||
import { Community as DbCommunity } from 'database'
|
||||
import { getLogger } from 'log4js'
|
||||
import { CommunityLoggingView, getHomeCommunity } from 'database'
|
||||
import { verifyAndDecrypt } from 'shared'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.logic.interpretEncryptedTransferArgs`)
|
||||
const createLogger = (functionName: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.logic.interpretEncryptedTransferArgs.${functionName}`)
|
||||
|
||||
export const interpretEncryptedTransferArgs = async (args: EncryptedTransferArgs): Promise<JwtPayloadType | null> => {
|
||||
const methodLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.logic.interpretEncryptedTransferArgs-method`)
|
||||
const methodLogger = createLogger('interpretEncryptedTransferArgs')
|
||||
methodLogger.addContext('handshakeID', args.handshakeID)
|
||||
methodLogger.debug('interpretEncryptedTransferArgs()... args:', args)
|
||||
const argsPublicKey = new Ed25519PublicKey(args.publicKey)
|
||||
// first find with args.publicKey the community 'requestingCom', which starts the request
|
||||
const requestingCom = await DbCommunity.findOneBy({ publicKey: Buffer.from(args.publicKey, 'hex') })
|
||||
// TODO: maybe use community from caller instead of loading it separately
|
||||
const requestingCom = await DbCommunity.findOneBy({ publicKey: argsPublicKey.asBuffer() })
|
||||
if (!requestingCom) {
|
||||
const errmsg = `unknown requesting community with publicKey ${Buffer.from(args.publicKey, 'hex')}`
|
||||
const errmsg = `unknown requesting community with publicKey ${argsPublicKey.asHex()}`
|
||||
methodLogger.error(errmsg)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
if (!requestingCom.publicJwtKey) {
|
||||
const errmsg = `missing publicJwtKey of requesting community with publicKey ${Buffer.from(args.publicKey, 'hex')}`
|
||||
const errmsg = `missing publicJwtKey of requesting community with publicKey ${argsPublicKey.asHex()}`
|
||||
methodLogger.error(errmsg)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
methodLogger.debug(`found requestingCom:`, new CommunityLoggingView(requestingCom))
|
||||
// verify the signing of args.jwt with homeCom.privateJwtKey and decrypt args.jwt with requestingCom.publicJwtKey
|
||||
// TODO: maybe use community from caller instead of loading it separately
|
||||
const homeCom = await getHomeCommunity()
|
||||
const jwtPayload = await verifyAndDecrypt(args.handshakeID, args.jwt, homeCom!.privateJwtKey!, requestingCom.publicJwtKey) as JwtPayloadType
|
||||
if (!jwtPayload) {
|
||||
const errmsg = `invalid payload of community with publicKey ${Buffer.from(args.publicKey, 'hex')}`
|
||||
const errmsg = `invalid payload of community with publicKey ${argsPublicKey.asHex()}`
|
||||
methodLogger.error(errmsg)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
methodLogger.debug('jwtPayload', jwtPayload)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
return jwtPayload
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import { Decimal } from 'decimal.js-light'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'
|
||||
import { PendingTransactionState } from 'shared'
|
||||
// import { LogError } from '@/server/LogError'
|
||||
import { calculateSenderBalance } from 'core'
|
||||
import { calculateSenderBalance } from '../../util/calculateSenderBalance'
|
||||
import { TRANSACTIONS_LOCK, getLastTransaction } from 'database'
|
||||
import { getLogger } from 'log4js'
|
||||
|
||||
|
||||
@ -22,4 +22,5 @@ export * from './util/calculateSenderBalance'
|
||||
export * from './util/utilities'
|
||||
export * from './validation/user'
|
||||
export * from './config/index'
|
||||
export * from './logic'
|
||||
|
||||
|
||||
33
core/src/logic/CommunityHandshakeState.logic.ts
Normal file
33
core/src/logic/CommunityHandshakeState.logic.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { CommunityHandshakeState, CommunityHandshakeStateType } from 'database'
|
||||
import { FEDERATION_AUTHENTICATION_TIMEOUT_MS } from 'shared'
|
||||
|
||||
export class CommunityHandshakeStateLogic {
|
||||
public constructor(private self: CommunityHandshakeState) {}
|
||||
|
||||
/**
|
||||
* Check for expired state and if not, check timeout and update (write into db) to expired state
|
||||
* @returns true if the community handshake state is expired
|
||||
*/
|
||||
public async isTimeoutUpdate(): Promise<boolean> {
|
||||
const timeout = this.isTimeout()
|
||||
if (timeout && this.self.status !== CommunityHandshakeStateType.EXPIRED) {
|
||||
this.self.status = CommunityHandshakeStateType.EXPIRED
|
||||
await this.self.save()
|
||||
}
|
||||
return timeout
|
||||
}
|
||||
|
||||
public isTimeout(): boolean {
|
||||
if (this.self.status === CommunityHandshakeStateType.EXPIRED) {
|
||||
return true
|
||||
}
|
||||
if ((Date.now() - this.self.updatedAt.getTime()) > FEDERATION_AUTHENTICATION_TIMEOUT_MS) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public isFailed(): boolean {
|
||||
return this.self.status === CommunityHandshakeStateType.FAILED
|
||||
}
|
||||
}
|
||||
22
core/src/logic/CommunityHandshakeStateLogic.test.ts
Normal file
22
core/src/logic/CommunityHandshakeStateLogic.test.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { CommunityHandshakeState } from 'database'
|
||||
import { CommunityHandshakeStateLogic } from './CommunityHandshakeState.logic'
|
||||
import { CommunityHandshakeStateType } from 'database'
|
||||
import { FEDERATION_AUTHENTICATION_TIMEOUT_MS } from 'shared'
|
||||
|
||||
describe('CommunityHandshakeStateLogic', () => {
|
||||
it('isTimeout', () => {
|
||||
const state = new CommunityHandshakeState()
|
||||
state.updatedAt = new Date(Date.now() - FEDERATION_AUTHENTICATION_TIMEOUT_MS * 2)
|
||||
state.status = CommunityHandshakeStateType.START_AUTHENTICATION
|
||||
const logic = new CommunityHandshakeStateLogic(state)
|
||||
expect(logic.isTimeout()).toEqual(true)
|
||||
})
|
||||
|
||||
it('isTimeout return false', () => {
|
||||
const state = new CommunityHandshakeState()
|
||||
state.updatedAt = new Date(Date.now())
|
||||
state.status = CommunityHandshakeStateType.START_AUTHENTICATION
|
||||
const logic = new CommunityHandshakeStateLogic(state)
|
||||
expect(logic.isTimeout()).toEqual(false)
|
||||
})
|
||||
})
|
||||
12
core/src/logic/community.logic.ts
Normal file
12
core/src/logic/community.logic.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from 'database'
|
||||
|
||||
export function getFederatedCommunityWithApiOrFail(
|
||||
community: DbCommunity,
|
||||
apiVersion: string
|
||||
): DbFederatedCommunity {
|
||||
const fedCom = community.federatedCommunities?.find((fedCom) => fedCom.apiVersion === apiVersion)
|
||||
if (!fedCom) {
|
||||
throw new Error(`Missing federated community with api version ${apiVersion}`)
|
||||
}
|
||||
return fedCom
|
||||
}
|
||||
2
core/src/logic/index.ts
Normal file
2
core/src/logic/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './CommunityHandshakeState.logic'
|
||||
export * from './community.logic'
|
||||
@ -36,6 +36,11 @@ export const delay = promisify(setTimeout)
|
||||
export const ensureUrlEndsWithSlash = (url: string): string => {
|
||||
return url.endsWith('/') ? url : url.concat('/')
|
||||
}
|
||||
export function splitUrlInEndPointAndApiVersion(url: string): { endPoint: string, apiVersion: string } {
|
||||
const endPoint = url.slice(0, url.lastIndexOf('/') + 1)
|
||||
const apiVersion = url.slice(url.lastIndexOf('/') + 1, url.length)
|
||||
return { endPoint, apiVersion }
|
||||
}
|
||||
/**
|
||||
* Calculates the date representing the first day of the month, a specified number of months prior to a given date.
|
||||
*
|
||||
|
||||
@ -47,7 +47,8 @@
|
||||
// "baseUrl": ".", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": { }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [".", "../database"], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
/* List of folders to include type definitions from. */
|
||||
"typeRoots": ["./node_modules/@types", "../node_modules/@types"],
|
||||
// "types": ["bun-types"], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
|
||||
@ -46,7 +46,9 @@ FROM base as bun-base
|
||||
|
||||
#RUN apt update && apt install -y --no-install-recommends ca-certificates curl bash unzip
|
||||
RUN apk update && apk add --no-cache curl tar bash
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
COPY .bun-version .bun-version
|
||||
RUN BUN_VERSION=$(cat .bun-version) && \
|
||||
curl -fsSL https://bun.com/install | bash -s "bun-v${BUN_VERSION}"
|
||||
# Add bun's global bin directory to PATH
|
||||
ENV PATH="/root/.bun/bin:${PATH}"
|
||||
|
||||
@ -63,7 +65,7 @@ COPY --chown=app:app . .
|
||||
FROM installer as build-shared
|
||||
|
||||
RUN bun install --filter shared --no-cache --frozen-lockfile \
|
||||
&& cd shared && yarn typecheck && yarn build
|
||||
&& cd shared && bun run typecheck && bun run build
|
||||
|
||||
##################################################################################
|
||||
# Build ##########################################################################
|
||||
@ -75,7 +77,7 @@ RUN bun install --filter database --production --no-cache --frozen-lockfile
|
||||
##################################################################################
|
||||
# PRODUCTION IMAGE ###############################################################
|
||||
##################################################################################
|
||||
FROM base as production
|
||||
FROM bun-base as production
|
||||
|
||||
COPY --chown=app:app --from=build-shared ${DOCKER_WORKDIR}/shared/build ./shared/build
|
||||
COPY --chown=app:app --from=build-shared ${DOCKER_WORKDIR}/shared/package.json ./shared/package.json
|
||||
@ -89,7 +91,7 @@ COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/package.json ./package.json
|
||||
FROM production as up
|
||||
|
||||
# Run command
|
||||
CMD /bin/sh -c "cd database && yarn up"
|
||||
CMD /bin/sh -c "cd database && bun run up"
|
||||
|
||||
##################################################################################
|
||||
# TEST RESET #####################################################################
|
||||
@ -97,7 +99,7 @@ CMD /bin/sh -c "cd database && yarn up"
|
||||
FROM production as reset
|
||||
|
||||
# Run command
|
||||
CMD /bin/sh -c "cd database && yarn reset"
|
||||
CMD /bin/sh -c "cd database && bun run reset"
|
||||
|
||||
##################################################################################
|
||||
# TEST DOWN ######################################################################
|
||||
@ -105,4 +107,4 @@ CMD /bin/sh -c "cd database && yarn reset"
|
||||
FROM production as down
|
||||
|
||||
# Run command
|
||||
CMD /bin/sh -c "cd database && yarn down"
|
||||
CMD /bin/sh -c "cd database && bun run down"
|
||||
|
||||
@ -23,20 +23,20 @@ TypeError: undefined is not an object (evaluating 'module.parent.parent.require'
|
||||
## Upgrade migrations
|
||||
|
||||
```bash
|
||||
yarn up
|
||||
turbo up
|
||||
```
|
||||
|
||||
## Downgrade migrations
|
||||
|
||||
```bash
|
||||
yarn down
|
||||
turbo down
|
||||
```
|
||||
|
||||
|
||||
## Reset database
|
||||
|
||||
```bash
|
||||
yarn reset
|
||||
turbo reset
|
||||
```
|
||||
|
||||
Runs all down migrations and after this all up migrations.
|
||||
@ -45,7 +45,7 @@ Runs all down migrations and after this all up migrations.
|
||||
call truncate for all tables
|
||||
|
||||
```bash
|
||||
yarn clear
|
||||
turbo clearDB
|
||||
```
|
||||
|
||||
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
/* 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(`
|
||||
CREATE TABLE \`dlt_users\` (
|
||||
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
\`user_id\` int(10) unsigned NOT NULL,
|
||||
\`message_id\` varchar(64) NULL DEFAULT NULL,
|
||||
\`verified\` tinyint(4) NOT NULL DEFAULT 0,
|
||||
\`created_at\` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
\`verified_at\` datetime(3),
|
||||
\`error\` text NULL DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`)
|
||||
|
||||
await queryFn(
|
||||
'ALTER TABLE `dlt_transactions` RENAME COLUMN `transactions_id` TO `transaction_id`;',
|
||||
)
|
||||
await queryFn('ALTER TABLE `dlt_transactions` ADD COLUMN `error` text NULL DEFAULT NULL;')
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`DROP TABLE \`dlt_users\`;`)
|
||||
|
||||
await queryFn(
|
||||
'ALTER TABLE `dlt_transactions` RENAME COLUMN `transaction_id` TO `transactions_id`;',
|
||||
)
|
||||
await queryFn('ALTER TABLE `dlt_transactions` DROP COLUMN `error`;')
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
/* 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(`DROP TABLE \`dlt_users\`;`)
|
||||
await queryFn(`
|
||||
ALTER TABLE \`dlt_transactions\`
|
||||
CHANGE \`transaction_id\` \`transaction_id\` INT(10) UNSIGNED NULL DEFAULT NULL,
|
||||
ADD \`user_id\` INT UNSIGNED NULL DEFAULT NULL AFTER \`transaction_id\`,
|
||||
ADD \`transaction_link_id\` INT UNSIGNED NULL DEFAULT NULL AFTER \`user_id\`,
|
||||
ADD \`type_id\` INT UNSIGNED NOT NULL AFTER \`transaction_link_id\`
|
||||
;
|
||||
`)
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`
|
||||
CREATE TABLE \`dlt_users\` (
|
||||
\`id\` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
\`user_id\` int(10) unsigned NOT NULL,
|
||||
\`message_id\` varchar(64) NULL DEFAULT NULL,
|
||||
\`verified\` tinyint(4) NOT NULL DEFAULT 0,
|
||||
\`created_at\` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
\`verified_at\` datetime(3),
|
||||
\`error\` text NULL DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`)
|
||||
|
||||
await queryFn(`
|
||||
ALTER TABLE \`dlt_transactions\`
|
||||
CHANGE \`transaction_id\` \`transaction_id\` INT(10) UNSIGNED NOT NULL,
|
||||
DROP COLUMN \`user_id\`,
|
||||
DROP COLUMN \`transaction_link_id\`
|
||||
DROP COLUMN \`type_id\`
|
||||
;
|
||||
`)
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(
|
||||
'ALTER TABLE `openai_threads` ADD COLUMN `updatedAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP AFTER `createdAt`;'
|
||||
)
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn('ALTER TABLE `openai_threads` DROP COLUMN `updatedAt`;')
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`
|
||||
CREATE TABLE community_handshake_states (
|
||||
id int unsigned NOT NULL AUTO_INCREMENT,
|
||||
handshake_id int unsigned NOT NULL,
|
||||
one_time_code int unsigned NULL DEFAULT NULL,
|
||||
public_key binary(32) NOT NULL,
|
||||
api_version varchar(255) NOT NULL,
|
||||
status varchar(255) NOT NULL DEFAULT 'OPEN_CONNECTION',
|
||||
last_error text,
|
||||
created_at datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_public_key (public_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`)
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`DROP TABLE community_handshake_states;`)
|
||||
}
|
||||
26
database/migration/migrations/0096-upgrade_dlt_tables.ts
Normal file
26
database/migration/migrations/0096-upgrade_dlt_tables.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/* 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 \`dlt_transactions\`
|
||||
CHANGE \`transactions_id\` \`transaction_id\` INT(10) UNSIGNED NULL DEFAULT NULL,
|
||||
ADD \`user_id\` INT UNSIGNED NULL DEFAULT NULL AFTER \`transaction_id\`,
|
||||
ADD \`transaction_link_id\` INT UNSIGNED NULL DEFAULT NULL AFTER \`user_id\`,
|
||||
ADD \`type_id\` INT UNSIGNED NOT NULL AFTER \`transaction_link_id\`,
|
||||
ADD \`error\` text NULL DEFAULT NULL AFTER \`verified_at\`
|
||||
;
|
||||
`)
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`
|
||||
ALTER TABLE \`dlt_transactions\`
|
||||
CHANGE \`transaction_id\` \`transactions_id\` INT(10) UNSIGNED NOT NULL,
|
||||
DROP COLUMN \`user_id\`,
|
||||
DROP COLUMN \`transaction_link_id\`,
|
||||
DROP COLUMN \`type_id\`,
|
||||
DROP COLUMN \`error\`
|
||||
;
|
||||
`)
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "database",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"description": "Gradido Database Tool to execute database migrations",
|
||||
"main": "./build/index.js",
|
||||
"types": "./src/index.ts",
|
||||
@ -40,6 +40,7 @@
|
||||
"@types/faker": "^5.5.9",
|
||||
"@types/geojson": "^7946.0.13",
|
||||
"@types/jest": "27.0.2",
|
||||
"@types/mysql": "^2.15.27",
|
||||
"@types/node": "^18.7.14",
|
||||
"await-semaphore": "^0.1.3",
|
||||
"crypto-random-bigint": "^2.1.1",
|
||||
@ -56,8 +57,8 @@
|
||||
"dotenv": "^10.0.0",
|
||||
"esbuild": "^0.25.2",
|
||||
"geojson": "^0.5.0",
|
||||
"joi-extract-type": "^15.0.8",
|
||||
"log4js": "^6.9.1",
|
||||
"mysql": "^2.18.1",
|
||||
"mysql2": "^2.3.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"shared": "*",
|
||||
|
||||
@ -93,7 +93,7 @@ export class AppDatabase {
|
||||
public async destroy(): Promise<void> {
|
||||
await this.dataSource?.destroy()
|
||||
}
|
||||
|
||||
|
||||
// ######################################
|
||||
// private methods
|
||||
// ######################################
|
||||
|
||||
37
database/src/entity/CommunityHandshakeState.ts
Normal file
37
database/src/entity/CommunityHandshakeState.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { CommunityHandshakeStateType } from '../enum'
|
||||
import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
|
||||
|
||||
@Entity('community_handshake_states')
|
||||
export class CommunityHandshakeState extends BaseEntity {
|
||||
@PrimaryGeneratedColumn({ unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ name: 'handshake_id', type: 'int', unsigned: true })
|
||||
handshakeId: number
|
||||
|
||||
@Column({ name: 'one_time_code', type: 'int', unsigned: true, default: null, nullable: true })
|
||||
oneTimeCode?: number
|
||||
|
||||
@Column({ name: 'public_key', type: 'binary', length: 32 })
|
||||
publicKey: Buffer
|
||||
|
||||
@Column({ name: 'api_version', type: 'varchar', length: 255 })
|
||||
apiVersion: string
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
default: CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION,
|
||||
nullable: false,
|
||||
})
|
||||
status: CommunityHandshakeStateType
|
||||
|
||||
@Column({ name: 'last_error', type: 'text', nullable: true })
|
||||
lastError?: string
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'datetime', precision: 3 })
|
||||
createdAt: Date
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'datetime', precision: 3 })
|
||||
updatedAt: Date
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { BaseEntity, Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm'
|
||||
import { Transaction } from './Transaction'
|
||||
import { User } from './User'
|
||||
import { TransactionLink } from './TransactionLink'
|
||||
import { User } from './User'
|
||||
|
||||
@Entity('dlt_transactions', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
||||
export class DltTransaction extends BaseEntity {
|
||||
@ -48,15 +48,24 @@ export class DltTransaction extends BaseEntity {
|
||||
@Column({ name: 'error', type: 'text', nullable: true })
|
||||
error: string | null
|
||||
|
||||
@OneToOne(() => Transaction, (transaction) => transaction.dltTransaction)
|
||||
@OneToOne(
|
||||
() => Transaction,
|
||||
(transaction) => transaction.dltTransaction,
|
||||
)
|
||||
@JoinColumn({ name: 'transaction_id' })
|
||||
transaction?: Transaction | null
|
||||
|
||||
@OneToOne(() => User, (user) => user.dltTransaction)
|
||||
@OneToOne(
|
||||
() => User,
|
||||
(user) => user.dltTransaction,
|
||||
)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user?: User | null
|
||||
|
||||
@OneToOne(() => TransactionLink, (transactionLink) => transactionLink.dltTransaction)
|
||||
@OneToOne(
|
||||
() => TransactionLink,
|
||||
(transactionLink) => transactionLink.dltTransaction,
|
||||
)
|
||||
@JoinColumn({ name: 'transaction_link_id' })
|
||||
transactionLink?: TransactionLink | null
|
||||
}
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryColumn } from 'typeorm'
|
||||
import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryColumn, UpdateDateColumn } from 'typeorm'
|
||||
|
||||
@Entity('openai_threads')
|
||||
export class OpenaiThreads extends BaseEntity {
|
||||
@PrimaryColumn({ type: 'char', length: 30 })
|
||||
id: string
|
||||
|
||||
@CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
|
||||
@CreateDateColumn({ type: 'timestamp' })
|
||||
createdAt: Date
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamp' })
|
||||
updatedAt: Date
|
||||
|
||||
@Column({ name: 'user_id', type: 'int', unsigned: true })
|
||||
userId: number
|
||||
}
|
||||
|
||||
@ -1,10 +1,18 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import { BaseEntity, Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryGeneratedColumn } from 'typeorm'
|
||||
import {
|
||||
BaseEntity,
|
||||
Column,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm'
|
||||
import { Contribution } from './Contribution'
|
||||
import { DltTransaction } from './DltTransaction'
|
||||
import { DecimalTransformer } from './transformer/DecimalTransformer'
|
||||
import { TransactionLink } from './TransactionLink'
|
||||
import { DecimalTransformer } from './transformer/DecimalTransformer'
|
||||
|
||||
@Entity('transactions')
|
||||
export class Transaction extends BaseEntity {
|
||||
@ -159,7 +167,10 @@ export class Transaction extends BaseEntity {
|
||||
@JoinColumn({ name: 'id', referencedColumnName: 'transactionId' })
|
||||
contribution?: Contribution | null
|
||||
|
||||
@OneToOne(() => DltTransaction, (dlt) => dlt.transactionId)
|
||||
@OneToOne(
|
||||
() => DltTransaction,
|
||||
(dlt) => dlt.transactionId,
|
||||
)
|
||||
@JoinColumn({ name: 'id', referencedColumnName: 'transactionId' })
|
||||
dltTransaction?: DltTransaction | null
|
||||
|
||||
@ -167,7 +178,10 @@ export class Transaction extends BaseEntity {
|
||||
@JoinColumn({ name: 'previous' })
|
||||
previousTransaction?: Transaction | null
|
||||
|
||||
@ManyToOne(() => TransactionLink, (transactionLink) => transactionLink.transactions)
|
||||
@ManyToOne(
|
||||
() => TransactionLink,
|
||||
(transactionLink) => transactionLink.transactions,
|
||||
)
|
||||
@JoinColumn({ name: 'transaction_link_id' })
|
||||
transactionLink?: TransactionLink | null
|
||||
}
|
||||
|
||||
@ -1,9 +1,18 @@
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import { BaseEntity, Column, DeleteDateColumn, Entity, JoinColumn, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm'
|
||||
import { DecimalTransformer } from './transformer/DecimalTransformer'
|
||||
import { User } from './User'
|
||||
import {
|
||||
BaseEntity,
|
||||
Column,
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm'
|
||||
import { DltTransaction } from './DltTransaction'
|
||||
import { Transaction } from './Transaction'
|
||||
import { DecimalTransformer } from './transformer/DecimalTransformer'
|
||||
import { User } from './User'
|
||||
|
||||
@Entity('transaction_links')
|
||||
export class TransactionLink extends BaseEntity {
|
||||
@ -62,15 +71,24 @@ export class TransactionLink extends BaseEntity {
|
||||
@Column({ type: 'int', unsigned: true, nullable: true })
|
||||
redeemedBy: number | null
|
||||
|
||||
@OneToOne(() => DltTransaction, (dlt) => dlt.transactionLinkId)
|
||||
@OneToOne(
|
||||
() => DltTransaction,
|
||||
(dlt) => dlt.transactionLinkId,
|
||||
)
|
||||
@JoinColumn({ name: 'id', referencedColumnName: 'transactionLinkId' })
|
||||
dltTransaction?: DltTransaction | null
|
||||
|
||||
@OneToOne(() => User, (user) => user.transactionLink)
|
||||
@OneToOne(
|
||||
() => User,
|
||||
(user) => user.transactionLink,
|
||||
)
|
||||
@JoinColumn({ name: 'userId' })
|
||||
user: User
|
||||
|
||||
@OneToMany(() => Transaction, (transaction) => transaction.transactionLink)
|
||||
@OneToMany(
|
||||
() => Transaction,
|
||||
(transaction) => transaction.transactionLink,
|
||||
)
|
||||
@JoinColumn({ referencedColumnName: 'transaction_link_id' })
|
||||
transactions: Transaction[]
|
||||
}
|
||||
|
||||
@ -13,11 +13,11 @@ import {
|
||||
import { Community } from './Community'
|
||||
import { Contribution } from './Contribution'
|
||||
import { ContributionMessage } from './ContributionMessage'
|
||||
import { DltTransaction } from './DltTransaction'
|
||||
import { TransactionLink } from './TransactionLink'
|
||||
import { UserContact } from './UserContact'
|
||||
import { UserRole } from './UserRole'
|
||||
import { GeometryTransformer } from './transformer/GeometryTransformer'
|
||||
import { DltTransaction } from './DltTransaction'
|
||||
import { TransactionLink } from './TransactionLink'
|
||||
|
||||
@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
||||
export class User extends BaseEntity {
|
||||
@ -216,11 +216,17 @@ export class User extends BaseEntity {
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
userContacts?: UserContact[]
|
||||
|
||||
@OneToOne(() => DltTransaction, (dlt) => dlt.userId)
|
||||
@OneToOne(
|
||||
() => DltTransaction,
|
||||
(dlt) => dlt.userId,
|
||||
)
|
||||
@JoinColumn({ name: 'id', referencedColumnName: 'userId' })
|
||||
dltTransaction?: DltTransaction | null
|
||||
|
||||
@OneToOne(() => TransactionLink, (transactionLink) => transactionLink.userId)
|
||||
@OneToOne(
|
||||
() => TransactionLink,
|
||||
(transactionLink) => transactionLink.userId,
|
||||
)
|
||||
@JoinColumn({ name: 'id', referencedColumnName: 'userId' })
|
||||
transactionLink?: TransactionLink | null
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import { Event } from './Event'
|
||||
import { FederatedCommunity } from './FederatedCommunity'
|
||||
import { LoginElopageBuys } from './LoginElopageBuys'
|
||||
import { Migration } from './Migration'
|
||||
import { CommunityHandshakeState } from './CommunityHandshakeState'
|
||||
import { OpenaiThreads } from './OpenaiThreads'
|
||||
import { PendingTransaction } from './PendingTransaction'
|
||||
import { ProjectBranding } from './ProjectBranding'
|
||||
@ -18,6 +19,7 @@ import { UserRole } from './UserRole'
|
||||
|
||||
export {
|
||||
Community,
|
||||
CommunityHandshakeState,
|
||||
Contribution,
|
||||
ContributionLink,
|
||||
ContributionMessage,
|
||||
@ -25,7 +27,7 @@ export {
|
||||
Event,
|
||||
FederatedCommunity,
|
||||
LoginElopageBuys,
|
||||
Migration,
|
||||
Migration,
|
||||
ProjectBranding,
|
||||
OpenaiThreads,
|
||||
PendingTransaction,
|
||||
@ -38,6 +40,7 @@ export {
|
||||
|
||||
export const entities = [
|
||||
Community,
|
||||
CommunityHandshakeState,
|
||||
Contribution,
|
||||
ContributionLink,
|
||||
ContributionMessage,
|
||||
|
||||
9
database/src/enum/CommunityHandshakeStateType.ts
Normal file
9
database/src/enum/CommunityHandshakeStateType.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export enum CommunityHandshakeStateType {
|
||||
START_COMMUNITY_AUTHENTICATION = 'START_COMMUNITY_AUTHENTICATION',
|
||||
START_OPEN_CONNECTION_CALLBACK = 'START_OPEN_CONNECTION_CALLBACK',
|
||||
START_AUTHENTICATION = 'START_AUTHENTICATION',
|
||||
|
||||
SUCCESS = 'SUCCESS',
|
||||
FAILED = 'FAILED',
|
||||
EXPIRED = 'EXPIRED'
|
||||
}
|
||||
1
database/src/enum/index.ts
Normal file
1
database/src/enum/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './CommunityHandshakeStateType'
|
||||
@ -1,64 +1,9 @@
|
||||
import { latestDbVersion } from './detectLastDBVersion'
|
||||
import { Community } from './entity/Community'
|
||||
import { Contribution } from './entity/Contribution'
|
||||
import { ContributionLink } from './entity/ContributionLink'
|
||||
import { ContributionMessage } from './entity/ContributionMessage'
|
||||
import { DltTransaction } from './entity/DltTransaction'
|
||||
import { Event } from './entity/Event'
|
||||
import { FederatedCommunity } from './entity/FederatedCommunity'
|
||||
import { LoginElopageBuys } from './entity/LoginElopageBuys'
|
||||
import { Migration } from './entity/Migration'
|
||||
import { OpenaiThreads } from './entity/OpenaiThreads'
|
||||
import { PendingTransaction } from './entity/PendingTransaction'
|
||||
import { ProjectBranding } from './entity/ProjectBranding'
|
||||
import { Transaction } from './entity/Transaction'
|
||||
import { TransactionLink } from './entity/TransactionLink'
|
||||
import { User } from './entity/User'
|
||||
import { UserContact } from './entity/UserContact'
|
||||
import { UserRole } from './entity/UserRole'
|
||||
|
||||
export {
|
||||
Community,
|
||||
Contribution,
|
||||
ContributionLink,
|
||||
ContributionMessage,
|
||||
DltTransaction,
|
||||
Event,
|
||||
FederatedCommunity,
|
||||
LoginElopageBuys,
|
||||
Migration,
|
||||
ProjectBranding,
|
||||
OpenaiThreads,
|
||||
PendingTransaction,
|
||||
Transaction,
|
||||
TransactionLink,
|
||||
User,
|
||||
UserContact,
|
||||
UserRole,
|
||||
}
|
||||
|
||||
export const entities = [
|
||||
Community,
|
||||
Contribution,
|
||||
ContributionLink,
|
||||
ContributionMessage,
|
||||
DltTransaction,
|
||||
Event,
|
||||
FederatedCommunity,
|
||||
LoginElopageBuys,
|
||||
Migration,
|
||||
ProjectBranding,
|
||||
OpenaiThreads,
|
||||
PendingTransaction,
|
||||
Transaction,
|
||||
TransactionLink,
|
||||
User,
|
||||
UserContact,
|
||||
UserRole,
|
||||
]
|
||||
|
||||
export { latestDbVersion }
|
||||
|
||||
export * from './entity'
|
||||
export * from './logging'
|
||||
export * from './queries'
|
||||
export * from './util'
|
||||
export * from './util'
|
||||
export * from './enum'
|
||||
export { AppDatabase } from './AppDatabase'
|
||||
|
||||
21
database/src/logging/CommunityHandshakeStateLogging.view.ts
Normal file
21
database/src/logging/CommunityHandshakeStateLogging.view.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { CommunityHandshakeState } from '..'
|
||||
import { AbstractLoggingView } from './AbstractLogging.view'
|
||||
|
||||
export class CommunityHandshakeStateLoggingView extends AbstractLoggingView {
|
||||
public constructor(private self: CommunityHandshakeState) {
|
||||
super()
|
||||
}
|
||||
|
||||
public toJSON(): any {
|
||||
return {
|
||||
id: this.self.id,
|
||||
handshakeId: this.self.handshakeId,
|
||||
oneTimeCode: this.self.oneTimeCode,
|
||||
publicKey: this.self.publicKey.toString(this.bufferStringFormat),
|
||||
status: this.self.status,
|
||||
lastError: this.self.lastError,
|
||||
createdAt: this.dateToString(this.self.createdAt),
|
||||
updatedAt: this.dateToString(this.self.updatedAt),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { Community } from '../entity'
|
||||
|
||||
import { FederatedCommunityLoggingView } from './FederatedCommunityLogging.view'
|
||||
import { AbstractLoggingView } from './AbstractLogging.view'
|
||||
|
||||
export class CommunityLoggingView extends AbstractLoggingView {
|
||||
@ -21,6 +21,9 @@ export class CommunityLoggingView extends AbstractLoggingView {
|
||||
creationDate: this.dateToString(this.self.creationDate),
|
||||
createdAt: this.dateToString(this.self.createdAt),
|
||||
updatedAt: this.dateToString(this.self.updatedAt),
|
||||
federatedCommunities: this.self.federatedCommunities?.map(
|
||||
(federatedCommunity) => new FederatedCommunityLoggingView(federatedCommunity)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,10 @@ enum OptInType {
|
||||
}
|
||||
|
||||
export class UserContactLoggingView extends AbstractLoggingView {
|
||||
public constructor(private self: UserContact, private showUser = true) {
|
||||
public constructor(
|
||||
private self: UserContact,
|
||||
private showUser = true,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
@ -16,9 +19,10 @@ export class UserContactLoggingView extends AbstractLoggingView {
|
||||
return {
|
||||
id: this.self.id,
|
||||
type: this.self.type,
|
||||
user: this.showUser && this.self.user
|
||||
? new UserLoggingView(this.self.user).toJSON()
|
||||
: { id: this.self.userId },
|
||||
user:
|
||||
this.showUser && this.self.user
|
||||
? new UserLoggingView(this.self.user).toJSON()
|
||||
: { id: this.self.userId },
|
||||
email: this.self.email?.substring(0, 3) + '...',
|
||||
emailVerificationCode: this.self.emailVerificationCode?.substring(0, 4) + '...',
|
||||
emailOptInTypeId: OptInType[this.self.emailOptInTypeId],
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { User } from '../entity'
|
||||
import { AbstractLoggingView } from './AbstractLogging.view'
|
||||
import { CommunityLoggingView } from './CommunityLogging.view'
|
||||
import { ContributionLoggingView } from './ContributionLogging.view'
|
||||
import { ContributionMessageLoggingView } from './ContributionMessageLogging.view'
|
||||
import { UserContactLoggingView } from './UserContactLogging.view'
|
||||
import { UserRoleLoggingView } from './UserRoleLogging.view'
|
||||
import { CommunityLoggingView } from './CommunityLogging.view'
|
||||
|
||||
enum PasswordEncryptionType {
|
||||
NO_PASSWORD = 0,
|
||||
|
||||
@ -3,16 +3,20 @@ import { AbstractLoggingView } from './AbstractLogging.view'
|
||||
import { UserLoggingView } from './UserLogging.view'
|
||||
|
||||
export class UserRoleLoggingView extends AbstractLoggingView {
|
||||
public constructor(private self: UserRole, private showUser = true) {
|
||||
public constructor(
|
||||
private self: UserRole,
|
||||
private showUser = true,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
public toJSON(): any {
|
||||
return {
|
||||
id: this.self.id,
|
||||
user: this.showUser && this.self.user
|
||||
? new UserLoggingView(this.self.user).toJSON()
|
||||
: { id: this.self.userId },
|
||||
user:
|
||||
this.showUser && this.self.user
|
||||
? new UserLoggingView(this.self.user).toJSON()
|
||||
: { id: this.self.userId },
|
||||
role: this.self.role,
|
||||
createdAt: this.dateToString(this.self.createdAt),
|
||||
updatedAt: this.dateToString(this.self.updatedAt),
|
||||
|
||||
@ -11,6 +11,7 @@ import { TransactionLoggingView } from './TransactionLogging.view'
|
||||
import { UserContactLoggingView } from './UserContactLogging.view'
|
||||
import { UserLoggingView } from './UserLogging.view'
|
||||
import { UserRoleLoggingView } from './UserRoleLogging.view'
|
||||
import { CommunityHandshakeStateLoggingView } from './CommunityHandshakeStateLogging.view'
|
||||
|
||||
export {
|
||||
AbstractLoggingView,
|
||||
@ -24,6 +25,7 @@ export {
|
||||
UserContactLoggingView,
|
||||
UserLoggingView,
|
||||
UserRoleLoggingView,
|
||||
CommunityHandshakeStateLoggingView,
|
||||
}
|
||||
|
||||
export const logger = getLogger(LOG4JS_BASE_CATEGORY_NAME)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from '..'
|
||||
import { AppDatabase } from '../AppDatabase'
|
||||
import { getHomeCommunity, getReachableCommunities } from './communities'
|
||||
import { getCommunityByPublicKeyOrFail, getHomeCommunity, getHomeCommunityWithFederatedCommunityOrFail, getReachableCommunities } from './communities'
|
||||
import { describe, expect, it, beforeEach, beforeAll, afterAll } from 'vitest'
|
||||
import { createCommunity, createVerifiedFederatedCommunity } from '../seeds/community'
|
||||
import { Ed25519PublicKey } from 'shared'
|
||||
|
||||
const db = AppDatabase.getInstance()
|
||||
|
||||
@ -39,6 +40,36 @@ describe('community.queries', () => {
|
||||
expect(community?.privateKey).toStrictEqual(homeCom.privateKey)
|
||||
})
|
||||
})
|
||||
describe('getHomeCommunityWithFederatedCommunityOrFail', () => {
|
||||
it('should return the home community with federated communities', async () => {
|
||||
const homeCom = await createCommunity(false)
|
||||
await createVerifiedFederatedCommunity('1_0', 100, homeCom)
|
||||
const community = await getHomeCommunityWithFederatedCommunityOrFail('1_0')
|
||||
expect(community).toBeDefined()
|
||||
expect(community?.federatedCommunities).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should throw if no home community exists', async () => {
|
||||
expect(() => getHomeCommunityWithFederatedCommunityOrFail('1_0')).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('should throw if no federated community exists', async () => {
|
||||
await createCommunity(false)
|
||||
expect(() => getHomeCommunityWithFederatedCommunityOrFail('1_0')).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('load community by public key returned from getHomeCommunityWithFederatedCommunityOrFail', async () => {
|
||||
const homeCom = await createCommunity(false)
|
||||
await createVerifiedFederatedCommunity('1_0', 100, homeCom)
|
||||
const community = await getHomeCommunityWithFederatedCommunityOrFail('1_0')
|
||||
expect(community).toBeDefined()
|
||||
expect(community?.federatedCommunities).toHaveLength(1)
|
||||
const ed25519PublicKey = new Ed25519PublicKey(community.federatedCommunities![0].publicKey)
|
||||
const communityByPublicKey = await getCommunityByPublicKeyOrFail(ed25519PublicKey)
|
||||
expect(communityByPublicKey).toBeDefined()
|
||||
expect(communityByPublicKey?.communityUuid).toBe(homeCom.communityUuid)
|
||||
})
|
||||
})
|
||||
describe('getReachableCommunities', () => {
|
||||
it('home community counts also to reachable communities', async () => {
|
||||
await createCommunity(false)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { FindOptionsOrder, FindOptionsWhere, IsNull, MoreThanOrEqual, Not } from 'typeorm'
|
||||
import { Community as DbCommunity } from '../entity'
|
||||
import { urlSchema, uuidv4Schema } from 'shared'
|
||||
import { Ed25519PublicKey, urlSchema, uuidv4Schema } from 'shared'
|
||||
|
||||
/**
|
||||
* Retrieves the home community, i.e., a community that is not foreign.
|
||||
@ -10,7 +10,14 @@ export async function getHomeCommunity(): Promise<DbCommunity | null> {
|
||||
// TODO: Put in Cache, it is needed nearly always
|
||||
// TODO: return only DbCommunity or throw to reduce unnecessary checks, because there should be always a home community
|
||||
return await DbCommunity.findOne({
|
||||
where: { foreign: false },
|
||||
where: { foreign: false }
|
||||
})
|
||||
}
|
||||
|
||||
export async function getHomeCommunityWithFederatedCommunityOrFail(apiVersion: string): Promise<DbCommunity> {
|
||||
return await DbCommunity.findOneOrFail({
|
||||
where: { foreign: false, federatedCommunities: { apiVersion } },
|
||||
relations: { federatedCommunities: true },
|
||||
})
|
||||
}
|
||||
|
||||
@ -42,6 +49,22 @@ export async function getCommunityWithFederatedCommunityByIdentifier(
|
||||
})
|
||||
}
|
||||
|
||||
export async function getCommunityWithFederatedCommunityWithApiOrFail(
|
||||
publicKey: Ed25519PublicKey,
|
||||
apiVersion: string
|
||||
): Promise<DbCommunity> {
|
||||
return await DbCommunity.findOneOrFail({
|
||||
where: { foreign: true, publicKey: publicKey.asBuffer(), federatedCommunities: { apiVersion } },
|
||||
relations: { federatedCommunities: true },
|
||||
})
|
||||
}
|
||||
|
||||
export async function getCommunityByPublicKeyOrFail(publicKey: Ed25519PublicKey): Promise<DbCommunity> {
|
||||
return await DbCommunity.findOneOrFail({
|
||||
where: { publicKey: publicKey.asBuffer() },
|
||||
})
|
||||
}
|
||||
|
||||
// returns all reachable communities
|
||||
// home community and all federated communities which have been verified within the last authenticationTimeoutMs
|
||||
export async function getReachableCommunities(
|
||||
@ -53,11 +76,20 @@ export async function getReachableCommunities(
|
||||
{
|
||||
authenticatedAt: Not(IsNull()),
|
||||
federatedCommunities: {
|
||||
verifiedAt: MoreThanOrEqual(new Date(Date.now() - authenticationTimeoutMs))
|
||||
verifiedAt: MoreThanOrEqual(new Date(Date.now() - authenticationTimeoutMs)),
|
||||
}
|
||||
},
|
||||
{ foreign: false },
|
||||
],
|
||||
order,
|
||||
})
|
||||
}
|
||||
|
||||
export async function getNotReachableCommunities(
|
||||
order?: FindOptionsOrder<DbCommunity>
|
||||
): Promise<DbCommunity[]> {
|
||||
return await DbCommunity.find({
|
||||
where: { authenticatedAt: IsNull(), foreign: true },
|
||||
order,
|
||||
})
|
||||
}
|
||||
71
database/src/queries/communityHandshakes.test.ts
Normal file
71
database/src/queries/communityHandshakes.test.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { AppDatabase } from '../AppDatabase'
|
||||
import {
|
||||
CommunityHandshakeState as DbCommunityHandshakeState,
|
||||
Community as DbCommunity,
|
||||
FederatedCommunity as DbFederatedCommunity,
|
||||
findPendingCommunityHandshake,
|
||||
CommunityHandshakeStateType
|
||||
} from '..'
|
||||
import { describe, expect, it, beforeEach, beforeAll, afterAll } from 'vitest'
|
||||
import { createCommunity, createVerifiedFederatedCommunity } from '../seeds/community'
|
||||
import { Ed25519PublicKey } from 'shared'
|
||||
import { randomBytes } from 'node:crypto'
|
||||
|
||||
const db = AppDatabase.getInstance()
|
||||
|
||||
beforeAll(async () => {
|
||||
await db.init()
|
||||
})
|
||||
afterAll(async () => {
|
||||
await db.destroy()
|
||||
})
|
||||
|
||||
async function createCommunityHandshakeState(publicKey: Buffer) {
|
||||
const state = new DbCommunityHandshakeState()
|
||||
state.publicKey = publicKey
|
||||
state.apiVersion = '1_0'
|
||||
state.status = CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION
|
||||
state.handshakeId = 1
|
||||
await state.save()
|
||||
}
|
||||
|
||||
describe('communityHandshakes', () => {
|
||||
// clean db for every test case
|
||||
beforeEach(async () => {
|
||||
await DbCommunity.clear()
|
||||
await DbFederatedCommunity.clear()
|
||||
await DbCommunityHandshakeState.clear()
|
||||
})
|
||||
|
||||
it('should find pending community handshake by public key', async () => {
|
||||
const com1 = await createCommunity(false)
|
||||
await createVerifiedFederatedCommunity('1_0', 100, com1)
|
||||
await createCommunityHandshakeState(com1.publicKey)
|
||||
const communityHandshakeState = await findPendingCommunityHandshake(new Ed25519PublicKey(com1.publicKey), '1_0')
|
||||
expect(communityHandshakeState).toBeDefined()
|
||||
expect(communityHandshakeState).toMatchObject({
|
||||
publicKey: com1.publicKey,
|
||||
apiVersion: '1_0',
|
||||
status: CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION,
|
||||
handshakeId: 1
|
||||
})
|
||||
})
|
||||
|
||||
it('update state', async () => {
|
||||
const publicKey = new Ed25519PublicKey(randomBytes(32))
|
||||
await createCommunityHandshakeState(publicKey.asBuffer())
|
||||
const communityHandshakeState = await findPendingCommunityHandshake(publicKey, '1_0')
|
||||
expect(communityHandshakeState).toBeDefined()
|
||||
communityHandshakeState!.status = CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK
|
||||
await communityHandshakeState!.save()
|
||||
const communityHandshakeState2 = await findPendingCommunityHandshake(publicKey, '1_0')
|
||||
const states = await DbCommunityHandshakeState.find()
|
||||
expect(communityHandshakeState2).toBeDefined()
|
||||
expect(communityHandshakeState2).toMatchObject({
|
||||
publicKey: publicKey.asBuffer(),
|
||||
apiVersion: '1_0',
|
||||
status: CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK,
|
||||
handshakeId: 1
|
||||
})
|
||||
})
|
||||
})
|
||||
35
database/src/queries/communityHandshakes.ts
Normal file
35
database/src/queries/communityHandshakes.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { Not, In } from 'typeorm'
|
||||
import { CommunityHandshakeState, CommunityHandshakeStateType} from '..'
|
||||
import { Ed25519PublicKey } from 'shared'
|
||||
|
||||
/**
|
||||
* Find a pending community handshake by public key.
|
||||
* @param publicKey The public key of the community.
|
||||
* @param apiVersion The API version of the community.
|
||||
* @param status The status of the community handshake. Optional, if not set, it will find a pending community handshake.
|
||||
* @returns The CommunityHandshakeState with associated federated community and community.
|
||||
*/
|
||||
export function findPendingCommunityHandshake(
|
||||
publicKey: Ed25519PublicKey, apiVersion: string, status?: CommunityHandshakeStateType
|
||||
): Promise<CommunityHandshakeState | null> {
|
||||
return CommunityHandshakeState.findOne({
|
||||
where: {
|
||||
publicKey: publicKey.asBuffer(),
|
||||
apiVersion,
|
||||
status: status || Not(In([
|
||||
CommunityHandshakeStateType.EXPIRED,
|
||||
CommunityHandshakeStateType.FAILED,
|
||||
CommunityHandshakeStateType.SUCCESS
|
||||
]))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function findPendingCommunityHandshakeOrFailByOneTimeCode(
|
||||
oneTimeCode: number
|
||||
): Promise<CommunityHandshakeState> {
|
||||
return CommunityHandshakeState.findOneOrFail({
|
||||
where: { oneTimeCode },
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,19 +1,15 @@
|
||||
import {
|
||||
ContributionLink as DbContributionLink,
|
||||
Event as DbEvent,
|
||||
User as DbUser
|
||||
} from '../entity'
|
||||
import { ContributionLink as DbContributionLink, Event as DbEvent, User as DbUser } from '../entity'
|
||||
|
||||
export async function findModeratorCreatingContributionLink(contributionLink: DbContributionLink): Promise<DbUser | undefined> {
|
||||
const event = await DbEvent.findOne(
|
||||
{
|
||||
where: {
|
||||
involvedContributionLinkId: contributionLink.id,
|
||||
// todo: move event types into db
|
||||
type: 'ADMIN_CONTRIBUTION_LINK_CREATE'
|
||||
},
|
||||
relations: { actingUser: true }
|
||||
}
|
||||
)
|
||||
export async function findModeratorCreatingContributionLink(
|
||||
contributionLink: DbContributionLink,
|
||||
): Promise<DbUser | undefined> {
|
||||
const event = await DbEvent.findOne({
|
||||
where: {
|
||||
involvedContributionLinkId: contributionLink.id,
|
||||
// todo: move event types into db
|
||||
type: 'ADMIN_CONTRIBUTION_LINK_CREATE',
|
||||
},
|
||||
relations: { actingUser: true },
|
||||
})
|
||||
return event?.actingUser
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,9 +2,10 @@ import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
|
||||
|
||||
export * from './user'
|
||||
export * from './communities'
|
||||
export * from './events'
|
||||
export * from './pendingTransactions'
|
||||
export * from './transactions'
|
||||
export * from './transactionLinks'
|
||||
export * from './events'
|
||||
export * from './communityHandshakes'
|
||||
|
||||
export const LOG4JS_QUERIES_CATEGORY_NAME = `${LOG4JS_BASE_CATEGORY_NAME}.queries`
|
||||
|
||||
@ -135,6 +135,6 @@ describe('user.queries', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
@ -11,7 +11,11 @@ export async function aliasExists(alias: string): Promise<boolean> {
|
||||
return user !== null
|
||||
}
|
||||
|
||||
export async function getUserById(id: number, withCommunity: boolean = false, withEmailContact: boolean = false): Promise<DbUser> {
|
||||
export async function getUserById(
|
||||
id: number,
|
||||
withCommunity: boolean = false,
|
||||
withEmailContact: boolean = false,
|
||||
): Promise<DbUser> {
|
||||
return DbUser.findOneOrFail({
|
||||
where: { id },
|
||||
relations: { community: withCommunity, emailContact: withEmailContact },
|
||||
|
||||
@ -2,7 +2,13 @@ import { Community, FederatedCommunity } from '../entity'
|
||||
import { randomBytes } from 'node:crypto'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export async function createCommunity(foreign: boolean, save: boolean = true): Promise<Community> {
|
||||
/**
|
||||
* Creates a community.
|
||||
* @param foreign
|
||||
* @param store if true, write to db, default: true
|
||||
* @returns
|
||||
*/
|
||||
export async function createCommunity(foreign: boolean, store: boolean = true): Promise<Community> {
|
||||
const community = new Community()
|
||||
community.publicKey = randomBytes(32)
|
||||
community.communityUuid = uuidv4()
|
||||
@ -23,14 +29,22 @@ export async function createCommunity(foreign: boolean, save: boolean = true): P
|
||||
community.description = 'HomeCommunity-description'
|
||||
community.url = 'http://localhost/api'
|
||||
}
|
||||
return save ? await community.save() : community
|
||||
return store ? await community.save() : community
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a verified federated community.
|
||||
* @param apiVersion
|
||||
* @param verifiedBeforeMs time in ms before the current time
|
||||
* @param community
|
||||
* @param store if true, write to db, default: true
|
||||
* @returns
|
||||
*/
|
||||
export async function createVerifiedFederatedCommunity(
|
||||
apiVersion: string,
|
||||
verifiedBeforeMs: number,
|
||||
community: Community,
|
||||
save: boolean = true
|
||||
store: boolean = true
|
||||
): Promise<FederatedCommunity> {
|
||||
const federatedCommunity = new FederatedCommunity()
|
||||
federatedCommunity.apiVersion = apiVersion
|
||||
@ -38,5 +52,5 @@ export async function createVerifiedFederatedCommunity(
|
||||
federatedCommunity.publicKey = community.publicKey
|
||||
federatedCommunity.community = community
|
||||
federatedCommunity.verifiedAt = new Date(Date.now() - verifiedBeforeMs)
|
||||
return save ? await federatedCommunity.save() : federatedCommunity
|
||||
return store ? await federatedCommunity.save() : federatedCommunity
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user