From 6acf2f3c5b869709d6ad2726d86f4d027a4e68e2 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 29 Aug 2023 00:49:51 +0200 Subject: [PATCH 01/14] first draft for settlePendingReceiverTransaction --- .../federation/client/1_0/SendCoinsClient.ts | 15 +- .../client/1_0/query/settleSendCoins.ts | 25 +++ .../graphql/resolver/TransactionResolver.ts | 18 ++ .../resolver/util/processXComSendCoins.ts | 67 ++++++ federation/src/graphql/api/1_0/const/const.ts | 12 ++ .../api/1_0/resolver/SendCoinsResolver.ts | 93 +++++++- .../1_0}/util/calculateRecepientBalance.ts | 7 +- .../util/settlePendingReceiveTransaction.ts | 199 ++++++++++++++++++ .../src/graphql/util/TRANSACTIONS_LOCK.ts | 4 + 9 files changed, 427 insertions(+), 13 deletions(-) create mode 100644 backend/src/federation/client/1_0/query/settleSendCoins.ts create mode 100644 federation/src/graphql/api/1_0/const/const.ts rename federation/src/graphql/{ => api/1_0}/util/calculateRecepientBalance.ts (76%) create mode 100644 federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts create mode 100644 federation/src/graphql/util/TRANSACTIONS_LOCK.ts diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index c57ca4823..d26ad80ee 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -7,6 +7,7 @@ import { backendLogger as logger } from '@/server/logger' import { SendCoinsArgs } from './model/SendCoinsArgs' import { revertSendCoins } from './query/revertSendCoins' import { voteForSendCoins } from './query/voteForSendCoins' +import { settleSendCoins } from './query/settleSendCoins' // eslint-disable-next-line camelcase export class SendCoinsClient { @@ -72,22 +73,20 @@ export class SendCoinsClient { } } - /* - commitSendCoins = async (args: SendCoinsArgs): Promise => { - logger.debug(`X-Com: commitSendCoins against endpoint='${this.endpoint}'...`) + settleSendCoins = async (args: SendCoinsArgs): Promise => { + logger.debug(`X-Com: settleSendCoins against endpoint='${this.endpoint}'...`) try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { data } = await this.client.rawRequest(commitSendCoins, { args }) + const { data } = await this.client.rawRequest(settleSendCoins, { args }) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (!data?.commitSendCoins?.acknowledged) { - logger.warn('X-Com: commitSendCoins without response data from endpoint', this.endpoint) + logger.warn('X-Com: settleSendCoins without response data from endpoint', this.endpoint) return false } - logger.debug(`X-Com: commitSendCoins successful from endpoint=${this.endpoint}`) + logger.debug(`X-Com: settleSendCoins successful from endpoint=${this.endpoint}`) return true } catch (err) { - throw new LogError(`X-Com: commitSendCoins failed for endpoint=${this.endpoint}`, err) + throw new LogError(`X-Com: settleSendCoins failed for endpoint=${this.endpoint}`, err) } } - */ } diff --git a/backend/src/federation/client/1_0/query/settleSendCoins.ts b/backend/src/federation/client/1_0/query/settleSendCoins.ts new file mode 100644 index 000000000..99f784bc7 --- /dev/null +++ b/backend/src/federation/client/1_0/query/settleSendCoins.ts @@ -0,0 +1,25 @@ +import { gql } from 'graphql-request' + +export const settleSendCoins = gql` + mutation ( + $communityReceiverIdentifier: String! + $userReceiverIdentifier: String! + $creationDate: Date! + $amount: Decimal! + $memo: String! + $communitySenderIdentifier: String! + $userSenderIdentifier: String! + $userSenderName: String! + ) { + settleSendCoins( + communityReceiverIdentifier: $communityReceiverIdentifier + userReceiverIdentifier: $userReceiverIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + communitySenderIdentifier: $communitySenderIdentifier + userSenderIdentifier: $userSenderIdentifier + userSenderName: $userSenderName + ) + } +` diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index ba5d6e155..2a3dd645a 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { getConnection, In } from '@dbTools/typeorm' +import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import { Transaction as dbTransaction } from '@entity/Transaction' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import { User as dbUser } from '@entity/User' @@ -12,6 +13,7 @@ import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql' import { Paginated } from '@arg/Paginated' import { TransactionSendArgs } from '@arg/TransactionSendArgs' import { Order } from '@enum/Order' +import { PendingTransactionState } from '@enum/PendingTransactionState' import { TransactionTypeId } from '@enum/TransactionTypeId' import { Transaction } from '@model/Transaction' import { TransactionList } from '@model/TransactionList' @@ -52,6 +54,22 @@ export const executeTransaction = async ( try { logger.info('executeTransaction', amount, memo, sender, recipient) + const openSenderPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: sender.gradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: sender.gradidoID, state: PendingTransactionState.NEW }, + ], + }) + const openReceiverPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: recipient.gradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: recipient.gradidoID, state: PendingTransactionState.NEW }, + ], + }) + if (openSenderPendingTx > 0 || openReceiverPendingTx > 0) { + throw new LogError('There are still pending Transactions for Sender and/or Recipient') + } + if (sender.id === recipient.id) { throw new LogError('Sender and Recipient are the same', sender.id) } diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 107f65cde..ef37685a9 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -114,3 +114,70 @@ export async function processXComPendingSendCoins( } return true } + +export async function processXComCommittingSendCoins( + receiverFCom: DbFederatedCommunity, + receiverCom: DbCommunity, + senderCom: DbCommunity, + creationDate: Date, + amount: Decimal, + memo: string, + sender: dbUser, + recipient: dbUser, +): Promise { + try { + logger.debug( + `XCom: processXComCommittingSendCoins...`, + receiverFCom, + receiverCom, + senderCom, + creationDate, + amount, + memo, + sender, + recipient, + ) + // first find pending Tx with given parameters + const pendingTx = await DbPendingTransaction.findOneBy({ + userCommunityUuid: senderCom.communityUuid ? senderCom.communityUuid : 'homeCom-UUID', + userGradidoID: sender.gradidoID, + userName: fullName(sender.firstName, sender.lastName), + linkedUserCommunityUuid: receiverCom.communityUuid + ? receiverCom.communityUuid + : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID, + linkedUserGradidoID: recipient.gradidoID, + typeId: TransactionTypeId.SEND, + state: PendingTransactionState.NEW, + balanceDate: creationDate, + memo, + }) + if (pendingTx) { + logger.debug(`X-Com: find pending Tx for settlement:`, pendingTx) + const client = SendCoinsClientFactory.getInstance(receiverFCom) + // eslint-disable-next-line camelcase + if (client instanceof V1_0_SendCoinsClient) { + const args = new SendCoinsArgs() + args.communityReceiverIdentifier = pendingTx.linkedUserCommunityUuid + ? pendingTx.linkedUserCommunityUuid + : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID + if (pendingTx.linkedUserGradidoID) { + args.userReceiverIdentifier = pendingTx.linkedUserGradidoID + } + args.creationDate = pendingTx.balanceDate + args.amount = pendingTx.amount + args.memo = pendingTx.memo + args.communitySenderIdentifier = pendingTx.userCommunityUuid + args.userSenderIdentifier = pendingTx.userGradidoID + if (pendingTx.userName) { + args.userSenderName = pendingTx.userName + } + logger.debug(`X-Com: ready for settleSendCoins with args=`, args) + const acknoleged = await client.settleSendCoins(args) + logger.debug(`X-Com: returnd from settleSendCoins:`, acknoleged) + } + } + } catch (err) { + logger.error(`Error:`, err) + } + return true +} diff --git a/federation/src/graphql/api/1_0/const/const.ts b/federation/src/graphql/api/1_0/const/const.ts new file mode 100644 index 000000000..b97694221 --- /dev/null +++ b/federation/src/graphql/api/1_0/const/const.ts @@ -0,0 +1,12 @@ +import { Decimal } from 'decimal.js-light' + +export const MAX_CREATION_AMOUNT = new Decimal(1000) +export const FULL_CREATION_AVAILABLE = [ + MAX_CREATION_AMOUNT, + MAX_CREATION_AMOUNT, + MAX_CREATION_AMOUNT, +] +export const CONTRIBUTIONLINK_NAME_MAX_CHARS = 100 +export const CONTRIBUTIONLINK_NAME_MIN_CHARS = 5 +export const MEMO_MAX_CHARS = 255 +export const MEMO_MIN_CHARS = 5 diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index d03f1e4f0..97c59f3ba 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -8,9 +8,11 @@ import { User as DbUser } from '@entity/User' import { LogError } from '@/server/LogError' import { PendingTransactionState } from '../enum/PendingTransactionState' import { TransactionTypeId } from '../enum/TransactionTypeId' -import { calculateRecepientBalance } from '@/graphql/util/calculateRecepientBalance' +import { calculateRecepientBalance } from '../util/calculateRecepientBalance' import Decimal from 'decimal.js-light' import { fullName } from '@/graphql/util/fullName' +import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction' +import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '../const/const' @Resolver() // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -50,6 +52,14 @@ export class SendCoinsResolver { homeCom.name, ) } + if (memo.length < MEMO_MIN_CHARS) { + throw new LogError('Memo text is too short', memo.length) + } + + if (memo.length > MEMO_MAX_CHARS) { + throw new LogError('Memo text is too long', memo.length) + } + const receiveBalance = await calculateRecepientBalance(receiverUser.id, amount, creationDate) const pendingTx = DbPendingTransaction.create() pendingTx.amount = amount @@ -151,4 +161,85 @@ export class SendCoinsResolver { throw new LogError(`Error in revertSendCoins: `, err) } } + + @Mutation(() => Boolean) + async settleSendCoins( + @Args() + { + communityReceiverIdentifier, + userReceiverIdentifier, + creationDate, + amount, + memo, + communitySenderIdentifier, + userSenderIdentifier, + userSenderName, + }: SendCoinsArgs, + ): Promise { + logger.debug(`settleSendCoins() via apiVersion=1_0 ...`) + try { + // first check if receiver community is correct + const homeCom = await DbCommunity.findOneByOrFail({ + communityUuid: communityReceiverIdentifier, + }) + /* + if (!homeCom) { + throw new LogError( + `settleSendCoins with wrong communityReceiverIdentifier`, + communityReceiverIdentifier, + ) + } + */ + // second check if receiver user exists in this community + const receiverUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) + /* + if (!receiverUser) { + throw new LogError( + `settleSendCoins with unknown userReceiverIdentifier in the community=`, + homeCom.name, + ) + } + */ + const pendingTx = await DbPendingTransaction.findOneBy({ + userCommunityUuid: communityReceiverIdentifier, + userGradidoID: userReceiverIdentifier, + state: PendingTransactionState.NEW, + typeId: TransactionTypeId.RECEIVE, + balanceDate: creationDate, + linkedUserCommunityUuid: communitySenderIdentifier, + linkedUserGradidoID: userSenderIdentifier, + }) + logger.debug('XCom: settleSendCoins found pendingTX=', pendingTx) + if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { + logger.debug('XCom: settleSendCoins matching pendingTX for settlement...') + try { + await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) + logger.debug('XCom: settlePendingReceiveTransaction successfully...') + } catch (err) { + throw new LogError('Error in settlePendingReceiveTransaction: ', err) + } + } else { + logger.debug( + 'XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...', + ) + throw new LogError( + `Can't find in settlePendingReceiveTransaction the pending receiver TX for args=`, + communityReceiverIdentifier, + userReceiverIdentifier, + PendingTransactionState.NEW, + TransactionTypeId.RECEIVE, + creationDate, + amount, + memo, + communitySenderIdentifier, + userSenderIdentifier, + userSenderName, + ) + } + logger.debug(`settlePendingReceiveTransaction()-1_0... successfull`) + return true + } catch (err) { + throw new LogError(`Error in settlePendingReceiveTransaction: `, err) + } + } } diff --git a/federation/src/graphql/util/calculateRecepientBalance.ts b/federation/src/graphql/api/1_0/util/calculateRecepientBalance.ts similarity index 76% rename from federation/src/graphql/util/calculateRecepientBalance.ts rename to federation/src/graphql/api/1_0/util/calculateRecepientBalance.ts index 56914afba..228862041 100644 --- a/federation/src/graphql/util/calculateRecepientBalance.ts +++ b/federation/src/graphql/api/1_0/util/calculateRecepientBalance.ts @@ -1,8 +1,7 @@ +import { calculateDecay } from '@/graphql/util/decay' +import { getLastTransaction } from '@/graphql/util/getLastTransaction' import { Decimal } from 'decimal.js-light' - -import { getLastTransaction } from './getLastTransaction' -import { calculateDecay } from './decay' -import { Decay } from '../api/1_0/model/Decay' +import { Decay } from '../model/Decay' export async function calculateRecepientBalance( userId: number, diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts new file mode 100644 index 000000000..4c6334074 --- /dev/null +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -0,0 +1,199 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable new-cap */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ + +import { getConnection, In } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' +import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' +import { Transaction as dbTransaction } from '@entity/Transaction' +import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' +import { User as DbUser } from '@entity/User' +import { Decimal } from 'decimal.js-light' + +import { PendingTransactionState } from '../enum/PendingTransactionState' +import { TransactionTypeId } from '../enum/TransactionTypeId' + +import { LogError } from '@/server/LogError' +import { federationLogger as logger } from '@/server/logger' + +import { getLastTransaction } from '@/graphql/util/getLastTransaction' +import { TRANSACTIONS_LOCK } from '@/graphql/util/TRANSACTIONS_LOCK' + +export async function settlePendingReceiveTransaction( + homeCom: DbCommunity, + receiverUser: DbUser, + pendingTx: DbPendingTransaction, +): Promise { + // acquire lock + const releaseLock = await TRANSACTIONS_LOCK.acquire() + try { + logger.info('X-Com: settlePendingReceiveTransaction:', homeCom, receiverUser, pendingTx) + + const openSenderPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: pendingTx?.userGradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: pendingTx?.linkedUserGradidoID, state: PendingTransactionState.NEW }, + ], + }) + const openReceiverPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: userReceiverIdentifier, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: userReceiverIdentifier, state: PendingTransactionState.NEW }, + ], + }) + if (openSenderPendingTx > 1 || openReceiverPendingTx > 1) { + throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient') + } + + if ( + communityReceiverIdentifier === communitySenderIdentifier && + communitySenderIdentifier === userSenderIdentifier + ) { + throw new LogError('Sender and Recipient are the same user: ', userSenderName) + } + + if (memo.length < MEMO_MIN_CHARS) { + throw new LogError('Memo text is too short', memo.length) + } + + if (memo.length > MEMO_MAX_CHARS) { + throw new LogError('Memo text is too long', memo.length) + } + + const recipientUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) + const lastTransaction = await getLastTransaction(recipientUser.id) + const pendingTx = await DbPendingTransaction.findOneByOrFail({ + userId: recipientUser.id, + userGradidoID: recipientUser.gradidoID, + }) + + if (lastTransaction?.id !== pendingTx.previous) { + throw new LogError( + `X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`, + ) + } + // validate amount + const receivedCallDate = new Date() + const sendBalance = await calculateBalance( + sender.id, + amount.mul(-1), + receivedCallDate, + transactionLink, + ) + logger.debug(`calculated Balance=${sendBalance}`) + if (!sendBalance) { + throw new LogError('User has not enough GDD or amount is < 0', sendBalance) + } + + const queryRunner = getConnection().createQueryRunner() + await queryRunner.connect() + await queryRunner.startTransaction('REPEATABLE READ') + logger.debug(`open Transaction to write...`) + try { + // transaction + const transactionSend = new dbTransaction() + transactionSend.typeId = TransactionTypeId.SEND + transactionSend.memo = memo + transactionSend.userId = sender.id + transactionSend.userGradidoID = sender.gradidoID + transactionSend.userName = fullName(sender.firstName, sender.lastName) + transactionSend.linkedUserId = recipient.id + transactionSend.linkedUserGradidoID = recipient.gradidoID + transactionSend.linkedUserName = fullName(recipient.firstName, recipient.lastName) + transactionSend.amount = amount.mul(-1) + transactionSend.balance = sendBalance.balance + transactionSend.balanceDate = receivedCallDate + transactionSend.decay = sendBalance.decay.decay + transactionSend.decayStart = sendBalance.decay.start + transactionSend.previous = sendBalance.lastTransactionId + transactionSend.transactionLinkId = transactionLink ? transactionLink.id : null + await queryRunner.manager.insert(dbTransaction, transactionSend) + + logger.debug(`sendTransaction inserted: ${dbTransaction}`) + + const transactionReceive = new dbTransaction() + transactionReceive.typeId = TransactionTypeId.RECEIVE + transactionReceive.memo = memo + transactionReceive.userId = recipient.id + transactionReceive.userGradidoID = recipient.gradidoID + transactionReceive.userName = fullName(recipient.firstName, recipient.lastName) + transactionReceive.linkedUserId = sender.id + transactionReceive.linkedUserGradidoID = sender.gradidoID + transactionReceive.linkedUserName = fullName(sender.firstName, sender.lastName) + transactionReceive.amount = amount + const receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate) + transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount + transactionReceive.balanceDate = receivedCallDate + transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) + transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null + transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null + transactionReceive.linkedTransactionId = transactionSend.id + transactionReceive.transactionLinkId = transactionLink ? transactionLink.id : null + await queryRunner.manager.insert(dbTransaction, transactionReceive) + logger.debug(`receive Transaction inserted: ${dbTransaction}`) + + // Save linked transaction id for send + transactionSend.linkedTransactionId = transactionReceive.id + await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend) + logger.debug('send Transaction updated', transactionSend) + + if (transactionLink) { + logger.info('transactionLink', transactionLink) + transactionLink.redeemedAt = receivedCallDate + transactionLink.redeemedBy = recipient.id + await queryRunner.manager.update( + dbTransactionLink, + { id: transactionLink.id }, + transactionLink, + ) + } + + await queryRunner.commitTransaction() + logger.info(`commit Transaction successful...`) + + await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) + + await EVENT_TRANSACTION_RECEIVE( + recipient, + sender, + transactionReceive, + transactionReceive.amount, + ) + + // trigger to send transaction via dlt-connector + void sendTransactionsToDltConnector() + } catch (e) { + await queryRunner.rollbackTransaction() + throw new LogError('Transaction was not successful', e) + } finally { + await queryRunner.release() + } + void sendTransactionReceivedEmail({ + firstName: recipient.firstName, + lastName: recipient.lastName, + email: recipient.emailContact.email, + language: recipient.language, + senderFirstName: sender.firstName, + senderLastName: sender.lastName, + senderEmail: sender.emailContact.email, + transactionAmount: amount, + }) + if (transactionLink) { + void sendTransactionLinkRedeemedEmail({ + firstName: sender.firstName, + lastName: sender.lastName, + email: sender.emailContact.email, + language: sender.language, + senderFirstName: recipient.firstName, + senderLastName: recipient.lastName, + senderEmail: recipient.emailContact.email, + transactionAmount: amount, + transactionMemo: memo, + }) + } + logger.info(`finished executeTransaction successfully`) + } finally { + releaseLock() + } + return true +} diff --git a/federation/src/graphql/util/TRANSACTIONS_LOCK.ts b/federation/src/graphql/util/TRANSACTIONS_LOCK.ts new file mode 100644 index 000000000..847386e4d --- /dev/null +++ b/federation/src/graphql/util/TRANSACTIONS_LOCK.ts @@ -0,0 +1,4 @@ +import { Semaphore } from 'await-semaphore' + +const CONCURRENT_TRANSACTIONS = 1 +export const TRANSACTIONS_LOCK = new Semaphore(CONCURRENT_TRANSACTIONS) From 9aedbea8f424d1e250284408667616de7dcc8093 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 29 Aug 2023 02:24:08 +0200 Subject: [PATCH 02/14] first draft of simple tradingLevel checks --- .../federation/client/1_0/SendCoinsClient.ts | 2 +- federation/src/config/index.ts | 5 ++++ .../api/1_0/resolver/SendCoinsResolver.ts | 14 ++++++++++ .../util/settlePendingReceiveTransaction.ts | 28 ++++--------------- .../src/graphql/util/checkTradingLevel.ts | 25 +++++++++++++++++ 5 files changed, 50 insertions(+), 24 deletions(-) create mode 100644 federation/src/graphql/util/checkTradingLevel.ts diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index d26ad80ee..3f22fdc73 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -6,8 +6,8 @@ import { backendLogger as logger } from '@/server/logger' import { SendCoinsArgs } from './model/SendCoinsArgs' import { revertSendCoins } from './query/revertSendCoins' -import { voteForSendCoins } from './query/voteForSendCoins' import { settleSendCoins } from './query/settleSendCoins' +import { voteForSendCoins } from './query/voteForSendCoins' // eslint-disable-next-line camelcase export class SendCoinsClient { diff --git a/federation/src/config/index.ts b/federation/src/config/index.ts index aceb15e98..1200bc1b8 100644 --- a/federation/src/config/index.ts +++ b/federation/src/config/index.ts @@ -55,6 +55,11 @@ const federation = { FEDERATION_API: process.env.FEDERATION_API || '1_0', FEDERATION_PORT: process.env.FEDERATION_PORT || 5010, FEDERATION_COMMUNITY_URL: process.env.FEDERATION_COMMUNITY_URL || null, + FEDERATION_TRADING_LEVEL: { + RECEIVER_COMMUNITY_URL: 'https://stage3.gradido.net/api/', + SEND_COINS: true, + AMOUNT: 100, + }, } const CONFIG = { diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 97c59f3ba..3218ea815 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -13,6 +13,7 @@ import Decimal from 'decimal.js-light' import { fullName } from '@/graphql/util/fullName' import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '../const/const' +import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' @Resolver() // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -44,6 +45,11 @@ export class SendCoinsResolver { communityReceiverIdentifier, ) } + if (!(await checkTradingLevel(homeCom, amount))) { + throw new LogError( + `X-Com: configuration of Trading-Level doesn't permit requested x-com sendCoin action!`, + ) + } // second check if receiver user exists in this community const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) if (!receiverUser) { @@ -52,6 +58,14 @@ export class SendCoinsResolver { homeCom.name, ) } + if ( + communitySenderIdentifier === communityReceiverIdentifier && + userReceiverIdentifier === userSenderIdentifier + ) { + throw new LogError( + `Sender and Recipient are the same: communityUUID=${communityReceiverIdentifier}, gradidoID=${userReceiverIdentifier}`, + ) + } if (memo.length < MEMO_MIN_CHARS) { throw new LogError('Memo text is too short', memo.length) } diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index 4c6334074..a1f9b95a7 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -29,43 +29,25 @@ export async function settlePendingReceiveTransaction( try { logger.info('X-Com: settlePendingReceiveTransaction:', homeCom, receiverUser, pendingTx) + // ensure that no other pendingTx with the same sender or recipient exists const openSenderPendingTx = await DbPendingTransaction.count({ where: [ - { userGradidoID: pendingTx?.userGradidoID, state: PendingTransactionState.NEW }, - { linkedUserGradidoID: pendingTx?.linkedUserGradidoID, state: PendingTransactionState.NEW }, + { userGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW }, ], }) const openReceiverPendingTx = await DbPendingTransaction.count({ where: [ - { userGradidoID: userReceiverIdentifier, state: PendingTransactionState.NEW }, - { linkedUserGradidoID: userReceiverIdentifier, state: PendingTransactionState.NEW }, + { userGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW }, ], }) if (openSenderPendingTx > 1 || openReceiverPendingTx > 1) { throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient') } - if ( - communityReceiverIdentifier === communitySenderIdentifier && - communitySenderIdentifier === userSenderIdentifier - ) { - throw new LogError('Sender and Recipient are the same user: ', userSenderName) - } - - if (memo.length < MEMO_MIN_CHARS) { - throw new LogError('Memo text is too short', memo.length) - } - - if (memo.length > MEMO_MAX_CHARS) { - throw new LogError('Memo text is too long', memo.length) - } - const recipientUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) const lastTransaction = await getLastTransaction(recipientUser.id) - const pendingTx = await DbPendingTransaction.findOneByOrFail({ - userId: recipientUser.id, - userGradidoID: recipientUser.gradidoID, - }) if (lastTransaction?.id !== pendingTx.previous) { throw new LogError( diff --git a/federation/src/graphql/util/checkTradingLevel.ts b/federation/src/graphql/util/checkTradingLevel.ts new file mode 100644 index 000000000..c67d70ad7 --- /dev/null +++ b/federation/src/graphql/util/checkTradingLevel.ts @@ -0,0 +1,25 @@ +import CONFIG from '@/config' +import { Community as DbCommunity } from '@entity/Community' +import { Decimal } from 'decimal.js-light' +import { federationLogger as logger } from '@/server/logger' + +export async function checkTradingLevel(homeCom: DbCommunity, amount: Decimal): Promise { + const tradingLevel = CONFIG.FEDERATION_TRADING_LEVEL + if (homeCom.url !== tradingLevel.RECEIVER_COMMUNITY_URL) { + logger.warn( + `X-Com: tradingLevel allows to receive coins only wiht url ${tradingLevel.RECEIVER_COMMUNITY_URL}`, + ) + return false + } + if (!tradingLevel.SEND_COINS) { + logger.warn(`X-Com: tradingLevel disable general x-com sendcoin actions!`) + return false + } + if (new Decimal(tradingLevel.AMOUNT) < amount) { + logger.warn( + `X-Com: tradingLevel only allows to receive coins lower than amount of ${tradingLevel.AMOUNT}`, + ) + return false + } + return true +} From 99017802cd738959b326d2164b38778a20b6d6a1 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 29 Aug 2023 02:54:35 +0200 Subject: [PATCH 03/14] next version of settlePendingReceiveTransaction --- .../api/1_0/resolver/SendCoinsResolver.ts | 3 +- .../util/settlePendingReceiveTransaction.ts | 85 +++++++++++++------ 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 3218ea815..fff969ae9 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -45,12 +45,13 @@ export class SendCoinsResolver { communityReceiverIdentifier, ) } + // second check is configured trading level if (!(await checkTradingLevel(homeCom, amount))) { throw new LogError( `X-Com: configuration of Trading-Level doesn't permit requested x-com sendCoin action!`, ) } - // second check if receiver user exists in this community + // third check if receiver user exists in this community const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) if (!receiverUser) { throw new LogError( diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index a1f9b95a7..938f3438f 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -6,18 +6,16 @@ import { getConnection, In } from '@dbTools/typeorm' import { Community as DbCommunity } from '@entity/Community' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import { Transaction as dbTransaction } from '@entity/Transaction' -import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import { User as DbUser } from '@entity/User' -import { Decimal } from 'decimal.js-light' import { PendingTransactionState } from '../enum/PendingTransactionState' -import { TransactionTypeId } from '../enum/TransactionTypeId' import { LogError } from '@/server/LogError' import { federationLogger as logger } from '@/server/logger' import { getLastTransaction } from '@/graphql/util/getLastTransaction' import { TRANSACTIONS_LOCK } from '@/graphql/util/TRANSACTIONS_LOCK' +import { calculateRecepientBalance } from './calculateRecepientBalance' export async function settlePendingReceiveTransaction( homeCom: DbCommunity, @@ -26,6 +24,11 @@ export async function settlePendingReceiveTransaction( ): Promise { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() + const queryRunner = getConnection().createQueryRunner() + await queryRunner.connect() + await queryRunner.startTransaction('REPEATABLE READ') + logger.debug(`open Transaction to write...`) + try { logger.info('X-Com: settlePendingReceiveTransaction:', homeCom, receiverUser, pendingTx) @@ -46,14 +49,44 @@ export async function settlePendingReceiveTransaction( throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient') } - const recipientUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) - const lastTransaction = await getLastTransaction(recipientUser.id) + const lastTransaction = await getLastTransaction(receiverUser.id) if (lastTransaction?.id !== pendingTx.previous) { throw new LogError( `X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`, ) } + + const transactionReceive = new dbTransaction() + transactionReceive.typeId = pendingTx.typeId + transactionReceive.memo = pendingTx.memo + transactionReceive.userId = pendingTx.userId + transactionReceive.userGradidoID = pendingTx.userGradidoID + transactionReceive.userName = pendingTx.userName + transactionReceive.linkedUserId = pendingTx.linkedUserId + transactionReceive.linkedUserGradidoID = pendingTx.linkedUserGradidoID + transactionReceive.linkedUserName = pendingTx.linkedUserName + transactionReceive.amount = pendingTx.amount + const receiveBalance = await calculateRecepientBalance( + receiverUser.id, + pendingTx.amount, + pendingTx.balanceDate, + ) + if (receiveBalance?.balance !== pendingTx.balance) { + throw new LogError( + `X-Com: Calculation-Error on receiver balance: receiveBalance=${receiveBalance?.balance}, pendingTx.balance=${pendingTx.balance}`, + ) + } + transactionReceive.balance = pendingTx.balance + transactionReceive.balanceDate = pendingTx.balanceDate + transactionReceive.decay = pendingTx.decay + transactionReceive.decayStart = pendingTx.decayStart + transactionReceive.previous = pendingTx.previous + transactionReceive.linkedTransactionId = pendingTx.linkedTransactionId + await queryRunner.manager.insert(dbTransaction, transactionReceive) + logger.debug(`receive Transaction inserted: ${dbTransaction}`) + + /* // validate amount const receivedCallDate = new Date() const sendBalance = await calculateBalance( @@ -129,27 +162,30 @@ export async function settlePendingReceiveTransaction( transactionLink, ) } + */ + await queryRunner.commitTransaction() + logger.info(`commit Transaction successful...`) - await queryRunner.commitTransaction() - logger.info(`commit Transaction successful...`) + /* + await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) - await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) - - await EVENT_TRANSACTION_RECEIVE( - recipient, - sender, - transactionReceive, - transactionReceive.amount, - ) - - // trigger to send transaction via dlt-connector - void sendTransactionsToDltConnector() - } catch (e) { - await queryRunner.rollbackTransaction() - throw new LogError('Transaction was not successful', e) - } finally { - await queryRunner.release() - } + await EVENT_TRANSACTION_RECEIVE( + recipient, + sender, + transactionReceive, + transactionReceive.amount, + ) + */ + // trigger to send transaction via dlt-connector + // void sendTransactionsToDltConnector() + } catch (e) { + await queryRunner.rollbackTransaction() + throw new LogError('Transaction was not successful', e) + } finally { + await queryRunner.release() + releaseLock() + } + /* void sendTransactionReceivedEmail({ firstName: recipient.firstName, lastName: recipient.lastName, @@ -177,5 +213,6 @@ export async function settlePendingReceiveTransaction( } finally { releaseLock() } + */ return true } From d403087ed14c860b7d52ccef896a4e71779263df Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 30 Aug 2023 15:42:18 +0200 Subject: [PATCH 04/14] revert settlement in federation modul --- federation/package.json | 1 + .../api/1_0/enum/PendingTransactionState.ts | 7 +- .../api/1_0/resolver/SendCoinsResolver.ts | 117 +++++++++++---- .../util/revertSettledReceiveTransaction.ts | 139 ++++++++++++++++++ .../util/settlePendingReceiveTransaction.ts | 89 ++--------- federation/tsconfig.json | 6 +- federation/yarn.lock | 5 + 7 files changed, 248 insertions(+), 116 deletions(-) create mode 100644 federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts diff --git a/federation/package.json b/federation/package.json index b70196149..eb78d6be0 100644 --- a/federation/package.json +++ b/federation/package.json @@ -17,6 +17,7 @@ }, "dependencies": { "apollo-server-express": "^2.25.2", + "await-semaphore": "0.1.3", "class-validator": "^0.13.2", "cors": "2.8.5", "cross-env": "^7.0.3", diff --git a/federation/src/graphql/api/1_0/enum/PendingTransactionState.ts b/federation/src/graphql/api/1_0/enum/PendingTransactionState.ts index 6a614be96..d89b0b0eb 100644 --- a/federation/src/graphql/api/1_0/enum/PendingTransactionState.ts +++ b/federation/src/graphql/api/1_0/enum/PendingTransactionState.ts @@ -2,10 +2,9 @@ import { registerEnumType } from 'type-graphql' export enum PendingTransactionState { NEW = 1, - WAIT_ON_PENDING = 2, - PENDING = 3, - WAIT_ON_CONFIRM = 4, - CONFIRMED = 5, + PENDING = 2, + SETTLED = 3, + REVERTED = 4, } registerEnumType(PendingTransactionState, { diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index fff969ae9..9d43ada62 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -14,6 +14,7 @@ import { fullName } from '@/graphql/util/fullName' import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from '../const/const' import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' +import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction' @Resolver() // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -146,7 +147,7 @@ export class SendCoinsResolver { linkedUserGradidoID: userSenderIdentifier, }) logger.debug('XCom: revertSendCoins found pendingTX=', pendingTx) - if (pendingTx && pendingTx.amount === amount) { + if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { logger.debug('XCom: revertSendCoins matching pendingTX for remove...') try { await pendingTx.remove() @@ -193,28 +194,6 @@ export class SendCoinsResolver { ): Promise { logger.debug(`settleSendCoins() via apiVersion=1_0 ...`) try { - // first check if receiver community is correct - const homeCom = await DbCommunity.findOneByOrFail({ - communityUuid: communityReceiverIdentifier, - }) - /* - if (!homeCom) { - throw new LogError( - `settleSendCoins with wrong communityReceiverIdentifier`, - communityReceiverIdentifier, - ) - } - */ - // second check if receiver user exists in this community - const receiverUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) - /* - if (!receiverUser) { - throw new LogError( - `settleSendCoins with unknown userReceiverIdentifier in the community=`, - homeCom.name, - ) - } - */ const pendingTx = await DbPendingTransaction.findOneBy({ userCommunityUuid: communityReceiverIdentifier, userGradidoID: userReceiverIdentifier, @@ -227,12 +206,15 @@ export class SendCoinsResolver { logger.debug('XCom: settleSendCoins found pendingTX=', pendingTx) if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { logger.debug('XCom: settleSendCoins matching pendingTX for settlement...') - try { - await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) - logger.debug('XCom: settlePendingReceiveTransaction successfully...') - } catch (err) { - throw new LogError('Error in settlePendingReceiveTransaction: ', err) - } + + const homeCom = await DbCommunity.findOneByOrFail({ + communityUuid: communityReceiverIdentifier, + }) + const receiverUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) + + await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) + logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successfull`) + return true } else { logger.debug( 'XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...', @@ -251,10 +233,83 @@ export class SendCoinsResolver { userSenderName, ) } - logger.debug(`settlePendingReceiveTransaction()-1_0... successfull`) - return true } catch (err) { throw new LogError(`Error in settlePendingReceiveTransaction: `, err) } } + + @Mutation(() => Boolean) + async revertSettledSendCoins( + @Args() + { + communityReceiverIdentifier, + userReceiverIdentifier, + creationDate, + amount, + memo, + communitySenderIdentifier, + userSenderIdentifier, + userSenderName, + }: SendCoinsArgs, + ): Promise { + try { + logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`) + // first check if receiver community is correct + const homeCom = await DbCommunity.findOneBy({ + communityUuid: communityReceiverIdentifier, + }) + if (!homeCom) { + throw new LogError( + `revertSettledSendCoins with wrong communityReceiverIdentifier`, + communityReceiverIdentifier, + ) + } + // second check if receiver user exists in this community + const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) + if (!receiverUser) { + throw new LogError( + `revertSettledSendCoins with unknown userReceiverIdentifier in the community=`, + homeCom.name, + ) + } + const pendingTx = await DbPendingTransaction.findOneBy({ + userCommunityUuid: communityReceiverIdentifier, + userGradidoID: userReceiverIdentifier, + state: PendingTransactionState.SETTLED, + typeId: TransactionTypeId.RECEIVE, + balanceDate: creationDate, + linkedUserCommunityUuid: communitySenderIdentifier, + linkedUserGradidoID: userSenderIdentifier, + }) + logger.debug('XCom: revertSettledSendCoins found pendingTX=', pendingTx) + if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { + logger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...') + try { + await revertSettledReceiveTransaction(homeCom, receiverUser, pendingTx) + logger.debug('XCom: revertSettledSendCoins pendingTX successfully') + } catch (err) { + throw new LogError('Error in revertSettledSendCoins of receiver: ', err) + } + } else { + logger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...') + throw new LogError( + `Can't find in revertSettledSendCoins the pending receiver TX for args=`, + communityReceiverIdentifier, + userReceiverIdentifier, + PendingTransactionState.SETTLED, + TransactionTypeId.RECEIVE, + creationDate, + amount, + memo, + communitySenderIdentifier, + userSenderIdentifier, + userSenderName, + ) + } + logger.debug(`revertSendCoins()-1_0... successfull`) + return true + } catch (err) { + throw new LogError(`Error in revertSendCoins: `, err) + } + } } diff --git a/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts new file mode 100644 index 000000000..ecf615667 --- /dev/null +++ b/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts @@ -0,0 +1,139 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable new-cap */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ + +import { getConnection } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' +import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' +import { Transaction as dbTransaction } from '@entity/Transaction' +import { User as DbUser } from '@entity/User' + +import { PendingTransactionState } from '../enum/PendingTransactionState' + +import { LogError } from '@/server/LogError' +import { federationLogger as logger } from '@/server/logger' + +import { getLastTransaction } from '@/graphql/util/getLastTransaction' +import { TRANSACTIONS_LOCK } from '@/graphql/util/TRANSACTIONS_LOCK' + +export async function revertSettledReceiveTransaction( + homeCom: DbCommunity, + receiverUser: DbUser, + pendingTx: DbPendingTransaction, +): Promise { + // TODO: synchronisation with TRANSACTION_LOCK of backend-modul necessary!!! + // acquire lock + const releaseLock = await TRANSACTIONS_LOCK.acquire() + const queryRunner = getConnection().createQueryRunner() + await queryRunner.connect() + await queryRunner.startTransaction('REPEATABLE READ') + logger.debug(`start Transaction for write-access...`) + + try { + logger.info('X-Com: revertSettledReceiveTransaction:', homeCom, receiverUser, pendingTx) + + // ensure that no other pendingTx with the same sender or recipient exists + const openSenderPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW }, + { userGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.SETTLED }, + { + linkedUserGradidoID: pendingTx.linkedUserGradidoID!, + state: PendingTransactionState.NEW, + }, + { + linkedUserGradidoID: pendingTx.linkedUserGradidoID!, + state: PendingTransactionState.SETTLED, + }, + ], + }) + const openReceiverPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW }, + { userGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.SETTLED }, + { linkedUserGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.SETTLED }, + ], + }) + if (openSenderPendingTx > 1 || openReceiverPendingTx > 1) { + throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient') + } + + const lastTransaction = await getLastTransaction(receiverUser.id) + // now the last Tx must be the equivalant to the pendingTX + if ( + lastTransaction && + lastTransaction.balance === pendingTx.balance && + lastTransaction.balanceDate === pendingTx.balanceDate && + lastTransaction.userGradidoID === pendingTx.userGradidoID && + lastTransaction.userName === pendingTx.userName && + lastTransaction.amount === pendingTx.amount && + lastTransaction.memo === pendingTx.memo && + lastTransaction.linkedUserGradidoID === pendingTx.linkedUserGradidoID && + lastTransaction.linkedUserName === pendingTx.linkedUserName + ) { + await queryRunner.manager.remove(dbTransaction, lastTransaction) + logger.debug(`X-Com: revert settlement receive Transaction removed:`, lastTransaction) + // and mark the pendingTx in the pending_transactions table as reverted + pendingTx.state = PendingTransactionState.REVERTED + await queryRunner.manager.save(DbPendingTransaction, pendingTx) + + await queryRunner.commitTransaction() + logger.info(`commit revert settlement recipient Transaction successful...`) + } else { + // TODO: if the last TX is not equivelant to pendingTX, the transactions must be corrected in EXPERT-MODE + throw new LogError( + `X-Com: missmatching transaction order for revert settlement! lastTransation=${lastTransaction} != pendingTx=${pendingTx}`, + ) + } + + /* + await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) + + await EVENT_TRANSACTION_RECEIVE( + recipient, + sender, + transactionReceive, + transactionReceive.amount, + ) + */ + // trigger to send transaction via dlt-connector + // void sendTransactionsToDltConnector() + } catch (e) { + await queryRunner.rollbackTransaction() + throw new LogError('X-Com: revert settlement recipient Transaction was not successful', e) + } finally { + await queryRunner.release() + releaseLock() + } + /* + void sendTransactionReceivedEmail({ + firstName: recipient.firstName, + lastName: recipient.lastName, + email: recipient.emailContact.email, + language: recipient.language, + senderFirstName: sender.firstName, + senderLastName: sender.lastName, + senderEmail: sender.emailContact.email, + transactionAmount: amount, + }) + if (transactionLink) { + void sendTransactionLinkRedeemedEmail({ + firstName: sender.firstName, + lastName: sender.lastName, + email: sender.emailContact.email, + language: sender.language, + senderFirstName: recipient.firstName, + senderLastName: recipient.lastName, + senderEmail: recipient.emailContact.email, + transactionAmount: amount, + transactionMemo: memo, + }) + } + logger.info(`finished executeTransaction successfully`) + } finally { + releaseLock() + } + */ + return true +} diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index 938f3438f..0823e7d41 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -2,7 +2,7 @@ /* eslint-disable new-cap */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { getConnection, In } from '@dbTools/typeorm' +import { getConnection } from '@dbTools/typeorm' import { Community as DbCommunity } from '@entity/Community' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import { Transaction as dbTransaction } from '@entity/Transaction' @@ -22,12 +22,13 @@ export async function settlePendingReceiveTransaction( receiverUser: DbUser, pendingTx: DbPendingTransaction, ): Promise { + // TODO: synchronisation with TRANSACTION_LOCK of backend-modul necessary!!! // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() const queryRunner = getConnection().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') - logger.debug(`open Transaction to write...`) + logger.debug(`start Transaction for write-access...`) try { logger.info('X-Com: settlePendingReceiveTransaction:', homeCom, receiverUser, pendingTx) @@ -57,6 +58,7 @@ export async function settlePendingReceiveTransaction( ) } + // transfer the pendingTx to the transactions table const transactionReceive = new dbTransaction() transactionReceive.typeId = pendingTx.typeId transactionReceive.memo = pendingTx.memo @@ -86,85 +88,12 @@ export async function settlePendingReceiveTransaction( await queryRunner.manager.insert(dbTransaction, transactionReceive) logger.debug(`receive Transaction inserted: ${dbTransaction}`) - /* - // validate amount - const receivedCallDate = new Date() - const sendBalance = await calculateBalance( - sender.id, - amount.mul(-1), - receivedCallDate, - transactionLink, - ) - logger.debug(`calculated Balance=${sendBalance}`) - if (!sendBalance) { - throw new LogError('User has not enough GDD or amount is < 0', sendBalance) - } + // and mark the pendingTx in the pending_transactions table as settled + pendingTx.state = PendingTransactionState.SETTLED + await queryRunner.manager.save(DbPendingTransaction, pendingTx) - const queryRunner = getConnection().createQueryRunner() - await queryRunner.connect() - await queryRunner.startTransaction('REPEATABLE READ') - logger.debug(`open Transaction to write...`) - try { - // transaction - const transactionSend = new dbTransaction() - transactionSend.typeId = TransactionTypeId.SEND - transactionSend.memo = memo - transactionSend.userId = sender.id - transactionSend.userGradidoID = sender.gradidoID - transactionSend.userName = fullName(sender.firstName, sender.lastName) - transactionSend.linkedUserId = recipient.id - transactionSend.linkedUserGradidoID = recipient.gradidoID - transactionSend.linkedUserName = fullName(recipient.firstName, recipient.lastName) - transactionSend.amount = amount.mul(-1) - transactionSend.balance = sendBalance.balance - transactionSend.balanceDate = receivedCallDate - transactionSend.decay = sendBalance.decay.decay - transactionSend.decayStart = sendBalance.decay.start - transactionSend.previous = sendBalance.lastTransactionId - transactionSend.transactionLinkId = transactionLink ? transactionLink.id : null - await queryRunner.manager.insert(dbTransaction, transactionSend) - - logger.debug(`sendTransaction inserted: ${dbTransaction}`) - - const transactionReceive = new dbTransaction() - transactionReceive.typeId = TransactionTypeId.RECEIVE - transactionReceive.memo = memo - transactionReceive.userId = recipient.id - transactionReceive.userGradidoID = recipient.gradidoID - transactionReceive.userName = fullName(recipient.firstName, recipient.lastName) - transactionReceive.linkedUserId = sender.id - transactionReceive.linkedUserGradidoID = sender.gradidoID - transactionReceive.linkedUserName = fullName(sender.firstName, sender.lastName) - transactionReceive.amount = amount - const receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate) - transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount - transactionReceive.balanceDate = receivedCallDate - transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) - transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null - transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null - transactionReceive.linkedTransactionId = transactionSend.id - transactionReceive.transactionLinkId = transactionLink ? transactionLink.id : null - await queryRunner.manager.insert(dbTransaction, transactionReceive) - logger.debug(`receive Transaction inserted: ${dbTransaction}`) - - // Save linked transaction id for send - transactionSend.linkedTransactionId = transactionReceive.id - await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend) - logger.debug('send Transaction updated', transactionSend) - - if (transactionLink) { - logger.info('transactionLink', transactionLink) - transactionLink.redeemedAt = receivedCallDate - transactionLink.redeemedBy = recipient.id - await queryRunner.manager.update( - dbTransactionLink, - { id: transactionLink.id }, - transactionLink, - ) - } - */ await queryRunner.commitTransaction() - logger.info(`commit Transaction successful...`) + logger.info(`commit recipient Transaction successful...`) /* await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) @@ -180,7 +109,7 @@ export async function settlePendingReceiveTransaction( // void sendTransactionsToDltConnector() } catch (e) { await queryRunner.rollbackTransaction() - throw new LogError('Transaction was not successful', e) + throw new LogError('X-Com: recipient Transaction was not successful', e) } finally { await queryRunner.release() releaseLock() diff --git a/federation/tsconfig.json b/federation/tsconfig.json index 2326786ac..ba0d6fef3 100644 --- a/federation/tsconfig.json +++ b/federation/tsconfig.json @@ -53,13 +53,17 @@ // "@model/*": ["src/graphql/model/*"], "@repository/*": ["src/typeorm/repository/*"], "@test/*": ["test/*"], + /* common */ + "@common/*": ["../common/src/*"], + "@email/*": ["../common/scr/email/*"], + "@event/*": ["../common/src/event/*"], /* external */ "@typeorm/*": ["../backend/src/typeorm/*", "../../backend/src/typeorm/*"], "@dbTools/*": ["../database/src/*", "../../database/build/src/*"], "@entity/*": ["../database/entity/*", "../../database/build/entity/*"] }, // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - "typeRoots": ["node_modules/@types"], /* List of folders to include type definitions from. */ + "typeRoots": ["node_modules/@types"], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ diff --git a/federation/yarn.lock b/federation/yarn.lock index a811712fa..758e7e4ce 100644 --- a/federation/yarn.lock +++ b/federation/yarn.lock @@ -1529,6 +1529,11 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +await-semaphore@0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/await-semaphore/-/await-semaphore-0.1.3.tgz#2b88018cc8c28e06167ae1cdff02504f1f9688d3" + integrity sha512-d1W2aNSYcz/sxYO4pMGX9vq65qOTu0P800epMud+6cYYX0QcT7zyqcxec3VWzpgvdXo57UWmVbZpLMjX2m1I7Q== + babel-jest@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" From 04a84c0ae2060f552f36b19eaca4c79d51b54838 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 30 Aug 2023 15:56:33 +0200 Subject: [PATCH 05/14] settle pending transactions on backend side --- .../federation/client/1_0/SendCoinsClient.ts | 23 ++- .../1_0/query/revertSettledSendCoins.ts | 25 +++ .../graphql/enum/PendingTransactionState.ts | 7 +- .../resolver/util/processXComSendCoins.ts | 26 ++++ .../util/settlePendingSenderTransaction.ts | 146 ++++++++++++++++++ 5 files changed, 222 insertions(+), 5 deletions(-) create mode 100644 backend/src/federation/client/1_0/query/revertSettledSendCoins.ts create mode 100644 backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index 3f22fdc73..90c3e2e20 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -8,6 +8,7 @@ import { SendCoinsArgs } from './model/SendCoinsArgs' import { revertSendCoins } from './query/revertSendCoins' import { settleSendCoins } from './query/settleSendCoins' import { voteForSendCoins } from './query/voteForSendCoins' +import { revertSettledSendCoins } from './query/revertSettledSendCoins' // eslint-disable-next-line camelcase export class SendCoinsClient { @@ -79,7 +80,7 @@ export class SendCoinsClient { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(settleSendCoins, { args }) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!data?.commitSendCoins?.acknowledged) { + if (!data?.settleSendCoins?.acknowledged) { logger.warn('X-Com: settleSendCoins without response data from endpoint', this.endpoint) return false } @@ -89,4 +90,24 @@ export class SendCoinsClient { throw new LogError(`X-Com: settleSendCoins failed for endpoint=${this.endpoint}`, err) } } + + revertSettledSendCoins = async (args: SendCoinsArgs): Promise => { + logger.debug(`X-Com: revertSettledSendCoins against endpoint='${this.endpoint}'...`) + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { data } = await this.client.rawRequest(revertSettledSendCoins, { args }) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (!data?.revertSettledSendCoins?.acknowledged) { + logger.warn( + 'X-Com: revertSettledSendCoins without response data from endpoint', + this.endpoint, + ) + return false + } + logger.debug(`X-Com: revertSettledSendCoins successful from endpoint=${this.endpoint}`) + return true + } catch (err) { + throw new LogError(`X-Com: revertSettledSendCoins failed for endpoint=${this.endpoint}`, err) + } + } } diff --git a/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts b/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts new file mode 100644 index 000000000..0d4447507 --- /dev/null +++ b/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts @@ -0,0 +1,25 @@ +import { gql } from 'graphql-request' + +export const revertSettledSendCoins = gql` + mutation ( + $communityReceiverIdentifier: String! + $userReceiverIdentifier: String! + $creationDate: Date! + $amount: Decimal! + $memo: String! + $communitySenderIdentifier: String! + $userSenderIdentifier: String! + $userSenderName: String! + ) { + revertSettledSendCoins( + communityReceiverIdentifier: $communityReceiverIdentifier + userReceiverIdentifier: $userReceiverIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + communitySenderIdentifier: $communitySenderIdentifier + userSenderIdentifier: $userSenderIdentifier + userSenderName: $userSenderName + ) + } +` diff --git a/backend/src/graphql/enum/PendingTransactionState.ts b/backend/src/graphql/enum/PendingTransactionState.ts index 6a614be96..d89b0b0eb 100644 --- a/backend/src/graphql/enum/PendingTransactionState.ts +++ b/backend/src/graphql/enum/PendingTransactionState.ts @@ -2,10 +2,9 @@ import { registerEnumType } from 'type-graphql' export enum PendingTransactionState { NEW = 1, - WAIT_ON_PENDING = 2, - PENDING = 3, - WAIT_ON_CONFIRM = 4, - CONFIRMED = 5, + PENDING = 2, + SETTLED = 3, + REVERTED = 4, } registerEnumType(PendingTransactionState, { diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index ef37685a9..40b7dd775 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -15,6 +15,7 @@ import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' import { calculateSenderBalance } from '@/util/calculateSenderBalance' import { fullName } from '@/util/utilities' +import { settlePendingSenderTransaction } from './settlePendingSenderTransaction' export async function processXComPendingSendCoins( receiverFCom: DbFederatedCommunity, @@ -174,6 +175,31 @@ export async function processXComCommittingSendCoins( logger.debug(`X-Com: ready for settleSendCoins with args=`, args) const acknoleged = await client.settleSendCoins(args) logger.debug(`X-Com: returnd from settleSendCoins:`, acknoleged) + if (acknoleged) { + // settle the pending transaction on receiver-side was successfull, so now settle the sender side + try { + await settlePendingSenderTransaction(senderCom, sender, pendingTx) + } catch (err) { + logger.error(`Error in writing sender pending transaction: `, err) + // revert the existing pending transaction on receiver side + let revertCount = 0 + logger.debug(`X-Com: first try to revertSetteledSendCoins of receiver`) + do { + if (await client.revertSettledSendCoins(args)) { + logger.debug( + `revertSettledSendCoins()-1_0... successfull after revertCount=`, + revertCount, + ) + // treat revertingSettledSendCoins as an error of the whole sendCoins-process + throw new LogError('Error in settle sender pending transaction: `, err') + } + } while (CONFIG.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS > revertCount++) + throw new LogError( + `Error in reverting receiver pending transaction even after revertCount=`, + revertCount, + ) + } + } } } } catch (err) { diff --git a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts new file mode 100644 index 000000000..8db8ba695 --- /dev/null +++ b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts @@ -0,0 +1,146 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable new-cap */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ + +import { getConnection } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' +import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' +import { Transaction as dbTransaction } from '@entity/Transaction' +import { User as DbUser } from '@entity/User' + +import { LogError } from '@/server/LogError' +import { backendLogger as logger } from '@/server/logger' +import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' + +import { getLastTransaction } from './getLastTransaction' +import { calculateSenderBalance } from '@/util/calculateSenderBalance' +import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState' + +export async function settlePendingSenderTransaction( + homeCom: DbCommunity, + senderUser: DbUser, + pendingTx: DbPendingTransaction, +): Promise { + // TODO: synchronisation with TRANSACTION_LOCK of federation-modul necessary!!! + // acquire lock + const releaseLock = await TRANSACTIONS_LOCK.acquire() + const queryRunner = getConnection().createQueryRunner() + await queryRunner.connect() + await queryRunner.startTransaction('REPEATABLE READ') + logger.debug(`start Transaction for write-access...`) + + try { + logger.info('X-Com: settlePendingSenderTransaction:', homeCom, senderUser, pendingTx) + + // ensure that no other pendingTx with the same sender or recipient exists + const openSenderPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW }, + ], + }) + const openReceiverPendingTx = await DbPendingTransaction.count({ + where: [ + { userGradidoID: pendingTx.linkedUserGradidoID!, state: PendingTransactionState.NEW }, + { linkedUserGradidoID: pendingTx.userGradidoID, state: PendingTransactionState.NEW }, + ], + }) + if (openSenderPendingTx > 1 || openReceiverPendingTx > 1) { + throw new LogError('There are more than 1 pending Transactions for Sender and/or Recipient') + } + + const lastTransaction = await getLastTransaction(senderUser.id) + + if (lastTransaction?.id !== pendingTx.previous) { + throw new LogError( + `X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`, + ) + } + + // transfer the pendingTx to the transactions table + const transactionSend = new dbTransaction() + transactionSend.typeId = pendingTx.typeId + transactionSend.memo = pendingTx.memo + transactionSend.userId = pendingTx.userId + transactionSend.userGradidoID = pendingTx.userGradidoID + transactionSend.userName = pendingTx.userName + transactionSend.linkedUserId = pendingTx.linkedUserId + transactionSend.linkedUserGradidoID = pendingTx.linkedUserGradidoID + transactionSend.linkedUserName = pendingTx.linkedUserName + transactionSend.amount = pendingTx.amount + const sendBalance = await calculateSenderBalance( + senderUser.id, + pendingTx.amount, + pendingTx.balanceDate, + ) + if (sendBalance?.balance !== pendingTx.balance) { + throw new LogError( + `X-Com: Calculation-Error on receiver balance: receiveBalance=${sendBalance?.balance}, pendingTx.balance=${pendingTx.balance}`, + ) + } + transactionSend.balance = pendingTx.balance + transactionSend.balanceDate = pendingTx.balanceDate + transactionSend.decay = pendingTx.decay + transactionSend.decayStart = pendingTx.decayStart + transactionSend.previous = pendingTx.previous + transactionSend.linkedTransactionId = pendingTx.linkedTransactionId + await queryRunner.manager.insert(dbTransaction, transactionSend) + logger.debug(`send Transaction inserted: ${dbTransaction}`) + + // and mark the pendingTx in the pending_transactions table as settled + pendingTx.state = PendingTransactionState.SETTLED + await queryRunner.manager.save(DbPendingTransaction, pendingTx) + + await queryRunner.commitTransaction() + logger.info(`commit send Transaction successful...`) + + /* + await EVENT_TRANSACTION_SEND(sender, recipient, transactionSend, transactionSend.amount) + + await EVENT_TRANSACTION_RECEIVE( + recipient, + sender, + transactionReceive, + transactionReceive.amount, + ) + */ + // trigger to send transaction via dlt-connector + // void sendTransactionsToDltConnector() + } catch (e) { + await queryRunner.rollbackTransaction() + throw new LogError('X-Com: send Transaction was not successful', e) + } finally { + await queryRunner.release() + releaseLock() + } + /* + void sendTransactionReceivedEmail({ + firstName: recipient.firstName, + lastName: recipient.lastName, + email: recipient.emailContact.email, + language: recipient.language, + senderFirstName: sender.firstName, + senderLastName: sender.lastName, + senderEmail: sender.emailContact.email, + transactionAmount: amount, + }) + if (transactionLink) { + void sendTransactionLinkRedeemedEmail({ + firstName: sender.firstName, + lastName: sender.lastName, + email: sender.emailContact.email, + language: sender.language, + senderFirstName: recipient.firstName, + senderLastName: recipient.lastName, + senderEmail: recipient.emailContact.email, + transactionAmount: amount, + transactionMemo: memo, + }) + } + logger.info(`finished executeTransaction successfully`) + } finally { + releaseLock() + } + */ + return true +} From bea2ceda4c87429b8255f6b58b94a1271334d225 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Thu, 31 Aug 2023 23:36:07 +0200 Subject: [PATCH 06/14] federation module now with Decimal and decay calculations as in backend --- .../federation/client/1_0/SendCoinsClient.ts | 2 +- .../resolver/util/processXComSendCoins.ts | 1 + .../util/settlePendingSenderTransaction.ts | 4 +- federation/src/config/index.ts | 9 +- .../1_0/resolver/SendCoinsResolver.test.ts | 107 ++++++++++++++++++ federation/src/index.ts | 4 +- federation/src/server/createServer.ts | 4 +- federation/test/extensions.ts | 37 ++++++ federation/test/helpers.test.ts | 7 ++ federation/test/helpers.ts | 66 +++++++++++ federation/tsconfig.json | 6 +- 11 files changed, 233 insertions(+), 14 deletions(-) create mode 100644 federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts create mode 100644 federation/test/extensions.ts create mode 100644 federation/test/helpers.test.ts create mode 100644 federation/test/helpers.ts diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index 90c3e2e20..7f1fad186 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -6,9 +6,9 @@ import { backendLogger as logger } from '@/server/logger' import { SendCoinsArgs } from './model/SendCoinsArgs' import { revertSendCoins } from './query/revertSendCoins' +import { revertSettledSendCoins } from './query/revertSettledSendCoins' import { settleSendCoins } from './query/settleSendCoins' import { voteForSendCoins } from './query/voteForSendCoins' -import { revertSettledSendCoins } from './query/revertSettledSendCoins' // eslint-disable-next-line camelcase export class SendCoinsClient { diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 40b7dd775..1fd526251 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -15,6 +15,7 @@ import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' import { calculateSenderBalance } from '@/util/calculateSenderBalance' import { fullName } from '@/util/utilities' + import { settlePendingSenderTransaction } from './settlePendingSenderTransaction' export async function processXComPendingSendCoins( diff --git a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts index 8db8ba695..e22276823 100644 --- a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts +++ b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts @@ -8,13 +8,13 @@ import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTrans import { Transaction as dbTransaction } from '@entity/Transaction' import { User as DbUser } from '@entity/User' +import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState' import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' +import { calculateSenderBalance } from '@/util/calculateSenderBalance' import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' import { getLastTransaction } from './getLastTransaction' -import { calculateSenderBalance } from '@/util/calculateSenderBalance' -import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState' export async function settlePendingSenderTransaction( homeCom: DbCommunity, diff --git a/federation/src/config/index.ts b/federation/src/config/index.ts index 1200bc1b8..f56f9decc 100644 --- a/federation/src/config/index.ts +++ b/federation/src/config/index.ts @@ -1,14 +1,15 @@ // ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env) +/* eslint-disable n/no-process-env */ + +import { Decimal } from 'decimal.js-light' import dotenv from 'dotenv' + dotenv.config() -/* -import Decimal from 'decimal.js-light' Decimal.set({ precision: 25, rounding: Decimal.ROUND_HALF_UP, }) -*/ const constants = { DB_VERSION: '0071-add-pending_transactions-table', @@ -62,7 +63,7 @@ const federation = { }, } -const CONFIG = { +export const CONFIG = { ...constants, ...server, ...database, diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts new file mode 100644 index 000000000..1400cfbeb --- /dev/null +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts @@ -0,0 +1,107 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +/* +import { createTestClient } from 'apollo-server-testing' +import { Community as DbCommunity } from '@entity/Community' +import CONFIG from '@/config' +import { cleanDB, testEnvironment } from '@test/helpers' +import { createServer } from '@/server/createServer' + +let query: any + +// to do: We need a setup for the tests that closes the connection +let con: any + +CONFIG.FEDERATION_API = '1_0' + +beforeAll(async () => { + const server = await createServer() + con = server.con + query = createTestClient(server.apollo).query + await cleanDB() + // DbCommunity.clear() +}) + +afterAll(async () => { + await con.close() +}) + +describe('SendCoinsResolver', () => { + const voteForSendCoinsMutation = ` + mutation ( + $communityReceiverIdentifier: String! + $userReceiverIdentifier: String! + $creationDate: Date! + $amount: Decimal! + $memo: String! + $communitySenderIdentifier: String! + $userSenderIdentifier: String! + $userSenderName: String! + ) { + voteForSendCoins( + communityReceiverIdentifier: $communityReceiverIdentifier + userReceiverIdentifier: $userReceiverIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + communitySenderIdentifier: $communitySenderIdentifier + userSenderIdentifier: $userSenderIdentifier + userSenderName: $userSenderName + ) + } +` + + describe('get 1st TX at all', () => { + let homeCom: DbCommunity + let foreignCom: DbCommunity + const varsPeterLustig = { + email: 'peter@lustig.de', + firstName: 'Peter', + lastName: 'Lustig', + language: 'de', + publisherId: 1234, + } + const varsBibiBloxberg = { + email: 'bibi@bloxberg.de', + firstName: 'Bibi', + lastName: 'Bloxberg', + language: 'de', + publisherId: 1234, + } + beforeEach(async () => { + homeCom = DbCommunity.create() + homeCom.foreign = false + homeCom.url = 'homeCommunity-url' + homeCom.name = 'Community-Name' + homeCom.description = 'Community-Description' + homeCom.creationDate = new Date() + homeCom.publicKey = Buffer.from('homeCommunity-publicKey') + await DbCommunity.insert(homeCom) + + foreignCom = DbCommunity.create() + foreignCom.foreign = true + foreignCom.url = 'foreignCommunity-url' + foreignCom.name = 'foreignCommunity-Name' + foreignCom.description = 'foreign Community-Description' + foreignCom.creationDate = new Date() + foreignCom.publicKey = Buffer.from('foreignCommunity-publicKey') + await DbCommunity.insert(foreignCom) + + }) + + it('returns public CommunityInfo', async () => { + await expect(query({ query: getPublicCommunityInfoQuery })).resolves.toMatchObject({ + data: { + getPublicCommunityInfo: { + name: 'Community-Name', + description: 'Community-Description', + creationDate: homeCom.creationDate?.toISOString(), + publicKey: expect.stringMatching('homeCommunity-publicKey'), + }, + }, + }) + }) + }) +}) +*/ diff --git a/federation/src/index.ts b/federation/src/index.ts index 997edb7aa..bdc66c87a 100644 --- a/federation/src/index.ts +++ b/federation/src/index.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import createServer from './server/createServer' +import { createServer } from './server/createServer' // config -import CONFIG from './config' +import { CONFIG } from './config' async function main() { // eslint-disable-next-line no-console diff --git a/federation/src/server/createServer.ts b/federation/src/server/createServer.ts index a47b6da7f..b79847254 100644 --- a/federation/src/server/createServer.ts +++ b/federation/src/server/createServer.ts @@ -13,7 +13,7 @@ import cors from './cors' import plugins from './plugins' // config -import CONFIG from '@/config' +import { CONFIG } from '@/config' // graphql import schema from '@/graphql/schema' @@ -33,7 +33,7 @@ import { Logger } from 'log4js' type ServerDef = { apollo: ApolloServer; app: Express; con: Connection } -const createServer = async ( +export const createServer = async ( // eslint-disable-next-line @typescript-eslint/no-explicit-any // context: any = serverContext, logger: Logger = apolloLogger, diff --git a/federation/test/extensions.ts b/federation/test/extensions.ts new file mode 100644 index 000000000..262a9bcdb --- /dev/null +++ b/federation/test/extensions.ts @@ -0,0 +1,37 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-empty-interface */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ + +import { Decimal } from 'decimal.js-light' + +expect.extend({ + decimalEqual(received, value) { + const pass = new Decimal(value).equals(received.toString()) + if (pass) { + return { + message: () => `expected ${received} to not equal ${value}`, + pass: true, + } + } else { + return { + message: () => `expected ${received} to equal ${value}`, + pass: false, + } + } + }, +}) + +interface CustomMatchers { + decimalEqual(value: number): R +} + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace jest { + interface Expect extends CustomMatchers {} + interface Matchers extends CustomMatchers {} + interface InverseAsymmetricMatchers extends CustomMatchers {} + } +} diff --git a/federation/test/helpers.test.ts b/federation/test/helpers.test.ts new file mode 100644 index 000000000..69d8f3fa4 --- /dev/null +++ b/federation/test/helpers.test.ts @@ -0,0 +1,7 @@ +import { contributionDateFormatter } from '@test/helpers' + +describe('contributionDateFormatter', () => { + it('formats the date correctly', () => { + expect(contributionDateFormatter(new Date('Thu Feb 29 2024 13:12:11'))).toEqual('2/29/2024') + }) +}) diff --git a/federation/test/helpers.ts b/federation/test/helpers.ts new file mode 100644 index 000000000..b5352e508 --- /dev/null +++ b/federation/test/helpers.ts @@ -0,0 +1,66 @@ +/* eslint-disable @typescript-eslint/unbound-method */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ + +import { createServer } from '@/server/createServer' +import { entities } from '@entity/index' +import { createTestClient } from 'apollo-server-testing' +import { logger } from './testSetup' + +// import { createServer } from '@/server/createServer' + +// import { i18n, logger } from './testSetup' + +export const headerPushMock = jest.fn((t) => { + context.token = t.value +}) + +const context = { + token: '', + setHeaders: { + push: headerPushMock, + forEach: jest.fn(), + }, + clientTimezoneOffset: 0, +} + +export const cleanDB = async () => { + // this only works as long we do not have foreign key constraints + for (const entity of entities) { + await resetEntity(entity) + } +} + +export const testEnvironment = async (testLogger = logger) => { + // , testI18n = i18n) => { + const server = await createServer(testLogger) // context, testLogger, testI18n) + const con = server.con + const testClient = createTestClient(server.apollo) + const mutate = testClient.mutate + const query = testClient.query + return { mutate, query, con } +} + +export const resetEntity = async (entity: any) => { + const items = await entity.find({ withDeleted: true }) + if (items.length > 0) { + const ids = items.map((e: any) => e.id) + await entity.delete(ids) + } +} + +export const resetToken = () => { + context.token = '' +} + +// format date string as it comes from the frontend for the contribution date +export const contributionDateFormatter = (date: Date): string => { + return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}` +} + +export const setClientTimezoneOffset = (offset: number): void => { + context.clientTimezoneOffset = offset +} diff --git a/federation/tsconfig.json b/federation/tsconfig.json index ba0d6fef3..50ce9d0c7 100644 --- a/federation/tsconfig.json +++ b/federation/tsconfig.json @@ -54,9 +54,9 @@ "@repository/*": ["src/typeorm/repository/*"], "@test/*": ["test/*"], /* common */ - "@common/*": ["../common/src/*"], - "@email/*": ["../common/scr/email/*"], - "@event/*": ["../common/src/event/*"], + // "@common/*": ["../common/src/*"], + // "@email/*": ["../common/scr/email/*"], + // "@event/*": ["../common/src/event/*"], /* external */ "@typeorm/*": ["../backend/src/typeorm/*", "../../backend/src/typeorm/*"], "@dbTools/*": ["../database/src/*", "../../database/build/src/*"], From 27ccb3b3e9fdd713f5d08504a982b5d1a7a4772f Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Tue, 12 Sep 2023 15:22:45 +0200 Subject: [PATCH 07/14] renaming after merge --- ...alculateRecepientBalance.ts => calculateRecipientBalance.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename federation/src/graphql/api/1_0/util/{calculateRecepientBalance.ts => calculateRecipientBalance.ts} (93%) diff --git a/federation/src/graphql/api/1_0/util/calculateRecepientBalance.ts b/federation/src/graphql/api/1_0/util/calculateRecipientBalance.ts similarity index 93% rename from federation/src/graphql/api/1_0/util/calculateRecepientBalance.ts rename to federation/src/graphql/api/1_0/util/calculateRecipientBalance.ts index 228862041..1b296f845 100644 --- a/federation/src/graphql/api/1_0/util/calculateRecepientBalance.ts +++ b/federation/src/graphql/api/1_0/util/calculateRecipientBalance.ts @@ -3,7 +3,7 @@ import { getLastTransaction } from '@/graphql/util/getLastTransaction' import { Decimal } from 'decimal.js-light' import { Decay } from '../model/Decay' -export async function calculateRecepientBalance( +export async function calculateRecipientBalance( userId: number, amount: Decimal, time: Date, From 0a61b61c63bfb0a25cffc0c67ca79cbaf687e11a Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Wed, 13 Sep 2023 02:46:47 +0200 Subject: [PATCH 08/14] add tests for settleSendCoins --- .../PublicCommunityInfoResolver.test.ts | 3 +- .../1_0/resolver/SendCoinsResolver.test.ts | 170 ++++++++++++++ .../api/1_0/resolver/SendCoinsResolver.ts | 212 ++++++++++-------- .../util/settlePendingReceiveTransaction.ts | 6 +- federation/test/helpers.ts | 40 +--- 5 files changed, 294 insertions(+), 137 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts index d18a30a7c..08544834f 100644 --- a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts @@ -4,11 +4,12 @@ import { createTestClient } from 'apollo-server-testing' import createServer from '@/server/createServer' import { Community as DbCommunity } from '@entity/Community' import CONFIG from '@/config' +import { Connection } from '@dbTools/typeorm' let query: any // to do: We need a setup for the tests that closes the connection -let con: any +let con: Connection CONFIG.FEDERATION_API = '1_0' diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts index 7b580b240..ce01a0f85 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts @@ -9,6 +9,11 @@ import { GraphQLError } from 'graphql' import { cleanDB, testEnvironment } from '@test/helpers' import { logger } from '@test/testSetup' import { Connection } from '@dbTools/typeorm' +import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' +import Decimal from 'decimal.js-light' +import { calculateRecipientBalance } from '../util/calculateRecipientBalance' +import { PendingTransactionState } from '../enum/PendingTransactionState' +import { TransactionTypeId } from '../enum/TransactionTypeId' let mutate: ApolloServerTestClient['mutate'], con: Connection // let query: ApolloServerTestClient['query'] @@ -89,6 +94,29 @@ describe('SendCoinsResolver', () => { } ` + const settleSendCoinsMutation = ` + mutation ( + $communityReceiverIdentifier: String! + $userReceiverIdentifier: String! + $creationDate: String! + $amount: Decimal! + $memo: String! + $communitySenderIdentifier: String! + $userSenderIdentifier: String! + $userSenderName: String! + ) { + settleSendCoins( + communityReceiverIdentifier: $communityReceiverIdentifier + userReceiverIdentifier: $userReceiverIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + communitySenderIdentifier: $communitySenderIdentifier + userSenderIdentifier: $userSenderIdentifier + userSenderName: $userSenderName + ) + } +` describe('voteForSendCoins', () => { let homeCom: DbCommunity let foreignCom: DbCommunity @@ -353,4 +381,146 @@ describe('SendCoinsResolver', () => { }) }) }) + + describe('settleSendCoins', () => { + let homeCom: DbCommunity + let foreignCom: DbCommunity + let sendUser: DbUser + let recipUser: DbUser + let pendingTx: DbPendingTransaction + const creationDate = new Date() + + beforeEach(async () => { + await cleanDB() + homeCom = DbCommunity.create() + homeCom.foreign = false + homeCom.url = 'homeCom-url' + homeCom.name = 'homeCom-Name' + homeCom.description = 'homeCom-Description' + homeCom.creationDate = new Date() + homeCom.publicKey = Buffer.from('homeCom-publicKey') + homeCom.communityUuid = 'homeCom-UUID' + await DbCommunity.insert(homeCom) + + foreignCom = DbCommunity.create() + foreignCom.foreign = true + foreignCom.url = 'foreignCom-url' + foreignCom.name = 'foreignCom-Name' + foreignCom.description = 'foreignCom-Description' + foreignCom.creationDate = new Date() + foreignCom.publicKey = Buffer.from('foreignCom-publicKey') + foreignCom.communityUuid = 'foreignCom-UUID' + await DbCommunity.insert(foreignCom) + + sendUser = DbUser.create() + sendUser.alias = 'sendUser-alias' + sendUser.firstName = 'sendUser-FirstName' + sendUser.gradidoID = 'sendUser-GradidoID' + sendUser.lastName = 'sendUser-LastName' + await DbUser.insert(sendUser) + + recipUser = DbUser.create() + recipUser.alias = 'recipUser-alias' + recipUser.firstName = 'recipUser-FirstName' + recipUser.gradidoID = 'recipUser-GradidoID' + recipUser.lastName = 'recipUser-LastName' + await DbUser.insert(recipUser) + + pendingTx = DbPendingTransaction.create() + pendingTx.amount = new Decimal(100) + pendingTx.balanceDate = creationDate + // pendingTx.balance = new Decimal(0) + pendingTx.linkedUserId = sendUser.id + pendingTx.linkedUserCommunityUuid = foreignCom.communityUuid + pendingTx.linkedUserGradidoID = sendUser.gradidoID + pendingTx.state = PendingTransactionState.NEW + pendingTx.typeId = TransactionTypeId.RECEIVE + pendingTx.memo = 'X-Com-TX memo' + pendingTx.userId = recipUser.id + pendingTx.userCommunityUuid = homeCom.communityUuid + pendingTx.userGradidoID = recipUser.gradidoID + await DbPendingTransaction.insert(pendingTx) + }) + + describe('unknown recipient community', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: settleSendCoinsMutation, + variables: { + communityReceiverIdentifier: 'invalid foreignCom', + userReceiverIdentifier: recipUser.gradidoID, + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + communitySenderIdentifier: foreignCom.communityUuid, + userSenderIdentifier: sendUser.gradidoID, + userSenderName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + errors: [new GraphQLError('settleSendCoins with wrong communityReceiverIdentifier')], + }), + ) + }) + }) + + describe('unknown recipient user', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: settleSendCoinsMutation, + variables: { + communityReceiverIdentifier: homeCom.communityUuid, + userReceiverIdentifier: 'invalid recipient', + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + communitySenderIdentifier: foreignCom.communityUuid, + userSenderIdentifier: sendUser.gradidoID, + userSenderName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + errors: [ + new GraphQLError( + 'settleSendCoins with unknown userReceiverIdentifier in the community=', + ), + ], + }), + ) + }) + }) + + describe('valid X-Com-TX settled', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: settleSendCoinsMutation, + variables: { + communityReceiverIdentifier: homeCom.communityUuid, + userReceiverIdentifier: recipUser.gradidoID, + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + communitySenderIdentifier: foreignCom.communityUuid, + userSenderIdentifier: sendUser.gradidoID, + userSenderName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + data: { + settleSendCoins: true, + }, + }), + ) + }) + }) + }) }) diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index c83c074b4..39c6f1e95 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -12,7 +12,7 @@ import { calculateRecipientBalance } from '../util/calculateRecipientBalance' import Decimal from 'decimal.js-light' import { fullName } from '@/graphql/util/fullName' import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction' -import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' +// import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction' @Resolver() @@ -176,50 +176,70 @@ export class SendCoinsResolver { userSenderName, }: SendCoinsArgs, ): Promise { - logger.debug(`settleSendCoins() via apiVersion=1_0 ...`) - try { - const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: communityReceiverIdentifier, - userGradidoID: userReceiverIdentifier, - state: PendingTransactionState.NEW, - typeId: TransactionTypeId.RECEIVE, - balanceDate: new Date(creationDate), - linkedUserCommunityUuid: communitySenderIdentifier, - linkedUserGradidoID: userSenderIdentifier, + logger.debug( + `settleSendCoins() via apiVersion=1_0 ...userCommunityUuid=${communityReceiverIdentifier}, userGradidoID=${userReceiverIdentifier}, balanceDate=${creationDate},amount=${amount.valueOf()}, memo=${memo}, linkedUserCommunityUuid = ${communitySenderIdentifier}, userSenderIdentifier=${userSenderIdentifier}, userSenderName=${userSenderName}`, + ) + // first check if receiver community is correct + const homeCom = await DbCommunity.findOneBy({ + communityUuid: communityReceiverIdentifier, + }) + if (!homeCom) { + throw new LogError( + `settleSendCoins with wrong communityReceiverIdentifier`, + communityReceiverIdentifier, + ) + } + // second check if receiver user exists in this community + const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) + if (!receiverUser) { + throw new LogError( + `settleSendCoins with unknown userReceiverIdentifier in the community=`, + homeCom.name, + ) + } + // try { + const pendingTx = await DbPendingTransaction.findOneBy({ + userCommunityUuid: communityReceiverIdentifier, + userGradidoID: userReceiverIdentifier, + state: PendingTransactionState.NEW, + typeId: TransactionTypeId.RECEIVE, + balanceDate: new Date(creationDate), + linkedUserCommunityUuid: communitySenderIdentifier, + linkedUserGradidoID: userSenderIdentifier, + }) + logger.debug('XCom: settleSendCoins found pendingTX=', pendingTx?.toString()) + if (pendingTx && pendingTx.amount.toString() === amount.toString() && pendingTx.memo === memo) { + logger.debug('XCom: settleSendCoins matching pendingTX for settlement...') + + const homeCom = await DbCommunity.findOneByOrFail({ + communityUuid: communityReceiverIdentifier, }) - logger.debug('XCom: settleSendCoins found pendingTX=', pendingTx) - if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { - logger.debug('XCom: settleSendCoins matching pendingTX for settlement...') + const receiverUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) - const homeCom = await DbCommunity.findOneByOrFail({ - communityUuid: communityReceiverIdentifier, - }) - const receiverUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) - - await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) - logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successfull`) - return true - } else { - logger.debug( - 'XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...', - ) - throw new LogError( - `Can't find in settlePendingReceiveTransaction the pending receiver TX for args=`, - communityReceiverIdentifier, - userReceiverIdentifier, - PendingTransactionState.NEW, - TransactionTypeId.RECEIVE, - creationDate, - amount, - memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, - ) - } + await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) + logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successfull`) + return true + } else { + logger.debug('XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...') + throw new LogError( + `Can't find in settlePendingReceiveTransaction the pending receiver TX for args=`, + communityReceiverIdentifier, + userReceiverIdentifier, + PendingTransactionState.NEW, + TransactionTypeId.RECEIVE, + creationDate, + amount, + memo, + communitySenderIdentifier, + userSenderIdentifier, + userSenderName, + ) + } +/* } catch (err) { throw new LogError(`Error in settlePendingReceiveTransaction: `, err) } +*/ } @Mutation(() => Boolean) @@ -236,64 +256,66 @@ export class SendCoinsResolver { userSenderName, }: SendCoinsArgs, ): Promise { - try { - logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`) - // first check if receiver community is correct - const homeCom = await DbCommunity.findOneBy({ - communityUuid: communityReceiverIdentifier, - }) - if (!homeCom) { - throw new LogError( - `revertSettledSendCoins with wrong communityReceiverIdentifier`, - communityReceiverIdentifier, - ) + // try { + logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`) + // first check if receiver community is correct + const homeCom = await DbCommunity.findOneBy({ + communityUuid: communityReceiverIdentifier, + }) + if (!homeCom) { + throw new LogError( + `revertSettledSendCoins with wrong communityReceiverIdentifier`, + communityReceiverIdentifier, + ) + } + // second check if receiver user exists in this community + const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) + if (!receiverUser) { + throw new LogError( + `revertSettledSendCoins with unknown userReceiverIdentifier in the community=`, + homeCom.name, + ) + } + const pendingTx = await DbPendingTransaction.findOneBy({ + userCommunityUuid: communityReceiverIdentifier, + userGradidoID: userReceiverIdentifier, + state: PendingTransactionState.SETTLED, + typeId: TransactionTypeId.RECEIVE, + balanceDate: new Date(creationDate), + linkedUserCommunityUuid: communitySenderIdentifier, + linkedUserGradidoID: userSenderIdentifier, + }) + logger.debug('XCom: revertSettledSendCoins found pendingTX=', pendingTx) + if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { + logger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...') + try { + await revertSettledReceiveTransaction(homeCom, receiverUser, pendingTx) + logger.debug('XCom: revertSettledSendCoins pendingTX successfully') + } catch (err) { + throw new LogError('Error in revertSettledSendCoins of receiver: ', err) } - // second check if receiver user exists in this community - const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) - if (!receiverUser) { - throw new LogError( - `revertSettledSendCoins with unknown userReceiverIdentifier in the community=`, - homeCom.name, - ) - } - const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: communityReceiverIdentifier, - userGradidoID: userReceiverIdentifier, - state: PendingTransactionState.SETTLED, - typeId: TransactionTypeId.RECEIVE, - balanceDate: new Date(creationDate), - linkedUserCommunityUuid: communitySenderIdentifier, - linkedUserGradidoID: userSenderIdentifier, - }) - logger.debug('XCom: revertSettledSendCoins found pendingTX=', pendingTx) - if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { - logger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...') - try { - await revertSettledReceiveTransaction(homeCom, receiverUser, pendingTx) - logger.debug('XCom: revertSettledSendCoins pendingTX successfully') - } catch (err) { - throw new LogError('Error in revertSettledSendCoins of receiver: ', err) - } - } else { - logger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...') - throw new LogError( - `Can't find in revertSettledSendCoins the pending receiver TX for args=`, - communityReceiverIdentifier, - userReceiverIdentifier, - PendingTransactionState.SETTLED, - TransactionTypeId.RECEIVE, - creationDate, - amount, - memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, - ) - } - logger.debug(`revertSendCoins()-1_0... successfull`) - return true + } else { + logger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...') + throw new LogError( + `Can't find in revertSettledSendCoins the pending receiver TX for args=`, + communityReceiverIdentifier, + userReceiverIdentifier, + PendingTransactionState.SETTLED, + TransactionTypeId.RECEIVE, + creationDate, + amount, + memo, + communitySenderIdentifier, + userSenderIdentifier, + userSenderName, + ) + } + logger.debug(`revertSendCoins()-1_0... successfull`) + return true +/* } catch (err) { throw new LogError(`Error in revertSendCoins: `, err) } +*/ } } diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index 0823e7d41..586c57998 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -15,7 +15,7 @@ import { federationLogger as logger } from '@/server/logger' import { getLastTransaction } from '@/graphql/util/getLastTransaction' import { TRANSACTIONS_LOCK } from '@/graphql/util/TRANSACTIONS_LOCK' -import { calculateRecepientBalance } from './calculateRecepientBalance' +import { calculateRecipientBalance } from './calculateRecipientBalance' export async function settlePendingReceiveTransaction( homeCom: DbCommunity, @@ -52,7 +52,7 @@ export async function settlePendingReceiveTransaction( const lastTransaction = await getLastTransaction(receiverUser.id) - if (lastTransaction?.id !== pendingTx.previous) { + if (lastTransaction === undefined && lastTransaction.id !== pendingTx.previous) { throw new LogError( `X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`, ) @@ -69,7 +69,7 @@ export async function settlePendingReceiveTransaction( transactionReceive.linkedUserGradidoID = pendingTx.linkedUserGradidoID transactionReceive.linkedUserName = pendingTx.linkedUserName transactionReceive.amount = pendingTx.amount - const receiveBalance = await calculateRecepientBalance( + const receiveBalance = await calculateRecipientBalance( receiverUser.id, pendingTx.amount, pendingTx.balanceDate, diff --git a/federation/test/helpers.ts b/federation/test/helpers.ts index 3c118ec43..3b05edf4d 100644 --- a/federation/test/helpers.ts +++ b/federation/test/helpers.ts @@ -4,45 +4,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-return */ +import { entities } from '@entity/index' +import { createTestClient } from 'apollo-server-testing' -/* <<<<<<< HEAD import { createServer } from '@/server/createServer' -import { entities } from '@entity/index' -import { createTestClient } from 'apollo-server-testing' -import { logger } from './testSetup' - -// import { createServer } from '@/server/createServer' - -// import { i18n, logger } from './testSetup' - -export const headerPushMock = jest.fn((t) => { - context.token = t.value -}) - -const context = { - token: '', - setHeaders: { - push: headerPushMock, - forEach: jest.fn(), - }, - clientTimezoneOffset: 0, -} - -export const cleanDB = async () => { - // this only works as long we do not have foreign key constraints - for (const entity of entities) { - await resetEntity(entity) - } -} - -export const testEnvironment = async (testLogger = logger) => { - // , testI18n = i18n) => { -======= -*/ -import { entities } from '@entity/index' -import { createTestClient } from 'apollo-server-testing' - -import createServer from '@/server/createServer' import { logger } from './testSetup' @@ -67,7 +32,6 @@ export const cleanDB = async () => { } export const testEnvironment = async (testLogger = logger) => { - // >>>>>>> refs/remotes/origin/master const server = await createServer(testLogger) // context, testLogger, testI18n) const con = server.con const testClient = createTestClient(server.apollo) From 3745c6a8e7fce0827805681241e12ad0610f1f99 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 15 Sep 2023 00:09:58 +0200 Subject: [PATCH 09/14] DB migration add communityUuid to transactions table --- backend/src/config/index.ts | 2 +- .../Transaction.ts | 158 ++++++++++++++++++ database/entity/Transaction.ts | 2 +- ...add_communityuuid_to_transactions_table.ts | 32 ++++ dht-node/src/config/index.ts | 2 +- federation/src/config/index.ts | 2 +- 6 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts create mode 100644 database/migrations/0072-add_communityuuid_to_transactions_table.ts diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 26e759c47..7c849f7d9 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -12,7 +12,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0071-add-pending_transactions-table', + DB_VERSION: '0072-add_communityuuid_to_transactions_table', DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info diff --git a/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts b/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts new file mode 100644 index 000000000..225ef9d23 --- /dev/null +++ b/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts @@ -0,0 +1,158 @@ +/* eslint-disable no-use-before-define */ +import { Decimal } from 'decimal.js-light' +import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm' +import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer' +import { Contribution } from '../Contribution' + +@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: false, + collation: 'utf8mb4_unicode_ci', + }) + userCommunityUuid: string + + @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(() => Transaction) + @JoinColumn({ name: 'previous' }) + previousTransaction?: Transaction | null +} diff --git a/database/entity/Transaction.ts b/database/entity/Transaction.ts index d08c84667..d1d7075a9 100644 --- a/database/entity/Transaction.ts +++ b/database/entity/Transaction.ts @@ -1 +1 @@ -export { Transaction } from './0070-add_dlt_transactions_table/Transaction' +export { Transaction } from './0072-add_communityuuid_to_transactions_table/Transaction' diff --git a/database/migrations/0072-add_communityuuid_to_transactions_table.ts b/database/migrations/0072-add_communityuuid_to_transactions_table.ts new file mode 100644 index 000000000..779da77c9 --- /dev/null +++ b/database/migrations/0072-add_communityuuid_to_transactions_table.ts @@ -0,0 +1,32 @@ +/* MIGRATION TO add users that have a transaction but do not exist */ + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `user_community_uuid` char(36) DEFAULT NULL NULL AFTER `user_id`;', + ) + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `linked_user_community_uuid` char(36) DEFAULT NULL NULL AFTER `linked_user_id`;', + ) + // read the community uuid of the homeCommunity + const result = await queryFn(`SELECT c.community_uuid from communities as c WHERE c.foreign = 0`) + // and if uuid exists enter the home_community_uuid for sender and recipient of each still existing transaction + if (result[0]) { + await queryFn( + `UPDATE transactions as t SET t.user_community_uuid = "${result[0].community_uuid}" WHERE t.user_id IS NOT NULL AND t.user_community_uuid IS NULL`, + ) + await queryFn( + `UPDATE transactions as t SET t.linked_user_community_uuid = "${result[0].community_uuid}" WHERE t.linked_user_id IS NOT NULL AND t.linked_user_community_uuid IS NULL`, + ) + } + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `user_community_uuid` char(36) NOT NULL AFTER `user_id`;', + ) +} + +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn('ALTER TABLE `transactions` DROP COLUMN `user_community_uuid`;') + await queryFn('ALTER TABLE `transactions` DROP COLUMN `linked_user_community_uuid`;') +} diff --git a/dht-node/src/config/index.ts b/dht-node/src/config/index.ts index 1d5d0672e..78755f280 100644 --- a/dht-node/src/config/index.ts +++ b/dht-node/src/config/index.ts @@ -4,7 +4,7 @@ import dotenv from 'dotenv' dotenv.config() const constants = { - DB_VERSION: '0071-add-pending_transactions-table', + DB_VERSION: '0072-add_communityuuid_to_transactions_table', LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info LOG_LEVEL: process.env.LOG_LEVEL || 'info', diff --git a/federation/src/config/index.ts b/federation/src/config/index.ts index 50d902219..e88267589 100644 --- a/federation/src/config/index.ts +++ b/federation/src/config/index.ts @@ -10,7 +10,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0071-add-pending_transactions-table', + DB_VERSION: '0072-add_communityuuid_to_transactions_table', DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info From a71877abed87ea89a6a715a8ae11f73120eff029 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 15 Sep 2023 21:10:38 +0200 Subject: [PATCH 10/14] tests for all sendCoins 2-Phase-Commit handshake requests --- .../client/1_0/model/SendCoinsArgs.ts | 10 +- .../client/1_0/query/revertSendCoins.ts | 20 +- .../1_0/query/revertSettledSendCoins.ts | 22 +- .../client/1_0/query/settleSendCoins.ts | 22 +- .../client/1_0/query/voteForSendCoins.ts | 20 +- .../Transaction.ts | 5 + federation/package.json | 4 +- .../graphql/api/1_0/model/SendCoinsArgs.ts | 10 +- .../1_0/resolver/SendCoinsResolver.test.ts | 581 ++++++++++-------- .../api/1_0/resolver/SendCoinsResolver.ts | 213 +++---- .../util/revertSettledReceiveTransaction.ts | 8 +- .../util/settlePendingReceiveTransaction.ts | 20 +- .../src/graphql/util/findUserByIdentifier.ts | 42 ++ federation/src/graphql/util/validateAlias.ts | 39 ++ federation/yarn.lock | 15 +- 15 files changed, 616 insertions(+), 415 deletions(-) create mode 100644 federation/src/graphql/util/findUserByIdentifier.ts create mode 100644 federation/src/graphql/util/validateAlias.ts diff --git a/backend/src/federation/client/1_0/model/SendCoinsArgs.ts b/backend/src/federation/client/1_0/model/SendCoinsArgs.ts index 545aab822..fb97da925 100644 --- a/backend/src/federation/client/1_0/model/SendCoinsArgs.ts +++ b/backend/src/federation/client/1_0/model/SendCoinsArgs.ts @@ -4,10 +4,10 @@ import { ArgsType, Field } from 'type-graphql' @ArgsType() export class SendCoinsArgs { @Field(() => String) - communityReceiverIdentifier: string + recipientCommunityUuid: string @Field(() => String) - userReceiverIdentifier: string + recipientUserIdentifier: string @Field(() => String) creationDate: string @@ -19,11 +19,11 @@ export class SendCoinsArgs { memo: string @Field(() => String) - communitySenderIdentifier: string + senderCommunityUuid: string @Field(() => String) - userSenderIdentifier: string + senderUserUuid: string @Field(() => String) - userSenderName: string + senderUserName: string } diff --git a/backend/src/federation/client/1_0/query/revertSendCoins.ts b/backend/src/federation/client/1_0/query/revertSendCoins.ts index 881107cb4..9cc23fe64 100644 --- a/backend/src/federation/client/1_0/query/revertSendCoins.ts +++ b/backend/src/federation/client/1_0/query/revertSendCoins.ts @@ -2,24 +2,24 @@ import { gql } from 'graphql-request' export const revertSendCoins = gql` mutation ( - $communityReceiverIdentifier: String! - $userReceiverIdentifier: String! + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! $creationDate: String! $amount: Decimal! $memo: String! - $communitySenderIdentifier: String! - $userSenderIdentifier: String! - $userSenderName: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! ) { revertSendCoins( - communityReceiverIdentifier: $communityReceiverIdentifier - userReceiverIdentifier: $userReceiverIdentifier + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier creationDate: $creationDate amount: $amount memo: $memo - communitySenderIdentifier: $communitySenderIdentifier - userSenderIdentifier: $userSenderIdentifier - userSenderName: $userSenderName + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName ) } ` diff --git a/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts b/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts index 0d4447507..74cdbd867 100644 --- a/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts +++ b/backend/src/federation/client/1_0/query/revertSettledSendCoins.ts @@ -2,24 +2,24 @@ import { gql } from 'graphql-request' export const revertSettledSendCoins = gql` mutation ( - $communityReceiverIdentifier: String! - $userReceiverIdentifier: String! - $creationDate: Date! + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! + $creationDate: String! $amount: Decimal! $memo: String! - $communitySenderIdentifier: String! - $userSenderIdentifier: String! - $userSenderName: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! ) { revertSettledSendCoins( - communityReceiverIdentifier: $communityReceiverIdentifier - userReceiverIdentifier: $userReceiverIdentifier + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier creationDate: $creationDate amount: $amount memo: $memo - communitySenderIdentifier: $communitySenderIdentifier - userSenderIdentifier: $userSenderIdentifier - userSenderName: $userSenderName + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName ) } ` diff --git a/backend/src/federation/client/1_0/query/settleSendCoins.ts b/backend/src/federation/client/1_0/query/settleSendCoins.ts index 99f784bc7..1696a0900 100644 --- a/backend/src/federation/client/1_0/query/settleSendCoins.ts +++ b/backend/src/federation/client/1_0/query/settleSendCoins.ts @@ -2,24 +2,24 @@ import { gql } from 'graphql-request' export const settleSendCoins = gql` mutation ( - $communityReceiverIdentifier: String! - $userReceiverIdentifier: String! - $creationDate: Date! + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! + $creationDate: String! $amount: Decimal! $memo: String! - $communitySenderIdentifier: String! - $userSenderIdentifier: String! - $userSenderName: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! ) { settleSendCoins( - communityReceiverIdentifier: $communityReceiverIdentifier - userReceiverIdentifier: $userReceiverIdentifier + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier creationDate: $creationDate amount: $amount memo: $memo - communitySenderIdentifier: $communitySenderIdentifier - userSenderIdentifier: $userSenderIdentifier - userSenderName: $userSenderName + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName ) } ` diff --git a/backend/src/federation/client/1_0/query/voteForSendCoins.ts b/backend/src/federation/client/1_0/query/voteForSendCoins.ts index f0f75198f..0f16ff32b 100644 --- a/backend/src/federation/client/1_0/query/voteForSendCoins.ts +++ b/backend/src/federation/client/1_0/query/voteForSendCoins.ts @@ -2,24 +2,24 @@ import { gql } from 'graphql-request' export const voteForSendCoins = gql` mutation ( - $communityReceiverIdentifier: String! - $userReceiverIdentifier: String! + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! $creationDate: String! $amount: Decimal! $memo: String! - $communitySenderIdentifier: String! - $userSenderIdentifier: String! - $userSenderName: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! ) { voteForSendCoins( - communityReceiverIdentifier: $communityReceiverIdentifier - userReceiverIdentifier: $userReceiverIdentifier + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier creationDate: $creationDate amount: $amount memo: $memo - communitySenderIdentifier: $communitySenderIdentifier - userSenderIdentifier: $userSenderIdentifier - userSenderName: $userSenderName + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName ) } ` diff --git a/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts b/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts index 225ef9d23..3efa78ada 100644 --- a/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts +++ b/database/entity/0072-add_communityuuid_to_transactions_table/Transaction.ts @@ -3,6 +3,7 @@ import { Decimal } from 'decimal.js-light' import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm' import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer' import { Contribution } from '../Contribution' +import { DltTransaction } from '../DltTransaction' @Entity('transactions') export class Transaction extends BaseEntity { @@ -152,6 +153,10 @@ export class Transaction extends BaseEntity { @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 diff --git a/federation/package.json b/federation/package.json index eb78d6be0..aaeaff08d 100644 --- a/federation/package.json +++ b/federation/package.json @@ -16,6 +16,7 @@ "lint": "eslint --max-warnings=0 --ext .js,.ts ." }, "dependencies": { + "@types/uuid": "8.3.4", "apollo-server-express": "^2.25.2", "await-semaphore": "0.1.3", "class-validator": "^0.13.2", @@ -28,7 +29,8 @@ "lodash.clonedeep": "^4.5.0", "log4js": "^6.7.1", "reflect-metadata": "^0.1.13", - "type-graphql": "^1.1.1" + "type-graphql": "^1.1.1", + "uuid": "8.3.2" }, "devDependencies": { "@types/express": "4.17.12", diff --git a/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts b/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts index 545aab822..fb97da925 100644 --- a/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts +++ b/federation/src/graphql/api/1_0/model/SendCoinsArgs.ts @@ -4,10 +4,10 @@ import { ArgsType, Field } from 'type-graphql' @ArgsType() export class SendCoinsArgs { @Field(() => String) - communityReceiverIdentifier: string + recipientCommunityUuid: string @Field(() => String) - userReceiverIdentifier: string + recipientUserIdentifier: string @Field(() => String) creationDate: string @@ -19,11 +19,11 @@ export class SendCoinsArgs { memo: string @Field(() => String) - communitySenderIdentifier: string + senderCommunityUuid: string @Field(() => String) - userSenderIdentifier: string + senderUserUuid: string @Field(() => String) - userSenderName: string + senderUserName: string } diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts index ce01a0f85..5120391d6 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts @@ -4,6 +4,7 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { Community as DbCommunity } from '@entity/Community' import CONFIG from '@/config' import { User as DbUser } from '@entity/User' +import { UserContact as DbUserContact } from '@entity/UserContact' import { fullName } from '@/graphql/util/fullName' import { GraphQLError } from 'graphql' import { cleanDB, testEnvironment } from '@test/helpers' @@ -11,9 +12,9 @@ import { logger } from '@test/testSetup' import { Connection } from '@dbTools/typeorm' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import Decimal from 'decimal.js-light' -import { calculateRecipientBalance } from '../util/calculateRecipientBalance' import { PendingTransactionState } from '../enum/PendingTransactionState' import { TransactionTypeId } from '../enum/TransactionTypeId' +import { Transaction as DbTransaction } from '@entity/Transaction' let mutate: ApolloServerTestClient['mutate'], con: Connection // let query: ApolloServerTestClient['query'] @@ -26,18 +27,18 @@ let testEnv: { CONFIG.FEDERATION_API = '1_0' +let homeCom: DbCommunity +let foreignCom: DbCommunity +let sendUser: DbUser +let sendContact: DbUserContact +let recipUser: DbUser +let recipContact: DbUserContact + beforeAll(async () => { testEnv = await testEnvironment(logger) mutate = testEnv.mutate // query = testEnv.query con = testEnv.con - - // const server = await createServer() - // con = server.con - // query = createTestClient(server.apollo).query - // mutate = createTestClient(server.apollo).mutate - // DbCommunity.clear() - // DbUser.clear() await cleanDB() }) @@ -48,118 +49,103 @@ afterAll(async () => { describe('SendCoinsResolver', () => { const voteForSendCoinsMutation = ` - mutation ( - $communityReceiverIdentifier: String! - $userReceiverIdentifier: String! - $creationDate: String! - $amount: Decimal! - $memo: String! - $communitySenderIdentifier: String! - $userSenderIdentifier: String! - $userSenderName: String! - ) { - voteForSendCoins( - communityReceiverIdentifier: $communityReceiverIdentifier - userReceiverIdentifier: $userReceiverIdentifier - creationDate: $creationDate - amount: $amount - memo: $memo - communitySenderIdentifier: $communitySenderIdentifier - userSenderIdentifier: $userSenderIdentifier - userSenderName: $userSenderName - ) - } -` - const revertSendCoinsMutation = ` - mutation ( - $communityReceiverIdentifier: String! - $userReceiverIdentifier: String! - $creationDate: String! - $amount: Decimal! - $memo: String! - $communitySenderIdentifier: String! - $userSenderIdentifier: String! - $userSenderName: String! - ) { - revertSendCoins( - communityReceiverIdentifier: $communityReceiverIdentifier - userReceiverIdentifier: $userReceiverIdentifier - creationDate: $creationDate - amount: $amount - memo: $memo - communitySenderIdentifier: $communitySenderIdentifier - userSenderIdentifier: $userSenderIdentifier - userSenderName: $userSenderName - ) - } -` + mutation ( + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! + $creationDate: String! + $amount: Decimal! + $memo: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! + ) { + voteForSendCoins( + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName + ) + }` const settleSendCoinsMutation = ` mutation ( - $communityReceiverIdentifier: String! - $userReceiverIdentifier: String! + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! $creationDate: String! $amount: Decimal! $memo: String! - $communitySenderIdentifier: String! - $userSenderIdentifier: String! - $userSenderName: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! ) { settleSendCoins( - communityReceiverIdentifier: $communityReceiverIdentifier - userReceiverIdentifier: $userReceiverIdentifier + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier creationDate: $creationDate amount: $amount memo: $memo - communitySenderIdentifier: $communitySenderIdentifier - userSenderIdentifier: $userSenderIdentifier - userSenderName: $userSenderName + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName ) - } -` + }` + + beforeEach(async () => { + await cleanDB() + homeCom = DbCommunity.create() + homeCom.foreign = false + homeCom.url = 'homeCom-url' + homeCom.name = 'homeCom-Name' + homeCom.description = 'homeCom-Description' + homeCom.creationDate = new Date() + homeCom.publicKey = Buffer.from('homeCom-publicKey') + homeCom.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894eba' + await DbCommunity.insert(homeCom) + + foreignCom = DbCommunity.create() + foreignCom.foreign = true + foreignCom.url = 'foreignCom-url' + foreignCom.name = 'foreignCom-Name' + foreignCom.description = 'foreignCom-Description' + foreignCom.creationDate = new Date() + foreignCom.publicKey = Buffer.from('foreignCom-publicKey') + foreignCom.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894ebb' + await DbCommunity.insert(foreignCom) + + sendUser = DbUser.create() + sendUser.alias = 'sendUser-alias' + sendUser.firstName = 'sendUser-FirstName' + sendUser.gradidoID = '56a55482-909e-46a4-bfa2-cd025e894ebc' + sendUser.lastName = 'sendUser-LastName' + await DbUser.insert(sendUser) + + sendContact = await newEmailContact('send.user@email.de', sendUser.id) + sendContact = await DbUserContact.save(sendContact) + + sendUser.emailContact = sendContact + sendUser.emailId = sendContact.id + await DbUser.save(sendUser) + + recipUser = DbUser.create() + recipUser.alias = 'recipUser-alias' + recipUser.firstName = 'recipUser-FirstName' + recipUser.gradidoID = '56a55482-909e-46a4-bfa2-cd025e894ebd' + recipUser.lastName = 'recipUser-LastName' + await DbUser.insert(recipUser) + + recipContact = await newEmailContact('recip.user@email.de', recipUser.id) + recipContact = await DbUserContact.save(recipContact) + + recipUser.emailContact = recipContact + recipUser.emailId = recipContact.id + await DbUser.save(recipUser) + }) + describe('voteForSendCoins', () => { - let homeCom: DbCommunity - let foreignCom: DbCommunity - let sendUser: DbUser - let recipUser: DbUser - - beforeEach(async () => { - await cleanDB() - homeCom = DbCommunity.create() - homeCom.foreign = false - homeCom.url = 'homeCom-url' - homeCom.name = 'homeCom-Name' - homeCom.description = 'homeCom-Description' - homeCom.creationDate = new Date() - homeCom.publicKey = Buffer.from('homeCom-publicKey') - homeCom.communityUuid = 'homeCom-UUID' - await DbCommunity.insert(homeCom) - - foreignCom = DbCommunity.create() - foreignCom.foreign = true - foreignCom.url = 'foreignCom-url' - foreignCom.name = 'foreignCom-Name' - foreignCom.description = 'foreignCom-Description' - foreignCom.creationDate = new Date() - foreignCom.publicKey = Buffer.from('foreignCom-publicKey') - foreignCom.communityUuid = 'foreignCom-UUID' - await DbCommunity.insert(foreignCom) - - sendUser = DbUser.create() - sendUser.alias = 'sendUser-alias' - sendUser.firstName = 'sendUser-FirstName' - sendUser.gradidoID = 'sendUser-GradidoID' - sendUser.lastName = 'sendUser-LastName' - await DbUser.insert(sendUser) - - recipUser = DbUser.create() - recipUser.alias = 'recipUser-alias' - recipUser.firstName = 'recipUser-FirstName' - recipUser.gradidoID = 'recipUser-GradidoID' - recipUser.lastName = 'recipUser-LastName' - await DbUser.insert(recipUser) - }) - describe('unknown recipient community', () => { it('throws an error', async () => { jest.clearAllMocks() @@ -167,19 +153,19 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: voteForSendCoinsMutation, variables: { - communityReceiverIdentifier: 'invalid foreignCom', - userReceiverIdentifier: recipUser.gradidoID, + recipientCommunityUuid: 'invalid foreignCom', + recipientUserIdentifier: recipUser.gradidoID, creationDate: new Date().toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: homeCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: homeCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( expect.objectContaining({ - errors: [new GraphQLError('voteForSendCoins with wrong communityReceiverIdentifier')], + errors: [new GraphQLError('voteForSendCoins with wrong recipientCommunityUuid')], }), ) }) @@ -192,21 +178,21 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: voteForSendCoinsMutation, variables: { - communityReceiverIdentifier: foreignCom.communityUuid, - userReceiverIdentifier: 'invalid recipient', + recipientCommunityUuid: foreignCom.communityUuid, + recipientUserIdentifier: 'invalid recipient', creationDate: new Date().toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: homeCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: homeCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( expect.objectContaining({ errors: [ new GraphQLError( - 'voteForSendCoins with unknown userReceiverIdentifier in the community=', + 'voteForSendCoins with unknown recipientUserIdentifier in the community=', ), ], }), @@ -221,14 +207,14 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: voteForSendCoinsMutation, variables: { - communityReceiverIdentifier: foreignCom.communityUuid, - userReceiverIdentifier: recipUser.gradidoID, + recipientCommunityUuid: foreignCom.communityUuid, + recipientUserIdentifier: recipUser.gradidoID, creationDate: new Date().toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: homeCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: homeCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( @@ -243,59 +229,43 @@ describe('SendCoinsResolver', () => { }) describe('revertSendCoins', () => { - let homeCom: DbCommunity - let foreignCom: DbCommunity - let sendUser: DbUser - let recipUser: DbUser + const revertSendCoinsMutation = ` + mutation ( + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! + $creationDate: String! + $amount: Decimal! + $memo: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! + ) { + revertSendCoins( + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName + ) + }` + const creationDate = new Date() beforeEach(async () => { - await cleanDB() - homeCom = DbCommunity.create() - homeCom.foreign = false - homeCom.url = 'homeCom-url' - homeCom.name = 'homeCom-Name' - homeCom.description = 'homeCom-Description' - homeCom.creationDate = new Date() - homeCom.publicKey = Buffer.from('homeCom-publicKey') - homeCom.communityUuid = 'homeCom-UUID' - await DbCommunity.insert(homeCom) - - foreignCom = DbCommunity.create() - foreignCom.foreign = true - foreignCom.url = 'foreignCom-url' - foreignCom.name = 'foreignCom-Name' - foreignCom.description = 'foreignCom-Description' - foreignCom.creationDate = new Date() - foreignCom.publicKey = Buffer.from('foreignCom-publicKey') - foreignCom.communityUuid = 'foreignCom-UUID' - await DbCommunity.insert(foreignCom) - - sendUser = DbUser.create() - sendUser.alias = 'sendUser-alias' - sendUser.firstName = 'sendUser-FirstName' - sendUser.gradidoID = 'sendUser-GradidoID' - sendUser.lastName = 'sendUser-LastName' - await DbUser.insert(sendUser) - - recipUser = DbUser.create() - recipUser.alias = 'recipUser-alias' - recipUser.firstName = 'recipUser-FirstName' - recipUser.gradidoID = 'recipUser-GradidoID' - recipUser.lastName = 'recipUser-LastName' - await DbUser.insert(recipUser) - await mutate({ mutation: voteForSendCoinsMutation, variables: { - communityReceiverIdentifier: foreignCom.communityUuid, - userReceiverIdentifier: recipUser.gradidoID, + recipientCommunityUuid: foreignCom.communityUuid, + recipientUserIdentifier: recipUser.gradidoID, creationDate: creationDate.toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: homeCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: homeCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }) }) @@ -307,19 +277,19 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: revertSendCoinsMutation, variables: { - communityReceiverIdentifier: 'invalid foreignCom', - userReceiverIdentifier: recipUser.gradidoID, + recipientCommunityUuid: 'invalid foreignCom', + recipientUserIdentifier: recipUser.gradidoID, creationDate: creationDate.toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: homeCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: homeCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( expect.objectContaining({ - errors: [new GraphQLError('revertSendCoins with wrong communityReceiverIdentifier')], + errors: [new GraphQLError('revertSendCoins with wrong recipientCommunityUuid')], }), ) }) @@ -332,21 +302,21 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: revertSendCoinsMutation, variables: { - communityReceiverIdentifier: foreignCom.communityUuid, - userReceiverIdentifier: 'invalid recipient', + recipientCommunityUuid: foreignCom.communityUuid, + recipientUserIdentifier: 'invalid recipient', creationDate: creationDate.toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: homeCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: homeCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( expect.objectContaining({ errors: [ new GraphQLError( - 'revertSendCoins with unknown userReceiverIdentifier in the community=', + 'revertSendCoins with unknown recipientUserIdentifier in the community=', ), ], }), @@ -361,14 +331,14 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: revertSendCoinsMutation, variables: { - communityReceiverIdentifier: foreignCom.communityUuid, - userReceiverIdentifier: recipUser.gradidoID, + recipientCommunityUuid: foreignCom.communityUuid, + recipientUserIdentifier: recipUser.gradidoID, creationDate: creationDate.toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: homeCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: homeCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( @@ -383,61 +353,26 @@ describe('SendCoinsResolver', () => { }) describe('settleSendCoins', () => { - let homeCom: DbCommunity - let foreignCom: DbCommunity - let sendUser: DbUser - let recipUser: DbUser let pendingTx: DbPendingTransaction const creationDate = new Date() beforeEach(async () => { - await cleanDB() - homeCom = DbCommunity.create() - homeCom.foreign = false - homeCom.url = 'homeCom-url' - homeCom.name = 'homeCom-Name' - homeCom.description = 'homeCom-Description' - homeCom.creationDate = new Date() - homeCom.publicKey = Buffer.from('homeCom-publicKey') - homeCom.communityUuid = 'homeCom-UUID' - await DbCommunity.insert(homeCom) - - foreignCom = DbCommunity.create() - foreignCom.foreign = true - foreignCom.url = 'foreignCom-url' - foreignCom.name = 'foreignCom-Name' - foreignCom.description = 'foreignCom-Description' - foreignCom.creationDate = new Date() - foreignCom.publicKey = Buffer.from('foreignCom-publicKey') - foreignCom.communityUuid = 'foreignCom-UUID' - await DbCommunity.insert(foreignCom) - - sendUser = DbUser.create() - sendUser.alias = 'sendUser-alias' - sendUser.firstName = 'sendUser-FirstName' - sendUser.gradidoID = 'sendUser-GradidoID' - sendUser.lastName = 'sendUser-LastName' - await DbUser.insert(sendUser) - - recipUser = DbUser.create() - recipUser.alias = 'recipUser-alias' - recipUser.firstName = 'recipUser-FirstName' - recipUser.gradidoID = 'recipUser-GradidoID' - recipUser.lastName = 'recipUser-LastName' - await DbUser.insert(recipUser) - pendingTx = DbPendingTransaction.create() pendingTx.amount = new Decimal(100) pendingTx.balanceDate = creationDate // pendingTx.balance = new Decimal(0) pendingTx.linkedUserId = sendUser.id - pendingTx.linkedUserCommunityUuid = foreignCom.communityUuid + if (foreignCom.communityUuid) { + pendingTx.linkedUserCommunityUuid = foreignCom.communityUuid + } pendingTx.linkedUserGradidoID = sendUser.gradidoID pendingTx.state = PendingTransactionState.NEW pendingTx.typeId = TransactionTypeId.RECEIVE pendingTx.memo = 'X-Com-TX memo' pendingTx.userId = recipUser.id - pendingTx.userCommunityUuid = homeCom.communityUuid + if (homeCom.communityUuid) { + pendingTx.userCommunityUuid = homeCom.communityUuid + } pendingTx.userGradidoID = recipUser.gradidoID await DbPendingTransaction.insert(pendingTx) }) @@ -449,19 +384,19 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: settleSendCoinsMutation, variables: { - communityReceiverIdentifier: 'invalid foreignCom', - userReceiverIdentifier: recipUser.gradidoID, + recipientCommunityUuid: 'invalid foreignCom', + recipientUserIdentifier: recipUser.gradidoID, creationDate: creationDate.toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: foreignCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: foreignCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( expect.objectContaining({ - errors: [new GraphQLError('settleSendCoins with wrong communityReceiverIdentifier')], + errors: [new GraphQLError('settleSendCoins with wrong recipientCommunityUuid')], }), ) }) @@ -474,21 +409,21 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: settleSendCoinsMutation, variables: { - communityReceiverIdentifier: homeCom.communityUuid, - userReceiverIdentifier: 'invalid recipient', + recipientCommunityUuid: homeCom.communityUuid, + recipientUserIdentifier: 'invalid recipient', creationDate: creationDate.toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: foreignCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: foreignCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( expect.objectContaining({ errors: [ new GraphQLError( - 'settleSendCoins with unknown userReceiverIdentifier in the community=', + 'settleSendCoins with unknown recipientUserIdentifier in the community=', ), ], }), @@ -503,14 +438,14 @@ describe('SendCoinsResolver', () => { await mutate({ mutation: settleSendCoinsMutation, variables: { - communityReceiverIdentifier: homeCom.communityUuid, - userReceiverIdentifier: recipUser.gradidoID, + recipientCommunityUuid: homeCom.communityUuid, + recipientUserIdentifier: recipUser.gradidoID, creationDate: creationDate.toISOString(), amount: 100, memo: 'X-Com-TX memo', - communitySenderIdentifier: foreignCom.communityUuid, - userSenderIdentifier: sendUser.gradidoID, - userSenderName: fullName(sendUser.firstName, sendUser.lastName), + senderCommunityUuid: foreignCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), }, }), ).toEqual( @@ -523,4 +458,164 @@ describe('SendCoinsResolver', () => { }) }) }) + + describe('revertSettledSendCoins', () => { + const revertSettledSendCoinsMutation = ` + mutation ( + $recipientCommunityUuid: String! + $recipientUserIdentifier: String! + $creationDate: String! + $amount: Decimal! + $memo: String! + $senderCommunityUuid: String! + $senderUserUuid: String! + $senderUserName: String! + ) { + revertSettledSendCoins( + recipientCommunityUuid: $recipientCommunityUuid + recipientUserIdentifier: $recipientUserIdentifier + creationDate: $creationDate + amount: $amount + memo: $memo + senderCommunityUuid: $senderCommunityUuid + senderUserUuid: $senderUserUuid + senderUserName: $senderUserName + ) + }` + + let pendingTx: DbPendingTransaction + let settledTx: DbTransaction + const creationDate = new Date() + + beforeEach(async () => { + pendingTx = DbPendingTransaction.create() + pendingTx.amount = new Decimal(100) + pendingTx.balanceDate = creationDate + // pendingTx.balance = new Decimal(0) + pendingTx.linkedUserId = sendUser.id + if (foreignCom.communityUuid) { + pendingTx.linkedUserCommunityUuid = foreignCom.communityUuid + } + pendingTx.linkedUserGradidoID = sendUser.gradidoID + pendingTx.linkedUserName = fullName(sendUser.firstName, sendUser.lastName) + pendingTx.state = PendingTransactionState.SETTLED + pendingTx.typeId = TransactionTypeId.RECEIVE + pendingTx.memo = 'X-Com-TX memo' + pendingTx.userId = recipUser.id + if (homeCom.communityUuid) { + pendingTx.userCommunityUuid = homeCom.communityUuid + } + pendingTx.userGradidoID = recipUser.gradidoID + await DbPendingTransaction.insert(pendingTx) + + settledTx = DbTransaction.create() + settledTx.amount = new Decimal(100) + settledTx.balanceDate = creationDate + // pendingTx.balance = new Decimal(0) + settledTx.linkedUserId = sendUser.id + settledTx.linkedUserCommunityUuid = foreignCom.communityUuid + settledTx.linkedUserGradidoID = sendUser.gradidoID + settledTx.linkedUserName = fullName(sendUser.firstName, sendUser.lastName) + settledTx.typeId = TransactionTypeId.RECEIVE + settledTx.memo = 'X-Com-TX memo' + settledTx.userId = recipUser.id + if (homeCom.communityUuid) { + settledTx.userCommunityUuid = homeCom.communityUuid + } + settledTx.userGradidoID = recipUser.gradidoID + await DbTransaction.insert(settledTx) + }) + + describe('unknown recipient community', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: revertSettledSendCoinsMutation, + variables: { + recipientCommunityUuid: 'invalid foreignCom', + recipientUserIdentifier: recipUser.gradidoID, + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + senderCommunityUuid: foreignCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + errors: [new GraphQLError('revertSettledSendCoins with wrong recipientCommunityUuid')], + }), + ) + }) + }) + + describe('unknown recipient user', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: revertSettledSendCoinsMutation, + variables: { + recipientCommunityUuid: homeCom.communityUuid, + recipientUserIdentifier: 'invalid recipient', + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + senderCommunityUuid: foreignCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + errors: [ + new GraphQLError( + 'revertSettledSendCoins with unknown recipientUserIdentifier in the community=', + ), + ], + }), + ) + }) + }) + + describe('valid X-Com-TX settled', () => { + it('throws an error', async () => { + jest.clearAllMocks() + expect( + await mutate({ + mutation: revertSettledSendCoinsMutation, + variables: { + recipientCommunityUuid: homeCom.communityUuid, + recipientUserIdentifier: recipUser.gradidoID, + creationDate: creationDate.toISOString(), + amount: 100, + memo: 'X-Com-TX memo', + senderCommunityUuid: foreignCom.communityUuid, + senderUserUuid: sendUser.gradidoID, + senderUserName: fullName(sendUser.firstName, sendUser.lastName), + }, + }), + ).toEqual( + expect.objectContaining({ + data: { + revertSettledSendCoins: true, + }, + }), + ) + }) + }) + }) }) + +async function newEmailContact(email: string, userId: number): Promise { + const emailContact = new DbUserContact() + emailContact.email = email + emailContact.userId = userId + emailContact.type = 'EMAIL' + emailContact.emailChecked = false + emailContact.emailOptInTypeId = 1 + emailContact.emailVerificationCode = '1' + userId + return emailContact +} diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 39c6f1e95..4196e015c 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -4,7 +4,6 @@ import { federationLogger as logger } from '@/server/logger' import { Community as DbCommunity } from '@entity/Community' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import { SendCoinsArgs } from '../model/SendCoinsArgs' -import { User as DbUser } from '@entity/User' import { LogError } from '@/server/LogError' import { PendingTransactionState } from '../enum/PendingTransactionState' import { TransactionTypeId } from '../enum/TransactionTypeId' @@ -14,6 +13,7 @@ import { fullName } from '@/graphql/util/fullName' import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction' // import { checkTradingLevel } from '@/graphql/util/checkTradingLevel' import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction' +import { findUserByIdentifier } from '@/graphql/util/findUserByIdentifier' @Resolver() // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -22,33 +22,46 @@ export class SendCoinsResolver { async voteForSendCoins( @Args() { - communityReceiverIdentifier, - userReceiverIdentifier, + recipientCommunityUuid, + recipientUserIdentifier, creationDate, amount, memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, + senderCommunityUuid, + senderUserUuid, + senderUserName, }: SendCoinsArgs, - ): Promise { - logger.debug(`voteForSendCoins() via apiVersion=1_0 ...`) - let result: string | null = null + ): Promise { + logger.debug( + `voteForSendCoins() via apiVersion=1_0 ...`, + recipientCommunityUuid, + recipientUserIdentifier, + creationDate, + amount.toString(), + memo, + senderCommunityUuid, + senderUserUuid, + senderUserName, + ) + let result: string // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: communityReceiverIdentifier, + communityUuid: recipientCommunityUuid, }) if (!homeCom) { throw new LogError( - `voteForSendCoins with wrong communityReceiverIdentifier`, - communityReceiverIdentifier, + `voteForSendCoins with wrong recipientCommunityUuid`, + recipientCommunityUuid, ) } - // second check if receiver user exists in this community - const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) - if (!receiverUser) { + let receiverUser + try { + // second check if receiver user exists in this community + receiverUser = await findUserByIdentifier(recipientUserIdentifier) + } catch (err) { + logger.error('Error in findUserByIdentifier:', err) throw new LogError( - `voteForSendCoins with unknown userReceiverIdentifier in the community=`, + `voteForSendCoins with unknown recipientUserIdentifier in the community=`, homeCom.name, ) } @@ -57,21 +70,21 @@ export class SendCoinsResolver { const receiveBalance = await calculateRecipientBalance(receiverUser.id, amount, txDate) const pendingTx = DbPendingTransaction.create() pendingTx.amount = amount - pendingTx.balance = receiveBalance ? receiveBalance.balance : new Decimal(0) + pendingTx.balance = receiveBalance ? receiveBalance.balance : amount pendingTx.balanceDate = txDate pendingTx.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) pendingTx.decayStart = receiveBalance ? receiveBalance.decay.start : null pendingTx.creationDate = new Date() - pendingTx.linkedUserCommunityUuid = communitySenderIdentifier - pendingTx.linkedUserGradidoID = userSenderIdentifier - pendingTx.linkedUserName = userSenderName + pendingTx.linkedUserCommunityUuid = senderCommunityUuid + pendingTx.linkedUserGradidoID = senderUserUuid + pendingTx.linkedUserName = senderUserName pendingTx.memo = memo pendingTx.previous = receiveBalance ? receiveBalance.lastTransactionId : null pendingTx.state = PendingTransactionState.NEW pendingTx.typeId = TransactionTypeId.RECEIVE pendingTx.userId = receiverUser.id - pendingTx.userCommunityUuid = communityReceiverIdentifier - pendingTx.userGradidoID = userReceiverIdentifier + pendingTx.userCommunityUuid = recipientCommunityUuid + pendingTx.userGradidoID = receiverUser.gradidoID pendingTx.userName = fullName(receiverUser.firstName, receiverUser.lastName) await DbPendingTransaction.insert(pendingTx) @@ -87,44 +100,47 @@ export class SendCoinsResolver { async revertSendCoins( @Args() { - communityReceiverIdentifier, - userReceiverIdentifier, + recipientCommunityUuid, + recipientUserIdentifier, creationDate, amount, memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, + senderCommunityUuid, + senderUserUuid, + senderUserName, }: SendCoinsArgs, ): Promise { logger.debug(`revertSendCoins() via apiVersion=1_0 ...`) // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: communityReceiverIdentifier, + communityUuid: recipientCommunityUuid, }) if (!homeCom) { throw new LogError( - `revertSendCoins with wrong communityReceiverIdentifier`, - communityReceiverIdentifier, + `revertSendCoins with wrong recipientCommunityUuid`, + recipientCommunityUuid, ) } - // second check if receiver user exists in this community - const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) - if (!receiverUser) { + let receiverUser + try { + // second check if receiver user exists in this community + receiverUser = await findUserByIdentifier(recipientUserIdentifier) + } catch (err) { + logger.error('Error in findUserByIdentifier:', err) throw new LogError( - `revertSendCoins with unknown userReceiverIdentifier in the community=`, + `revertSendCoins with unknown recipientUserIdentifier in the community=`, homeCom.name, ) } try { const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: communityReceiverIdentifier, - userGradidoID: userReceiverIdentifier, + userCommunityUuid: recipientCommunityUuid, + userGradidoID: receiverUser.gradidoID, state: PendingTransactionState.NEW, typeId: TransactionTypeId.RECEIVE, balanceDate: new Date(creationDate), - linkedUserCommunityUuid: communitySenderIdentifier, - linkedUserGradidoID: userSenderIdentifier, + linkedUserCommunityUuid: senderCommunityUuid, + linkedUserGradidoID: senderUserUuid, }) logger.debug('XCom: revertSendCoins found pendingTX=', pendingTx) if (pendingTx && pendingTx.amount.toString() === amount.toString()) { @@ -143,16 +159,16 @@ export class SendCoinsResolver { ) throw new LogError( `Can't find in revertSendCoins the pending receiver TX for args=`, - communityReceiverIdentifier, - userReceiverIdentifier, + recipientCommunityUuid, + recipientUserIdentifier, PendingTransactionState.NEW, TransactionTypeId.RECEIVE, creationDate, amount, memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, + senderCommunityUuid, + senderUserUuid, + senderUserName, ) } logger.debug(`revertSendCoins()-1_0... successfull`) @@ -166,56 +182,53 @@ export class SendCoinsResolver { async settleSendCoins( @Args() { - communityReceiverIdentifier, - userReceiverIdentifier, + recipientCommunityUuid, + recipientUserIdentifier, creationDate, amount, memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, + senderCommunityUuid, + senderUserUuid, + senderUserName, }: SendCoinsArgs, ): Promise { logger.debug( - `settleSendCoins() via apiVersion=1_0 ...userCommunityUuid=${communityReceiverIdentifier}, userGradidoID=${userReceiverIdentifier}, balanceDate=${creationDate},amount=${amount.valueOf()}, memo=${memo}, linkedUserCommunityUuid = ${communitySenderIdentifier}, userSenderIdentifier=${userSenderIdentifier}, userSenderName=${userSenderName}`, + `settleSendCoins() via apiVersion=1_0 ...userCommunityUuid=${recipientCommunityUuid}, userGradidoID=${recipientUserIdentifier}, balanceDate=${creationDate},amount=${amount.valueOf()}, memo=${memo}, linkedUserCommunityUuid = ${senderCommunityUuid}, userSenderIdentifier=${senderUserUuid}, userSenderName=${senderUserName}`, ) // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: communityReceiverIdentifier, + communityUuid: recipientCommunityUuid, }) if (!homeCom) { throw new LogError( - `settleSendCoins with wrong communityReceiverIdentifier`, - communityReceiverIdentifier, + `settleSendCoins with wrong recipientCommunityUuid`, + recipientCommunityUuid, ) } - // second check if receiver user exists in this community - const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) - if (!receiverUser) { + let receiverUser + try { + // second check if receiver user exists in this community + receiverUser = await findUserByIdentifier(recipientUserIdentifier) + } catch (err) { + logger.error('Error in findUserByIdentifier:', err) throw new LogError( - `settleSendCoins with unknown userReceiverIdentifier in the community=`, + `settleSendCoins with unknown recipientUserIdentifier in the community=`, homeCom.name, ) } - // try { const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: communityReceiverIdentifier, - userGradidoID: userReceiverIdentifier, + userCommunityUuid: recipientCommunityUuid, + userGradidoID: receiverUser.gradidoID, state: PendingTransactionState.NEW, typeId: TransactionTypeId.RECEIVE, balanceDate: new Date(creationDate), - linkedUserCommunityUuid: communitySenderIdentifier, - linkedUserGradidoID: userSenderIdentifier, + linkedUserCommunityUuid: senderCommunityUuid, + linkedUserGradidoID: senderUserUuid, }) logger.debug('XCom: settleSendCoins found pendingTX=', pendingTx?.toString()) if (pendingTx && pendingTx.amount.toString() === amount.toString() && pendingTx.memo === memo) { logger.debug('XCom: settleSendCoins matching pendingTX for settlement...') - const homeCom = await DbCommunity.findOneByOrFail({ - communityUuid: communityReceiverIdentifier, - }) - const receiverUser = await DbUser.findOneByOrFail({ gradidoID: userReceiverIdentifier }) - await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx) logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successfull`) return true @@ -223,70 +236,67 @@ export class SendCoinsResolver { logger.debug('XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...') throw new LogError( `Can't find in settlePendingReceiveTransaction the pending receiver TX for args=`, - communityReceiverIdentifier, - userReceiverIdentifier, + recipientCommunityUuid, + recipientUserIdentifier, PendingTransactionState.NEW, TransactionTypeId.RECEIVE, creationDate, amount, memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, + senderCommunityUuid, + senderUserUuid, + senderUserName, ) } -/* - } catch (err) { - throw new LogError(`Error in settlePendingReceiveTransaction: `, err) - } -*/ } @Mutation(() => Boolean) async revertSettledSendCoins( @Args() { - communityReceiverIdentifier, - userReceiverIdentifier, + recipientCommunityUuid, + recipientUserIdentifier, creationDate, amount, memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, + senderCommunityUuid, + senderUserUuid, + senderUserName, }: SendCoinsArgs, ): Promise { - // try { logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`) // first check if receiver community is correct const homeCom = await DbCommunity.findOneBy({ - communityUuid: communityReceiverIdentifier, + communityUuid: recipientCommunityUuid, }) if (!homeCom) { throw new LogError( - `revertSettledSendCoins with wrong communityReceiverIdentifier`, - communityReceiverIdentifier, + `revertSettledSendCoins with wrong recipientCommunityUuid`, + recipientCommunityUuid, ) } - // second check if receiver user exists in this community - const receiverUser = await DbUser.findOneBy({ gradidoID: userReceiverIdentifier }) - if (!receiverUser) { + let receiverUser + try { + // second check if receiver user exists in this community + receiverUser = await findUserByIdentifier(recipientUserIdentifier) + } catch (err) { + logger.error('Error in findUserByIdentifier:', err) throw new LogError( - `revertSettledSendCoins with unknown userReceiverIdentifier in the community=`, + `revertSettledSendCoins with unknown recipientUserIdentifier in the community=`, homeCom.name, ) } const pendingTx = await DbPendingTransaction.findOneBy({ - userCommunityUuid: communityReceiverIdentifier, - userGradidoID: userReceiverIdentifier, + userCommunityUuid: recipientCommunityUuid, + userGradidoID: receiverUser.gradidoID, state: PendingTransactionState.SETTLED, typeId: TransactionTypeId.RECEIVE, balanceDate: new Date(creationDate), - linkedUserCommunityUuid: communitySenderIdentifier, - linkedUserGradidoID: userSenderIdentifier, + linkedUserCommunityUuid: senderCommunityUuid, + linkedUserGradidoID: senderUserUuid, }) logger.debug('XCom: revertSettledSendCoins found pendingTX=', pendingTx) - if (pendingTx && pendingTx.amount === amount && pendingTx.memo === memo) { + if (pendingTx && pendingTx.amount.toString() === amount.toString() && pendingTx.memo === memo) { logger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...') try { await revertSettledReceiveTransaction(homeCom, receiverUser, pendingTx) @@ -298,24 +308,19 @@ export class SendCoinsResolver { logger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...') throw new LogError( `Can't find in revertSettledSendCoins the pending receiver TX for args=`, - communityReceiverIdentifier, - userReceiverIdentifier, + recipientCommunityUuid, + recipientUserIdentifier, PendingTransactionState.SETTLED, TransactionTypeId.RECEIVE, creationDate, amount, memo, - communitySenderIdentifier, - userSenderIdentifier, - userSenderName, + senderCommunityUuid, + senderUserUuid, + senderUserName, ) } logger.debug(`revertSendCoins()-1_0... successfull`) return true -/* - } catch (err) { - throw new LogError(`Error in revertSendCoins: `, err) - } -*/ } } diff --git a/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts index ecf615667..6eb933f6a 100644 --- a/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts @@ -64,10 +64,10 @@ export async function revertSettledReceiveTransaction( if ( lastTransaction && lastTransaction.balance === pendingTx.balance && - lastTransaction.balanceDate === pendingTx.balanceDate && + lastTransaction.balanceDate.toISOString() === pendingTx.balanceDate.toISOString() && lastTransaction.userGradidoID === pendingTx.userGradidoID && lastTransaction.userName === pendingTx.userName && - lastTransaction.amount === pendingTx.amount && + lastTransaction.amount.toString() === pendingTx.amount.toString() && lastTransaction.memo === pendingTx.memo && lastTransaction.linkedUserGradidoID === pendingTx.linkedUserGradidoID && lastTransaction.linkedUserName === pendingTx.linkedUserName @@ -83,7 +83,9 @@ export async function revertSettledReceiveTransaction( } else { // TODO: if the last TX is not equivelant to pendingTX, the transactions must be corrected in EXPERT-MODE throw new LogError( - `X-Com: missmatching transaction order for revert settlement! lastTransation=${lastTransaction} != pendingTx=${pendingTx}`, + `X-Com: missmatching transaction order for revert settlement!`, + lastTransaction, + pendingTx, ) } diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index 586c57998..5c522061c 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -16,6 +16,7 @@ import { federationLogger as logger } from '@/server/logger' import { getLastTransaction } from '@/graphql/util/getLastTransaction' import { TRANSACTIONS_LOCK } from '@/graphql/util/TRANSACTIONS_LOCK' import { calculateRecipientBalance } from './calculateRecipientBalance' +import Decimal from 'decimal.js-light' export async function settlePendingReceiveTransaction( homeCom: DbCommunity, @@ -52,7 +53,7 @@ export async function settlePendingReceiveTransaction( const lastTransaction = await getLastTransaction(receiverUser.id) - if (lastTransaction === undefined && lastTransaction.id !== pendingTx.previous) { + if (lastTransaction !== null && lastTransaction.id !== pendingTx.previous) { throw new LogError( `X-Com: missmatching transaction order! lastTransationId=${lastTransaction?.id} != pendingTx.previous=${pendingTx.previous}`, ) @@ -63,9 +64,11 @@ export async function settlePendingReceiveTransaction( transactionReceive.typeId = pendingTx.typeId transactionReceive.memo = pendingTx.memo transactionReceive.userId = pendingTx.userId + transactionReceive.userCommunityUuid = pendingTx.userCommunityUuid transactionReceive.userGradidoID = pendingTx.userGradidoID transactionReceive.userName = pendingTx.userName transactionReceive.linkedUserId = pendingTx.linkedUserId + transactionReceive.linkedUserCommunityUuid = pendingTx.linkedUserCommunityUuid transactionReceive.linkedUserGradidoID = pendingTx.linkedUserGradidoID transactionReceive.linkedUserName = pendingTx.linkedUserName transactionReceive.amount = pendingTx.amount @@ -74,16 +77,19 @@ export async function settlePendingReceiveTransaction( pendingTx.amount, pendingTx.balanceDate, ) - if (receiveBalance?.balance !== pendingTx.balance) { + if ( + receiveBalance !== null && + receiveBalance.balance.toString() !== pendingTx.balance.toString() + ) { throw new LogError( - `X-Com: Calculation-Error on receiver balance: receiveBalance=${receiveBalance?.balance}, pendingTx.balance=${pendingTx.balance}`, + `X-Com: Calculation-Error on receiver balance: receiveBalance=${receiveBalance.balance}, pendingTx.balance=${pendingTx.balance}`, ) } - transactionReceive.balance = pendingTx.balance + transactionReceive.balance = receiveBalance ? receiveBalance.balance : pendingTx.amount transactionReceive.balanceDate = pendingTx.balanceDate - transactionReceive.decay = pendingTx.decay - transactionReceive.decayStart = pendingTx.decayStart - transactionReceive.previous = pendingTx.previous + transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) + transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null + transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null transactionReceive.linkedTransactionId = pendingTx.linkedTransactionId await queryRunner.manager.insert(dbTransaction, transactionReceive) logger.debug(`receive Transaction inserted: ${dbTransaction}`) diff --git a/federation/src/graphql/util/findUserByIdentifier.ts b/federation/src/graphql/util/findUserByIdentifier.ts new file mode 100644 index 000000000..96c9eb458 --- /dev/null +++ b/federation/src/graphql/util/findUserByIdentifier.ts @@ -0,0 +1,42 @@ +import { User as DbUser } from '@entity/User' +import { UserContact as DbUserContact } from '@entity/UserContact' +import { validate, version } from 'uuid' + +import { LogError } from '@/server/LogError' + +import { VALID_ALIAS_REGEX } from './validateAlias' + +export const findUserByIdentifier = async (identifier: string): Promise => { + let user: DbUser | null + if (validate(identifier) && version(identifier) === 4) { + user = await DbUser.findOne({ where: { gradidoID: identifier }, relations: ['emailContact'] }) + if (!user) { + throw new LogError('No user found to given identifier', identifier) + } + } else if (/^.{2,}@.{2,}\..{2,}$/.exec(identifier)) { + const userContact = await DbUserContact.findOne({ + where: { + email: identifier, + emailChecked: true, + }, + relations: ['user'], + }) + if (!userContact) { + throw new LogError('No user with this credentials', identifier) + } + if (!userContact.user) { + throw new LogError('No user to given contact', identifier) + } + user = userContact.user + user.emailContact = userContact + } else if (VALID_ALIAS_REGEX.exec(identifier)) { + user = await DbUser.findOne({ where: { alias: identifier }, relations: ['emailContact'] }) + if (!user) { + throw new LogError('No user found to given identifier', identifier) + } + } else { + throw new LogError('Unknown identifier type', identifier) + } + + return user +} diff --git a/federation/src/graphql/util/validateAlias.ts b/federation/src/graphql/util/validateAlias.ts new file mode 100644 index 000000000..cd2d8fe1f --- /dev/null +++ b/federation/src/graphql/util/validateAlias.ts @@ -0,0 +1,39 @@ +import { Raw } from '@dbTools/typeorm' +import { User as DbUser } from '@entity/User' + +import { LogError } from '@/server/LogError' + +export const VALID_ALIAS_REGEX = /^(?=.{3,20}$)[a-zA-Z0-9]+(?:[_-][a-zA-Z0-9]+?)*$/ + +const RESERVED_ALIAS = [ + 'admin', + 'email', + 'gast', + 'gdd', + 'gradido', + 'guest', + 'home', + 'root', + 'support', + 'temp', + 'tmp', + 'tmp', + 'user', + 'usr', + 'var', +] + +export const validateAlias = async (alias: string): Promise => { + if (alias.length < 3) throw new LogError('Given alias is too short', alias) + if (alias.length > 20) throw new LogError('Given alias is too long', alias) + if (!alias.match(VALID_ALIAS_REGEX)) throw new LogError('Invalid characters in alias', alias) + if (RESERVED_ALIAS.includes(alias.toLowerCase())) + throw new LogError('Alias is not allowed', alias) + const aliasInUse = await DbUser.find({ + where: { alias: Raw((a) => `LOWER(${a}) = "${alias.toLowerCase()}"`) }, + }) + if (aliasInUse.length !== 0) { + throw new LogError('Alias already in use', alias) + } + return true +} diff --git a/federation/yarn.lock b/federation/yarn.lock index 758e7e4ce..87bd7f0d4 100644 --- a/federation/yarn.lock +++ b/federation/yarn.lock @@ -1057,6 +1057,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/uuid@8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + "@types/ws@^7.0.0": version "7.4.7" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" @@ -5442,16 +5447,16 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== +uuid@8.3.2, uuid@^8.0.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + uuid@^3.1.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.0.0: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" From 9a20a85ec9d665005f40fca710c6c313c1b5f17b Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 15 Sep 2023 21:20:42 +0200 Subject: [PATCH 11/14] changes base on renaming of SendCoinArgs --- .../resolver/util/processXComSendCoins.ts | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts index 19b29d47a..3bae8acd3 100644 --- a/backend/src/graphql/resolver/util/processXComSendCoins.ts +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -51,18 +51,16 @@ export async function processXComPendingSendCoins( // eslint-disable-next-line camelcase if (client instanceof V1_0_SendCoinsClient) { const args = new SendCoinsArgs() - args.communityReceiverIdentifier = receiverCom.communityUuid + args.recipientCommunityUuid = receiverCom.communityUuid ? receiverCom.communityUuid : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID - args.userReceiverIdentifier = recipient.gradidoID + args.recipientUserIdentifier = recipient.gradidoID args.creationDate = creationDate.toISOString() args.amount = amount args.memo = memo - args.communitySenderIdentifier = senderCom.communityUuid - ? senderCom.communityUuid - : 'homeCom-UUID' - args.userSenderIdentifier = sender.gradidoID - args.userSenderName = fullName(sender.firstName, sender.lastName) + args.senderCommunityUuid = senderCom.communityUuid ? senderCom.communityUuid : 'homeCom-UUID' + args.senderUserUuid = sender.gradidoID + args.senderUserName = fullName(sender.firstName, sender.lastName) logger.debug(`X-Com: ready for voteForSendCoins with args=`, args) const recipientName = await client.voteForSendCoins(args) logger.debug(`X-Com: returnd from voteForSendCoins:`, recipientName) @@ -159,19 +157,19 @@ export async function processXComCommittingSendCoins( // eslint-disable-next-line camelcase if (client instanceof V1_0_SendCoinsClient) { const args = new SendCoinsArgs() - args.communityReceiverIdentifier = pendingTx.linkedUserCommunityUuid + args.recipientCommunityUuid = pendingTx.linkedUserCommunityUuid ? pendingTx.linkedUserCommunityUuid : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID if (pendingTx.linkedUserGradidoID) { - args.userReceiverIdentifier = pendingTx.linkedUserGradidoID + args.recipientUserIdentifier = pendingTx.linkedUserGradidoID } - args.creationDate = pendingTx.balanceDate + args.creationDate = pendingTx.balanceDate.toISOString() args.amount = pendingTx.amount args.memo = pendingTx.memo - args.communitySenderIdentifier = pendingTx.userCommunityUuid - args.userSenderIdentifier = pendingTx.userGradidoID + args.senderCommunityUuid = pendingTx.userCommunityUuid + args.senderUserUuid = pendingTx.userGradidoID if (pendingTx.userName) { - args.userSenderName = pendingTx.userName + args.senderUserName = pendingTx.userName } logger.debug(`X-Com: ready for settleSendCoins with args=`, args) const acknoleged = await client.settleSendCoins(args) From b88ff9b3dfe10828a5a7ddc6547b44104dcf5c7a Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Mon, 18 Sep 2023 21:49:08 +0200 Subject: [PATCH 12/14] correct seeding with home community --- .../graphql/resolver/ContributionResolver.ts | 5 +++ backend/src/seeds/community/index.ts | 34 +++++++++++++++++++ backend/src/seeds/index.ts | 4 +++ backend/src/util/virtualTransactions.ts | 4 +++ 4 files changed, 47 insertions(+) create mode 100644 backend/src/seeds/community/index.ts diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 1ffa53b27..af313a9a7 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -1,4 +1,5 @@ import { IsNull, getConnection } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' import { Contribution as DbContribution } from '@entity/Contribution' import { ContributionMessage } from '@entity/ContributionMessage' import { Transaction as DbTransaction } from '@entity/Transaction' @@ -447,6 +448,7 @@ export class ContributionResolver { if (user.deletedAt) { throw new LogError('Can not confirm contribution since the user was deleted') } + const homeCom = await DbCommunity.findOneOrFail({ where: { foreign: false } }) const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false) validateContribution( creations, @@ -480,6 +482,9 @@ export class ContributionResolver { transaction.typeId = TransactionTypeId.CREATION transaction.memo = contribution.memo transaction.userId = contribution.userId + if (homeCom.communityUuid) { + transaction.userCommunityUuid = homeCom.communityUuid + } transaction.userGradidoID = user.gradidoID transaction.userName = fullName(user.firstName, user.lastName) transaction.previous = lastTransaction ? lastTransaction.id : null diff --git a/backend/src/seeds/community/index.ts b/backend/src/seeds/community/index.ts new file mode 100644 index 000000000..d66c02c21 --- /dev/null +++ b/backend/src/seeds/community/index.ts @@ -0,0 +1,34 @@ +import { Community as DbCommunity } from '@entity/Community' +import { v4 as uuidv4 } from 'uuid' + +import { CONFIG } from '@/config' + +export async function writeHomeCommunityEntry(): Promise { + try { + // check for existing homeCommunity entry + let homeCom = await DbCommunity.findOne({ where: { foreign: false } }) + if (homeCom) { + // simply update the existing entry, but it MUST keep the ID and UUID because of possible relations + homeCom.publicKey = Buffer.from('public-key-data-seeding') // keyPair.publicKey + // homeCom.privateKey = keyPair.secretKey + homeCom.url = 'http://localhost/api/' + homeCom.name = CONFIG.COMMUNITY_NAME + homeCom.description = CONFIG.COMMUNITY_DESCRIPTION + await DbCommunity.save(homeCom) + } else { + // insert a new homecommunity entry including a new ID and a new but ensured unique UUID + homeCom = new DbCommunity() + homeCom.foreign = false + homeCom.publicKey = Buffer.from('public-key-data-seeding') // keyPair.publicKey + // homeCom.privateKey = keyPair.secretKey + homeCom.communityUuid = uuidv4() // await newCommunityUuid() + homeCom.url = 'http://localhost/api/' + homeCom.name = CONFIG.COMMUNITY_NAME + homeCom.description = CONFIG.COMMUNITY_DESCRIPTION + homeCom.creationDate = new Date() + await DbCommunity.insert(homeCom) + } + } catch (err) { + throw new Error(`Seeding: Error writing HomeCommunity-Entry: ${err}`) + } +} diff --git a/backend/src/seeds/index.ts b/backend/src/seeds/index.ts index bc7950f26..b22409759 100644 --- a/backend/src/seeds/index.ts +++ b/backend/src/seeds/index.ts @@ -12,6 +12,7 @@ import { CONFIG } from '@/config' import { createServer } from '@/server/createServer' import { backendLogger as logger } from '@/server/logger' +import { writeHomeCommunityEntry } from './community' import { contributionLinks } from './contributionLink/index' import { creations } from './creation/index' import { contributionLinkFactory } from './factory/contributionLink' @@ -57,6 +58,9 @@ const run = async () => { await cleanDB() logger.info('##seed## clean database successful...') + // seed home community + await writeHomeCommunityEntry() + // seed the standard users for (const user of users) { await userFactory(seedClient, user) diff --git a/backend/src/util/virtualTransactions.ts b/backend/src/util/virtualTransactions.ts index a10e566d1..74a065d55 100644 --- a/backend/src/util/virtualTransactions.ts +++ b/backend/src/util/virtualTransactions.ts @@ -58,6 +58,8 @@ const virtualLinkTransaction = ( userName: null, linkedUserGradidoID: null, linkedUserName: null, + userCommunityUuid: '', + linkedUserCommunityUuid: null, } return new Transaction(linkDbTransaction, user) } @@ -92,6 +94,8 @@ const virtualDecayTransaction = ( userName: null, linkedUserGradidoID: null, linkedUserName: null, + userCommunityUuid: '', + linkedUserCommunityUuid: null, } return new Transaction(decayDbTransaction, user) } From 8b2bbfdb2bcc10c3a8f7e5534e206bed697c6866 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Mon, 18 Sep 2023 22:28:54 +0200 Subject: [PATCH 13/14] local transactions with homeCom_uuid --- .../resolver/TransactionLinkResolver.ts | 6 +++++ .../graphql/resolver/TransactionResolver.ts | 23 +++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 32ec5a654..667e168b5 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -1,6 +1,7 @@ import { randomBytes } from 'crypto' import { getConnection } from '@dbTools/typeorm' +import { Community as DbCommunity } from '@entity/Community' import { Contribution as DbContribution } from '@entity/Contribution' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' import { Transaction as DbTransaction } from '@entity/Transaction' @@ -165,6 +166,7 @@ export class TransactionLinkResolver { @Ctx() context: Context, ): Promise { const clientTimezoneOffset = getClientTimezoneOffset(context) + const homeCom = await DbCommunity.findOneOrFail({ where: { foreign: false } }) const user = getUser(context) if (code.match(/^CL-/)) { @@ -271,6 +273,9 @@ export class TransactionLinkResolver { transaction.typeId = TransactionTypeId.CREATION transaction.memo = contribution.memo transaction.userId = contribution.userId + if (homeCom.communityUuid) { + transaction.userCommunityUuid = homeCom.communityUuid + } transaction.userGradidoID = user.gradidoID transaction.userName = fullName(user.firstName, user.lastName) transaction.previous = lastTransaction ? lastTransaction.id : null @@ -343,6 +348,7 @@ export class TransactionLinkResolver { transactionLink.memo, linkedUser, user, + homeCom, transactionLink, ) await EVENT_TRANSACTION_LINK_REDEEM( diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 2dff4af79..7560ba306 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { getConnection, In, IsNull } from '@dbTools/typeorm' +import { Community as dbCommunity } from '@entity/Community' import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' import { Transaction as dbTransaction } from '@entity/Transaction' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' @@ -46,12 +47,13 @@ export const executeTransaction = async ( memo: string, sender: dbUser, recipient: dbUser, + homeCom: dbCommunity, transactionLink?: dbTransactionLink | null, ): Promise => { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() try { - logger.info('executeTransaction', amount, memo, sender, recipient) + logger.info('executeTransaction', amount, memo, homeCom, sender, recipient) const openSenderPendingTx = await DbPendingTransaction.count({ where: [ @@ -96,9 +98,15 @@ export const executeTransaction = async ( transactionSend.typeId = TransactionTypeId.SEND transactionSend.memo = memo transactionSend.userId = sender.id + if (homeCom.communityUuid) { + transactionSend.userCommunityUuid = homeCom.communityUuid + } transactionSend.userGradidoID = sender.gradidoID transactionSend.userName = fullName(sender.firstName, sender.lastName) transactionSend.linkedUserId = recipient.id + if (homeCom.communityUuid) { + transactionSend.linkedUserCommunityUuid = homeCom.communityUuid + } transactionSend.linkedUserGradidoID = recipient.gradidoID transactionSend.linkedUserName = fullName(recipient.firstName, recipient.lastName) transactionSend.amount = amount.mul(-1) @@ -116,9 +124,15 @@ export const executeTransaction = async ( transactionReceive.typeId = TransactionTypeId.RECEIVE transactionReceive.memo = memo transactionReceive.userId = recipient.id + if (homeCom.communityUuid) { + transactionReceive.userCommunityUuid = homeCom.communityUuid + } transactionReceive.userGradidoID = recipient.gradidoID transactionReceive.userName = fullName(recipient.firstName, recipient.lastName) transactionReceive.linkedUserId = sender.id + if (homeCom.communityUuid) { + transactionReceive.linkedUserCommunityUuid = homeCom.communityUuid + } transactionReceive.linkedUserGradidoID = sender.gradidoID transactionReceive.linkedUserName = fullName(sender.firstName, sender.lastName) transactionReceive.amount = amount @@ -348,12 +362,13 @@ export class TransactionResolver { @Mutation(() => Boolean) async sendCoins( @Args() - { /* recipientCommunityIdentifier, */ recipientIdentifier, amount, memo }: TransactionSendArgs, + { recipientCommunityIdentifier, recipientIdentifier, amount, memo }: TransactionSendArgs, @Ctx() context: Context, ): Promise { logger.info( - `sendCoins(recipientIdentifier=${recipientIdentifier}, amount=${amount}, memo=${memo})`, + `sendCoins(recipientCommunityIdentifier=${recipientCommunityIdentifier}, recipientIdentifier=${recipientIdentifier}, amount=${amount}, memo=${memo})`, ) + const homeCom = await dbCommunity.findOneOrFail({ where: { foreign: false } }) const senderUser = getUser(context) @@ -363,7 +378,7 @@ export class TransactionResolver { throw new LogError('The recipient user was not found', recipientUser) } - await executeTransaction(amount, memo, senderUser, recipientUser) + await executeTransaction(amount, memo, senderUser, recipientUser, homeCom) logger.info('successful executeTransaction', amount, memo, senderUser, recipientUser) return true } From 90c51596a1d00564ccfa3d5fe737accd67440dec Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Mon, 18 Sep 2023 23:16:24 +0200 Subject: [PATCH 14/14] linting --- backend/src/seeds/community/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/seeds/community/index.ts b/backend/src/seeds/community/index.ts index d66c02c21..6a639ee44 100644 --- a/backend/src/seeds/community/index.ts +++ b/backend/src/seeds/community/index.ts @@ -29,6 +29,6 @@ export async function writeHomeCommunityEntry(): Promise { await DbCommunity.insert(homeCom) } } catch (err) { - throw new Error(`Seeding: Error writing HomeCommunity-Entry: ${err}`) + throw new Error(`Seeding: Error writing HomeCommunity-Entry`) // : ${err}`) } }