mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
refactor, pack in one graphql in put model, add redeeming transaction link in dlt
This commit is contained in:
parent
7ddd1cf922
commit
a24ed8ef98
@ -109,21 +109,6 @@ describe('transmitTransaction', () => {
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
*/
|
||||
|
||||
it('invalid transaction type', async () => {
|
||||
const localTransaction = new DbTransaction()
|
||||
localTransaction.typeId = 12
|
||||
try {
|
||||
await DltConnectorClient.getInstance()?.transmitTransaction(
|
||||
new TransactionDraft(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()
|
||||
|
||||
@ -1,17 +1,11 @@
|
||||
import { gql, GraphQLClient } from 'graphql-request'
|
||||
// eslint-disable-next-line import/named, n/no-extraneous-import
|
||||
import { FetchError } from 'node-fetch'
|
||||
|
||||
import { CONFIG } from '@/config'
|
||||
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
// eslint-disable-next-line import/named, n/no-extraneous-import
|
||||
|
||||
import { TransactionDraft } from './model/TransactionDraft'
|
||||
import { TransactionLinkDraft } from './model/TransactionLinkDraft'
|
||||
import { TransactionResult } from './model/TransactionResult'
|
||||
import { UserAccountDraft } from './model/UserAccountDraft'
|
||||
|
||||
const sendTransaction = gql`
|
||||
mutation ($input: TransactionDraft!) {
|
||||
@ -30,40 +24,6 @@ const sendTransaction = gql`
|
||||
}
|
||||
`
|
||||
|
||||
const deferredTransfer = gql`
|
||||
mutation ($input: TransactionLinkDraft!) {
|
||||
deferredTransfer(data: $input) {
|
||||
error {
|
||||
message
|
||||
name
|
||||
}
|
||||
succeed
|
||||
recipe {
|
||||
createdAt
|
||||
type
|
||||
messageIdHex
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const registerAddress = gql`
|
||||
mutation ($input: UserAccountDraft!) {
|
||||
registerAddress(data: $input) {
|
||||
error {
|
||||
message
|
||||
name
|
||||
}
|
||||
succeed
|
||||
recipe {
|
||||
createdAt
|
||||
type
|
||||
messageIdHex
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
|
||||
// and ../federation/client/FederationClientFactory.ts
|
||||
/**
|
||||
@ -116,75 +76,17 @@ export class DltConnectorClient {
|
||||
return DltConnectorClient.instance
|
||||
}
|
||||
|
||||
private handleTransactionResult(result: TransactionResult) {
|
||||
if (result.error) {
|
||||
throw new Error(result.error.message)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private async sendTransaction(input: TransactionDraft) {
|
||||
/**
|
||||
* transmit transaction via dlt-connector to iota
|
||||
* and update dltTransactionId of transaction in db with iota message id
|
||||
*/
|
||||
public async sendTransaction(input: TransactionDraft): Promise<TransactionResult | undefined> {
|
||||
logger.debug('transmit transaction or user to dlt connector', input)
|
||||
const {
|
||||
data: { sendTransaction: result },
|
||||
} = await this.client.rawRequest<{ sendTransaction: TransactionResult }>(sendTransaction, {
|
||||
input,
|
||||
})
|
||||
return this.handleTransactionResult(result)
|
||||
}
|
||||
|
||||
private async deferredTransfer(input: TransactionLinkDraft) {
|
||||
const {
|
||||
data: { deferredTransfer: result },
|
||||
} = await this.client.rawRequest<{ deferredTransfer: TransactionResult }>(deferredTransfer, {
|
||||
input,
|
||||
})
|
||||
return this.handleTransactionResult(result)
|
||||
}
|
||||
|
||||
private async registerAddress(input: UserAccountDraft) {
|
||||
const {
|
||||
data: { registerAddress: result },
|
||||
} = await this.client.rawRequest<{ registerAddress: TransactionResult }>(registerAddress, {
|
||||
input,
|
||||
})
|
||||
return this.handleTransactionResult(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* transmit transaction via dlt-connector to iota
|
||||
* and update dltTransactionId of transaction in db with iota message id
|
||||
*/
|
||||
public async transmitTransaction(
|
||||
input: TransactionDraft | UserAccountDraft | TransactionLinkDraft,
|
||||
): Promise<TransactionResult | undefined> {
|
||||
// we don't need the receive transactions, there contain basically the same data as the send transactions
|
||||
if (
|
||||
input instanceof TransactionDraft &&
|
||||
TransactionTypeId[input.type as keyof typeof TransactionTypeId] === TransactionTypeId.RECEIVE
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
logger.debug('transmit transaction or user to dlt connector', input)
|
||||
if (input instanceof TransactionDraft) {
|
||||
return await this.sendTransaction(input)
|
||||
} else if (input instanceof UserAccountDraft) {
|
||||
return await this.registerAddress(input)
|
||||
} else if (input instanceof TransactionLinkDraft) {
|
||||
return await this.deferredTransfer(input)
|
||||
} else {
|
||||
throw new LogError('unhandled branch reached')
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(e)
|
||||
if (e instanceof FetchError) {
|
||||
throw e
|
||||
} else if (e instanceof Error) {
|
||||
throw new LogError(`from dlt-connector: ${e.message}`)
|
||||
} else {
|
||||
throw new LogError('Exception sending transfer transaction to dlt-connector', e)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,18 @@
|
||||
import { registerEnumType } from 'type-graphql'
|
||||
|
||||
/**
|
||||
* Transaction Types on Blockchain
|
||||
*/
|
||||
export enum TransactionType {
|
||||
GRADIDO_TRANSFER = 1,
|
||||
GRADIDO_CREATION = 2,
|
||||
GROUP_FRIENDS_UPDATE = 3,
|
||||
REGISTER_ADDRESS = 4,
|
||||
GRADIDO_DEFERRED_TRANSFER = 5,
|
||||
COMMUNITY_ROOT = 6,
|
||||
GRADIDO_TRANSFER = 'GRADIDO_TRANSFER',
|
||||
GRADIDO_CREATION = 'GRADIDO_CREATION',
|
||||
GROUP_FRIENDS_UPDATE = 'GROUP_FRIENDS_UPDATE',
|
||||
REGISTER_ADDRESS = 'REGISTER_ADDRESS',
|
||||
GRADIDO_DEFERRED_TRANSFER = 'GRADIDO_DEFERRED_TRANSFER',
|
||||
COMMUNITY_ROOT = 'COMMUNITY_ROOT',
|
||||
}
|
||||
|
||||
registerEnumType(TransactionType, {
|
||||
name: 'TransactionType', // this one is mandatory
|
||||
description: 'Type of the transaction', // this one is optional
|
||||
})
|
||||
|
||||
@ -3,8 +3,6 @@ import { ObjectLiteral, OrderByCondition, SelectQueryBuilder } from '@dbTools/ty
|
||||
import { DltTransaction } from '@entity/DltTransaction'
|
||||
|
||||
import { TransactionDraft } from '@dltConnector/model/TransactionDraft'
|
||||
import { TransactionLinkDraft } from '@dltConnector/model/TransactionLinkDraft'
|
||||
import { UserAccountDraft } from '@dltConnector/model/UserAccountDraft'
|
||||
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
@ -14,10 +12,7 @@ export abstract class AbstractTransactionToDltRole<T extends ObjectLiteral> {
|
||||
// public interface
|
||||
public abstract initWithLast(): Promise<this>
|
||||
public abstract getTimestamp(): number
|
||||
public abstract convertToGraphqlInput():
|
||||
| TransactionDraft
|
||||
| UserAccountDraft
|
||||
| TransactionLinkDraft
|
||||
public abstract convertToGraphqlInput(): TransactionDraft
|
||||
|
||||
public getEntity(): T | null {
|
||||
return this.self
|
||||
@ -48,7 +43,7 @@ export abstract class AbstractTransactionToDltRole<T extends ObjectLiteral> {
|
||||
qb: SelectQueryBuilder<T>,
|
||||
joinCondition: string,
|
||||
orderBy: OrderByCondition,
|
||||
): Promise<T | null> {
|
||||
): SelectQueryBuilder<T> {
|
||||
return qb
|
||||
.leftJoin(DltTransaction, 'dltTransaction', joinCondition)
|
||||
.where('dltTransaction.user_id IS NULL')
|
||||
@ -56,7 +51,6 @@ export abstract class AbstractTransactionToDltRole<T extends ObjectLiteral> {
|
||||
.andWhere('dltTransaction.transaction_link_Id IS NULL')
|
||||
.orderBy(orderBy)
|
||||
.limit(1)
|
||||
.getOne()
|
||||
}
|
||||
|
||||
protected createDltTransactionEntry(messageId: string, error: string | null): DltTransaction {
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { DltTransaction } from '@entity/DltTransaction'
|
||||
import { TransactionLink } from '@entity/TransactionLink'
|
||||
|
||||
import { TransactionType } from '@dltConnector/enum/TransactionType'
|
||||
import { CommunityUser } from '@dltConnector/model/CommunityUser'
|
||||
import { IdentifierSeed } from '@dltConnector/model/IdentifierSeed'
|
||||
import { TransactionDraft } from '@dltConnector/model/TransactionDraft'
|
||||
import { TransactionLinkDraft } from '@dltConnector/model/TransactionLinkDraft'
|
||||
import { UserAccountDraft } from '@dltConnector/model/UserAccountDraft'
|
||||
import { UserIdentifier } from '@dltConnector/model/UserIdentifier'
|
||||
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
@ -19,7 +21,7 @@ export class TransactionLinkToDltRole extends AbstractTransactionToDltRole<Trans
|
||||
'TransactionLink.id = dltTransaction.transactionLinkId',
|
||||
// eslint-disable-next-line camelcase
|
||||
{ TransactionLink_createdAt: 'ASC', User_id: 'ASC' },
|
||||
)
|
||||
).getOne()
|
||||
return this
|
||||
}
|
||||
|
||||
@ -30,11 +32,19 @@ export class TransactionLinkToDltRole extends AbstractTransactionToDltRole<Trans
|
||||
return this.self.createdAt.getTime()
|
||||
}
|
||||
|
||||
public convertToGraphqlInput(): TransactionDraft | UserAccountDraft | TransactionLinkDraft {
|
||||
public convertToGraphqlInput(): TransactionDraft {
|
||||
if (!this.self) {
|
||||
throw new LogError('try to create dlt entry for empty transaction link')
|
||||
}
|
||||
return new TransactionLinkDraft(this.self)
|
||||
const draft = new TransactionDraft()
|
||||
draft.amount = this.self.amount.abs().toString()
|
||||
const user = this.self.user
|
||||
draft.user = new UserIdentifier(user.communityUuid, new CommunityUser(user.gradidoID))
|
||||
draft.linkedUser = new UserIdentifier(user.communityUuid, new IdentifierSeed(this.self.code))
|
||||
draft.createdAt = this.self.createdAt.toISOString()
|
||||
draft.timeoutDate = this.self.validUntil.toISOString()
|
||||
draft.type = TransactionType.GRADIDO_DEFERRED_TRANSFER
|
||||
return draft
|
||||
}
|
||||
|
||||
protected setJoinId(dltTransaction: DltTransaction): void {
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
import { DltTransaction } from '@entity/DltTransaction'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
|
||||
import { TransactionType } from '@dltConnector/enum/TransactionType'
|
||||
import { CommunityUser } from '@dltConnector/model/CommunityUser'
|
||||
import { IdentifierSeed } from '@dltConnector/model/IdentifierSeed'
|
||||
import { TransactionDraft } from '@dltConnector/model/TransactionDraft'
|
||||
import { TransactionLinkDraft } from '@dltConnector/model/TransactionLinkDraft'
|
||||
import { UserAccountDraft } from '@dltConnector/model/UserAccountDraft'
|
||||
import { UserIdentifier } from '@dltConnector/model/UserIdentifier'
|
||||
|
||||
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
import { AbstractTransactionToDltRole } from './AbstractTransactionToDlt.role'
|
||||
@ -15,11 +18,17 @@ import { AbstractTransactionToDltRole } from './AbstractTransactionToDlt.role'
|
||||
export class TransactionToDltRole extends AbstractTransactionToDltRole<Transaction> {
|
||||
async initWithLast(): Promise<this> {
|
||||
this.self = await this.createQueryForPendingItems(
|
||||
Transaction.createQueryBuilder(),
|
||||
Transaction.createQueryBuilder().leftJoinAndSelect(
|
||||
'Transaction.transactionLink',
|
||||
'transactionLink',
|
||||
),
|
||||
'Transaction.id = dltTransaction.transactionId',
|
||||
// eslint-disable-next-line camelcase
|
||||
{ balance_date: 'ASC', Transaction_id: 'ASC' },
|
||||
)
|
||||
// we don't need the receive transactions, there contain basically the same data as the send transactions
|
||||
.andWhere('transaction.typeId NOT :typeId', { typeId: TransactionTypeId.RECEIVE })
|
||||
.getOne()
|
||||
return this
|
||||
}
|
||||
|
||||
@ -30,11 +39,53 @@ export class TransactionToDltRole extends AbstractTransactionToDltRole<Transacti
|
||||
return this.self.balanceDate.getTime()
|
||||
}
|
||||
|
||||
public convertToGraphqlInput(): TransactionDraft | UserAccountDraft | TransactionLinkDraft {
|
||||
public convertToGraphqlInput(): TransactionDraft {
|
||||
if (!this.self) {
|
||||
throw new LogError('try to create dlt entry for empty transaction')
|
||||
}
|
||||
return new TransactionDraft(this.self)
|
||||
const draft = new TransactionDraft()
|
||||
draft.amount = this.self.amount.abs().toString()
|
||||
|
||||
if (
|
||||
!this.self.linkedUserGradidoID ||
|
||||
!this.self.linkedUserCommunityUuid ||
|
||||
!this.self.userCommunityUuid
|
||||
) {
|
||||
throw new LogError(
|
||||
`missing necessary field in transaction: ${this.self.id}, need linkedUserGradidoID, linkedUserCommunityUuid and userCommunityUuid`,
|
||||
)
|
||||
}
|
||||
// it is a redeem of a transaction link?
|
||||
const transactionLink = this.self.transactionLink
|
||||
if (transactionLink) {
|
||||
draft.user = new UserIdentifier(
|
||||
this.self.userCommunityUuid,
|
||||
new IdentifierSeed(transactionLink.code),
|
||||
)
|
||||
} else {
|
||||
draft.user = new UserIdentifier(
|
||||
this.self.userCommunityUuid,
|
||||
new CommunityUser(this.self.userGradidoID),
|
||||
)
|
||||
}
|
||||
draft.linkedUser = new UserIdentifier(
|
||||
this.self.linkedUserCommunityUuid,
|
||||
new CommunityUser(this.self.linkedUserGradidoID),
|
||||
)
|
||||
draft.createdAt = this.self.balanceDate.toISOString()
|
||||
draft.targetDate = this.self.creationDate?.toISOString()
|
||||
switch (this.self.typeId as TransactionTypeId) {
|
||||
case TransactionTypeId.CREATION:
|
||||
draft.type = TransactionType.GRADIDO_CREATION
|
||||
break
|
||||
case TransactionTypeId.SEND:
|
||||
case TransactionTypeId.RECEIVE:
|
||||
draft.type = TransactionType.GRADIDO_TRANSFER
|
||||
break
|
||||
default:
|
||||
throw new LogError('wrong role for type', this.self.typeId as TransactionTypeId)
|
||||
}
|
||||
return draft
|
||||
}
|
||||
|
||||
protected setJoinId(dltTransaction: DltTransaction): void {
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { DltTransaction } from '@entity/DltTransaction'
|
||||
import { User } from '@entity/User'
|
||||
|
||||
import { AccountType } from '@dltConnector/enum/AccountType'
|
||||
import { TransactionType } from '@dltConnector/enum/TransactionType'
|
||||
import { CommunityUser } from '@dltConnector/model/CommunityUser'
|
||||
import { TransactionDraft } from '@dltConnector/model/TransactionDraft'
|
||||
import { TransactionLinkDraft } from '@dltConnector/model/TransactionLinkDraft'
|
||||
import { UserAccountDraft } from '@dltConnector/model/UserAccountDraft'
|
||||
import { UserIdentifier } from '@dltConnector/model/UserIdentifier'
|
||||
|
||||
import { LogError } from '@/server/LogError'
|
||||
|
||||
@ -19,7 +21,7 @@ export class UserToDltRole extends AbstractTransactionToDltRole<User> {
|
||||
'User.id = dltTransaction.userId',
|
||||
// eslint-disable-next-line camelcase
|
||||
{ User_created_at: 'ASC', User_id: 'ASC' },
|
||||
)
|
||||
).getOne()
|
||||
return this
|
||||
}
|
||||
|
||||
@ -30,11 +32,16 @@ export class UserToDltRole extends AbstractTransactionToDltRole<User> {
|
||||
return this.self.createdAt.getTime()
|
||||
}
|
||||
|
||||
public convertToGraphqlInput(): TransactionDraft | UserAccountDraft | TransactionLinkDraft {
|
||||
public convertToGraphqlInput(): TransactionDraft {
|
||||
if (!this.self) {
|
||||
throw new LogError('try to create dlt entry for empty transaction')
|
||||
}
|
||||
return new UserAccountDraft(this.self)
|
||||
const draft = new TransactionDraft()
|
||||
draft.user = new UserIdentifier(this.self.communityUuid, new CommunityUser(this.self.gradidoID))
|
||||
draft.createdAt = this.self.createdAt.toISOString()
|
||||
draft.accountType = AccountType.COMMUNITY_HUMAN
|
||||
draft.type = TransactionType.REGISTER_ADDRESS
|
||||
return draft
|
||||
}
|
||||
|
||||
protected setJoinId(dltTransaction: DltTransaction): void {
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
import { EntityPropertyNotFoundError, QueryFailedError, TypeORMError } from '@dbTools/typeorm'
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
import { TransactionLink } from '@entity/TransactionLink'
|
||||
import { User } from '@entity/User'
|
||||
// eslint-disable-next-line import/named, n/no-extraneous-import
|
||||
import { FetchError } from 'node-fetch'
|
||||
|
||||
import { DltConnectorClient } from '@/apis/dltConnector/DltConnectorClient'
|
||||
import { TransactionResult } from '@/apis/dltConnector/model/TransactionResult'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
import { AbstractTransactionToDltRole } from './AbstractTransactionToDlt.role'
|
||||
import { TransactionLinkToDltRole } from './TransactionLinkToDlt.role'
|
||||
@ -40,21 +37,17 @@ export async function transactionToDlt(dltConnector: DltConnectorClient): Promis
|
||||
if (!pendingTransaction) {
|
||||
break
|
||||
}
|
||||
let result: TransactionResult | undefined
|
||||
let messageId = ''
|
||||
let error: string | null = null
|
||||
|
||||
try {
|
||||
result = await dltConnector.transmitTransaction(
|
||||
pendingTransactionRole.convertToGraphqlInput(),
|
||||
)
|
||||
if (result?.succeed && result.recipe) {
|
||||
messageId = result.recipe.messageIdHex
|
||||
} else {
|
||||
error = 'skipped'
|
||||
}
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : String(e)
|
||||
const result = await dltConnector.sendTransaction(
|
||||
pendingTransactionRole.convertToGraphqlInput(),
|
||||
)
|
||||
if (result?.succeed && result.recipe) {
|
||||
messageId = result.recipe.messageIdHex
|
||||
} else if (result?.error) {
|
||||
error = result.error.message
|
||||
logger.error('error from dlt-connector', result.error)
|
||||
}
|
||||
|
||||
await pendingTransactionRole.saveTransactionResult(messageId, error)
|
||||
|
||||
10
backend/src/apis/dltConnector/model/CommunityUser.ts
Normal file
10
backend/src/apis/dltConnector/model/CommunityUser.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export class CommunityUser {
|
||||
// for community user, uuid and communityUuid used
|
||||
uuid: string
|
||||
accountNr?: number
|
||||
|
||||
constructor(uuid: string, accountNr?: number) {
|
||||
this.uuid = uuid
|
||||
this.accountNr = accountNr
|
||||
}
|
||||
}
|
||||
9
backend/src/apis/dltConnector/model/IdentifierSeed.ts
Normal file
9
backend/src/apis/dltConnector/model/IdentifierSeed.ts
Normal file
@ -0,0 +1,9 @@
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
|
||||
export class IdentifierSeed {
|
||||
seed: string
|
||||
|
||||
constructor(seed: string) {
|
||||
this.seed = seed
|
||||
}
|
||||
}
|
||||
@ -1,39 +1,21 @@
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
import { Transaction } from '@entity/Transaction'
|
||||
|
||||
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { AccountType } from '@dltConnector/enum/AccountType'
|
||||
import { TransactionType } from '@dltConnector/enum/TransactionType'
|
||||
|
||||
import { UserIdentifier } from './UserIdentifier'
|
||||
|
||||
export class TransactionDraft {
|
||||
user: UserIdentifier
|
||||
linkedUser: UserIdentifier
|
||||
amount: string
|
||||
type: string
|
||||
// not used for simply register address
|
||||
linkedUser?: UserIdentifier
|
||||
// not used for register address
|
||||
amount?: string
|
||||
type: TransactionType
|
||||
createdAt: string
|
||||
// only for creation transactions
|
||||
// only for creation transaction
|
||||
targetDate?: string
|
||||
|
||||
constructor(transaction: Transaction) {
|
||||
this.amount = transaction.amount.abs().toString()
|
||||
|
||||
if (
|
||||
!transaction.linkedUserGradidoID ||
|
||||
!transaction.linkedUserCommunityUuid ||
|
||||
!transaction.userCommunityUuid
|
||||
) {
|
||||
throw new LogError(
|
||||
`missing necessary field in transaction: ${transaction.id}, need linkedUserGradidoID, linkedUserCommunityUuid and userCommunityUuid`,
|
||||
)
|
||||
}
|
||||
this.user = new UserIdentifier(transaction.userGradidoID, transaction.userCommunityUuid)
|
||||
this.linkedUser = new UserIdentifier(
|
||||
transaction.linkedUserGradidoID,
|
||||
transaction.linkedUserCommunityUuid,
|
||||
)
|
||||
this.createdAt = transaction.balanceDate.toISOString()
|
||||
this.targetDate = transaction.creationDate?.toISOString()
|
||||
this.type = TransactionTypeId[transaction.typeId]
|
||||
}
|
||||
// only for deferred transaction
|
||||
timeoutDate?: string
|
||||
// only for register address
|
||||
accountType?: AccountType
|
||||
}
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
import { TransactionLink } from '@entity/TransactionLink'
|
||||
|
||||
import { UserIdentifier } from './UserIdentifier'
|
||||
|
||||
export class TransactionLinkDraft {
|
||||
user: UserIdentifier
|
||||
seed: string
|
||||
amount: string
|
||||
createdAt: string
|
||||
timeoutDate: string
|
||||
|
||||
constructor(transaction: TransactionLink) {
|
||||
this.amount = transaction.amount.abs().toString()
|
||||
const user = transaction.user
|
||||
this.user = new UserIdentifier(user.gradidoID, user.communityUuid)
|
||||
this.seed = transaction.code
|
||||
this.createdAt = transaction.createdAt.toISOString()
|
||||
this.timeoutDate = transaction.validUntil.toISOString()
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
import { User } from '@entity/User'
|
||||
|
||||
import { AccountType } from '@/apis/dltConnector/enum/AccountType'
|
||||
|
||||
import { UserIdentifier } from './UserIdentifier'
|
||||
|
||||
export class UserAccountDraft {
|
||||
user: UserIdentifier
|
||||
createdAt: string
|
||||
accountType: AccountType
|
||||
|
||||
constructor(user: User) {
|
||||
this.user = new UserIdentifier(user.gradidoID, user.communityUuid)
|
||||
this.createdAt = user.createdAt.toISOString()
|
||||
this.accountType = AccountType.COMMUNITY_HUMAN
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,17 @@
|
||||
export class UserIdentifier {
|
||||
uuid: string
|
||||
communityUuid: string
|
||||
accountNr?: number
|
||||
import { CommunityUser } from './CommunityUser'
|
||||
import { IdentifierSeed } from './IdentifierSeed'
|
||||
|
||||
constructor(uuid: string, communityUuid: string, accountNr?: number) {
|
||||
this.uuid = uuid
|
||||
export class UserIdentifier {
|
||||
communityUuid: string
|
||||
communityUser?: CommunityUser
|
||||
seed?: IdentifierSeed // used for deferred transfers
|
||||
|
||||
constructor(communityUuid: string, input: CommunityUser | IdentifierSeed) {
|
||||
if (input instanceof CommunityUser) {
|
||||
this.communityUser = input
|
||||
} else if (input instanceof IdentifierSeed) {
|
||||
this.seed = input
|
||||
}
|
||||
this.communityUuid = communityUuid
|
||||
this.accountNr = accountNr
|
||||
}
|
||||
}
|
||||
|
||||
176
database/entity/0088-merge_dlt_tables/Transaction.ts
Normal file
176
database/entity/0088-merge_dlt_tables/Transaction.ts
Normal file
@ -0,0 +1,176 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import {
|
||||
BaseEntity,
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
OneToOne,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
} from 'typeorm'
|
||||
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||
import { Contribution } from '../Contribution'
|
||||
import { DltTransaction } from '../DltTransaction'
|
||||
import { TransactionLink } from '../TransactionLink'
|
||||
|
||||
@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_community_uuid',
|
||||
type: 'varchar',
|
||||
length: 36,
|
||||
nullable: true,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
userCommunityUuid: string | null
|
||||
|
||||
@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_community_uuid',
|
||||
type: 'varchar',
|
||||
length: 36,
|
||||
nullable: true,
|
||||
collation: 'utf8mb4_unicode_ci',
|
||||
})
|
||||
linkedUserCommunityUuid: string | 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
|
||||
|
||||
@ManyToOne(() => TransactionLink, (transactionLink) => transactionLink.transactions)
|
||||
@JoinColumn({ name: 'transactionLinkId' })
|
||||
transactionLink?: TransactionLink | null
|
||||
}
|
||||
@ -7,10 +7,12 @@ import {
|
||||
DeleteDateColumn,
|
||||
OneToOne,
|
||||
JoinColumn,
|
||||
OneToMany,
|
||||
} from 'typeorm'
|
||||
import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer'
|
||||
import { DltTransaction } from '../DltTransaction'
|
||||
import { User } from '../User'
|
||||
import { Transaction } from '../Transaction'
|
||||
|
||||
@Entity('transaction_links')
|
||||
export class TransactionLink extends BaseEntity {
|
||||
@ -76,4 +78,8 @@ export class TransactionLink extends BaseEntity {
|
||||
@OneToOne(() => User, (user) => user.transactionLink)
|
||||
@JoinColumn({ name: 'userId' })
|
||||
user: User
|
||||
|
||||
@OneToMany(() => Transaction, (transaction) => transaction.transactionLink)
|
||||
@JoinColumn({ referencedColumnName: 'transactionLinkId' })
|
||||
transactions: Transaction[]
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
export { Transaction } from './0072-add_communityuuid_to_transactions_table/Transaction'
|
||||
export { Transaction } from './0088-merge_dlt_tables/Transaction'
|
||||
|
||||
35
database/logging/TransactionLinkLogging.view.ts
Normal file
35
database/logging/TransactionLinkLogging.view.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { TransactionLink } from '../entity/TransactionLink'
|
||||
import { AbstractLoggingView } from './AbstractLogging.view'
|
||||
import { DltTransactionLoggingView } from './DltTransactionLogging.view'
|
||||
import { TransactionLoggingView } from './TransactionLogging.view'
|
||||
import { UserLoggingView } from './UserLogging.view'
|
||||
|
||||
export class TransactionLinkLoggingView extends AbstractLoggingView {
|
||||
public constructor(private self: TransactionLink) {
|
||||
super()
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
public toJSON(): any {
|
||||
return {
|
||||
id: this.self.id,
|
||||
userId: this.self.userId,
|
||||
amount: this.decimalToString(this.self.amount),
|
||||
holdAvailableAmount: this.decimalToString(this.self.holdAvailableAmount),
|
||||
memoLength: this.self.memo.length,
|
||||
createdAt: this.dateToString(this.self.createdAt),
|
||||
deletedAt: this.dateToString(this.self.deletedAt),
|
||||
validUntil: this.dateToString(this.self.validUntil),
|
||||
redeemedAt: this.dateToString(this.self.redeemedAt),
|
||||
redeemedBy: this.self.redeemedBy,
|
||||
dltTransaction: this.self.dltTransaction
|
||||
? new DltTransactionLoggingView(this.self.dltTransaction).toJSON()
|
||||
: undefined,
|
||||
user: this.self.user ? new UserLoggingView(this.self.user).toJSON() : undefined,
|
||||
transactions: this.self.transactions.forEach(
|
||||
(transaction) => new TransactionLoggingView(transaction),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,10 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { LargeNumberLike } from 'crypto'
|
||||
import { Transaction } from '../entity/Transaction'
|
||||
import { AbstractLoggingView } from './AbstractLogging.view'
|
||||
import { ContributionLoggingView } from './ContributionLogging.view'
|
||||
import { DltTransactionLoggingView } from './DltTransactionLogging.view'
|
||||
import { TransactionLinkLoggingView } from './TransactionLinkLogging.view'
|
||||
|
||||
// TODO: move enum into database, maybe rename database
|
||||
enum TransactionTypeId {
|
||||
@ -43,7 +45,7 @@ export class TransactionLoggingView extends AbstractLoggingView {
|
||||
linkedUserName: this.self.linkedUserName?.substring(0, 3) + '...',
|
||||
linkedTransactionId: this.self.linkedTransactionId,
|
||||
contribution: this.self.contribution
|
||||
? new ContributionLoggingView(this.self.contribution)
|
||||
? new ContributionLoggingView(this.self.contribution).toJSON()
|
||||
: undefined,
|
||||
dltTransaction: this.self.dltTransaction
|
||||
? new DltTransactionLoggingView(this.self.dltTransaction).toJSON()
|
||||
@ -51,6 +53,9 @@ export class TransactionLoggingView extends AbstractLoggingView {
|
||||
previousTransaction: this.self.previousTransaction
|
||||
? new TransactionLoggingView(this.self.previousTransaction).toJSON()
|
||||
: undefined,
|
||||
transactionLink: this.self.transactionLink
|
||||
? new TransactionLinkLoggingView(this.self.transactionLink).toJSON()
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,12 +3,12 @@ import { registerEnumType } from 'type-graphql'
|
||||
// enum for graphql but with int because it is the same in backend
|
||||
// for transaction type from backend
|
||||
export enum InputTransactionType {
|
||||
CREATION = 1,
|
||||
SEND = 2,
|
||||
RECEIVE = 3,
|
||||
// This is a virtual property, never occurring on the database
|
||||
DECAY = 4,
|
||||
LINK_SUMMARY = 5,
|
||||
GRADIDO_TRANSFER = 'GRADIDO_TRANSFER',
|
||||
GRADIDO_CREATION = 'GRADIDO_CREATION',
|
||||
GROUP_FRIENDS_UPDATE = 'GROUP_FRIENDS_UPDATE',
|
||||
REGISTER_ADDRESS = 'REGISTER_ADDRESS',
|
||||
GRADIDO_DEFERRED_TRANSFER = 'GRADIDO_DEFERRED_TRANSFER',
|
||||
COMMUNITY_ROOT = 'COMMUNITY_ROOT',
|
||||
}
|
||||
|
||||
registerEnumType(InputTransactionType, {
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
|
||||
import { isValidDateString } from '@validator/DateString'
|
||||
import { IsBoolean, IsUUID } from 'class-validator'
|
||||
import { Field, InputType } from 'type-graphql'
|
||||
|
||||
import { isValidDateString } from '@validator/DateString'
|
||||
|
||||
@InputType()
|
||||
export class CommunityDraft {
|
||||
@Field(() => String)
|
||||
|
||||
15
dlt-connector/src/graphql/input/CommunityUser.ts
Normal file
15
dlt-connector/src/graphql/input/CommunityUser.ts
Normal file
@ -0,0 +1,15 @@
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
|
||||
import { IsPositive, IsUUID } from 'class-validator'
|
||||
import { Field, Int, InputType } from 'type-graphql'
|
||||
|
||||
@InputType()
|
||||
export class CommunityUser {
|
||||
@Field(() => String)
|
||||
@IsUUID('4')
|
||||
uuid: string
|
||||
|
||||
@Field(() => Int, { defaultValue: 1, nullable: true })
|
||||
@IsPositive()
|
||||
accountNr?: number
|
||||
}
|
||||
@ -4,6 +4,8 @@ import { isValidDateString, isValidNumberString } from '@validator/DateString'
|
||||
import { IsEnum, IsObject, ValidateNested } from 'class-validator'
|
||||
import { InputType, Field } from 'type-graphql'
|
||||
|
||||
import { AccountType } from '@/graphql/enum/AccountType'
|
||||
|
||||
import { UserIdentifier } from './UserIdentifier'
|
||||
|
||||
@InputType()
|
||||
@ -13,14 +15,16 @@ export class TransactionDraft {
|
||||
@ValidateNested()
|
||||
user: UserIdentifier
|
||||
|
||||
@Field(() => UserIdentifier)
|
||||
// not used for simply register address
|
||||
@Field(() => UserIdentifier, { nullable: true })
|
||||
@IsObject()
|
||||
@ValidateNested()
|
||||
linkedUser: UserIdentifier
|
||||
linkedUser?: UserIdentifier
|
||||
|
||||
@Field(() => String)
|
||||
// not used for register address
|
||||
@Field(() => String, { nullable: true })
|
||||
@isValidNumberString()
|
||||
amount: string
|
||||
amount?: string
|
||||
|
||||
@Field(() => InputTransactionType)
|
||||
@IsEnum(InputTransactionType)
|
||||
@ -34,4 +38,14 @@ export class TransactionDraft {
|
||||
@Field(() => String, { nullable: true })
|
||||
@isValidDateString()
|
||||
targetDate?: string
|
||||
|
||||
// only for deferred transaction
|
||||
@Field(() => String, { nullable: true })
|
||||
@isValidDateString()
|
||||
timeoutDate?: string
|
||||
|
||||
// only for register address
|
||||
@Field(() => AccountType, { nullable: true })
|
||||
@IsEnum(AccountType)
|
||||
accountType?: AccountType
|
||||
}
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
import { isValidDateString, isValidNumberString } from '@validator/DateString'
|
||||
import { IsObject, IsString, ValidateNested } from 'class-validator'
|
||||
import { InputType, Field } from 'type-graphql'
|
||||
|
||||
import { UserIdentifier } from './UserIdentifier'
|
||||
|
||||
@InputType()
|
||||
export class TransactionLinkDraft {
|
||||
@Field(() => UserIdentifier)
|
||||
@IsObject()
|
||||
@ValidateNested()
|
||||
user: UserIdentifier
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
seed: string
|
||||
|
||||
@Field(() => String)
|
||||
@isValidNumberString()
|
||||
amount: string
|
||||
|
||||
@Field(() => String)
|
||||
@isValidDateString()
|
||||
createdAt: string
|
||||
|
||||
@Field(() => String)
|
||||
@isValidDateString()
|
||||
timeoutDate: string
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
|
||||
import { IsEnum, IsObject, ValidateNested } from 'class-validator'
|
||||
import { InputType, Field } from 'type-graphql'
|
||||
|
||||
import { isValidDateString } from '@validator/DateString'
|
||||
|
||||
import { AccountType } from '@/graphql/enum/AccountType'
|
||||
|
||||
import { UserIdentifier } from './UserIdentifier'
|
||||
|
||||
@InputType()
|
||||
export class UserAccountDraft {
|
||||
@Field(() => UserIdentifier)
|
||||
@IsObject()
|
||||
@ValidateNested()
|
||||
user: UserIdentifier
|
||||
|
||||
@Field(() => String)
|
||||
@isValidDateString()
|
||||
createdAt: string
|
||||
|
||||
@Field(() => AccountType)
|
||||
@IsEnum(AccountType)
|
||||
accountType: AccountType
|
||||
}
|
||||
@ -1,19 +1,24 @@
|
||||
// https://www.npmjs.com/package/@apollo/protobufjs
|
||||
|
||||
import { IsPositive, IsUUID } from 'class-validator'
|
||||
import { Field, Int, InputType } from 'type-graphql'
|
||||
import { IsObject, IsUUID, ValidateNested } from 'class-validator'
|
||||
import { Field, InputType } from 'type-graphql'
|
||||
|
||||
import { CommunityUser } from './CommunityUser'
|
||||
import { IdentifierSeed } from './IdentifierSeed'
|
||||
|
||||
@InputType()
|
||||
export class UserIdentifier {
|
||||
@Field(() => String)
|
||||
@IsUUID('4')
|
||||
uuid: string
|
||||
|
||||
@Field(() => String)
|
||||
@IsUUID('4')
|
||||
communityUuid: string
|
||||
|
||||
@Field(() => Int, { defaultValue: 1, nullable: true })
|
||||
@IsPositive()
|
||||
accountNr?: number
|
||||
@Field(() => CommunityUser, { nullable: true })
|
||||
@IsObject()
|
||||
@ValidateNested()
|
||||
communityUser?: CommunityUser
|
||||
|
||||
@Field(() => IdentifierSeed, { nullable: true })
|
||||
@IsObject()
|
||||
@ValidateNested()
|
||||
seed?: IdentifierSeed
|
||||
}
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
/* eslint-disable camelcase */
|
||||
import { AddressType_NONE } from 'gradido-blockchain-js'
|
||||
import { Arg, Mutation, Query, Resolver } from 'type-graphql'
|
||||
import { Arg, Query, Resolver } from 'type-graphql'
|
||||
|
||||
import { getAddressType } from '@/client/GradidoNode'
|
||||
import { KeyPairCalculation } from '@/interactions/keyPairCalculation/KeyPairCalculation.context'
|
||||
import { SendToIotaContext } from '@/interactions/sendToIota/SendToIota.context'
|
||||
import { logger } from '@/logging/logger'
|
||||
import { KeyPairCacheManager } from '@/manager/KeyPairCacheManager'
|
||||
import { uuid4ToHash } from '@/utils/typeConverter'
|
||||
|
||||
import { TransactionErrorType } from '../enum/TransactionErrorType'
|
||||
import { UserAccountDraft } from '../input/UserAccountDraft'
|
||||
import { UserIdentifier } from '../input/UserIdentifier'
|
||||
import { TransactionError } from '../model/TransactionError'
|
||||
import { TransactionResult } from '../model/TransactionResult'
|
||||
@ -25,6 +24,7 @@ export class AccountResolver {
|
||||
new TransactionError(TransactionErrorType.NOT_FOUND, 'cannot get user public key'),
|
||||
)
|
||||
}
|
||||
|
||||
// ask gradido node server for account type, if type !== NONE account exist
|
||||
const addressType = await getAddressType(
|
||||
publicKey.data(),
|
||||
@ -33,21 +33,4 @@ export class AccountResolver {
|
||||
logger.info('isAccountExist', userIdentifier)
|
||||
return addressType !== AddressType_NONE
|
||||
}
|
||||
|
||||
@Mutation(() => TransactionResult)
|
||||
async registerAddress(
|
||||
@Arg('data')
|
||||
userAccountDraft: UserAccountDraft,
|
||||
): Promise<TransactionResult> {
|
||||
try {
|
||||
return await SendToIotaContext(userAccountDraft)
|
||||
} catch (err) {
|
||||
if (err instanceof TransactionError) {
|
||||
return new TransactionResult(err)
|
||||
} else {
|
||||
logger.error('error in register address: ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { TransactionLinkDraft } from '@input/TransactionLinkDraft'
|
||||
import { Resolver, Arg, Mutation } from 'type-graphql'
|
||||
|
||||
import { SendToIotaContext } from '@/interactions/sendToIota/SendToIota.context'
|
||||
@ -25,21 +24,4 @@ export class TransactionResolver {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Mutation(() => TransactionResult)
|
||||
async deferredTransfer(
|
||||
@Arg('data')
|
||||
transactionLinkDraft: TransactionLinkDraft,
|
||||
): Promise<TransactionResult> {
|
||||
try {
|
||||
return await SendToIotaContext(transactionLinkDraft)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
if (error instanceof TransactionError) {
|
||||
return new TransactionResult(error)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,9 +15,7 @@ import { UserKeyPairRole } from './UserKeyPair.role'
|
||||
* @DCI-Context
|
||||
* Context for calculating key pair for signing transactions
|
||||
*/
|
||||
export async function KeyPairCalculation(
|
||||
input: UserIdentifier | string | IdentifierSeed,
|
||||
): Promise<KeyPairEd25519> {
|
||||
export async function KeyPairCalculation(input: UserIdentifier | string): Promise<KeyPairEd25519> {
|
||||
const cache = KeyPairCacheManager.getInstance()
|
||||
|
||||
// Try cache lookup first
|
||||
@ -26,11 +24,9 @@ export async function KeyPairCalculation(
|
||||
return keyPair
|
||||
}
|
||||
|
||||
const retrieveKeyPair = async (
|
||||
input: UserIdentifier | string | IdentifierSeed,
|
||||
): Promise<KeyPairEd25519> => {
|
||||
if (input instanceof IdentifierSeed) {
|
||||
return new LinkedTransactionKeyPairRole(input.seed).generateKeyPair()
|
||||
const retrieveKeyPair = async (input: UserIdentifier | string): Promise<KeyPairEd25519> => {
|
||||
if (input instanceof UserIdentifier && input.seed) {
|
||||
return new LinkedTransactionKeyPairRole(input.seed.seed).generateKeyPair()
|
||||
}
|
||||
|
||||
const communityUUID = input instanceof UserIdentifier ? input.communityUuid : input
|
||||
@ -51,7 +47,7 @@ export async function KeyPairCalculation(
|
||||
}
|
||||
if (input instanceof UserIdentifier) {
|
||||
const userKeyPair = new UserKeyPairRole(input, communityKeyPair).generateKeyPair()
|
||||
const accountNr = input.accountNr ?? 1
|
||||
const accountNr = input.communityUser?.accountNr ?? 1
|
||||
return new AccountKeyPairRole(accountNr, userKeyPair).generateKeyPair()
|
||||
}
|
||||
return communityKeyPair
|
||||
|
||||
@ -13,7 +13,11 @@ export class RemoteAccountKeyPairRole extends AbstractRemoteKeyPairRole {
|
||||
}
|
||||
|
||||
public async retrieveKeyPair(): Promise<KeyPairEd25519> {
|
||||
const nameHash = uuid4ToHash(this.user.uuid)
|
||||
if (!this.user.communityUser) {
|
||||
throw new LogError('missing community user')
|
||||
}
|
||||
|
||||
const nameHash = uuid4ToHash(this.user.communityUser.uuid)
|
||||
const confirmedTransactions = await getTransactions(
|
||||
0,
|
||||
30,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { KeyPairEd25519 } from 'gradido-blockchain-js'
|
||||
|
||||
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { hardenDerivationIndex } from '@/utils/derivationHelper'
|
||||
import { uuid4ToBuffer } from '@/utils/typeConverter'
|
||||
|
||||
@ -14,7 +15,10 @@ export class UserKeyPairRole extends AbstractKeyPairRole {
|
||||
public generateKeyPair(): KeyPairEd25519 {
|
||||
// example gradido id: 03857ac1-9cc2-483e-8a91-e5b10f5b8d16 =>
|
||||
// wholeHex: '03857ac19cc2483e8a91e5b10f5b8d16']
|
||||
const wholeHex = uuid4ToBuffer(this.user.uuid)
|
||||
if (!this.user.communityUser) {
|
||||
throw new LogError('missing community user')
|
||||
}
|
||||
const wholeHex = uuid4ToBuffer(this.user.communityUser.uuid)
|
||||
const parts = []
|
||||
for (let i = 0; i < 4; i++) {
|
||||
parts[i] = hardenDerivationIndex(wholeHex.subarray(i * 4, (i + 1) * 4).readUInt32BE())
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { GradidoTransactionBuilder, TransferAmount } from 'gradido-blockchain-js'
|
||||
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { TransactionError } from '@/graphql/model/TransactionError'
|
||||
|
||||
import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context'
|
||||
|
||||
@ -17,12 +18,27 @@ export class CreationTransactionRole extends AbstractTransactionRole {
|
||||
}
|
||||
|
||||
getRecipientCommunityUuid(): string {
|
||||
throw new LogError('cannot be used as cross group transaction')
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.LOGIC_ERROR,
|
||||
'creation: cannot be used as cross group transaction',
|
||||
)
|
||||
}
|
||||
|
||||
public async getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder> {
|
||||
if (!this.self.targetDate) {
|
||||
throw new LogError('target date missing for creation transaction')
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.MISSING_PARAMETER,
|
||||
'creation: target date missing',
|
||||
)
|
||||
}
|
||||
if (!this.self.linkedUser) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.MISSING_PARAMETER,
|
||||
'creation: linked user missing',
|
||||
)
|
||||
}
|
||||
if (!this.self.amount) {
|
||||
throw new TransactionError(TransactionErrorType.MISSING_PARAMETER, 'creation: amount missing')
|
||||
}
|
||||
const builder = new GradidoTransactionBuilder()
|
||||
const recipientKeyPair = await KeyPairCalculation(this.self.user)
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { GradidoTransactionBuilder, GradidoTransfer, TransferAmount } from 'gradido-blockchain-js'
|
||||
|
||||
import { IdentifierSeed } from '@/graphql/input/IdentifierSeed'
|
||||
import { TransactionLinkDraft } from '@/graphql/input/TransactionLinkDraft'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
import { TransactionError } from '@/graphql/model/TransactionError'
|
||||
|
||||
import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context'
|
||||
|
||||
import { AbstractTransactionRole } from './AbstractTransaction.role'
|
||||
|
||||
export class DeferredTransferTransactionRole extends AbstractTransactionRole {
|
||||
constructor(protected self: TransactionLinkDraft) {
|
||||
constructor(protected self: TransactionDraft) {
|
||||
super()
|
||||
}
|
||||
|
||||
@ -18,13 +18,34 @@ export class DeferredTransferTransactionRole extends AbstractTransactionRole {
|
||||
}
|
||||
|
||||
getRecipientCommunityUuid(): string {
|
||||
throw new LogError('cannot be used as cross group transaction')
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.LOGIC_ERROR,
|
||||
'deferred transfer: cannot be used as cross group transaction',
|
||||
)
|
||||
}
|
||||
|
||||
public async getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder> {
|
||||
if (!this.self.linkedUser || !this.self.linkedUser.seed) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.MISSING_PARAMETER,
|
||||
'deferred transfer: missing linked user or not a seed',
|
||||
)
|
||||
}
|
||||
if (!this.self.amount) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.MISSING_PARAMETER,
|
||||
'deferred transfer: amount missing',
|
||||
)
|
||||
}
|
||||
if (!this.self.timeoutDate) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.MISSING_PARAMETER,
|
||||
'deferred transfer: timeout date missing',
|
||||
)
|
||||
}
|
||||
const builder = new GradidoTransactionBuilder()
|
||||
const senderKeyPair = await KeyPairCalculation(this.self.user)
|
||||
const recipientKeyPair = await KeyPairCalculation(new IdentifierSeed(this.self.seed))
|
||||
const recipientKeyPair = await KeyPairCalculation(this.self.linkedUser)
|
||||
|
||||
builder
|
||||
.setCreatedAt(new Date(this.self.createdAt))
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
/* eslint-disable camelcase */
|
||||
import { GradidoTransactionBuilder } from 'gradido-blockchain-js'
|
||||
|
||||
import { UserAccountDraft } from '@/graphql/input/UserAccountDraft'
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
import { TransactionError } from '@/graphql/model/TransactionError'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { accountTypeToAddressType, uuid4ToHash } from '@/utils/typeConverter'
|
||||
|
||||
@ -10,7 +12,7 @@ import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.con
|
||||
import { AbstractTransactionRole } from './AbstractTransaction.role'
|
||||
|
||||
export class RegisterAddressTransactionRole extends AbstractTransactionRole {
|
||||
constructor(private self: UserAccountDraft) {
|
||||
constructor(private self: TransactionDraft) {
|
||||
super()
|
||||
}
|
||||
|
||||
@ -23,6 +25,20 @@ export class RegisterAddressTransactionRole extends AbstractTransactionRole {
|
||||
}
|
||||
|
||||
public async getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder> {
|
||||
if (!this.self.accountType) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.MISSING_PARAMETER,
|
||||
'register address: account type missing',
|
||||
)
|
||||
}
|
||||
|
||||
if (!this.self.user.communityUser) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.MISSING_PARAMETER,
|
||||
"register address: user isn't a community user",
|
||||
)
|
||||
}
|
||||
|
||||
const builder = new GradidoTransactionBuilder()
|
||||
const communityKeyPair = await KeyPairCalculation(this.self.user.communityUuid)
|
||||
const accountKeyPair = await KeyPairCalculation(this.self.user)
|
||||
@ -31,7 +47,7 @@ export class RegisterAddressTransactionRole extends AbstractTransactionRole {
|
||||
.setRegisterAddress(
|
||||
accountKeyPair.getPublicKey(),
|
||||
accountTypeToAddressType(this.self.accountType),
|
||||
uuid4ToHash(this.self.user.uuid),
|
||||
uuid4ToHash(this.self.user.communityUser.uuid),
|
||||
)
|
||||
.sign(communityKeyPair)
|
||||
.sign(accountKeyPair)
|
||||
|
||||
@ -12,8 +12,6 @@ import { InputTransactionType } from '@/graphql/enum/InputTransactionType'
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
import { TransactionLinkDraft } from '@/graphql/input/TransactionLinkDraft'
|
||||
import { UserAccountDraft } from '@/graphql/input/UserAccountDraft'
|
||||
import { TransactionError } from '@/graphql/model/TransactionError'
|
||||
import { TransactionRecipe } from '@/graphql/model/TransactionRecipe'
|
||||
import { TransactionResult } from '@/graphql/model/TransactionResult'
|
||||
@ -34,7 +32,7 @@ import { TransferTransactionRole } from './TransferTransaction.role'
|
||||
* send every transaction only once to iota!
|
||||
*/
|
||||
export async function SendToIotaContext(
|
||||
input: TransactionDraft | UserAccountDraft | CommunityDraft | TransactionLinkDraft,
|
||||
input: TransactionDraft | CommunityDraft,
|
||||
): Promise<TransactionResult> {
|
||||
const validate = (transaction: GradidoTransaction): void => {
|
||||
try {
|
||||
@ -76,17 +74,25 @@ export async function SendToIotaContext(
|
||||
|
||||
let role: AbstractTransactionRole
|
||||
if (input instanceof TransactionDraft) {
|
||||
if (input.type === InputTransactionType.CREATION) {
|
||||
role = new CreationTransactionRole(input)
|
||||
} else if (input.type === InputTransactionType.SEND) {
|
||||
role = new TransferTransactionRole(input)
|
||||
} else {
|
||||
throw new LogError('not supported transaction type')
|
||||
switch (input.type) {
|
||||
case InputTransactionType.GRADIDO_CREATION:
|
||||
role = new CreationTransactionRole(input)
|
||||
break
|
||||
case InputTransactionType.GRADIDO_TRANSFER:
|
||||
role = new TransferTransactionRole(input)
|
||||
break
|
||||
case InputTransactionType.REGISTER_ADDRESS:
|
||||
role = new RegisterAddressTransactionRole(input)
|
||||
break
|
||||
case InputTransactionType.GRADIDO_DEFERRED_TRANSFER:
|
||||
role = new DeferredTransferTransactionRole(input)
|
||||
break
|
||||
default:
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.NOT_IMPLEMENTED_YET,
|
||||
'not supported transaction type: ' + input.type,
|
||||
)
|
||||
}
|
||||
} else if (input instanceof TransactionLinkDraft) {
|
||||
role = new DeferredTransferTransactionRole(input)
|
||||
} else if (input instanceof UserAccountDraft) {
|
||||
role = new RegisterAddressTransactionRole(input)
|
||||
} else if (input instanceof CommunityDraft) {
|
||||
role = new CommunityRootTransactionRole(input)
|
||||
} else {
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { GradidoTransactionBuilder, TransferAmount } from 'gradido-blockchain-js'
|
||||
|
||||
import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType'
|
||||
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
|
||||
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
|
||||
import { TransactionError } from '@/graphql/model/TransactionError'
|
||||
import { uuid4ToHash } from '@/utils/typeConverter'
|
||||
|
||||
import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.context'
|
||||
@ -8,8 +11,16 @@ import { KeyPairCalculation } from '../keyPairCalculation/KeyPairCalculation.con
|
||||
import { AbstractTransactionRole } from './AbstractTransaction.role'
|
||||
|
||||
export class TransferTransactionRole extends AbstractTransactionRole {
|
||||
constructor(protected self: TransactionDraft) {
|
||||
private linkedUser: UserIdentifier
|
||||
constructor(private self: TransactionDraft) {
|
||||
super()
|
||||
if (!this.self.linkedUser) {
|
||||
throw new TransactionError(
|
||||
TransactionErrorType.MISSING_PARAMETER,
|
||||
'transfer: linked user missing',
|
||||
)
|
||||
}
|
||||
this.linkedUser = this.self.linkedUser
|
||||
}
|
||||
|
||||
getSenderCommunityUuid(): string {
|
||||
@ -17,13 +28,16 @@ export class TransferTransactionRole extends AbstractTransactionRole {
|
||||
}
|
||||
|
||||
getRecipientCommunityUuid(): string {
|
||||
return this.self.linkedUser.communityUuid
|
||||
return this.linkedUser.communityUuid
|
||||
}
|
||||
|
||||
public async getGradidoTransactionBuilder(): Promise<GradidoTransactionBuilder> {
|
||||
if (!this.self.amount) {
|
||||
throw new TransactionError(TransactionErrorType.MISSING_PARAMETER, 'transfer: amount missing')
|
||||
}
|
||||
const builder = new GradidoTransactionBuilder()
|
||||
const senderKeyPair = await KeyPairCalculation(this.self.user)
|
||||
const recipientKeyPair = await KeyPairCalculation(this.self.linkedUser)
|
||||
const recipientKeyPair = await KeyPairCalculation(this.linkedUser)
|
||||
builder
|
||||
.setCreatedAt(new Date(this.self.createdAt))
|
||||
.setMemo('dummy memo for transfer')
|
||||
@ -32,7 +46,7 @@ export class TransferTransactionRole extends AbstractTransactionRole {
|
||||
recipientKeyPair.getPublicKey(),
|
||||
)
|
||||
const senderCommunity = this.self.user.communityUuid
|
||||
const recipientCommunity = this.self.linkedUser.communityUuid
|
||||
const recipientCommunity = this.linkedUser.communityUuid
|
||||
if (senderCommunity !== recipientCommunity) {
|
||||
// we have a cross group transaction
|
||||
builder
|
||||
|
||||
17
dlt-connector/src/logging/CommunityUserLogging.view.ts
Normal file
17
dlt-connector/src/logging/CommunityUserLogging.view.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { CommunityUser } from '@/graphql/input/CommunityUser'
|
||||
|
||||
import { AbstractLoggingView } from './AbstractLogging.view'
|
||||
|
||||
export class CommunityUserLoggingView extends AbstractLoggingView {
|
||||
public constructor(private self: CommunityUser) {
|
||||
super()
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
public toJSON(): any {
|
||||
return {
|
||||
uuid: this.self.uuid,
|
||||
accountNr: this.self.accountNr,
|
||||
}
|
||||
}
|
||||
}
|
||||
16
dlt-connector/src/logging/IdentifierSeedLogging.view.ts
Normal file
16
dlt-connector/src/logging/IdentifierSeedLogging.view.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { IdentifierSeed } from '@/graphql/input/IdentifierSeed'
|
||||
|
||||
import { AbstractLoggingView } from './AbstractLogging.view'
|
||||
|
||||
export class IdentifierSeedLoggingView extends AbstractLoggingView {
|
||||
public constructor(private self: IdentifierSeed) {
|
||||
super()
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
public toJSON(): any {
|
||||
return {
|
||||
seed: this.self.seed,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
|
||||
|
||||
import { AbstractLoggingView } from './AbstractLogging.view'
|
||||
import { CommunityUserLoggingView } from './CommunityUserLogging.view'
|
||||
import { IdentifierSeedLoggingView } from './IdentifierSeedLogging.view'
|
||||
|
||||
export class UserIdentifierLoggingView extends AbstractLoggingView {
|
||||
public constructor(private self: UserIdentifier) {
|
||||
@ -10,9 +12,11 @@ export class UserIdentifierLoggingView extends AbstractLoggingView {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
public toJSON(): any {
|
||||
return {
|
||||
uuid: this.self.uuid,
|
||||
communityUuid: this.self.communityUuid,
|
||||
accountNr: this.self.accountNr,
|
||||
communityUser: this.self.communityUser
|
||||
? new CommunityUserLoggingView(this.self.communityUser).toJSON()
|
||||
: undefined,
|
||||
seed: this.self.seed ? new IdentifierSeedLoggingView(this.self.seed).toJSON() : undefined,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,14 +45,11 @@ export class KeyPairCacheManager {
|
||||
return this.homeCommunityUUID
|
||||
}
|
||||
|
||||
public findKeyPair(input: UserIdentifier | string | IdentifierSeed): KeyPairEd25519 | undefined {
|
||||
public findKeyPair(input: UserIdentifier | string): KeyPairEd25519 | undefined {
|
||||
return this.cache.get(this.getKey(input))
|
||||
}
|
||||
|
||||
public addKeyPair(
|
||||
input: UserIdentifier | string | IdentifierSeed,
|
||||
keyPair: KeyPairEd25519,
|
||||
): void {
|
||||
public addKeyPair(input: UserIdentifier | string, keyPair: KeyPairEd25519): void {
|
||||
const key = this.getKey(input)
|
||||
if (this.cache.has(key)) {
|
||||
throw new LogError('key already exist, cannot add', key)
|
||||
@ -60,13 +57,17 @@ export class KeyPairCacheManager {
|
||||
this.cache.set(key, keyPair)
|
||||
}
|
||||
|
||||
protected getKey(input: UserIdentifier | string | IdentifierSeed): string {
|
||||
protected getKey(input: UserIdentifier | string): string {
|
||||
if (input instanceof UserIdentifier) {
|
||||
return input.uuid
|
||||
} else if (input instanceof IdentifierSeed) {
|
||||
return input.seed
|
||||
} else {
|
||||
if (input.communityUser) {
|
||||
return input.communityUser.uuid
|
||||
} else if (input.seed) {
|
||||
return input.seed.seed
|
||||
}
|
||||
throw new LogError('unhandled branch')
|
||||
} else if (typeof input === 'string') {
|
||||
return input
|
||||
}
|
||||
throw new LogError('unhandled input type')
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user