From 1254f971e353272fbf6e51a62a66839c7fac056b Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Mon, 4 Mar 2024 16:17:19 +0530 Subject: [PATCH 1/2] feat: added connection list from agent Signed-off-by: tipusinghaw --- .../src/agent-service.controller.ts | 4 +- .../src/agent-service.service.ts | 3 +- .../src/connection/connection.controller.ts | 39 +++- .../src/connection/connection.service.ts | 11 +- .../dtos/get-all-connections.dto.ts | 94 +++++--- .../interfaces/IConnectionSearch.interface.ts | 47 ++-- apps/connection/src/connection.controller.ts | 9 + apps/connection/src/connection.service.ts | 217 +++++++++++------- .../src/interfaces/connection.interfaces.ts | 28 +++ .../src/interfaces/agent-service.interface.ts | 53 +++++ 10 files changed, 363 insertions(+), 142 deletions(-) create mode 100644 libs/common/src/interfaces/agent-service.interface.ts diff --git a/apps/agent-service/src/agent-service.controller.ts b/apps/agent-service/src/agent-service.controller.ts index 55e6452d2..163c7edca 100644 --- a/apps/agent-service/src/agent-service.controller.ts +++ b/apps/agent-service/src/agent-service.controller.ts @@ -3,9 +3,9 @@ import { MessagePattern } from '@nestjs/microservices'; import { AgentServiceService } from './agent-service.service'; import { IAgentStatus, IConnectionDetails, IUserRequestInterface, ISendProofRequestPayload, IAgentSpinUpSatus, IGetCredDefAgentRedirection, IGetSchemaAgentRedirection, IAgentSpinupDto, IIssuanceCreateOffer, ITenantCredDef, ITenantDto, ITenantSchema, IOutOfBandCredentialOffer, IProofPresentation, IAgentProofRequest, IPresentation } from './interface/agent-service.interface'; import { user } from '@prisma/client'; -import { ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface'; import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; import { IProofPresentationDetails } from '@credebl/common/interfaces/verification.interface'; +import { InvitationMessage } from '@credebl/common/interfaces/agent-service.interface'; @Controller() export class AgentServiceController { @@ -54,7 +54,7 @@ export class AgentServiceController { //DONE @MessagePattern({ cmd: 'agent-create-connection-legacy-invitation' }) - async createLegacyConnectionInvitation(payload: { connectionPayload: IConnectionDetails, url: string, apiKey: string }): Promise { + async createLegacyConnectionInvitation(payload: { connectionPayload: IConnectionDetails, url: string, apiKey: string }): Promise { return this.agentServiceService.createLegacyConnectionInvitation(payload.connectionPayload, payload.url, payload.apiKey); } diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 6dca30728..36552d004 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -36,6 +36,7 @@ import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { IProofPresentationDetails } from '@credebl/common/interfaces/verification.interface'; import { ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface'; import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; +import { InvitationMessage } from '@credebl/common/interfaces/agent-service.interface'; @Injectable() @WebSocketGateway() @@ -952,7 +953,7 @@ export class AgentServiceService { } } - async createLegacyConnectionInvitation(connectionPayload: IConnectionDetails, url: string, apiKey: string): Promise { + async createLegacyConnectionInvitation(connectionPayload: IConnectionDetails, url: string, apiKey: string): Promise { try { diff --git a/apps/api-gateway/src/connection/connection.controller.ts b/apps/api-gateway/src/connection/connection.controller.ts index 686b97075..b37af06d4 100644 --- a/apps/api-gateway/src/connection/connection.controller.ts +++ b/apps/api-gateway/src/connection/connection.controller.ts @@ -15,7 +15,7 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler import { OrgRoles } from 'libs/org-roles/enums'; import { Roles } from '../authz/decorators/roles.decorator'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; -import { GetAllConnectionsDto } from './dtos/get-all-connections.dto'; +import { GetAllAgentConnectionsDto, GetAllConnectionsDto } from './dtos/get-all-connections.dto'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface'; import { SortFields } from 'apps/connection/src/enum/connection.enum'; @@ -108,7 +108,42 @@ export class ConnectionController { return res.status(HttpStatus.OK).json(finalResponse); } - + /** + * Description: Get all connections from agent + * @param user + * @param orgId + * + */ + @Get('/orgs/:orgId/agent/connections') + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @ApiOperation({ + summary: `Fetch all connections from agent by orgId`, + description: `Fetch all connections from agent by orgId` + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + async getConnectionListFromAgent( + @Query() getAllConnectionsDto: GetAllAgentConnectionsDto, + @User() user: IUserRequest, + @Param('orgId') orgId: string, + @Res() res: Response + ): Promise { + + const connectionDetails = await this.connectionService.getConnectionListFromAgent( + getAllConnectionsDto, + user, + orgId + ); + + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.connection.success.fetch, + data: connectionDetails + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + @Get('orgs/:orgId/question-answer/question') @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER, OrgRoles.SUPER_ADMIN, OrgRoles.PLATFORM_ADMIN) diff --git a/apps/api-gateway/src/connection/connection.service.ts b/apps/api-gateway/src/connection/connection.service.ts index c01321093..c01e971f3 100644 --- a/apps/api-gateway/src/connection/connection.service.ts +++ b/apps/api-gateway/src/connection/connection.service.ts @@ -5,7 +5,7 @@ import { BaseService } from 'libs/service/base.service'; import { ConnectionDto, CreateConnectionDto, ReceiveInvitationDto, ReceiveInvitationUrlDto } from './dtos/connection.dto'; import { IReceiveInvitationRes, IUserRequestInterface } from './interfaces'; import { IConnectionList, ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface'; -import { IConnectionDetailsById, IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface'; +import { AgentConnectionSearchCriteria, IConnectionDetailsById, IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface'; import { QuestionDto } from './dtos/question-answer.dto'; @Injectable() @@ -77,6 +77,15 @@ export class ConnectionService extends BaseService { return this.sendNatsMessage(this.connectionServiceProxy, 'get-all-connections', payload); } + getConnectionListFromAgent( + connectionSearchCriteria: AgentConnectionSearchCriteria, + user: IUserRequest, + orgId: string + ): Promise { + const payload = { connectionSearchCriteria, user, orgId }; + return this.sendNatsMessage(this.connectionServiceProxy, 'get-all-agent-connection-list', payload); + } + getConnectionsById( user: IUserRequest, connectionId: string, diff --git a/apps/api-gateway/src/connection/dtos/get-all-connections.dto.ts b/apps/api-gateway/src/connection/dtos/get-all-connections.dto.ts index 553a8a5a2..d0b4bcca9 100644 --- a/apps/api-gateway/src/connection/dtos/get-all-connections.dto.ts +++ b/apps/api-gateway/src/connection/dtos/get-all-connections.dto.ts @@ -1,41 +1,61 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { Transform, Type } from "class-transformer"; -import { IsEnum, IsOptional } from "class-validator"; -import { SortValue } from "../../enum"; -import { trim } from "@credebl/common/cast.helper"; -import { SortFields } from "apps/connection/src/enum/connection.enum"; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform, Type } from 'class-transformer'; +import { IsEnum, IsOptional } from 'class-validator'; +import { SortValue } from '../../enum'; +import { trim } from '@credebl/common/cast.helper'; +import { SortFields } from 'apps/connection/src/enum/connection.enum'; export class GetAllConnectionsDto { - - @ApiProperty({ required: false, example: '1' }) - @IsOptional() - pageNumber: number = 1; - - @ApiProperty({ required: false, example: '10' }) - @IsOptional() - pageSize: number = 10; - - @ApiProperty({ required: false }) - @IsOptional() - @Transform(({ value }) => trim(value)) - @Type(() => String) - searchByText: string = ''; - - @ApiProperty({ - required: false - }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsEnum(SortFields) - sortField: string = SortFields.CREATED_DATE_TIME; - - @ApiProperty({ - enum: [SortValue.DESC, SortValue.ASC], - required: false - }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsEnum(SortValue) - sortBy: string = SortValue.DESC; + @ApiProperty({ required: false, example: '1' }) + @IsOptional() + pageNumber: number = 1; + + @ApiProperty({ required: false, example: '10' }) + @IsOptional() + pageSize: number = 10; + + @ApiProperty({ required: false }) + @IsOptional() + @Transform(({ value }) => trim(value)) + @Type(() => String) + searchByText: string = ''; + + @ApiProperty({ + required: false + }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsEnum(SortFields) + sortField: string = SortFields.CREATED_DATE_TIME; + + @ApiProperty({ + enum: [SortValue.DESC, SortValue.ASC], + required: false + }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsEnum(SortValue) + sortBy: string = SortValue.DESC; } +export class GetAllAgentConnectionsDto { + @ApiProperty({ required: false, example: 'e315f30d-9beb-4068-aea4-abb5fe5eecb1' }) + @IsOptional() + outOfBandId: string = ''; + + @ApiProperty({ required: false, example: 'Test' }) + @IsOptional() + alias: string = ''; + + @ApiProperty({ required: false, example: 'did:example:123456789abcdefghi' }) + @IsOptional() + myDid: string = ''; + + @ApiProperty({ required: false, example: 'did:example:123456789abcdefghi' }) + @IsOptional() + theirDid: string = ''; + + @ApiProperty({ required: false, example: 'Bob' }) + @IsOptional() + theirLabel: string = ''; +} diff --git a/apps/api-gateway/src/interfaces/IConnectionSearch.interface.ts b/apps/api-gateway/src/interfaces/IConnectionSearch.interface.ts index 35d79949d..a228ae636 100644 --- a/apps/api-gateway/src/interfaces/IConnectionSearch.interface.ts +++ b/apps/api-gateway/src/interfaces/IConnectionSearch.interface.ts @@ -1,25 +1,34 @@ import { IUserRequestInterface } from './IUserRequestInterface'; export interface IConnectionSearchCriteria { - pageNumber: number; - pageSize: number; - sortField: string; - sortBy: string; - searchByText: string; - user?: IUserRequestInterface + pageNumber: number; + pageSize: number; + sortField: string; + sortBy: string; + searchByText: string; + user?: IUserRequestInterface; } export interface IConnectionDetailsById { - id: string; - createdAt: string; - did: string; - theirDid: string; - theirLabel: string; - state: string; - role: string; - autoAcceptConnection: boolean; - threadId: string; - protocol: string; - outOfBandId: string; - updatedAt: string; - } + id: string; + createdAt: string; + did: string; + theirDid: string; + theirLabel: string; + state: string; + role: string; + autoAcceptConnection: boolean; + threadId: string; + protocol: string; + outOfBandId: string; + updatedAt: string; +} + +export interface AgentConnectionSearchCriteria { + outOfBandId?: string; + alias?: string; + state?: string; + myDid?: string; + theirDid?: string; + theirLabel?: string; +} diff --git a/apps/connection/src/connection.controller.ts b/apps/connection/src/connection.controller.ts index 923a15190..6bbde21e9 100644 --- a/apps/connection/src/connection.controller.ts +++ b/apps/connection/src/connection.controller.ts @@ -2,6 +2,7 @@ import { Controller } from '@nestjs/common'; // Import the common service in the import { ConnectionService } from './connection.service'; // Import the common service in connection module import { MessagePattern } from '@nestjs/microservices'; // Import the nestjs microservices package import { + GetAllConnections, IConnection, ICreateConnection, IFetchConnectionById, @@ -54,6 +55,14 @@ export class ConnectionController { return this.connectionService.getConnections(user, orgId, connectionSearchCriteria); } + @MessagePattern({ cmd: 'get-all-agent-connection-list' }) + async getConnectionListFromAgent(payload: GetAllConnections): Promise<{ + response: string; + }> { + const { user, orgId, connectionSearchCriteria } = payload; + return this.connectionService.getAllConnectionListFromAgent(user, orgId, connectionSearchCriteria); + } + /** * * @param connectionId diff --git a/apps/connection/src/connection.service.ts b/apps/connection/src/connection.service.ts index 7dbb306f7..c6d244574 100644 --- a/apps/connection/src/connection.service.ts +++ b/apps/connection/src/connection.service.ts @@ -5,8 +5,9 @@ import { HttpException, Inject, Injectable, Logger, NotFoundException } from '@n import { ClientProxy, RpcException } from '@nestjs/microservices'; import { map } from 'rxjs'; import { + ConnectionResponseDetail, + AgentConnectionSearchCriteria, IConnection, - IConnectionInvitation, IConnectionSearchCriteria, ICreateConnection, IReceiveInvitation, @@ -22,6 +23,7 @@ import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { IConnectionList, ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface'; import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; import { IQuestionPayload } from './interfaces/question-answer.interfaces'; +import { InvitationMessage } from '@credebl/common/interfaces/agent-service.interface'; @Injectable() export class ConnectionService { @@ -31,7 +33,7 @@ export class ConnectionService { private readonly connectionRepository: ConnectionRepository, private readonly logger: Logger, @Inject(CACHE_MANAGER) private cacheService: Cache - ) { } + ) {} /** * Create connection legacy invitation URL @@ -40,10 +42,18 @@ export class ConnectionService { * @returns Connection legacy invitation URL */ async createLegacyConnectionInvitation(payload: IConnection): Promise { - - const { orgId, multiUseInvitation, autoAcceptConnection, alias, imageUrl, goal, goalCode, handshake, handshakeProtocols } = payload; + const { + orgId, + multiUseInvitation, + autoAcceptConnection, + alias, + imageUrl, + goal, + goalCode, + handshake, + handshakeProtocols + } = payload; try { - const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); const { agentEndPoint, id, organisation } = agentDetails; @@ -86,7 +96,20 @@ export class ConnectionService { agentId, orgId ); - return saveConnectionDetails; + + const connectionDetailRecords: ConnectionResponseDetail = { + id: saveConnectionDetails.id, + orgId: saveConnectionDetails.orgId, + agentId: saveConnectionDetails.agentId, + connectionInvitation: saveConnectionDetails.connectionInvitation, + multiUse: saveConnectionDetails.multiUse, + createDateTime: saveConnectionDetails.createDateTime, + createdBy: saveConnectionDetails.createdBy, + lastChangedDateTime: saveConnectionDetails.lastChangedDateTime, + lastChangedBy: saveConnectionDetails.lastChangedBy, + recordId: createConnectionInvitation.message.outOfBandRecord.id + }; + return connectionDetailRecords; } catch (error) { this.logger.error(`[createLegacyConnectionInvitation] - error in connection invitation: ${error}`); if (error && error?.status && error?.status?.message && error?.status?.message?.error) { @@ -126,8 +149,7 @@ export class ConnectionService { connectionPayload: object, url: string, apiKey: string - ): Promise { - + ): Promise { //nats call in agent-service to create an invitation url const pattern = { cmd: 'agent-create-connection-legacy-invitation' }; const payload = { connectionPayload, url, apiKey }; @@ -222,10 +244,49 @@ export class ConnectionService { }; return connectionResponse; } catch (error) { + this.logger.error(`[getConnections] [NATS call]- error in fetch connections details : ${JSON.stringify(error)}`); - this.logger.error( - `[getConnections] [NATS call]- error in fetch connections details : ${JSON.stringify(error)}` - ); + throw new RpcException(error.response ? error.response : error); + } + } + + async getAllConnectionListFromAgent( + user: IUserRequest, + orgId: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + connectionSearchCriteria: AgentConnectionSearchCriteria + ): Promise<{ + response: string; + }> { + try { + // const { alias, myDid, outOfBandId, state, theirDid, theirLabel } = connectionSearchCriteria; + const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); + const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + const { agentEndPoint } = agentDetails; + if (!agentDetails) { + throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); + } + //Need to think about the dynamic URL creation for { alias, myDid, outOfBandId, state, theirDid, theirLabel } + let url; + if (orgAgentType === OrgAgentType.DEDICATED) { + url = `${agentEndPoint}${CommonConstants.URL_CONN_GET_CONNECTIONS}`; + } else if (orgAgentType === OrgAgentType.SHARED) { + url = + `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CREATEED_INVITATIONS}`.replace( + '#', + agentDetails.tenantId + ); + } else { + throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); + } + + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } + return this._getAllConnections(url, apiKey); + } catch (error) { + this.logger.error(`[getConnectionsFromAgent] [NATS call]- error in fetch connections details : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } @@ -288,7 +349,6 @@ export class ConnectionService { throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); } - // const apiKey = await this._getOrgAgentApiKey(orgId); let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); if (!apiKey || null === apiKey || undefined === apiKey) { @@ -296,8 +356,6 @@ export class ConnectionService { } const createConnectionInvitation = await this._getConnectionsByConnectionId(url, apiKey); return createConnectionInvitation; - - } catch (error) { this.logger.error(`[getConnectionsById] - error in get connections : ${JSON.stringify(error)}`); @@ -324,15 +382,13 @@ export class ConnectionService { const label = 'get-question-answer-record'; const url = await this.getQuestionAnswerAgentUrl(label, orgAgentType, agentEndPoint, agentDetails?.tenantId); - + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); if (!apiKey || null === apiKey || undefined === apiKey) { apiKey = await this._getOrgAgentApiKey(orgId); } const record = await this._getQuestionAnswersRecord(url, apiKey); return record; - - } catch (error) { this.logger.error(`[sendQuestion] - error in get question answer record: ${error}`); if (error && error?.status && error?.status?.message && error?.status?.message?.error) { @@ -348,18 +404,14 @@ export class ConnectionService { } } - async _getConnectionsByConnectionId( - url: string, - apiKey: string - ): Promise { - + async _getConnectionsByConnectionId(url: string, apiKey: string): Promise { //nats call in agent service for fetch connection details const pattern = { cmd: 'agent-get-connection-details-by-connectionId' }; const payload = { url, apiKey }; return this.connectionServiceProxy .send(pattern, payload) .toPromise() - .catch(error => { + .catch((error) => { this.logger.error( `[_getConnectionsByConnectionId] [NATS call]- error in fetch connections : ${JSON.stringify(error)}` ); @@ -368,21 +420,19 @@ export class ConnectionService { status: error.statusCode, error: error.error?.message?.error ? error.error?.message?.error : error.error, message: error.message - }, error.error); + }, + error.error + ); }); } - async _getQuestionAnswersRecord( - url: string, - apiKey: string - ): Promise { - + async _getQuestionAnswersRecord(url: string, apiKey: string): Promise { const pattern = { cmd: 'agent-get-question-answer-record' }; const payload = { url, apiKey }; return this.connectionServiceProxy .send(pattern, payload) .toPromise() - .catch(error => { + .catch((error) => { this.logger.error( `[_getQuestionAnswersRecord] [NATS call]- error in fetch connections : ${JSON.stringify(error)}` ); @@ -391,7 +441,9 @@ export class ConnectionService { status: error.statusCode, error: error.error?.message?.error ? error.error?.message?.error : error.error, message: error.message - }, error.error); + }, + error.error + ); }); } @@ -425,20 +477,23 @@ export class ConnectionService { connectionId?: string ): Promise { try { - let url; switch (label) { case 'send-question': { - url = orgAgentType === OrgAgentType.DEDICATED + url = + orgAgentType === OrgAgentType.DEDICATED ? `${agentEndPoint}${CommonConstants.URL_SEND_QUESTION}`.replace('#', connectionId) : orgAgentType === OrgAgentType.SHARED - ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_SEND_QUESTION}`.replace('#', connectionId).replace('@', tenantId) + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_SEND_QUESTION}` + .replace('#', connectionId) + .replace('@', tenantId) : null; break; } case 'get-question-answer-record': { - url = orgAgentType === OrgAgentType.DEDICATED + url = + orgAgentType === OrgAgentType.DEDICATED ? `${agentEndPoint}${CommonConstants.URL_QUESTION_ANSWER_RECORD}` : orgAgentType === OrgAgentType.SHARED ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_QUESTION_ANSWER_RECORD}`.replace('#', tenantId) @@ -472,14 +527,21 @@ export class ConnectionService { return message; } catch (error) { this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException({ - status: error.status, - error: error.message - }, error.status); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); } } - async receiveInvitationUrl(user: IUserRequest, receiveInvitationUrl: IReceiveInvitationUrl, orgId: string): Promise { + async receiveInvitationUrl( + user: IUserRequest, + receiveInvitationUrl: IReceiveInvitationUrl, + orgId: string + ): Promise { try { const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); @@ -489,13 +551,14 @@ export class ConnectionService { throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); } - let url; if (orgAgentType === OrgAgentType.DEDICATED) { url = `${agentEndPoint}${CommonConstants.URL_RECEIVE_INVITATION_URL}`; } else if (orgAgentType === OrgAgentType.SHARED) { - url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_RECEIVE_INVITATION_URL}` - .replace('#', agentDetails.tenantId); + url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_RECEIVE_INVITATION_URL}`.replace( + '#', + agentDetails.tenantId + ); } else { throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); } @@ -506,8 +569,6 @@ export class ConnectionService { } const createConnectionInvitation = await this._receiveInvitationUrl(url, apiKey, receiveInvitationUrl); return createConnectionInvitation; - - } catch (error) { this.logger.error(`[receiveInvitationUrl] - error in receive invitation url : ${JSON.stringify(error)}`); @@ -528,13 +589,12 @@ export class ConnectionService { apiKey: string, receiveInvitationUrl: IReceiveInvitationUrl ): Promise { - const pattern = { cmd: 'agent-receive-invitation-url' }; const payload = { url, apiKey, receiveInvitationUrl }; return this.connectionServiceProxy .send(pattern, payload) .toPromise() - .catch(error => { + .catch((error) => { this.logger.error( `[_receiveInvitationUrl] [NATS call]- error in receive invitation url : ${JSON.stringify(error)}` ); @@ -543,11 +603,17 @@ export class ConnectionService { status: error.statusCode, error: error.error?.message?.error ? error.error?.message?.error : error.error, message: error.message - }, error.error); + }, + error.error + ); }); } - async receiveInvitation(user: IUserRequest, receiveInvitation: IReceiveInvitation, orgId: string): Promise { + async receiveInvitation( + user: IUserRequest, + receiveInvitation: IReceiveInvitation, + orgId: string + ): Promise { try { const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); @@ -561,8 +627,7 @@ export class ConnectionService { if (orgAgentType === OrgAgentType.DEDICATED) { url = `${agentEndPoint}${CommonConstants.URL_RECEIVE_INVITATION}`; } else if (orgAgentType === OrgAgentType.SHARED) { - url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_RECEIVE_INVITATION}` - .replace('#', agentDetails.tenantId); + url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_RECEIVE_INVITATION}`.replace('#', agentDetails.tenantId); } else { throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); } @@ -573,8 +638,6 @@ export class ConnectionService { } const createConnectionInvitation = await this._receiveInvitation(url, apiKey, receiveInvitation); return createConnectionInvitation; - - } catch (error) { this.logger.error(`[receiveInvitation] - error in receive invitation : ${JSON.stringify(error)}`); @@ -595,61 +658,51 @@ export class ConnectionService { apiKey: string, receiveInvitation: IReceiveInvitation ): Promise { - const pattern = { cmd: 'agent-receive-invitation' }; const payload = { url, apiKey, receiveInvitation }; return this.connectionServiceProxy .send(pattern, payload) .toPromise() - .catch(error => { - this.logger.error( - `[_receiveInvitation] [NATS call]- error in receive invitation : ${JSON.stringify(error)}` - ); + .catch((error) => { + this.logger.error(`[_receiveInvitation] [NATS call]- error in receive invitation : ${JSON.stringify(error)}`); throw new HttpException( { status: error.statusCode, error: error.error?.message?.error ? error.error?.message?.error : error.error, message: error.message - }, error.error); + }, + error.error + ); }); } - async _sendQuestion( - questionPayload: IQuestionPayload, - url: string, - apiKey: string - ): Promise { - + async _sendQuestion(questionPayload: IQuestionPayload, url: string, apiKey: string): Promise { const pattern = { cmd: 'agent-send-question' }; const payload = { questionPayload, url, apiKey }; return this.connectionServiceProxy .send(pattern, payload) .toPromise() - .catch(error => { - this.logger.error( - `[_sendQuestion] [NATS call]- error in send question : ${JSON.stringify(error)}` - ); + .catch((error) => { + this.logger.error(`[_sendQuestion] [NATS call]- error in send question : ${JSON.stringify(error)}`); throw new HttpException( { status: error.statusCode, error: error.error?.message?.error ? error.error?.message?.error : error.error, message: error.message - }, error.error); + }, + error.error + ); }); - - } async sendQuestion(payload: IQuestionPayload): Promise { - - const { detail, validResponses, question, orgId, connectionId} = payload; + const { detail, validResponses, question, orgId, connectionId } = payload; try { - const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); - const { agentEndPoint} = agentDetails; - + const { agentEndPoint } = agentDetails; + if (!agentDetails) { throw new NotFoundException(ResponseMessages.connection.error.agentEndPointNotFound); } @@ -662,14 +715,19 @@ export class ConnectionService { const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); const label = 'send-question'; - const url = await this.getQuestionAnswerAgentUrl(label, orgAgentType, agentEndPoint, agentDetails?.tenantId, connectionId); + const url = await this.getQuestionAnswerAgentUrl( + label, + orgAgentType, + agentEndPoint, + agentDetails?.tenantId, + connectionId + ); let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); if (!apiKey || null === apiKey || undefined === apiKey) { apiKey = await this._getOrgAgentApiKey(orgId); } const createQuestion = await this._sendQuestion(questionPayload, url, apiKey); return createQuestion; - } catch (error) { this.logger.error(`[sendQuestion] - error in sending question: ${error}`); if (error && error?.status && error?.status?.message && error?.status?.message?.error) { @@ -685,4 +743,3 @@ export class ConnectionService { } } } - diff --git a/apps/connection/src/interfaces/connection.interfaces.ts b/apps/connection/src/interfaces/connection.interfaces.ts index f26a6fd2f..d7cc5da99 100644 --- a/apps/connection/src/interfaces/connection.interfaces.ts +++ b/apps/connection/src/interfaces/connection.interfaces.ts @@ -80,6 +80,12 @@ export interface IFetchConnections { orgId: string; } +export interface GetAllConnections { + connectionSearchCriteria: AgentConnectionSearchCriteria; + user: IUserRequest; + orgId: string; +} + export interface IFetchConnectionById { user: IUserRequest; connectionId: string; @@ -124,6 +130,15 @@ export interface IConnectionSearchCriteria { user: IUserRequestInterface } +export interface AgentConnectionSearchCriteria { + outOfBandId: string; + alias: string; + state: string; + myDid: string; + theirDid: string; + theirLabel: string; +} + export interface IReceiveInvitationByUrlOrg { user: IUserRequestInterface, receiveInvitationUrl: IReceiveInvitationUrl, @@ -236,3 +251,16 @@ export interface IReceiveInvitationResponse { outOfBandRecord: OutOfBandRecord; connectionRecord: ConnectionRecord; } + +export interface ConnectionResponseDetail { + id: string; + orgId: string; + agentId: string; + connectionInvitation: string; + multiUse: boolean; + createDateTime: Date; + createdBy: number; + lastChangedDateTime: Date; + lastChangedBy: number; + recordId: string; +} diff --git a/libs/common/src/interfaces/agent-service.interface.ts b/libs/common/src/interfaces/agent-service.interface.ts new file mode 100644 index 000000000..ea63de484 --- /dev/null +++ b/libs/common/src/interfaces/agent-service.interface.ts @@ -0,0 +1,53 @@ +export interface InvitationMessage { + message: { + invitationUrl: string; + invitation: { + '@type': string; + '@id': string; + label: string; + recipientKeys: string[]; + serviceEndpoint: string; + routingKeys: string[]; + }; + outOfBandRecord: OutOfBandRecord; + }; + } + + interface OutOfBandRecord { + _tags: Tags; + metadata?: { [key: string]: string }; + id: string; + createdAt: string; + outOfBandInvitation: OutOfBandInvitation; + role: string; + state: string; + autoAcceptConnection: boolean; + reusable: boolean; + updatedAt: string; + } + + interface Tags { + invitationId: string; + recipientKeyFingerprints: string[]; + role: string; + state: string; + threadId: string; + } + + interface OutOfBandInvitation { + '@type': string; + '@id': string; + label: string; + accept: string[]; + handshake_protocols: string[]; + services: OutOfBandInvitationService[]; + } + + interface OutOfBandInvitationService { + id: string; + serviceEndpoint: string; + type: string; + recipientKeys: string[]; + routingKeys: string[]; + } + \ No newline at end of file From 3fb1cdcd2135315ec15263afd2cb78de31b2d082 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Thu, 7 Mar 2024 12:09:40 +0530 Subject: [PATCH 2/2] feat: agent connection list API Signed-off-by: tipusinghaw --- .../src/connection/connection.controller.ts | 2 - .../src/connection/connection.service.ts | 3 +- .../dtos/get-all-connections.dto.ts | 4 +- apps/connection/src/connection.controller.ts | 8 ++- apps/connection/src/connection.service.ts | 50 +++++++++++-------- 5 files changed, 36 insertions(+), 31 deletions(-) diff --git a/apps/api-gateway/src/connection/connection.controller.ts b/apps/api-gateway/src/connection/connection.controller.ts index b37af06d4..9fc45bfb7 100644 --- a/apps/api-gateway/src/connection/connection.controller.ts +++ b/apps/api-gateway/src/connection/connection.controller.ts @@ -124,14 +124,12 @@ export class ConnectionController { @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async getConnectionListFromAgent( @Query() getAllConnectionsDto: GetAllAgentConnectionsDto, - @User() user: IUserRequest, @Param('orgId') orgId: string, @Res() res: Response ): Promise { const connectionDetails = await this.connectionService.getConnectionListFromAgent( getAllConnectionsDto, - user, orgId ); diff --git a/apps/api-gateway/src/connection/connection.service.ts b/apps/api-gateway/src/connection/connection.service.ts index c01e971f3..5b2998dc1 100644 --- a/apps/api-gateway/src/connection/connection.service.ts +++ b/apps/api-gateway/src/connection/connection.service.ts @@ -79,10 +79,9 @@ export class ConnectionService extends BaseService { getConnectionListFromAgent( connectionSearchCriteria: AgentConnectionSearchCriteria, - user: IUserRequest, orgId: string ): Promise { - const payload = { connectionSearchCriteria, user, orgId }; + const payload = { connectionSearchCriteria, orgId }; return this.sendNatsMessage(this.connectionServiceProxy, 'get-all-agent-connection-list', payload); } diff --git a/apps/api-gateway/src/connection/dtos/get-all-connections.dto.ts b/apps/api-gateway/src/connection/dtos/get-all-connections.dto.ts index d0b4bcca9..ac5ddb329 100644 --- a/apps/api-gateway/src/connection/dtos/get-all-connections.dto.ts +++ b/apps/api-gateway/src/connection/dtos/get-all-connections.dto.ts @@ -47,11 +47,11 @@ export class GetAllAgentConnectionsDto { @IsOptional() alias: string = ''; - @ApiProperty({ required: false, example: 'did:example:123456789abcdefghi' }) + @ApiProperty({ required: false, example: 'did:example:e315f30d-9beb-4068-aea4-abb5fe5eecb1' }) @IsOptional() myDid: string = ''; - @ApiProperty({ required: false, example: 'did:example:123456789abcdefghi' }) + @ApiProperty({ required: false, example: 'did:example:e315f30d-9beb-4068-aea4-abb5fe5eecb1' }) @IsOptional() theirDid: string = ''; diff --git a/apps/connection/src/connection.controller.ts b/apps/connection/src/connection.controller.ts index 6bbde21e9..ca67a4149 100644 --- a/apps/connection/src/connection.controller.ts +++ b/apps/connection/src/connection.controller.ts @@ -56,11 +56,9 @@ export class ConnectionController { } @MessagePattern({ cmd: 'get-all-agent-connection-list' }) - async getConnectionListFromAgent(payload: GetAllConnections): Promise<{ - response: string; - }> { - const { user, orgId, connectionSearchCriteria } = payload; - return this.connectionService.getAllConnectionListFromAgent(user, orgId, connectionSearchCriteria); + async getConnectionListFromAgent(payload: GetAllConnections): Promise { + const {orgId, connectionSearchCriteria } = payload; + return this.connectionService.getAllConnectionListFromAgent(orgId, connectionSearchCriteria); } /** diff --git a/apps/connection/src/connection.service.ts b/apps/connection/src/connection.service.ts index c6d244574..b7f1bc084 100644 --- a/apps/connection/src/connection.service.ts +++ b/apps/connection/src/connection.service.ts @@ -251,40 +251,50 @@ export class ConnectionService { } async getAllConnectionListFromAgent( - user: IUserRequest, orgId: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars connectionSearchCriteria: AgentConnectionSearchCriteria - ): Promise<{ - response: string; - }> { + ): Promise { try { - // const { alias, myDid, outOfBandId, state, theirDid, theirLabel } = connectionSearchCriteria; + const { alias, myDid, outOfBandId, state, theirDid, theirLabel } = connectionSearchCriteria; const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); const { agentEndPoint } = agentDetails; if (!agentDetails) { throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); } - //Need to think about the dynamic URL creation for { alias, myDid, outOfBandId, state, theirDid, theirLabel } - let url; - if (orgAgentType === OrgAgentType.DEDICATED) { - url = `${agentEndPoint}${CommonConstants.URL_CONN_GET_CONNECTIONS}`; - } else if (orgAgentType === OrgAgentType.SHARED) { - url = - `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CREATEED_INVITATIONS}`.replace( - '#', - agentDetails.tenantId - ); - } else { - throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); - } + + let url: string; + if (orgAgentType === OrgAgentType.DEDICATED) { + url = `${agentEndPoint}${CommonConstants.URL_CONN_GET_CONNECTIONS}`; + } else if (orgAgentType === OrgAgentType.SHARED) { + url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CREATEED_INVITATIONS}`.replace( + '#', + agentDetails.tenantId + ); + } else { + throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); + } + + //Create the dynamic URL for Search Criteria + const criteriaParams = []; + if (alias) { criteriaParams.push(`alias=${alias}`); } + if (myDid) { criteriaParams.push(`myDid=${myDid}`); } + if (outOfBandId) { criteriaParams.push(`outOfBandId=${outOfBandId}`); } + if (state) { criteriaParams.push(`state=${state}`); } + if (theirDid) { criteriaParams.push(`theirDid=${theirDid}`); } + if (theirLabel) { criteriaParams.push(`theirLabel=${theirLabel}`); } + + if (0 < criteriaParams.length) { + url += `?${criteriaParams.join('&')}`; + } let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); if (!apiKey || null === apiKey || undefined === apiKey) { apiKey = await this._getOrgAgentApiKey(orgId); } - return this._getAllConnections(url, apiKey); + + const connectionResponse = await this._getAllConnections(url, apiKey); + return connectionResponse.response; } catch (error) { this.logger.error(`[getConnectionsFromAgent] [NATS call]- error in fetch connections details : ${JSON.stringify(error)}`);