mirror of
https://github.com/IT4Change/gradido.git
synced 2026-03-01 12:44:43 +00:00
Merge pull request #3594 from gradido/3214-feature-x-com-sendcoins-4-activate-email-and-event-actions-in-federation-modul
feat(backend): send emails after x-com-tx
This commit is contained in:
commit
b2ab427e22
@ -15,6 +15,8 @@ import {
|
||||
EncryptedTransferArgs,
|
||||
fullName,
|
||||
interpretEncryptedTransferArgs,
|
||||
sendTransactionLinkRedeemedEmail,
|
||||
sendTransactionReceivedEmail,
|
||||
TransactionTypeId,
|
||||
} from 'core'
|
||||
import { randomBytes } from 'crypto'
|
||||
@ -29,6 +31,7 @@ import {
|
||||
User as DbUser,
|
||||
findModeratorCreatingContributionLink,
|
||||
findTransactionLinkByCode,
|
||||
findUserByIdentifier,
|
||||
getHomeCommunity,
|
||||
getLastTransaction,
|
||||
} from 'database'
|
||||
@ -660,6 +663,62 @@ 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}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new LogError(errmsg)
|
||||
}
|
||||
if (recipientUser.emailContact?.email !== null) {
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(
|
||||
'Sending TransactionLinkRedeem Email to recipient=' +
|
||||
recipientUser.firstName +
|
||||
' ' +
|
||||
recipientUser.lastName +
|
||||
'sender=' +
|
||||
senderUser.firstName +
|
||||
' ' +
|
||||
senderUser.lastName,
|
||||
)
|
||||
}
|
||||
try {
|
||||
await sendTransactionLinkRedeemedEmail({
|
||||
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 TransactionLinkRedeem 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}`
|
||||
methodLogger.error(errmsg)
|
||||
|
||||
55
core/src/command/BaseCommand.ts
Normal file
55
core/src/command/BaseCommand.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
|
||||
import { Command } from './Command'
|
||||
|
||||
const createLogger = (method: string) =>
|
||||
getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.BaseCommand.${method}`)
|
||||
|
||||
export abstract class BaseCommand<T = any> implements Command<T> {
|
||||
protected abstract requiredFields: string[]
|
||||
|
||||
protected constructor(protected readonly params: any[]) {
|
||||
// this.validateRequiredFields();
|
||||
}
|
||||
|
||||
abstract execute(): Promise<string | boolean | null | Error>
|
||||
|
||||
private validateRequiredFields(): void {
|
||||
const methodLogger = createLogger(`validateRequiredFields`)
|
||||
if (!this.requiredFields || this.requiredFields.length === 0) {
|
||||
methodLogger.debug(`validateRequiredFields() no required fields`)
|
||||
return
|
||||
}
|
||||
methodLogger.debug(
|
||||
`validateRequiredFields() requiredFields=${JSON.stringify(this.requiredFields)}`,
|
||||
)
|
||||
/*
|
||||
const commandArgs = JSON.parse(this.params[0])
|
||||
const missingFields = this.requiredFields.filter(field =>
|
||||
commandArgs.{ field } === undefined || commandArgs.{ field } === null || commandArgs.{ field } === ''
|
||||
);
|
||||
methodLogger.debug(`validateRequiredFields() missingFields=${JSON.stringify(missingFields)}`)
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
methodLogger.error(`validateRequiredFields() missing fields: ${missingFields.join(', ')}`)
|
||||
throw new Error(`Missing required fields for ${this.constructor.name}: ${missingFields.join(', ')}`);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
validate(): boolean {
|
||||
const methodLogger = createLogger(`validate`)
|
||||
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 true
|
||||
}
|
||||
}
|
||||
4
core/src/command/Command.ts
Normal file
4
core/src/command/Command.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface Command<_T = any> {
|
||||
execute(): Promise<string | boolean | null | Error>
|
||||
validate?(): boolean
|
||||
}
|
||||
77
core/src/command/CommandExecutor.ts
Normal file
77
core/src/command/CommandExecutor.ts
Normal file
@ -0,0 +1,77 @@
|
||||
// core/src/command/CommandExecutor.ts
|
||||
|
||||
import { getLogger } from 'log4js'
|
||||
import { CommandJwtPayloadType } from 'shared'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
|
||||
import { interpretEncryptedTransferArgs } from '../graphql/logic/interpretEncryptedTransferArgs'
|
||||
import { CommandResult } from '../graphql/model/CommandResult'
|
||||
import { EncryptedTransferArgs } from '../graphql/model/EncryptedTransferArgs'
|
||||
import { Command } from './Command'
|
||||
import { CommandFactory } from './CommandFactory'
|
||||
|
||||
const createLogger = (method: string) =>
|
||||
getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandExecutor.${method}`)
|
||||
|
||||
export class CommandExecutor {
|
||||
async executeCommand<T>(command: Command<T>): Promise<CommandResult> {
|
||||
const methodLogger = createLogger(`executeCommand`)
|
||||
methodLogger.debug(`executeCommand() command=${command.constructor.name}`)
|
||||
try {
|
||||
if (command.validate && !command.validate()) {
|
||||
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()
|
||||
methodLogger.debug(`executeCommand() executed result=${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<_T>(encryptedArgs: EncryptedTransferArgs): Promise<CommandResult> {
|
||||
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.commandArgs,
|
||||
)
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`executeEncryptedCommand() command=${JSON.stringify(command)}`)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
81
core/src/command/CommandFactory.ts
Normal file
81
core/src/command/CommandFactory.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
|
||||
import { BaseCommand } from './BaseCommand'
|
||||
import { Command } from './Command'
|
||||
import { ICommandConstructor } from './CommandTypes'
|
||||
// import { ICommandConstructor } from './CommandTypes';
|
||||
import { SendEmailCommand } from './commands/SendEmailCommand'
|
||||
|
||||
const createLogger = (method: string) =>
|
||||
getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandFactory.${method}`)
|
||||
|
||||
export class CommandFactory {
|
||||
private static instance: CommandFactory
|
||||
private commands: Map<string, ICommandConstructor> = new Map()
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static getInstance(): CommandFactory {
|
||||
if (!CommandFactory.instance) {
|
||||
CommandFactory.instance = new CommandFactory()
|
||||
}
|
||||
return CommandFactory.instance
|
||||
}
|
||||
|
||||
registerCommand<T>(name: string, commandClass: ICommandConstructor<T>): void {
|
||||
const methodLogger = createLogger(`registerCommand`)
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`registerCommand() name=${name}, commandClass=${commandClass.name}`)
|
||||
}
|
||||
this.commands.set(name, commandClass)
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`registerCommand() commands=${JSON.stringify(this.commands.entries())}`)
|
||||
}
|
||||
}
|
||||
|
||||
createCommand<T>(name: string, params: string[]): Command<T> {
|
||||
const methodLogger = createLogger(`createCommand`)
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
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 === undefined) {
|
||||
const errmsg = `Command ${name} not found`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
/*
|
||||
try {
|
||||
const command = new CommandClass(params) as Command<T>;
|
||||
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);
|
||||
}
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
5
core/src/command/CommandTypes.ts
Normal file
5
core/src/command/CommandTypes.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Command } from './Command'
|
||||
|
||||
export interface ICommandConstructor<T = any> {
|
||||
new (params: any): Command<T>
|
||||
}
|
||||
145
core/src/command/commands/SendEmailCommand.ts
Normal file
145
core/src/command/commands/SendEmailCommand.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import { findUserByUuids } from 'database'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'
|
||||
import { sendTransactionReceivedEmail } from '../../emails/sendEmailVariants'
|
||||
import { BaseCommand } from '../BaseCommand'
|
||||
|
||||
const createLogger = (method: string) =>
|
||||
getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.commands.SendEmailCommand.${method}`)
|
||||
|
||||
export interface SendEmailCommandParams {
|
||||
mailType: string
|
||||
senderComUuid: string
|
||||
senderGradidoId: string
|
||||
receiverComUuid: string
|
||||
receiverGradidoId: string
|
||||
memo?: string
|
||||
amount?: string
|
||||
}
|
||||
export class SendEmailCommand extends BaseCommand<
|
||||
Record<string, unknown> | boolean | null | Error
|
||||
> {
|
||||
static readonly SEND_MAIL_COMMAND = 'SEND_MAIL_COMMAND'
|
||||
protected requiredFields: string[] = [
|
||||
'mailType',
|
||||
'senderComUuid',
|
||||
'senderGradidoId',
|
||||
'receiverComUuid',
|
||||
'receiverGradidoId',
|
||||
]
|
||||
protected sendEmailCommandParams: SendEmailCommandParams
|
||||
|
||||
constructor(params: any[]) {
|
||||
const methodLogger = createLogger(`constructor`)
|
||||
methodLogger.debug(`constructor() params=${JSON.stringify(params)}`)
|
||||
super(params)
|
||||
this.sendEmailCommandParams = JSON.parse(params[0]) as SendEmailCommandParams
|
||||
}
|
||||
|
||||
validate(): boolean {
|
||||
const baseValid = super.validate()
|
||||
if (!baseValid) {
|
||||
return false
|
||||
}
|
||||
// Additional validations
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async execute(): Promise<string | boolean | null | Error> {
|
||||
const methodLogger = createLogger(`execute`)
|
||||
methodLogger.debug(
|
||||
`execute() sendEmailCommandParams=${JSON.stringify(this.sendEmailCommandParams)}`,
|
||||
)
|
||||
let result: string
|
||||
if (!this.validate()) {
|
||||
throw new Error('Invalid command parameters')
|
||||
}
|
||||
// find sender user
|
||||
methodLogger.debug(
|
||||
`find sender user: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`,
|
||||
)
|
||||
const senderUser = await findUserByUuids(
|
||||
this.sendEmailCommandParams.senderComUuid,
|
||||
this.sendEmailCommandParams.senderGradidoId,
|
||||
true,
|
||||
)
|
||||
methodLogger.debug(`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 findUserByUuids(
|
||||
this.sendEmailCommandParams.receiverComUuid,
|
||||
this.sendEmailCommandParams.receiverGradidoId,
|
||||
)
|
||||
methodLogger.debug(`recipientUser=${JSON.stringify(recipientUser)}`)
|
||||
if (!recipientUser) {
|
||||
const errmsg = `Recipient user not found: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
|
||||
const emailParams = {
|
||||
firstName: recipientUser.firstName,
|
||||
lastName: recipientUser.lastName,
|
||||
email: recipientUser.emailContact.email,
|
||||
language: recipientUser.language,
|
||||
senderFirstName: senderUser.firstName,
|
||||
senderLastName: senderUser.lastName,
|
||||
senderEmail: senderUser.emailId !== null ? senderUser.emailContact.email : null,
|
||||
memo: this.sendEmailCommandParams.memo || '',
|
||||
transactionAmount: new Decimal(this.sendEmailCommandParams.amount || 0).abs(),
|
||||
}
|
||||
methodLogger.debug(`emailParams=${JSON.stringify(emailParams)}`)
|
||||
switch (this.sendEmailCommandParams.mailType) {
|
||||
case 'sendTransactionReceivedEmail': {
|
||||
const emailResult = await sendTransactionReceivedEmail(emailParams)
|
||||
result = this.getEmailResult(emailResult)
|
||||
break
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown mail type: ${this.sendEmailCommandParams.mailType}`)
|
||||
}
|
||||
|
||||
try {
|
||||
// Example: const result = await emailService.sendEmail(this.params);
|
||||
return result
|
||||
} catch (error) {
|
||||
methodLogger.error('Error executing SendEmailCommand:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private getEmailResult(result: Record<string, unknown> | boolean | null | Error): string {
|
||||
const methodLogger = createLogger(`getEmailResult`)
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`result=${JSON.stringify(result)}`)
|
||||
}
|
||||
let emailResult: string
|
||||
if (result === null) {
|
||||
emailResult = `result is null`
|
||||
} else if (typeof result === 'boolean') {
|
||||
emailResult = `result is ${result}`
|
||||
} else if (result instanceof Error) {
|
||||
emailResult = `error-message is ${result.message}`
|
||||
} else if (typeof result === 'object') {
|
||||
// {"accepted":["stage5@gradido.net"],"rejected":[],"ehlo":["PIPELINING","SIZE 25600000","ETRN","AUTH DIGEST-MD5 CRAM-MD5 PLAIN LOGIN","ENHANCEDSTATUSCODES","8BITMIME","DSN","CHUNKING"],"envelopeTime":23,"messageTime":135,"messageSize":37478,"response":"250 2.0.0 Ok: queued as C45C2100BD7","envelope":{"from":"stage5@gradido.net","to":["stage5@gradido.net"]},"messageId":"<d269161f-f3d2-2c96-49c0-58154366271b@gradido.net>"
|
||||
const accepted = (result as Record<string, unknown>).accepted
|
||||
const messageSize = (result as Record<string, unknown>).messageSize
|
||||
const response = (result as Record<string, unknown>).response
|
||||
const envelope = JSON.stringify((result as Record<string, unknown>).envelope)
|
||||
emailResult = `accepted=${accepted}, messageSize=${messageSize}, response=${response}, envelope=${envelope}`
|
||||
} else {
|
||||
emailResult = `result is unknown type`
|
||||
}
|
||||
|
||||
return emailResult
|
||||
}
|
||||
}
|
||||
11
core/src/command/initCommands.ts
Normal file
11
core/src/command/initCommands.ts
Normal file
@ -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...
|
||||
}
|
||||
@ -175,18 +175,18 @@ export const sendTransactionReceivedEmail = (
|
||||
data: EmailCommonData & {
|
||||
senderFirstName: string
|
||||
senderLastName: string
|
||||
senderEmail: string
|
||||
senderEmail: string | null
|
||||
memo: string
|
||||
transactionAmount: Decimal
|
||||
},
|
||||
): Promise<Record<string, unknown> | boolean | null | Error> => {
|
||||
return sendEmailTranslated({
|
||||
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
|
||||
template: 'transactionReceived',
|
||||
template: data.senderEmail !== null ? 'transactionReceived' : 'transactionReceivedNoSender',
|
||||
locals: {
|
||||
...data,
|
||||
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
|
||||
...getEmailCommonLocales(),
|
||||
...(data.senderEmail !== null ? getEmailCommonLocales() : { locale: data.language }),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
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')}
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1 @@
|
||||
= t('emails.transactionReceived.subject', { senderFirstName, senderLastName, transactionAmount })
|
||||
45
core/src/federation/client/1_0/CommandClient.ts
Normal file
45
core/src/federation/client/1_0/CommandClient.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { FederatedCommunity as DbFederatedCommunity } from 'database'
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../../../config/const'
|
||||
import { EncryptedTransferArgs } from '../../../graphql/model/EncryptedTransferArgs'
|
||||
import { ensureUrlEndsWithSlash } from '../../../util/utilities'
|
||||
import { sendCommand as sendCommandQuery } from './query/sendCommand'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.client.1_0.CommandClient`)
|
||||
|
||||
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<boolean> {
|
||||
logger.debug(`sendCommand at ${this.endpoint} for args:`, args)
|
||||
try {
|
||||
const { data } = await this.client.rawRequest<{ success: boolean }>(sendCommandQuery, {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
11
core/src/federation/client/1_0/query/sendCommand.ts
Normal file
11
core/src/federation/client/1_0/query/sendCommand.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { gql } from 'graphql-request'
|
||||
|
||||
export const sendCommand = gql`
|
||||
mutation ($args: EncryptedTransferArgs!) {
|
||||
sendCommand(encryptedArgs: $args) {
|
||||
success
|
||||
data
|
||||
error
|
||||
}
|
||||
}
|
||||
`
|
||||
3
core/src/federation/client/1_1/CommandClient.ts
Normal file
3
core/src/federation/client/1_1/CommandClient.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { CommandClient as V1_0_CommandClient } from '../1_0/CommandClient'
|
||||
|
||||
export class CommandClient extends V1_0_CommandClient {}
|
||||
55
core/src/federation/client/CommandClientFactory.ts
Normal file
55
core/src/federation/client/CommandClientFactory.ts
Normal file
@ -0,0 +1,55 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
14
core/src/graphql/logic/processCommand.ts
Normal file
14
core/src/graphql/logic/processCommand.ts
Normal file
@ -0,0 +1,14 @@
|
||||
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...')
|
||||
}
|
||||
@ -16,6 +16,7 @@ import { Decimal } from 'decimal.js-light'
|
||||
// import { LogError } from '@server/LogError'
|
||||
import { getLogger } from 'log4js'
|
||||
import {
|
||||
CommandJwtPayloadType,
|
||||
encryptAndSign,
|
||||
PendingTransactionState,
|
||||
SendCoinsJwtPayloadType,
|
||||
@ -23,11 +24,15 @@ import {
|
||||
verifyAndDecrypt,
|
||||
} from 'shared'
|
||||
import { randombytes_random } from 'sodium-native'
|
||||
import { SendEmailCommand } from '../../command/commands/SendEmailCommand'
|
||||
import { CONFIG as CONFIG_CORE } from '../../config'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'
|
||||
import { sendTransactionLinkRedeemedEmail, sendTransactionReceivedEmail } from '../../emails'
|
||||
import { CommandClient as V1_0_CommandClient } from '../../federation/client/1_0/CommandClient'
|
||||
import { SendCoinsResultLoggingView } from '../../federation/client/1_0/logging/SendCoinsResultLogging.view'
|
||||
import { SendCoinsResult } from '../../federation/client/1_0/model/SendCoinsResult'
|
||||
import { SendCoinsClient as V1_0_SendCoinsClient } from '../../federation/client/1_0/SendCoinsClient'
|
||||
import { CommandClientFactory } from '../../federation/client/CommandClientFactory'
|
||||
import { SendCoinsClientFactory } from '../../federation/client/SendCoinsClientFactory'
|
||||
import { TransactionTypeId } from '../../graphql/enum/TransactionTypeId'
|
||||
import { EncryptedTransferArgs } from '../../graphql/model/EncryptedTransferArgs'
|
||||
@ -167,6 +172,32 @@ 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: 'unknown', // foreignUser.emailContact.email,
|
||||
transactionAmount: new Decimal(amount),
|
||||
transactionMemo: memo,
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
const errmsg =
|
||||
@ -227,7 +258,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,
|
||||
},
|
||||
})
|
||||
@ -484,6 +515,37 @@ 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)}`,
|
||||
|
||||
@ -1,5 +1,13 @@
|
||||
import { Community as DbCommunity, User as DbUser, 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'
|
||||
|
||||
@ -35,17 +43,38 @@ 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:',
|
||||
new UserContactLoggingView(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 === 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 or alias:',
|
||||
user,
|
||||
new UserLoggingView(user),
|
||||
committingResult,
|
||||
)
|
||||
if (committingResult.recipFirstName !== null) {
|
||||
@ -57,11 +86,39 @@ 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,
|
||||
)
|
||||
let foreignUserEmail = DbUserContact.create()
|
||||
foreignUserEmail.type = UserContactType.USER_CONTACT_EMAIL
|
||||
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
|
||||
user.emailId = foreignUserEmail.id
|
||||
} else if (user.emailContact && 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:', 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) {
|
||||
|
||||
13
core/src/graphql/model/CommandResult.ts
Normal file
13
core/src/graphql/model/CommandResult.ts
Normal file
@ -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
|
||||
}
|
||||
@ -1,5 +1,9 @@
|
||||
export * from './command/CommandExecutor'
|
||||
export * from './command/CommandFactory'
|
||||
export * from './command/initCommands'
|
||||
export * from './config/index'
|
||||
export * from './emails'
|
||||
export { CommandClient as V1_0_CommandClient } from './federation/client/1_0/CommandClient'
|
||||
export * from './federation/client/1_0/logging/SendCoinsArgsLogging.view'
|
||||
export * from './federation/client/1_0/logging/SendCoinsResultLogging.view'
|
||||
export * from './federation/client/1_0/model/SendCoinsArgs'
|
||||
@ -9,15 +13,19 @@ 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 { SendCoinsClient as V1_0_SendCoinsClient } from './federation/client/1_0/SendCoinsClient'
|
||||
export { CommandClient as V1_1_CommandClient } from './federation/client/1_1/CommandClient'
|
||||
export { SendCoinsClient as V1_1_SendCoinsClient } from './federation/client/1_1/SendCoinsClient'
|
||||
export * from './federation/client/CommandClientFactory'
|
||||
export * from './federation/client/SendCoinsClientFactory'
|
||||
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 './logic'
|
||||
|
||||
@ -17,21 +17,25 @@ 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +82,17 @@ export async function findForeignUserByUuids(
|
||||
})
|
||||
}
|
||||
|
||||
export async function findUserByUuids(
|
||||
communityUuid: string,
|
||||
gradidoID: string,
|
||||
foreign: boolean = false,
|
||||
): Promise<DbUser | null> {
|
||||
return DbUser.findOne({
|
||||
where: { foreign, communityUuid, gradidoID },
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
}
|
||||
|
||||
export async function findUserNamesByIds(userIds: number[]): Promise<Map<number, string>> {
|
||||
const users = await DbUser.find({
|
||||
select: { id: true, firstName: true, lastName: true, alias: true },
|
||||
|
||||
@ -0,0 +1,273 @@
|
||||
<mxfile host="Electron" modified="2025-12-18T01:25:36.977Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.0.3 Chrome/114.0.5735.289 Electron/25.8.4 Safari/537.36" etag="ulUa-DwD6iMx1WLL9hxg" version="22.0.3" type="device">
|
||||
<diagram name="Seite-1" id="pXcQQGq2mbEeDNBOBd4l">
|
||||
<mxGraphModel dx="1206" dy="702" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-4" value="recepient: backend:<br>TransactionLinkResolver" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="40" width="140" height="920" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-5" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-4">
|
||||
<mxGeometry x="65" y="70" width="10" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-6" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">queryTransactionLink</div>" style="html=1;verticalAlign=bottom;startArrow=oval;endArrow=block;startSize=8;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-5" parent="EPUhzRXaTLY6ULSqPNZg-4">
|
||||
<mxGeometry x="-0.2381" y="5" relative="1" as="geometry">
|
||||
<mxPoint x="-40" y="75" as="sourcePoint" />
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-7" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-4">
|
||||
<mxGeometry x="65" y="160" width="10" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-8" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">queryRedeemJwtLink</div>" style="html=1;align=left;spacingLeft=2;endArrow=block;rounded=0;edgeStyle=orthogonalEdgeStyle;curved=0;rounded=0;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-7" parent="EPUhzRXaTLY6ULSqPNZg-4">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="70" y="140" as="sourcePoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="100" y="170" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-11" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-4">
|
||||
<mxGeometry x="65" y="230" width="10" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-12" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">redeemTransactionLink</div>" style="html=1;verticalAlign=bottom;startArrow=oval;endArrow=block;startSize=8;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-11" parent="EPUhzRXaTLY6ULSqPNZg-4">
|
||||
<mxGeometry x="-0.4286" y="5" relative="1" as="geometry">
|
||||
<mxPoint x="-40" y="235" as="sourcePoint" />
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-13" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-4">
|
||||
<mxGeometry x="65" y="393" width="10" height="367" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-14" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;"><div style="line-height: 19px;">disburseTransactionLink</div></div>" style="html=1;verticalAlign=bottom;startArrow=oval;endArrow=block;startSize=8;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" parent="EPUhzRXaTLY6ULSqPNZg-4" target="EPUhzRXaTLY6ULSqPNZg-13">
|
||||
<mxGeometry x="-0.4286" y="5" relative="1" as="geometry">
|
||||
<mxPoint x="-40" y="398" as="sourcePoint" />
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-9" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">TransactionLink</div>" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;" edge="1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="140" y="170" as="sourcePoint" />
|
||||
<mxPoint x="40" y="170" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-10" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">RedeemJwtLink</div>" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;" edge="1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="140" y="230" as="sourcePoint" />
|
||||
<mxPoint x="40" y="230" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-15" value="backend:<br>:TransactionReslover" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="240" y="40" width="120" height="920" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-16" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-15">
|
||||
<mxGeometry x="55" y="290" width="10" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-17" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">executeTransaction</div>" style="html=1;verticalAlign=bottom;startArrow=oval;endArrow=block;startSize=8;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-16" parent="1" source="EPUhzRXaTLY6ULSqPNZg-11">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="265" y="335" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-18" value="sender: federation:<br>:DisbursementResolver" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="380" y="40" width="130" height="920" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-19" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-18">
|
||||
<mxGeometry x="60" y="400" width="10" height="320" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-20" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">processDisburseJwtOnSenderCommunity</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-19" parent="1" source="EPUhzRXaTLY6ULSqPNZg-13">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="430" y="445" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-22" value="sender: core:<br>processXComSendCoins" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="570" y="40" width="140" height="1080" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-23" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-22">
|
||||
<mxGeometry x="65" y="410" width="10" height="640" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-26" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-22">
|
||||
<mxGeometry x="68" y="435" width="10" height="225" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-27" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">processXComPendingSendCoins</div>" style="html=1;align=left;spacingLeft=2;endArrow=block;rounded=0;edgeStyle=orthogonalEdgeStyle;curved=0;rounded=0;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-26" parent="EPUhzRXaTLY6ULSqPNZg-22">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="70" y="420" as="sourcePoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="100" y="420" />
|
||||
<mxPoint x="100" y="450" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-43" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-22">
|
||||
<mxGeometry x="68" y="710" width="10" height="270" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-44" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">processXComCommittingSendCoins</div>" style="html=1;align=left;spacingLeft=2;endArrow=block;rounded=0;edgeStyle=orthogonalEdgeStyle;curved=0;rounded=0;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-43" parent="EPUhzRXaTLY6ULSqPNZg-22">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="73" y="690" as="sourcePoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="103" y="720" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-24" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">processXComCompleteTransaction</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-23" parent="1" source="EPUhzRXaTLY6ULSqPNZg-19">
|
||||
<mxGeometry x="-0.027" y="5" relative="1" as="geometry">
|
||||
<mxPoint x="595" y="455" as="sourcePoint" />
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-28" value="recepient: federation:<br>SendCoinsResolver" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="840" y="40" width="120" height="1080" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-29" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-28">
|
||||
<mxGeometry x="55" y="488" width="10" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-30" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">voteForSendCoins</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-29" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="533" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-32" value="recepient: database:<br>PendingTransaction" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="960" y="480" width="120" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-33" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-32">
|
||||
<mxGeometry x="55" y="58" width="10" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-35" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" source="EPUhzRXaTLY6ULSqPNZg-33" parent="1" target="EPUhzRXaTLY6ULSqPNZg-29">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="945" y="613" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-34" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">insert</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-33" parent="1" source="EPUhzRXaTLY6ULSqPNZg-29">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="945" y="543" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-31" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" source="EPUhzRXaTLY6ULSqPNZg-29" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="573" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-36" value="sender: database:<br>PendingTransaction" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="715" y="588" width="120" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-37" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-36">
|
||||
<mxGeometry x="55" y="60" width="10" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-38" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" parent="1" source="EPUhzRXaTLY6ULSqPNZg-37">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="673" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-39" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">insert</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" parent="1" target="EPUhzRXaTLY6ULSqPNZg-37">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="653" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-21" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" source="EPUhzRXaTLY6ULSqPNZg-19" parent="1" target="EPUhzRXaTLY6ULSqPNZg-13">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="370" y="515" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-41" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;" edge="1" parent="1" target="EPUhzRXaTLY6ULSqPNZg-23">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="690" as="sourcePoint" />
|
||||
<mxPoint x="740" y="690" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="670" y="690" />
|
||||
<mxPoint x="670" y="720" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-46" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="895" y="788" width="10" height="132" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-47" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;"><div style="line-height: 19px;">settleSendCoins</div></div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" parent="1" target="EPUhzRXaTLY6ULSqPNZg-46">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="793" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-48" value="recepient: database:<br>Transaction" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="1210" y="740" width="120" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-49" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-48">
|
||||
<mxGeometry x="55" y="60" width="10" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-50" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" parent="1" source="EPUhzRXaTLY6ULSqPNZg-49" target="EPUhzRXaTLY6ULSqPNZg-58">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="1153" y="823" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-51" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">insert</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;exitX=1;exitY=0;exitDx=0;exitDy=5;exitPerimeter=0;" edge="1" parent="1" source="EPUhzRXaTLY6ULSqPNZg-59" target="EPUhzRXaTLY6ULSqPNZg-49">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="1153" y="803" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-52" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="910" as="targetPoint" />
|
||||
<mxPoint x="895" y="910" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-53" value="sender: database:<br>PendingTransaction" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="715" y="920" width="120" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-54" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-53">
|
||||
<mxGeometry x="55" y="60" width="10" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-55" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" parent="1" source="EPUhzRXaTLY6ULSqPNZg-54" target="EPUhzRXaTLY6ULSqPNZg-43">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="995" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-56" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">insert</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" parent="1" target="EPUhzRXaTLY6ULSqPNZg-54" source="EPUhzRXaTLY6ULSqPNZg-43">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="975" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-57" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;" edge="1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="1012" as="sourcePoint" />
|
||||
<mxPoint x="645" y="1042" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="670" y="1012" />
|
||||
<mxPoint x="670" y="1042" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-58" value="recepient: federation:<br>settlePendingReceiveTransaction" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="990" y="740" width="200" height="220" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-59" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-58">
|
||||
<mxGeometry x="95" y="60" width="10" height="110" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-60" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" parent="1" source="EPUhzRXaTLY6ULSqPNZg-59" target="EPUhzRXaTLY6ULSqPNZg-28">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="905" y="823" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-61" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;"><div style="line-height: 19px;">settlePendingReceiveTransaction</div></div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" parent="1" target="EPUhzRXaTLY6ULSqPNZg-59">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="905" y="803" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-62" value="recepient: database:<br>PendingTransaction" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="1300" y="810" width="120" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-63" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-62">
|
||||
<mxGeometry x="55" y="60" width="10" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-64" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;exitPerimeter=0;" edge="1" parent="1" source="EPUhzRXaTLY6ULSqPNZg-63" target="EPUhzRXaTLY6ULSqPNZg-59">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="1160" y="907.5" as="targetPoint" />
|
||||
<mxPoint x="1335" y="907.5" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-65" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">settled</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;entryPerimeter=0;" edge="1" parent="1" source="EPUhzRXaTLY6ULSqPNZg-59" target="EPUhzRXaTLY6ULSqPNZg-63">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="1165" y="887.5" as="sourcePoint" />
|
||||
<mxPoint x="1335" y="887.5" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
17
federation/src/graphql/api/1_0/resolver/CommandResolver.ts
Normal file
17
federation/src/graphql/api/1_0/resolver/CommandResolver.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { CommandExecutor, CommandResult, EncryptedTransferArgs } from 'core'
|
||||
import { Arg, Ctx, Mutation, Resolver } from 'type-graphql'
|
||||
|
||||
@Resolver()
|
||||
export class CommandResolver {
|
||||
private commandExecutor = new CommandExecutor()
|
||||
|
||||
@Mutation(() => CommandResult)
|
||||
async sendCommand(
|
||||
@Arg('encryptedArgs', () => EncryptedTransferArgs) encryptedArgs: any,
|
||||
@Ctx() context: any,
|
||||
): Promise<CommandResult> {
|
||||
// Convert to EncryptedTransferArgs if needed
|
||||
const result = await this.commandExecutor.executeEncryptedCommand(encryptedArgs)
|
||||
return result as unknown as CommandResult
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import { NonEmptyArray } from 'type-graphql'
|
||||
import { AuthenticationResolver } from './resolver/AuthenticationResolver'
|
||||
import { CommandResolver } from './resolver/CommandResolver'
|
||||
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<Function> => {
|
||||
return [
|
||||
AuthenticationResolver,
|
||||
CommandResolver,
|
||||
DisbursementResolver,
|
||||
PublicCommunityInfoResolver,
|
||||
PublicKeyResolver,
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
import { NonEmptyArray } from 'type-graphql'
|
||||
import { AuthenticationResolver } from '../1_0/resolver/AuthenticationResolver'
|
||||
import { CommandResolver } from '../1_0/resolver/CommandResolver'
|
||||
import { PublicCommunityInfoResolver } from '../1_0/resolver/PublicCommunityInfoResolver'
|
||||
import { SendCoinsResolver } from '../1_0/resolver/SendCoinsResolver'
|
||||
import { PublicKeyResolver } from './resolver/PublicKeyResolver'
|
||||
|
||||
export const getApiResolvers = (): NonEmptyArray<Function> => {
|
||||
return [AuthenticationResolver, PublicCommunityInfoResolver, PublicKeyResolver, SendCoinsResolver]
|
||||
return [
|
||||
AuthenticationResolver,
|
||||
CommandResolver,
|
||||
PublicCommunityInfoResolver,
|
||||
PublicKeyResolver,
|
||||
SendCoinsResolver,
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'source-map-support/register'
|
||||
|
||||
import { defaultCategory, initLogger } from 'config-schema'
|
||||
import { initializeCommands } from 'core'
|
||||
import { getLogger } from 'log4js'
|
||||
import { onShutdown, printServerCrashAsciiArt, ShutdownReason } from 'shared'
|
||||
// config
|
||||
@ -44,6 +45,8 @@ async function main() {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
initializeCommands()
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
|
||||
@ -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'
|
||||
|
||||
22
shared/src/jwt/payloadtypes/CommandJwtPayloadType.ts
Normal file
22
shared/src/jwt/payloadtypes/CommandJwtPayloadType.ts
Normal file
@ -0,0 +1,22 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user