diff --git a/apps/agent-service/src/agent-service.controller.ts b/apps/agent-service/src/agent-service.controller.ts index 2d0becf33..bf5dc67a4 100644 --- a/apps/agent-service/src/agent-service.controller.ts +++ b/apps/agent-service/src/agent-service.controller.ts @@ -261,6 +261,11 @@ export class AgentServiceController { return this.agentServiceService.sendQuestion(payload.questionPayload, payload.url, payload.orgId); } + @MessagePattern({ cmd: 'agent-send-basic-message' }) + async sendBasicMessage(payload: { url, orgId, content }): Promise { + return this.agentServiceService.sendBasicMessage(payload.content, payload.url, payload.orgId); + } + @MessagePattern({ cmd: 'agent-get-question-answer-record' }) async getQuestionAnswersRecord(payload: { url: string, orgId: string }): Promise { return this.agentServiceService.getQuestionAnswersRecord(payload.url, payload.orgId); diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 034db1331..3ed866425 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -53,7 +53,8 @@ import { AgentHealthData, IAgentStore, IAgentConfigure, - OrgDid + OrgDid, + IBasicMessage } from './interface/agent-service.interface'; import { AgentSpinUpStatus, AgentType, DidMethod, Ledgers, OrgAgentType, PromiseResult } from '@credebl/enum/enum'; import { AgentServiceRepository } from './repositories/agent-service.repository'; @@ -1811,6 +1812,19 @@ export class AgentServiceService { } } + async sendBasicMessage(questionPayload: IBasicMessage, url: string, orgId: string): Promise { + try { + const getApiKey = await this.getOrgAgentApiKey(orgId); + const sendQuestionRes = await this.commonService + .httpPost(url, questionPayload, { headers: { authorization: getApiKey } }) + .then(async (response) => response); + return sendQuestionRes; + } catch (error) { + this.logger.error(`Error in sendBasicMessage in agent service : ${JSON.stringify(error)}`); + throw error; + } + } + async getQuestionAnswersRecord(url: string, orgId: string): Promise { try { const getQuestionAnswersRecord = await this.agentCall(url, orgId); diff --git a/apps/agent-service/src/interface/agent-service.interface.ts b/apps/agent-service/src/interface/agent-service.interface.ts index f8a1726ba..ef12eee2c 100644 --- a/apps/agent-service/src/interface/agent-service.interface.ts +++ b/apps/agent-service/src/interface/agent-service.interface.ts @@ -550,6 +550,9 @@ export interface IQuestionPayload { connectionId: string; tenantId: string; } +export interface IBasicMessage { + content: string; +} interface Ledger { id: string; createDateTime: string; diff --git a/apps/api-gateway/src/connection/connection.controller.ts b/apps/api-gateway/src/connection/connection.controller.ts index 1775feb62..29bca50c2 100644 --- a/apps/api-gateway/src/connection/connection.controller.ts +++ b/apps/api-gateway/src/connection/connection.controller.ts @@ -20,7 +20,7 @@ import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface'; import { SortFields } from 'apps/connection/src/enum/connection.enum'; import { ClientProxy} from '@nestjs/microservices'; -import { QuestionAnswerWebhookDto, QuestionDto} from './dtos/question-answer.dto'; +import { BasicMessageDto, QuestionAnswerWebhookDto, QuestionDto} from './dtos/question-answer.dto'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { user } from '@prisma/client'; @UseFilters(CustomExceptionFilter) @@ -365,4 +365,30 @@ export class ConnectionController { }; return res.status(HttpStatus.OK).json(finalResponse); } + + // WIP : Create final Response + @Post('/orgs/:orgId/basic-message/:connectionId') + @ApiOperation({ summary: '', description: 'send question' }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER, OrgRoles.SUPER_ADMIN, OrgRoles.PLATFORM_ADMIN) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + async sendBasicMessage( + @Param('orgId') orgId: string, + @Param('connectionId') connectionId: string, + @Body() basicMessageDto: BasicMessageDto, + @User() reqUser: IUserRequestInterface, + @Res() res: Response + ): Promise { + + basicMessageDto.orgId = orgId; + basicMessageDto.connectionId = connectionId; + const basicMesgResponse = await this.connectionService.sendBasicMessage(basicMessageDto); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.connection.success.basicMessage, + data: basicMesgResponse + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + + } } diff --git a/apps/api-gateway/src/connection/connection.service.ts b/apps/api-gateway/src/connection/connection.service.ts index 615b5f57f..4b726eec8 100644 --- a/apps/api-gateway/src/connection/connection.service.ts +++ b/apps/api-gateway/src/connection/connection.service.ts @@ -6,7 +6,7 @@ import { ConnectionDto, CreateOutOfBandConnectionInvitation, ReceiveInvitationDt import { IReceiveInvitationRes, IUserRequestInterface } from './interfaces'; import { IConnectionList, IDeletedConnectionsRecord } from '@credebl/common/interfaces/connection.interface'; import { AgentConnectionSearchCriteria, IConnectionDetailsById, IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface'; -import { QuestionDto } from './dtos/question-answer.dto'; +import { BasicMessageDto, QuestionDto } from './dtos/question-answer.dto'; import { user } from '@prisma/client'; @Injectable() export class ConnectionService extends BaseService { @@ -24,6 +24,16 @@ export class ConnectionService extends BaseService { } } + sendBasicMessage( + basicMessageDto: BasicMessageDto + ): Promise { + try { + return this.sendNatsMessage(this.connectionServiceProxy, 'send-basic-message', basicMessageDto); + } catch (error) { + throw new RpcException(error.response); + } + } + getConnectionWebhook( connectionDto: ConnectionDto, orgId: string diff --git a/apps/api-gateway/src/connection/dtos/question-answer.dto.ts b/apps/api-gateway/src/connection/dtos/question-answer.dto.ts index 583448183..d9deb3dfc 100644 --- a/apps/api-gateway/src/connection/dtos/question-answer.dto.ts +++ b/apps/api-gateway/src/connection/dtos/question-answer.dto.ts @@ -35,6 +35,17 @@ export class QuestionDto { connectionId: string; } +export class BasicMessageDto { + @ApiPropertyOptional() + @IsOptional() + @IsString({ message: 'content must be a string' }) + @IsNotEmpty({ message: 'please provide valid content' }) + content: string; + + orgId: string; + connectionId: string; +} + export class QuestionAnswerWebhookDto { diff --git a/apps/connection/src/connection.controller.ts b/apps/connection/src/connection.controller.ts index 2be981052..30c33a6fc 100644 --- a/apps/connection/src/connection.controller.ts +++ b/apps/connection/src/connection.controller.ts @@ -13,7 +13,7 @@ import { } from './interfaces/connection.interfaces'; import { IConnectionList, IDeletedConnectionsRecord } from '@credebl/common/interfaces/connection.interface'; import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; -import { IQuestionPayload } from './interfaces/question-answer.interfaces'; +import { IQuestionPayload } from './interfaces/messaging.interfaces'; import { user } from '@prisma/client'; @Controller() export class ConnectionController { @@ -101,4 +101,9 @@ export class ConnectionController { const { orgId, userDetails } = payload; return this.connectionService.deleteConnectionRecords(orgId, userDetails); } + + @MessagePattern({ cmd: 'send-basic-message' }) + async sendBasicMessage(payload: {content: string, orgId: string, connectionId: string}): Promise { + return this.connectionService.sendBasicMesage(payload); + } } diff --git a/apps/connection/src/connection.service.ts b/apps/connection/src/connection.service.ts index 2e14f89e2..5cad28ba7 100644 --- a/apps/connection/src/connection.service.ts +++ b/apps/connection/src/connection.service.ts @@ -23,7 +23,7 @@ import { Cache } from 'cache-manager'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { IConnectionList, ICreateConnectionUrl, IDeletedConnectionsRecord } from '@credebl/common/interfaces/connection.interface'; import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; -import { IQuestionPayload } from './interfaces/question-answer.interfaces'; +import { IBasicMessage, IQuestionPayload } from './interfaces/messaging.interfaces'; import { RecordType, user } from '@prisma/client'; import { UserActivityRepository } from 'libs/user-activity/repositories'; import { agent_invitations } from '@prisma/client'; @@ -841,5 +841,56 @@ export class ConnectionService { this.logger.error(`[deleteConnectionRecords] - error in deleting connection records: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } -} + } + + + async sendBasicMesage(payload: IBasicMessage): Promise { + const { content, orgId, connectionId } = payload; + try { + const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); + + const { agentEndPoint } = agentDetails; + + if (!agentDetails) { + throw new NotFoundException(ResponseMessages.connection.error.agentEndPointNotFound); + } + + const questionPayload = { + content + }; + + const organizationAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + const label = 'send-basic-message'; + const agentUrl = await this.commonService.sendBasicMessageAgentUrl( + label, + organizationAgentType, + agentEndPoint, + agentDetails?.tenantId, + connectionId + ); + + const sendBasicMessage = await this._sendBasicMessageToAgent(questionPayload, agentUrl, orgId); + return sendBasicMessage; + } catch (error) { + this.logger.error(`[sendBasicMesage] - error in send basic message: ${error}`); + if (error && error?.status && error?.status?.message && error?.status?.message?.error) { + throw new RpcException({ + message: error?.status?.message?.error?.reason + ? error?.status?.message?.error?.reason + : error?.status?.message?.error, + statusCode: error?.status?.code + }); + } else { + throw new RpcException(error.response ? error.response : error); + } + } + } + + async _sendBasicMessageToAgent(content: IBasicMessage, url: string, orgId: string): Promise { + const pattern = { cmd: 'agent-send-basic-message' }; + const payload = { content, url, orgId }; + // eslint-disable-next-line no-return-await + return await this.natsCall(pattern, payload); + } + } diff --git a/apps/connection/src/interfaces/messaging.interfaces.ts b/apps/connection/src/interfaces/messaging.interfaces.ts new file mode 100644 index 000000000..e5cbbacd2 --- /dev/null +++ b/apps/connection/src/interfaces/messaging.interfaces.ts @@ -0,0 +1,19 @@ +export interface IValidResponses { + text: string; +} + +export interface IQuestionPayload { + detail: string; + validResponses: IValidResponses[]; + question: string; + orgId?: string; + connectionId?: string; + tenantId?: string; +} + +export interface IBasicMessage { + content: string; + orgId?: string; + connectionId?: string; + tenantId?: string; +} diff --git a/apps/connection/src/interfaces/question-answer.interfaces.ts b/apps/connection/src/interfaces/question-answer.interfaces.ts deleted file mode 100644 index 6fcabd9ef..000000000 --- a/apps/connection/src/interfaces/question-answer.interfaces.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface IValidResponses { - text: string; - } - export interface IQuestionPayload { - detail: string; - validResponses: IValidResponses[]; - question: string; - orgId?: string; - connectionId?: string; - tenantId?: string; - } diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index e80085d05..0f1f698c6 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -77,6 +77,7 @@ export enum CommonConstants { URL_ACCEPT_CREDENTIALS = '/credentials/accept-offer', URL_SEND_QUESTION = '/question-answer/question/#', URL_QUESTION_ANSWER_RECORD = '/question-answer', + URL_SEND_BASIC_MESSAGE = '/basic-messages/#', // SCHEMA & CRED DEF SERVICES URL_SCHM_CREATE_SCHEMA = '/schemas', @@ -119,6 +120,7 @@ export enum CommonConstants { URL_SHAGENT_SEND_ANSWER = '/multi-tenancy/question-answer/answer/#/@', URL_SHAGENT_QUESTION_ANSWER_RECORD = '/multi-tenancy/question-answer/#', URL_SHAGENT_DELETE_SUB_WALLET = '/multi-tenancy/#', + URL_SHARED_SEND_BASIC_MESSAGE = '/multi-tenancy/basic-message/#/@', URL_SHAGENT_ACCEPT_PROOF_REQUEST = '/multi-tenancy/proofs/#/accept-request/@', diff --git a/libs/common/src/common.service.ts b/libs/common/src/common.service.ts index 00298689f..ce3addceb 100644 --- a/libs/common/src/common.service.ts +++ b/libs/common/src/common.service.ts @@ -11,7 +11,8 @@ import { HttpException, HttpStatus, Injectable, - Logger + Logger, + NotFoundException } from '@nestjs/common'; import { CommonConstants } from './common.constant'; @@ -21,6 +22,7 @@ import * as dotenv from 'dotenv'; import { RpcException } from '@nestjs/microservices'; import { ResponseMessages } from './response-messages'; import { IOptionalParams } from './interfaces/interface'; +import { OrgAgentType } from '@credebl/enum/enum'; dotenv.config(); @Injectable() @@ -479,5 +481,41 @@ async createDynamicUrl(urlOptions: IOptionalParams): Promise { } } +async sendBasicMessageAgentUrl( + label: string, + orgAgentType: string, + agentEndPoint: string, + tenantId?: string, + connectionId?: string +): Promise { + try { + let url; + switch (label) { + case 'send-basic-message': { + url = + orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_SEND_BASIC_MESSAGE}`.replace('#', connectionId) + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHARED_SEND_BASIC_MESSAGE}` + .replace('#', connectionId) + .replace('@', tenantId) + : null; + break; + } + + default: { + break; + } + } + + if (!url) { + throw new NotFoundException(ResponseMessages.issuance.error.agentUrlNotFound); + } + return url; + } catch (error) { + this.logger.error(`Error in getting basic-message Url: ${JSON.stringify(error)}`); + throw error; + } +} } diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 6c7086858..a2b044dcd 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -275,7 +275,8 @@ export const ResponseMessages = { fetch: 'Connections details fetched successfully', questionAnswerRecord: 'Question Answer record fetched successfully', questionSend: 'Question sent successfully', - deleteConnectionRecord: 'Connection records deleted' + deleteConnectionRecord: 'Connection records deleted', + basicMessage: 'Basic message sent successfully' }, error: { exists: 'Connection is already exist',