mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
Merge remote-tracking branch 'origin/master' into
3187-feature-x-sendcoind-23-invoke-settlement-of-x-pending-tx
This commit is contained in:
commit
2f5ca8d250
1
.github/workflows/lint_pr.yml
vendored
1
.github/workflows/lint_pr.yml
vendored
@ -27,6 +27,7 @@ jobs:
|
|||||||
frontend
|
frontend
|
||||||
admin
|
admin
|
||||||
database
|
database
|
||||||
|
dlt-database
|
||||||
release
|
release
|
||||||
federation
|
federation
|
||||||
dht
|
dht
|
||||||
|
|||||||
@ -7,7 +7,7 @@ module.exports = {
|
|||||||
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
|
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
lines: 89,
|
lines: 86,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setupFiles: ['<rootDir>/test/testSetup.ts'],
|
setupFiles: ['<rootDir>/test/testSetup.ts'],
|
||||||
|
|||||||
@ -9,8 +9,8 @@ export class SendCoinsArgs {
|
|||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
userReceiverIdentifier: string
|
userReceiverIdentifier: string
|
||||||
|
|
||||||
@Field(() => Date)
|
@Field(() => String)
|
||||||
creationDate: Date
|
creationDate: string
|
||||||
|
|
||||||
@Field(() => Decimal)
|
@Field(() => Decimal)
|
||||||
amount: Decimal
|
amount: Decimal
|
||||||
|
|||||||
@ -4,7 +4,7 @@ export const revertSendCoins = gql`
|
|||||||
mutation (
|
mutation (
|
||||||
$communityReceiverIdentifier: String!
|
$communityReceiverIdentifier: String!
|
||||||
$userReceiverIdentifier: String!
|
$userReceiverIdentifier: String!
|
||||||
$creationDate: Date!
|
$creationDate: String!
|
||||||
$amount: Decimal!
|
$amount: Decimal!
|
||||||
$memo: String!
|
$memo: String!
|
||||||
$communitySenderIdentifier: String!
|
$communitySenderIdentifier: String!
|
||||||
|
|||||||
@ -4,7 +4,7 @@ export const voteForSendCoins = gql`
|
|||||||
mutation (
|
mutation (
|
||||||
$communityReceiverIdentifier: String!
|
$communityReceiverIdentifier: String!
|
||||||
$userReceiverIdentifier: String!
|
$userReceiverIdentifier: String!
|
||||||
$creationDate: Date!
|
$creationDate: String!
|
||||||
$amount: Decimal!
|
$amount: Decimal!
|
||||||
$memo: String!
|
$memo: String!
|
||||||
$communitySenderIdentifier: String!
|
$communitySenderIdentifier: String!
|
||||||
|
|||||||
@ -294,7 +294,7 @@ describe('CommunityResolver', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with several community entries', () => {
|
describe('returns 2 filtered communities even with 3 existing entries', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await cleanDB()
|
await cleanDB()
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
@ -316,8 +316,8 @@ describe('CommunityResolver', () => {
|
|||||||
foreignCom1.url = 'http://stage-2.gradido.net/api'
|
foreignCom1.url = 'http://stage-2.gradido.net/api'
|
||||||
foreignCom1.publicKey = Buffer.from('publicKey-stage-2_Community')
|
foreignCom1.publicKey = Buffer.from('publicKey-stage-2_Community')
|
||||||
foreignCom1.privateKey = Buffer.from('privateKey-stage-2_Community')
|
foreignCom1.privateKey = Buffer.from('privateKey-stage-2_Community')
|
||||||
foreignCom1.communityUuid = 'Stage2-Com-UUID'
|
// foreignCom1.communityUuid = 'Stage2-Com-UUID'
|
||||||
foreignCom1.authenticatedAt = new Date()
|
// foreignCom1.authenticatedAt = new Date()
|
||||||
foreignCom1.name = 'Stage-2_Community-name'
|
foreignCom1.name = 'Stage-2_Community-name'
|
||||||
foreignCom1.description = 'Stage-2_Community-description'
|
foreignCom1.description = 'Stage-2_Community-description'
|
||||||
foreignCom1.creationDate = new Date()
|
foreignCom1.creationDate = new Date()
|
||||||
@ -336,7 +336,7 @@ describe('CommunityResolver', () => {
|
|||||||
await DbCommunity.insert(foreignCom2)
|
await DbCommunity.insert(foreignCom2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns 3 community entries', async () => {
|
it('returns 2 community entries', async () => {
|
||||||
await expect(query({ query: communities })).resolves.toMatchObject({
|
await expect(query({ query: communities })).resolves.toMatchObject({
|
||||||
data: {
|
data: {
|
||||||
communities: [
|
communities: [
|
||||||
@ -350,6 +350,7 @@ describe('CommunityResolver', () => {
|
|||||||
uuid: homeCom1.communityUuid,
|
uuid: homeCom1.communityUuid,
|
||||||
authenticatedAt: homeCom1.authenticatedAt?.toISOString(),
|
authenticatedAt: homeCom1.authenticatedAt?.toISOString(),
|
||||||
},
|
},
|
||||||
|
/*
|
||||||
{
|
{
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
foreign: foreignCom1.foreign,
|
foreign: foreignCom1.foreign,
|
||||||
@ -360,6 +361,7 @@ describe('CommunityResolver', () => {
|
|||||||
uuid: foreignCom1.communityUuid,
|
uuid: foreignCom1.communityUuid,
|
||||||
authenticatedAt: foreignCom1.authenticatedAt?.toISOString(),
|
authenticatedAt: foreignCom1.authenticatedAt?.toISOString(),
|
||||||
},
|
},
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
id: expect.any(Number),
|
id: expect.any(Number),
|
||||||
foreign: foreignCom2.foreign,
|
foreign: foreignCom2.foreign,
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { IsNull, Not } from '@dbTools/typeorm'
|
||||||
import { Community as DbCommunity } from '@entity/Community'
|
import { Community as DbCommunity } from '@entity/Community'
|
||||||
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
|
||||||
import { Resolver, Query, Authorized } from 'type-graphql'
|
import { Resolver, Query, Authorized } from 'type-graphql'
|
||||||
@ -28,6 +29,7 @@ export class CommunityResolver {
|
|||||||
@Query(() => [Community])
|
@Query(() => [Community])
|
||||||
async communities(): Promise<Community[]> {
|
async communities(): Promise<Community[]> {
|
||||||
const dbCommunities: DbCommunity[] = await DbCommunity.find({
|
const dbCommunities: DbCommunity[] = await DbCommunity.find({
|
||||||
|
where: { communityUuid: Not(IsNull()) }, //, authenticatedAt: Not(IsNull()) },
|
||||||
order: {
|
order: {
|
||||||
name: 'ASC',
|
name: 'ASC',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
/* eslint-disable new-cap */
|
/* eslint-disable new-cap */
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
|
|
||||||
import { getConnection, In } from '@dbTools/typeorm'
|
import { getConnection, In, IsNull } from '@dbTools/typeorm'
|
||||||
import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction'
|
import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction'
|
||||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||||
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink'
|
||||||
@ -282,7 +282,28 @@ export class TransactionResolver {
|
|||||||
logger.debug(`transactions=${transactions}`)
|
logger.debug(`transactions=${transactions}`)
|
||||||
|
|
||||||
// virtual transaction for pending transaction-links sum
|
// virtual transaction for pending transaction-links sum
|
||||||
if (sumHoldAvailableAmount.greaterThan(0)) {
|
if (sumHoldAvailableAmount.isZero()) {
|
||||||
|
const linkCount = await dbTransactionLink.count({
|
||||||
|
where: {
|
||||||
|
userId: user.id,
|
||||||
|
redeemedAt: IsNull(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (linkCount > 0) {
|
||||||
|
transactions.push(
|
||||||
|
virtualLinkTransaction(
|
||||||
|
lastTransaction.balance,
|
||||||
|
new Decimal(0),
|
||||||
|
new Decimal(0),
|
||||||
|
new Decimal(0),
|
||||||
|
now,
|
||||||
|
now,
|
||||||
|
self,
|
||||||
|
(userTransactions.length && userTransactions[0].balance) || new Decimal(0),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (sumHoldAvailableAmount.greaterThan(0)) {
|
||||||
logger.debug(`sumHoldAvailableAmount > 0: transactions=${transactions}`)
|
logger.debug(`sumHoldAvailableAmount > 0: transactions=${transactions}`)
|
||||||
transactions.push(
|
transactions.push(
|
||||||
virtualLinkTransaction(
|
virtualLinkTransaction(
|
||||||
|
|||||||
@ -55,7 +55,7 @@ export async function processXComPendingSendCoins(
|
|||||||
? receiverCom.communityUuid
|
? receiverCom.communityUuid
|
||||||
: CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID
|
: CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID
|
||||||
args.userReceiverIdentifier = recipient.gradidoID
|
args.userReceiverIdentifier = recipient.gradidoID
|
||||||
args.creationDate = creationDate
|
args.creationDate = creationDate.toISOString()
|
||||||
args.amount = amount
|
args.amount = amount
|
||||||
args.memo = memo
|
args.memo = memo
|
||||||
args.communitySenderIdentifier = senderCom.communityUuid
|
args.communitySenderIdentifier = senderCom.communityUuid
|
||||||
|
|||||||
6
dlt-database/.env.dist
Normal file
6
dlt-database/.env.dist
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_USER=root
|
||||||
|
DB_PASSWORD=
|
||||||
|
DB_DATABASE=gradido_dlt
|
||||||
|
MIGRATIONS_TABLE=migrations
|
||||||
8
dlt-database/.env.template
Normal file
8
dlt-database/.env.template
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
CONFIG_VERSION=$DATABASE_CONFIG_VERSION
|
||||||
|
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_USER=$DB_USER
|
||||||
|
DB_PASSWORD=$DB_PASSWORD
|
||||||
|
DB_DATABASE=gradido_dlt
|
||||||
|
MIGRATIONS_TABLE=migrations
|
||||||
3
dlt-database/.eslintignore
Normal file
3
dlt-database/.eslintignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
**/*.min.js
|
||||||
|
build
|
||||||
206
dlt-database/.eslintrc.js
Normal file
206
dlt-database/.eslintrc.js
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
// eslint-disable-next-line import/no-commonjs, import/unambiguous
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['prettier', '@typescript-eslint', 'import', 'n', 'promise'],
|
||||||
|
extends: [
|
||||||
|
'standard',
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
'plugin:import/recommended',
|
||||||
|
'plugin:import/typescript',
|
||||||
|
// 'plugin:security/recommended',
|
||||||
|
'plugin:@eslint-community/eslint-comments/recommended',
|
||||||
|
],
|
||||||
|
settings: {
|
||||||
|
'import/parsers': {
|
||||||
|
'@typescript-eslint/parser': ['.ts', '.tsx'],
|
||||||
|
},
|
||||||
|
'import/resolver': {
|
||||||
|
typescript: {
|
||||||
|
project: ['./tsconfig.json'],
|
||||||
|
},
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-console': 'error',
|
||||||
|
camelcase: 'error',
|
||||||
|
'no-debugger': 'error',
|
||||||
|
'prettier/prettier': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
htmlWhitespaceSensitivity: 'ignore',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// import
|
||||||
|
'import/export': 'error',
|
||||||
|
'import/no-deprecated': 'error',
|
||||||
|
'import/no-empty-named-blocks': 'error',
|
||||||
|
// 'import/no-extraneous-dependencies': 'error',
|
||||||
|
'import/no-mutable-exports': 'error',
|
||||||
|
'import/no-unused-modules': 'error',
|
||||||
|
'import/no-named-as-default': 'error',
|
||||||
|
'import/no-named-as-default-member': 'error',
|
||||||
|
'import/no-amd': 'error',
|
||||||
|
'import/no-commonjs': 'error',
|
||||||
|
'import/no-import-module-exports': 'error',
|
||||||
|
'import/no-nodejs-modules': 'off',
|
||||||
|
'import/unambiguous': 'error',
|
||||||
|
'import/default': 'error',
|
||||||
|
'import/named': 'error',
|
||||||
|
'import/namespace': 'error',
|
||||||
|
'import/no-absolute-path': 'error',
|
||||||
|
// 'import/no-cycle': 'error',
|
||||||
|
'import/no-dynamic-require': 'error',
|
||||||
|
'import/no-internal-modules': 'off',
|
||||||
|
'import/no-relative-packages': 'error',
|
||||||
|
// 'import/no-relative-parent-imports': ['error', { ignore: ['@/*'] }],
|
||||||
|
'import/no-self-import': 'error',
|
||||||
|
'import/no-unresolved': 'error',
|
||||||
|
'import/no-useless-path-segments': 'error',
|
||||||
|
'import/no-webpack-loader-syntax': 'error',
|
||||||
|
'import/consistent-type-specifier-style': 'error',
|
||||||
|
'import/exports-last': 'off',
|
||||||
|
'import/extensions': 'error',
|
||||||
|
'import/first': 'error',
|
||||||
|
'import/group-exports': 'off',
|
||||||
|
'import/newline-after-import': 'error',
|
||||||
|
'import/no-anonymous-default-export': 'error',
|
||||||
|
'import/no-default-export': 'error',
|
||||||
|
'import/no-duplicates': 'error',
|
||||||
|
'import/no-named-default': 'error',
|
||||||
|
'import/no-namespace': 'error',
|
||||||
|
'import/no-unassigned-import': 'error',
|
||||||
|
// 'import/order': [
|
||||||
|
// 'error',
|
||||||
|
// {
|
||||||
|
// groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
|
||||||
|
// 'newlines-between': 'always',
|
||||||
|
// pathGroups: [
|
||||||
|
// {
|
||||||
|
// pattern: '@?*/**',
|
||||||
|
// group: 'external',
|
||||||
|
// position: 'after',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// pattern: '@/**',
|
||||||
|
// group: 'external',
|
||||||
|
// position: 'after',
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// alphabetize: {
|
||||||
|
// order: 'asc' /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */,
|
||||||
|
// caseInsensitive: true /* ignore case. Options: [true, false] */,
|
||||||
|
// },
|
||||||
|
// distinctGroup: true,
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
'import/prefer-default-export': 'off',
|
||||||
|
// n
|
||||||
|
'n/handle-callback-err': 'error',
|
||||||
|
'n/no-callback-literal': 'error',
|
||||||
|
'n/no-exports-assign': 'error',
|
||||||
|
// 'n/no-extraneous-import': 'error',
|
||||||
|
'n/no-extraneous-require': 'error',
|
||||||
|
'n/no-hide-core-modules': 'error',
|
||||||
|
'n/no-missing-import': 'off', // not compatible with typescript
|
||||||
|
'n/no-missing-require': 'error',
|
||||||
|
'n/no-new-require': 'error',
|
||||||
|
'n/no-path-concat': 'error',
|
||||||
|
// 'n/no-process-exit': 'error',
|
||||||
|
'n/no-unpublished-bin': 'error',
|
||||||
|
'n/no-unpublished-import': 'off', // TODO need to exclude seeds
|
||||||
|
'n/no-unpublished-require': 'error',
|
||||||
|
'n/no-unsupported-features': ['error', { ignores: ['modules'] }],
|
||||||
|
'n/no-unsupported-features/es-builtins': 'error',
|
||||||
|
'n/no-unsupported-features/es-syntax': 'error',
|
||||||
|
'n/no-unsupported-features/node-builtins': 'error',
|
||||||
|
'n/process-exit-as-throw': 'error',
|
||||||
|
'n/shebang': 'error',
|
||||||
|
'n/callback-return': 'error',
|
||||||
|
'n/exports-style': 'error',
|
||||||
|
'n/file-extension-in-import': 'off',
|
||||||
|
'n/global-require': 'error',
|
||||||
|
'n/no-mixed-requires': 'error',
|
||||||
|
'n/no-process-env': 'error',
|
||||||
|
'n/no-restricted-import': 'error',
|
||||||
|
'n/no-restricted-require': 'error',
|
||||||
|
// 'n/no-sync': 'error',
|
||||||
|
'n/prefer-global/buffer': 'error',
|
||||||
|
'n/prefer-global/console': 'error',
|
||||||
|
'n/prefer-global/process': 'error',
|
||||||
|
'n/prefer-global/text-decoder': 'error',
|
||||||
|
'n/prefer-global/text-encoder': 'error',
|
||||||
|
'n/prefer-global/url': 'error',
|
||||||
|
'n/prefer-global/url-search-params': 'error',
|
||||||
|
'n/prefer-promises/dns': 'error',
|
||||||
|
// 'n/prefer-promises/fs': 'error',
|
||||||
|
// promise
|
||||||
|
// 'promise/catch-or-return': 'error',
|
||||||
|
// 'promise/no-return-wrap': 'error',
|
||||||
|
// 'promise/param-names': 'error',
|
||||||
|
// 'promise/always-return': 'error',
|
||||||
|
// 'promise/no-native': 'off',
|
||||||
|
// 'promise/no-nesting': 'warn',
|
||||||
|
// 'promise/no-promise-in-callback': 'warn',
|
||||||
|
// 'promise/no-callback-in-promise': 'warn',
|
||||||
|
// 'promise/avoid-new': 'warn',
|
||||||
|
// 'promise/no-new-statics': 'error',
|
||||||
|
// 'promise/no-return-in-finally': 'warn',
|
||||||
|
// 'promise/valid-params': 'warn',
|
||||||
|
// 'promise/prefer-await-to-callbacks': 'error',
|
||||||
|
// 'promise/no-multiple-resolved': 'error',
|
||||||
|
// eslint comments
|
||||||
|
'@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
|
||||||
|
'@eslint-community/eslint-comments/no-restricted-disable': 'error',
|
||||||
|
'@eslint-community/eslint-comments/no-use': 'off',
|
||||||
|
'@eslint-community/eslint-comments/require-description': 'off',
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
// only for ts files
|
||||||
|
{
|
||||||
|
files: ['*.ts', '*.tsx'],
|
||||||
|
extends: [
|
||||||
|
// 'plugin:@typescript-eslint/recommended',
|
||||||
|
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||||
|
// 'plugin:@typescript-eslint/strict',
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
// allow explicitly defined dangling promises
|
||||||
|
// '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
|
||||||
|
'no-void': ['error', { allowAsStatement: true }],
|
||||||
|
// ignore prefer-regexp-exec rule to allow string.match(regex)
|
||||||
|
'@typescript-eslint/prefer-regexp-exec': 'off',
|
||||||
|
// this should not run on ts files: https://github.com/import-js/eslint-plugin-import/issues/2215#issuecomment-911245486
|
||||||
|
'import/unambiguous': 'off',
|
||||||
|
// this is not compatible with typeorm, due to joined tables can be null, but are not defined as nullable
|
||||||
|
'@typescript-eslint/no-unnecessary-condition': 'off',
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
project: ['./tsconfig.json'],
|
||||||
|
// this is to properly reference the referenced project database without requirement of compiling it
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// we do not have testing on the database
|
||||||
|
// {
|
||||||
|
// files: ['*.test.ts'],
|
||||||
|
// plugins: ['jest'],
|
||||||
|
// rules: {
|
||||||
|
// 'jest/no-disabled-tests': 'error',
|
||||||
|
// 'jest/no-focused-tests': 'error',
|
||||||
|
// 'jest/no-identical-title': 'error',
|
||||||
|
// 'jest/prefer-to-have-length': 'error',
|
||||||
|
// 'jest/valid-expect': 'error',
|
||||||
|
// '@typescript-eslint/unbound-method': 'off',
|
||||||
|
// 'jest/unbound-method': 'error',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
}
|
||||||
27
dlt-database/.gitignore
vendored
Normal file
27
dlt-database/.gitignore
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
build/
|
||||||
|
.cache/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
test/unit/coverage
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
/.env
|
||||||
|
/.env.bak
|
||||||
|
.env.development.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
|
||||||
|
# coverage folder
|
||||||
|
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
*~
|
||||||
9
dlt-database/.prettierrc.js
Normal file
9
dlt-database/.prettierrc.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module.exports = {
|
||||||
|
semi: false,
|
||||||
|
printWidth: 100,
|
||||||
|
singleQuote: true,
|
||||||
|
trailingComma: "all",
|
||||||
|
tabWidth: 2,
|
||||||
|
bracketSpacing: true,
|
||||||
|
endOfLine: "auto",
|
||||||
|
};
|
||||||
130
dlt-database/Dockerfile
Normal file
130
dlt-database/Dockerfile
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
##################################################################################
|
||||||
|
# BASE ###########################################################################
|
||||||
|
##################################################################################
|
||||||
|
FROM node:18.7.0-alpine3.16 as base
|
||||||
|
|
||||||
|
# ENVs (available in production aswell, can be overwritten by commandline or env file)
|
||||||
|
## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
|
||||||
|
ENV DOCKER_WORKDIR="/app"
|
||||||
|
## We Cannot do `$(date -u +'%Y-%m-%dT%H:%M:%SZ')` here so we use unix timestamp=0
|
||||||
|
ENV BUILD_DATE="1970-01-01T00:00:00.00Z"
|
||||||
|
## We cannot do $(npm run version).${BUILD_NUMBER} here so we default to 0.0.0.0
|
||||||
|
ENV BUILD_VERSION="0.0.0.0"
|
||||||
|
## We cannot do `$(git rev-parse --short HEAD)` here so we default to 0000000
|
||||||
|
ENV BUILD_COMMIT="0000000"
|
||||||
|
## SET NODE_ENV
|
||||||
|
ENV NODE_ENV="production"
|
||||||
|
|
||||||
|
# Labels
|
||||||
|
LABEL org.label-schema.build-date="${BUILD_DATE}"
|
||||||
|
LABEL org.label-schema.name="gradido:database"
|
||||||
|
LABEL org.label-schema.description="Gradido Database Migration Service"
|
||||||
|
LABEL org.label-schema.usage="https://github.com/gradido/gradido/blob/master/README.md"
|
||||||
|
LABEL org.label-schema.url="https://gradido.net"
|
||||||
|
LABEL org.label-schema.vcs-url="https://github.com/gradido/gradido/tree/master/database"
|
||||||
|
LABEL org.label-schema.vcs-ref="${BUILD_COMMIT}"
|
||||||
|
LABEL org.label-schema.vendor="Gradido Community"
|
||||||
|
LABEL org.label-schema.version="${BUILD_VERSION}"
|
||||||
|
LABEL org.label-schema.schema-version="1.0"
|
||||||
|
LABEL maintainer="support@gradido.net"
|
||||||
|
|
||||||
|
# Install Additional Software
|
||||||
|
## install: git
|
||||||
|
#RUN apk --no-cache add git
|
||||||
|
|
||||||
|
## Workdir
|
||||||
|
RUN mkdir -p ${DOCKER_WORKDIR}
|
||||||
|
WORKDIR ${DOCKER_WORKDIR}
|
||||||
|
|
||||||
|
##################################################################################
|
||||||
|
# DEVELOPMENT (Connected to the local environment, to reload on demand) ##########
|
||||||
|
##################################################################################
|
||||||
|
FROM base as development
|
||||||
|
|
||||||
|
# We don't need to copy or build anything since we gonna bind to the
|
||||||
|
# local filesystem which will need a rebuild anyway
|
||||||
|
|
||||||
|
# Run command
|
||||||
|
# (for development we need to execute npm install since the
|
||||||
|
# node_modules are on another volume and need updating)
|
||||||
|
CMD /bin/sh -c "yarn install"
|
||||||
|
|
||||||
|
##################################################################################
|
||||||
|
# BUILD (Does contain all files and is therefore bloated) ########################
|
||||||
|
##################################################################################
|
||||||
|
FROM base as build
|
||||||
|
|
||||||
|
# Copy everything
|
||||||
|
COPY . .
|
||||||
|
# npm install
|
||||||
|
RUN yarn install --production=false --frozen-lockfile --non-interactive
|
||||||
|
# npm build
|
||||||
|
RUN yarn run build
|
||||||
|
|
||||||
|
##################################################################################
|
||||||
|
# TEST UP ########################################################################
|
||||||
|
##################################################################################
|
||||||
|
FROM build as test_up
|
||||||
|
|
||||||
|
# Run command
|
||||||
|
CMD /bin/sh -c "yarn install && yarn run dev_up"
|
||||||
|
|
||||||
|
##################################################################################
|
||||||
|
# TEST RESET #####################################################################
|
||||||
|
##################################################################################
|
||||||
|
FROM build as test_reset
|
||||||
|
|
||||||
|
# Run command
|
||||||
|
CMD /bin/sh -c "yarn install && yarn run dev_reset"
|
||||||
|
|
||||||
|
##################################################################################
|
||||||
|
# TEST DOWN ######################################################################
|
||||||
|
##################################################################################
|
||||||
|
FROM build as test_down
|
||||||
|
|
||||||
|
# Run command
|
||||||
|
CMD /bin/sh -c "yarn install && yarn run dev_down"
|
||||||
|
|
||||||
|
##################################################################################
|
||||||
|
# PRODUCTION (Does contain only "binary"- and static-files to reduce image size) #
|
||||||
|
##################################################################################
|
||||||
|
FROM base as production
|
||||||
|
|
||||||
|
# Copy "binary"-files from build image
|
||||||
|
COPY --from=build ${DOCKER_WORKDIR}/build ./build
|
||||||
|
# We also copy the node_modules express and serve-static for the run script
|
||||||
|
COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules
|
||||||
|
# Copy static files
|
||||||
|
# COPY --from=build ${DOCKER_WORKDIR}/public ./public
|
||||||
|
# Copy package.json for script definitions (lock file should not be needed)
|
||||||
|
COPY --from=build ${DOCKER_WORKDIR}/package.json ./package.json
|
||||||
|
# Copy Mnemonic files
|
||||||
|
COPY --from=build ${DOCKER_WORKDIR}/src/config/*.txt ./src/config/
|
||||||
|
# Copy log folder
|
||||||
|
COPY --from=build ${DOCKER_WORKDIR}/log ./log
|
||||||
|
# Copy run scripts run/
|
||||||
|
# COPY --from=build ${DOCKER_WORKDIR}/run ./run
|
||||||
|
|
||||||
|
##################################################################################
|
||||||
|
# PRODUCTION UP ##################################################################
|
||||||
|
##################################################################################
|
||||||
|
FROM production as production_up
|
||||||
|
|
||||||
|
# Run command
|
||||||
|
CMD /bin/sh -c "yarn run up"
|
||||||
|
|
||||||
|
##################################################################################
|
||||||
|
# PRODUCTION RESET ###############################################################
|
||||||
|
##################################################################################
|
||||||
|
FROM production as production_reset
|
||||||
|
|
||||||
|
# Run command
|
||||||
|
CMD /bin/sh -c "yarn run reset"
|
||||||
|
|
||||||
|
##################################################################################
|
||||||
|
# PRODUCTION DOWN ################################################################
|
||||||
|
##################################################################################
|
||||||
|
FROM production as production_down
|
||||||
|
|
||||||
|
# Run command
|
||||||
|
CMD /bin/sh -c "yarn run down"
|
||||||
39
dlt-database/README.md
Normal file
39
dlt-database/README.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# database
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upgrade migrations production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn up
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upgrade migrations development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev_up
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downgrade migrations production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downgrade migrations development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev_down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reset database
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev_reset
|
||||||
|
```
|
||||||
|
|
||||||
|
Runs all down migrations and after this all up migrations.
|
||||||
79
dlt-database/entity/0001-init_db/Account.ts
Normal file
79
dlt-database/entity/0001-init_db/Account.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
OneToMany,
|
||||||
|
ManyToMany,
|
||||||
|
JoinTable,
|
||||||
|
} from 'typeorm'
|
||||||
|
import { User } from './User'
|
||||||
|
import { Community } from './Community'
|
||||||
|
import { TransactionRecipe } from './TransactionRecipe'
|
||||||
|
import { ConfirmedTransaction } from './ConfirmedTransaction'
|
||||||
|
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||||
|
import { Decimal } from 'decimal.js-light'
|
||||||
|
|
||||||
|
@Entity('accounts')
|
||||||
|
export class Account {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@ManyToOne(() => User, (user) => user.accounts) // Assuming you have a User entity with 'accounts' relation
|
||||||
|
@JoinColumn({ name: 'user_id' })
|
||||||
|
user: User
|
||||||
|
|
||||||
|
// if user id is null, account belongs to community gmw or auf
|
||||||
|
@Column({ name: 'user_id', type: 'int', unsigned: true, nullable: true })
|
||||||
|
userId?: number
|
||||||
|
|
||||||
|
@Column({ name: 'derivation_index', type: 'int', unsigned: true })
|
||||||
|
derivationIndex: number
|
||||||
|
|
||||||
|
@Column({ name: 'derive2_pubkey', type: 'binary', length: 32, unique: true })
|
||||||
|
derive2Pubkey: Buffer
|
||||||
|
|
||||||
|
@Column({ type: 'tinyint', unsigned: true })
|
||||||
|
type: number
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'datetime', default: () => 'CURRENT_TIMESTAMP(3)' })
|
||||||
|
createdAt: Date
|
||||||
|
|
||||||
|
@Column({ name: 'confirmed_at', type: 'datetime', nullable: true })
|
||||||
|
confirmedAt?: Date
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 40,
|
||||||
|
scale: 20,
|
||||||
|
default: 0,
|
||||||
|
transformer: DecimalTransformer,
|
||||||
|
})
|
||||||
|
balance: Decimal
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'balance_date',
|
||||||
|
type: 'datetime',
|
||||||
|
default: () => 'CURRENT_TIMESTAMP(3)',
|
||||||
|
})
|
||||||
|
balanceDate: Date
|
||||||
|
|
||||||
|
@ManyToMany(() => Community, (community) => community.communityAccounts)
|
||||||
|
@JoinTable({
|
||||||
|
name: 'accounts_communities',
|
||||||
|
joinColumn: { name: 'account_id', referencedColumnName: 'id' },
|
||||||
|
inverseJoinColumn: { name: 'community_id', referencedColumnName: 'id' },
|
||||||
|
})
|
||||||
|
accountCommunities: Community[]
|
||||||
|
|
||||||
|
@OneToMany(() => TransactionRecipe, (recipe) => recipe.signingAccount)
|
||||||
|
transactionRecipesSigning?: TransactionRecipe[]
|
||||||
|
|
||||||
|
@OneToMany(() => TransactionRecipe, (recipe) => recipe.recipientAccount)
|
||||||
|
transactionRecipesRecipient?: TransactionRecipe[]
|
||||||
|
|
||||||
|
@OneToMany(() => ConfirmedTransaction, (transaction) => transaction.account)
|
||||||
|
confirmedTransactions?: ConfirmedTransaction[]
|
||||||
|
}
|
||||||
30
dlt-database/entity/0001-init_db/AccountCommunity.ts
Normal file
30
dlt-database/entity/0001-init_db/AccountCommunity.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm'
|
||||||
|
|
||||||
|
import { Account } from './Account'
|
||||||
|
import { Community } from './Community'
|
||||||
|
|
||||||
|
@Entity('accounts_communities')
|
||||||
|
export class AccountCommunity {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@ManyToOne(() => Account, (account) => account.accountCommunities)
|
||||||
|
@JoinColumn({ name: 'account_id' })
|
||||||
|
account: Account
|
||||||
|
|
||||||
|
@Column({ name: 'account_id', type: 'int', unsigned: true })
|
||||||
|
accountId: number
|
||||||
|
|
||||||
|
@ManyToOne(() => Community, (community) => community.communityAccounts)
|
||||||
|
@JoinColumn({ name: 'community_id' })
|
||||||
|
community: Community
|
||||||
|
|
||||||
|
@Column({ name: 'community_id', type: 'int', unsigned: true })
|
||||||
|
communityId: number
|
||||||
|
|
||||||
|
@Column({ name: 'valid_from', type: 'datetime' })
|
||||||
|
validFrom: Date
|
||||||
|
|
||||||
|
@Column({ name: 'valid_to', type: 'datetime', nullable: true })
|
||||||
|
validTo?: Date
|
||||||
|
}
|
||||||
68
dlt-database/entity/0001-init_db/Community.ts
Normal file
68
dlt-database/entity/0001-init_db/Community.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
JoinColumn,
|
||||||
|
OneToOne,
|
||||||
|
OneToMany,
|
||||||
|
ManyToMany,
|
||||||
|
JoinTable,
|
||||||
|
} from 'typeorm'
|
||||||
|
import { Account } from './Account'
|
||||||
|
import { TransactionRecipe } from './TransactionRecipe'
|
||||||
|
|
||||||
|
@Entity('communities')
|
||||||
|
export class Community {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({ name: 'iota_topic', collation: 'utf8mb4_unicode_ci' })
|
||||||
|
iotaTopic: string
|
||||||
|
|
||||||
|
@Column({ name: 'root_pubkey', type: 'binary', length: 32, unique: true })
|
||||||
|
rootPubkey: Buffer
|
||||||
|
|
||||||
|
@Column({ name: 'root_privkey', type: 'binary', length: 32, nullable: true })
|
||||||
|
rootPrivkey?: Buffer
|
||||||
|
|
||||||
|
@Column({ name: 'root_chaincode', type: 'binary', length: 32, nullable: true })
|
||||||
|
rootChaincode?: Buffer
|
||||||
|
|
||||||
|
@Column({ type: 'tinyint', default: true })
|
||||||
|
foreign: boolean
|
||||||
|
|
||||||
|
@Column({ name: 'gmw_account_id', type: 'int', unsigned: true, nullable: true })
|
||||||
|
gmwAccountId?: number
|
||||||
|
|
||||||
|
@OneToOne(() => Account)
|
||||||
|
@JoinColumn({ name: 'gmw_account_id' })
|
||||||
|
gmwAccount?: Account
|
||||||
|
|
||||||
|
@Column({ name: 'auf_account_id', type: 'int', unsigned: true, nullable: true })
|
||||||
|
aufAccountId?: number
|
||||||
|
|
||||||
|
@OneToOne(() => Account)
|
||||||
|
@JoinColumn({ name: 'auf_account_id' })
|
||||||
|
aufAccount?: Account
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'datetime', default: () => 'CURRENT_TIMESTAMP(3)' })
|
||||||
|
createdAt: Date
|
||||||
|
|
||||||
|
@Column({ name: 'confirmed_at', type: 'datetime', nullable: true })
|
||||||
|
confirmedAt?: Date
|
||||||
|
|
||||||
|
@ManyToMany(() => Account, (account) => account.accountCommunities)
|
||||||
|
@JoinTable({
|
||||||
|
name: 'accounts_communities',
|
||||||
|
joinColumn: { name: 'community_id', referencedColumnName: 'id' },
|
||||||
|
inverseJoinColumn: { name: 'account_id', referencedColumnName: 'id' },
|
||||||
|
})
|
||||||
|
communityAccounts: Account[]
|
||||||
|
|
||||||
|
@OneToMany(() => TransactionRecipe, (recipe) => recipe.senderCommunity)
|
||||||
|
transactionRecipesSender?: TransactionRecipe[]
|
||||||
|
|
||||||
|
@OneToMany(() => TransactionRecipe, (recipe) => recipe.recipientCommunity)
|
||||||
|
transactionRecipesRecipient?: TransactionRecipe[]
|
||||||
|
}
|
||||||
49
dlt-database/entity/0001-init_db/ConfirmedTransaction.ts
Normal file
49
dlt-database/entity/0001-init_db/ConfirmedTransaction.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, OneToOne } from 'typeorm'
|
||||||
|
import { Decimal } from 'decimal.js-light'
|
||||||
|
|
||||||
|
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||||
|
import { Account } from './Account'
|
||||||
|
import { TransactionRecipe } from './TransactionRecipe'
|
||||||
|
|
||||||
|
@Entity('confirmed_transactions')
|
||||||
|
export class ConfirmedTransaction {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true, type: 'bigint' })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@OneToOne(() => TransactionRecipe, (recipe) => recipe.confirmedTransaction)
|
||||||
|
@JoinColumn({ name: 'transaction_recipe_id' })
|
||||||
|
transactionRecipe: TransactionRecipe
|
||||||
|
|
||||||
|
@Column({ name: 'transaction_recipe_id', type: 'int', unsigned: true })
|
||||||
|
transactionRecipeId: number
|
||||||
|
|
||||||
|
@Column({ type: 'bigint' })
|
||||||
|
nr: number
|
||||||
|
|
||||||
|
@Column({ type: 'binary', length: 48 })
|
||||||
|
runningHash: Buffer
|
||||||
|
|
||||||
|
@ManyToOne(() => Account, (account) => account.confirmedTransactions)
|
||||||
|
@JoinColumn({ name: 'account_id' })
|
||||||
|
account: Account
|
||||||
|
|
||||||
|
@Column({ name: 'account_id', type: 'int', unsigned: true })
|
||||||
|
accountId: number
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'account_balance',
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 40,
|
||||||
|
scale: 20,
|
||||||
|
nullable: false,
|
||||||
|
default: 0,
|
||||||
|
transformer: DecimalTransformer,
|
||||||
|
})
|
||||||
|
accountBalance: Decimal
|
||||||
|
|
||||||
|
@Column({ name: 'iota_milestone', type: 'bigint' })
|
||||||
|
iotaMilestone: number
|
||||||
|
|
||||||
|
@Column({ name: 'confirmed_at', type: 'datetime' })
|
||||||
|
confirmedAt: Date
|
||||||
|
}
|
||||||
10
dlt-database/entity/0001-init_db/InvalidTransaction.ts
Normal file
10
dlt-database/entity/0001-init_db/InvalidTransaction.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
||||||
|
|
||||||
|
@Entity('invalid_transactions')
|
||||||
|
export class InvalidTransaction {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true, type: 'bigint' })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({ name: 'iota_message_id', type: 'binary', length: 32 })
|
||||||
|
iotaMessageId: Buffer
|
||||||
|
}
|
||||||
80
dlt-database/entity/0001-init_db/TransactionRecipe.ts
Normal file
80
dlt-database/entity/0001-init_db/TransactionRecipe.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
ManyToOne,
|
||||||
|
OneToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm'
|
||||||
|
import { Decimal } from 'decimal.js-light'
|
||||||
|
|
||||||
|
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||||
|
import { Account } from './Account'
|
||||||
|
import { Community } from './Community'
|
||||||
|
import { ConfirmedTransaction } from './ConfirmedTransaction'
|
||||||
|
|
||||||
|
@Entity('transaction_recipes')
|
||||||
|
export class TransactionRecipe {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true, type: 'bigint' })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({ name: 'iota_message_id', type: 'binary', length: 32, nullable: true })
|
||||||
|
iotaMessageId?: Buffer
|
||||||
|
|
||||||
|
// if transaction has a sender than it is also the sender account
|
||||||
|
@ManyToOne(() => Account, (account) => account.transactionRecipesSigning)
|
||||||
|
@JoinColumn({ name: 'signing_account_id' })
|
||||||
|
signingAccount: Account
|
||||||
|
|
||||||
|
@Column({ name: 'signing_account_id', type: 'int', unsigned: true })
|
||||||
|
signingAccountId: number
|
||||||
|
|
||||||
|
@ManyToOne(() => Account, (account) => account.transactionRecipesRecipient)
|
||||||
|
@JoinColumn({ name: 'recipient_account_id' })
|
||||||
|
recipientAccount?: Account
|
||||||
|
|
||||||
|
@Column({ name: 'recipient_account_id', type: 'int', unsigned: true, nullable: true })
|
||||||
|
recipientAccountId?: number
|
||||||
|
|
||||||
|
@ManyToOne(() => Community, (community) => community.transactionRecipesSender)
|
||||||
|
@JoinColumn({ name: 'sender_community_id' })
|
||||||
|
senderCommunity: Community
|
||||||
|
|
||||||
|
@Column({ name: 'sender_community_id', type: 'int', unsigned: true })
|
||||||
|
senderCommunityId: number
|
||||||
|
|
||||||
|
@ManyToOne(() => Community, (community) => community.transactionRecipesRecipient)
|
||||||
|
@JoinColumn({ name: 'recipient_community_id' })
|
||||||
|
recipientCommunity?: Community
|
||||||
|
|
||||||
|
@Column({ name: 'sender_community_id', type: 'int', unsigned: true, nullable: true })
|
||||||
|
recipientCommunityId?: number
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 40,
|
||||||
|
scale: 20,
|
||||||
|
nullable: true,
|
||||||
|
transformer: DecimalTransformer,
|
||||||
|
})
|
||||||
|
amount?: Decimal
|
||||||
|
|
||||||
|
@Column({ type: 'tinyint' })
|
||||||
|
type: number
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'datetime' })
|
||||||
|
createdAt: Date
|
||||||
|
|
||||||
|
@Column({ name: 'body_bytes', type: 'blob' })
|
||||||
|
bodyBytes: Buffer
|
||||||
|
|
||||||
|
@Column({ type: 'binary', length: 64 })
|
||||||
|
signature: Buffer
|
||||||
|
|
||||||
|
@Column({ name: 'protocol_version', type: 'int', default: 1 })
|
||||||
|
protocolVersion: number
|
||||||
|
|
||||||
|
@OneToOne(() => ConfirmedTransaction, (transaction) => transaction.transactionRecipe)
|
||||||
|
confirmedTransaction?: ConfirmedTransaction
|
||||||
|
}
|
||||||
46
dlt-database/entity/0001-init_db/User.ts
Normal file
46
dlt-database/entity/0001-init_db/User.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
OneToMany,
|
||||||
|
JoinColumn,
|
||||||
|
CreateDateColumn,
|
||||||
|
} from 'typeorm'
|
||||||
|
|
||||||
|
import { Account } from './Account'
|
||||||
|
|
||||||
|
@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
||||||
|
export class User extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'gradido_id',
|
||||||
|
length: 36,
|
||||||
|
nullable: true,
|
||||||
|
collation: 'utf8mb4_unicode_ci',
|
||||||
|
})
|
||||||
|
gradidoID?: string
|
||||||
|
|
||||||
|
@Column({ name: 'derive1_pubkey', type: 'binary', length: 32, unique: true })
|
||||||
|
derive1Pubkey: Buffer
|
||||||
|
|
||||||
|
@CreateDateColumn({
|
||||||
|
name: 'created_at',
|
||||||
|
type: 'datetime',
|
||||||
|
default: () => 'CURRENT_TIMESTAMP(3)',
|
||||||
|
})
|
||||||
|
createdAt: Date
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'confirmed_at',
|
||||||
|
type: 'datetime',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
confirmedAt?: Date
|
||||||
|
|
||||||
|
@OneToMany(() => Account, (account) => account.user)
|
||||||
|
@JoinColumn({ name: 'user_id' })
|
||||||
|
accounts?: Account[]
|
||||||
|
}
|
||||||
1
dlt-database/entity/Account.ts
Normal file
1
dlt-database/entity/Account.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { Account } from './0001-init_db/Account'
|
||||||
1
dlt-database/entity/AccountCommunity.ts
Normal file
1
dlt-database/entity/AccountCommunity.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { AccountCommunity } from './0001-init_db/AccountCommunity'
|
||||||
1
dlt-database/entity/Community.ts
Normal file
1
dlt-database/entity/Community.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { Community } from './0001-init_db/Community'
|
||||||
1
dlt-database/entity/ConfirmedTransaction.ts
Normal file
1
dlt-database/entity/ConfirmedTransaction.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { ConfirmedTransaction } from './0001-init_db/ConfirmedTransaction'
|
||||||
1
dlt-database/entity/InvalidTransaction.ts
Normal file
1
dlt-database/entity/InvalidTransaction.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { InvalidTransaction } from './0001-init_db/InvalidTransaction'
|
||||||
1
dlt-database/entity/TransactionRecipe.ts
Normal file
1
dlt-database/entity/TransactionRecipe.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { TransactionRecipe } from './0001-init_db/TransactionRecipe'
|
||||||
1
dlt-database/entity/User.ts
Normal file
1
dlt-database/entity/User.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { User } from './0001-init_db/User'
|
||||||
2
dlt-database/log/.gitignore
vendored
Normal file
2
dlt-database/log/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
130
dlt-database/migrations/0001-init_db.ts
Normal file
130
dlt-database/migrations/0001-init_db.ts
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/* FIRST MIGRATION
|
||||||
|
*
|
||||||
|
* This migration is special since it takes into account that
|
||||||
|
* the database can be setup already but also may not be.
|
||||||
|
* Therefore you will find all `CREATE TABLE` statements with
|
||||||
|
* a `IF NOT EXISTS`, all `INSERT` with an `IGNORE` and in the
|
||||||
|
* downgrade function all `DROP TABLE` with a `IF EXISTS`.
|
||||||
|
* This ensures compatibility for existing or non-existing
|
||||||
|
* databases.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 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>>) {
|
||||||
|
// write upgrade logic as parameter of queryFn
|
||||||
|
await queryFn(`
|
||||||
|
CREATE TABLE IF NOT EXISTS \`users\` (
|
||||||
|
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
\`gradido_id\` char(36) DEFAULT NULL,
|
||||||
|
\`derive1_pubkey\` binary(32) NOT NULL,
|
||||||
|
\`created_at\` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
\`confirmed_at\` datetime(3) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (\`id\`),
|
||||||
|
INDEX \`gradido_id\` (\`gradido_id\`),
|
||||||
|
UNIQUE KEY \`pubkey\` (\`pubkey\`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`)
|
||||||
|
|
||||||
|
await queryFn(`
|
||||||
|
CREATE TABLE IF NOT EXISTS \`accounts\` (
|
||||||
|
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
\`user_id\` int(10) unsigned DEFAULT NULL,
|
||||||
|
\`derivation_index\` int(10) unsigned NOT NULL,
|
||||||
|
\`derive2_pubkey\` binary(32) NOT NULL,
|
||||||
|
\`type\` tinyint unsigned NOT NULL,
|
||||||
|
\`created_at\` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
\`confirmed_at\` datetime(3) DEFAULT NULL,
|
||||||
|
\`balance\` decimal(40,20) NOT NULL DEFAULT 0,
|
||||||
|
\`balance_date\` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
PRIMARY KEY (\`id\`),
|
||||||
|
UNIQUE KEY \`pubkey\` (\`pubkey\`),
|
||||||
|
FOREIGN KEY (\`user_id\`) REFERENCES users(id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
`)
|
||||||
|
|
||||||
|
await queryFn(`
|
||||||
|
CREATE TABLE IF NOT EXISTS \`communities\` (
|
||||||
|
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
\`iota_topic\` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
\`root_pubkey\` binary(32) NOT NULL,
|
||||||
|
\`root_privkey\` binary(32) DEFAULT NULL,
|
||||||
|
\`root_chaincode\` binary(32) DEFAULT NULL,
|
||||||
|
\`foreign\` tinyint(4) NOT NULL DEFAULT true,
|
||||||
|
\`gmw_account_id\` int(10) unsigned DEFAULT NULL,
|
||||||
|
\`auf_account_id\` int(10) unsigned DEFAULT NULL,
|
||||||
|
\`created_at\` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
\`confirmed_at\` datetime(3) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (\`id\`),
|
||||||
|
UNIQUE KEY \`pubkey\` (\`pubkey\`),
|
||||||
|
FOREIGN KEY (\`gmw_account_id\`) REFERENCES accounts(id),
|
||||||
|
FOREIGN KEY (\`auf_account_id\`) REFERENCES accounts(id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`)
|
||||||
|
|
||||||
|
await queryFn(`
|
||||||
|
CREATE TABLE IF NOT EXISTS \`accounts_communities\` (
|
||||||
|
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
\`account_id\` int(10) unsigned NOT NULL,
|
||||||
|
\`community_id\` int(10) unsigned NOT NULL,
|
||||||
|
\`valid_from\` datetime(3) NOT NULL,
|
||||||
|
\`valid_to\` datetime(3) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (\`id\`),
|
||||||
|
FOREIGN KEY (\`account_id\`) REFERENCES accounts(id),
|
||||||
|
FOREIGN KEY (\`community_id\`) REFERENCES communities(id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`)
|
||||||
|
|
||||||
|
await queryFn(`
|
||||||
|
CREATE TABLE IF NOT EXISTS \`transaction_recipes\` (
|
||||||
|
\`id\` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
\`iota_message_id\` binary(32) DEFAULT NULL,
|
||||||
|
\`signing_account_id\` int(10) unsigned NOT NULL,
|
||||||
|
\`recipient_account_id\` int(10) unsigned DEFAULT NULL,
|
||||||
|
\`sender_community_id\` int(10) unsigned NOT NULL,
|
||||||
|
\`recipient_community_id\` int(10) unsigned DEFAULT NULL,
|
||||||
|
\`amount\` decimal(40,20) DEFAULT NULL,
|
||||||
|
\`type\` tinyint unsigned NOT NULL,
|
||||||
|
\`created_at\` datetime(3) NOT NULL,
|
||||||
|
\`body_bytes\` BLOB NOT NULL,
|
||||||
|
\`signature\` binary(64) NOT NULL,
|
||||||
|
\`protocol_version\` int(10) NOT NULL DEFAULT 1,
|
||||||
|
PRIMARY KEY (\`id\`),
|
||||||
|
FOREIGN KEY (\`signing_account_id\`) REFERENCES accounts(id),
|
||||||
|
FOREIGN KEY (\`recipient_account_id\`) REFERENCES accounts(id),
|
||||||
|
FOREIGN KEY (\`sender_community_id\`) REFERENCES communities(id),
|
||||||
|
FOREIGN KEY (\`recipient_community_id\`) REFERENCES communities(id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`)
|
||||||
|
|
||||||
|
await queryFn(`
|
||||||
|
CREATE TABLE IF NOT EXISTS \`confirmed_transactions\` (
|
||||||
|
\`id\` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
\`transaction_recipe_id\` bigint unsigned NOT NULL,
|
||||||
|
\`nr\` bigint unsigned NOT NULL,
|
||||||
|
\`running_hash\` binary(48) NOT NULL,
|
||||||
|
\`account_id\` int(10) unsigned NOT NULL,
|
||||||
|
\`account_balance\` decimal(40,20) NOT NULL DEFAULT 0,
|
||||||
|
\`iota_milestone\` bigint NOT NULL,
|
||||||
|
\`confirmed_at\` datetime(3) NOT NULL,
|
||||||
|
PRIMARY KEY (\`id\`),
|
||||||
|
FOREIGN KEY (\`transaction_recipe_id\`) REFERENCES transaction_recipes(id),
|
||||||
|
FOREIGN KEY (\`account_id\`) REFERENCES accounts(id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`)
|
||||||
|
|
||||||
|
await queryFn(`
|
||||||
|
CREATE TABLE IF NOT EXISTS \`invalid_transactions\` (
|
||||||
|
\`id\` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
\`iota_message_id\` binary(32) NOT NULL,
|
||||||
|
PRIMARY KEY (\`id\`),
|
||||||
|
INDEX (\`iota_message_id\`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
// write downgrade logic as parameter of queryFn
|
||||||
|
await queryFn(`DROP TABLE IF EXISTS \`users\`;`)
|
||||||
|
await queryFn(`DROP TABLE IF EXISTS \`accounts\`;`)
|
||||||
|
await queryFn(`DROP TABLE IF EXISTS \`accounts_communities\`;`)
|
||||||
|
await queryFn(`DROP TABLE IF EXISTS \`transaction_recipes\`;`)
|
||||||
|
await queryFn(`DROP TABLE IF EXISTS \`confirmed_transactions\`;`)
|
||||||
|
await queryFn(`DROP TABLE IF EXISTS \`community\`;`)
|
||||||
|
await queryFn(`DROP TABLE IF EXISTS \`invalid_transactions\`;`)
|
||||||
|
}
|
||||||
55
dlt-database/package.json
Normal file
55
dlt-database/package.json
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"name": "gradido-database",
|
||||||
|
"version": "1.23.2",
|
||||||
|
"description": "Gradido Database Tool to execute database migrations",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"repository": "https://github.com/gradido/gradido/database",
|
||||||
|
"author": "Ulf Gebhardt",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"private": false,
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc --build",
|
||||||
|
"clean": "tsc --build --clean",
|
||||||
|
"up": "cross-env TZ=UTC node build/src/index.js up",
|
||||||
|
"down": "cross-env TZ=UTC node build/src/index.js down",
|
||||||
|
"reset": "cross-env TZ=UTC node build/src/index.js reset",
|
||||||
|
"dev_up": "cross-env TZ=UTC ts-node src/index.ts up",
|
||||||
|
"dev_down": "cross-env TZ=UTC ts-node src/index.ts down",
|
||||||
|
"dev_reset": "cross-env TZ=UTC ts-node src/index.ts reset",
|
||||||
|
"lint": "eslint --max-warnings=0 --ext .js,.ts ."
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint-community/eslint-plugin-eslint-comments": "^3.2.1",
|
||||||
|
"@types/faker": "^5.5.9",
|
||||||
|
"@types/node": "^16.10.3",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||||
|
"@typescript-eslint/parser": "^5.57.1",
|
||||||
|
"eslint": "^8.37.0",
|
||||||
|
"eslint-config-prettier": "^8.8.0",
|
||||||
|
"eslint-config-standard": "^17.0.0",
|
||||||
|
"eslint-import-resolver-typescript": "^3.5.4",
|
||||||
|
"eslint-plugin-import": "^2.27.5",
|
||||||
|
"eslint-plugin-n": "^15.7.0",
|
||||||
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"eslint-plugin-promise": "^6.1.1",
|
||||||
|
"eslint-plugin-security": "^1.7.1",
|
||||||
|
"prettier": "^2.8.7",
|
||||||
|
"ts-node": "^10.2.1",
|
||||||
|
"typescript": "^4.3.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/uuid": "^8.3.4",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
|
"decimal.js-light": "^2.5.1",
|
||||||
|
"dotenv": "^10.0.0",
|
||||||
|
"mysql2": "^2.3.0",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"ts-mysql-migrate": "^1.0.2",
|
||||||
|
"typeorm": "^0.3.16",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
}
|
||||||
39
dlt-database/src/config/index.ts
Normal file
39
dlt-database/src/config/index.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/* eslint-disable n/no-process-env */
|
||||||
|
|
||||||
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
|
dotenv.config()
|
||||||
|
|
||||||
|
const constants = {
|
||||||
|
CONFIG_VERSION: {
|
||||||
|
DEFAULT: 'DEFAULT',
|
||||||
|
EXPECTED: 'v1.2022-08-22',
|
||||||
|
CURRENT: '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const database = {
|
||||||
|
DB_HOST: process.env.DB_HOST || 'localhost',
|
||||||
|
DB_PORT: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 3306,
|
||||||
|
DB_USER: process.env.DB_USER || 'root',
|
||||||
|
DB_PASSWORD: process.env.DB_PASSWORD || '',
|
||||||
|
DB_DATABASE: process.env.DB_DATABASE || 'gradido_dlt',
|
||||||
|
}
|
||||||
|
|
||||||
|
const migrations = {
|
||||||
|
MIGRATIONS_TABLE: process.env.MIGRATIONS_TABLE || 'migrations',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check config version
|
||||||
|
constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION || constants.CONFIG_VERSION.DEFAULT
|
||||||
|
if (
|
||||||
|
![constants.CONFIG_VERSION.EXPECTED, constants.CONFIG_VERSION.DEFAULT].includes(
|
||||||
|
constants.CONFIG_VERSION.CURRENT,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`Fatal: Config Version incorrect - expected "${constants.CONFIG_VERSION.EXPECTED}" or "${constants.CONFIG_VERSION.DEFAULT}", but found "${constants.CONFIG_VERSION.CURRENT}"`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CONFIG = { ...constants, ...database, ...migrations }
|
||||||
56
dlt-database/src/index.ts
Normal file
56
dlt-database/src/index.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { createDatabase } from './prepare'
|
||||||
|
import { CONFIG } from './config'
|
||||||
|
|
||||||
|
import { createPool } from 'mysql'
|
||||||
|
import { Migration } from 'ts-mysql-migrate'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
const run = async (command: string) => {
|
||||||
|
// Database actions not supported by our migration library
|
||||||
|
await createDatabase()
|
||||||
|
|
||||||
|
// Initialize Migrations
|
||||||
|
const pool = createPool({
|
||||||
|
host: CONFIG.DB_HOST,
|
||||||
|
port: CONFIG.DB_PORT,
|
||||||
|
user: CONFIG.DB_USER,
|
||||||
|
password: CONFIG.DB_PASSWORD,
|
||||||
|
database: CONFIG.DB_DATABASE,
|
||||||
|
})
|
||||||
|
const migration = new Migration({
|
||||||
|
conn: pool,
|
||||||
|
tableName: CONFIG.MIGRATIONS_TABLE,
|
||||||
|
silent: true,
|
||||||
|
dir: path.join(__dirname, '..', 'migrations'),
|
||||||
|
})
|
||||||
|
await migration.initialize()
|
||||||
|
|
||||||
|
// Execute command
|
||||||
|
switch (command) {
|
||||||
|
case 'up':
|
||||||
|
await migration.up() // use for upgrade script
|
||||||
|
break
|
||||||
|
case 'down':
|
||||||
|
await migration.down() // use for downgrade script
|
||||||
|
break
|
||||||
|
case 'reset':
|
||||||
|
// TODO protect from production
|
||||||
|
await migration.reset()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported command ${command}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminate connections gracefully
|
||||||
|
pool.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
run(process.argv[2])
|
||||||
|
.catch((err) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
process.exit()
|
||||||
|
})
|
||||||
22
dlt-database/src/prepare.ts
Normal file
22
dlt-database/src/prepare.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { createConnection } from 'mysql2/promise'
|
||||||
|
|
||||||
|
import { CONFIG } from './config'
|
||||||
|
|
||||||
|
export const createDatabase = async (): Promise<void> => {
|
||||||
|
const con = await createConnection({
|
||||||
|
host: CONFIG.DB_HOST,
|
||||||
|
port: CONFIG.DB_PORT,
|
||||||
|
user: CONFIG.DB_USER,
|
||||||
|
password: CONFIG.DB_PASSWORD,
|
||||||
|
})
|
||||||
|
|
||||||
|
await con.connect()
|
||||||
|
|
||||||
|
// Create Database `gradido_dlt`
|
||||||
|
await con.query(`
|
||||||
|
CREATE DATABASE IF NOT EXISTS ${CONFIG.DB_DATABASE}
|
||||||
|
DEFAULT CHARACTER SET utf8mb4
|
||||||
|
DEFAULT COLLATE utf8mb4_unicode_ci;`)
|
||||||
|
|
||||||
|
await con.end()
|
||||||
|
}
|
||||||
1
dlt-database/src/typeorm.ts
Normal file
1
dlt-database/src/typeorm.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from 'typeorm'
|
||||||
19
dlt-database/src/typeorm/DecimalTransformer.ts
Normal file
19
dlt-database/src/typeorm/DecimalTransformer.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Decimal } from 'decimal.js-light'
|
||||||
|
import { ValueTransformer } from 'typeorm'
|
||||||
|
|
||||||
|
Decimal.set({
|
||||||
|
precision: 25,
|
||||||
|
rounding: Decimal.ROUND_HALF_UP,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DecimalTransformer: ValueTransformer = {
|
||||||
|
/**
|
||||||
|
* Used to marshal Decimal when writing to the database.
|
||||||
|
*/
|
||||||
|
to: (decimal: Decimal | null): string | null => (decimal ? decimal.toString() : null),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to unmarshal Decimal when reading from the database.
|
||||||
|
*/
|
||||||
|
from: (decimal: string | null): Decimal | null => (decimal ? new Decimal(decimal) : null),
|
||||||
|
}
|
||||||
73
dlt-database/tsconfig.json
Normal file
73
dlt-database/tsconfig.json
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
|
||||||
|
/* Basic Options */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||||
|
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||||
|
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||||
|
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
"declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./build/outfile.js", /* Concatenate and emit output to single file. */
|
||||||
|
"outDir": "./build", /* Redirect output structure to the directory. */
|
||||||
|
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
"composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
"strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */
|
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||||
|
|
||||||
|
/* Module Resolution Options */
|
||||||
|
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "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. */
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
|
||||||
|
/* Experimental Options */
|
||||||
|
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
|
||||||
|
/* Advanced Options */
|
||||||
|
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||||
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||||
|
},
|
||||||
|
"references": [] /* Any project that is referenced must itself have a `references` array (which may be empty). */
|
||||||
|
}
|
||||||
2573
dlt-database/yarn.lock
Normal file
2573
dlt-database/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -35,6 +35,12 @@ services:
|
|||||||
########################################################
|
########################################################
|
||||||
database:
|
database:
|
||||||
platform: linux/amd64
|
platform: linux/amd64
|
||||||
|
|
||||||
|
########################################################
|
||||||
|
# DLT-DATABASE #############################################
|
||||||
|
########################################################
|
||||||
|
dlt-database:
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
#########################################################
|
#########################################################
|
||||||
## NGINX ################################################
|
## NGINX ################################################
|
||||||
|
|||||||
@ -149,6 +149,28 @@ services:
|
|||||||
# bind the local folder to the docker to allow live reload
|
# bind the local folder to the docker to allow live reload
|
||||||
- ./database:/app
|
- ./database:/app
|
||||||
|
|
||||||
|
########################################################
|
||||||
|
# DLT-DATABASE ##############################################
|
||||||
|
########################################################
|
||||||
|
dlt-database:
|
||||||
|
# we always run on production here since else the service lingers
|
||||||
|
# feel free to change this behaviour if it seems useful
|
||||||
|
# Due to problems with the volume caching the built files
|
||||||
|
# we changed this to test build. This keeps the service running.
|
||||||
|
# name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there
|
||||||
|
image: gradido/dlt-database:local-test_up
|
||||||
|
build:
|
||||||
|
target: test_up
|
||||||
|
environment:
|
||||||
|
- NODE_ENV="development"
|
||||||
|
volumes:
|
||||||
|
# This makes sure the docker container has its own node modules.
|
||||||
|
# Therefore it is possible to have a different node version on the host machine
|
||||||
|
- dlt-database_node_modules:/app/node_modules
|
||||||
|
- dlt-database_build:/app/build
|
||||||
|
# bind the local folder to the docker to allow live reload
|
||||||
|
- ./dlt-database:/app
|
||||||
|
|
||||||
#########################################################
|
#########################################################
|
||||||
## MARIADB ##############################################
|
## MARIADB ##############################################
|
||||||
#########################################################
|
#########################################################
|
||||||
@ -203,4 +225,6 @@ volumes:
|
|||||||
federation_database_node_modules:
|
federation_database_node_modules:
|
||||||
federation_database_build:
|
federation_database_build:
|
||||||
database_node_modules:
|
database_node_modules:
|
||||||
database_build:
|
database_build:
|
||||||
|
dlt-database_node_modules:
|
||||||
|
dlt-database_build:
|
||||||
@ -77,6 +77,18 @@ services:
|
|||||||
- NODE_ENV="test"
|
- NODE_ENV="test"
|
||||||
# restart: always # this is very dangerous, but worth a test for the delayed mariadb startup at first run
|
# restart: always # this is very dangerous, but worth a test for the delayed mariadb startup at first run
|
||||||
|
|
||||||
|
########################################################
|
||||||
|
# DLT-DATABASE #############################################
|
||||||
|
########################################################
|
||||||
|
dlt-database:
|
||||||
|
image: gradido/dlt-database:test_up
|
||||||
|
build:
|
||||||
|
context: ./dlt-database
|
||||||
|
target: test_up
|
||||||
|
environment:
|
||||||
|
- NODE_ENV="test"
|
||||||
|
# restart: always # this is very dangerous, but worth a test for the delayed mariadb startup at first run
|
||||||
|
|
||||||
#########################################################
|
#########################################################
|
||||||
## MARIADB ##############################################
|
## MARIADB ##############################################
|
||||||
#########################################################
|
#########################################################
|
||||||
|
|||||||
@ -239,6 +239,32 @@ services:
|
|||||||
# Application only envs
|
# Application only envs
|
||||||
#env_file:
|
#env_file:
|
||||||
# - ./frontend/.env
|
# - ./frontend/.env
|
||||||
|
|
||||||
|
########################################################
|
||||||
|
# DLT-DATABASE #############################################
|
||||||
|
########################################################
|
||||||
|
dlt-database:
|
||||||
|
# name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there
|
||||||
|
image: gradido/dlt-database:local-production_up
|
||||||
|
build:
|
||||||
|
context: ./dlt-database
|
||||||
|
target: production_up
|
||||||
|
depends_on:
|
||||||
|
- mariadb
|
||||||
|
networks:
|
||||||
|
- internal-net
|
||||||
|
- external-net # this is required to fetch the packages
|
||||||
|
environment:
|
||||||
|
# Envs used in Dockerfile
|
||||||
|
# - DOCKER_WORKDIR="/app"
|
||||||
|
- BUILD_DATE
|
||||||
|
- BUILD_VERSION
|
||||||
|
- BUILD_COMMIT
|
||||||
|
- NODE_ENV="production"
|
||||||
|
- DB_HOST=mariadb
|
||||||
|
# Application only envs
|
||||||
|
#env_file:
|
||||||
|
# - ./frontend/.env
|
||||||
|
|
||||||
#########################################################
|
#########################################################
|
||||||
## NGINX ################################################
|
## NGINX ################################################
|
||||||
|
|||||||
4
docu/Concepts/DLT/database.md
Normal file
4
docu/Concepts/DLT/database.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# DLT Connector Database
|
||||||
|
|
||||||
|

|
||||||
|
[Link zum PDF](img/dlt-diagramm.pdf)
|
||||||
26
docu/Concepts/DLT/derived_keys.md
Normal file
26
docu/Concepts/DLT/derived_keys.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Key Derivation
|
||||||
|
The DLT connector uses key derivation to derive keys for each user in the community account with a master key.
|
||||||
|

|
||||||
|
|
||||||
|
## user accounts
|
||||||
|
The Path for key derivation contain the gradido id, and derivation index of account
|
||||||
|
Gradido ID: 03857ac1-9cc2-483e-8a91-e5b10f5b8d16
|
||||||
|
Derivation Index: 1
|
||||||
|
Key derivation Path:
|
||||||
|
```
|
||||||
|
m/03857ac1'/9cc2'/483e'/8a91'/e5b10f5b8d16'/1
|
||||||
|
```
|
||||||
|
|
||||||
|
## gmw and auf accounts
|
||||||
|
For gmw and auf accounts two special Paths used:
|
||||||
|
gmw account => account nr 1
|
||||||
|
```
|
||||||
|
m/1'
|
||||||
|
```
|
||||||
|
auf account => account nr 2
|
||||||
|
```
|
||||||
|
m/2'
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
11057
docu/Concepts/DLT/img/dlt-diagramm.pdf
Normal file
11057
docu/Concepts/DLT/img/dlt-diagramm.pdf
Normal file
File diff suppressed because one or more lines are too long
BIN
docu/Concepts/DLT/img/dlt-diagramm.png
Normal file
BIN
docu/Concepts/DLT/img/dlt-diagramm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
67
docu/Concepts/DLT/overview.md
Normal file
67
docu/Concepts/DLT/overview.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# DLT-Connector Overview
|
||||||
|
|
||||||
|
What the DLT Connector does roughly.
|
||||||
|
|
||||||
|
- create transaction
|
||||||
|
- receive transactions from iota
|
||||||
|
- warmup, load missing transactions from iota on startup
|
||||||
|
|
||||||
|
## create transaction
|
||||||
|
- called from backend with transaction details
|
||||||
|
- sender user | signing user (in case of contribution)
|
||||||
|
- uuid
|
||||||
|
- account nr | default = 1
|
||||||
|
- community uuid
|
||||||
|
- recipient user
|
||||||
|
- uuid
|
||||||
|
- account nr | default = 1
|
||||||
|
- community uuid
|
||||||
|
- amount
|
||||||
|
- memo
|
||||||
|
- type
|
||||||
|
- createdAt
|
||||||
|
- load or create accounts
|
||||||
|
- compose protobuf transaction
|
||||||
|
- derive correct private key for signing account and sign transaction
|
||||||
|
- validate transaction
|
||||||
|
- write transaction into transaction_recipes table
|
||||||
|
- send transaction to iota
|
||||||
|
- update iota message id in transaction_recipes table
|
||||||
|
- return to backend with iota message id
|
||||||
|
|
||||||
|
|
||||||
|
## receive transactions from iota
|
||||||
|
- listen on all registered community topics on iota
|
||||||
|
- make sure we have everything from milestone
|
||||||
|
- sort per community by iota milestone, createdAt ASC
|
||||||
|
- per message:
|
||||||
|
- deserialize to protobuf object
|
||||||
|
- validate
|
||||||
|
- if valid:
|
||||||
|
- calculate running_hash and account_balance
|
||||||
|
- write into confirmed_transactions
|
||||||
|
- if invalid:
|
||||||
|
- write into invalid_transactions
|
||||||
|
- send request to backend with final transaction data for comparison
|
||||||
|
- sender user | signing user (in case of contribution)
|
||||||
|
- uuid
|
||||||
|
- account nr
|
||||||
|
- community uuid
|
||||||
|
- recipient user
|
||||||
|
- uuid
|
||||||
|
- account nr
|
||||||
|
- community uuid
|
||||||
|
- amount
|
||||||
|
- memo
|
||||||
|
- createdAt
|
||||||
|
- confirmedAt
|
||||||
|
- type
|
||||||
|
- iota message id
|
||||||
|
- balance for createdAt
|
||||||
|
- decay for createdAt
|
||||||
|
|
||||||
|
## warmup, load missing transactions from iota or Chronicle on startup
|
||||||
|
- read all iota message ids from all registered topics
|
||||||
|
- check if already exist
|
||||||
|
- load details for not existing message ids
|
||||||
|
- do for every message [receive](#receive-transactions-from-iota)
|
||||||
@ -1,6 +1,4 @@
|
|||||||
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
|
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
|
||||||
/* eslint-disable n/no-process-env */
|
|
||||||
|
|
||||||
import { Decimal } from 'decimal.js-light'
|
import { Decimal } from 'decimal.js-light'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
|
|||||||
@ -9,8 +9,8 @@ export class SendCoinsArgs {
|
|||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
userReceiverIdentifier: string
|
userReceiverIdentifier: string
|
||||||
|
|
||||||
@Field(() => Date)
|
@Field(() => String)
|
||||||
creationDate: Date
|
creationDate: string
|
||||||
|
|
||||||
@Field(() => Decimal)
|
@Field(() => Decimal)
|
||||||
amount: Decimal
|
amount: Decimal
|
||||||
|
|||||||
@ -1,38 +1,52 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||||
/*
|
|
||||||
import { createTestClient } from 'apollo-server-testing'
|
|
||||||
import { Community as DbCommunity } from '@entity/Community'
|
import { Community as DbCommunity } from '@entity/Community'
|
||||||
import CONFIG from '@/config'
|
import CONFIG from '@/config'
|
||||||
|
import { User as DbUser } from '@entity/User'
|
||||||
|
import { fullName } from '@/graphql/util/fullName'
|
||||||
|
import { GraphQLError } from 'graphql'
|
||||||
import { cleanDB, testEnvironment } from '@test/helpers'
|
import { cleanDB, testEnvironment } from '@test/helpers'
|
||||||
import { createServer } from '@/server/createServer'
|
import { logger } from '@test/testSetup'
|
||||||
|
import { Connection } from '@dbTools/typeorm'
|
||||||
|
|
||||||
let query: any
|
let mutate: ApolloServerTestClient['mutate'], con: Connection
|
||||||
|
// let query: ApolloServerTestClient['query']
|
||||||
|
|
||||||
// to do: We need a setup for the tests that closes the connection
|
let testEnv: {
|
||||||
let con: any
|
mutate: ApolloServerTestClient['mutate']
|
||||||
|
query: ApolloServerTestClient['query']
|
||||||
|
con: Connection
|
||||||
|
}
|
||||||
|
|
||||||
CONFIG.FEDERATION_API = '1_0'
|
CONFIG.FEDERATION_API = '1_0'
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const server = await createServer()
|
testEnv = await testEnvironment(logger)
|
||||||
con = server.con
|
mutate = testEnv.mutate
|
||||||
query = createTestClient(server.apollo).query
|
// query = testEnv.query
|
||||||
await cleanDB()
|
con = testEnv.con
|
||||||
|
|
||||||
|
// const server = await createServer()
|
||||||
|
// con = server.con
|
||||||
|
// query = createTestClient(server.apollo).query
|
||||||
|
// mutate = createTestClient(server.apollo).mutate
|
||||||
// DbCommunity.clear()
|
// DbCommunity.clear()
|
||||||
|
// DbUser.clear()
|
||||||
|
await cleanDB()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await con.close()
|
// await cleanDB()
|
||||||
|
await con.destroy()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('SendCoinsResolver', () => {
|
describe('SendCoinsResolver', () => {
|
||||||
const voteForSendCoinsMutation = `
|
const voteForSendCoinsMutation = `
|
||||||
mutation (
|
mutation (
|
||||||
$communityReceiverIdentifier: String!
|
$communityReceiverIdentifier: String!
|
||||||
$userReceiverIdentifier: String!
|
$userReceiverIdentifier: String!
|
||||||
$creationDate: Date!
|
$creationDate: String!
|
||||||
$amount: Decimal!
|
$amount: Decimal!
|
||||||
$memo: String!
|
$memo: String!
|
||||||
$communitySenderIdentifier: String!
|
$communitySenderIdentifier: String!
|
||||||
@ -51,57 +65,292 @@ describe('SendCoinsResolver', () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
const revertSendCoinsMutation = `
|
||||||
|
mutation (
|
||||||
|
$communityReceiverIdentifier: String!
|
||||||
|
$userReceiverIdentifier: String!
|
||||||
|
$creationDate: String!
|
||||||
|
$amount: Decimal!
|
||||||
|
$memo: String!
|
||||||
|
$communitySenderIdentifier: String!
|
||||||
|
$userSenderIdentifier: String!
|
||||||
|
$userSenderName: String!
|
||||||
|
) {
|
||||||
|
revertSendCoins(
|
||||||
|
communityReceiverIdentifier: $communityReceiverIdentifier
|
||||||
|
userReceiverIdentifier: $userReceiverIdentifier
|
||||||
|
creationDate: $creationDate
|
||||||
|
amount: $amount
|
||||||
|
memo: $memo
|
||||||
|
communitySenderIdentifier: $communitySenderIdentifier
|
||||||
|
userSenderIdentifier: $userSenderIdentifier
|
||||||
|
userSenderName: $userSenderName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
describe('get 1st TX at all', () => {
|
describe('voteForSendCoins', () => {
|
||||||
let homeCom: DbCommunity
|
let homeCom: DbCommunity
|
||||||
let foreignCom: DbCommunity
|
let foreignCom: DbCommunity
|
||||||
const varsPeterLustig = {
|
let sendUser: DbUser
|
||||||
email: 'peter@lustig.de',
|
let recipUser: DbUser
|
||||||
firstName: 'Peter',
|
|
||||||
lastName: 'Lustig',
|
|
||||||
language: 'de',
|
|
||||||
publisherId: 1234,
|
|
||||||
}
|
|
||||||
const varsBibiBloxberg = {
|
|
||||||
email: 'bibi@bloxberg.de',
|
|
||||||
firstName: 'Bibi',
|
|
||||||
lastName: 'Bloxberg',
|
|
||||||
language: 'de',
|
|
||||||
publisherId: 1234,
|
|
||||||
}
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
await cleanDB()
|
||||||
homeCom = DbCommunity.create()
|
homeCom = DbCommunity.create()
|
||||||
homeCom.foreign = false
|
homeCom.foreign = false
|
||||||
homeCom.url = 'homeCommunity-url'
|
homeCom.url = 'homeCom-url'
|
||||||
homeCom.name = 'Community-Name'
|
homeCom.name = 'homeCom-Name'
|
||||||
homeCom.description = 'Community-Description'
|
homeCom.description = 'homeCom-Description'
|
||||||
homeCom.creationDate = new Date()
|
homeCom.creationDate = new Date()
|
||||||
homeCom.publicKey = Buffer.from('homeCommunity-publicKey')
|
homeCom.publicKey = Buffer.from('homeCom-publicKey')
|
||||||
|
homeCom.communityUuid = 'homeCom-UUID'
|
||||||
await DbCommunity.insert(homeCom)
|
await DbCommunity.insert(homeCom)
|
||||||
|
|
||||||
foreignCom = DbCommunity.create()
|
foreignCom = DbCommunity.create()
|
||||||
foreignCom.foreign = true
|
foreignCom.foreign = true
|
||||||
foreignCom.url = 'foreignCommunity-url'
|
foreignCom.url = 'foreignCom-url'
|
||||||
foreignCom.name = 'foreignCommunity-Name'
|
foreignCom.name = 'foreignCom-Name'
|
||||||
foreignCom.description = 'foreign Community-Description'
|
foreignCom.description = 'foreignCom-Description'
|
||||||
foreignCom.creationDate = new Date()
|
foreignCom.creationDate = new Date()
|
||||||
foreignCom.publicKey = Buffer.from('foreignCommunity-publicKey')
|
foreignCom.publicKey = Buffer.from('foreignCom-publicKey')
|
||||||
|
foreignCom.communityUuid = 'foreignCom-UUID'
|
||||||
await DbCommunity.insert(foreignCom)
|
await DbCommunity.insert(foreignCom)
|
||||||
|
|
||||||
|
sendUser = DbUser.create()
|
||||||
|
sendUser.alias = 'sendUser-alias'
|
||||||
|
sendUser.firstName = 'sendUser-FirstName'
|
||||||
|
sendUser.gradidoID = 'sendUser-GradidoID'
|
||||||
|
sendUser.lastName = 'sendUser-LastName'
|
||||||
|
await DbUser.insert(sendUser)
|
||||||
|
|
||||||
|
recipUser = DbUser.create()
|
||||||
|
recipUser.alias = 'recipUser-alias'
|
||||||
|
recipUser.firstName = 'recipUser-FirstName'
|
||||||
|
recipUser.gradidoID = 'recipUser-GradidoID'
|
||||||
|
recipUser.lastName = 'recipUser-LastName'
|
||||||
|
await DbUser.insert(recipUser)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns public CommunityInfo', async () => {
|
describe('unknown recipient community', () => {
|
||||||
await expect(query({ query: getPublicCommunityInfoQuery })).resolves.toMatchObject({
|
it('throws an error', async () => {
|
||||||
data: {
|
jest.clearAllMocks()
|
||||||
getPublicCommunityInfo: {
|
expect(
|
||||||
name: 'Community-Name',
|
await mutate({
|
||||||
description: 'Community-Description',
|
mutation: voteForSendCoinsMutation,
|
||||||
creationDate: homeCom.creationDate?.toISOString(),
|
variables: {
|
||||||
publicKey: expect.stringMatching('homeCommunity-publicKey'),
|
communityReceiverIdentifier: 'invalid foreignCom',
|
||||||
},
|
userReceiverIdentifier: recipUser.gradidoID,
|
||||||
|
creationDate: new Date().toISOString(),
|
||||||
|
amount: 100,
|
||||||
|
memo: 'X-Com-TX memo',
|
||||||
|
communitySenderIdentifier: homeCom.communityUuid,
|
||||||
|
userSenderIdentifier: sendUser.gradidoID,
|
||||||
|
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('voteForSendCoins with wrong communityReceiverIdentifier')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('unknown recipient user', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
expect(
|
||||||
|
await mutate({
|
||||||
|
mutation: voteForSendCoinsMutation,
|
||||||
|
variables: {
|
||||||
|
communityReceiverIdentifier: foreignCom.communityUuid,
|
||||||
|
userReceiverIdentifier: 'invalid recipient',
|
||||||
|
creationDate: new Date().toISOString(),
|
||||||
|
amount: 100,
|
||||||
|
memo: 'X-Com-TX memo',
|
||||||
|
communitySenderIdentifier: homeCom.communityUuid,
|
||||||
|
userSenderIdentifier: sendUser.gradidoID,
|
||||||
|
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [
|
||||||
|
new GraphQLError(
|
||||||
|
'voteForSendCoins with unknown userReceiverIdentifier in the community=',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('valid X-Com-TX voted', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
expect(
|
||||||
|
await mutate({
|
||||||
|
mutation: voteForSendCoinsMutation,
|
||||||
|
variables: {
|
||||||
|
communityReceiverIdentifier: foreignCom.communityUuid,
|
||||||
|
userReceiverIdentifier: recipUser.gradidoID,
|
||||||
|
creationDate: new Date().toISOString(),
|
||||||
|
amount: 100,
|
||||||
|
memo: 'X-Com-TX memo',
|
||||||
|
communitySenderIdentifier: homeCom.communityUuid,
|
||||||
|
userSenderIdentifier: sendUser.gradidoID,
|
||||||
|
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
voteForSendCoins: 'recipUser-FirstName recipUser-LastName',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('revertSendCoins', () => {
|
||||||
|
let homeCom: DbCommunity
|
||||||
|
let foreignCom: DbCommunity
|
||||||
|
let sendUser: DbUser
|
||||||
|
let recipUser: DbUser
|
||||||
|
const creationDate = new Date()
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
homeCom = DbCommunity.create()
|
||||||
|
homeCom.foreign = false
|
||||||
|
homeCom.url = 'homeCom-url'
|
||||||
|
homeCom.name = 'homeCom-Name'
|
||||||
|
homeCom.description = 'homeCom-Description'
|
||||||
|
homeCom.creationDate = new Date()
|
||||||
|
homeCom.publicKey = Buffer.from('homeCom-publicKey')
|
||||||
|
homeCom.communityUuid = 'homeCom-UUID'
|
||||||
|
await DbCommunity.insert(homeCom)
|
||||||
|
|
||||||
|
foreignCom = DbCommunity.create()
|
||||||
|
foreignCom.foreign = true
|
||||||
|
foreignCom.url = 'foreignCom-url'
|
||||||
|
foreignCom.name = 'foreignCom-Name'
|
||||||
|
foreignCom.description = 'foreignCom-Description'
|
||||||
|
foreignCom.creationDate = new Date()
|
||||||
|
foreignCom.publicKey = Buffer.from('foreignCom-publicKey')
|
||||||
|
foreignCom.communityUuid = 'foreignCom-UUID'
|
||||||
|
await DbCommunity.insert(foreignCom)
|
||||||
|
|
||||||
|
sendUser = DbUser.create()
|
||||||
|
sendUser.alias = 'sendUser-alias'
|
||||||
|
sendUser.firstName = 'sendUser-FirstName'
|
||||||
|
sendUser.gradidoID = 'sendUser-GradidoID'
|
||||||
|
sendUser.lastName = 'sendUser-LastName'
|
||||||
|
await DbUser.insert(sendUser)
|
||||||
|
|
||||||
|
recipUser = DbUser.create()
|
||||||
|
recipUser.alias = 'recipUser-alias'
|
||||||
|
recipUser.firstName = 'recipUser-FirstName'
|
||||||
|
recipUser.gradidoID = 'recipUser-GradidoID'
|
||||||
|
recipUser.lastName = 'recipUser-LastName'
|
||||||
|
await DbUser.insert(recipUser)
|
||||||
|
|
||||||
|
await mutate({
|
||||||
|
mutation: voteForSendCoinsMutation,
|
||||||
|
variables: {
|
||||||
|
communityReceiverIdentifier: foreignCom.communityUuid,
|
||||||
|
userReceiverIdentifier: recipUser.gradidoID,
|
||||||
|
creationDate: creationDate.toISOString(),
|
||||||
|
amount: 100,
|
||||||
|
memo: 'X-Com-TX memo',
|
||||||
|
communitySenderIdentifier: homeCom.communityUuid,
|
||||||
|
userSenderIdentifier: sendUser.gradidoID,
|
||||||
|
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('unknown recipient community', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
expect(
|
||||||
|
await mutate({
|
||||||
|
mutation: revertSendCoinsMutation,
|
||||||
|
variables: {
|
||||||
|
communityReceiverIdentifier: 'invalid foreignCom',
|
||||||
|
userReceiverIdentifier: recipUser.gradidoID,
|
||||||
|
creationDate: creationDate.toISOString(),
|
||||||
|
amount: 100,
|
||||||
|
memo: 'X-Com-TX memo',
|
||||||
|
communitySenderIdentifier: homeCom.communityUuid,
|
||||||
|
userSenderIdentifier: sendUser.gradidoID,
|
||||||
|
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('revertSendCoins with wrong communityReceiverIdentifier')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('unknown recipient user', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
expect(
|
||||||
|
await mutate({
|
||||||
|
mutation: revertSendCoinsMutation,
|
||||||
|
variables: {
|
||||||
|
communityReceiverIdentifier: foreignCom.communityUuid,
|
||||||
|
userReceiverIdentifier: 'invalid recipient',
|
||||||
|
creationDate: creationDate.toISOString(),
|
||||||
|
amount: 100,
|
||||||
|
memo: 'X-Com-TX memo',
|
||||||
|
communitySenderIdentifier: homeCom.communityUuid,
|
||||||
|
userSenderIdentifier: sendUser.gradidoID,
|
||||||
|
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [
|
||||||
|
new GraphQLError(
|
||||||
|
'revertSendCoins with unknown userReceiverIdentifier in the community=',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('valid X-Com-TX reverted', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
expect(
|
||||||
|
await mutate({
|
||||||
|
mutation: revertSendCoinsMutation,
|
||||||
|
variables: {
|
||||||
|
communityReceiverIdentifier: foreignCom.communityUuid,
|
||||||
|
userReceiverIdentifier: recipUser.gradidoID,
|
||||||
|
creationDate: creationDate.toISOString(),
|
||||||
|
amount: 100,
|
||||||
|
memo: 'X-Com-TX memo',
|
||||||
|
communitySenderIdentifier: homeCom.communityUuid,
|
||||||
|
userSenderIdentifier: sendUser.gradidoID,
|
||||||
|
userSenderName: fullName(sendUser.firstName, sendUser.lastName),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
revertSendCoins: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
*/
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
import { Args, Mutation, Query, Resolver } from 'type-graphql'
|
import { Args, Mutation, Resolver } from 'type-graphql'
|
||||||
import { federationLogger as logger } from '@/server/logger'
|
import { federationLogger as logger } from '@/server/logger'
|
||||||
import { Community as DbCommunity } from '@entity/Community'
|
import { Community as DbCommunity } from '@entity/Community'
|
||||||
import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction'
|
import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction'
|
||||||
@ -8,11 +8,10 @@ import { User as DbUser } from '@entity/User'
|
|||||||
import { LogError } from '@/server/LogError'
|
import { LogError } from '@/server/LogError'
|
||||||
import { PendingTransactionState } from '../enum/PendingTransactionState'
|
import { PendingTransactionState } from '../enum/PendingTransactionState'
|
||||||
import { TransactionTypeId } from '../enum/TransactionTypeId'
|
import { TransactionTypeId } from '../enum/TransactionTypeId'
|
||||||
import { calculateRecepientBalance } from '../util/calculateRecepientBalance'
|
import { calculateRecipientBalance } from '../util/calculateRecipientBalance'
|
||||||
import Decimal from 'decimal.js-light'
|
import Decimal from 'decimal.js-light'
|
||||||
import { fullName } from '@/graphql/util/fullName'
|
import { fullName } from '@/graphql/util/fullName'
|
||||||
import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction'
|
import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction'
|
||||||
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '../const/const'
|
|
||||||
import { checkTradingLevel } from '@/graphql/util/checkTradingLevel'
|
import { checkTradingLevel } from '@/graphql/util/checkTradingLevel'
|
||||||
import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction'
|
import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction'
|
||||||
|
|
||||||
@ -35,54 +34,34 @@ export class SendCoinsResolver {
|
|||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
logger.debug(`voteForSendCoins() via apiVersion=1_0 ...`)
|
logger.debug(`voteForSendCoins() via apiVersion=1_0 ...`)
|
||||||
let result: string | null = null
|
let result: string | null = null
|
||||||
|
// first check if receiver community is correct
|
||||||
|
const homeCom = await DbCommunity.findOneBy({
|
||||||
|
communityUuid: communityReceiverIdentifier,
|
||||||
|
})
|
||||||
|
if (!homeCom) {
|
||||||
|
throw new LogError(
|
||||||
|
`voteForSendCoins with wrong communityReceiverIdentifier`,
|
||||||
|
communityReceiverIdentifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// second check if receiver user exists in this community
|
||||||
|
const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier })
|
||||||
|
if (!receiverUser) {
|
||||||
|
throw new LogError(
|
||||||
|
`voteForSendCoins with unknown userReceiverIdentifier in the community=`,
|
||||||
|
homeCom.name,
|
||||||
|
)
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// first check if receiver community is correct
|
const txDate = new Date(creationDate)
|
||||||
const homeCom = await DbCommunity.findOneBy({
|
const receiveBalance = await calculateRecipientBalance(receiverUser.id, amount, txDate)
|
||||||
communityUuid: communityReceiverIdentifier,
|
|
||||||
})
|
|
||||||
if (!homeCom) {
|
|
||||||
throw new LogError(
|
|
||||||
`voteForSendCoins with wrong communityReceiverIdentifier`,
|
|
||||||
communityReceiverIdentifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// second check is configured trading level
|
|
||||||
if (!(await checkTradingLevel(homeCom, amount))) {
|
|
||||||
throw new LogError(
|
|
||||||
`X-Com: configuration of Trading-Level doesn't permit requested x-com sendCoin action!`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// third check if receiver user exists in this community
|
|
||||||
const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier })
|
|
||||||
if (!receiverUser) {
|
|
||||||
throw new LogError(
|
|
||||||
`voteForSendCoins with unknown userReceiverIdentifier in the community=`,
|
|
||||||
homeCom.name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
communitySenderIdentifier === communityReceiverIdentifier &&
|
|
||||||
userReceiverIdentifier === userSenderIdentifier
|
|
||||||
) {
|
|
||||||
throw new LogError(
|
|
||||||
`Sender and Recipient are the same: communityUUID=${communityReceiverIdentifier}, gradidoID=${userReceiverIdentifier}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (memo.length < MEMO_MIN_CHARS) {
|
|
||||||
throw new LogError('Memo text is too short', memo.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memo.length > MEMO_MAX_CHARS) {
|
|
||||||
throw new LogError('Memo text is too long', memo.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
const receiveBalance = await calculateRecepientBalance(receiverUser.id, amount, creationDate)
|
|
||||||
const pendingTx = DbPendingTransaction.create()
|
const pendingTx = DbPendingTransaction.create()
|
||||||
pendingTx.amount = amount
|
pendingTx.amount = amount
|
||||||
pendingTx.balance = receiveBalance ? receiveBalance.balance : new Decimal(0)
|
pendingTx.balance = receiveBalance ? receiveBalance.balance : new Decimal(0)
|
||||||
pendingTx.balanceDate = creationDate
|
pendingTx.balanceDate = txDate
|
||||||
pendingTx.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0)
|
pendingTx.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0)
|
||||||
pendingTx.decayStart = receiveBalance ? receiveBalance.decay.start : null
|
pendingTx.decayStart = receiveBalance ? receiveBalance.decay.start : null
|
||||||
|
pendingTx.creationDate = new Date()
|
||||||
pendingTx.linkedUserCommunityUuid = communitySenderIdentifier
|
pendingTx.linkedUserCommunityUuid = communitySenderIdentifier
|
||||||
pendingTx.linkedUserGradidoID = userSenderIdentifier
|
pendingTx.linkedUserGradidoID = userSenderIdentifier
|
||||||
pendingTx.linkedUserName = userSenderName
|
pendingTx.linkedUserName = userSenderName
|
||||||
@ -90,6 +69,7 @@ export class SendCoinsResolver {
|
|||||||
pendingTx.previous = receiveBalance ? receiveBalance.lastTransactionId : null
|
pendingTx.previous = receiveBalance ? receiveBalance.lastTransactionId : null
|
||||||
pendingTx.state = PendingTransactionState.NEW
|
pendingTx.state = PendingTransactionState.NEW
|
||||||
pendingTx.typeId = TransactionTypeId.RECEIVE
|
pendingTx.typeId = TransactionTypeId.RECEIVE
|
||||||
|
pendingTx.userId = receiverUser.id
|
||||||
pendingTx.userCommunityUuid = communityReceiverIdentifier
|
pendingTx.userCommunityUuid = communityReceiverIdentifier
|
||||||
pendingTx.userGradidoID = userReceiverIdentifier
|
pendingTx.userGradidoID = userReceiverIdentifier
|
||||||
pendingTx.userName = fullName(receiverUser.firstName, receiverUser.lastName)
|
pendingTx.userName = fullName(receiverUser.firstName, receiverUser.lastName)
|
||||||
@ -118,36 +98,36 @@ export class SendCoinsResolver {
|
|||||||
}: SendCoinsArgs,
|
}: SendCoinsArgs,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
logger.debug(`revertSendCoins() via apiVersion=1_0 ...`)
|
logger.debug(`revertSendCoins() via apiVersion=1_0 ...`)
|
||||||
|
// first check if receiver community is correct
|
||||||
|
const homeCom = await DbCommunity.findOneBy({
|
||||||
|
communityUuid: communityReceiverIdentifier,
|
||||||
|
})
|
||||||
|
if (!homeCom) {
|
||||||
|
throw new LogError(
|
||||||
|
`revertSendCoins with wrong communityReceiverIdentifier`,
|
||||||
|
communityReceiverIdentifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// second check if receiver user exists in this community
|
||||||
|
const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier })
|
||||||
|
if (!receiverUser) {
|
||||||
|
throw new LogError(
|
||||||
|
`revertSendCoins with unknown userReceiverIdentifier in the community=`,
|
||||||
|
homeCom.name,
|
||||||
|
)
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// first check if receiver community is correct
|
|
||||||
const homeCom = await DbCommunity.findOneBy({
|
|
||||||
communityUuid: communityReceiverIdentifier,
|
|
||||||
})
|
|
||||||
if (!homeCom) {
|
|
||||||
throw new LogError(
|
|
||||||
`revertSendCoins with wrong communityReceiverIdentifier`,
|
|
||||||
communityReceiverIdentifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// second check if receiver user exists in this community
|
|
||||||
const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier })
|
|
||||||
if (!receiverUser) {
|
|
||||||
throw new LogError(
|
|
||||||
`revertSendCoins with unknown userReceiverIdentifier in the community=`,
|
|
||||||
homeCom.name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const pendingTx = await DbPendingTransaction.findOneBy({
|
const pendingTx = await DbPendingTransaction.findOneBy({
|
||||||
userCommunityUuid: communityReceiverIdentifier,
|
userCommunityUuid: communityReceiverIdentifier,
|
||||||
userGradidoID: userReceiverIdentifier,
|
userGradidoID: userReceiverIdentifier,
|
||||||
state: PendingTransactionState.NEW,
|
state: PendingTransactionState.NEW,
|
||||||
typeId: TransactionTypeId.RECEIVE,
|
typeId: TransactionTypeId.RECEIVE,
|
||||||
balanceDate: creationDate,
|
balanceDate: new Date(creationDate),
|
||||||
linkedUserCommunityUuid: communitySenderIdentifier,
|
linkedUserCommunityUuid: communitySenderIdentifier,
|
||||||
linkedUserGradidoID: userSenderIdentifier,
|
linkedUserGradidoID: userSenderIdentifier,
|
||||||
})
|
})
|
||||||
logger.debug('XCom: revertSendCoins found pendingTX=', pendingTx)
|
logger.debug('XCom: revertSendCoins found pendingTX=', pendingTx)
|
||||||
if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) {
|
if (pendingTx && pendingTx.amount.toString() === amount.toString()) {
|
||||||
logger.debug('XCom: revertSendCoins matching pendingTX for remove...')
|
logger.debug('XCom: revertSendCoins matching pendingTX for remove...')
|
||||||
try {
|
try {
|
||||||
await pendingTx.remove()
|
await pendingTx.remove()
|
||||||
@ -156,7 +136,11 @@ export class SendCoinsResolver {
|
|||||||
throw new LogError('Error in revertSendCoins on removing pendingTx of receiver: ', err)
|
throw new LogError('Error in revertSendCoins on removing pendingTx of receiver: ', err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.debug('XCom: revertSendCoins NOT matching pendingTX for remove...')
|
logger.debug(
|
||||||
|
'XCom: revertSendCoins NOT matching pendingTX for remove:',
|
||||||
|
pendingTx?.amount,
|
||||||
|
amount,
|
||||||
|
)
|
||||||
throw new LogError(
|
throw new LogError(
|
||||||
`Can't find in revertSendCoins the pending receiver TX for args=`,
|
`Can't find in revertSendCoins the pending receiver TX for args=`,
|
||||||
communityReceiverIdentifier,
|
communityReceiverIdentifier,
|
||||||
@ -199,7 +183,7 @@ export class SendCoinsResolver {
|
|||||||
userGradidoID: userReceiverIdentifier,
|
userGradidoID: userReceiverIdentifier,
|
||||||
state: PendingTransactionState.NEW,
|
state: PendingTransactionState.NEW,
|
||||||
typeId: TransactionTypeId.RECEIVE,
|
typeId: TransactionTypeId.RECEIVE,
|
||||||
balanceDate: creationDate,
|
balanceDate: new Date(creationDate),
|
||||||
linkedUserCommunityUuid: communitySenderIdentifier,
|
linkedUserCommunityUuid: communitySenderIdentifier,
|
||||||
linkedUserGradidoID: userSenderIdentifier,
|
linkedUserGradidoID: userSenderIdentifier,
|
||||||
})
|
})
|
||||||
@ -277,7 +261,7 @@ export class SendCoinsResolver {
|
|||||||
userGradidoID: userReceiverIdentifier,
|
userGradidoID: userReceiverIdentifier,
|
||||||
state: PendingTransactionState.SETTLED,
|
state: PendingTransactionState.SETTLED,
|
||||||
typeId: TransactionTypeId.RECEIVE,
|
typeId: TransactionTypeId.RECEIVE,
|
||||||
balanceDate: creationDate,
|
balanceDate: new Date(creationDate),
|
||||||
linkedUserCommunityUuid: communitySenderIdentifier,
|
linkedUserCommunityUuid: communitySenderIdentifier,
|
||||||
linkedUserGradidoID: userSenderIdentifier,
|
linkedUserGradidoID: userSenderIdentifier,
|
||||||
})
|
})
|
||||||
|
|||||||
20
federation/src/graphql/util/calculateRecipientBalance.ts
Normal file
20
federation/src/graphql/util/calculateRecipientBalance.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Decimal } from 'decimal.js-light'
|
||||||
|
|
||||||
|
import { getLastTransaction } from './getLastTransaction'
|
||||||
|
import { calculateDecay } from './decay'
|
||||||
|
import { Decay } from '../api/1_0/model/Decay'
|
||||||
|
|
||||||
|
export async function calculateRecipientBalance(
|
||||||
|
userId: number,
|
||||||
|
amount: Decimal,
|
||||||
|
time: Date,
|
||||||
|
): Promise<{ balance: Decimal; decay: Decay; lastTransactionId: number } | null> {
|
||||||
|
const lastTransaction = await getLastTransaction(userId)
|
||||||
|
if (!lastTransaction) return null
|
||||||
|
|
||||||
|
const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time)
|
||||||
|
|
||||||
|
const balance = decay.balance.add(amount.toString())
|
||||||
|
|
||||||
|
return { balance, lastTransactionId: lastTransaction.id, decay }
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
|
|
||||||
|
/* <<<<<<< HEAD
|
||||||
import { createServer } from '@/server/createServer'
|
import { createServer } from '@/server/createServer'
|
||||||
import { entities } from '@entity/index'
|
import { entities } from '@entity/index'
|
||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
@ -36,6 +37,37 @@ export const cleanDB = async () => {
|
|||||||
|
|
||||||
export const testEnvironment = async (testLogger = logger) => {
|
export const testEnvironment = async (testLogger = logger) => {
|
||||||
// , testI18n = i18n) => {
|
// , testI18n = i18n) => {
|
||||||
|
=======
|
||||||
|
*/
|
||||||
|
import { entities } from '@entity/index'
|
||||||
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
|
|
||||||
|
import createServer from '@/server/createServer'
|
||||||
|
|
||||||
|
import { logger } from './testSetup'
|
||||||
|
|
||||||
|
export const headerPushMock = jest.fn((t) => {
|
||||||
|
context.token = t.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
token: '',
|
||||||
|
setHeaders: {
|
||||||
|
push: headerPushMock,
|
||||||
|
forEach: jest.fn(),
|
||||||
|
},
|
||||||
|
clientTimezoneOffset: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cleanDB = async () => {
|
||||||
|
// this only works as long we do not have foreign key constraints
|
||||||
|
for (const entity of entities) {
|
||||||
|
await resetEntity(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const testEnvironment = async (testLogger = logger) => {
|
||||||
|
// >>>>>>> refs/remotes/origin/master
|
||||||
const server = await createServer(testLogger) // context, testLogger, testI18n)
|
const server = await createServer(testLogger) // context, testLogger, testI18n)
|
||||||
const con = server.con
|
const con = server.con
|
||||||
const testClient = createTestClient(server.apollo)
|
const testClient = createTestClient(server.apollo)
|
||||||
|
|||||||
@ -56,3 +56,23 @@ export default {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.community-switch > div,
|
||||||
|
.community-switch ul.dropdown-menu {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.community-switch > div > button {
|
||||||
|
border-radius: 17px;
|
||||||
|
height: 50px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.community-switch .dropdown-toggle::after {
|
||||||
|
float: right;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.community-switch ul li:first-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
<div class="mt-3 font-weight-bold">{{ $t('contributionText') }}</div>
|
<div class="mt-3 font-weight-bold">{{ $t('contributionText') }}</div>
|
||||||
<div class="mb-3 text-break word-break">{{ memo }}</div>
|
<div class="mb-3 text-break word-break">{{ memo }}</div>
|
||||||
<div
|
<div
|
||||||
v-if="status === 'IN_PROGRESS'"
|
v-if="status === 'IN_PROGRESS' && !allContribution"
|
||||||
class="text-205 pointer hover-font-bold"
|
class="text-205 pointer hover-font-bold"
|
||||||
@click="visible = !visible"
|
@click="visible = !visible"
|
||||||
>
|
>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user