diff --git a/backend/src/apis/dltConnector/DltConnectorClient.test.ts b/backend/src/apis/dltConnector/DltConnectorClient.test.ts index 8169be112..5cd7c3269 100644 --- a/backend/src/apis/dltConnector/DltConnectorClient.test.ts +++ b/backend/src/apis/dltConnector/DltConnectorClient.test.ts @@ -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() diff --git a/backend/src/apis/dltConnector/DltConnectorClient.ts b/backend/src/apis/dltConnector/DltConnectorClient.ts index 5bdf60c35..a9a6557d5 100644 --- a/backend/src/apis/dltConnector/DltConnectorClient.ts +++ b/backend/src/apis/dltConnector/DltConnectorClient.ts @@ -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 { + 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 { - // 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 } } diff --git a/backend/src/apis/dltConnector/enum/TransactionType.ts b/backend/src/apis/dltConnector/enum/TransactionType.ts index 51b87c134..095f56c47 100644 --- a/backend/src/apis/dltConnector/enum/TransactionType.ts +++ b/backend/src/apis/dltConnector/enum/TransactionType.ts @@ -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 +}) diff --git a/backend/src/apis/dltConnector/interaction/transactionToDlt/AbstractTransactionToDlt.role.ts b/backend/src/apis/dltConnector/interaction/transactionToDlt/AbstractTransactionToDlt.role.ts index c1a73edd0..e21b853e6 100644 --- a/backend/src/apis/dltConnector/interaction/transactionToDlt/AbstractTransactionToDlt.role.ts +++ b/backend/src/apis/dltConnector/interaction/transactionToDlt/AbstractTransactionToDlt.role.ts @@ -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 { // public interface public abstract initWithLast(): Promise 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 { qb: SelectQueryBuilder, joinCondition: string, orderBy: OrderByCondition, - ): Promise { + ): SelectQueryBuilder { return qb .leftJoin(DltTransaction, 'dltTransaction', joinCondition) .where('dltTransaction.user_id IS NULL') @@ -56,7 +51,6 @@ export abstract class AbstractTransactionToDltRole { .andWhere('dltTransaction.transaction_link_Id IS NULL') .orderBy(orderBy) .limit(1) - .getOne() } protected createDltTransactionEntry(messageId: string, error: string | null): DltTransaction { diff --git a/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkToDlt.role.ts b/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkToDlt.role.ts index 5b1efdad6..8aeccfa91 100644 --- a/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkToDlt.role.ts +++ b/backend/src/apis/dltConnector/interaction/transactionToDlt/TransactionLinkToDlt.role.ts @@ -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 { async initWithLast(): Promise { 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 { '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 { 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 { diff --git a/backend/src/apis/dltConnector/interaction/transactionToDlt/transactionToDlt.context.ts b/backend/src/apis/dltConnector/interaction/transactionToDlt/transactionToDlt.context.ts index 9e56eee81..7bf271cd0 100644 --- a/backend/src/apis/dltConnector/interaction/transactionToDlt/transactionToDlt.context.ts +++ b/backend/src/apis/dltConnector/interaction/transactionToDlt/transactionToDlt.context.ts @@ -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) diff --git a/backend/src/apis/dltConnector/model/CommunityUser.ts b/backend/src/apis/dltConnector/model/CommunityUser.ts new file mode 100644 index 000000000..0a4eadebf --- /dev/null +++ b/backend/src/apis/dltConnector/model/CommunityUser.ts @@ -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 + } +} diff --git a/backend/src/apis/dltConnector/model/IdentifierSeed.ts b/backend/src/apis/dltConnector/model/IdentifierSeed.ts new file mode 100644 index 000000000..7f7e2fe34 --- /dev/null +++ b/backend/src/apis/dltConnector/model/IdentifierSeed.ts @@ -0,0 +1,9 @@ +// https://www.npmjs.com/package/@apollo/protobufjs + +export class IdentifierSeed { + seed: string + + constructor(seed: string) { + this.seed = seed + } +} diff --git a/backend/src/apis/dltConnector/model/TransactionDraft.ts b/backend/src/apis/dltConnector/model/TransactionDraft.ts index e31003986..8a2d7bdd5 100755 --- a/backend/src/apis/dltConnector/model/TransactionDraft.ts +++ b/backend/src/apis/dltConnector/model/TransactionDraft.ts @@ -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 } diff --git a/backend/src/apis/dltConnector/model/TransactionLinkDraft.ts b/backend/src/apis/dltConnector/model/TransactionLinkDraft.ts deleted file mode 100644 index 1a12f552b..000000000 --- a/backend/src/apis/dltConnector/model/TransactionLinkDraft.ts +++ /dev/null @@ -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() - } -} diff --git a/backend/src/apis/dltConnector/model/UserAccountDraft.ts b/backend/src/apis/dltConnector/model/UserAccountDraft.ts deleted file mode 100644 index dc52065d1..000000000 --- a/backend/src/apis/dltConnector/model/UserAccountDraft.ts +++ /dev/null @@ -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 - } -} diff --git a/backend/src/apis/dltConnector/model/UserIdentifier.ts b/backend/src/apis/dltConnector/model/UserIdentifier.ts index 1824d17f3..059405bd0 100644 --- a/backend/src/apis/dltConnector/model/UserIdentifier.ts +++ b/backend/src/apis/dltConnector/model/UserIdentifier.ts @@ -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 } } diff --git a/database/entity/0088-merge_dlt_tables/Transaction.ts b/database/entity/0088-merge_dlt_tables/Transaction.ts new file mode 100644 index 000000000..d5abed738 --- /dev/null +++ b/database/entity/0088-merge_dlt_tables/Transaction.ts @@ -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 +} diff --git a/database/entity/0088-merge_dlt_tables/TransactionLink.ts b/database/entity/0088-merge_dlt_tables/TransactionLink.ts index 72c80195b..937544bd9 100644 --- a/database/entity/0088-merge_dlt_tables/TransactionLink.ts +++ b/database/entity/0088-merge_dlt_tables/TransactionLink.ts @@ -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[] } diff --git a/database/entity/Transaction.ts b/database/entity/Transaction.ts index d1d7075a9..7b014e17a 100644 --- a/database/entity/Transaction.ts +++ b/database/entity/Transaction.ts @@ -1 +1 @@ -export { Transaction } from './0072-add_communityuuid_to_transactions_table/Transaction' +export { Transaction } from './0088-merge_dlt_tables/Transaction' diff --git a/database/logging/TransactionLinkLogging.view.ts b/database/logging/TransactionLinkLogging.view.ts new file mode 100644 index 000000000..781916131 --- /dev/null +++ b/database/logging/TransactionLinkLogging.view.ts @@ -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), + ), + } + } +} diff --git a/database/logging/TransactionLogging.view.ts b/database/logging/TransactionLogging.view.ts index 7912c7e5d..fff2b46cb 100644 --- a/database/logging/TransactionLogging.view.ts +++ b/database/logging/TransactionLogging.view.ts @@ -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, } } } diff --git a/dlt-connector/src/graphql/enum/InputTransactionType.ts b/dlt-connector/src/graphql/enum/InputTransactionType.ts index 94cd39ff3..20e5e40f9 100755 --- a/dlt-connector/src/graphql/enum/InputTransactionType.ts +++ b/dlt-connector/src/graphql/enum/InputTransactionType.ts @@ -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, { diff --git a/dlt-connector/src/graphql/input/CommunityDraft.ts b/dlt-connector/src/graphql/input/CommunityDraft.ts index 665e10b75..0b26e68d0 100644 --- a/dlt-connector/src/graphql/input/CommunityDraft.ts +++ b/dlt-connector/src/graphql/input/CommunityDraft.ts @@ -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) diff --git a/dlt-connector/src/graphql/input/CommunityUser.ts b/dlt-connector/src/graphql/input/CommunityUser.ts new file mode 100644 index 000000000..97eff7a76 --- /dev/null +++ b/dlt-connector/src/graphql/input/CommunityUser.ts @@ -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 +} diff --git a/dlt-connector/src/graphql/input/TransactionDraft.ts b/dlt-connector/src/graphql/input/TransactionDraft.ts index d1fa48c3c..3b2099ab6 100755 --- a/dlt-connector/src/graphql/input/TransactionDraft.ts +++ b/dlt-connector/src/graphql/input/TransactionDraft.ts @@ -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 } diff --git a/dlt-connector/src/graphql/input/TransactionLinkDraft.ts b/dlt-connector/src/graphql/input/TransactionLinkDraft.ts deleted file mode 100644 index 7ac621e35..000000000 --- a/dlt-connector/src/graphql/input/TransactionLinkDraft.ts +++ /dev/null @@ -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 -} diff --git a/dlt-connector/src/graphql/input/UserAccountDraft.ts b/dlt-connector/src/graphql/input/UserAccountDraft.ts deleted file mode 100644 index 9ae544e32..000000000 --- a/dlt-connector/src/graphql/input/UserAccountDraft.ts +++ /dev/null @@ -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 -} diff --git a/dlt-connector/src/graphql/input/UserIdentifier.ts b/dlt-connector/src/graphql/input/UserIdentifier.ts index 7d9035b93..f622c24e4 100644 --- a/dlt-connector/src/graphql/input/UserIdentifier.ts +++ b/dlt-connector/src/graphql/input/UserIdentifier.ts @@ -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 } diff --git a/dlt-connector/src/graphql/resolver/AccountsResolver.ts b/dlt-connector/src/graphql/resolver/AccountsResolver.ts index a7f264764..ab7203285 100644 --- a/dlt-connector/src/graphql/resolver/AccountsResolver.ts +++ b/dlt-connector/src/graphql/resolver/AccountsResolver.ts @@ -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 { - 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 - } - } - } } diff --git a/dlt-connector/src/graphql/resolver/TransactionsResolver.ts b/dlt-connector/src/graphql/resolver/TransactionsResolver.ts index 8d3d5970b..e7d1c105e 100755 --- a/dlt-connector/src/graphql/resolver/TransactionsResolver.ts +++ b/dlt-connector/src/graphql/resolver/TransactionsResolver.ts @@ -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 { - 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 - } - } - } } diff --git a/dlt-connector/src/interactions/keyPairCalculation/KeyPairCalculation.context.ts b/dlt-connector/src/interactions/keyPairCalculation/KeyPairCalculation.context.ts index ca54d8d16..3cb1f43cf 100644 --- a/dlt-connector/src/interactions/keyPairCalculation/KeyPairCalculation.context.ts +++ b/dlt-connector/src/interactions/keyPairCalculation/KeyPairCalculation.context.ts @@ -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 { +export async function KeyPairCalculation(input: UserIdentifier | string): Promise { 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 => { - if (input instanceof IdentifierSeed) { - return new LinkedTransactionKeyPairRole(input.seed).generateKeyPair() + const retrieveKeyPair = async (input: UserIdentifier | string): Promise => { + 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 diff --git a/dlt-connector/src/interactions/keyPairCalculation/RemoteAccountKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/RemoteAccountKeyPair.role.ts index 1cc9c9891..132e86499 100644 --- a/dlt-connector/src/interactions/keyPairCalculation/RemoteAccountKeyPair.role.ts +++ b/dlt-connector/src/interactions/keyPairCalculation/RemoteAccountKeyPair.role.ts @@ -13,7 +13,11 @@ export class RemoteAccountKeyPairRole extends AbstractRemoteKeyPairRole { } public async retrieveKeyPair(): Promise { - 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, diff --git a/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts b/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts index 473b203cd..6f433a7df 100644 --- a/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts +++ b/dlt-connector/src/interactions/keyPairCalculation/UserKeyPair.role.ts @@ -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()) diff --git a/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts index c448a1704..2865193a9 100644 --- a/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToIota/CreationTransaction.role.ts @@ -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 { 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) diff --git a/dlt-connector/src/interactions/sendToIota/DeferredTransferTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/DeferredTransferTransaction.role.ts index 7a4c627a1..511efa869 100644 --- a/dlt-connector/src/interactions/sendToIota/DeferredTransferTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToIota/DeferredTransferTransaction.role.ts @@ -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 { + 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)) diff --git a/dlt-connector/src/interactions/sendToIota/RegisterAddressTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/RegisterAddressTransaction.role.ts index dc80d7c02..f8c82586d 100644 --- a/dlt-connector/src/interactions/sendToIota/RegisterAddressTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToIota/RegisterAddressTransaction.role.ts @@ -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 { + 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) diff --git a/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts b/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts index caf4df11f..be02eef55 100644 --- a/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts +++ b/dlt-connector/src/interactions/sendToIota/SendToIota.context.ts @@ -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 { 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 { diff --git a/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts b/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts index 1e5dde5dd..7a4cd7bb0 100644 --- a/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts +++ b/dlt-connector/src/interactions/sendToIota/TransferTransaction.role.ts @@ -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 { + 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 diff --git a/dlt-connector/src/logging/CommunityUserLogging.view.ts b/dlt-connector/src/logging/CommunityUserLogging.view.ts new file mode 100644 index 000000000..f1b421cd6 --- /dev/null +++ b/dlt-connector/src/logging/CommunityUserLogging.view.ts @@ -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, + } + } +} diff --git a/dlt-connector/src/logging/IdentifierSeedLogging.view.ts b/dlt-connector/src/logging/IdentifierSeedLogging.view.ts new file mode 100644 index 000000000..d34012e17 --- /dev/null +++ b/dlt-connector/src/logging/IdentifierSeedLogging.view.ts @@ -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, + } + } +} diff --git a/dlt-connector/src/logging/UserIdentifierLogging.view.ts b/dlt-connector/src/logging/UserIdentifierLogging.view.ts index 54ac4b07d..16343551f 100644 --- a/dlt-connector/src/logging/UserIdentifierLogging.view.ts +++ b/dlt-connector/src/logging/UserIdentifierLogging.view.ts @@ -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, } } } diff --git a/dlt-connector/src/manager/KeyPairCacheManager.ts b/dlt-connector/src/manager/KeyPairCacheManager.ts index 844f5ad58..5f5d94f41 100644 --- a/dlt-connector/src/manager/KeyPairCacheManager.ts +++ b/dlt-connector/src/manager/KeyPairCacheManager.ts @@ -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') } }