From bb88048a25c11960b40e411dd0bf402327c50907 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Fri, 5 Dec 2025 02:17:19 +0100 Subject: [PATCH 01/61] send emails after x-com-tx --- .../client/1_0/model/SendCoinsResult.ts | 3 ++ .../src/graphql/logic/processXComSendCoins.ts | 30 +++++++++++++++++++ core/src/graphql/logic/storeForeignUser.ts | 26 ++++++++++++++-- .../api/1_0/resolver/SendCoinsResolver.ts | 1 + .../SendCoinsResponseJwtPayloadType.ts | 3 ++ 5 files changed, 60 insertions(+), 3 deletions(-) diff --git a/core/src/federation/client/1_0/model/SendCoinsResult.ts b/core/src/federation/client/1_0/model/SendCoinsResult.ts index 8fc21305f..bef399b01 100644 --- a/core/src/federation/client/1_0/model/SendCoinsResult.ts +++ b/core/src/federation/client/1_0/model/SendCoinsResult.ts @@ -20,4 +20,7 @@ export class SendCoinsResult { @Field(() => String, { nullable: true }) recipAlias: string | null + + @Field(() => String, { nullable: true }) + recipEmail: string | null } diff --git a/core/src/graphql/logic/processXComSendCoins.ts b/core/src/graphql/logic/processXComSendCoins.ts index ceb33a378..c9db8b63d 100644 --- a/core/src/graphql/logic/processXComSendCoins.ts +++ b/core/src/graphql/logic/processXComSendCoins.ts @@ -36,6 +36,7 @@ import { fullName } from '../../util/utilities' import { settlePendingSenderTransaction } from './settlePendingSenderTransaction' import { storeForeignUser } from './storeForeignUser' import { storeLinkAsRedeemed } from './storeLinkAsRedeemed' +import { sendTransactionLinkRedeemedEmail, sendTransactionReceivedEmail } from '../../emails' const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.processXComSendCoins.${method}`) @@ -167,6 +168,34 @@ export async function processXComCompleteTransaction( ) } } + + + await sendTransactionReceivedEmail({ + firstName: foreignUser.firstName, + lastName: foreignUser.lastName, + email: foreignUser.emailContact.email, + language: foreignUser.language, + memo, + senderFirstName: senderUser.firstName, + senderLastName: senderUser.lastName, + senderEmail: senderUser.emailContact.email, + transactionAmount: new Decimal(amount), + }) + if (dbTransactionLink) { + await sendTransactionLinkRedeemedEmail({ + firstName: senderUser.firstName, + lastName: senderUser.lastName, + email: senderUser.emailContact.email, + language: senderUser.language, + senderFirstName: foreignUser.firstName, + senderLastName: foreignUser.lastName, + senderEmail: foreignUser.emailContact.email, + transactionAmount: new Decimal(amount), + transactionMemo: memo, + }) + } + + } } catch (err) { const errmsg = @@ -483,6 +512,7 @@ export async function processXComCommittingSendCoins( } sendCoinsResult.recipGradidoID = pendingTx.linkedUserGradidoID sendCoinsResult.recipAlias = recipient.recipAlias + sendCoinsResult.recipEmail = recipient.recipEmail } } catch (err) { methodLogger.error( diff --git a/core/src/graphql/logic/storeForeignUser.ts b/core/src/graphql/logic/storeForeignUser.ts index a367ee8e3..602070e05 100644 --- a/core/src/graphql/logic/storeForeignUser.ts +++ b/core/src/graphql/logic/storeForeignUser.ts @@ -1,4 +1,7 @@ -import { Community as DbCommunity, User as DbUser, findForeignUserByUuids } from 'database' +import { Community as DbCommunity, + User as DbUser, + UserContact as DbUserContact, + findForeignUserByUuids } from 'database' import { getLogger } from 'log4js' import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const' import { SendCoinsResult } from '../../federation/client/1_0/model/SendCoinsResult' @@ -35,16 +38,28 @@ export async function storeForeignUser( } foreignUser.gradidoID = committingResult.recipGradidoID foreignUser = await DbUser.save(foreignUser) + logger.debug('new foreignUser inserted:', foreignUser) + let foreignUserEmail = DbUserContact.create() + foreignUserEmail.email = committingResult.recipEmail! + foreignUserEmail.emailChecked = true + foreignUserEmail.user = foreignUser + foreignUserEmail = await DbUserContact.save(foreignUserEmail) + logger.debug('new foreignUserEmail inserted:', foreignUserEmail) + + foreignUser.emailContact = foreignUserEmail + foreignUser.emailId = foreignUserEmail.id + foreignUser = await DbUser.save(foreignUser) return foreignUser } else if ( user.firstName !== committingResult.recipFirstName || user.lastName !== committingResult.recipLastName || - user.alias !== committingResult.recipAlias + user.alias !== committingResult.recipAlias || + user.emailContact.email !== committingResult.recipEmail ) { logger.warn( - 'foreignUser still exists, but with different name or alias:', + 'foreignUser still exists, but with different name, alias or email:', user, committingResult, ) @@ -57,6 +72,11 @@ export async function storeForeignUser( if (committingResult.recipAlias !== null) { user.alias = committingResult.recipAlias } + if (committingResult.recipEmail != null) { + let userContact = user.emailContact + userContact.email = committingResult.recipEmail + user.emailContact = await DbUserContact.save(userContact) + } await DbUser.save(user) logger.debug('update recipient successful.', user) return user diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 4e6dc71d2..10f8acae0 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -128,6 +128,7 @@ export class SendCoinsResolver { receiverUser.firstName, receiverUser.lastName, receiverUser.alias, + receiverUser.emailContact.email ) const responseJwt = await encryptAndSign( responseArgs, diff --git a/shared/src/jwt/payloadtypes/SendCoinsResponseJwtPayloadType.ts b/shared/src/jwt/payloadtypes/SendCoinsResponseJwtPayloadType.ts index fb08d6a97..b6335b262 100644 --- a/shared/src/jwt/payloadtypes/SendCoinsResponseJwtPayloadType.ts +++ b/shared/src/jwt/payloadtypes/SendCoinsResponseJwtPayloadType.ts @@ -8,6 +8,7 @@ export class SendCoinsResponseJwtPayloadType extends JwtPayloadType { recipFirstName: string | null recipLastName: string | null recipAlias: string | null + recipEmail: string | null constructor( handshakeID: string, @@ -16,6 +17,7 @@ export class SendCoinsResponseJwtPayloadType extends JwtPayloadType { recipFirstName: string | null, recipLastName: string | null, recipAlias: string | null, + recipEmail: string | null, ) { super(handshakeID) this.tokentype = SendCoinsResponseJwtPayloadType.SEND_COINS_RESPONSE_TYPE @@ -24,5 +26,6 @@ export class SendCoinsResponseJwtPayloadType extends JwtPayloadType { this.recipFirstName = recipFirstName this.recipLastName = recipLastName this.recipAlias = recipAlias + this.recipEmail = recipEmail } } From 3c0a4d3332aaa5645cf56bc49ffe319de38a248e Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 9 Dec 2025 01:36:43 +0100 Subject: [PATCH 02/61] linting --- core/src/graphql/logic/processXComSendCoins.ts | 14 +++++++------- core/src/graphql/logic/storeForeignUser.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/graphql/logic/processXComSendCoins.ts b/core/src/graphql/logic/processXComSendCoins.ts index c9db8b63d..4955655d0 100644 --- a/core/src/graphql/logic/processXComSendCoins.ts +++ b/core/src/graphql/logic/processXComSendCoins.ts @@ -1,15 +1,15 @@ import { - CommunityLoggingView, countOpenPendingTransactions, - Community as DbCommunity, - FederatedCommunity as DbFederatedCommunity, - PendingTransaction as DbPendingTransaction, - TransactionLink as DbTransactionLink, - User as dbUser, findTransactionLinkByCode, findUserByIdentifier, getCommunityByUuid, + Community as DbCommunity, + CommunityLoggingView, + FederatedCommunity as DbFederatedCommunity, + PendingTransaction as DbPendingTransaction, PendingTransactionLoggingView, + TransactionLink as DbTransactionLink, + User as dbUser, UserLoggingView, } from 'database' import { Decimal } from 'decimal.js-light' @@ -25,6 +25,7 @@ import { import { randombytes_random } from 'sodium-native' import { CONFIG as CONFIG_CORE } from '../../config' import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const' +import { sendTransactionLinkRedeemedEmail, sendTransactionReceivedEmail } from '../../emails' import { SendCoinsResultLoggingView } from '../../federation/client/1_0/logging/SendCoinsResultLogging.view' import { SendCoinsResult } from '../../federation/client/1_0/model/SendCoinsResult' import { SendCoinsClient as V1_0_SendCoinsClient } from '../../federation/client/1_0/SendCoinsClient' @@ -36,7 +37,6 @@ import { fullName } from '../../util/utilities' import { settlePendingSenderTransaction } from './settlePendingSenderTransaction' import { storeForeignUser } from './storeForeignUser' import { storeLinkAsRedeemed } from './storeLinkAsRedeemed' -import { sendTransactionLinkRedeemedEmail, sendTransactionReceivedEmail } from '../../emails' const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.processXComSendCoins.${method}`) diff --git a/core/src/graphql/logic/storeForeignUser.ts b/core/src/graphql/logic/storeForeignUser.ts index 602070e05..ebd1b8aec 100644 --- a/core/src/graphql/logic/storeForeignUser.ts +++ b/core/src/graphql/logic/storeForeignUser.ts @@ -73,7 +73,7 @@ export async function storeForeignUser( user.alias = committingResult.recipAlias } if (committingResult.recipEmail != null) { - let userContact = user.emailContact + const userContact = user.emailContact userContact.email = committingResult.recipEmail user.emailContact = await DbUserContact.save(userContact) } From b152809932113b4088843e41f77e2a6857ee56fa Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 9 Dec 2025 23:10:20 +0100 Subject: [PATCH 03/61] correct receiver email treatment --- .../logging/SendCoinsResultLogging.view.ts | 1 + .../src/graphql/logic/processXComSendCoins.ts | 5 +---- core/src/graphql/logic/storeForeignUser.ts | 21 ++++++++++--------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/core/src/federation/client/1_0/logging/SendCoinsResultLogging.view.ts b/core/src/federation/client/1_0/logging/SendCoinsResultLogging.view.ts index 1eb08c432..d92a20e71 100644 --- a/core/src/federation/client/1_0/logging/SendCoinsResultLogging.view.ts +++ b/core/src/federation/client/1_0/logging/SendCoinsResultLogging.view.ts @@ -14,6 +14,7 @@ export class SendCoinsResultLoggingView extends AbstractLoggingView { recipFirstName: this.self.recipFirstName?.substring(0, 3), recipLastName: this.self.recipLastName?.substring(0, 3), recipAlias: this.self.recipAlias?.substring(0, 3), + recipEmail: this.self.recipEmail?.substring(0, 3), } } } diff --git a/core/src/graphql/logic/processXComSendCoins.ts b/core/src/graphql/logic/processXComSendCoins.ts index 4955655d0..443b87288 100644 --- a/core/src/graphql/logic/processXComSendCoins.ts +++ b/core/src/graphql/logic/processXComSendCoins.ts @@ -169,7 +169,6 @@ export async function processXComCompleteTransaction( } } - await sendTransactionReceivedEmail({ firstName: foreignUser.firstName, lastName: foreignUser.lastName, @@ -194,8 +193,6 @@ export async function processXComCompleteTransaction( transactionMemo: memo, }) } - - } } catch (err) { const errmsg = @@ -512,7 +509,7 @@ export async function processXComCommittingSendCoins( } sendCoinsResult.recipGradidoID = pendingTx.linkedUserGradidoID sendCoinsResult.recipAlias = recipient.recipAlias - sendCoinsResult.recipEmail = recipient.recipEmail + sendCoinsResult.recipEmail = recipient.recipEmail } } catch (err) { methodLogger.error( diff --git a/core/src/graphql/logic/storeForeignUser.ts b/core/src/graphql/logic/storeForeignUser.ts index ebd1b8aec..48616ba24 100644 --- a/core/src/graphql/logic/storeForeignUser.ts +++ b/core/src/graphql/logic/storeForeignUser.ts @@ -40,16 +40,17 @@ export async function storeForeignUser( foreignUser = await DbUser.save(foreignUser) logger.debug('new foreignUser inserted:', foreignUser) - let foreignUserEmail = DbUserContact.create() - foreignUserEmail.email = committingResult.recipEmail! - foreignUserEmail.emailChecked = true - foreignUserEmail.user = foreignUser - foreignUserEmail = await DbUserContact.save(foreignUserEmail) - logger.debug('new foreignUserEmail inserted:', foreignUserEmail) - - foreignUser.emailContact = foreignUserEmail - foreignUser.emailId = foreignUserEmail.id - foreignUser = await DbUser.save(foreignUser) + if (committingResult.recipEmail !== null) { + let foreignUserEmail = DbUserContact.create() + foreignUserEmail.email = committingResult.recipEmail! + foreignUserEmail.emailChecked = true + foreignUserEmail.user = foreignUser + foreignUserEmail = await DbUserContact.save(foreignUserEmail) + logger.debug('new foreignUserEmail inserted:', foreignUserEmail) + foreignUser.emailContact = foreignUserEmail + foreignUser.emailId = foreignUserEmail.id + foreignUser = await DbUser.save(foreignUser) + } return foreignUser } else if ( From f5cd91e75268ee42a87b0efc172d2a7b9e933e1e Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 10 Dec 2025 00:06:28 +0100 Subject: [PATCH 04/61] correct storing foreignUsers emailContact --- core/src/graphql/logic/storeForeignUser.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/core/src/graphql/logic/storeForeignUser.ts b/core/src/graphql/logic/storeForeignUser.ts index 48616ba24..7f6ca2786 100644 --- a/core/src/graphql/logic/storeForeignUser.ts +++ b/core/src/graphql/logic/storeForeignUser.ts @@ -57,7 +57,8 @@ export async function storeForeignUser( user.firstName !== committingResult.recipFirstName || user.lastName !== committingResult.recipLastName || user.alias !== committingResult.recipAlias || - user.emailContact.email !== committingResult.recipEmail + (user.emailContact === null && committingResult.recipEmail !== null) || + (user.emailContact !== null && user.emailContact.email !== committingResult.recipEmail) ) { logger.warn( 'foreignUser still exists, but with different name, alias or email:', @@ -73,10 +74,22 @@ export async function storeForeignUser( if (committingResult.recipAlias !== null) { user.alias = committingResult.recipAlias } - if (committingResult.recipEmail != null) { + if(user.emailContact === null && committingResult.recipEmail !== null) { + let foreignUserEmail = DbUserContact.create() + foreignUserEmail.email = committingResult.recipEmail! + foreignUserEmail.emailChecked = true + foreignUserEmail.user = user + foreignUserEmail = await DbUserContact.save(foreignUserEmail) + logger.debug('new foreignUserEmail inserted:', foreignUserEmail) + user.emailContact = foreignUserEmail + user.emailId = foreignUserEmail.id + } + else if (user.emailContact !== null && committingResult.recipEmail != null) { const userContact = user.emailContact userContact.email = committingResult.recipEmail user.emailContact = await DbUserContact.save(userContact) + user.emailId = userContact.id + logger.debug('foreignUserEmail updated:', userContact) } await DbUser.save(user) logger.debug('update recipient successful.', user) From a73d7b5859767f1ebf4b70b04168fb7c72d6fb0f Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 10 Dec 2025 00:36:27 +0100 Subject: [PATCH 05/61] next try to correct storeforeignuser --- core/src/graphql/logic/storeForeignUser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/graphql/logic/storeForeignUser.ts b/core/src/graphql/logic/storeForeignUser.ts index 7f6ca2786..a554e8a93 100644 --- a/core/src/graphql/logic/storeForeignUser.ts +++ b/core/src/graphql/logic/storeForeignUser.ts @@ -58,7 +58,7 @@ export async function storeForeignUser( user.lastName !== committingResult.recipLastName || user.alias !== committingResult.recipAlias || (user.emailContact === null && committingResult.recipEmail !== null) || - (user.emailContact !== null && user.emailContact.email !== committingResult.recipEmail) + (user.emailContact !== null && user.emailContact.email !== null && user.emailContact.email !== committingResult.recipEmail) ) { logger.warn( 'foreignUser still exists, but with different name, alias or email:', From e7465ae093ee86e84e33bdccbd3697a22745be62 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 10 Dec 2025 01:14:34 +0100 Subject: [PATCH 06/61] change type of receiverCom.publicKey in where statement --- core/src/graphql/logic/processXComSendCoins.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/graphql/logic/processXComSendCoins.ts b/core/src/graphql/logic/processXComSendCoins.ts index 443b87288..8187165fd 100644 --- a/core/src/graphql/logic/processXComSendCoins.ts +++ b/core/src/graphql/logic/processXComSendCoins.ts @@ -253,7 +253,7 @@ export async function processXComPendingSendCoins( const receiverFCom = await DbFederatedCommunity.findOneOrFail({ where: { - publicKey: Buffer.from(receiverCom.publicKey), + publicKey: receiverCom.publicKey, apiVersion: CONFIG_CORE.FEDERATION_BACKEND_SEND_ON_API, }, }) From 846ead3119194b6d70c4ba0b702a482435b19a05 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 11 Dec 2025 01:01:31 +0100 Subject: [PATCH 07/61] next try for property email --- core/src/graphql/logic/storeForeignUser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/graphql/logic/storeForeignUser.ts b/core/src/graphql/logic/storeForeignUser.ts index a554e8a93..8ef16984d 100644 --- a/core/src/graphql/logic/storeForeignUser.ts +++ b/core/src/graphql/logic/storeForeignUser.ts @@ -58,7 +58,7 @@ export async function storeForeignUser( user.lastName !== committingResult.recipLastName || user.alias !== committingResult.recipAlias || (user.emailContact === null && committingResult.recipEmail !== null) || - (user.emailContact !== null && user.emailContact.email !== null && user.emailContact.email !== committingResult.recipEmail) + (user.emailContact !== null && user.emailContact?.email !== null && user.emailContact?.email !== committingResult.recipEmail) ) { logger.warn( 'foreignUser still exists, but with different name, alias or email:', From 047272e9872b8e584eb4c847c3402a4596321189 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 11 Dec 2025 01:44:40 +0100 Subject: [PATCH 08/61] additional loggoutput --- core/src/graphql/logic/storeForeignUser.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/core/src/graphql/logic/storeForeignUser.ts b/core/src/graphql/logic/storeForeignUser.ts index 8ef16984d..800672e12 100644 --- a/core/src/graphql/logic/storeForeignUser.ts +++ b/core/src/graphql/logic/storeForeignUser.ts @@ -5,6 +5,7 @@ import { Community as DbCommunity, import { getLogger } from 'log4js' import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const' import { SendCoinsResult } from '../../federation/client/1_0/model/SendCoinsResult' +import { UserLoggingView, UserContactLoggingView } from 'database' const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.logic.storeForeignUser`) @@ -39,14 +40,14 @@ export async function storeForeignUser( foreignUser.gradidoID = committingResult.recipGradidoID foreignUser = await DbUser.save(foreignUser) - logger.debug('new foreignUser inserted:', foreignUser) + logger.debug('new foreignUser inserted:', new UserLoggingView(foreignUser)) if (committingResult.recipEmail !== null) { let foreignUserEmail = DbUserContact.create() foreignUserEmail.email = committingResult.recipEmail! foreignUserEmail.emailChecked = true foreignUserEmail.user = foreignUser foreignUserEmail = await DbUserContact.save(foreignUserEmail) - logger.debug('new foreignUserEmail inserted:', foreignUserEmail) + logger.debug('new foreignUserEmail inserted:', new UserContactLoggingView(foreignUserEmail)) foreignUser.emailContact = foreignUserEmail foreignUser.emailId = foreignUserEmail.id foreignUser = await DbUser.save(foreignUser) @@ -60,9 +61,9 @@ export async function storeForeignUser( (user.emailContact === null && committingResult.recipEmail !== null) || (user.emailContact !== null && user.emailContact?.email !== null && user.emailContact?.email !== committingResult.recipEmail) ) { - logger.warn( + logger.debug( 'foreignUser still exists, but with different name, alias or email:', - user, + new UserLoggingView(user), committingResult, ) if (committingResult.recipFirstName !== null) { @@ -75,12 +76,13 @@ export async function storeForeignUser( user.alias = committingResult.recipAlias } if(user.emailContact === null && committingResult.recipEmail !== null) { + logger.debug('creating new userContact:', new UserContactLoggingView(user.emailContact), committingResult) let foreignUserEmail = DbUserContact.create() foreignUserEmail.email = committingResult.recipEmail! foreignUserEmail.emailChecked = true foreignUserEmail.user = user foreignUserEmail = await DbUserContact.save(foreignUserEmail) - logger.debug('new foreignUserEmail inserted:', foreignUserEmail) + logger.debug('new foreignUserEmail inserted:', new UserContactLoggingView(foreignUserEmail)) user.emailContact = foreignUserEmail user.emailId = foreignUserEmail.id } @@ -89,13 +91,13 @@ export async function storeForeignUser( userContact.email = committingResult.recipEmail user.emailContact = await DbUserContact.save(userContact) user.emailId = userContact.id - logger.debug('foreignUserEmail updated:', userContact) + logger.debug('foreignUserEmail updated:', new UserContactLoggingView(userContact)) } await DbUser.save(user) - logger.debug('update recipient successful.', user) + logger.debug('update recipient successful.', new UserLoggingView(user)) return user } else { - logger.debug('foreignUser still exists...:', user) + logger.debug('foreignUser still exists...:', new UserLoggingView(user)) return user } } catch (err) { From d00da185d774b6f5368e14d5fb57db52df10ebb9 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 11 Dec 2025 01:58:11 +0100 Subject: [PATCH 09/61] correct checks against user.emailContact --- core/src/graphql/logic/storeForeignUser.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/graphql/logic/storeForeignUser.ts b/core/src/graphql/logic/storeForeignUser.ts index 800672e12..1ab8ccca8 100644 --- a/core/src/graphql/logic/storeForeignUser.ts +++ b/core/src/graphql/logic/storeForeignUser.ts @@ -75,7 +75,7 @@ export async function storeForeignUser( if (committingResult.recipAlias !== null) { user.alias = committingResult.recipAlias } - if(user.emailContact === null && committingResult.recipEmail !== null) { + if(!user.emailContact && committingResult.recipEmail !== null) { logger.debug('creating new userContact:', new UserContactLoggingView(user.emailContact), committingResult) let foreignUserEmail = DbUserContact.create() foreignUserEmail.email = committingResult.recipEmail! @@ -86,7 +86,7 @@ export async function storeForeignUser( user.emailContact = foreignUserEmail user.emailId = foreignUserEmail.id } - else if (user.emailContact !== null && committingResult.recipEmail != null) { + else if (user.emailContact && committingResult.recipEmail != null) { const userContact = user.emailContact userContact.email = committingResult.recipEmail user.emailContact = await DbUserContact.save(userContact) From bb69008f5f070bde164de6a5f64ec8123da18299 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 16 Dec 2025 22:56:23 +0100 Subject: [PATCH 10/61] correct toJSON in case of undefined UserContact --- .../src/logging/UserContactLogging.view.ts | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/database/src/logging/UserContactLogging.view.ts b/database/src/logging/UserContactLogging.view.ts index 5230c4311..426866b03 100644 --- a/database/src/logging/UserContactLogging.view.ts +++ b/database/src/logging/UserContactLogging.view.ts @@ -17,21 +17,24 @@ export class UserContactLoggingView extends AbstractLoggingView { public toJSON(): any { return { - id: this.self.id, - type: this.self.type, - user: - this.showUser && this.self.user - ? new UserLoggingView(this.self.user).toJSON() - : { id: this.self.userId }, - email: this.self.email?.substring(0, 3) + '...', - emailVerificationCode: this.self.emailVerificationCode?.substring(0, 4) + '...', - emailOptInTypeId: OptInType[this.self.emailOptInTypeId], - emailResendCount: this.self.emailResendCount, - emailChecked: this.self.emailChecked, - phone: this.self.phone ? this.self.phone.substring(0, 3) + '...' : undefined, - createdAt: this.dateToString(this.self.createdAt), - updatedAt: this.dateToString(this.self.updatedAt), - deletedAt: this.dateToString(this.self.deletedAt), + self: this.self ? + { + id: this.self.id, + type: this.self.type, + user: + this.showUser && this.self.user + ? new UserLoggingView(this.self.user).toJSON() + : { id: this.self.userId }, + email: this.self.email?.substring(0, 3) + '...', + emailVerificationCode: this.self.emailVerificationCode?.substring(0, 4) + '...', + emailOptInTypeId: OptInType[this.self.emailOptInTypeId], + emailResendCount: this.self.emailResendCount, + emailChecked: this.self.emailChecked, + phone: this.self.phone ? this.self.phone.substring(0, 3) + '...' : undefined, + createdAt: this.dateToString(this.self.createdAt), + updatedAt: this.dateToString(this.self.updatedAt), + deletedAt: this.dateToString(this.self.deletedAt), + } : undefined } } } From 093a2d4745f1d091b1abec0a5950c681b7994ab6 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 16 Dec 2025 23:28:05 +0100 Subject: [PATCH 11/61] set field type on creating new userContact --- core/src/graphql/logic/storeForeignUser.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/graphql/logic/storeForeignUser.ts b/core/src/graphql/logic/storeForeignUser.ts index 1ab8ccca8..9d6542cdb 100644 --- a/core/src/graphql/logic/storeForeignUser.ts +++ b/core/src/graphql/logic/storeForeignUser.ts @@ -6,6 +6,7 @@ import { getLogger } from 'log4js' import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const' import { SendCoinsResult } from '../../federation/client/1_0/model/SendCoinsResult' import { UserLoggingView, UserContactLoggingView } from 'database' +import { UserContactType } from 'shared' const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.logic.storeForeignUser`) @@ -78,6 +79,7 @@ export async function storeForeignUser( if(!user.emailContact && committingResult.recipEmail !== null) { logger.debug('creating new userContact:', new UserContactLoggingView(user.emailContact), committingResult) let foreignUserEmail = DbUserContact.create() + foreignUserEmail.type = UserContactType.USER_CONTACT_EMAIL foreignUserEmail.email = committingResult.recipEmail! foreignUserEmail.emailChecked = true foreignUserEmail.user = user From eb6f74466465539c80adc9e12bed5e974d42324a Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 16 Dec 2025 23:58:36 +0100 Subject: [PATCH 12/61] set userId in new userContact --- core/src/graphql/logic/storeForeignUser.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/graphql/logic/storeForeignUser.ts b/core/src/graphql/logic/storeForeignUser.ts index 9d6542cdb..191463943 100644 --- a/core/src/graphql/logic/storeForeignUser.ts +++ b/core/src/graphql/logic/storeForeignUser.ts @@ -83,6 +83,7 @@ export async function storeForeignUser( foreignUserEmail.email = committingResult.recipEmail! foreignUserEmail.emailChecked = true foreignUserEmail.user = user + foreignUserEmail.userId = user.id foreignUserEmail = await DbUserContact.save(foreignUserEmail) logger.debug('new foreignUserEmail inserted:', new UserContactLoggingView(foreignUserEmail)) user.emailContact = foreignUserEmail From abf257f077ac6c529c16c8c8f40f73ad960b5943 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 17 Dec 2025 16:46:06 +0100 Subject: [PATCH 13/61] linting core --- .../src/graphql/logic/processXComSendCoins.ts | 12 +++--- core/src/graphql/logic/storeForeignUser.ts | 40 +++++++++++++------ 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/core/src/graphql/logic/processXComSendCoins.ts b/core/src/graphql/logic/processXComSendCoins.ts index 8187165fd..7b39f2927 100644 --- a/core/src/graphql/logic/processXComSendCoins.ts +++ b/core/src/graphql/logic/processXComSendCoins.ts @@ -1,15 +1,15 @@ import { + CommunityLoggingView, countOpenPendingTransactions, + Community as DbCommunity, + FederatedCommunity as DbFederatedCommunity, + PendingTransaction as DbPendingTransaction, + TransactionLink as DbTransactionLink, + User as dbUser, findTransactionLinkByCode, findUserByIdentifier, getCommunityByUuid, - Community as DbCommunity, - CommunityLoggingView, - FederatedCommunity as DbFederatedCommunity, - PendingTransaction as DbPendingTransaction, PendingTransactionLoggingView, - TransactionLink as DbTransactionLink, - User as dbUser, UserLoggingView, } from 'database' import { Decimal } from 'decimal.js-light' diff --git a/core/src/graphql/logic/storeForeignUser.ts b/core/src/graphql/logic/storeForeignUser.ts index 191463943..3722ca351 100644 --- a/core/src/graphql/logic/storeForeignUser.ts +++ b/core/src/graphql/logic/storeForeignUser.ts @@ -1,12 +1,15 @@ -import { Community as DbCommunity, - User as DbUser, - UserContact as DbUserContact, - findForeignUserByUuids } from 'database' +import { + Community as DbCommunity, + User as DbUser, + UserContact as DbUserContact, + findForeignUserByUuids, + UserContactLoggingView, + UserLoggingView, +} from 'database' import { getLogger } from 'log4js' +import { UserContactType } from 'shared' import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const' import { SendCoinsResult } from '../../federation/client/1_0/model/SendCoinsResult' -import { UserLoggingView, UserContactLoggingView } from 'database' -import { UserContactType } from 'shared' const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.logic.storeForeignUser`) @@ -48,7 +51,10 @@ export async function storeForeignUser( foreignUserEmail.emailChecked = true foreignUserEmail.user = foreignUser foreignUserEmail = await DbUserContact.save(foreignUserEmail) - logger.debug('new foreignUserEmail inserted:', new UserContactLoggingView(foreignUserEmail)) + logger.debug( + 'new foreignUserEmail inserted:', + new UserContactLoggingView(foreignUserEmail), + ) foreignUser.emailContact = foreignUserEmail foreignUser.emailId = foreignUserEmail.id foreignUser = await DbUser.save(foreignUser) @@ -60,7 +66,9 @@ export async function storeForeignUser( user.lastName !== committingResult.recipLastName || user.alias !== committingResult.recipAlias || (user.emailContact === null && committingResult.recipEmail !== null) || - (user.emailContact !== null && user.emailContact?.email !== null && user.emailContact?.email !== committingResult.recipEmail) + (user.emailContact !== null && + user.emailContact?.email !== null && + user.emailContact?.email !== committingResult.recipEmail) ) { logger.debug( 'foreignUser still exists, but with different name, alias or email:', @@ -76,8 +84,12 @@ export async function storeForeignUser( if (committingResult.recipAlias !== null) { user.alias = committingResult.recipAlias } - if(!user.emailContact && committingResult.recipEmail !== null) { - logger.debug('creating new userContact:', new UserContactLoggingView(user.emailContact), committingResult) + if (!user.emailContact && committingResult.recipEmail !== null) { + logger.debug( + 'creating new userContact:', + new UserContactLoggingView(user.emailContact), + committingResult, + ) let foreignUserEmail = DbUserContact.create() foreignUserEmail.type = UserContactType.USER_CONTACT_EMAIL foreignUserEmail.email = committingResult.recipEmail! @@ -85,11 +97,13 @@ export async function storeForeignUser( foreignUserEmail.user = user foreignUserEmail.userId = user.id foreignUserEmail = await DbUserContact.save(foreignUserEmail) - logger.debug('new foreignUserEmail inserted:', new UserContactLoggingView(foreignUserEmail)) + logger.debug( + 'new foreignUserEmail inserted:', + new UserContactLoggingView(foreignUserEmail), + ) user.emailContact = foreignUserEmail user.emailId = foreignUserEmail.id - } - else if (user.emailContact && committingResult.recipEmail != null) { + } else if (user.emailContact && committingResult.recipEmail != null) { const userContact = user.emailContact userContact.email = committingResult.recipEmail user.emailContact = await DbUserContact.save(userContact) From bdaf48b29ee740af7c89b57a37476dffd4be9036 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 17 Dec 2025 16:47:59 +0100 Subject: [PATCH 14/61] linting federation --- federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 10f8acae0..7e29fbe29 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -128,7 +128,7 @@ export class SendCoinsResolver { receiverUser.firstName, receiverUser.lastName, receiverUser.alias, - receiverUser.emailContact.email + receiverUser.emailContact.email, ) const responseJwt = await encryptAndSign( responseArgs, From a013f848149b3c55a727e54181ee2aa246305df6 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 17 Dec 2025 16:50:42 +0100 Subject: [PATCH 15/61] linting database --- .../src/logging/UserContactLogging.view.ts | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/database/src/logging/UserContactLogging.view.ts b/database/src/logging/UserContactLogging.view.ts index 426866b03..c064a8b2d 100644 --- a/database/src/logging/UserContactLogging.view.ts +++ b/database/src/logging/UserContactLogging.view.ts @@ -17,24 +17,25 @@ export class UserContactLoggingView extends AbstractLoggingView { public toJSON(): any { return { - self: this.self ? - { - id: this.self.id, - type: this.self.type, - user: - this.showUser && this.self.user - ? new UserLoggingView(this.self.user).toJSON() - : { id: this.self.userId }, - email: this.self.email?.substring(0, 3) + '...', - emailVerificationCode: this.self.emailVerificationCode?.substring(0, 4) + '...', - emailOptInTypeId: OptInType[this.self.emailOptInTypeId], - emailResendCount: this.self.emailResendCount, - emailChecked: this.self.emailChecked, - phone: this.self.phone ? this.self.phone.substring(0, 3) + '...' : undefined, - createdAt: this.dateToString(this.self.createdAt), - updatedAt: this.dateToString(this.self.updatedAt), - deletedAt: this.dateToString(this.self.deletedAt), - } : undefined + self: this.self + ? { + id: this.self.id, + type: this.self.type, + user: + this.showUser && this.self.user + ? new UserLoggingView(this.self.user).toJSON() + : { id: this.self.userId }, + email: this.self.email?.substring(0, 3) + '...', + emailVerificationCode: this.self.emailVerificationCode?.substring(0, 4) + '...', + emailOptInTypeId: OptInType[this.self.emailOptInTypeId], + emailResendCount: this.self.emailResendCount, + emailChecked: this.self.emailChecked, + phone: this.self.phone ? this.self.phone.substring(0, 3) + '...' : undefined, + createdAt: this.dateToString(this.self.createdAt), + updatedAt: this.dateToString(this.self.updatedAt), + deletedAt: this.dateToString(this.self.deletedAt), + } + : undefined, } } } From ede4b12ed1755b7755c009603ed789a8b293561c Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 6 Jan 2026 01:09:54 +0100 Subject: [PATCH 16/61] add usecase doku as sequence-diagram --- .../resolver/TransactionLinkResolver.ts | 19 ++ .../image/UC_queryTransactionLink.drawio | 273 ++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 docu/Concepts/TechnicalRequirements/image/UC_queryTransactionLink.drawio diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 82fd1ca2e..907761309 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -29,6 +29,7 @@ import { User as DbUser, findModeratorCreatingContributionLink, findTransactionLinkByCode, + findUserByIdentifier, getHomeCommunity, getLastTransaction, } from 'database' @@ -660,6 +661,24 @@ export class TransactionLinkResolver { if (methodLogger.isDebugEnabled()) { methodLogger.debug('Disburse JWT was sent successfully with result=', result) } + const recipientUser = await findUserByIdentifier(recipientGradidoId, recipientCommunityUuid) + if (!recipientUser) { + const errmsg = `Recipient user not found with identifier=${recipientGradidoId}` + methodLogger.error(errmsg) + throw new LogError(errmsg) + } + await sendTransactionReceivedEmail({ + firstName: recipientFirstName, + lastName: recipientUser.lastName, + email: recipientUser.emailContact.email, + language: recipientUser.language, + memo, + senderFirstName: senderUser.firstName, + senderLastName: senderUser.lastName, + senderEmail: senderUser.emailContact.email, + transactionAmount: new Decimal(amount), + }) + } catch (e) { const errmsg = `Disburse JWT was not sent successfully with error=${e}` methodLogger.error(errmsg) diff --git a/docu/Concepts/TechnicalRequirements/image/UC_queryTransactionLink.drawio b/docu/Concepts/TechnicalRequirements/image/UC_queryTransactionLink.drawio new file mode 100644 index 000000000..32d12ac10 --- /dev/null +++ b/docu/Concepts/TechnicalRequirements/image/UC_queryTransactionLink.drawio @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 6b558c92562f1fb3de1b2020a59eb19febf472dd Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 6 Jan 2026 23:00:26 +0100 Subject: [PATCH 17/61] remove email exchange of recepient-user between involved communities --- .../src/graphql/resolver/TransactionLinkResolver.ts | 7 +++++++ .../client/1_0/logging/SendCoinsResultLogging.view.ts | 1 - .../src/federation/client/1_0/model/SendCoinsResult.ts | 3 --- core/src/graphql/logic/processXComSendCoins.ts | 6 +++--- core/src/graphql/logic/storeForeignUser.ts | 10 +++++++--- .../src/graphql/api/1_0/resolver/SendCoinsResolver.ts | 1 - .../payloadtypes/SendCoinsResponseJwtPayloadType.ts | 4 +--- 7 files changed, 18 insertions(+), 14 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 907761309..28412ea59 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -15,6 +15,7 @@ import { EncryptedTransferArgs, fullName, interpretEncryptedTransferArgs, + sendTransactionReceivedEmail, TransactionTypeId, } from 'core' import { randomBytes } from 'crypto' @@ -661,6 +662,12 @@ export class TransactionLinkResolver { if (methodLogger.isDebugEnabled()) { methodLogger.debug('Disburse JWT was sent successfully with result=', result) } + const senderUser = await findUserByIdentifier(senderGradidoId, senderCommunityUuid) + if (!senderUser) { + const errmsg = `Sender user not found with identifier=${senderGradidoId}` + methodLogger.error(errmsg) + throw new LogError(errmsg) + } const recipientUser = await findUserByIdentifier(recipientGradidoId, recipientCommunityUuid) if (!recipientUser) { const errmsg = `Recipient user not found with identifier=${recipientGradidoId}` diff --git a/core/src/federation/client/1_0/logging/SendCoinsResultLogging.view.ts b/core/src/federation/client/1_0/logging/SendCoinsResultLogging.view.ts index d92a20e71..1eb08c432 100644 --- a/core/src/federation/client/1_0/logging/SendCoinsResultLogging.view.ts +++ b/core/src/federation/client/1_0/logging/SendCoinsResultLogging.view.ts @@ -14,7 +14,6 @@ export class SendCoinsResultLoggingView extends AbstractLoggingView { recipFirstName: this.self.recipFirstName?.substring(0, 3), recipLastName: this.self.recipLastName?.substring(0, 3), recipAlias: this.self.recipAlias?.substring(0, 3), - recipEmail: this.self.recipEmail?.substring(0, 3), } } } diff --git a/core/src/federation/client/1_0/model/SendCoinsResult.ts b/core/src/federation/client/1_0/model/SendCoinsResult.ts index bef399b01..8fc21305f 100644 --- a/core/src/federation/client/1_0/model/SendCoinsResult.ts +++ b/core/src/federation/client/1_0/model/SendCoinsResult.ts @@ -20,7 +20,4 @@ export class SendCoinsResult { @Field(() => String, { nullable: true }) recipAlias: string | null - - @Field(() => String, { nullable: true }) - recipEmail: string | null } diff --git a/core/src/graphql/logic/processXComSendCoins.ts b/core/src/graphql/logic/processXComSendCoins.ts index 7b39f2927..7b2ca3dc6 100644 --- a/core/src/graphql/logic/processXComSendCoins.ts +++ b/core/src/graphql/logic/processXComSendCoins.ts @@ -168,7 +168,7 @@ export async function processXComCompleteTransaction( ) } } - + /* await sendTransactionReceivedEmail({ firstName: foreignUser.firstName, lastName: foreignUser.lastName, @@ -180,6 +180,7 @@ export async function processXComCompleteTransaction( senderEmail: senderUser.emailContact.email, transactionAmount: new Decimal(amount), }) + */ if (dbTransactionLink) { await sendTransactionLinkRedeemedEmail({ firstName: senderUser.firstName, @@ -188,7 +189,7 @@ export async function processXComCompleteTransaction( language: senderUser.language, senderFirstName: foreignUser.firstName, senderLastName: foreignUser.lastName, - senderEmail: foreignUser.emailContact.email, + senderEmail: 'unknown', // foreignUser.emailContact.email, transactionAmount: new Decimal(amount), transactionMemo: memo, }) @@ -509,7 +510,6 @@ export async function processXComCommittingSendCoins( } sendCoinsResult.recipGradidoID = pendingTx.linkedUserGradidoID sendCoinsResult.recipAlias = recipient.recipAlias - sendCoinsResult.recipEmail = recipient.recipEmail } } catch (err) { methodLogger.error( diff --git a/core/src/graphql/logic/storeForeignUser.ts b/core/src/graphql/logic/storeForeignUser.ts index 3722ca351..90c7c7c7b 100644 --- a/core/src/graphql/logic/storeForeignUser.ts +++ b/core/src/graphql/logic/storeForeignUser.ts @@ -45,6 +45,7 @@ export async function storeForeignUser( foreignUser = await DbUser.save(foreignUser) logger.debug('new foreignUser inserted:', new UserLoggingView(foreignUser)) + /* if (committingResult.recipEmail !== null) { let foreignUserEmail = DbUserContact.create() foreignUserEmail.email = committingResult.recipEmail! @@ -59,19 +60,20 @@ export async function storeForeignUser( foreignUser.emailId = foreignUserEmail.id foreignUser = await DbUser.save(foreignUser) } - + */ return foreignUser } else if ( user.firstName !== committingResult.recipFirstName || user.lastName !== committingResult.recipLastName || - user.alias !== committingResult.recipAlias || + user.alias !== committingResult.recipAlias/* || (user.emailContact === null && committingResult.recipEmail !== null) || (user.emailContact !== null && user.emailContact?.email !== null && user.emailContact?.email !== committingResult.recipEmail) + */ ) { logger.debug( - 'foreignUser still exists, but with different name, alias or email:', + 'foreignUser still exists, but with different name or alias:', new UserLoggingView(user), committingResult, ) @@ -84,6 +86,7 @@ export async function storeForeignUser( if (committingResult.recipAlias !== null) { user.alias = committingResult.recipAlias } + /* if (!user.emailContact && committingResult.recipEmail !== null) { logger.debug( 'creating new userContact:', @@ -110,6 +113,7 @@ export async function storeForeignUser( user.emailId = userContact.id logger.debug('foreignUserEmail updated:', new UserContactLoggingView(userContact)) } + */ await DbUser.save(user) logger.debug('update recipient successful.', new UserLoggingView(user)) return user diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 7e29fbe29..4e6dc71d2 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -128,7 +128,6 @@ export class SendCoinsResolver { receiverUser.firstName, receiverUser.lastName, receiverUser.alias, - receiverUser.emailContact.email, ) const responseJwt = await encryptAndSign( responseArgs, diff --git a/shared/src/jwt/payloadtypes/SendCoinsResponseJwtPayloadType.ts b/shared/src/jwt/payloadtypes/SendCoinsResponseJwtPayloadType.ts index b6335b262..a857cceae 100644 --- a/shared/src/jwt/payloadtypes/SendCoinsResponseJwtPayloadType.ts +++ b/shared/src/jwt/payloadtypes/SendCoinsResponseJwtPayloadType.ts @@ -8,7 +8,7 @@ export class SendCoinsResponseJwtPayloadType extends JwtPayloadType { recipFirstName: string | null recipLastName: string | null recipAlias: string | null - recipEmail: string | null + constructor( handshakeID: string, @@ -17,7 +17,6 @@ export class SendCoinsResponseJwtPayloadType extends JwtPayloadType { recipFirstName: string | null, recipLastName: string | null, recipAlias: string | null, - recipEmail: string | null, ) { super(handshakeID) this.tokentype = SendCoinsResponseJwtPayloadType.SEND_COINS_RESPONSE_TYPE @@ -26,6 +25,5 @@ export class SendCoinsResponseJwtPayloadType extends JwtPayloadType { this.recipFirstName = recipFirstName this.recipLastName = recipLastName this.recipAlias = recipAlias - this.recipEmail = recipEmail } } From 3f648bcadcd2013d20061c0249b1d73c7f93cad8 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 7 Jan 2026 16:41:52 +0100 Subject: [PATCH 18/61] only send TX-Receive-Email after disbursement if recipient.email exists --- .../resolver/TransactionLinkResolver.ts | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 28412ea59..d5da5a612 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -674,18 +674,32 @@ export class TransactionLinkResolver { methodLogger.error(errmsg) throw new LogError(errmsg) } - await sendTransactionReceivedEmail({ - firstName: recipientFirstName, - lastName: recipientUser.lastName, - email: recipientUser.emailContact.email, - language: recipientUser.language, - memo, - senderFirstName: senderUser.firstName, - senderLastName: senderUser.lastName, - senderEmail: senderUser.emailContact.email, - transactionAmount: new Decimal(amount), - }) - + if(recipientUser.emailContact?.email !== null){ + if (methodLogger.isDebugEnabled()) { + methodLogger.debug('Sending Transaction Received Email to recipient:', recipientUser.firstName, recipientUser.lastName) + } + try { + await sendTransactionReceivedEmail({ + firstName: recipientFirstName, + lastName: recipientUser.lastName, + email: recipientUser.emailContact.email, + language: recipientUser.language, + memo, + senderFirstName: senderUser.firstName, + senderLastName: senderUser.lastName, + senderEmail: senderUser.emailContact.email, + transactionAmount: new Decimal(amount), + }) + } catch (e) { + const errmsg = `Send Transaction Received Email to recipient failed with error=${e}` + methodLogger.error(errmsg) + throw new Error(errmsg) + } + } else { + if (methodLogger.isDebugEnabled()) { + methodLogger.debug('Recipient as foreign user has no email contact, not sending Transaction Received Email to recipient:', recipientUser.firstName, recipientUser.lastName) + } + } } catch (e) { const errmsg = `Disburse JWT was not sent successfully with error=${e}` methodLogger.error(errmsg) From 232aa55c80c0d2dfed5971f9e99780132d9108b8 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 7 Jan 2026 16:59:06 +0100 Subject: [PATCH 19/61] sender and recipient must not foreign user for sending email --- backend/src/graphql/resolver/TransactionLinkResolver.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index d5da5a612..d46d3b9f6 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -674,9 +674,9 @@ export class TransactionLinkResolver { methodLogger.error(errmsg) throw new LogError(errmsg) } - if(recipientUser.emailContact?.email !== null){ + if(recipientUser.emailContact?.email !== null && senderUser.emailContact?.email !== null){ if (methodLogger.isDebugEnabled()) { - methodLogger.debug('Sending Transaction Received Email to recipient:', recipientUser.firstName, recipientUser.lastName) + methodLogger.debug('Sending Transaction Received Email to recipient=' + recipientUser.firstName + ' ' + recipientUser.lastName + 'sender=' + senderUser.firstName + ' ' + senderUser.lastName) } try { await sendTransactionReceivedEmail({ @@ -697,7 +697,7 @@ export class TransactionLinkResolver { } } else { if (methodLogger.isDebugEnabled()) { - methodLogger.debug('Recipient as foreign user has no email contact, not sending Transaction Received Email to recipient:', recipientUser.firstName, recipientUser.lastName) + methodLogger.debug('Sender or Recipient are foreign users with no email contact, not sending Transaction Received Email: recipient=' + recipientUser.firstName + ' ' + recipientUser.lastName + 'sender=' + senderUser.firstName + ' ' + senderUser.lastName) } } } catch (e) { From a305fa24b1901f0e79e6d3593261c17db4649941 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 8 Jan 2026 01:07:07 +0100 Subject: [PATCH 20/61] add email template transactionReceivedNoSender without sender-answering --- .../resolver/TransactionLinkResolver.ts | 41 ++++++++----------- .../transactionReceivedNoSender/html.pug | 23 +++++++++++ .../transactionReceivedNoSender/subject.pug | 1 + 3 files changed, 40 insertions(+), 25 deletions(-) create mode 100644 core/src/emails/templates/transactionReceivedNoSender/html.pug create mode 100644 core/src/emails/templates/transactionReceivedNoSender/subject.pug diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index d46d3b9f6..a224ffbbd 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -674,31 +674,22 @@ export class TransactionLinkResolver { methodLogger.error(errmsg) throw new LogError(errmsg) } - if(recipientUser.emailContact?.email !== null && senderUser.emailContact?.email !== null){ - if (methodLogger.isDebugEnabled()) { - methodLogger.debug('Sending Transaction Received Email to recipient=' + recipientUser.firstName + ' ' + recipientUser.lastName + 'sender=' + senderUser.firstName + ' ' + senderUser.lastName) - } - try { - await sendTransactionReceivedEmail({ - firstName: recipientFirstName, - lastName: recipientUser.lastName, - email: recipientUser.emailContact.email, - language: recipientUser.language, - memo, - senderFirstName: senderUser.firstName, - senderLastName: senderUser.lastName, - senderEmail: senderUser.emailContact.email, - transactionAmount: new Decimal(amount), - }) - } catch (e) { - const errmsg = `Send Transaction Received Email to recipient failed with error=${e}` - methodLogger.error(errmsg) - throw new Error(errmsg) - } - } else { - if (methodLogger.isDebugEnabled()) { - methodLogger.debug('Sender or Recipient are foreign users with no email contact, not sending Transaction Received Email: recipient=' + recipientUser.firstName + ' ' + recipientUser.lastName + 'sender=' + senderUser.firstName + ' ' + senderUser.lastName) - } + try { + await sendTransactionReceivedEmail({ + firstName: recipientFirstName, + lastName: recipientUser.lastName, + email: recipientUser.emailContact.email, + language: recipientUser.language, + memo, + senderFirstName: senderUser.firstName, + senderLastName: senderUser.lastName, + senderEmail: senderUser.emailContact.email, + transactionAmount: new Decimal(amount), + }) + } catch (e) { + const errmsg = `Send Transaction Received Email to recipient failed with error=${e}` + methodLogger.error(errmsg) + throw new Error(errmsg) } } catch (e) { const errmsg = `Disburse JWT was not sent successfully with error=${e}` diff --git a/core/src/emails/templates/transactionReceivedNoSender/html.pug b/core/src/emails/templates/transactionReceivedNoSender/html.pug new file mode 100644 index 000000000..a3d16e9b9 --- /dev/null +++ b/core/src/emails/templates/transactionReceivedNoSender/html.pug @@ -0,0 +1,23 @@ +extend ../layout.pug + +block content + mixin mailto(email, subject) + - var formattedSubject = encodeURIComponent(subject) + a(class!=attributes.class href=`mailto:${email}?subject=${formattedSubject}`) + block + + - var subject= t('emails.transactionReceived.replySubject', { senderFirstName, senderLastName, transactionAmount }) + h2= t('emails.transactionReceived.title', { senderFirstName, senderLastName, transactionAmount }) + .text-block + include ../includes/salutation.pug + p + = t('emails.transactionReceived.haveReceivedAmountGDDFrom', { transactionAmount, senderFirstName, senderLastName }) + .content + h2= t('emails.general.message') + .child-left + div(class="p_content")= memo + + a.button-3(href=`${communityURL}/transactions`) #{t('emails.general.toAccount')} + + + diff --git a/core/src/emails/templates/transactionReceivedNoSender/subject.pug b/core/src/emails/templates/transactionReceivedNoSender/subject.pug new file mode 100644 index 000000000..872806ebc --- /dev/null +++ b/core/src/emails/templates/transactionReceivedNoSender/subject.pug @@ -0,0 +1 @@ += t('emails.transactionReceived.subject', { senderFirstName, senderLastName, transactionAmount }) From 58a7629a979072107e49320049e5a00ce82948f6 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 8 Jan 2026 01:07:46 +0100 Subject: [PATCH 21/61] conditional email template usage --- core/src/emails/sendEmailVariants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/emails/sendEmailVariants.ts b/core/src/emails/sendEmailVariants.ts index f50f88181..7d9c7be71 100644 --- a/core/src/emails/sendEmailVariants.ts +++ b/core/src/emails/sendEmailVariants.ts @@ -182,7 +182,7 @@ export const sendTransactionReceivedEmail = ( ): Promise | boolean | null | Error> => { return sendEmailTranslated({ receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` }, - template: 'transactionReceived', + template: data.senderEmail ? 'transactionReceived' : 'transactionReceivedNoSender', locals: { ...data, transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language), From 21ec3cb96a87f6667dca1e3b8ad4f22456adbe92 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 8 Jan 2026 02:17:05 +0100 Subject: [PATCH 22/61] switch to sendTransactionLinkRedeemedEmail --- .../resolver/TransactionLinkResolver.ts | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index a224ffbbd..9342f8c11 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -15,6 +15,7 @@ import { EncryptedTransferArgs, fullName, interpretEncryptedTransferArgs, + sendTransactionLinkRedeemedEmail, sendTransactionReceivedEmail, TransactionTypeId, } from 'core' @@ -674,22 +675,31 @@ export class TransactionLinkResolver { methodLogger.error(errmsg) throw new LogError(errmsg) } - try { - await sendTransactionReceivedEmail({ - firstName: recipientFirstName, - lastName: recipientUser.lastName, - email: recipientUser.emailContact.email, - language: recipientUser.language, - memo, - senderFirstName: senderUser.firstName, - senderLastName: senderUser.lastName, - senderEmail: senderUser.emailContact.email, - transactionAmount: new Decimal(amount), - }) - } catch (e) { - const errmsg = `Send Transaction Received Email to recipient failed with error=${e}` - methodLogger.error(errmsg) - throw new Error(errmsg) + if(recipientUser.emailContact?.email !== null){ + if (methodLogger.isDebugEnabled()) { + methodLogger.debug('Sending Transaction Received Email to recipient=' + recipientUser.firstName + ' ' + recipientUser.lastName + 'sender=' + senderUser.firstName + ' ' + senderUser.lastName) + } + try { + await sendTransactionLinkRedeemedEmail({ + firstName: senderUser.firstName, + lastName: senderUser.lastName, + email: senderUser.emailContact.email, + language: senderUser.language, + senderFirstName: recipientFirstName, + senderLastName: recipientUser.lastName, + senderEmail: recipientUser.emailContact.email, + transactionMemo: memo, + transactionAmount: new Decimal(amount), + }) + } catch (e) { + const errmsg = `Send Transaction Received Email to recipient failed with error=${e}` + methodLogger.error(errmsg) + throw new Error(errmsg) + } + } else { + if (methodLogger.isDebugEnabled()) { + methodLogger.debug('Sender or Recipient are foreign users with no email contact, not sending Transaction Received Email: recipient=' + recipientUser.firstName + ' ' + recipientUser.lastName + 'sender=' + senderUser.firstName + ' ' + senderUser.lastName) + } } } catch (e) { const errmsg = `Disburse JWT was not sent successfully with error=${e}` From bf227200c027a0e24b69e3a4f8d4fcdebe76519b Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 8 Jan 2026 23:02:58 +0100 Subject: [PATCH 23/61] exchange sender / recipient for redeemlink email after disbursement --- .../resolver/TransactionLinkResolver.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 9342f8c11..16783a25a 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -677,22 +677,22 @@ export class TransactionLinkResolver { } if(recipientUser.emailContact?.email !== null){ if (methodLogger.isDebugEnabled()) { - methodLogger.debug('Sending Transaction Received Email to recipient=' + recipientUser.firstName + ' ' + recipientUser.lastName + 'sender=' + senderUser.firstName + ' ' + senderUser.lastName) + methodLogger.debug('Sending TransactionLinkRedeem Email to recipient=' + recipientUser.firstName + ' ' + recipientUser.lastName + 'sender=' + senderUser.firstName + ' ' + senderUser.lastName) } try { await sendTransactionLinkRedeemedEmail({ - firstName: senderUser.firstName, - lastName: senderUser.lastName, - email: senderUser.emailContact.email, - language: senderUser.language, - senderFirstName: recipientFirstName, - senderLastName: recipientUser.lastName, - senderEmail: recipientUser.emailContact.email, + firstName: recipientUser.firstName, + lastName: recipientUser.lastName, + email: recipientUser.emailContact.email, + language: recipientUser.language, + senderFirstName: senderUser.firstName, + senderLastName: senderUser.lastName, + senderEmail: senderUser.emailContact?.email, transactionMemo: memo, transactionAmount: new Decimal(amount), }) } catch (e) { - const errmsg = `Send Transaction Received Email to recipient failed with error=${e}` + const errmsg = `Send TransactionLinkRedeem Email to recipient failed with error=${e}` methodLogger.error(errmsg) throw new Error(errmsg) } From 1d7d93aee6593446dec95dd748e05a398675dcf0 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 13 Jan 2026 18:34:49 +0100 Subject: [PATCH 24/61] linting --- .../resolver/TransactionLinkResolver.ts | 24 ++++++++++++++++--- core/src/graphql/logic/storeForeignUser.ts | 2 +- .../SendCoinsResponseJwtPayloadType.ts | 1 - 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 16783a25a..193d539e5 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -675,9 +675,18 @@ export class TransactionLinkResolver { methodLogger.error(errmsg) throw new LogError(errmsg) } - if(recipientUser.emailContact?.email !== null){ + if (recipientUser.emailContact?.email !== null) { if (methodLogger.isDebugEnabled()) { - methodLogger.debug('Sending TransactionLinkRedeem Email to recipient=' + recipientUser.firstName + ' ' + recipientUser.lastName + 'sender=' + senderUser.firstName + ' ' + senderUser.lastName) + methodLogger.debug( + 'Sending TransactionLinkRedeem Email to recipient=' + + recipientUser.firstName + + ' ' + + recipientUser.lastName + + 'sender=' + + senderUser.firstName + + ' ' + + senderUser.lastName, + ) } try { await sendTransactionLinkRedeemedEmail({ @@ -698,7 +707,16 @@ export class TransactionLinkResolver { } } else { if (methodLogger.isDebugEnabled()) { - methodLogger.debug('Sender or Recipient are foreign users with no email contact, not sending Transaction Received Email: recipient=' + recipientUser.firstName + ' ' + recipientUser.lastName + 'sender=' + senderUser.firstName + ' ' + senderUser.lastName) + methodLogger.debug( + 'Sender or Recipient are foreign users with no email contact, not sending Transaction Received Email: recipient=' + + recipientUser.firstName + + ' ' + + recipientUser.lastName + + 'sender=' + + senderUser.firstName + + ' ' + + senderUser.lastName, + ) } } } catch (e) { diff --git a/core/src/graphql/logic/storeForeignUser.ts b/core/src/graphql/logic/storeForeignUser.ts index 90c7c7c7b..2fb07e23f 100644 --- a/core/src/graphql/logic/storeForeignUser.ts +++ b/core/src/graphql/logic/storeForeignUser.ts @@ -65,7 +65,7 @@ export async function storeForeignUser( } else if ( user.firstName !== committingResult.recipFirstName || user.lastName !== committingResult.recipLastName || - user.alias !== committingResult.recipAlias/* || + user.alias !== committingResult.recipAlias /* || (user.emailContact === null && committingResult.recipEmail !== null) || (user.emailContact !== null && user.emailContact?.email !== null && diff --git a/shared/src/jwt/payloadtypes/SendCoinsResponseJwtPayloadType.ts b/shared/src/jwt/payloadtypes/SendCoinsResponseJwtPayloadType.ts index a857cceae..fb08d6a97 100644 --- a/shared/src/jwt/payloadtypes/SendCoinsResponseJwtPayloadType.ts +++ b/shared/src/jwt/payloadtypes/SendCoinsResponseJwtPayloadType.ts @@ -9,7 +9,6 @@ export class SendCoinsResponseJwtPayloadType extends JwtPayloadType { recipLastName: string | null recipAlias: string | null - constructor( handshakeID: string, vote: boolean, From 47e94c167164d8102c18f0171f63b9944945aaf2 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 27 Jan 2026 02:10:57 +0100 Subject: [PATCH 25/61] sendEmail per CommandPattern --- backend/src/graphql/schema.ts | 2 + core/src/command/BaseCommand.ts | 15 ++++ core/src/command/Command.ts | 4 + core/src/command/CommandExecutor.ts | 68 +++++++++++++++++ core/src/command/CommandFactory.ts | 28 +++++++ core/src/command/CommandRegistry.ts | 24 ++++++ core/src/command/commands/ExampleCommands.ts | 26 +++++++ core/src/command/commands/SendEmailCommand.ts | 75 +++++++++++++++++++ core/src/command/initCommands.ts | 11 +++ .../federation/client/1_0/CommandClient.ts | 44 +++++++++++ .../client/1_0/query/sendCommand.ts | 11 +++ .../federation/client/1_1/CommandClient.ts | 3 + .../federation/client/CommandClientFactory.ts | 57 ++++++++++++++ core/src/graphql/logic/processCommand.ts | 10 +++ .../src/graphql/logic/processXComSendCoins.ts | 34 +++++++++ core/src/graphql/model/CommandResult.ts | 13 ++++ core/src/graphql/resolver/CommandResolver.ts | 19 +++++ core/src/index.ts | 9 +++ federation/src/graphql/api/1_0/schema.ts | 2 + federation/src/index.ts | 4 + shared/src/index.ts | 1 + .../jwt/payloadtypes/CommandJwtPayloadType.ts | 17 +++++ 22 files changed, 477 insertions(+) create mode 100644 core/src/command/BaseCommand.ts create mode 100644 core/src/command/Command.ts create mode 100644 core/src/command/CommandExecutor.ts create mode 100644 core/src/command/CommandFactory.ts create mode 100644 core/src/command/CommandRegistry.ts create mode 100644 core/src/command/commands/ExampleCommands.ts create mode 100644 core/src/command/commands/SendEmailCommand.ts create mode 100644 core/src/command/initCommands.ts create mode 100644 core/src/federation/client/1_0/CommandClient.ts create mode 100644 core/src/federation/client/1_0/query/sendCommand.ts create mode 100644 core/src/federation/client/1_1/CommandClient.ts create mode 100644 core/src/federation/client/CommandClientFactory.ts create mode 100644 core/src/graphql/logic/processCommand.ts create mode 100644 core/src/graphql/model/CommandResult.ts create mode 100644 core/src/graphql/resolver/CommandResolver.ts create mode 100644 shared/src/jwt/payloadtypes/CommandJwtPayloadType.ts diff --git a/backend/src/graphql/schema.ts b/backend/src/graphql/schema.ts index bebc3dbda..77e0d8469 100644 --- a/backend/src/graphql/schema.ts +++ b/backend/src/graphql/schema.ts @@ -6,6 +6,7 @@ import { buildSchema } from 'type-graphql' import { isAuthorized } from './directive/isAuthorized' import { AiChatResolver } from './resolver/AiChatResolver' import { BalanceResolver } from './resolver/BalanceResolver' +import { CommandResolver } from 'core/src/graphql/resolver/CommandResolver' import { CommunityResolver } from './resolver/CommunityResolver' import { ContributionLinkResolver } from './resolver/ContributionLinkResolver' import { ContributionMessageResolver } from './resolver/ContributionMessageResolver' @@ -25,6 +26,7 @@ export const schema = async (): Promise => { resolvers: [ AiChatResolver, BalanceResolver, + CommandResolver, CommunityResolver, ContributionLinkResolver, ContributionMessageResolver, diff --git a/core/src/command/BaseCommand.ts b/core/src/command/BaseCommand.ts new file mode 100644 index 000000000..a2c75d902 --- /dev/null +++ b/core/src/command/BaseCommand.ts @@ -0,0 +1,15 @@ +import { Command } from './Command'; + +export abstract class BaseCommand implements Command { + protected constructor(protected readonly params: any = {}) {} + + abstract execute(): Promise; + + validate(): boolean { + return true; // Default implementation, override in subclasses + } + + protected validateParams(requiredParams: string[]): boolean { + return requiredParams.every(param => this.params[param] !== undefined); + } +} diff --git a/core/src/command/Command.ts b/core/src/command/Command.ts new file mode 100644 index 000000000..c95e45af9 --- /dev/null +++ b/core/src/command/Command.ts @@ -0,0 +1,4 @@ +export interface Command { + execute(): Promise; + validate?(): boolean; +} diff --git a/core/src/command/CommandExecutor.ts b/core/src/command/CommandExecutor.ts new file mode 100644 index 000000000..ec7209d20 --- /dev/null +++ b/core/src/command/CommandExecutor.ts @@ -0,0 +1,68 @@ +// core/src/command/CommandExecutor.ts +import { CommandJwtPayloadType } from 'shared'; +import { interpretEncryptedTransferArgs } from '../graphql/logic/interpretEncryptedTransferArgs'; +import { EncryptedTransferArgs } from '../graphql/model/EncryptedTransferArgs'; +import { BaseCommand } from './BaseCommand'; +import { Command } from './Command'; +import { getLogger } from 'log4js'; +import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'; +import { CommandFactory } from './CommandFactory'; +import { CommandResult } from '../graphql/model/CommandResult'; + +const createLogger = (method: string) => + getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandExecutor.${method}`) + +export class CommandExecutor { + async executeCommand(command: Command): Promise { + const methodLogger = createLogger(`executeCommand`) + try { + if (command.validate && !command.validate()) { + return { success: false, error: 'Command validation failed' }; + } + methodLogger.debug(`executeCommand() executing command=${command.constructor.name}`) + const result = await command.execute(); + methodLogger.debug(`executeCommand() executed result=${JSON.stringify(result)}`) + return { success: true, data: result }; + } catch (error) { + methodLogger.error(`executeCommand() error=${error}`) + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error occurred' + }; + } + } + + async executeEncryptedCommand( + encryptedArgs: EncryptedTransferArgs + ): Promise { + const methodLogger = createLogger(`executeEncryptedCommand`) + try { + // Decrypt the command data + const commandArgs = (await interpretEncryptedTransferArgs(encryptedArgs)) as CommandJwtPayloadType + if (!commandArgs) { + const errmsg = `invalid commandArgs payload of requesting community with publicKey=${encryptedArgs.publicKey}` + methodLogger.error(errmsg) + throw new Error(errmsg) + } + if (methodLogger.isDebugEnabled()) { + methodLogger.debug(`executeEncryptedCommand() commandArgs=${JSON.stringify(commandArgs)}`) + } + const command = CommandFactory.getInstance().createCommand(commandArgs.commandName, commandArgs.params); + + // Execute the command + const result = await this.executeCommand(command); + if (methodLogger.isDebugEnabled()) { + methodLogger.debug(`executeCommand() result=${JSON.stringify(result)}`) + } + + return result + } catch (error) { + methodLogger.error(`executeEncryptedCommand() error=${error}`) + const errorResult: CommandResult = { + success: false, + error: error instanceof Error ? error.message : 'Failed to process command' + }; + return errorResult; + } + } +} diff --git a/core/src/command/CommandFactory.ts b/core/src/command/CommandFactory.ts new file mode 100644 index 000000000..41064ed44 --- /dev/null +++ b/core/src/command/CommandFactory.ts @@ -0,0 +1,28 @@ +import { Command } from './Command'; +import { BaseCommand } from './BaseCommand'; + +export class CommandFactory { + private static instance: CommandFactory; + private commands: Map Command> = new Map(); + + private constructor() {} + + static getInstance(): CommandFactory { + if (!CommandFactory.instance) { + CommandFactory.instance = new CommandFactory(); + } + return CommandFactory.instance; + } + + registerCommand(name: string, commandClass: new (params: any) => Command): void { + this.commands.set(name, commandClass); + } + + createCommand(name: string, params: any = {}): Command { + const CommandClass = this.commands.get(name); + if (!CommandClass) { + throw new Error(`Command ${name} not found`); + } + return new CommandClass(params) as Command; + } +} diff --git a/core/src/command/CommandRegistry.ts b/core/src/command/CommandRegistry.ts new file mode 100644 index 000000000..321ca370d --- /dev/null +++ b/core/src/command/CommandRegistry.ts @@ -0,0 +1,24 @@ +// core/src/command/CommandRegistry.ts +import { ICommand } from './CommandTypes'; + +export class CommandRegistry { + private static instance: CommandRegistry; + private commands: Map ICommand> = new Map(); + + private constructor() {} + + static getInstance(): CommandRegistry { + if (!CommandRegistry.instance) { + CommandRegistry.instance = new CommandRegistry(); + } + return CommandRegistry.instance; + } + + static registerCommand(type: string, commandClass: new (params: any) => ICommand): void { + this.getInstance().commands.set(type, commandClass); + } + + static getCommandClass(type: string): (new (params: any) => ICommand) | undefined { + return this.getInstance().commands.get(type); + } +} diff --git a/core/src/command/commands/ExampleCommands.ts b/core/src/command/commands/ExampleCommands.ts new file mode 100644 index 000000000..9fcc3787e --- /dev/null +++ b/core/src/command/commands/ExampleCommands.ts @@ -0,0 +1,26 @@ +// core/src/command/commands/ExampleCommand.ts +import { BaseCommand } from '../BaseCommand'; +import { CommandRegistry } from '../CommandRegistry'; + +export interface ExampleCommandParams { + someData: string; +} + +export class ExampleCommand extends BaseCommand<{ processed: boolean }> { + constructor(params: ExampleCommandParams) { + super('EXAMPLE_COMMAND', params); + } + + validate(): boolean { + return !!this.params.someData; + } + + async execute(): Promise<{ processed: boolean }> { + // Command implementation here + console.log('Executing ExampleCommand with data:', this.params.someData); + return { processed: true }; + } +} + +// Register the command +CommandRegistry.registerCommand('EXAMPLE_COMMAND', ExampleCommand); diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts new file mode 100644 index 000000000..f9abc51c7 --- /dev/null +++ b/core/src/command/commands/SendEmailCommand.ts @@ -0,0 +1,75 @@ +import { BaseCommand } from '../BaseCommand'; +import { sendTransactionReceivedEmail } from '../../emails/sendEmailVariants'; +import { findForeignUserByUuids, findUserByIdentifier } from 'database'; +import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'; +import { getLogger } from 'log4js'; + +const createLogger = (method: string) => + getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandExecutor.${method}`) + +export class SendEmailCommand extends BaseCommand<{ success: boolean }> { + static readonly SEND_MAIL_COMMAND = 'SEND_MAIL_COMMAND'; + + constructor(params: { + mailType: string, + senderComUuid: string, + senderGradidoId: string, + receiverComUuid: string, + receiverGradidoId: string, + memo?: string, + amount?: number, + }) { + super(params); + } + + validate(): boolean { + return this.validateParams(['mailType', 'senderComUuid', 'senderGradidoId', 'receiverComUuid', 'receiverGradidoId']); + } + + async execute(): Promise<{ success: boolean }> { + const methodLogger = createLogger(`execute`) + if (!this.validate()) { + throw new Error('Invalid command parameters'); + } + // find sender user + const senderUser = await findForeignUserByUuids(this.params.senderComUuid, this.params.senderGradidoId); + if (!senderUser) { + const errmsg = `Sender user not found: ${this.params.senderComUuid} ${this.params.senderGradidoId}`; + methodLogger.error(errmsg); + throw new Error(errmsg); + } + const recipientUser = await findUserByIdentifier(this.params.receiverGradidoId, this.params.receiverComUuid); + if (!recipientUser) { + const errmsg = `Recipient user not found: ${this.params.receiverComUuid} ${this.params.receiverGradidoId}`; + methodLogger.error(errmsg); + throw new Error(errmsg); + } + + const emailParams = { + firstName: recipientUser.firstName, + lastName: recipientUser.lastName, + email: recipientUser.emailContact.email, + language: recipientUser.language, + senderFirstName: senderUser.firstName, + senderLastName: senderUser.lastName, + senderEmail: senderUser.emailContact?.email, + memo: this.params.memo || '', + transactionAmount: this.params.amount || 0, + }; + switch(this.params.mailType) { + case 'sendTransactionReceivedEmail': + await sendTransactionReceivedEmail(emailParams); + break; + default: + throw new Error(`Unknown mail type: ${this.params.mailType}`); + } + + try { + // Example: const result = await emailService.sendEmail(this.params); + return { success: true }; + } catch (error) { + methodLogger.error('Error executing SendEmailCommand:', error); + throw error; + } + } +} \ No newline at end of file diff --git a/core/src/command/initCommands.ts b/core/src/command/initCommands.ts new file mode 100644 index 000000000..631335d26 --- /dev/null +++ b/core/src/command/initCommands.ts @@ -0,0 +1,11 @@ +import { CommandFactory } from './CommandFactory'; +import { SendEmailCommand } from './commands/SendEmailCommand'; +// Import other commands... + +export function initializeCommands(): void { + const factory = CommandFactory.getInstance(); + + // Register all commands + factory.registerCommand(SendEmailCommand.SEND_MAIL_COMMAND, SendEmailCommand); + // Register other commands... + } diff --git a/core/src/federation/client/1_0/CommandClient.ts b/core/src/federation/client/1_0/CommandClient.ts new file mode 100644 index 000000000..448615765 --- /dev/null +++ b/core/src/federation/client/1_0/CommandClient.ts @@ -0,0 +1,44 @@ +import { EncryptedTransferArgs, ensureUrlEndsWithSlash } from 'core' +import { FederatedCommunity as DbFederatedCommunity } from 'database' +import { GraphQLClient } from 'graphql-request' +import { getLogger } from 'log4js' +import { LOG4JS_BASE_CATEGORY_NAME } from '../../../config/const' +import { sendCommand } from './query/sendCommand' + +const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.client.1_0.CommandClient`) + +export class CommandClient { + dbCom: DbFederatedCommunity + endpoint: string + client: GraphQLClient + + constructor(dbCom: DbFederatedCommunity) { + this.dbCom = dbCom + this.endpoint = ensureUrlEndsWithSlash(dbCom.endPoint).concat(dbCom.apiVersion).concat('/') + this.client = new GraphQLClient(this.endpoint, { + method: 'POST', + jsonSerializer: { + parse: JSON.parse, + stringify: JSON.stringify, + }, + }) + } + + async sendCommand(args: EncryptedTransferArgs): Promise { + logger.debug(`sendCommand at ${this.endpoint} for args:`, args) + try { + const { data } = await this.client.rawRequest<{ success: boolean }>(sendCommand, { + args, + }) + if (!data?.success) { + logger.warn('sendCommand without response data from endpoint', this.endpoint) + return false + } + logger.debug('sendCommand successfully started with endpoint', this.endpoint) + return true + } catch (err) { + logger.error('error on sendCommand: ', err) + return false + } + } +} diff --git a/core/src/federation/client/1_0/query/sendCommand.ts b/core/src/federation/client/1_0/query/sendCommand.ts new file mode 100644 index 000000000..906833c82 --- /dev/null +++ b/core/src/federation/client/1_0/query/sendCommand.ts @@ -0,0 +1,11 @@ +import { gql } from 'graphql-request' + +export const sendCommand = gql` + mutation ($args: EncryptedTransferArgs!) { + sendCommand(encryptedArgs: $args) { + success + data + error + } + } +` diff --git a/core/src/federation/client/1_1/CommandClient.ts b/core/src/federation/client/1_1/CommandClient.ts new file mode 100644 index 000000000..2d0b35152 --- /dev/null +++ b/core/src/federation/client/1_1/CommandClient.ts @@ -0,0 +1,3 @@ +import { CommandClient as V1_0_CommandClient } from 'core/src/federation/client/1_0/CommandClient' + +export class CommandClient extends V1_0_CommandClient {} diff --git a/core/src/federation/client/CommandClientFactory.ts b/core/src/federation/client/CommandClientFactory.ts new file mode 100644 index 000000000..a03fb493a --- /dev/null +++ b/core/src/federation/client/CommandClientFactory.ts @@ -0,0 +1,57 @@ +import { ApiVersionType } from 'core' +import { FederatedCommunity as DbFederatedCommunity } from 'database' +import { CommandClient as V1_0_CommandClient } from './1_0/CommandClient' +import { CommandClient as V1_1_CommandClient } from './1_1/CommandClient' + +type CommandClient = V1_0_CommandClient | V1_1_CommandClient + +interface CommandClientInstance { + id: number + + client: CommandClient +} + +export class CommandClientFactory { + private static instanceArray: CommandClientInstance[] = [] + + /** + * The Singleton's constructor should always be private to prevent direct + * construction calls with the `new` operator. + */ + + private constructor() {} + + private static createCommandClient = (dbCom: DbFederatedCommunity) => { + switch (dbCom.apiVersion) { + case ApiVersionType.V1_0: + return new V1_0_CommandClient(dbCom) + case ApiVersionType.V1_1: + return new V1_1_CommandClient(dbCom) + default: + return null + } + } + + /** + * The static method that controls the access to the singleton instance. + * + * This implementation let you subclass the Singleton class while keeping + * just one instance of each subclass around. + */ + public static getInstance(dbCom: DbFederatedCommunity): CommandClient | null { + const instance = CommandClientFactory.instanceArray.find( + (instance) => instance.id === dbCom.id, + ) + if (instance) { + return instance.client + } + const client = CommandClientFactory.createCommandClient(dbCom) + if (client) { + CommandClientFactory.instanceArray.push({ + id: dbCom.id, + client, + } as CommandClientInstance) + } + return client + } +} diff --git a/core/src/graphql/logic/processCommand.ts b/core/src/graphql/logic/processCommand.ts new file mode 100644 index 000000000..355f71892 --- /dev/null +++ b/core/src/graphql/logic/processCommand.ts @@ -0,0 +1,10 @@ +import { getLogger } from 'log4js' +import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const' + +const createLogger = (method: string) => + getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.logic.processCommand.${method}`) + +export async function processCommand(commandClass: string, commandMethod: string, commandArgs: string[]) { + const methodLogger = createLogger(`processCommand`) + methodLogger.info('processing a command...') +} diff --git a/core/src/graphql/logic/processXComSendCoins.ts b/core/src/graphql/logic/processXComSendCoins.ts index 7b2ca3dc6..345c6e406 100644 --- a/core/src/graphql/logic/processXComSendCoins.ts +++ b/core/src/graphql/logic/processXComSendCoins.ts @@ -16,6 +16,7 @@ import { Decimal } from 'decimal.js-light' // import { LogError } from '@server/LogError' import { getLogger } from 'log4js' import { + CommandJwtPayloadType, encryptAndSign, PendingTransactionState, SendCoinsJwtPayloadType, @@ -30,6 +31,7 @@ import { SendCoinsResultLoggingView } from '../../federation/client/1_0/logging/ import { SendCoinsResult } from '../../federation/client/1_0/model/SendCoinsResult' import { SendCoinsClient as V1_0_SendCoinsClient } from '../../federation/client/1_0/SendCoinsClient' import { SendCoinsClientFactory } from '../../federation/client/SendCoinsClientFactory' +import { CommandClientFactory } from '../../federation/client/CommandClientFactory' import { TransactionTypeId } from '../../graphql/enum/TransactionTypeId' import { EncryptedTransferArgs } from '../../graphql/model/EncryptedTransferArgs' import { calculateSenderBalance } from '../../util/calculateSenderBalance' @@ -37,6 +39,8 @@ import { fullName } from '../../util/utilities' import { settlePendingSenderTransaction } from './settlePendingSenderTransaction' import { storeForeignUser } from './storeForeignUser' import { storeLinkAsRedeemed } from './storeLinkAsRedeemed' +import { V1_0_CommandClient } from '../..' +import { SendEmailCommand } from '../../command/commands/SendEmailCommand' const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.processXComSendCoins.${method}`) @@ -511,6 +515,36 @@ export async function processXComCommittingSendCoins( sendCoinsResult.recipGradidoID = pendingTx.linkedUserGradidoID sendCoinsResult.recipAlias = recipient.recipAlias } + // ** after successfull settle of the pending transaction on sender side we have to send a trigger to the recipient community to send an email to the x-com-tx recipient + const cmdClient = CommandClientFactory.getInstance(receiverFCom) + + if (cmdClient instanceof V1_0_CommandClient) { + const payload = new CommandJwtPayloadType( + handshakeID, + SendEmailCommand.SEND_MAIL_COMMAND, + SendEmailCommand.name, + [ + JSON.stringify({ + mailType: 'sendTransactionReceivedEmail', + senderComUuid: senderCom.communityUuid, + senderGradidoId: sender.gradidoID, + receiverComUuid: receiverCom.communityUuid, + receiverGradidoId: sendCoinsResult.recipGradidoID, + memo: pendingTx.memo, + amount: pendingTx.amount, + }), + ]) + const jws = await encryptAndSign( + payload, + senderCom.privateJwtKey!, + receiverCom.publicJwtKey!, + ) + const args = new EncryptedTransferArgs() + args.publicKey = senderCom.publicKey.toString('hex') + args.jwt = jws + args.handshakeID = handshakeID + cmdClient.sendCommand(args) + } } catch (err) { methodLogger.error( `Error in writing sender pending transaction: ${JSON.stringify(err, null, 2)}`, diff --git a/core/src/graphql/model/CommandResult.ts b/core/src/graphql/model/CommandResult.ts new file mode 100644 index 000000000..38b7e2dd4 --- /dev/null +++ b/core/src/graphql/model/CommandResult.ts @@ -0,0 +1,13 @@ +import { Field, ObjectType } from 'type-graphql' + +@ObjectType() +export class CommandResult { + @Field(() => Boolean) + success: boolean; + + @Field(() => String, { nullable: true }) + data?: any; + + @Field(() => String, { nullable: true }) + error?: string; +} diff --git a/core/src/graphql/resolver/CommandResolver.ts b/core/src/graphql/resolver/CommandResolver.ts new file mode 100644 index 000000000..eeedf9c34 --- /dev/null +++ b/core/src/graphql/resolver/CommandResolver.ts @@ -0,0 +1,19 @@ +// backend/src/graphql/resolver/CommandResolver.ts +import { Resolver, Mutation, Arg, Ctx } from 'type-graphql'; +import { CommandExecutor } from '../../command/CommandExecutor'; +import { CommandResult } from '../model/CommandResult'; + +@Resolver() +export class CommandResolver { + private commandExecutor = new CommandExecutor(); + + @Mutation(() => CommandResult) + async executeCommand( + @Arg('encryptedArgs', () => Object) encryptedArgs: any, + @Ctx() context: any + ): Promise { + // Convert to EncryptedTransferArgs if needed + const result = await this.commandExecutor.executeEncryptedCommand(encryptedArgs); + return result as unknown as CommandResult; + } +} diff --git a/core/src/index.ts b/core/src/index.ts index ddc204f5b..1c7817fb4 100644 --- a/core/src/index.ts +++ b/core/src/index.ts @@ -1,4 +1,7 @@ export * from './config/index' +export * from './command/CommandExecutor' +export * from './command/CommandFactory' +export * from './command/initCommands' export * from './emails' export * from './federation/client/1_0/logging/SendCoinsArgsLogging.view' export * from './federation/client/1_0/logging/SendCoinsResultLogging.view' @@ -8,18 +11,24 @@ export * from './federation/client/1_0/query/revertSendCoins' export * from './federation/client/1_0/query/revertSettledSendCoins' export * from './federation/client/1_0/query/settleSendCoins' export * from './federation/client/1_0/query/voteForSendCoins' +export { CommandClient as V1_0_CommandClient } from './federation/client/1_0/CommandClient' +export { CommandClient as V1_1_CommandClient } from './federation/client/1_1/CommandClient' export { SendCoinsClient as V1_0_SendCoinsClient } from './federation/client/1_0/SendCoinsClient' export { SendCoinsClient as V1_1_SendCoinsClient } from './federation/client/1_1/SendCoinsClient' +export * from './federation/client/CommandClientFactory' export * from './federation/client/SendCoinsClientFactory' export * from './federation/enum/apiVersionType' export * from './graphql/enum/TransactionTypeId' export * from './graphql/logging/DecayLogging.view' export * from './graphql/logic/interpretEncryptedTransferArgs' +export * from './graphql/logic/processCommand' export * from './graphql/logic/processXComSendCoins' export * from './graphql/logic/settlePendingSenderTransaction' export * from './graphql/logic/storeForeignUser' +export * from './graphql/model/CommandResult' export * from './graphql/model/Decay' export * from './graphql/model/EncryptedTransferArgs' +export * from './graphql/resolver/CommandResolver' export * from './logic' export * from './util/calculateSenderBalance' export * from './util/utilities' diff --git a/federation/src/graphql/api/1_0/schema.ts b/federation/src/graphql/api/1_0/schema.ts index 90ed13cb1..9c4741549 100644 --- a/federation/src/graphql/api/1_0/schema.ts +++ b/federation/src/graphql/api/1_0/schema.ts @@ -1,5 +1,6 @@ import { NonEmptyArray } from 'type-graphql' import { AuthenticationResolver } from './resolver/AuthenticationResolver' +import { CommandResolver } from 'core' import { DisbursementResolver } from './resolver/DisbursementResolver' import { PublicCommunityInfoResolver } from './resolver/PublicCommunityInfoResolver' import { PublicKeyResolver } from './resolver/PublicKeyResolver' @@ -8,6 +9,7 @@ import { SendCoinsResolver } from './resolver/SendCoinsResolver' export const getApiResolvers = (): NonEmptyArray => { return [ AuthenticationResolver, + CommandResolver, DisbursementResolver, PublicCommunityInfoResolver, PublicKeyResolver, diff --git a/federation/src/index.ts b/federation/src/index.ts index 421934c58..5dea4703f 100644 --- a/federation/src/index.ts +++ b/federation/src/index.ts @@ -7,6 +7,7 @@ import { onShutdown, printServerCrashAsciiArt, ShutdownReason } from 'shared' import { CONFIG } from './config' import { LOG4JS_BASE_CATEGORY_NAME } from './config/const' import { createServer } from './server/createServer' +import { initializeCommands } from 'core' async function main() { const startTime = new Date() @@ -44,6 +45,9 @@ async function main() { } }) }) + + initializeCommands() + } main().catch((e) => { diff --git a/shared/src/index.ts b/shared/src/index.ts index aff9f4ea9..ba3a5ad23 100644 --- a/shared/src/index.ts +++ b/shared/src/index.ts @@ -4,6 +4,7 @@ export * from './helper' export * from './jwt/JWT' export * from './jwt/payloadtypes/AuthenticationJwtPayloadType' export * from './jwt/payloadtypes/AuthenticationResponseJwtPayloadType' +export * from './jwt/payloadtypes/CommandJwtPayloadType' export * from './jwt/payloadtypes/DisburseJwtPayloadType' export * from './jwt/payloadtypes/EncryptedJWEJwtPayloadType' export * from './jwt/payloadtypes/JwtPayloadType' diff --git a/shared/src/jwt/payloadtypes/CommandJwtPayloadType.ts b/shared/src/jwt/payloadtypes/CommandJwtPayloadType.ts new file mode 100644 index 000000000..f41832466 --- /dev/null +++ b/shared/src/jwt/payloadtypes/CommandJwtPayloadType.ts @@ -0,0 +1,17 @@ +import { JwtPayloadType } from './JwtPayloadType' + +export class CommandJwtPayloadType extends JwtPayloadType { + static COMMAND_TYPE = 'command' + + commandName: string + commandClass: string + commandArgs: string[] + + constructor(handshakeID: string, commandName: string, commandClass: string, commandArgs: string[]) { + super(handshakeID) + this.tokentype = CommandJwtPayloadType.COMMAND_TYPE + this.commandName = commandName + this.commandClass = commandClass + this.commandArgs = commandArgs + } +} From 20044951f7c4ed6380c9373656411a52b3e6c5ab Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Wed, 28 Jan 2026 13:53:54 +0100 Subject: [PATCH 26/61] fix wrong import paths --- backend/src/graphql/schema.ts | 2 +- core/src/federation/client/1_1/CommandClient.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/schema.ts b/backend/src/graphql/schema.ts index 77e0d8469..28a35511d 100644 --- a/backend/src/graphql/schema.ts +++ b/backend/src/graphql/schema.ts @@ -6,7 +6,7 @@ import { buildSchema } from 'type-graphql' import { isAuthorized } from './directive/isAuthorized' import { AiChatResolver } from './resolver/AiChatResolver' import { BalanceResolver } from './resolver/BalanceResolver' -import { CommandResolver } from 'core/src/graphql/resolver/CommandResolver' +import { CommandResolver } from 'core' import { CommunityResolver } from './resolver/CommunityResolver' import { ContributionLinkResolver } from './resolver/ContributionLinkResolver' import { ContributionMessageResolver } from './resolver/ContributionMessageResolver' diff --git a/core/src/federation/client/1_1/CommandClient.ts b/core/src/federation/client/1_1/CommandClient.ts index 2d0b35152..70bfee1c3 100644 --- a/core/src/federation/client/1_1/CommandClient.ts +++ b/core/src/federation/client/1_1/CommandClient.ts @@ -1,3 +1,3 @@ -import { CommandClient as V1_0_CommandClient } from 'core/src/federation/client/1_0/CommandClient' +import { CommandClient as V1_0_CommandClient } from '../1_0/CommandClient' export class CommandClient extends V1_0_CommandClient {} From afa74f1b106c8abe0d4b8c1c8e83cce66ff63734 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 28 Jan 2026 22:21:53 +0100 Subject: [PATCH 27/61] change args type to EncryptedTransferArgs --- core/src/graphql/resolver/CommandResolver.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/graphql/resolver/CommandResolver.ts b/core/src/graphql/resolver/CommandResolver.ts index eeedf9c34..d5a1de360 100644 --- a/core/src/graphql/resolver/CommandResolver.ts +++ b/core/src/graphql/resolver/CommandResolver.ts @@ -2,6 +2,7 @@ import { Resolver, Mutation, Arg, Ctx } from 'type-graphql'; import { CommandExecutor } from '../../command/CommandExecutor'; import { CommandResult } from '../model/CommandResult'; +import { EncryptedTransferArgs } from '../model/EncryptedTransferArgs'; @Resolver() export class CommandResolver { @@ -9,7 +10,7 @@ export class CommandResolver { @Mutation(() => CommandResult) async executeCommand( - @Arg('encryptedArgs', () => Object) encryptedArgs: any, + @Arg('encryptedArgs', () => EncryptedTransferArgs) encryptedArgs: any, @Ctx() context: any ): Promise { // Convert to EncryptedTransferArgs if needed From 5155578df7b975dbb61aeac555dc653658a81d9e Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Fri, 30 Jan 2026 00:06:32 +0100 Subject: [PATCH 28/61] correct imports --- core/src/federation/client/1_0/CommandClient.ts | 3 ++- core/src/graphql/logic/processXComSendCoins.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/federation/client/1_0/CommandClient.ts b/core/src/federation/client/1_0/CommandClient.ts index 448615765..f5e88cad3 100644 --- a/core/src/federation/client/1_0/CommandClient.ts +++ b/core/src/federation/client/1_0/CommandClient.ts @@ -1,9 +1,10 @@ -import { EncryptedTransferArgs, ensureUrlEndsWithSlash } from 'core' +import { EncryptedTransferArgs } from '../../../graphql/model/EncryptedTransferArgs' import { FederatedCommunity as DbFederatedCommunity } from 'database' import { GraphQLClient } from 'graphql-request' import { getLogger } from 'log4js' import { LOG4JS_BASE_CATEGORY_NAME } from '../../../config/const' import { sendCommand } from './query/sendCommand' +import { ensureUrlEndsWithSlash } from '../../../util/utilities' const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.client.1_0.CommandClient`) diff --git a/core/src/graphql/logic/processXComSendCoins.ts b/core/src/graphql/logic/processXComSendCoins.ts index 345c6e406..9a4ba4fd0 100644 --- a/core/src/graphql/logic/processXComSendCoins.ts +++ b/core/src/graphql/logic/processXComSendCoins.ts @@ -39,7 +39,7 @@ import { fullName } from '../../util/utilities' import { settlePendingSenderTransaction } from './settlePendingSenderTransaction' import { storeForeignUser } from './storeForeignUser' import { storeLinkAsRedeemed } from './storeLinkAsRedeemed' -import { V1_0_CommandClient } from '../..' +import { CommandClient as V1_0_CommandClient } from '../../federation/client/1_0/CommandClient' import { SendEmailCommand } from '../../command/commands/SendEmailCommand' const createLogger = (method: string) => From b7d2568dcc0897e1d0d2976366808fd7ca1e695c Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Fri, 30 Jan 2026 01:21:20 +0100 Subject: [PATCH 29/61] rename sendCommandQuery --- core/src/federation/client/1_0/CommandClient.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/federation/client/1_0/CommandClient.ts b/core/src/federation/client/1_0/CommandClient.ts index f5e88cad3..817fb7bd7 100644 --- a/core/src/federation/client/1_0/CommandClient.ts +++ b/core/src/federation/client/1_0/CommandClient.ts @@ -3,7 +3,7 @@ import { FederatedCommunity as DbFederatedCommunity } from 'database' import { GraphQLClient } from 'graphql-request' import { getLogger } from 'log4js' import { LOG4JS_BASE_CATEGORY_NAME } from '../../../config/const' -import { sendCommand } from './query/sendCommand' +import { sendCommand as sendCommandQuery} from './query/sendCommand' import { ensureUrlEndsWithSlash } from '../../../util/utilities' const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.client.1_0.CommandClient`) @@ -28,7 +28,7 @@ export class CommandClient { async sendCommand(args: EncryptedTransferArgs): Promise { logger.debug(`sendCommand at ${this.endpoint} for args:`, args) try { - const { data } = await this.client.rawRequest<{ success: boolean }>(sendCommand, { + const { data } = await this.client.rawRequest<{ success: boolean }>(sendCommandQuery, { args, }) if (!data?.success) { From f6dc8ef02c4fd195dd734ff3132d670ba1fd2cf2 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 3 Feb 2026 00:19:31 +0100 Subject: [PATCH 30/61] shift CommandResolver from core to federation modul --- core/src/index.ts | 1 - core/tsconfig.json | 1 - .../src/graphql/api/1_0}/resolver/CommandResolver.ts | 7 +++---- federation/src/graphql/api/1_0/schema.ts | 2 +- federation/src/graphql/api/1_1/schema.ts | 3 ++- 5 files changed, 6 insertions(+), 8 deletions(-) rename {core/src/graphql => federation/src/graphql/api/1_0}/resolver/CommandResolver.ts (68%) diff --git a/core/src/index.ts b/core/src/index.ts index 1c7817fb4..3bbfdc578 100644 --- a/core/src/index.ts +++ b/core/src/index.ts @@ -28,7 +28,6 @@ export * from './graphql/logic/storeForeignUser' export * from './graphql/model/CommandResult' export * from './graphql/model/Decay' export * from './graphql/model/EncryptedTransferArgs' -export * from './graphql/resolver/CommandResolver' export * from './logic' export * from './util/calculateSenderBalance' export * from './util/utilities' diff --git a/core/tsconfig.json b/core/tsconfig.json index 711c21cc9..9b7d654d2 100644 --- a/core/tsconfig.json +++ b/core/tsconfig.json @@ -76,7 +76,6 @@ }, "include": [ "src/**/*.ts", - "src/**/*.json", ], "references": [], /* Any project that is referenced must itself have a `references` array (which may be empty). */ "exclude": ["**/*.test.ts", "**/*.spec.ts", "test/*", "**/bun.d.ts", "esbuild.config.ts"], diff --git a/core/src/graphql/resolver/CommandResolver.ts b/federation/src/graphql/api/1_0/resolver/CommandResolver.ts similarity index 68% rename from core/src/graphql/resolver/CommandResolver.ts rename to federation/src/graphql/api/1_0/resolver/CommandResolver.ts index d5a1de360..1302f62f0 100644 --- a/core/src/graphql/resolver/CommandResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/CommandResolver.ts @@ -1,8 +1,7 @@ -// backend/src/graphql/resolver/CommandResolver.ts import { Resolver, Mutation, Arg, Ctx } from 'type-graphql'; -import { CommandExecutor } from '../../command/CommandExecutor'; -import { CommandResult } from '../model/CommandResult'; -import { EncryptedTransferArgs } from '../model/EncryptedTransferArgs'; +import { CommandExecutor } from 'core'; +import { CommandResult } from 'core'; +import { EncryptedTransferArgs } from 'core'; @Resolver() export class CommandResolver { diff --git a/federation/src/graphql/api/1_0/schema.ts b/federation/src/graphql/api/1_0/schema.ts index 9c4741549..ebb885755 100644 --- a/federation/src/graphql/api/1_0/schema.ts +++ b/federation/src/graphql/api/1_0/schema.ts @@ -1,6 +1,6 @@ import { NonEmptyArray } from 'type-graphql' import { AuthenticationResolver } from './resolver/AuthenticationResolver' -import { CommandResolver } from 'core' +import { CommandResolver } from './resolver/CommandResolver' import { DisbursementResolver } from './resolver/DisbursementResolver' import { PublicCommunityInfoResolver } from './resolver/PublicCommunityInfoResolver' import { PublicKeyResolver } from './resolver/PublicKeyResolver' diff --git a/federation/src/graphql/api/1_1/schema.ts b/federation/src/graphql/api/1_1/schema.ts index 07871cefa..891c83dc9 100644 --- a/federation/src/graphql/api/1_1/schema.ts +++ b/federation/src/graphql/api/1_1/schema.ts @@ -3,7 +3,8 @@ import { AuthenticationResolver } from '../1_0/resolver/AuthenticationResolver' import { PublicCommunityInfoResolver } from '../1_0/resolver/PublicCommunityInfoResolver' import { SendCoinsResolver } from '../1_0/resolver/SendCoinsResolver' import { PublicKeyResolver } from './resolver/PublicKeyResolver' +import { CommandResolver } from '../1_0/resolver/CommandResolver' export const getApiResolvers = (): NonEmptyArray => { - return [AuthenticationResolver, PublicCommunityInfoResolver, PublicKeyResolver, SendCoinsResolver] + return [AuthenticationResolver, CommandResolver, PublicCommunityInfoResolver, PublicKeyResolver, SendCoinsResolver] } From e9e8fbac98929f7c5528d6c99dfd2d9c9fa5cf50 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 3 Feb 2026 00:55:52 +0100 Subject: [PATCH 31/61] rename to sendCommand --- federation/src/graphql/api/1_0/resolver/CommandResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/federation/src/graphql/api/1_0/resolver/CommandResolver.ts b/federation/src/graphql/api/1_0/resolver/CommandResolver.ts index 1302f62f0..559ae4963 100644 --- a/federation/src/graphql/api/1_0/resolver/CommandResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/CommandResolver.ts @@ -8,7 +8,7 @@ export class CommandResolver { private commandExecutor = new CommandExecutor(); @Mutation(() => CommandResult) - async executeCommand( + async sendCommand( @Arg('encryptedArgs', () => EncryptedTransferArgs) encryptedArgs: any, @Ctx() context: any ): Promise { From 5d69d3720c723cad2183279b77673d947b4f6e45 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 4 Feb 2026 23:42:08 +0100 Subject: [PATCH 32/61] change validation of mandatory Command-parameters --- core/src/command/BaseCommand.ts | 27 +++++++++++++++++-- core/src/command/CommandExecutor.ts | 5 +++- core/src/command/commands/SendEmailCommand.ts | 7 ++++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/core/src/command/BaseCommand.ts b/core/src/command/BaseCommand.ts index a2c75d902..b1386d6ab 100644 --- a/core/src/command/BaseCommand.ts +++ b/core/src/command/BaseCommand.ts @@ -1,10 +1,14 @@ import { Command } from './Command'; export abstract class BaseCommand implements Command { - protected constructor(protected readonly params: any = {}) {} + protected abstract requiredFields: string[]; + + protected constructor(protected readonly params: any = {}) { + this.validateRequiredFields(); + } abstract execute(): Promise; - +/* validate(): boolean { return true; // Default implementation, override in subclasses } @@ -12,4 +16,23 @@ export abstract class BaseCommand implements Command { protected validateParams(requiredParams: string[]): boolean { return requiredParams.every(param => this.params[param] !== undefined); } +*/ + private validateRequiredFields(): void { + const missingFields = this.requiredFields.filter(field => + this.params[field] === undefined || this.params[field] === null || this.params[field] === '' + ); + + if (missingFields.length > 0) { + throw new Error(`Missing required fields for ${this.constructor.name}: ${missingFields.join(', ')}`); + } + } + + validate(): boolean { + return this.requiredFields.every(field => + this.params[field] !== undefined && + this.params[field] !== null && + this.params[field] !== '' + ); + } + } diff --git a/core/src/command/CommandExecutor.ts b/core/src/command/CommandExecutor.ts index ec7209d20..c0cf397a5 100644 --- a/core/src/command/CommandExecutor.ts +++ b/core/src/command/CommandExecutor.ts @@ -15,9 +15,12 @@ const createLogger = (method: string) => export class CommandExecutor { async executeCommand(command: Command): Promise { const methodLogger = createLogger(`executeCommand`) + methodLogger.debug(`executeCommand() command=${command.constructor.name}`) try { if (command.validate && !command.validate()) { - return { success: false, error: 'Command validation failed' }; + const errmsg = `Command validation failed for command=${command.constructor.name}` + methodLogger.error(errmsg) + return { success: false, error: errmsg }; } methodLogger.debug(`executeCommand() executing command=${command.constructor.name}`) const result = await command.execute(); diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index f9abc51c7..2caf51b76 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -9,6 +9,7 @@ const createLogger = (method: string) => export class SendEmailCommand extends BaseCommand<{ success: boolean }> { static readonly SEND_MAIL_COMMAND = 'SEND_MAIL_COMMAND'; + protected requiredFields: string[] = ['mailType', 'senderComUuid', 'senderGradidoId', 'receiverComUuid', 'receiverGradidoId']; constructor(params: { mailType: string, @@ -23,7 +24,11 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { } validate(): boolean { - return this.validateParams(['mailType', 'senderComUuid', 'senderGradidoId', 'receiverComUuid', 'receiverGradidoId']); + const baseValid = super.validate(); + if (!baseValid) { + return false; + } + return true; } async execute(): Promise<{ success: boolean }> { From 299a77b034ac6d1c72ee47795d62e6a5ccf05878 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 5 Feb 2026 00:57:34 +0100 Subject: [PATCH 33/61] add logger --- core/src/command/CommandExecutor.ts | 3 +++ core/src/command/CommandFactory.ts | 20 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/core/src/command/CommandExecutor.ts b/core/src/command/CommandExecutor.ts index c0cf397a5..1ec8b9a86 100644 --- a/core/src/command/CommandExecutor.ts +++ b/core/src/command/CommandExecutor.ts @@ -51,6 +51,9 @@ export class CommandExecutor { methodLogger.debug(`executeEncryptedCommand() commandArgs=${JSON.stringify(commandArgs)}`) } const command = CommandFactory.getInstance().createCommand(commandArgs.commandName, commandArgs.params); + if (methodLogger.isDebugEnabled()) { + methodLogger.debug(`executeEncryptedCommand() command=${JSON.stringify(command)}`) + } // Execute the command const result = await this.executeCommand(command); diff --git a/core/src/command/CommandFactory.ts b/core/src/command/CommandFactory.ts index 41064ed44..b4272200e 100644 --- a/core/src/command/CommandFactory.ts +++ b/core/src/command/CommandFactory.ts @@ -1,5 +1,10 @@ import { Command } from './Command'; import { BaseCommand } from './BaseCommand'; +import { getLogger } from 'log4js'; +import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'; + +const createLogger = (method: string) => + getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandFactory.${method}`) export class CommandFactory { private static instance: CommandFactory; @@ -16,13 +21,24 @@ export class CommandFactory { registerCommand(name: string, commandClass: new (params: any) => Command): void { this.commands.set(name, commandClass); + const methodLogger = createLogger(`registerCommand`) + if (methodLogger.isDebugEnabled()) { + methodLogger.debug(`registerCommand() name=${name}`) + } } createCommand(name: string, params: any = {}): Command { + const methodLogger = createLogger(`createCommand`) const CommandClass = this.commands.get(name); if (!CommandClass) { - throw new Error(`Command ${name} not found`); + const errmsg = `Command ${name} not found`; + methodLogger.error(errmsg); + throw new Error(errmsg); } - return new CommandClass(params) as Command; + const command = new CommandClass(params) as Command; + if (methodLogger.isDebugEnabled()) { + methodLogger.debug(`createCommand() command=${JSON.stringify(command)}`) + } + return command; } } From db2b35d148722e28466ee15747ba43282481d1dc Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 5 Feb 2026 01:32:47 +0100 Subject: [PATCH 34/61] add loggers --- core/src/command/CommandFactory.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/command/CommandFactory.ts b/core/src/command/CommandFactory.ts index b4272200e..af367b276 100644 --- a/core/src/command/CommandFactory.ts +++ b/core/src/command/CommandFactory.ts @@ -20,15 +20,21 @@ export class CommandFactory { } registerCommand(name: string, commandClass: new (params: any) => Command): void { - this.commands.set(name, commandClass); const methodLogger = createLogger(`registerCommand`) if (methodLogger.isDebugEnabled()) { methodLogger.debug(`registerCommand() name=${name}`) } + this.commands.set(name, commandClass); + if (methodLogger.isDebugEnabled()) { + methodLogger.debug(`registerCommand() commands=${JSON.stringify(Array.from(this.commands.entries()))}`) + } } createCommand(name: string, params: any = {}): Command { const methodLogger = createLogger(`createCommand`) + if (methodLogger.isDebugEnabled()) { + methodLogger.debug(`createCommand() name=${name} params=${JSON.stringify(params)}`) + } const CommandClass = this.commands.get(name); if (!CommandClass) { const errmsg = `Command ${name} not found`; From dd27cd5313acca7044430db05d3a213a6a87911d Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 5 Feb 2026 23:06:17 +0100 Subject: [PATCH 35/61] correct implementation of command registering and creation --- core/src/command/CommandFactory.ts | 10 ++++++--- core/src/command/CommandTypes.ts | 5 +++++ core/src/command/commands/SendEmailCommand.ts | 21 +++++++++++-------- 3 files changed, 24 insertions(+), 12 deletions(-) create mode 100644 core/src/command/CommandTypes.ts diff --git a/core/src/command/CommandFactory.ts b/core/src/command/CommandFactory.ts index af367b276..7f39d3b02 100644 --- a/core/src/command/CommandFactory.ts +++ b/core/src/command/CommandFactory.ts @@ -2,13 +2,14 @@ import { Command } from './Command'; import { BaseCommand } from './BaseCommand'; import { getLogger } from 'log4js'; import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'; +import { ICommandConstructor } from './CommandTypes'; const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandFactory.${method}`) export class CommandFactory { private static instance: CommandFactory; - private commands: Map Command> = new Map(); + private commands: Map = new Map(); private constructor() {} @@ -19,10 +20,10 @@ export class CommandFactory { return CommandFactory.instance; } - registerCommand(name: string, commandClass: new (params: any) => Command): void { + registerCommand(name: string, commandClass: ICommandConstructor): void { const methodLogger = createLogger(`registerCommand`) if (methodLogger.isDebugEnabled()) { - methodLogger.debug(`registerCommand() name=${name}`) + methodLogger.debug(`registerCommand() name=${name}, commandClass=${commandClass.name}`) } this.commands.set(name, commandClass); if (methodLogger.isDebugEnabled()) { @@ -36,6 +37,9 @@ export class CommandFactory { methodLogger.debug(`createCommand() name=${name} params=${JSON.stringify(params)}`) } const CommandClass = this.commands.get(name); + if (methodLogger.isDebugEnabled()) { + methodLogger.debug(`createCommand() name=${name} commandClass=${CommandClass ? CommandClass.name : 'null'}`) + } if (!CommandClass) { const errmsg = `Command ${name} not found`; methodLogger.error(errmsg); diff --git a/core/src/command/CommandTypes.ts b/core/src/command/CommandTypes.ts new file mode 100644 index 000000000..70fc16515 --- /dev/null +++ b/core/src/command/CommandTypes.ts @@ -0,0 +1,5 @@ +import { Command } from "./Command"; + +export interface ICommandConstructor { + new (params: any): Command; +} diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index 2caf51b76..78db629bc 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -7,19 +7,20 @@ import { getLogger } from 'log4js'; const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandExecutor.${method}`) +export interface SendEmailCommandParams { + mailType: string; + senderComUuid: string; + senderGradidoId: string; + receiverComUuid: string; + receiverGradidoId: string; + memo?: string; + amount?: number; +} export class SendEmailCommand extends BaseCommand<{ success: boolean }> { static readonly SEND_MAIL_COMMAND = 'SEND_MAIL_COMMAND'; protected requiredFields: string[] = ['mailType', 'senderComUuid', 'senderGradidoId', 'receiverComUuid', 'receiverGradidoId']; - constructor(params: { - mailType: string, - senderComUuid: string, - senderGradidoId: string, - receiverComUuid: string, - receiverGradidoId: string, - memo?: string, - amount?: number, - }) { + constructor(params: SendEmailCommandParams) { super(params); } @@ -28,6 +29,8 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { if (!baseValid) { return false; } + // Additional validations + return true; } From 6913fc7a4d3cd90c6b134a72b4dd7f3ad2c662d5 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Fri, 6 Feb 2026 00:36:59 +0100 Subject: [PATCH 36/61] correct commandArgs parameter on command creation --- core/src/command/CommandExecutor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/command/CommandExecutor.ts b/core/src/command/CommandExecutor.ts index 1ec8b9a86..0c5301d6c 100644 --- a/core/src/command/CommandExecutor.ts +++ b/core/src/command/CommandExecutor.ts @@ -50,7 +50,7 @@ export class CommandExecutor { if (methodLogger.isDebugEnabled()) { methodLogger.debug(`executeEncryptedCommand() commandArgs=${JSON.stringify(commandArgs)}`) } - const command = CommandFactory.getInstance().createCommand(commandArgs.commandName, commandArgs.params); + const command = CommandFactory.getInstance().createCommand(commandArgs.commandName, commandArgs.commandArgs); if (methodLogger.isDebugEnabled()) { methodLogger.debug(`executeEncryptedCommand() command=${JSON.stringify(command)}`) } From 4a71bec63cb2102d579637610b92bfebc1afe1b4 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Fri, 6 Feb 2026 01:23:16 +0100 Subject: [PATCH 37/61] correct requiredFields definition --- core/src/command/commands/SendEmailCommand.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index 78db629bc..6395724df 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -18,9 +18,11 @@ export interface SendEmailCommandParams { } export class SendEmailCommand extends BaseCommand<{ success: boolean }> { static readonly SEND_MAIL_COMMAND = 'SEND_MAIL_COMMAND'; - protected requiredFields: string[] = ['mailType', 'senderComUuid', 'senderGradidoId', 'receiverComUuid', 'receiverGradidoId']; + requiredFields: string[] = ['mailType', 'senderComUuid', 'senderGradidoId', 'receiverComUuid', 'receiverGradidoId']; constructor(params: SendEmailCommandParams) { + const methodLogger = createLogger(`constructor`) + methodLogger.debug(`constructor() params=${JSON.stringify(params)}`) super(params); } From 4eef68e334524143204368c92598f14ee5184382 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Mon, 9 Feb 2026 22:59:30 +0100 Subject: [PATCH 38/61] correct validRequiredFields --- core/src/command/BaseCommand.ts | 16 ++++++++++++++-- core/src/command/commands/SendEmailCommand.ts | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/core/src/command/BaseCommand.ts b/core/src/command/BaseCommand.ts index b1386d6ab..46590ae48 100644 --- a/core/src/command/BaseCommand.ts +++ b/core/src/command/BaseCommand.ts @@ -1,10 +1,15 @@ +import { getLogger } from 'log4js'; import { Command } from './Command'; +import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'; + +const createLogger = (method: string) => + getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandExecutor.${method}`) export abstract class BaseCommand implements Command { protected abstract requiredFields: string[]; - + protected constructor(protected readonly params: any = {}) { - this.validateRequiredFields(); + // this.validateRequiredFields(); } abstract execute(): Promise; @@ -18,11 +23,18 @@ export abstract class BaseCommand implements Command { } */ private validateRequiredFields(): void { + if(!this.requiredFields || this.requiredFields.length === 0) { + const methodLogger = createLogger(`validateRequiredFields`) + methodLogger.debug(`validateRequiredFields() no required fields`) + return; + } const missingFields = this.requiredFields.filter(field => this.params[field] === undefined || this.params[field] === null || this.params[field] === '' ); if (missingFields.length > 0) { + const methodLogger = createLogger(`validateRequiredFields`) + methodLogger.error(`validateRequiredFields() missing fields: ${missingFields.join(', ')}`) throw new Error(`Missing required fields for ${this.constructor.name}: ${missingFields.join(', ')}`); } } diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index 6395724df..a034eb025 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -18,7 +18,7 @@ export interface SendEmailCommandParams { } export class SendEmailCommand extends BaseCommand<{ success: boolean }> { static readonly SEND_MAIL_COMMAND = 'SEND_MAIL_COMMAND'; - requiredFields: string[] = ['mailType', 'senderComUuid', 'senderGradidoId', 'receiverComUuid', 'receiverGradidoId']; + protected requiredFields: string[] = ['mailType', 'senderComUuid', 'senderGradidoId', 'receiverComUuid', 'receiverGradidoId']; constructor(params: SendEmailCommandParams) { const methodLogger = createLogger(`constructor`) From 4de64ea070f0aa8fdd73b3859da59f9dbce85de1 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Mon, 9 Feb 2026 23:28:31 +0100 Subject: [PATCH 39/61] add logger --- core/src/command/BaseCommand.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/core/src/command/BaseCommand.ts b/core/src/command/BaseCommand.ts index 46590ae48..1bb7b7b90 100644 --- a/core/src/command/BaseCommand.ts +++ b/core/src/command/BaseCommand.ts @@ -13,38 +13,36 @@ export abstract class BaseCommand implements Command { } abstract execute(): Promise; -/* - validate(): boolean { - return true; // Default implementation, override in subclasses - } - protected validateParams(requiredParams: string[]): boolean { - return requiredParams.every(param => this.params[param] !== undefined); - } -*/ private validateRequiredFields(): void { + const methodLogger = createLogger(`validateRequiredFields`) if(!this.requiredFields || this.requiredFields.length === 0) { - const methodLogger = createLogger(`validateRequiredFields`) methodLogger.debug(`validateRequiredFields() no required fields`) return; } + methodLogger.debug(`validateRequiredFields() requiredFields=${JSON.stringify(this.requiredFields)}`) const missingFields = this.requiredFields.filter(field => this.params[field] === undefined || this.params[field] === null || this.params[field] === '' ); + methodLogger.debug(`validateRequiredFields() missingFields=${JSON.stringify(missingFields)}`) if (missingFields.length > 0) { - const methodLogger = createLogger(`validateRequiredFields`) methodLogger.error(`validateRequiredFields() missing fields: ${missingFields.join(', ')}`) throw new Error(`Missing required fields for ${this.constructor.name}: ${missingFields.join(', ')}`); } } validate(): boolean { - return this.requiredFields.every(field => + const methodLogger = createLogger(`validate`) + methodLogger.debug(`validate() params=${JSON.stringify(this.params)}`) + + const isValid = this.requiredFields.every(field => this.params[field] !== undefined && this.params[field] !== null && this.params[field] !== '' ); + methodLogger.debug(`validate() isValid=${isValid}`) + return isValid; } } From 7615d6efb14a407535a306bc23e67927a4ec2968 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 10 Feb 2026 00:05:44 +0100 Subject: [PATCH 40/61] surround command instanciation with try-catch --- core/src/command/CommandFactory.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/core/src/command/CommandFactory.ts b/core/src/command/CommandFactory.ts index 7f39d3b02..cc62681e9 100644 --- a/core/src/command/CommandFactory.ts +++ b/core/src/command/CommandFactory.ts @@ -38,17 +38,24 @@ export class CommandFactory { } const CommandClass = this.commands.get(name); if (methodLogger.isDebugEnabled()) { - methodLogger.debug(`createCommand() name=${name} commandClass=${CommandClass ? CommandClass.name : 'null'}`) + methodLogger.debug(`createCommand() name=${name} commandClass=${CommandClass ? JSON.stringify(CommandClass) : 'null'}`) } if (!CommandClass) { const errmsg = `Command ${name} not found`; methodLogger.error(errmsg); throw new Error(errmsg); } - const command = new CommandClass(params) as Command; - if (methodLogger.isDebugEnabled()) { - methodLogger.debug(`createCommand() command=${JSON.stringify(command)}`) + try { + const command = new CommandClass(params) as Command; + if (methodLogger.isDebugEnabled()) { + methodLogger.debug(`createCommand() command=${JSON.stringify(command)}`) + } + return command; + } catch (error) { + const errmsg = `Failed to create command ${name}: ${error instanceof Error ? error.message : 'Unknown error'}`; + methodLogger.error(errmsg); + throw new Error(errmsg); } - return command; + } } From 92077f954d9f7a086f9adf0d1e7c1397ef7d567a Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 10 Feb 2026 02:42:39 +0100 Subject: [PATCH 41/61] change check for commandClass --- core/src/command/CommandFactory.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/command/CommandFactory.ts b/core/src/command/CommandFactory.ts index cc62681e9..d9acd1a0e 100644 --- a/core/src/command/CommandFactory.ts +++ b/core/src/command/CommandFactory.ts @@ -27,7 +27,7 @@ export class CommandFactory { } this.commands.set(name, commandClass); if (methodLogger.isDebugEnabled()) { - methodLogger.debug(`registerCommand() commands=${JSON.stringify(Array.from(this.commands.entries()))}`) + methodLogger.debug(`registerCommand() commands=${JSON.stringify(this.commands.entries())}`) } } @@ -38,9 +38,9 @@ export class CommandFactory { } const CommandClass = this.commands.get(name); if (methodLogger.isDebugEnabled()) { - methodLogger.debug(`createCommand() name=${name} commandClass=${CommandClass ? JSON.stringify(CommandClass) : 'null'}`) + methodLogger.debug(`createCommand() name=${name} commandClass=${CommandClass ? CommandClass.name : 'null'}`) } - if (!CommandClass) { + if (CommandClass === undefined) { const errmsg = `Command ${name} not found`; methodLogger.error(errmsg); throw new Error(errmsg); From fa4e104db7c922f4be87c2acb73ec690b8d018a9 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 11 Feb 2026 16:37:39 +0100 Subject: [PATCH 42/61] now with explizit command instantiation --- core/src/command/CommandFactory.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/core/src/command/CommandFactory.ts b/core/src/command/CommandFactory.ts index d9acd1a0e..3fa1510ce 100644 --- a/core/src/command/CommandFactory.ts +++ b/core/src/command/CommandFactory.ts @@ -3,6 +3,7 @@ import { BaseCommand } from './BaseCommand'; import { getLogger } from 'log4js'; import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'; import { ICommandConstructor } from './CommandTypes'; +import { SendEmailCommand } from './commands/SendEmailCommand'; const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandFactory.${method}`) @@ -45,6 +46,7 @@ export class CommandFactory { methodLogger.error(errmsg); throw new Error(errmsg); } + /* try { const command = new CommandClass(params) as Command; if (methodLogger.isDebugEnabled()) { @@ -56,6 +58,20 @@ export class CommandFactory { methodLogger.error(errmsg); throw new Error(errmsg); } - + */ + let command: BaseCommand; + switch(CommandClass.name) { + case 'SendEmailCommand': + command = new SendEmailCommand(params); + break; + default: + const errmsg = `Command ${name} not found`; + methodLogger.error(errmsg); + throw new Error(errmsg); + } + if (methodLogger.isDebugEnabled()) { + methodLogger.debug(`createCommand() created command=${JSON.stringify(command)}`) + } + return command } } From fcba1bc526b5b4bed230dcb7b2bbaa70cfbd829d Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 11 Feb 2026 16:44:45 +0100 Subject: [PATCH 43/61] correct logger names --- core/src/command/BaseCommand.ts | 2 +- core/src/command/commands/SendEmailCommand.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/command/BaseCommand.ts b/core/src/command/BaseCommand.ts index 1bb7b7b90..59c76e5fe 100644 --- a/core/src/command/BaseCommand.ts +++ b/core/src/command/BaseCommand.ts @@ -3,7 +3,7 @@ import { Command } from './Command'; import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'; const createLogger = (method: string) => - getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandExecutor.${method}`) + getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.BaseCommand.${method}`) export abstract class BaseCommand implements Command { protected abstract requiredFields: string[]; diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index a034eb025..957e08bc8 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -5,7 +5,7 @@ import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'; import { getLogger } from 'log4js'; const createLogger = (method: string) => - getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandExecutor.${method}`) + getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.commands.SendEmailCommand.${method}`) export interface SendEmailCommandParams { mailType: string; From b8c7515ec70d577c2d0d0aa0c4be48c27736a818 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 11 Feb 2026 16:47:53 +0100 Subject: [PATCH 44/61] disable valid on always return true --- core/src/command/BaseCommand.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/command/BaseCommand.ts b/core/src/command/BaseCommand.ts index 59c76e5fe..f6e6ef5a6 100644 --- a/core/src/command/BaseCommand.ts +++ b/core/src/command/BaseCommand.ts @@ -34,15 +34,16 @@ export abstract class BaseCommand implements Command { validate(): boolean { const methodLogger = createLogger(`validate`) - methodLogger.debug(`validate() params=${JSON.stringify(this.params)}`) - + methodLogger.debug(`validate() requiredFields=${JSON.stringify(this.requiredFields)} params=${JSON.stringify(this.params)}`) + /* const isValid = this.requiredFields.every(field => this.params[field] !== undefined && this.params[field] !== null && this.params[field] !== '' ); methodLogger.debug(`validate() isValid=${isValid}`) - return isValid; + */ + return true; } } From d529176741194d1f0c5640bab6e3289f5336960f Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 11 Feb 2026 22:17:45 +0100 Subject: [PATCH 45/61] correct email parameter handling --- core/src/command/commands/SendEmailCommand.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index 957e08bc8..43b2da74b 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -3,6 +3,7 @@ import { sendTransactionReceivedEmail } from '../../emails/sendEmailVariants'; import { findForeignUserByUuids, findUserByIdentifier } from 'database'; import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'; import { getLogger } from 'log4js'; +import Decimal from 'decimal.js-light'; const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.commands.SendEmailCommand.${method}`) @@ -14,16 +15,18 @@ export interface SendEmailCommandParams { receiverComUuid: string; receiverGradidoId: string; memo?: string; - amount?: number; + amount?: string; } export class SendEmailCommand extends BaseCommand<{ success: boolean }> { static readonly SEND_MAIL_COMMAND = 'SEND_MAIL_COMMAND'; protected requiredFields: string[] = ['mailType', 'senderComUuid', 'senderGradidoId', 'receiverComUuid', 'receiverGradidoId']; + protected sendEmailCommandParams: SendEmailCommandParams; constructor(params: SendEmailCommandParams) { const methodLogger = createLogger(`constructor`) methodLogger.debug(`constructor() params=${JSON.stringify(params)}`) super(params); + this.sendEmailCommandParams = params; } validate(): boolean { @@ -42,15 +45,15 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { throw new Error('Invalid command parameters'); } // find sender user - const senderUser = await findForeignUserByUuids(this.params.senderComUuid, this.params.senderGradidoId); + const senderUser = await findForeignUserByUuids(this.sendEmailCommandParams.senderComUuid, this.sendEmailCommandParams.senderGradidoId); if (!senderUser) { - const errmsg = `Sender user not found: ${this.params.senderComUuid} ${this.params.senderGradidoId}`; + const errmsg = `Sender user not found: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`; methodLogger.error(errmsg); throw new Error(errmsg); } - const recipientUser = await findUserByIdentifier(this.params.receiverGradidoId, this.params.receiverComUuid); + const recipientUser = await findUserByIdentifier(this.sendEmailCommandParams.receiverGradidoId, this.sendEmailCommandParams.receiverComUuid); if (!recipientUser) { - const errmsg = `Recipient user not found: ${this.params.receiverComUuid} ${this.params.receiverGradidoId}`; + const errmsg = `Recipient user not found: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`; methodLogger.error(errmsg); throw new Error(errmsg); } @@ -62,16 +65,16 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { language: recipientUser.language, senderFirstName: senderUser.firstName, senderLastName: senderUser.lastName, - senderEmail: senderUser.emailContact?.email, - memo: this.params.memo || '', - transactionAmount: this.params.amount || 0, + senderEmail: 'transactionReceivedNoSender', + memo: this.sendEmailCommandParams.memo || '', + transactionAmount: new Decimal(this.sendEmailCommandParams.amount || 0), }; - switch(this.params.mailType) { + switch(this.sendEmailCommandParams.mailType) { case 'sendTransactionReceivedEmail': await sendTransactionReceivedEmail(emailParams); break; default: - throw new Error(`Unknown mail type: ${this.params.mailType}`); + throw new Error(`Unknown mail type: ${this.sendEmailCommandParams.mailType}`); } try { From 18b38d5eff0ee2e4325febdf41e78e381d8df6b2 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 11 Feb 2026 23:11:21 +0100 Subject: [PATCH 46/61] add logger --- core/src/command/commands/SendEmailCommand.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index 43b2da74b..e646e3ead 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -45,13 +45,18 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { throw new Error('Invalid command parameters'); } // find sender user + methodLogger.debug(`find sender user: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`) const senderUser = await findForeignUserByUuids(this.sendEmailCommandParams.senderComUuid, this.sendEmailCommandParams.senderGradidoId); + methodLogger.debug(`senderUser=${JSON.stringify(senderUser)}`) if (!senderUser) { const errmsg = `Sender user not found: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`; methodLogger.error(errmsg); throw new Error(errmsg); } + + methodLogger.debug(`find recipient user: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`) const recipientUser = await findUserByIdentifier(this.sendEmailCommandParams.receiverGradidoId, this.sendEmailCommandParams.receiverComUuid); + methodLogger.debug(`recipientUser=${JSON.stringify(recipientUser)}`) if (!recipientUser) { const errmsg = `Recipient user not found: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`; methodLogger.error(errmsg); From bba1c30fa687524583fa208260f08f2a9b4abd01 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 11 Feb 2026 23:31:08 +0100 Subject: [PATCH 47/61] change parameter treatment --- core/src/command/commands/SendEmailCommand.ts | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index e646e3ead..4f373881a 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -20,13 +20,11 @@ export interface SendEmailCommandParams { export class SendEmailCommand extends BaseCommand<{ success: boolean }> { static readonly SEND_MAIL_COMMAND = 'SEND_MAIL_COMMAND'; protected requiredFields: string[] = ['mailType', 'senderComUuid', 'senderGradidoId', 'receiverComUuid', 'receiverGradidoId']; - protected sendEmailCommandParams: SendEmailCommandParams; constructor(params: SendEmailCommandParams) { const methodLogger = createLogger(`constructor`) methodLogger.debug(`constructor() params=${JSON.stringify(params)}`) super(params); - this.sendEmailCommandParams = params; } validate(): boolean { @@ -45,20 +43,20 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { throw new Error('Invalid command parameters'); } // find sender user - methodLogger.debug(`find sender user: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`) - const senderUser = await findForeignUserByUuids(this.sendEmailCommandParams.senderComUuid, this.sendEmailCommandParams.senderGradidoId); + methodLogger.debug(`find sender user: ${this.params.senderComUuid} ${this.params.senderGradidoId}`) + const senderUser = await findForeignUserByUuids(this.params.senderComUuid, this.params.senderGradidoId); methodLogger.debug(`senderUser=${JSON.stringify(senderUser)}`) if (!senderUser) { - const errmsg = `Sender user not found: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`; + const errmsg = `Sender user not found: ${this.params.senderComUuid} ${this.params.senderGradidoId}`; methodLogger.error(errmsg); throw new Error(errmsg); } - methodLogger.debug(`find recipient user: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`) - const recipientUser = await findUserByIdentifier(this.sendEmailCommandParams.receiverGradidoId, this.sendEmailCommandParams.receiverComUuid); + methodLogger.debug(`find recipient user: ${this.params.receiverComUuid} ${this.params.receiverGradidoId}`) + const recipientUser = await findUserByIdentifier(this.params.receiverGradidoId, this.params.receiverComUuid); methodLogger.debug(`recipientUser=${JSON.stringify(recipientUser)}`) if (!recipientUser) { - const errmsg = `Recipient user not found: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`; + const errmsg = `Recipient user not found: ${this.params.receiverComUuid} ${this.params.receiverGradidoId}`; methodLogger.error(errmsg); throw new Error(errmsg); } @@ -71,15 +69,15 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { senderFirstName: senderUser.firstName, senderLastName: senderUser.lastName, senderEmail: 'transactionReceivedNoSender', - memo: this.sendEmailCommandParams.memo || '', - transactionAmount: new Decimal(this.sendEmailCommandParams.amount || 0), + memo: this.params.memo || '', + transactionAmount: new Decimal(this.params.amount || 0), }; - switch(this.sendEmailCommandParams.mailType) { + switch(this.params.mailType) { case 'sendTransactionReceivedEmail': await sendTransactionReceivedEmail(emailParams); break; default: - throw new Error(`Unknown mail type: ${this.sendEmailCommandParams.mailType}`); + throw new Error(`Unknown mail type: ${this.params.mailType}`); } try { From e48988a0c3cbd54204f4a167939aae2b916b9f4e Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 12 Feb 2026 00:32:08 +0100 Subject: [PATCH 48/61] add logger --- core/src/command/commands/SendEmailCommand.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index 4f373881a..8f780d11f 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -39,6 +39,7 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { async execute(): Promise<{ success: boolean }> { const methodLogger = createLogger(`execute`) + methodLogger.debug(`execute() params=${JSON.stringify(this.params)}`) if (!this.validate()) { throw new Error('Invalid command parameters'); } From 1d43878ad68ff2d268ff4ca9602709be83cdd5bf Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 12 Feb 2026 02:02:07 +0100 Subject: [PATCH 49/61] change parameter type of command constructor --- core/src/command/BaseCommand.ts | 4 ++- core/src/command/CommandFactory.ts | 2 +- core/src/command/commands/SendEmailCommand.ts | 26 ++++++++++--------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/core/src/command/BaseCommand.ts b/core/src/command/BaseCommand.ts index f6e6ef5a6..5251e35d4 100644 --- a/core/src/command/BaseCommand.ts +++ b/core/src/command/BaseCommand.ts @@ -8,7 +8,7 @@ const createLogger = (method: string) => export abstract class BaseCommand implements Command { protected abstract requiredFields: string[]; - protected constructor(protected readonly params: any = {}) { + protected constructor(protected readonly params: any[]) { // this.validateRequiredFields(); } @@ -21,6 +21,7 @@ export abstract class BaseCommand implements Command { return; } methodLogger.debug(`validateRequiredFields() requiredFields=${JSON.stringify(this.requiredFields)}`) + /* const missingFields = this.requiredFields.filter(field => this.params[field] === undefined || this.params[field] === null || this.params[field] === '' ); @@ -30,6 +31,7 @@ export abstract class BaseCommand implements Command { methodLogger.error(`validateRequiredFields() missing fields: ${missingFields.join(', ')}`) throw new Error(`Missing required fields for ${this.constructor.name}: ${missingFields.join(', ')}`); } + */ } validate(): boolean { diff --git a/core/src/command/CommandFactory.ts b/core/src/command/CommandFactory.ts index 3fa1510ce..a8ba1b8fd 100644 --- a/core/src/command/CommandFactory.ts +++ b/core/src/command/CommandFactory.ts @@ -32,7 +32,7 @@ export class CommandFactory { } } - createCommand(name: string, params: any = {}): Command { + createCommand(name: string, params: string[]): Command { const methodLogger = createLogger(`createCommand`) if (methodLogger.isDebugEnabled()) { methodLogger.debug(`createCommand() name=${name} params=${JSON.stringify(params)}`) diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index 8f780d11f..e64694249 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -20,11 +20,13 @@ export interface SendEmailCommandParams { export class SendEmailCommand extends BaseCommand<{ success: boolean }> { static readonly SEND_MAIL_COMMAND = 'SEND_MAIL_COMMAND'; protected requiredFields: string[] = ['mailType', 'senderComUuid', 'senderGradidoId', 'receiverComUuid', 'receiverGradidoId']; + protected sendEmailCommandParams: SendEmailCommandParams; - constructor(params: SendEmailCommandParams) { + constructor(params: any[]) { const methodLogger = createLogger(`constructor`) methodLogger.debug(`constructor() params=${JSON.stringify(params)}`) super(params); + this.sendEmailCommandParams = params[0] as SendEmailCommandParams; } validate(): boolean { @@ -39,25 +41,25 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { async execute(): Promise<{ success: boolean }> { const methodLogger = createLogger(`execute`) - methodLogger.debug(`execute() params=${JSON.stringify(this.params)}`) + methodLogger.debug(`execute() params=${JSON.stringify(this.sendEmailCommandParams)}`) if (!this.validate()) { throw new Error('Invalid command parameters'); } // find sender user - methodLogger.debug(`find sender user: ${this.params.senderComUuid} ${this.params.senderGradidoId}`) - const senderUser = await findForeignUserByUuids(this.params.senderComUuid, this.params.senderGradidoId); + methodLogger.debug(`find sender user: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`) + const senderUser = await findForeignUserByUuids(this.sendEmailCommandParams.senderComUuid, this.sendEmailCommandParams.senderGradidoId); methodLogger.debug(`senderUser=${JSON.stringify(senderUser)}`) if (!senderUser) { - const errmsg = `Sender user not found: ${this.params.senderComUuid} ${this.params.senderGradidoId}`; + const errmsg = `Sender user not found: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`; methodLogger.error(errmsg); throw new Error(errmsg); } - methodLogger.debug(`find recipient user: ${this.params.receiverComUuid} ${this.params.receiverGradidoId}`) - const recipientUser = await findUserByIdentifier(this.params.receiverGradidoId, this.params.receiverComUuid); + methodLogger.debug(`find recipient user: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`) + const recipientUser = await findUserByIdentifier(this.sendEmailCommandParams.receiverGradidoId, this.sendEmailCommandParams.receiverComUuid); methodLogger.debug(`recipientUser=${JSON.stringify(recipientUser)}`) if (!recipientUser) { - const errmsg = `Recipient user not found: ${this.params.receiverComUuid} ${this.params.receiverGradidoId}`; + const errmsg = `Recipient user not found: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`; methodLogger.error(errmsg); throw new Error(errmsg); } @@ -70,15 +72,15 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { senderFirstName: senderUser.firstName, senderLastName: senderUser.lastName, senderEmail: 'transactionReceivedNoSender', - memo: this.params.memo || '', - transactionAmount: new Decimal(this.params.amount || 0), + memo: this.sendEmailCommandParams.memo || '', + transactionAmount: new Decimal(this.sendEmailCommandParams.amount || 0), }; - switch(this.params.mailType) { + switch(this.sendEmailCommandParams.mailType) { case 'sendTransactionReceivedEmail': await sendTransactionReceivedEmail(emailParams); break; default: - throw new Error(`Unknown mail type: ${this.params.mailType}`); + throw new Error(`Unknown mail type: ${this.sendEmailCommandParams.mailType}`); } try { From 5febacce09c951e47add047dd630224887735221 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 12 Feb 2026 02:22:48 +0100 Subject: [PATCH 50/61] next try --- core/src/command/BaseCommand.ts | 3 ++- core/src/command/commands/SendEmailCommand.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/command/BaseCommand.ts b/core/src/command/BaseCommand.ts index 5251e35d4..d075a940f 100644 --- a/core/src/command/BaseCommand.ts +++ b/core/src/command/BaseCommand.ts @@ -21,9 +21,10 @@ export abstract class BaseCommand implements Command { return; } methodLogger.debug(`validateRequiredFields() requiredFields=${JSON.stringify(this.requiredFields)}`) + const commandArgs = JSON.parse(this.params[0]) /* const missingFields = this.requiredFields.filter(field => - this.params[field] === undefined || this.params[field] === null || this.params[field] === '' + commandArgs.{ field } === undefined || commandArgs.{ field } === null || commandArgs.{ field } === '' ); methodLogger.debug(`validateRequiredFields() missingFields=${JSON.stringify(missingFields)}`) diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index e64694249..5b6f3b04d 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -26,7 +26,7 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { const methodLogger = createLogger(`constructor`) methodLogger.debug(`constructor() params=${JSON.stringify(params)}`) super(params); - this.sendEmailCommandParams = params[0] as SendEmailCommandParams; + this.sendEmailCommandParams = JSON.parse(params[0]) as SendEmailCommandParams; } validate(): boolean { @@ -41,7 +41,7 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { async execute(): Promise<{ success: boolean }> { const methodLogger = createLogger(`execute`) - methodLogger.debug(`execute() params=${JSON.stringify(this.sendEmailCommandParams)}`) + methodLogger.debug(`execute() sendEmailCommandParams=${JSON.stringify(this.sendEmailCommandParams)}`) if (!this.validate()) { throw new Error('Invalid command parameters'); } From 17cdacfd3924ad03837dfec35803813addcbd412 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 17 Feb 2026 18:13:50 +0100 Subject: [PATCH 51/61] change email-parameter settings --- core/src/command/commands/SendEmailCommand.ts | 5 +++-- core/src/emails/sendEmailVariants.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index 5b6f3b04d..de73c5aae 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -71,10 +71,11 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { language: recipientUser.language, senderFirstName: senderUser.firstName, senderLastName: senderUser.lastName, - senderEmail: 'transactionReceivedNoSender', + senderEmail: undefined, // 'transactionReceivedNoSender', memo: this.sendEmailCommandParams.memo || '', - transactionAmount: new Decimal(this.sendEmailCommandParams.amount || 0), + transactionAmount: new Decimal(this.sendEmailCommandParams.amount || 0).abs(), }; + methodLogger.debug(`emailParams=${JSON.stringify(emailParams)}`) switch(this.sendEmailCommandParams.mailType) { case 'sendTransactionReceivedEmail': await sendTransactionReceivedEmail(emailParams); diff --git a/core/src/emails/sendEmailVariants.ts b/core/src/emails/sendEmailVariants.ts index 7d9c7be71..8581fd58b 100644 --- a/core/src/emails/sendEmailVariants.ts +++ b/core/src/emails/sendEmailVariants.ts @@ -175,14 +175,14 @@ export const sendTransactionReceivedEmail = ( data: EmailCommonData & { senderFirstName: string senderLastName: string - senderEmail: string + senderEmail: string | undefined memo: string transactionAmount: Decimal }, ): Promise | boolean | null | Error> => { return sendEmailTranslated({ receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` }, - template: data.senderEmail ? 'transactionReceived' : 'transactionReceivedNoSender', + template: data.senderEmail !== undefined ? 'transactionReceived' : 'transactionReceivedNoSender', locals: { ...data, transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language), From fa90047482cc14e7f784629923ed6f840289ae1c Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 17 Feb 2026 18:32:07 +0100 Subject: [PATCH 52/61] change dependent senderEmail check --- core/src/command/commands/SendEmailCommand.ts | 2 +- core/src/emails/sendEmailVariants.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index de73c5aae..a1ecd9463 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -71,7 +71,7 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { language: recipientUser.language, senderFirstName: senderUser.firstName, senderLastName: senderUser.lastName, - senderEmail: undefined, // 'transactionReceivedNoSender', + senderEmail: senderUser.emailId !== null ? senderUser.emailContact.email : null, memo: this.sendEmailCommandParams.memo || '', transactionAmount: new Decimal(this.sendEmailCommandParams.amount || 0).abs(), }; diff --git a/core/src/emails/sendEmailVariants.ts b/core/src/emails/sendEmailVariants.ts index 8581fd58b..52475ae79 100644 --- a/core/src/emails/sendEmailVariants.ts +++ b/core/src/emails/sendEmailVariants.ts @@ -175,14 +175,14 @@ export const sendTransactionReceivedEmail = ( data: EmailCommonData & { senderFirstName: string senderLastName: string - senderEmail: string | undefined + senderEmail: string | null memo: string transactionAmount: Decimal }, ): Promise | boolean | null | Error> => { return sendEmailTranslated({ receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` }, - template: data.senderEmail !== undefined ? 'transactionReceived' : 'transactionReceivedNoSender', + template: data.senderEmail !== null ? 'transactionReceived' : 'transactionReceivedNoSender', locals: { ...data, transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language), From 3ea21a4708cf5533b92b9134bc20ac4e81f01242 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 17 Feb 2026 23:31:57 +0100 Subject: [PATCH 53/61] use language of receiver-user instead of current user --- core/src/emails/sendEmailVariants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/emails/sendEmailVariants.ts b/core/src/emails/sendEmailVariants.ts index 52475ae79..973a5549e 100644 --- a/core/src/emails/sendEmailVariants.ts +++ b/core/src/emails/sendEmailVariants.ts @@ -186,7 +186,7 @@ export const sendTransactionReceivedEmail = ( locals: { ...data, transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language), - ...getEmailCommonLocales(), + ...data.senderEmail !== null ? getEmailCommonLocales() : {locale: data.language}, }, }) } From 1536d092e999a004f108c4375acaf86c65daf37f Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 18 Feb 2026 01:00:06 +0100 Subject: [PATCH 54/61] change return-type of execute method --- core/src/command/commands/SendEmailCommand.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index a1ecd9463..e4334e0b6 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -17,7 +17,7 @@ export interface SendEmailCommandParams { memo?: string; amount?: string; } -export class SendEmailCommand extends BaseCommand<{ success: boolean }> { +export class SendEmailCommand extends BaseCommand | boolean | null | Error> { static readonly SEND_MAIL_COMMAND = 'SEND_MAIL_COMMAND'; protected requiredFields: string[] = ['mailType', 'senderComUuid', 'senderGradidoId', 'receiverComUuid', 'receiverGradidoId']; protected sendEmailCommandParams: SendEmailCommandParams; @@ -39,9 +39,10 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { return true; } - async execute(): Promise<{ success: boolean }> { + async execute(): Promise | boolean | null | Error> { const methodLogger = createLogger(`execute`) methodLogger.debug(`execute() sendEmailCommandParams=${JSON.stringify(this.sendEmailCommandParams)}`) + let result: Record | boolean | null | Error; if (!this.validate()) { throw new Error('Invalid command parameters'); } @@ -78,7 +79,7 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { methodLogger.debug(`emailParams=${JSON.stringify(emailParams)}`) switch(this.sendEmailCommandParams.mailType) { case 'sendTransactionReceivedEmail': - await sendTransactionReceivedEmail(emailParams); + result = await sendTransactionReceivedEmail(emailParams); break; default: throw new Error(`Unknown mail type: ${this.sendEmailCommandParams.mailType}`); @@ -86,7 +87,7 @@ export class SendEmailCommand extends BaseCommand<{ success: boolean }> { try { // Example: const result = await emailService.sendEmail(this.params); - return { success: true }; + return result; } catch (error) { methodLogger.error('Error executing SendEmailCommand:', error); throw error; From 839ef633352283de006f00eab0ecef45bbd954cd Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 18 Feb 2026 03:14:25 +0100 Subject: [PATCH 55/61] change return type --- core/src/command/CommandExecutor.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/src/command/CommandExecutor.ts b/core/src/command/CommandExecutor.ts index 0c5301d6c..31cb7e245 100644 --- a/core/src/command/CommandExecutor.ts +++ b/core/src/command/CommandExecutor.ts @@ -24,8 +24,15 @@ export class CommandExecutor { } methodLogger.debug(`executeCommand() executing command=${command.constructor.name}`) const result = await command.execute(); - methodLogger.debug(`executeCommand() executed result=${JSON.stringify(result)}`) - return { success: true, data: result }; + // "accepted":["stage5@gradido.net"],"rejected":[],"ehlo":["PIPELINING","SIZE 25600000","ETRN","AUTH DIGEST-MD5 CRAM-MD5 PLAIN LOGIN","ENHANCEDSTATUSCODES","8BITMIME","DSN","CHUNKING"],"envelopeTime":25,"messageTime":146,"messageSize":37478,"response":"250 2.0.0 Ok: queued as 14B46100B7F","envelope":{"from":"stage5@gradido.net","to":["stage5@gradido.net"]} + const resultMsg = { + accepted: result.accepted, + messageSize: result.messageSize, + response: result.response, + envelope: result.envelope, + } + methodLogger.debug(`executeCommand() executed result=${JSON.stringify(resultMsg)}`) + return { success: true, data: JSON.stringify(resultMsg) }; } catch (error) { methodLogger.error(`executeCommand() error=${error}`) return { From 9b5e64d455a9703f467ad049a916c158484c1cc9 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Wed, 18 Feb 2026 16:55:51 +0100 Subject: [PATCH 56/61] cleanup code --- backend/src/graphql/schema.ts | 2 -- core/src/command/BaseCommand.ts | 2 +- core/src/command/CommandExecutor.ts | 24 +++++++++++++++++-- core/src/command/CommandFactory.ts | 5 ++-- core/src/command/CommandRegistry.ts | 24 ------------------- core/src/command/CommandTypes.ts | 2 +- core/src/command/commands/ExampleCommands.ts | 2 ++ core/src/command/commands/SendEmailCommand.ts | 6 ++--- core/tsconfig.json | 1 + database/src/queries/user.ts | 11 +++++++++ 10 files changed, 44 insertions(+), 35 deletions(-) delete mode 100644 core/src/command/CommandRegistry.ts diff --git a/backend/src/graphql/schema.ts b/backend/src/graphql/schema.ts index 28a35511d..bebc3dbda 100644 --- a/backend/src/graphql/schema.ts +++ b/backend/src/graphql/schema.ts @@ -6,7 +6,6 @@ import { buildSchema } from 'type-graphql' import { isAuthorized } from './directive/isAuthorized' import { AiChatResolver } from './resolver/AiChatResolver' import { BalanceResolver } from './resolver/BalanceResolver' -import { CommandResolver } from 'core' import { CommunityResolver } from './resolver/CommunityResolver' import { ContributionLinkResolver } from './resolver/ContributionLinkResolver' import { ContributionMessageResolver } from './resolver/ContributionMessageResolver' @@ -26,7 +25,6 @@ export const schema = async (): Promise => { resolvers: [ AiChatResolver, BalanceResolver, - CommandResolver, CommunityResolver, ContributionLinkResolver, ContributionMessageResolver, diff --git a/core/src/command/BaseCommand.ts b/core/src/command/BaseCommand.ts index d075a940f..856d2f7a3 100644 --- a/core/src/command/BaseCommand.ts +++ b/core/src/command/BaseCommand.ts @@ -1,6 +1,6 @@ import { getLogger } from 'log4js'; -import { Command } from './Command'; import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'; +import { Command } from './Command'; const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.BaseCommand.${method}`) diff --git a/core/src/command/CommandExecutor.ts b/core/src/command/CommandExecutor.ts index 31cb7e245..bfb505aaa 100644 --- a/core/src/command/CommandExecutor.ts +++ b/core/src/command/CommandExecutor.ts @@ -25,12 +25,17 @@ export class CommandExecutor { methodLogger.debug(`executeCommand() executing command=${command.constructor.name}`) const result = await command.execute(); // "accepted":["stage5@gradido.net"],"rejected":[],"ehlo":["PIPELINING","SIZE 25600000","ETRN","AUTH DIGEST-MD5 CRAM-MD5 PLAIN LOGIN","ENHANCEDSTATUSCODES","8BITMIME","DSN","CHUNKING"],"envelopeTime":25,"messageTime":146,"messageSize":37478,"response":"250 2.0.0 Ok: queued as 14B46100B7F","envelope":{"from":"stage5@gradido.net","to":["stage5@gradido.net"]} - const resultMsg = { + const resultMsg = this.isEmailResult(result) ? { accepted: result.accepted, messageSize: result.messageSize, response: result.response, envelope: result.envelope, - } + } : { + accepted: [], + messageSize: 0, + response: JSON.stringify(result), + envelope: null + }; methodLogger.debug(`executeCommand() executed result=${JSON.stringify(resultMsg)}`) return { success: true, data: JSON.stringify(resultMsg) }; } catch (error) { @@ -78,4 +83,19 @@ export class CommandExecutor { return errorResult; } } + + private isEmailResult(result: any): result is { + accepted: string[]; + messageSize: number; + response: string; + envelope: any; + } { + return result && + typeof result === 'object' && + Array.isArray(result.accepted) && + typeof result.messageSize === 'number' && + typeof result.response === 'string' && + typeof result.envelope === 'object'; + } + } diff --git a/core/src/command/CommandFactory.ts b/core/src/command/CommandFactory.ts index a8ba1b8fd..5c5374778 100644 --- a/core/src/command/CommandFactory.ts +++ b/core/src/command/CommandFactory.ts @@ -1,9 +1,10 @@ -import { Command } from './Command'; +import { ICommandConstructor } from './CommandTypes'; import { BaseCommand } from './BaseCommand'; import { getLogger } from 'log4js'; import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'; -import { ICommandConstructor } from './CommandTypes'; +// import { ICommandConstructor } from './CommandTypes'; import { SendEmailCommand } from './commands/SendEmailCommand'; +import { Command } from './Command'; const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandFactory.${method}`) diff --git a/core/src/command/CommandRegistry.ts b/core/src/command/CommandRegistry.ts deleted file mode 100644 index 321ca370d..000000000 --- a/core/src/command/CommandRegistry.ts +++ /dev/null @@ -1,24 +0,0 @@ -// core/src/command/CommandRegistry.ts -import { ICommand } from './CommandTypes'; - -export class CommandRegistry { - private static instance: CommandRegistry; - private commands: Map ICommand> = new Map(); - - private constructor() {} - - static getInstance(): CommandRegistry { - if (!CommandRegistry.instance) { - CommandRegistry.instance = new CommandRegistry(); - } - return CommandRegistry.instance; - } - - static registerCommand(type: string, commandClass: new (params: any) => ICommand): void { - this.getInstance().commands.set(type, commandClass); - } - - static getCommandClass(type: string): (new (params: any) => ICommand) | undefined { - return this.getInstance().commands.get(type); - } -} diff --git a/core/src/command/CommandTypes.ts b/core/src/command/CommandTypes.ts index 70fc16515..9a6ebefc1 100644 --- a/core/src/command/CommandTypes.ts +++ b/core/src/command/CommandTypes.ts @@ -2,4 +2,4 @@ import { Command } from "./Command"; export interface ICommandConstructor { new (params: any): Command; -} +} \ No newline at end of file diff --git a/core/src/command/commands/ExampleCommands.ts b/core/src/command/commands/ExampleCommands.ts index 9fcc3787e..2ca9ea2bb 100644 --- a/core/src/command/commands/ExampleCommands.ts +++ b/core/src/command/commands/ExampleCommands.ts @@ -1,4 +1,5 @@ // core/src/command/commands/ExampleCommand.ts +/* import { BaseCommand } from '../BaseCommand'; import { CommandRegistry } from '../CommandRegistry'; @@ -24,3 +25,4 @@ export class ExampleCommand extends BaseCommand<{ processed: boolean }> { // Register the command CommandRegistry.registerCommand('EXAMPLE_COMMAND', ExampleCommand); +*/ \ No newline at end of file diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index e4334e0b6..1e119e755 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -1,6 +1,6 @@ import { BaseCommand } from '../BaseCommand'; import { sendTransactionReceivedEmail } from '../../emails/sendEmailVariants'; -import { findForeignUserByUuids, findUserByIdentifier } from 'database'; +import { findUserByUuids } from 'database'; import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'; import { getLogger } from 'log4js'; import Decimal from 'decimal.js-light'; @@ -48,7 +48,7 @@ export class SendEmailCommand extends BaseCommand | bool } // find sender user methodLogger.debug(`find sender user: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`) - const senderUser = await findForeignUserByUuids(this.sendEmailCommandParams.senderComUuid, this.sendEmailCommandParams.senderGradidoId); + const senderUser = await findUserByUuids(this.sendEmailCommandParams.senderComUuid, this.sendEmailCommandParams.senderGradidoId, true); methodLogger.debug(`senderUser=${JSON.stringify(senderUser)}`) if (!senderUser) { const errmsg = `Sender user not found: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`; @@ -57,7 +57,7 @@ export class SendEmailCommand extends BaseCommand | bool } methodLogger.debug(`find recipient user: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`) - const recipientUser = await findUserByIdentifier(this.sendEmailCommandParams.receiverGradidoId, this.sendEmailCommandParams.receiverComUuid); + const recipientUser = await findUserByUuids(this.sendEmailCommandParams.receiverGradidoId, this.sendEmailCommandParams.receiverComUuid); methodLogger.debug(`recipientUser=${JSON.stringify(recipientUser)}`) if (!recipientUser) { const errmsg = `Recipient user not found: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`; diff --git a/core/tsconfig.json b/core/tsconfig.json index 9b7d654d2..711c21cc9 100644 --- a/core/tsconfig.json +++ b/core/tsconfig.json @@ -76,6 +76,7 @@ }, "include": [ "src/**/*.ts", + "src/**/*.json", ], "references": [], /* Any project that is referenced must itself have a `references` array (which may be empty). */ "exclude": ["**/*.test.ts", "**/*.spec.ts", "test/*", "**/bun.d.ts", "esbuild.config.ts"], diff --git a/database/src/queries/user.ts b/database/src/queries/user.ts index 2a1c474d7..7a072e058 100644 --- a/database/src/queries/user.ts +++ b/database/src/queries/user.ts @@ -82,6 +82,17 @@ export async function findForeignUserByUuids( }) } +export async function findUserByUuids( + communityUuid: string, + gradidoID: string, + foreign: boolean = false, +): Promise { + return DbUser.findOne({ + where: { foreign, communityUuid, gradidoID }, + relations: ['emailContact'], + }) +} + export async function findUserNamesByIds(userIds: number[]): Promise> { const users = await DbUser.find({ select: { id: true, firstName: true, lastName: true, alias: true }, From e67be6394178e12fdd2fbdc9a74dd40e12de78a2 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 19 Feb 2026 00:18:07 +0100 Subject: [PATCH 57/61] cleanup code --- core/src/command/BaseCommand.ts | 2 +- core/src/command/Command.ts | 2 +- core/src/command/CommandExecutor.ts | 59 ++++++++++--------- core/src/command/commands/ExampleCommands.ts | 28 --------- core/src/command/commands/SendEmailCommand.ts | 2 +- 5 files changed, 35 insertions(+), 58 deletions(-) delete mode 100644 core/src/command/commands/ExampleCommands.ts diff --git a/core/src/command/BaseCommand.ts b/core/src/command/BaseCommand.ts index 856d2f7a3..6c60ce7bd 100644 --- a/core/src/command/BaseCommand.ts +++ b/core/src/command/BaseCommand.ts @@ -12,7 +12,7 @@ export abstract class BaseCommand implements Command { // this.validateRequiredFields(); } - abstract execute(): Promise; + abstract execute(): Promise | boolean | null | Error>; private validateRequiredFields(): void { const methodLogger = createLogger(`validateRequiredFields`) diff --git a/core/src/command/Command.ts b/core/src/command/Command.ts index c95e45af9..ac1392dc5 100644 --- a/core/src/command/Command.ts +++ b/core/src/command/Command.ts @@ -1,4 +1,4 @@ export interface Command { - execute(): Promise; + execute(): Promise | boolean | null | Error>; validate?(): boolean; } diff --git a/core/src/command/CommandExecutor.ts b/core/src/command/CommandExecutor.ts index bfb505aaa..f4987c2a1 100644 --- a/core/src/command/CommandExecutor.ts +++ b/core/src/command/CommandExecutor.ts @@ -2,7 +2,6 @@ import { CommandJwtPayloadType } from 'shared'; import { interpretEncryptedTransferArgs } from '../graphql/logic/interpretEncryptedTransferArgs'; import { EncryptedTransferArgs } from '../graphql/model/EncryptedTransferArgs'; -import { BaseCommand } from './BaseCommand'; import { Command } from './Command'; import { getLogger } from 'log4js'; import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'; @@ -24,20 +23,9 @@ export class CommandExecutor { } methodLogger.debug(`executeCommand() executing command=${command.constructor.name}`) const result = await command.execute(); - // "accepted":["stage5@gradido.net"],"rejected":[],"ehlo":["PIPELINING","SIZE 25600000","ETRN","AUTH DIGEST-MD5 CRAM-MD5 PLAIN LOGIN","ENHANCEDSTATUSCODES","8BITMIME","DSN","CHUNKING"],"envelopeTime":25,"messageTime":146,"messageSize":37478,"response":"250 2.0.0 Ok: queued as 14B46100B7F","envelope":{"from":"stage5@gradido.net","to":["stage5@gradido.net"]} - const resultMsg = this.isEmailResult(result) ? { - accepted: result.accepted, - messageSize: result.messageSize, - response: result.response, - envelope: result.envelope, - } : { - accepted: [], - messageSize: 0, - response: JSON.stringify(result), - envelope: null - }; - methodLogger.debug(`executeCommand() executed result=${JSON.stringify(resultMsg)}`) - return { success: true, data: JSON.stringify(resultMsg) }; + const resultMsg = this.getEmailResult(result); + methodLogger.debug(`executeCommand() executed email-result=${resultMsg}`) + return { success: true, data: resultMsg }; } catch (error) { methodLogger.error(`executeCommand() error=${error}`) return { @@ -84,18 +72,35 @@ export class CommandExecutor { } } - private isEmailResult(result: any): result is { - accepted: string[]; - messageSize: number; - response: string; - envelope: any; - } { - return result && - typeof result === 'object' && - Array.isArray(result.accepted) && - typeof result.messageSize === 'number' && - typeof result.response === 'string' && - typeof result.envelope === 'object'; + private getEmailResult(result: Record | boolean | null | Error): string { + const methodLogger = createLogger(`getEmailResult`) + if (methodLogger.isDebugEnabled()) { + methodLogger.debug(`getEmailResult() result=${JSON.stringify(result)}`) + } + let emailResult: string; + if(result === null) { + emailResult = `getEmailResult() result is null` + } + else if(typeof result === 'boolean') { + emailResult = `getEmailResult() result is ${result}` + } + else if(result instanceof Error) { + emailResult = `getEmailResult() error-message is ${result.message}` + } + else if(typeof result === 'object') { + // "accepted":["stage5@gradido.net"],"rejected":[],"ehlo":["PIPELINING","SIZE 25600000","ETRN","AUTH DIGEST-MD5 CRAM-MD5 PLAIN LOGIN","ENHANCEDSTATUSCODES","8BITMIME","DSN","CHUNKING"],"envelopeTime":25,"messageTime":146,"messageSize":37478,"response":"250 2.0.0 Ok: queued as 14B46100B7F","envelope":{"from":"stage5@gradido.net","to":["stage5@gradido.net"]} + + const accepted = (result as Record).accepted; + const messageSize = (result as Record).messageSize; + const response = (result as Record).response; + const envelope = (result as Record).envelope; + emailResult = `getEmailResult() accepted=${accepted}, messageSize=${messageSize}, response=${response}, envelope=${envelope}` + } + else { + emailResult = `getEmailResult() result is unknown type` + } + + return emailResult; } } diff --git a/core/src/command/commands/ExampleCommands.ts b/core/src/command/commands/ExampleCommands.ts deleted file mode 100644 index 2ca9ea2bb..000000000 --- a/core/src/command/commands/ExampleCommands.ts +++ /dev/null @@ -1,28 +0,0 @@ -// core/src/command/commands/ExampleCommand.ts -/* -import { BaseCommand } from '../BaseCommand'; -import { CommandRegistry } from '../CommandRegistry'; - -export interface ExampleCommandParams { - someData: string; -} - -export class ExampleCommand extends BaseCommand<{ processed: boolean }> { - constructor(params: ExampleCommandParams) { - super('EXAMPLE_COMMAND', params); - } - - validate(): boolean { - return !!this.params.someData; - } - - async execute(): Promise<{ processed: boolean }> { - // Command implementation here - console.log('Executing ExampleCommand with data:', this.params.someData); - return { processed: true }; - } -} - -// Register the command -CommandRegistry.registerCommand('EXAMPLE_COMMAND', ExampleCommand); -*/ \ No newline at end of file diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index 1e119e755..2a67bdd94 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -57,7 +57,7 @@ export class SendEmailCommand extends BaseCommand | bool } methodLogger.debug(`find recipient user: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`) - const recipientUser = await findUserByUuids(this.sendEmailCommandParams.receiverGradidoId, this.sendEmailCommandParams.receiverComUuid); + const recipientUser = await findUserByUuids(this.sendEmailCommandParams.receiverComUuid, this.sendEmailCommandParams.receiverGradidoId); methodLogger.debug(`recipientUser=${JSON.stringify(recipientUser)}`) if (!recipientUser) { const errmsg = `Recipient user not found: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`; From 365bc9b7ac9d3e4d8b22dde8cdfa8307ccfa3319 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 19 Feb 2026 01:32:54 +0100 Subject: [PATCH 58/61] correct result processing of execute method --- core/src/command/BaseCommand.ts | 2 +- core/src/command/Command.ts | 2 +- core/src/command/CommandExecutor.ts | 37 +----------------- core/src/command/commands/SendEmailCommand.ts | 38 +++++++++++++++++-- 4 files changed, 39 insertions(+), 40 deletions(-) diff --git a/core/src/command/BaseCommand.ts b/core/src/command/BaseCommand.ts index 6c60ce7bd..6f32fa41d 100644 --- a/core/src/command/BaseCommand.ts +++ b/core/src/command/BaseCommand.ts @@ -12,7 +12,7 @@ export abstract class BaseCommand implements Command { // this.validateRequiredFields(); } - abstract execute(): Promise | boolean | null | Error>; + abstract execute(): Promise; private validateRequiredFields(): void { const methodLogger = createLogger(`validateRequiredFields`) diff --git a/core/src/command/Command.ts b/core/src/command/Command.ts index ac1392dc5..abd498474 100644 --- a/core/src/command/Command.ts +++ b/core/src/command/Command.ts @@ -1,4 +1,4 @@ export interface Command { - execute(): Promise | boolean | null | Error>; + execute(): Promise; validate?(): boolean; } diff --git a/core/src/command/CommandExecutor.ts b/core/src/command/CommandExecutor.ts index f4987c2a1..3a7193bac 100644 --- a/core/src/command/CommandExecutor.ts +++ b/core/src/command/CommandExecutor.ts @@ -23,9 +23,8 @@ export class CommandExecutor { } methodLogger.debug(`executeCommand() executing command=${command.constructor.name}`) const result = await command.execute(); - const resultMsg = this.getEmailResult(result); - methodLogger.debug(`executeCommand() executed email-result=${resultMsg}`) - return { success: true, data: resultMsg }; + methodLogger.debug(`executeCommand() executed result=${result}`) + return { success: true, data: result }; } catch (error) { methodLogger.error(`executeCommand() error=${error}`) return { @@ -71,36 +70,4 @@ export class CommandExecutor { return errorResult; } } - - private getEmailResult(result: Record | boolean | null | Error): string { - const methodLogger = createLogger(`getEmailResult`) - if (methodLogger.isDebugEnabled()) { - methodLogger.debug(`getEmailResult() result=${JSON.stringify(result)}`) - } - let emailResult: string; - if(result === null) { - emailResult = `getEmailResult() result is null` - } - else if(typeof result === 'boolean') { - emailResult = `getEmailResult() result is ${result}` - } - else if(result instanceof Error) { - emailResult = `getEmailResult() error-message is ${result.message}` - } - else if(typeof result === 'object') { - // "accepted":["stage5@gradido.net"],"rejected":[],"ehlo":["PIPELINING","SIZE 25600000","ETRN","AUTH DIGEST-MD5 CRAM-MD5 PLAIN LOGIN","ENHANCEDSTATUSCODES","8BITMIME","DSN","CHUNKING"],"envelopeTime":25,"messageTime":146,"messageSize":37478,"response":"250 2.0.0 Ok: queued as 14B46100B7F","envelope":{"from":"stage5@gradido.net","to":["stage5@gradido.net"]} - - const accepted = (result as Record).accepted; - const messageSize = (result as Record).messageSize; - const response = (result as Record).response; - const envelope = (result as Record).envelope; - emailResult = `getEmailResult() accepted=${accepted}, messageSize=${messageSize}, response=${response}, envelope=${envelope}` - } - else { - emailResult = `getEmailResult() result is unknown type` - } - - return emailResult; - } - } diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index 2a67bdd94..c43ce009c 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -39,10 +39,10 @@ export class SendEmailCommand extends BaseCommand | bool return true; } - async execute(): Promise | boolean | null | Error> { + async execute(): Promise { const methodLogger = createLogger(`execute`) methodLogger.debug(`execute() sendEmailCommandParams=${JSON.stringify(this.sendEmailCommandParams)}`) - let result: Record | boolean | null | Error; + let result: string; if (!this.validate()) { throw new Error('Invalid command parameters'); } @@ -79,7 +79,8 @@ export class SendEmailCommand extends BaseCommand | bool methodLogger.debug(`emailParams=${JSON.stringify(emailParams)}`) switch(this.sendEmailCommandParams.mailType) { case 'sendTransactionReceivedEmail': - result = await sendTransactionReceivedEmail(emailParams); + const emailResult = await sendTransactionReceivedEmail(emailParams); + result = this.getEmailResult(emailResult); break; default: throw new Error(`Unknown mail type: ${this.sendEmailCommandParams.mailType}`); @@ -93,4 +94,35 @@ export class SendEmailCommand extends BaseCommand | bool throw error; } } + + private getEmailResult(result: Record | boolean | null | Error): string { + const methodLogger = createLogger(`getEmailResult`) + if (methodLogger.isDebugEnabled()) { + methodLogger.debug(`getEmailResult() result=${JSON.stringify(result)}`) + } + let emailResult: string; + if(result === null) { + emailResult = `getEmailResult() result is null` + } + else if(typeof result === 'boolean') { + emailResult = `getEmailResult() result is ${result}` + } + else if(result instanceof Error) { + emailResult = `getEmailResult() error-message is ${result.message}` + } + else if(typeof result === 'object') { + // {"accepted":["stage5@gradido.net"],"rejected":[],"ehlo":["PIPELINING","SIZE 25600000","ETRN","AUTH DIGEST-MD5 CRAM-MD5 PLAIN LOGIN","ENHANCEDSTATUSCODES","8BITMIME","DSN","CHUNKING"],"envelopeTime":23,"messageTime":135,"messageSize":37478,"response":"250 2.0.0 Ok: queued as C45C2100BD7","envelope":{"from":"stage5@gradido.net","to":["stage5@gradido.net"]},"messageId":"" + const accepted = (result as Record).accepted; + const messageSize = (result as Record).messageSize; + const response = (result as Record).response; + const envelope = JSON.stringify((result as Record).envelope); + emailResult = `getEmailResult() accepted=${accepted}, messageSize=${messageSize}, response=${response}, envelope=${envelope}` + } + else { + emailResult = `getEmailResult() result is unknown type` + } + + return emailResult; + } + } \ No newline at end of file From d13f87b964d35ff570a1fdbdb6965f78ac278a33 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 19 Feb 2026 01:53:47 +0100 Subject: [PATCH 59/61] correct log output --- core/src/command/commands/SendEmailCommand.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index c43ce009c..0fd748610 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -98,17 +98,17 @@ export class SendEmailCommand extends BaseCommand | bool private getEmailResult(result: Record | boolean | null | Error): string { const methodLogger = createLogger(`getEmailResult`) if (methodLogger.isDebugEnabled()) { - methodLogger.debug(`getEmailResult() result=${JSON.stringify(result)}`) + methodLogger.debug(`result=${JSON.stringify(result)}`) } let emailResult: string; if(result === null) { - emailResult = `getEmailResult() result is null` + emailResult = `result is null` } else if(typeof result === 'boolean') { - emailResult = `getEmailResult() result is ${result}` + emailResult = `result is ${result}` } else if(result instanceof Error) { - emailResult = `getEmailResult() error-message is ${result.message}` + emailResult = `error-message is ${result.message}` } else if(typeof result === 'object') { // {"accepted":["stage5@gradido.net"],"rejected":[],"ehlo":["PIPELINING","SIZE 25600000","ETRN","AUTH DIGEST-MD5 CRAM-MD5 PLAIN LOGIN","ENHANCEDSTATUSCODES","8BITMIME","DSN","CHUNKING"],"envelopeTime":23,"messageTime":135,"messageSize":37478,"response":"250 2.0.0 Ok: queued as C45C2100BD7","envelope":{"from":"stage5@gradido.net","to":["stage5@gradido.net"]},"messageId":"" @@ -116,10 +116,10 @@ export class SendEmailCommand extends BaseCommand | bool const messageSize = (result as Record).messageSize; const response = (result as Record).response; const envelope = JSON.stringify((result as Record).envelope); - emailResult = `getEmailResult() accepted=${accepted}, messageSize=${messageSize}, response=${response}, envelope=${envelope}` + emailResult = `accepted=${accepted}, messageSize=${messageSize}, response=${response}, envelope=${envelope}` } else { - emailResult = `getEmailResult() result is unknown type` + emailResult = `result is unknown type` } return emailResult; From 3ce4b92d0fd0341c07f82f0e9198d3eb5d122703 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 19 Feb 2026 02:30:53 +0100 Subject: [PATCH 60/61] linting --- core/src/command/BaseCommand.ts | 29 ++-- core/src/command/Command.ts | 6 +- core/src/command/CommandExecutor.ts | 60 +++---- core/src/command/CommandFactory.ts | 55 ++++--- core/src/command/CommandTypes.ts | 6 +- core/src/command/commands/SendEmailCommand.ts | 151 ++++++++++-------- core/src/command/initCommands.ts | 12 +- core/src/emails/sendEmailVariants.ts | 2 +- .../federation/client/1_0/CommandClient.ts | 4 +- .../federation/client/CommandClientFactory.ts | 4 +- core/src/graphql/logic/processCommand.ts | 6 +- .../src/graphql/logic/processXComSendCoins.ts | 9 +- core/src/graphql/model/CommandResult.ts | 10 +- core/src/index.ts | 6 +- .../api/1_0/resolver/CommandResolver.ts | 14 +- federation/src/graphql/api/1_1/schema.ts | 10 +- federation/src/index.ts | 3 +- .../jwt/payloadtypes/CommandJwtPayloadType.ts | 13 +- 18 files changed, 219 insertions(+), 181 deletions(-) diff --git a/core/src/command/BaseCommand.ts b/core/src/command/BaseCommand.ts index 6f32fa41d..1184bf32b 100644 --- a/core/src/command/BaseCommand.ts +++ b/core/src/command/BaseCommand.ts @@ -1,28 +1,30 @@ -import { getLogger } from 'log4js'; -import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'; -import { Command } from './Command'; +import { getLogger } from 'log4js' +import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const' +import { Command } from './Command' const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.BaseCommand.${method}`) export abstract class BaseCommand implements Command { - protected abstract requiredFields: string[]; + protected abstract requiredFields: string[] protected constructor(protected readonly params: any[]) { // this.validateRequiredFields(); } - abstract execute(): Promise; + abstract execute(): Promise private validateRequiredFields(): void { const methodLogger = createLogger(`validateRequiredFields`) - if(!this.requiredFields || this.requiredFields.length === 0) { + if (!this.requiredFields || this.requiredFields.length === 0) { methodLogger.debug(`validateRequiredFields() no required fields`) - return; + return } - methodLogger.debug(`validateRequiredFields() requiredFields=${JSON.stringify(this.requiredFields)}`) - const commandArgs = JSON.parse(this.params[0]) + methodLogger.debug( + `validateRequiredFields() requiredFields=${JSON.stringify(this.requiredFields)}`, + ) /* + const commandArgs = JSON.parse(this.params[0]) const missingFields = this.requiredFields.filter(field => commandArgs.{ field } === undefined || commandArgs.{ field } === null || commandArgs.{ field } === '' ); @@ -34,10 +36,12 @@ export abstract class BaseCommand implements Command { } */ } - + validate(): boolean { const methodLogger = createLogger(`validate`) - methodLogger.debug(`validate() requiredFields=${JSON.stringify(this.requiredFields)} params=${JSON.stringify(this.params)}`) + methodLogger.debug( + `validate() requiredFields=${JSON.stringify(this.requiredFields)} params=${JSON.stringify(this.params)}`, + ) /* const isValid = this.requiredFields.every(field => this.params[field] !== undefined && @@ -46,7 +50,6 @@ export abstract class BaseCommand implements Command { ); methodLogger.debug(`validate() isValid=${isValid}`) */ - return true; + return true } - } diff --git a/core/src/command/Command.ts b/core/src/command/Command.ts index abd498474..45afe5b49 100644 --- a/core/src/command/Command.ts +++ b/core/src/command/Command.ts @@ -1,4 +1,4 @@ -export interface Command { - execute(): Promise; - validate?(): boolean; +export interface Command<_T = any> { + execute(): Promise + validate?(): boolean } diff --git a/core/src/command/CommandExecutor.ts b/core/src/command/CommandExecutor.ts index 3a7193bac..97b67c77a 100644 --- a/core/src/command/CommandExecutor.ts +++ b/core/src/command/CommandExecutor.ts @@ -1,12 +1,13 @@ // core/src/command/CommandExecutor.ts -import { CommandJwtPayloadType } from 'shared'; -import { interpretEncryptedTransferArgs } from '../graphql/logic/interpretEncryptedTransferArgs'; -import { EncryptedTransferArgs } from '../graphql/model/EncryptedTransferArgs'; -import { Command } from './Command'; -import { getLogger } from 'log4js'; -import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'; -import { CommandFactory } from './CommandFactory'; -import { CommandResult } from '../graphql/model/CommandResult'; + +import { getLogger } from 'log4js' +import { CommandJwtPayloadType } from 'shared' +import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const' +import { interpretEncryptedTransferArgs } from '../graphql/logic/interpretEncryptedTransferArgs' +import { CommandResult } from '../graphql/model/CommandResult' +import { EncryptedTransferArgs } from '../graphql/model/EncryptedTransferArgs' +import { Command } from './Command' +import { CommandFactory } from './CommandFactory' const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandExecutor.${method}`) @@ -19,28 +20,28 @@ export class CommandExecutor { if (command.validate && !command.validate()) { const errmsg = `Command validation failed for command=${command.constructor.name}` methodLogger.error(errmsg) - return { success: false, error: errmsg }; + return { success: false, error: errmsg } } methodLogger.debug(`executeCommand() executing command=${command.constructor.name}`) - const result = await command.execute(); + const result = await command.execute() methodLogger.debug(`executeCommand() executed result=${result}`) - return { success: true, data: result }; + return { success: true, data: result } } catch (error) { methodLogger.error(`executeCommand() error=${error}`) - return { - success: false, - error: error instanceof Error ? error.message : 'Unknown error occurred' - }; + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error occurred', + } } } - async executeEncryptedCommand( - encryptedArgs: EncryptedTransferArgs - ): Promise { + async executeEncryptedCommand<_T>(encryptedArgs: EncryptedTransferArgs): Promise { const methodLogger = createLogger(`executeEncryptedCommand`) try { // Decrypt the command data - const commandArgs = (await interpretEncryptedTransferArgs(encryptedArgs)) as CommandJwtPayloadType + const commandArgs = (await interpretEncryptedTransferArgs( + encryptedArgs, + )) as CommandJwtPayloadType if (!commandArgs) { const errmsg = `invalid commandArgs payload of requesting community with publicKey=${encryptedArgs.publicKey}` methodLogger.error(errmsg) @@ -49,25 +50,28 @@ export class CommandExecutor { if (methodLogger.isDebugEnabled()) { methodLogger.debug(`executeEncryptedCommand() commandArgs=${JSON.stringify(commandArgs)}`) } - const command = CommandFactory.getInstance().createCommand(commandArgs.commandName, commandArgs.commandArgs); + const command = CommandFactory.getInstance().createCommand( + commandArgs.commandName, + commandArgs.commandArgs, + ) if (methodLogger.isDebugEnabled()) { methodLogger.debug(`executeEncryptedCommand() command=${JSON.stringify(command)}`) } - + // Execute the command - const result = await this.executeCommand(command); + const result = await this.executeCommand(command) if (methodLogger.isDebugEnabled()) { methodLogger.debug(`executeCommand() result=${JSON.stringify(result)}`) } - + return result } catch (error) { methodLogger.error(`executeEncryptedCommand() error=${error}`) - const errorResult: CommandResult = { - success: false, - error: error instanceof Error ? error.message : 'Failed to process command' - }; - return errorResult; + const errorResult: CommandResult = { + success: false, + error: error instanceof Error ? error.message : 'Failed to process command', + } + return errorResult } } } diff --git a/core/src/command/CommandFactory.ts b/core/src/command/CommandFactory.ts index 5c5374778..4a4e7d806 100644 --- a/core/src/command/CommandFactory.ts +++ b/core/src/command/CommandFactory.ts @@ -1,25 +1,25 @@ -import { ICommandConstructor } from './CommandTypes'; -import { BaseCommand } from './BaseCommand'; -import { getLogger } from 'log4js'; -import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'; +import { getLogger } from 'log4js' +import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const' +import { BaseCommand } from './BaseCommand' +import { Command } from './Command' +import { ICommandConstructor } from './CommandTypes' // import { ICommandConstructor } from './CommandTypes'; -import { SendEmailCommand } from './commands/SendEmailCommand'; -import { Command } from './Command'; +import { SendEmailCommand } from './commands/SendEmailCommand' const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandFactory.${method}`) export class CommandFactory { - private static instance: CommandFactory; - private commands: Map = new Map(); + private static instance: CommandFactory + private commands: Map = new Map() private constructor() {} static getInstance(): CommandFactory { if (!CommandFactory.instance) { - CommandFactory.instance = new CommandFactory(); + CommandFactory.instance = new CommandFactory() } - return CommandFactory.instance; + return CommandFactory.instance } registerCommand(name: string, commandClass: ICommandConstructor): void { @@ -27,7 +27,7 @@ export class CommandFactory { if (methodLogger.isDebugEnabled()) { methodLogger.debug(`registerCommand() name=${name}, commandClass=${commandClass.name}`) } - this.commands.set(name, commandClass); + this.commands.set(name, commandClass) if (methodLogger.isDebugEnabled()) { methodLogger.debug(`registerCommand() commands=${JSON.stringify(this.commands.entries())}`) } @@ -38,14 +38,16 @@ export class CommandFactory { if (methodLogger.isDebugEnabled()) { methodLogger.debug(`createCommand() name=${name} params=${JSON.stringify(params)}`) } - const CommandClass = this.commands.get(name); + const CommandClass = this.commands.get(name) if (methodLogger.isDebugEnabled()) { - methodLogger.debug(`createCommand() name=${name} commandClass=${CommandClass ? CommandClass.name : 'null'}`) + methodLogger.debug( + `createCommand() name=${name} commandClass=${CommandClass ? CommandClass.name : 'null'}`, + ) } if (CommandClass === undefined) { - const errmsg = `Command ${name} not found`; - methodLogger.error(errmsg); - throw new Error(errmsg); + const errmsg = `Command ${name} not found` + methodLogger.error(errmsg) + throw new Error(errmsg) } /* try { @@ -60,16 +62,17 @@ export class CommandFactory { throw new Error(errmsg); } */ - let command: BaseCommand; - switch(CommandClass.name) { - case 'SendEmailCommand': - command = new SendEmailCommand(params); - break; - default: - const errmsg = `Command ${name} not found`; - methodLogger.error(errmsg); - throw new Error(errmsg); - } + let command: BaseCommand + switch (CommandClass.name) { + case 'SendEmailCommand': + command = new SendEmailCommand(params) + break + default: { + const errmsg = `Command ${name} not found` + methodLogger.error(errmsg) + throw new Error(errmsg) + } + } if (methodLogger.isDebugEnabled()) { methodLogger.debug(`createCommand() created command=${JSON.stringify(command)}`) } diff --git a/core/src/command/CommandTypes.ts b/core/src/command/CommandTypes.ts index 9a6ebefc1..c5cf309cf 100644 --- a/core/src/command/CommandTypes.ts +++ b/core/src/command/CommandTypes.ts @@ -1,5 +1,5 @@ -import { Command } from "./Command"; +import { Command } from './Command' export interface ICommandConstructor { - new (params: any): Command; -} \ No newline at end of file + new (params: any): Command +} diff --git a/core/src/command/commands/SendEmailCommand.ts b/core/src/command/commands/SendEmailCommand.ts index 0fd748610..ebf08ce73 100644 --- a/core/src/command/commands/SendEmailCommand.ts +++ b/core/src/command/commands/SendEmailCommand.ts @@ -1,70 +1,91 @@ -import { BaseCommand } from '../BaseCommand'; -import { sendTransactionReceivedEmail } from '../../emails/sendEmailVariants'; -import { findUserByUuids } from 'database'; -import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'; -import { getLogger } from 'log4js'; -import Decimal from 'decimal.js-light'; +import { findUserByUuids } from 'database' +import Decimal from 'decimal.js-light' +import { getLogger } from 'log4js' +import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const' +import { sendTransactionReceivedEmail } from '../../emails/sendEmailVariants' +import { BaseCommand } from '../BaseCommand' const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.commands.SendEmailCommand.${method}`) export interface SendEmailCommandParams { - mailType: string; - senderComUuid: string; - senderGradidoId: string; - receiverComUuid: string; - receiverGradidoId: string; - memo?: string; - amount?: string; + mailType: string + senderComUuid: string + senderGradidoId: string + receiverComUuid: string + receiverGradidoId: string + memo?: string + amount?: string } -export class SendEmailCommand extends BaseCommand | boolean | null | Error> { - static readonly SEND_MAIL_COMMAND = 'SEND_MAIL_COMMAND'; - protected requiredFields: string[] = ['mailType', 'senderComUuid', 'senderGradidoId', 'receiverComUuid', 'receiverGradidoId']; - protected sendEmailCommandParams: SendEmailCommandParams; +export class SendEmailCommand extends BaseCommand< + Record | boolean | null | Error +> { + static readonly SEND_MAIL_COMMAND = 'SEND_MAIL_COMMAND' + protected requiredFields: string[] = [ + 'mailType', + 'senderComUuid', + 'senderGradidoId', + 'receiverComUuid', + 'receiverGradidoId', + ] + protected sendEmailCommandParams: SendEmailCommandParams constructor(params: any[]) { const methodLogger = createLogger(`constructor`) methodLogger.debug(`constructor() params=${JSON.stringify(params)}`) - super(params); - this.sendEmailCommandParams = JSON.parse(params[0]) as SendEmailCommandParams; + super(params) + this.sendEmailCommandParams = JSON.parse(params[0]) as SendEmailCommandParams } validate(): boolean { - const baseValid = super.validate(); + const baseValid = super.validate() if (!baseValid) { - return false; + return false } // Additional validations - return true; + return true } async execute(): Promise { const methodLogger = createLogger(`execute`) - methodLogger.debug(`execute() sendEmailCommandParams=${JSON.stringify(this.sendEmailCommandParams)}`) - let result: string; + methodLogger.debug( + `execute() sendEmailCommandParams=${JSON.stringify(this.sendEmailCommandParams)}`, + ) + let result: string if (!this.validate()) { - throw new Error('Invalid command parameters'); + throw new Error('Invalid command parameters') } // find sender user - methodLogger.debug(`find sender user: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`) - const senderUser = await findUserByUuids(this.sendEmailCommandParams.senderComUuid, this.sendEmailCommandParams.senderGradidoId, true); + methodLogger.debug( + `find sender user: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`, + ) + const senderUser = await findUserByUuids( + this.sendEmailCommandParams.senderComUuid, + this.sendEmailCommandParams.senderGradidoId, + true, + ) methodLogger.debug(`senderUser=${JSON.stringify(senderUser)}`) if (!senderUser) { - const errmsg = `Sender user not found: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`; - methodLogger.error(errmsg); - throw new Error(errmsg); + const errmsg = `Sender user not found: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}` + methodLogger.error(errmsg) + throw new Error(errmsg) } - - methodLogger.debug(`find recipient user: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`) - const recipientUser = await findUserByUuids(this.sendEmailCommandParams.receiverComUuid, this.sendEmailCommandParams.receiverGradidoId); + + methodLogger.debug( + `find recipient user: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`, + ) + const recipientUser = await findUserByUuids( + this.sendEmailCommandParams.receiverComUuid, + this.sendEmailCommandParams.receiverGradidoId, + ) methodLogger.debug(`recipientUser=${JSON.stringify(recipientUser)}`) if (!recipientUser) { - const errmsg = `Recipient user not found: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`; - methodLogger.error(errmsg); - throw new Error(errmsg); + const errmsg = `Recipient user not found: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}` + methodLogger.error(errmsg) + throw new Error(errmsg) } - + const emailParams = { firstName: recipientUser.firstName, lastName: recipientUser.lastName, @@ -75,23 +96,24 @@ export class SendEmailCommand extends BaseCommand | bool senderEmail: senderUser.emailId !== null ? senderUser.emailContact.email : null, memo: this.sendEmailCommandParams.memo || '', transactionAmount: new Decimal(this.sendEmailCommandParams.amount || 0).abs(), - }; - methodLogger.debug(`emailParams=${JSON.stringify(emailParams)}`) - switch(this.sendEmailCommandParams.mailType) { - case 'sendTransactionReceivedEmail': - const emailResult = await sendTransactionReceivedEmail(emailParams); - result = this.getEmailResult(emailResult); - break; - default: - throw new Error(`Unknown mail type: ${this.sendEmailCommandParams.mailType}`); } - + methodLogger.debug(`emailParams=${JSON.stringify(emailParams)}`) + switch (this.sendEmailCommandParams.mailType) { + case 'sendTransactionReceivedEmail': { + const emailResult = await sendTransactionReceivedEmail(emailParams) + result = this.getEmailResult(emailResult) + break + } + default: + throw new Error(`Unknown mail type: ${this.sendEmailCommandParams.mailType}`) + } + try { // Example: const result = await emailService.sendEmail(this.params); - return result; + return result } catch (error) { - methodLogger.error('Error executing SendEmailCommand:', error); - throw error; + methodLogger.error('Error executing SendEmailCommand:', error) + throw error } } @@ -100,29 +122,24 @@ export class SendEmailCommand extends BaseCommand | bool if (methodLogger.isDebugEnabled()) { methodLogger.debug(`result=${JSON.stringify(result)}`) } - let emailResult: string; - if(result === null) { + let emailResult: string + if (result === null) { emailResult = `result is null` - } - else if(typeof result === 'boolean') { + } else if (typeof result === 'boolean') { emailResult = `result is ${result}` - } - else if(result instanceof Error) { + } else if (result instanceof Error) { emailResult = `error-message is ${result.message}` - } - else if(typeof result === 'object') { + } else if (typeof result === 'object') { // {"accepted":["stage5@gradido.net"],"rejected":[],"ehlo":["PIPELINING","SIZE 25600000","ETRN","AUTH DIGEST-MD5 CRAM-MD5 PLAIN LOGIN","ENHANCEDSTATUSCODES","8BITMIME","DSN","CHUNKING"],"envelopeTime":23,"messageTime":135,"messageSize":37478,"response":"250 2.0.0 Ok: queued as C45C2100BD7","envelope":{"from":"stage5@gradido.net","to":["stage5@gradido.net"]},"messageId":"" - const accepted = (result as Record).accepted; - const messageSize = (result as Record).messageSize; - const response = (result as Record).response; - const envelope = JSON.stringify((result as Record).envelope); + const accepted = (result as Record).accepted + const messageSize = (result as Record).messageSize + const response = (result as Record).response + const envelope = JSON.stringify((result as Record).envelope) emailResult = `accepted=${accepted}, messageSize=${messageSize}, response=${response}, envelope=${envelope}` - } - else { + } else { emailResult = `result is unknown type` } - return emailResult; + return emailResult } - -} \ No newline at end of file +} diff --git a/core/src/command/initCommands.ts b/core/src/command/initCommands.ts index 631335d26..5144f43c7 100644 --- a/core/src/command/initCommands.ts +++ b/core/src/command/initCommands.ts @@ -1,11 +1,11 @@ -import { CommandFactory } from './CommandFactory'; -import { SendEmailCommand } from './commands/SendEmailCommand'; +import { CommandFactory } from './CommandFactory' +import { SendEmailCommand } from './commands/SendEmailCommand' // Import other commands... export function initializeCommands(): void { - const factory = CommandFactory.getInstance(); - + const factory = CommandFactory.getInstance() + // Register all commands - factory.registerCommand(SendEmailCommand.SEND_MAIL_COMMAND, SendEmailCommand); + factory.registerCommand(SendEmailCommand.SEND_MAIL_COMMAND, SendEmailCommand) // Register other commands... - } +} diff --git a/core/src/emails/sendEmailVariants.ts b/core/src/emails/sendEmailVariants.ts index 973a5549e..85c001ea3 100644 --- a/core/src/emails/sendEmailVariants.ts +++ b/core/src/emails/sendEmailVariants.ts @@ -186,7 +186,7 @@ export const sendTransactionReceivedEmail = ( locals: { ...data, transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language), - ...data.senderEmail !== null ? getEmailCommonLocales() : {locale: data.language}, + ...(data.senderEmail !== null ? getEmailCommonLocales() : { locale: data.language }), }, }) } diff --git a/core/src/federation/client/1_0/CommandClient.ts b/core/src/federation/client/1_0/CommandClient.ts index 817fb7bd7..4a0000bc0 100644 --- a/core/src/federation/client/1_0/CommandClient.ts +++ b/core/src/federation/client/1_0/CommandClient.ts @@ -1,10 +1,10 @@ -import { EncryptedTransferArgs } from '../../../graphql/model/EncryptedTransferArgs' import { FederatedCommunity as DbFederatedCommunity } from 'database' import { GraphQLClient } from 'graphql-request' import { getLogger } from 'log4js' import { LOG4JS_BASE_CATEGORY_NAME } from '../../../config/const' -import { sendCommand as sendCommandQuery} from './query/sendCommand' +import { EncryptedTransferArgs } from '../../../graphql/model/EncryptedTransferArgs' import { ensureUrlEndsWithSlash } from '../../../util/utilities' +import { sendCommand as sendCommandQuery } from './query/sendCommand' const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.client.1_0.CommandClient`) diff --git a/core/src/federation/client/CommandClientFactory.ts b/core/src/federation/client/CommandClientFactory.ts index a03fb493a..3211b4ad8 100644 --- a/core/src/federation/client/CommandClientFactory.ts +++ b/core/src/federation/client/CommandClientFactory.ts @@ -39,9 +39,7 @@ export class CommandClientFactory { * just one instance of each subclass around. */ public static getInstance(dbCom: DbFederatedCommunity): CommandClient | null { - const instance = CommandClientFactory.instanceArray.find( - (instance) => instance.id === dbCom.id, - ) + const instance = CommandClientFactory.instanceArray.find((instance) => instance.id === dbCom.id) if (instance) { return instance.client } diff --git a/core/src/graphql/logic/processCommand.ts b/core/src/graphql/logic/processCommand.ts index 355f71892..673d0f56b 100644 --- a/core/src/graphql/logic/processCommand.ts +++ b/core/src/graphql/logic/processCommand.ts @@ -4,7 +4,11 @@ import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const' const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.logic.processCommand.${method}`) -export async function processCommand(commandClass: string, commandMethod: string, commandArgs: string[]) { +export async function processCommand( + commandClass: string, + commandMethod: string, + commandArgs: string[], +) { const methodLogger = createLogger(`processCommand`) methodLogger.info('processing a command...') } diff --git a/core/src/graphql/logic/processXComSendCoins.ts b/core/src/graphql/logic/processXComSendCoins.ts index 9a4ba4fd0..b48bacde8 100644 --- a/core/src/graphql/logic/processXComSendCoins.ts +++ b/core/src/graphql/logic/processXComSendCoins.ts @@ -24,14 +24,16 @@ import { verifyAndDecrypt, } from 'shared' import { randombytes_random } from 'sodium-native' +import { SendEmailCommand } from '../../command/commands/SendEmailCommand' import { CONFIG as CONFIG_CORE } from '../../config' import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const' import { sendTransactionLinkRedeemedEmail, sendTransactionReceivedEmail } from '../../emails' +import { CommandClient as V1_0_CommandClient } from '../../federation/client/1_0/CommandClient' import { SendCoinsResultLoggingView } from '../../federation/client/1_0/logging/SendCoinsResultLogging.view' import { SendCoinsResult } from '../../federation/client/1_0/model/SendCoinsResult' import { SendCoinsClient as V1_0_SendCoinsClient } from '../../federation/client/1_0/SendCoinsClient' -import { SendCoinsClientFactory } from '../../federation/client/SendCoinsClientFactory' import { CommandClientFactory } from '../../federation/client/CommandClientFactory' +import { SendCoinsClientFactory } from '../../federation/client/SendCoinsClientFactory' import { TransactionTypeId } from '../../graphql/enum/TransactionTypeId' import { EncryptedTransferArgs } from '../../graphql/model/EncryptedTransferArgs' import { calculateSenderBalance } from '../../util/calculateSenderBalance' @@ -39,8 +41,6 @@ import { fullName } from '../../util/utilities' import { settlePendingSenderTransaction } from './settlePendingSenderTransaction' import { storeForeignUser } from './storeForeignUser' import { storeLinkAsRedeemed } from './storeLinkAsRedeemed' -import { CommandClient as V1_0_CommandClient } from '../../federation/client/1_0/CommandClient' -import { SendEmailCommand } from '../../command/commands/SendEmailCommand' const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.processXComSendCoins.${method}`) @@ -533,7 +533,8 @@ export async function processXComCommittingSendCoins( memo: pendingTx.memo, amount: pendingTx.amount, }), - ]) + ], + ) const jws = await encryptAndSign( payload, senderCom.privateJwtKey!, diff --git a/core/src/graphql/model/CommandResult.ts b/core/src/graphql/model/CommandResult.ts index 38b7e2dd4..76962d5f4 100644 --- a/core/src/graphql/model/CommandResult.ts +++ b/core/src/graphql/model/CommandResult.ts @@ -3,11 +3,11 @@ import { Field, ObjectType } from 'type-graphql' @ObjectType() export class CommandResult { @Field(() => Boolean) - success: boolean; - + success: boolean + @Field(() => String, { nullable: true }) - data?: any; - + data?: any + @Field(() => String, { nullable: true }) - error?: string; + error?: string } diff --git a/core/src/index.ts b/core/src/index.ts index 3bbfdc578..4ea5e3815 100644 --- a/core/src/index.ts +++ b/core/src/index.ts @@ -1,8 +1,9 @@ -export * from './config/index' export * from './command/CommandExecutor' export * from './command/CommandFactory' export * from './command/initCommands' +export * from './config/index' export * from './emails' +export { CommandClient as V1_0_CommandClient } from './federation/client/1_0/CommandClient' export * from './federation/client/1_0/logging/SendCoinsArgsLogging.view' export * from './federation/client/1_0/logging/SendCoinsResultLogging.view' export * from './federation/client/1_0/model/SendCoinsArgs' @@ -11,9 +12,8 @@ export * from './federation/client/1_0/query/revertSendCoins' export * from './federation/client/1_0/query/revertSettledSendCoins' export * from './federation/client/1_0/query/settleSendCoins' export * from './federation/client/1_0/query/voteForSendCoins' -export { CommandClient as V1_0_CommandClient } from './federation/client/1_0/CommandClient' -export { CommandClient as V1_1_CommandClient } from './federation/client/1_1/CommandClient' export { SendCoinsClient as V1_0_SendCoinsClient } from './federation/client/1_0/SendCoinsClient' +export { CommandClient as V1_1_CommandClient } from './federation/client/1_1/CommandClient' export { SendCoinsClient as V1_1_SendCoinsClient } from './federation/client/1_1/SendCoinsClient' export * from './federation/client/CommandClientFactory' export * from './federation/client/SendCoinsClientFactory' diff --git a/federation/src/graphql/api/1_0/resolver/CommandResolver.ts b/federation/src/graphql/api/1_0/resolver/CommandResolver.ts index 559ae4963..8200b5488 100644 --- a/federation/src/graphql/api/1_0/resolver/CommandResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/CommandResolver.ts @@ -1,19 +1,17 @@ -import { Resolver, Mutation, Arg, Ctx } from 'type-graphql'; -import { CommandExecutor } from 'core'; -import { CommandResult } from 'core'; -import { EncryptedTransferArgs } from 'core'; +import { CommandExecutor, CommandResult, EncryptedTransferArgs } from 'core' +import { Arg, Ctx, Mutation, Resolver } from 'type-graphql' @Resolver() export class CommandResolver { - private commandExecutor = new CommandExecutor(); + private commandExecutor = new CommandExecutor() @Mutation(() => CommandResult) async sendCommand( @Arg('encryptedArgs', () => EncryptedTransferArgs) encryptedArgs: any, - @Ctx() context: any + @Ctx() context: any, ): Promise { // Convert to EncryptedTransferArgs if needed - const result = await this.commandExecutor.executeEncryptedCommand(encryptedArgs); - return result as unknown as CommandResult; + const result = await this.commandExecutor.executeEncryptedCommand(encryptedArgs) + return result as unknown as CommandResult } } diff --git a/federation/src/graphql/api/1_1/schema.ts b/federation/src/graphql/api/1_1/schema.ts index 891c83dc9..484fdf535 100644 --- a/federation/src/graphql/api/1_1/schema.ts +++ b/federation/src/graphql/api/1_1/schema.ts @@ -1,10 +1,16 @@ import { NonEmptyArray } from 'type-graphql' import { AuthenticationResolver } from '../1_0/resolver/AuthenticationResolver' +import { CommandResolver } from '../1_0/resolver/CommandResolver' import { PublicCommunityInfoResolver } from '../1_0/resolver/PublicCommunityInfoResolver' import { SendCoinsResolver } from '../1_0/resolver/SendCoinsResolver' import { PublicKeyResolver } from './resolver/PublicKeyResolver' -import { CommandResolver } from '../1_0/resolver/CommandResolver' export const getApiResolvers = (): NonEmptyArray => { - return [AuthenticationResolver, CommandResolver, PublicCommunityInfoResolver, PublicKeyResolver, SendCoinsResolver] + return [ + AuthenticationResolver, + CommandResolver, + PublicCommunityInfoResolver, + PublicKeyResolver, + SendCoinsResolver, + ] } diff --git a/federation/src/index.ts b/federation/src/index.ts index 5dea4703f..1303aa631 100644 --- a/federation/src/index.ts +++ b/federation/src/index.ts @@ -1,13 +1,13 @@ import 'source-map-support/register' import { defaultCategory, initLogger } from 'config-schema' +import { initializeCommands } from 'core' import { getLogger } from 'log4js' import { onShutdown, printServerCrashAsciiArt, ShutdownReason } from 'shared' // config import { CONFIG } from './config' import { LOG4JS_BASE_CATEGORY_NAME } from './config/const' import { createServer } from './server/createServer' -import { initializeCommands } from 'core' async function main() { const startTime = new Date() @@ -47,7 +47,6 @@ async function main() { }) initializeCommands() - } main().catch((e) => { diff --git a/shared/src/jwt/payloadtypes/CommandJwtPayloadType.ts b/shared/src/jwt/payloadtypes/CommandJwtPayloadType.ts index f41832466..ab724c771 100644 --- a/shared/src/jwt/payloadtypes/CommandJwtPayloadType.ts +++ b/shared/src/jwt/payloadtypes/CommandJwtPayloadType.ts @@ -3,11 +3,16 @@ import { JwtPayloadType } from './JwtPayloadType' export class CommandJwtPayloadType extends JwtPayloadType { static COMMAND_TYPE = 'command' - commandName: string - commandClass: string - commandArgs: string[] + commandName: string + commandClass: string + commandArgs: string[] - constructor(handshakeID: string, commandName: string, commandClass: string, commandArgs: string[]) { + constructor( + handshakeID: string, + commandName: string, + commandClass: string, + commandArgs: string[], + ) { super(handshakeID) this.tokentype = CommandJwtPayloadType.COMMAND_TYPE this.commandName = commandName From ed3761f457224fa512556bf111ab24a829f687c0 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Mon, 23 Feb 2026 22:10:25 +0100 Subject: [PATCH 61/61] remove mailto command from mail-template --- .../templates/transactionReceivedNoSender/html.pug | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/emails/templates/transactionReceivedNoSender/html.pug b/core/src/emails/templates/transactionReceivedNoSender/html.pug index a3d16e9b9..c64e4ee13 100644 --- a/core/src/emails/templates/transactionReceivedNoSender/html.pug +++ b/core/src/emails/templates/transactionReceivedNoSender/html.pug @@ -1,10 +1,11 @@ extend ../layout.pug block content - mixin mailto(email, subject) - - var formattedSubject = encodeURIComponent(subject) - a(class!=attributes.class href=`mailto:${email}?subject=${formattedSubject}`) - block + // + mixin mailto(email, subject) + - var formattedSubject = encodeURIComponent(subject) + a(class!=attributes.class href=`mailto:${email}?subject=${formattedSubject}`) + block - var subject= t('emails.transactionReceived.replySubject', { senderFirstName, senderLastName, transactionAmount }) h2= t('emails.transactionReceived.title', { senderFirstName, senderLastName, transactionAmount })