diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 744f1d3cc..9fbf66507 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -19,7 +19,7 @@ const constants = { LOG_LEVEL: process.env.LOG_LEVEL ?? 'info', CONFIG_VERSION: { DEFAULT: 'DEFAULT', - EXPECTED: 'v18.2023-07-10', + EXPECTED: 'v19.2023-08-25', CURRENT: '', }, } @@ -124,6 +124,9 @@ if ( const federation = { FEDERATION_VALIDATE_COMMUNITY_TIMER: Number(process.env.FEDERATION_VALIDATE_COMMUNITY_TIMER) || 60000, + // default value for community-uuid is equal uuid of stage-3 + FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID: + process.env.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID ?? '56a55482-909e-46a4-bfa2-cd025e894ebc', } export const CONFIG = { diff --git a/backend/src/federation/client/1_0/SendCoinsClient.ts b/backend/src/federation/client/1_0/SendCoinsClient.ts index bc6d9c58d..e15e13100 100644 --- a/backend/src/federation/client/1_0/SendCoinsClient.ts +++ b/backend/src/federation/client/1_0/SendCoinsClient.ts @@ -6,6 +6,7 @@ import { backendLogger as logger } from '@/server/logger' import { SendCoinsArgs } from './model/SendCoinsArgs' import { voteForSendCoins } from './query/voteForSendCoins' +import { SendCoinsResult } from './model/SendCoinsResult' // eslint-disable-next-line camelcase export class SendCoinsClient { @@ -27,23 +28,25 @@ export class SendCoinsClient { }) } - voteForSendCoins = async (args: SendCoinsArgs): Promise => { + voteForSendCoins = async (args: SendCoinsArgs): Promise => { logger.debug('X-Com: voteForSendCoins against endpoint', this.endpoint) try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { data } = await this.client.rawRequest(voteForSendCoins, { args }) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!data?.voteForSendCoins) { - logger.warn('X-Com: voteForSendCoins without response data from endpoint', this.endpoint) - return false + if (!data?.voteForSendCoins?.SendCoinsResult.vote) { + logger.warn( + 'X-Com: voteForSendCoins failed with: ', + data?.voteForSendCoins?.SendCoinsResult, + ) + return null } logger.debug( - 'X-Com: voteForSendCoins successful from endpoint', - this.endpoint, + 'X-Com: voteForSendCoins successful with result=', // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - data.voteForSendCoins.vote, + data.voteForSendCoins.SendCoinsResult, ) - return true + return data.voteForSendCoins.SendCoinsResult } catch (err) { throw new LogError(`X-Com: voteForSendCoins failed for endpoint=${this.endpoint}:`, err) } diff --git a/backend/src/federation/client/1_0/model/SendCoinsResult.ts b/backend/src/federation/client/1_0/model/SendCoinsResult.ts new file mode 100644 index 000000000..1897410cc --- /dev/null +++ b/backend/src/federation/client/1_0/model/SendCoinsResult.ts @@ -0,0 +1,17 @@ +import { ArgsType, Field } from 'type-graphql' + +@ArgsType() +export class SendCoinsResult { + constructor() { + this.vote = false + } + + @Field(() => Boolean) + vote: boolean + + @Field(() => String) + receiverFirstName: string + + @Field(() => String) + receiverLastName: string +} diff --git a/backend/src/graphql/resolver/util/processXComSendCoins.ts b/backend/src/graphql/resolver/util/processXComSendCoins.ts new file mode 100644 index 000000000..957522f65 --- /dev/null +++ b/backend/src/graphql/resolver/util/processXComSendCoins.ts @@ -0,0 +1,80 @@ +import { Community as DbCommunity } from '@entity/Community' +import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' +import { PendingTransaction as DbPendingTransaction } from '@entity/PendingTransaction' +import { User as dbUser } from '@entity/User' +import { Decimal } from 'decimal.js-light' + +import { SendCoinsArgs } from '@/federation/client/1_0/model/SendCoinsArgs' +// eslint-disable-next-line camelcase +import { SendCoinsClient as V1_0_SendCoinsClient } from '@/federation/client/1_0/SendCoinsClient' +import { SendCoinsClientFactory } from '@/federation/client/SendCoinsClientFactory' +import { backendLogger as logger } from '@/server/logger' +import { CONFIG } from '@/config' +import { fullName } from '@/util/utilities' +import { calculateSenderBalance } from '@/util/calculateSenderBalance' +import { LogError } from '@/server/LogError' + + +export async function processXComSendCoins( + receiverFCom: DbFederatedCommunity, + senderFCom: DbFederatedCommunity, + receiverCom: DbCommunity, + senderCom: DbCommunity, + creationDate: Date, + amount: Decimal, + memo: string, + sender: dbUser, + recipient: dbUser, +): Promise { + try { + const senderBalance = await calculateSenderBalance(sender.id, amount.mul(-1), creationDate) + if (!senderBalance) { + throw new LogError('User has not enough GDD or amount is < 0', senderBalance) + } + + const client = SendCoinsClientFactory.getInstance(receiverFCom) + // eslint-disable-next-line camelcase + if (client instanceof V1_0_SendCoinsClient) { + const args = new SendCoinsArgs() + args.communityReceiverIdentifier = receiverCom.communityUuid + ? receiverCom.communityUuid + : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID + args.userReceiverIdentifier = recipient.gradidoID + args.creationDate = creationDate + 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) + const result = await client.voteForSendCoins(args) + if(result) { + const pendingTx = DbPendingTransaction.create() + pendingTx.amount = amount.mul(-1) + pendingTx.balance = senderBalance ? senderBalance.balance : new Decimal(0) + pendingTx.balanceDate = creationDate + pendingTx.decay = senderBalance ? senderBalance.decay.decay : new Decimal(0) + pendingTx.decayStart = senderBalance ? senderBalance.decay.start : null + pendingTx.linkedUserCommunityUuid = receiverCom.communityUuid + ? receiverCom.communityUuid + : CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID + pendingTx.linkedUserGradidoID = recipient.gradidoID + pendingTx.linkedUserName = userSenderName + pendingTx.memo = memo + pendingTx.previous = receiveBalance ? receiveBalance.lastTransactionId : null + pendingTx.state = PendingTransactionState.NEW + pendingTx.typeId = TransactionTypeId.RECEIVE + pendingTx.userCommunityUuid = communityReceiverIdentifier + pendingTx.userGradidoID = userReceiverIdentifier + pendingTx.userName = fullName(receiverUser.firstName, receiverUser.lastName) + + await DbPendingTransaction.insert(pendingTx) + logger.debug(`voteForSendCoins()-1_0... successfull`) + } + } + } catch (err) { + logger.error(`Error:`, err) + } + return true +} diff --git a/backend/src/util/calculateSenderBalance.ts b/backend/src/util/calculateSenderBalance.ts new file mode 100644 index 000000000..89e417d35 --- /dev/null +++ b/backend/src/util/calculateSenderBalance.ts @@ -0,0 +1,21 @@ +import { Decimal } from 'decimal.js-light' + +import { Decay } from '@model/Decay' + +import { getLastTransaction } from '@/graphql/resolver/util/getLastTransaction' + +import { calculateDecay } from './decay' + +export async function calculateSenderBalance( + userId: number, + amount: Decimal, + time: Date, +): Promise<{ balance: Decimal; decay: Decay; lastTransactionId: number } | null> { + const lastTransaction = await getLastTransaction(userId) + if (!lastTransaction) return null + + const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time) + + const balance = decay.balance.add(amount.toString()) + return { balance, lastTransactionId: lastTransaction.id, decay } +} diff --git a/federation/src/graphql/api/1_0/model/SendCoinsResult.ts b/federation/src/graphql/api/1_0/model/SendCoinsResult.ts new file mode 100644 index 000000000..1897410cc --- /dev/null +++ b/federation/src/graphql/api/1_0/model/SendCoinsResult.ts @@ -0,0 +1,17 @@ +import { ArgsType, Field } from 'type-graphql' + +@ArgsType() +export class SendCoinsResult { + constructor() { + this.vote = false + } + + @Field(() => Boolean) + vote: boolean + + @Field(() => String) + receiverFirstName: string + + @Field(() => String) + receiverLastName: string +} diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index dd8958e9f..73c6e077c 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -11,6 +11,7 @@ import { TransactionTypeId } from '../enum/TransactionTypeId' import { calculateRecepientBalance } from '@/graphql/util/calculateRecepientBalance' import Decimal from 'decimal.js-light' import { fullName } from '@/graphql/util/fullName' +import { SendCoinsResult } from '../model/SendCoinsResult' @Resolver() // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -28,7 +29,8 @@ export class SendCoinsResolver { userSenderIdentifier, userSenderName, }: SendCoinsArgs, - ): Promise { + ): Promise { + const result = new SendCoinsResult() logger.debug(`voteForSendCoins() via apiVersion=1_0 ...`) try { // first check if receiver community is correct @@ -68,10 +70,13 @@ export class SendCoinsResolver { pendingTx.userName = fullName(receiverUser.firstName, receiverUser.lastName) await DbPendingTransaction.insert(pendingTx) + result.vote = true + result.receiverFirstName = receiverUser.firstName + result.receiverLastName = receiverUser.lastName logger.debug(`voteForSendCoins()-1_0... successfull`) } catch (err) { throw new LogError(`Error in voteForSendCoins: `, err) } - return true + return result } }