Merge branch 'master' into admin_extend_user_search

This commit is contained in:
einhornimmond 2023-08-16 14:22:04 +02:00 committed by GitHub
commit 637034eba1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1466 additions and 7 deletions

View File

@ -21,6 +21,10 @@ KLICKTIPP_PASSWORD=secret321
KLICKTIPP_APIKEY_DE=SomeFakeKeyDE
KLICKTIPP_APIKEY_EN=SomeFakeKeyEN
# DltConnector
DLT_CONNECTOR=true
DLT_CONNECTOR_URL=http://localhost:6010
# Community
COMMUNITY_NAME=Gradido Entwicklung
COMMUNITY_URL=http://localhost/

View File

@ -22,6 +22,10 @@ KLICKTIPP_PASSWORD=$KLICKTIPP_PASSWORD
KLICKTIPP_APIKEY_DE=$KLICKTIPP_APIKEY_DE
KLICKTIPP_APIKEY_EN=$KLICKTIPP_APIKEY_EN
# DltConnector
DLT_CONNECTOR=$DLT_CONNECTOR
DLT_CONNECTOR_URL=$DLT_CONNECTOR_URL
# Community
COMMUNITY_NAME=$COMMUNITY_NAME
COMMUNITY_URL=$COMMUNITY_URL

View File

@ -0,0 +1,174 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable security/detect-object-injection */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { Connection } from '@dbTools/typeorm'
import { Transaction as DbTransaction } from '@entity/Transaction'
import { Decimal } from 'decimal.js-light'
import { cleanDB, testEnvironment } from '@test/helpers'
import { CONFIG } from '@/config'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { DltConnectorClient } from './DltConnectorClient'
let con: Connection
let testEnv: {
con: Connection
}
// Mock the GraphQLClient
jest.mock('graphql-request', () => {
const originalModule = jest.requireActual('graphql-request')
let testCursor = 0
return {
__esModule: true,
...originalModule,
GraphQLClient: jest.fn().mockImplementation((url: string) => {
if (url === 'invalid') {
throw new Error('invalid url')
}
return {
// why not using mockResolvedValueOnce or mockReturnValueOnce?
// I have tried, but it didn't work and return every time the first value
request: jest.fn().mockImplementation(() => {
testCursor++
if (testCursor === 4) {
return Promise.resolve(
// invalid, is 33 Bytes long as binary
{
transmitTransaction: {
dltTransactionIdHex:
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516212A',
},
},
)
} else if (testCursor === 5) {
throw Error('Connection error')
} else {
return Promise.resolve(
// valid, is 32 Bytes long as binary
{
transmitTransaction: {
dltTransactionIdHex:
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
},
},
)
}
}),
}
}),
}
})
describe('undefined DltConnectorClient', () => {
it('invalid url', () => {
CONFIG.DLT_CONNECTOR_URL = 'invalid'
CONFIG.DLT_CONNECTOR = true
const result = DltConnectorClient.getInstance()
expect(result).toBeUndefined()
CONFIG.DLT_CONNECTOR_URL = 'http://dlt-connector:6010'
})
it('DLT_CONNECTOR is false', () => {
CONFIG.DLT_CONNECTOR = false
const result = DltConnectorClient.getInstance()
expect(result).toBeUndefined()
CONFIG.DLT_CONNECTOR = true
})
})
/*
describe.skip('transmitTransaction, without db connection', () => {
const transaction = new DbTransaction()
transaction.typeId = 2 // Example transaction type ID
transaction.amount = new Decimal('10.00') // Example amount
transaction.balanceDate = new Date() // Example creation date
transaction.id = 1 // Example transaction ID
it('cannot query for transaction id', async () => {
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
expect(result).toBe(false)
})
})
*/
describe('transmitTransaction', () => {
beforeAll(async () => {
testEnv = await testEnvironment(logger)
con = testEnv.con
await cleanDB()
})
afterAll(async () => {
await cleanDB()
await con.close()
})
const transaction = new DbTransaction()
transaction.typeId = 2 // Example transaction type ID
transaction.amount = new Decimal('10.00') // Example amount
transaction.balanceDate = new Date() // Example creation date
transaction.id = 1 // Example transaction ID
// data needed to let save succeed
transaction.memo = "I'm a dummy memo"
transaction.userId = 1
transaction.userGradidoID = 'dummy gradido id'
/*
it.skip('cannot find transaction in db', async () => {
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
expect(result).toBe(false)
})
*/
it('invalid transaction type', async () => {
const localTransaction = new DbTransaction()
localTransaction.typeId = 12
try {
await DltConnectorClient.getInstance()?.transmitTransaction(localTransaction)
} catch (e) {
expect(e).toMatchObject(
new LogError('invalid transaction type id: ' + localTransaction.typeId.toString()),
)
}
})
/*
it.skip('should transmit the transaction and update the dltTransactionId in the database', async () => {
await transaction.save()
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
expect(result).toBe(true)
})
it.skip('invalid dltTransactionId (maximal 32 Bytes in Binary)', async () => {
await transaction.save()
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
expect(result).toBe(false)
})
*/
})
/*
describe.skip('try transmitTransaction but graphql request failed', () => {
it('graphql request should throw', async () => {
const transaction = new DbTransaction()
transaction.typeId = 2 // Example transaction type ID
transaction.amount = new Decimal('10.00') // Example amount
transaction.balanceDate = new Date() // Example creation date
transaction.id = 1 // Example transaction ID
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
expect(result).toBe(false)
})
})
*/

View File

@ -0,0 +1,104 @@
import { Transaction as DbTransaction } from '@entity/Transaction'
import { gql, GraphQLClient } from 'graphql-request'
import { CONFIG } from '@/config'
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
const sendTransaction = gql`
mutation ($input: TransactionInput!) {
sendTransaction(data: $input) {
dltTransactionIdHex
}
}
`
// from ChatGPT
function getTransactionTypeString(id: TransactionTypeId): string {
const key = Object.keys(TransactionTypeId).find(
(key) => TransactionTypeId[key as keyof typeof TransactionTypeId] === id,
)
if (key === undefined) {
throw new LogError('invalid transaction type id: ' + id.toString())
}
return key
}
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
// and ../federation/client/FederationClientFactory.ts
/**
* A Singleton class defines the `getInstance` method that lets clients access
* the unique singleton instance.
*/
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class DltConnectorClient {
// eslint-disable-next-line no-use-before-define
private static instance: DltConnectorClient
client: GraphQLClient
/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/
// eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function
private constructor() {}
/**
* The static method that controls the access to the singleton instance.
*
* This implementation let you subclass the Singleton class while keeping
* just one instance of each subclass around.
*/
public static getInstance(): DltConnectorClient | undefined {
if (!CONFIG.DLT_CONNECTOR || !CONFIG.DLT_CONNECTOR_URL) {
logger.info(`dlt-connector are disabled via config...`)
return
}
if (!DltConnectorClient.instance) {
DltConnectorClient.instance = new DltConnectorClient()
}
if (!DltConnectorClient.instance.client) {
try {
DltConnectorClient.instance.client = new GraphQLClient(CONFIG.DLT_CONNECTOR_URL, {
method: 'GET',
jsonSerializer: {
parse: JSON.parse,
stringify: JSON.stringify,
},
})
} catch (e) {
logger.error("couldn't connect to dlt-connector: ", e)
return
}
}
return DltConnectorClient.instance
}
/**
* transmit transaction via dlt-connector to iota
* and update dltTransactionId of transaction in db with iota message id
*/
public async transmitTransaction(transaction?: DbTransaction | null): Promise<string> {
if (transaction) {
const typeString = getTransactionTypeString(transaction.typeId)
const secondsSinceEpoch = Math.round(transaction.balanceDate.getTime() / 1000)
const amountString = transaction.amount.toString()
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { data } = await this.client.rawRequest(sendTransaction, {
input: {
type: typeString,
amount: amountString,
createdAt: secondsSinceEpoch,
},
})
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
return data.sendTransaction.dltTransactionIdHex
} catch (e) {
throw new LogError('Error send sending transaction to dlt-connector: ', e)
}
} else {
throw new LogError('parameter transaction not set...')
}
}
}

View File

@ -12,14 +12,14 @@ Decimal.set({
})
const constants = {
DB_VERSION: '0069-add_user_roles_table',
DB_VERSION: '0070-add_dlt_transactions_table',
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
LOG4JS_CONFIG: 'log4js-config.json',
// default log level on production should be info
LOG_LEVEL: process.env.LOG_LEVEL ?? 'info',
CONFIG_VERSION: {
DEFAULT: 'DEFAULT',
EXPECTED: 'v17.2023-07-03',
EXPECTED: 'v18.2023-07-10',
CURRENT: '',
},
}
@ -51,6 +51,11 @@ const klicktipp = {
KLICKTIPP_APIKEY_EN: process.env.KLICKTIPP_APIKEY_EN ?? 'SomeFakeKeyEN',
}
const dltConnector = {
DLT_CONNECTOR: process.env.DLT_CONNECTOR === 'true' || false,
DLT_CONNECTOR_URL: process.env.DLT_CONNECTOR_URL ?? 'http://localhost:6010',
}
const community = {
COMMUNITY_NAME: process.env.COMMUNITY_NAME ?? 'Gradido Entwicklung',
COMMUNITY_URL: process.env.COMMUNITY_URL ?? 'http://localhost/',
@ -126,6 +131,7 @@ export const CONFIG = {
...server,
...database,
...klicktipp,
...dltConnector,
...community,
...email,
...loginServer,

View File

@ -55,6 +55,7 @@ import {
} from './util/creations'
import { findContributions } from './util/findContributions'
import { getLastTransaction } from './util/getLastTransaction'
import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector'
@Resolver()
export class ContributionResolver {
@ -514,6 +515,10 @@ export class ContributionResolver {
await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution)
await queryRunner.commitTransaction()
// trigger to send transaction via dlt-connector
void sendTransactionsToDltConnector()
logger.info('creation commited successfuly.')
void sendContributionConfirmedEmail({
firstName: user.firstName,

View File

@ -41,6 +41,7 @@ import { calculateBalance } from '@/util/validate'
import { executeTransaction } from './TransactionResolver'
import { getUserCreation, validateContribution } from './util/creations'
import { getLastTransaction } from './util/getLastTransaction'
import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector'
import { transactionLinkList } from './util/transactionLinkList'
// TODO: do not export, test it inside the resolver
@ -290,6 +291,7 @@ export class TransactionLinkResolver {
await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution)
await queryRunner.commitTransaction()
await EVENT_CONTRIBUTION_LINK_REDEEM(
user,
transaction,
@ -306,6 +308,8 @@ export class TransactionLinkResolver {
} finally {
releaseLock()
}
// trigger to send transaction via dlt-connector
void sendTransactionsToDltConnector()
return true
} else {
const now = new Date()

View File

@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { Connection } from '@dbTools/typeorm'
import { Connection, In } from '@dbTools/typeorm'
import { DltTransaction } from '@entity/DltTransaction'
import { Event as DbEvent } from '@entity/Event'
import { Transaction } from '@entity/Transaction'
import { User } from '@entity/User'
@ -371,7 +372,6 @@ describe('send coins', () => {
memo: 'unrepeatable memo',
},
})
await expect(DbEvent.find()).resolves.toContainEqual(
expect.objectContaining({
type: EventType.TRANSACTION_RECEIVE,
@ -382,6 +382,52 @@ describe('send coins', () => {
}),
)
})
describe('sendTransactionsToDltConnector', () => {
let transaction: Transaction[]
let dltTransactions: DltTransaction[]
beforeAll(async () => {
// Find the previous created transactions of sendCoin mutation
transaction = await Transaction.find({
where: { memo: 'unrepeatable memo' },
order: { balanceDate: 'ASC', id: 'ASC' },
})
// and read aslong as all async created dlt-transactions are finished
do {
dltTransactions = await DltTransaction.find({
where: { transactionId: In([transaction[0].id, transaction[1].id]) },
// relations: ['transaction'],
// order: { createdAt: 'ASC', id: 'ASC' },
})
} while (transaction.length > dltTransactions.length)
})
it('has wait till sendTransactionsToDltConnector created all dlt-transactions', () => {
expect(logger.info).toBeCalledWith('sendTransactionsToDltConnector...')
expect(dltTransactions).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
transactionId: transaction[0].id,
messageId: null,
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: transaction[1].id,
messageId: null,
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
]),
)
})
})
})
describe('send coins via gradido ID', () => {

View File

@ -37,6 +37,7 @@ import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
import { findUserByIdentifier } from './util/findUserByIdentifier'
import { getLastTransaction } from './util/getLastTransaction'
import { getTransactionList } from './util/getTransactionList'
import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector'
import { transactionLinkSummary } from './util/transactionLinkSummary'
export const executeTransaction = async (
@ -150,6 +151,9 @@ export const executeTransaction = async (
transactionReceive,
transactionReceive.amount,
)
// trigger to send transaction via dlt-connector
void sendTransactionsToDltConnector()
} catch (e) {
await queryRunner.rollbackTransaction()
throw new LogError('Transaction was not successful', e)

View File

@ -0,0 +1,778 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Connection } from '@dbTools/typeorm'
import { DltTransaction } from '@entity/DltTransaction'
import { Transaction } from '@entity/Transaction'
import { ApolloServerTestClient } from 'apollo-server-testing'
import { Decimal } from 'decimal.js-light'
// import { GraphQLClient } from 'graphql-request'
// import { Response } from 'graphql-request/dist/types'
import { GraphQLClient } from 'graphql-request'
import { Response } from 'graphql-request/dist/types'
import { testEnvironment, cleanDB } from '@test/helpers'
import { logger, i18n as localization } from '@test/testSetup'
import { CONFIG } from '@/config'
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
import { sendTransactionsToDltConnector } from './sendTransactionsToDltConnector'
/*
// Mock the GraphQLClient
jest.mock('graphql-request', () => {
const originalModule = jest.requireActual('graphql-request')
let testCursor = 0
return {
__esModule: true,
...originalModule,
GraphQLClient: jest.fn().mockImplementation((url: string) => {
if (url === 'invalid') {
throw new Error('invalid url')
}
return {
// why not using mockResolvedValueOnce or mockReturnValueOnce?
// I have tried, but it didn't work and return every time the first value
request: jest.fn().mockImplementation(() => {
testCursor++
if (testCursor === 4) {
return Promise.resolve(
// invalid, is 33 Bytes long as binary
{
transmitTransaction: {
dltTransactionIdHex:
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516212A',
},
},
)
} else if (testCursor === 5) {
throw Error('Connection error')
} else {
return Promise.resolve(
// valid, is 32 Bytes long as binary
{
transmitTransaction: {
dltTransactionIdHex:
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
},
},
)
}
}),
}
}),
}
})
let mutate: ApolloServerTestClient['mutate'],
query: ApolloServerTestClient['query'],
con: Connection
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
}
*/
async function createTxCREATION1(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(1000)
tx.balance = new Decimal(100)
tx.balanceDate = new Date('01.01.2023 00:00:00')
tx.memo = 'txCREATION1'
tx.typeId = TransactionTypeId.CREATION
tx.userGradidoID = 'txCREATION1.userGradidoID'
tx.userId = 1
tx.userName = 'txCREATION 1'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('01.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c1'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('01.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxCREATION2(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(1000)
tx.balance = new Decimal(200)
tx.balanceDate = new Date('02.01.2023 00:00:00')
tx.memo = 'txCREATION2'
tx.typeId = TransactionTypeId.CREATION
tx.userGradidoID = 'txCREATION2.userGradidoID'
tx.userId = 2
tx.userName = 'txCREATION 2'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('02.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c2'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('02.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxCREATION3(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(1000)
tx.balance = new Decimal(300)
tx.balanceDate = new Date('03.01.2023 00:00:00')
tx.memo = 'txCREATION3'
tx.typeId = TransactionTypeId.CREATION
tx.userGradidoID = 'txCREATION3.userGradidoID'
tx.userId = 3
tx.userName = 'txCREATION 3'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('03.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c3'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('03.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxSend1ToReceive2(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(100)
tx.balance = new Decimal(1000)
tx.balanceDate = new Date('11.01.2023 00:00:00')
tx.memo = 'txSEND1 to txRECEIVE2'
tx.typeId = TransactionTypeId.SEND
tx.userGradidoID = 'txSEND1.userGradidoID'
tx.userId = 1
tx.userName = 'txSEND 1'
tx.linkedUserGradidoID = 'txRECEIVE2.linkedUserGradidoID'
tx.linkedUserId = 2
tx.linkedUserName = 'txRECEIVE 2'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('11.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516a1'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('11.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxReceive2FromSend1(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(100)
tx.balance = new Decimal(1300)
tx.balanceDate = new Date('11.01.2023 00:00:00')
tx.memo = 'txSEND1 to txRECEIVE2'
tx.typeId = TransactionTypeId.RECEIVE
tx.userGradidoID = 'txRECEIVE2.linkedUserGradidoID'
tx.userId = 2
tx.userName = 'txRECEIVE 2'
tx.linkedUserGradidoID = 'txSEND1.userGradidoID'
tx.linkedUserId = 1
tx.linkedUserName = 'txSEND 1'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('11.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516b2'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('11.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
/*
async function createTxSend2ToReceive3(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(200)
tx.balance = new Decimal(1100)
tx.balanceDate = new Date('23.01.2023 00:00:00')
tx.memo = 'txSEND2 to txRECEIVE3'
tx.typeId = TransactionTypeId.SEND
tx.userGradidoID = 'txSEND2.userGradidoID'
tx.userId = 2
tx.userName = 'txSEND 2'
tx.linkedUserGradidoID = 'txRECEIVE3.linkedUserGradidoID'
tx.linkedUserId = 3
tx.linkedUserName = 'txRECEIVE 3'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('23.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516a2'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('23.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxReceive3FromSend2(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(200)
tx.balance = new Decimal(1500)
tx.balanceDate = new Date('23.01.2023 00:00:00')
tx.memo = 'txSEND2 to txRECEIVE3'
tx.typeId = TransactionTypeId.RECEIVE
tx.userGradidoID = 'txRECEIVE3.linkedUserGradidoID'
tx.userId = 3
tx.userName = 'txRECEIVE 3'
tx.linkedUserGradidoID = 'txSEND2.userGradidoID'
tx.linkedUserId = 2
tx.linkedUserName = 'txSEND 2'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('23.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516b3'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('23.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxSend3ToReceive1(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(300)
tx.balance = new Decimal(1200)
tx.balanceDate = new Date('31.01.2023 00:00:00')
tx.memo = 'txSEND3 to txRECEIVE1'
tx.typeId = TransactionTypeId.SEND
tx.userGradidoID = 'txSEND3.userGradidoID'
tx.userId = 3
tx.userName = 'txSEND 3'
tx.linkedUserGradidoID = 'txRECEIVE1.linkedUserGradidoID'
tx.linkedUserId = 1
tx.linkedUserName = 'txRECEIVE 1'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('31.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516a3'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('31.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxReceive1FromSend3(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(300)
tx.balance = new Decimal(1300)
tx.balanceDate = new Date('31.01.2023 00:00:00')
tx.memo = 'txSEND3 to txRECEIVE1'
tx.typeId = TransactionTypeId.RECEIVE
tx.userGradidoID = 'txRECEIVE1.linkedUserGradidoID'
tx.userId = 1
tx.userName = 'txRECEIVE 1'
tx.linkedUserGradidoID = 'txSEND3.userGradidoID'
tx.linkedUserId = 3
tx.linkedUserName = 'txSEND 3'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('31.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516b1'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('31.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
*/
let con: Connection
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
}
beforeAll(async () => {
testEnv = await testEnvironment(logger, localization)
con = testEnv.con
await cleanDB()
})
afterAll(async () => {
await cleanDB()
await con.close()
})
describe('create and send Transactions to DltConnector', () => {
let txCREATION1: Transaction
let txCREATION2: Transaction
let txCREATION3: Transaction
let txSEND1to2: Transaction
let txRECEIVE2From1: Transaction
// let txSEND2To3: Transaction
// let txRECEIVE3From2: Transaction
// let txSEND3To1: Transaction
// let txRECEIVE1From3: Transaction
beforeEach(() => {
jest.clearAllMocks()
})
afterEach(async () => {
await cleanDB()
})
describe('with 3 creations but inactive dlt-connector', () => {
it('found 3 dlt-transactions', async () => {
txCREATION1 = await createTxCREATION1(false)
txCREATION2 = await createTxCREATION2(false)
txCREATION3 = await createTxCREATION3(false)
CONFIG.DLT_CONNECTOR = false
await sendTransactionsToDltConnector()
expect(logger.info).toBeCalledWith('sendTransactionsToDltConnector...')
// Find the previous created transactions of sendCoin mutation
const transactions = await Transaction.find({
// where: { memo: 'unrepeatable memo' },
order: { balanceDate: 'ASC', id: 'ASC' },
})
const dltTransactions = await DltTransaction.find({
// where: { transactionId: In([transaction[0].id, transaction[1].id]) },
// relations: ['transaction'],
order: { createdAt: 'ASC', id: 'ASC' },
})
expect(dltTransactions).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[0].id,
messageId: null,
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[1].id,
messageId: null,
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[2].id,
messageId: null,
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
]),
)
expect(logger.info).nthCalledWith(3, 'sending to DltConnector currently not configured...')
})
})
describe('with 3 creations and active dlt-connector', () => {
it('found 3 dlt-transactions', async () => {
txCREATION1 = await createTxCREATION1(false)
txCREATION2 = await createTxCREATION2(false)
txCREATION3 = await createTxCREATION3(false)
CONFIG.DLT_CONNECTOR = true
// eslint-disable-next-line @typescript-eslint/require-await
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return {
data: {
sendTransaction: {
dltTransactionIdHex:
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
},
},
} as Response<unknown>
})
await sendTransactionsToDltConnector()
expect(logger.info).toBeCalledWith('sendTransactionsToDltConnector...')
// Find the previous created transactions of sendCoin mutation
const transactions = await Transaction.find({
// where: { memo: 'unrepeatable memo' },
order: { balanceDate: 'ASC', id: 'ASC' },
})
const dltTransactions = await DltTransaction.find({
// where: { transactionId: In([transaction[0].id, transaction[1].id]) },
// relations: ['transaction'],
order: { createdAt: 'ASC', id: 'ASC' },
})
expect(dltTransactions).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[0].id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[1].id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[2].id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
]),
)
})
})
describe('with 3 verified creations, 1 sendCoins and active dlt-connector', () => {
it('found 3 dlt-transactions', async () => {
txCREATION1 = await createTxCREATION1(true)
txCREATION2 = await createTxCREATION2(true)
txCREATION3 = await createTxCREATION3(true)
txSEND1to2 = await createTxSend1ToReceive2(false)
txRECEIVE2From1 = await createTxReceive2FromSend1(false)
/*
txSEND2To3 = await createTxSend2ToReceive3()
txRECEIVE3From2 = await createTxReceive3FromSend2()
txSEND3To1 = await createTxSend3ToReceive1()
txRECEIVE1From3 = await createTxReceive1FromSend3()
*/
CONFIG.DLT_CONNECTOR = true
// eslint-disable-next-line @typescript-eslint/require-await
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return {
data: {
sendTransaction: {
dltTransactionIdHex:
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
},
},
} as Response<unknown>
})
await sendTransactionsToDltConnector()
expect(logger.info).toBeCalledWith('sendTransactionsToDltConnector...')
// Find the previous created transactions of sendCoin mutation
/*
const transactions = await Transaction.find({
// where: { memo: 'unrepeatable memo' },
order: { balanceDate: 'ASC', id: 'ASC' },
})
*/
const dltTransactions = await DltTransaction.find({
// where: { transactionId: In([transaction[0].id, transaction[1].id]) },
// relations: ['transaction'],
order: { createdAt: 'ASC', id: 'ASC' },
})
expect(dltTransactions).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
transactionId: txCREATION1.id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c1',
verified: true,
createdAt: new Date('01.01.2023 00:00:10'),
verifiedAt: new Date('01.01.2023 00:01:10'),
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: txCREATION2.id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c2',
verified: true,
createdAt: new Date('02.01.2023 00:00:10'),
verifiedAt: new Date('02.01.2023 00:01:10'),
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: txCREATION3.id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c3',
verified: true,
createdAt: new Date('03.01.2023 00:00:10'),
verifiedAt: new Date('03.01.2023 00:01:10'),
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: txSEND1to2.id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: txRECEIVE2From1.id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
]),
)
})
/*
describe('with one Community of api 1_0 and not matching pubKey', () => {
beforeEach(async () => {
// eslint-disable-next-line @typescript-eslint/require-await
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return {
data: {
getPublicKey: {
publicKey: 'somePubKey',
},
},
} as Response<unknown>
})
const variables1 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
apiVersion: '1_0',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
}
await DbFederatedCommunity.createQueryBuilder()
.insert()
.into(DbFederatedCommunity)
.values(variables1)
.orUpdate({
// eslint-disable-next-line camelcase
conflict_target: ['id', 'publicKey', 'apiVersion'],
overwrite: ['end_point', 'last_announced_at'],
})
.execute()
jest.clearAllMocks()
// await validateCommunities()
})
it('logs one community found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs not matching publicKeys', () => {
expect(logger.warn).toBeCalledWith(
'Federation: received not matching publicKey:',
'somePubKey',
expect.stringMatching('11111111111111111111111111111111'),
)
})
})
describe('with one Community of api 1_0 and matching pubKey', () => {
beforeEach(async () => {
// eslint-disable-next-line @typescript-eslint/require-await
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return {
data: {
getPublicKey: {
publicKey: '11111111111111111111111111111111',
},
},
} as Response<unknown>
})
const variables1 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
apiVersion: '1_0',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
}
await DbFederatedCommunity.createQueryBuilder()
.insert()
.into(DbFederatedCommunity)
.values(variables1)
.orUpdate({
// eslint-disable-next-line camelcase
conflict_target: ['id', 'publicKey', 'apiVersion'],
overwrite: ['end_point', 'last_announced_at'],
})
.execute()
await DbFederatedCommunity.update({}, { verifiedAt: null })
jest.clearAllMocks()
// await validateCommunities()
})
it('logs one community found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs community pubKey verified', () => {
expect(logger.info).toHaveBeenNthCalledWith(
3,
'Federation: verified community with',
'http//localhost:5001/api/',
)
})
})
describe('with two Communities of api 1_0 and 1_1', () => {
beforeEach(async () => {
jest.clearAllMocks()
// eslint-disable-next-line @typescript-eslint/require-await
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return {
data: {
getPublicKey: {
publicKey: '11111111111111111111111111111111',
},
},
} as Response<unknown>
})
const variables2 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
apiVersion: '1_1',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
}
await DbFederatedCommunity.createQueryBuilder()
.insert()
.into(DbFederatedCommunity)
.values(variables2)
.orUpdate({
// eslint-disable-next-line camelcase
conflict_target: ['id', 'publicKey', 'apiVersion'],
overwrite: ['end_point', 'last_announced_at'],
})
.execute()
await DbFederatedCommunity.update({}, { verifiedAt: null })
jest.clearAllMocks()
// await validateCommunities()
})
it('logs two communities found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 2 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs requestGetPublicKey for community api 1_1 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_1/',
)
})
})
describe('with three Communities of api 1_0, 1_1 and 2_0', () => {
let dbCom: DbFederatedCommunity
beforeEach(async () => {
const variables3 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
apiVersion: '2_0',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
}
await DbFederatedCommunity.createQueryBuilder()
.insert()
.into(DbFederatedCommunity)
.values(variables3)
.orUpdate({
// eslint-disable-next-line camelcase
conflict_target: ['id', 'publicKey', 'apiVersion'],
overwrite: ['end_point', 'last_announced_at'],
})
.execute()
dbCom = await DbFederatedCommunity.findOneOrFail({
where: { publicKey: variables3.publicKey, apiVersion: variables3.apiVersion },
})
await DbFederatedCommunity.update({}, { verifiedAt: null })
jest.clearAllMocks()
// await validateCommunities()
})
it('logs three community found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 3 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs requestGetPublicKey for community api 1_1 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_1/',
)
})
it('logs unsupported api for community with api 2_0 ', () => {
expect(logger.warn).toBeCalledWith(
'Federation: dbCom with unsupported apiVersion',
dbCom.endPoint,
'2_0',
)
})
})
*/
})
})

View File

@ -0,0 +1,80 @@
import { IsNull } from '@dbTools/typeorm'
import { DltTransaction } from '@entity/DltTransaction'
import { Transaction } from '@entity/Transaction'
import { DltConnectorClient } from '@/apis/DltConnectorClient'
import { backendLogger as logger } from '@/server/logger'
import { Monitor, MonitorNames } from '@/util/Monitor'
export async function sendTransactionsToDltConnector(): Promise<void> {
logger.info('sendTransactionsToDltConnector...')
// check if this logic is still occupied, no concurrecy allowed
if (!Monitor.isLocked(MonitorNames.SEND_DLT_TRANSACTIONS)) {
// mark this block for occuption to prevent concurrency
Monitor.lockIt(MonitorNames.SEND_DLT_TRANSACTIONS)
try {
await createDltTransactions()
const dltConnector = DltConnectorClient.getInstance()
if (dltConnector) {
logger.debug('with sending to DltConnector...')
const dltTransactions = await DltTransaction.find({
where: { messageId: IsNull() },
relations: ['transaction'],
order: { createdAt: 'ASC', id: 'ASC' },
})
for (const dltTx of dltTransactions) {
try {
const messageId = await dltConnector.transmitTransaction(dltTx.transaction)
const dltMessageId = Buffer.from(messageId, 'hex')
if (dltMessageId.length !== 32) {
logger.error(
'Error dlt message id is invalid: %s, should by 32 Bytes long in binary after converting from hex',
dltMessageId,
)
return
}
dltTx.messageId = dltMessageId.toString('hex')
await DltTransaction.save(dltTx)
logger.info('store messageId=%s in dltTx=%d', dltTx.messageId, dltTx.id)
} catch (e) {
logger.error(
`error while sending to dlt-connector or writing messageId of dltTx=${dltTx.id}`,
e,
)
}
}
} else {
logger.info('sending to DltConnector currently not configured...')
}
} catch (e) {
logger.error('error on sending transactions to dlt-connector.', e)
} finally {
// releae Monitor occupation
Monitor.releaseIt(MonitorNames.SEND_DLT_TRANSACTIONS)
}
} else {
logger.info('sendTransactionsToDltConnector currently locked by monitor...')
}
}
async function createDltTransactions(): Promise<void> {
const dltqb = DltTransaction.createQueryBuilder().select('transactions_id')
const newTransactions: Transaction[] = await Transaction.createQueryBuilder()
.select('id')
.addSelect('balance_date')
.where('id NOT IN (' + dltqb.getSql() + ')')
// eslint-disable-next-line camelcase
.orderBy({ balance_date: 'ASC', id: 'ASC' })
.getRawMany()
const dltTxArray: DltTransaction[] = []
let idx = 0
while (newTransactions.length > dltTxArray.length) {
// timing problems with for(let idx = 0; idx < newTransactions.length; idx++) {
const dltTx = DltTransaction.create()
dltTx.transactionId = newTransactions[idx++].id
await DltTransaction.save(dltTx)
dltTxArray.push(dltTx)
}
}

View File

@ -0,0 +1,50 @@
import { registerEnumType } from 'type-graphql'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
export enum MonitorNames {
SEND_DLT_TRANSACTIONS = 1,
}
registerEnumType(MonitorNames, {
name: 'MonitorNames', // this one is mandatory
description: 'Name of Monitor-keys', // this one is optional
})
/* @typescript-eslint/no-extraneous-class */
export class Monitor {
private static locks = new Map<MonitorNames, boolean>()
// eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function
private constructor() {}
private _dummy = `to avoid unexpected class with only static properties`
public get dummy() {
return this._dummy
}
public static isLocked(key: MonitorNames): boolean | undefined {
if (this.locks.has(key)) {
logger.debug(`Monitor isLocked key=${key} = `, this.locks.get(key))
return this.locks.get(key)
}
logger.debug(`Monitor isLocked key=${key} not exists`)
return false
}
public static lockIt(key: MonitorNames): void {
logger.debug(`Monitor lockIt key=`, key)
if (this.locks.has(key)) {
throw new LogError('still existing Monitor with key=', key)
}
this.locks.set(key, true)
}
public static releaseIt(key: MonitorNames): void {
logger.debug(`Monitor releaseIt key=`, key)
if (this.locks.has(key)) {
this.locks.delete(key)
}
}
}

View File

@ -0,0 +1,33 @@
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm'
import { Transaction } from '../Transaction'
@Entity('dlt_transactions', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
export class DltTransaction extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ name: 'transactions_id', type: 'int', unsigned: true, nullable: false })
transactionId: number
@Column({
name: 'message_id',
length: 64,
nullable: true,
default: null,
collation: 'utf8mb4_unicode_ci',
})
messageId: string
@Column({ name: 'verified', type: 'bool', nullable: false, default: false })
verified: boolean
@Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP(3)', nullable: false })
createdAt: Date
@Column({ name: 'verified_at', nullable: true, default: null, type: 'datetime' })
verifiedAt: Date | null
@OneToOne(() => Transaction, (transaction) => transaction.dltTransaction)
@JoinColumn({ name: 'transactions_id' })
transaction?: Transaction | null
}

View File

@ -0,0 +1,145 @@
/* eslint-disable no-use-before-define */
import { Decimal } from 'decimal.js-light'
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm'
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
import { Contribution } from '../Contribution'
import { DltTransaction } from './DltTransaction'
@Entity('transactions')
export class Transaction extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ type: 'int', unsigned: true, unique: true, nullable: true, default: null })
previous: number | null
@Column({ name: 'type_id', unsigned: true, nullable: false })
typeId: number
@Column({
name: 'transaction_link_id',
type: 'int',
unsigned: true,
nullable: true,
default: null,
})
transactionLinkId?: number | null
@Column({
type: 'decimal',
precision: 40,
scale: 20,
nullable: false,
transformer: DecimalTransformer,
})
amount: Decimal
@Column({
type: 'decimal',
precision: 40,
scale: 20,
nullable: false,
transformer: DecimalTransformer,
})
balance: Decimal
@Column({
name: 'balance_date',
type: 'datetime',
default: () => 'CURRENT_TIMESTAMP',
nullable: false,
})
balanceDate: Date
@Column({
type: 'decimal',
precision: 40,
scale: 20,
nullable: false,
transformer: DecimalTransformer,
})
decay: Decimal
@Column({
name: 'decay_start',
type: 'datetime',
nullable: true,
default: null,
})
decayStart: Date | null
@Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
memo: string
@Column({ name: 'creation_date', type: 'datetime', nullable: true, default: null })
creationDate: Date | null
@Column({ name: 'user_id', unsigned: true, nullable: false })
userId: number
@Column({
name: 'user_gradido_id',
type: 'varchar',
length: 36,
nullable: false,
collation: 'utf8mb4_unicode_ci',
})
userGradidoID: string
@Column({
name: 'user_name',
type: 'varchar',
length: 512,
nullable: true,
collation: 'utf8mb4_unicode_ci',
})
userName: string | null
@Column({
name: 'linked_user_id',
type: 'int',
unsigned: true,
nullable: true,
default: null,
})
linkedUserId?: number | null
@Column({
name: 'linked_user_gradido_id',
type: 'varchar',
length: 36,
nullable: true,
collation: 'utf8mb4_unicode_ci',
})
linkedUserGradidoID: string | null
@Column({
name: 'linked_user_name',
type: 'varchar',
length: 512,
nullable: true,
collation: 'utf8mb4_unicode_ci',
})
linkedUserName: string | null
@Column({
name: 'linked_transaction_id',
type: 'int',
unsigned: true,
nullable: true,
default: null,
})
linkedTransactionId?: number | null
@OneToOne(() => Contribution, (contribution) => contribution.transaction)
@JoinColumn({ name: 'id', referencedColumnName: 'transactionId' })
contribution?: Contribution | null
@OneToOne(() => DltTransaction, (dlt) => dlt.transactionId)
@JoinColumn({ name: 'id', referencedColumnName: 'transactionId' })
dltTransaction?: DltTransaction | null
@OneToOne(() => Transaction)
@JoinColumn({ name: 'previous' })
previousTransaction?: Transaction | null
}

View File

@ -0,0 +1 @@
export { DltTransaction } from './0070-add_dlt_transactions_table/DltTransaction'

View File

@ -1 +1 @@
export { Transaction } from './0066-x-community-sendcoins-transactions_table/Transaction'
export { Transaction } from './0070-add_dlt_transactions_table/Transaction'

View File

@ -12,12 +12,14 @@ import { ContributionMessage } from './ContributionMessage'
import { Community } from './Community'
import { FederatedCommunity } from './FederatedCommunity'
import { UserRole } from './UserRole'
import { DltTransaction } from './DltTransaction'
export const entities = [
Community,
Contribution,
ContributionLink,
ContributionMessage,
DltTransaction,
Event,
FederatedCommunity,
LoginElopageBuys,

View File

@ -0,0 +1,19 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`
CREATE TABLE dlt_transactions (
id int unsigned NOT NULL AUTO_INCREMENT,
transactions_id int(10) unsigned NOT NULL,
message_id varchar(64) NULL DEFAULT NULL,
verified tinyint(4) NOT NULL DEFAULT 0,
created_at datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
verified_at datetime(3),
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`DROP TABLE dlt_transactions;`)
}

View File

@ -4,7 +4,7 @@ import dotenv from 'dotenv'
dotenv.config()
const constants = {
DB_VERSION: '0069-add_user_roles_table',
DB_VERSION: '0070-add_dlt_transactions_table',
LOG4JS_CONFIG: 'log4js-config.json',
// default log level on production should be info
LOG_LEVEL: process.env.LOG_LEVEL || 'info',

View File

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