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