Skip to content

Commit

Permalink
feat: add question answer protocol (#539)
Browse files Browse the repository at this point in the history
* feat:added question answer protocol

Signed-off-by: pallavicoder <[email protected]>

* fear:added question answer webhook

Signed-off-by: pallavicoder <[email protected]>

* feat:add question answer protocol inside platform

Signed-off-by: pallavicoder <[email protected]>

* fix:removed extra cost variables

Signed-off-by: pallavicoder <[email protected]>

---------

Signed-off-by: pallavicoder <[email protected]>
Signed-off-by: bhavanakarwade <[email protected]>
  • Loading branch information
pallavighule authored and bhavanakarwade committed Feb 27, 2024
1 parent 6f6c4da commit 2d3c593
Show file tree
Hide file tree
Showing 12 changed files with 468 additions and 5 deletions.
Empty file modified apps/agent-provisioning/AFJ/scripts/start_agent.sh
100644 → 100755
Empty file.
16 changes: 16 additions & 0 deletions apps/agent-service/src/agent-service.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,20 @@ export class AgentServiceController {
}): Promise<string> {
return this.agentServiceService.receiveInvitation(payload.receiveInvitation, payload.url, payload.apiKey);
}

@MessagePattern({ cmd: 'agent-send-question' })
async sendQuestion(payload: {
url,
apiKey,
questionPayload
}): Promise<object> {

return this.agentServiceService.sendQuestion(payload.questionPayload, payload.url, payload.apiKey);
}

@MessagePattern({ cmd: 'agent-get-question-answer-record' })
async getQuestionAnswersRecord(payload: { url: string, apiKey: string }): Promise<object> {
return this.agentServiceService.getQuestionAnswersRecord(payload.url, payload.apiKey);
}

}
31 changes: 30 additions & 1 deletion apps/agent-service/src/agent-service.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import * as dotenv from 'dotenv';
import * as fs from 'fs';
import { map } from 'rxjs/operators';
dotenv.config();
import { IGetCredDefAgentRedirection, IConnectionDetails, IUserRequestInterface, IAgentSpinupDto, IStoreOrgAgentDetails, ITenantCredDef, ITenantDto, ITenantSchema, IWalletProvision, ISendProofRequestPayload, IIssuanceCreateOffer, IOutOfBandCredentialOffer, IAgentSpinUpSatus, ICreateTenant, IAgentStatus, ICreateOrgAgent, IOrgAgentsResponse, IProofPresentation, IAgentProofRequest, IPresentation, IReceiveInvitationUrl, IReceiveInvitation } from './interface/agent-service.interface';
import { IGetCredDefAgentRedirection, IConnectionDetails, IUserRequestInterface, IAgentSpinupDto, IStoreOrgAgentDetails, ITenantCredDef, ITenantDto, ITenantSchema, IWalletProvision, ISendProofRequestPayload, IIssuanceCreateOffer, IOutOfBandCredentialOffer, IAgentSpinUpSatus, ICreateTenant, IAgentStatus, ICreateOrgAgent, IOrgAgentsResponse, IProofPresentation, IAgentProofRequest, IPresentation, IReceiveInvitationUrl, IReceiveInvitation, IQuestionPayload } from './interface/agent-service.interface';
import { AgentSpinUpStatus, AgentType, Ledgers, OrgAgentType } from '@credebl/enum/enum';
import { AgentServiceRepository } from './repositories/agent-service.repository';
import { ledgers, org_agents, organisation, platform_config } from '@prisma/client';
Expand Down Expand Up @@ -1305,5 +1305,34 @@ export class AgentServiceService {
throw error;
}
}

async sendQuestion(questionPayload: IQuestionPayload, url: string, apiKey: string): Promise<object> {
try {
const sendQuestionRes = await this.commonService
.httpPost(url, questionPayload, { headers: { 'authorization': apiKey } })
.then(async response => response);
return sendQuestionRes;
} catch (error) {
this.logger.error(`Error in send question in agent service : ${JSON.stringify(error)}`);
throw error;
}
}

async getQuestionAnswersRecord(url: string, apiKey: string): Promise<object> {

try {
const data = await this.commonService
.httpGet(url, { headers: { 'authorization': apiKey } })
.then(async response => response)
.catch(error => this.handleAgentSpinupStatusErrors(error));

return data;
} catch (error) {
this.logger.error(`Error in getQuestionAnswersRecord in agent service : ${JSON.stringify(error)}`);
throw new RpcException(error.response ? error.response : error);
}

}

}

11 changes: 11 additions & 0 deletions apps/agent-service/src/interface/agent-service.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,3 +416,14 @@ interface ITags {
threadId: string;
}

export interface IValidResponses {
text: string;
}
export interface IQuestionPayload {
detail: string;
validResponses: IValidResponses[];
question: string;
orgId?: string;
connectionId: string;
tenantId: string;
}
84 changes: 84 additions & 0 deletions apps/api-gateway/src/connection/connection.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +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';

@UseFilters(CustomExceptionFilter)
@Controller()
Expand Down Expand Up @@ -107,6 +108,29 @@ export class ConnectionController {
return res.status(HttpStatus.OK).json(finalResponse);
}


@Get('orgs/:orgId/question-answer/question/:tenantId')
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
@Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER)
@ApiOperation({
summary: `Get question-answer record`,
description: `Get question-answer record`
})
@ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto })
async getQuestionAnswersRecord(
@Param('tenantId') tenantId: string,
@Param('orgId') orgId: string,
@Res() res: Response
): Promise<Response> {
const record = await this.connectionService.getQuestionAnswersRecord(tenantId, orgId);
const finalResponse: IResponse = {
statusCode: HttpStatus.OK,
message: ResponseMessages.connection.success.questionAnswerRecord,
data: record
};
return res.status(HttpStatus.OK).json(finalResponse);
}

/**
* Create out-of-band connection legacy invitation
* @param connectionDto
Expand Down Expand Up @@ -136,6 +160,33 @@ export class ConnectionController {

}

@Post('/orgs/:orgId/question-answer/question/:connectionId/:tenantId')
@ApiOperation({ summary: '', description: 'question-answer/question' })
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
@Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER)
@ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto })
async sendQuestion(
@Param('orgId') orgId: string,
@Param('connectionId') connectionId: string,
@Param('tenantId') tenantId: string,
@Body() questionDto: QuestionDto,
@User() reqUser: IUserRequestInterface,
@Res() res: Response
): Promise<Response> {

questionDto.orgId = orgId;
questionDto.connectionId = connectionId;
questionDto.tenantId = tenantId;
const questionData = await this.connectionService.sendQuestion(questionDto);
const finalResponse: IResponse = {
statusCode: HttpStatus.CREATED,
message: ResponseMessages.connection.success.questionSend,
data: questionData
};
return res.status(HttpStatus.CREATED).json(finalResponse);

}

@Post('/orgs/:orgId/receive-invitation-url')
@ApiOperation({ summary: 'Receive Invitation URL', description: 'Receive Invitation URL' })
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
Expand Down Expand Up @@ -218,4 +269,37 @@ export class ConnectionController {
}
return res.status(HttpStatus.CREATED).json(finalResponse);
}


@Post('wh/:orgId/question-answer/')
@ApiExcludeEndpoint()
@ApiOperation({
summary: 'Catch question-answer webhook responses',
description: 'Callback URL for question-answer'
})
@ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto })
async getQuestionAnswerWebhook(
@Body() questionAnswerWebhookDto:QuestionAnswerWebhookDto,
@Param('orgId') orgId: string,
@Res() res: Response
): Promise<Response> {
questionAnswerWebhookDto.type = 'question-answer';
this.logger.debug(`questionAnswer ::: ${JSON.stringify(questionAnswerWebhookDto)} ${orgId}`);

const finalResponse: IResponse = {
statusCode: HttpStatus.CREATED,
message: ResponseMessages.connection.success.create,
data: ''
};
const webhookUrl = await this.connectionService._getWebhookUrl(questionAnswerWebhookDto.contextCorrelationId).catch(error => {
this.logger.debug(`error in getting webhook url ::: ${JSON.stringify(error)}`);

});
if (webhookUrl) {
await this.connectionService._postWebhookResponse(webhookUrl, { data: questionAnswerWebhookDto }).catch(error => {
this.logger.debug(`error in posting webhook response to webhook url ::: ${JSON.stringify(error)}`);
});
}
return res.status(HttpStatus.CREATED).json(finalResponse);
}
}
20 changes: 20 additions & 0 deletions apps/api-gateway/src/connection/connection.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@ import { ConnectionDto, CreateConnectionDto, ReceiveInvitationDto, ReceiveInvita
import { IReceiveInvitationRes, IUserRequestInterface } from './interfaces';
import { IConnectionList, ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface';
import { IConnectionDetailsById, IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface';
import { QuestionDto } from './dtos/question-answer.dto';

@Injectable()
export class ConnectionService extends BaseService {
constructor(@Inject('NATS_CLIENT') private readonly connectionServiceProxy: ClientProxy) {
super('ConnectionService');
}

sendQuestion(
questionDto: QuestionDto
): Promise<object> {
try {
return this.sendNatsMessage(this.connectionServiceProxy, 'send-question', questionDto);
} catch (error) {
throw new RpcException(error.response);
}
}

createLegacyConnectionInvitation(
connectionDto: CreateConnectionDto,
user: IUserRequestInterface
Expand Down Expand Up @@ -75,6 +86,15 @@ export class ConnectionService extends BaseService {
return this.sendNatsMessage(this.connectionServiceProxy, 'get-connection-details-by-connectionId', payload);
}


getQuestionAnswersRecord(
tenantId: string,
orgId: string
): Promise<object> {
const payload = { tenantId, orgId };
return this.sendNatsMessage(this.connectionServiceProxy, 'get-question-answer-record', payload);
}

receiveInvitationUrl(
receiveInvitationUrl: ReceiveInvitationUrlDto,
orgId: string,
Expand Down
95 changes: 95 additions & 0 deletions apps/api-gateway/src/connection/dtos/question-answer.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { trim } from '@credebl/common/cast.helper';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import { IsArray, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator';

class ValidResponses {
@ApiProperty({ example: 'Emma' })
@IsNotEmpty({ message: 'text is required' })
@IsString({ message: 'text should be a string' })
@Transform(({ value }) => trim(value))
@Type(() => String)
text: string;
}
export class QuestionDto {
@ApiPropertyOptional()
@IsOptional()
@IsString({ message: 'detail must be a string' })
@IsNotEmpty({ message: 'please provide valid detail' })
detail: string;

@ApiProperty({ example: [{ 'text': 'Emma'}, { 'text': 'Kiva'}] })
@IsNotEmpty({ message: 'Please provide valid responses' })
@IsArray({ message: 'Responses should be array' })
@ValidateNested({ each: true })
@Type(() => ValidResponses)
validResponses: ValidResponses[];

@ApiProperty({ example: 'What is your name'})
@IsNotEmpty({ message: 'question is required' })
@IsString({ message: 'question must be a string' })
@IsNotEmpty({ message: 'please provide valid question' })
question: string;

orgId: string;
connectionId: string;
tenantId: string;
}

export class QuestionAnswerWebhookDto {


@ApiPropertyOptional()
@IsOptional()
id: string;

@ApiPropertyOptional()
@IsOptional()
createdAt: string;

@ApiPropertyOptional()
@IsOptional()
questionText: string;

@ApiPropertyOptional()
@IsOptional()
questionDetail: string;

@ApiPropertyOptional()
@IsOptional()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
validResponses:any;

@ApiPropertyOptional()
@IsOptional()
connectionId: string;

@ApiPropertyOptional()
@IsOptional()
role: string;

@ApiPropertyOptional()
@IsOptional()
signatureRequired: boolean;

@ApiPropertyOptional()
@IsOptional()
state: boolean;

@ApiPropertyOptional()
@IsOptional()
threadId: string;

@ApiPropertyOptional()
@IsOptional()
updatedAt: string;

@ApiPropertyOptional()
@IsOptional()
contextCorrelationId: string;

@ApiPropertyOptional()
@IsOptional()
type: string;

}
12 changes: 12 additions & 0 deletions apps/connection/src/connection.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from './interfaces/connection.interfaces';
import { IConnectionList, ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface';
import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface';
import { IQuestionAnswerPayload, IQuestionPayload } from './interfaces/question-answer.interfaces';

@Controller()
export class ConnectionController {
Expand Down Expand Up @@ -76,4 +77,15 @@ export class ConnectionController {
const { user, receiveInvitation, orgId } = payload;
return this.connectionService.receiveInvitation(user, receiveInvitation, orgId);
}

@MessagePattern({ cmd: 'send-question' })
async sendQuestion(payload: IQuestionPayload): Promise<object> {
return this.connectionService.sendQuestion(payload);
}

@MessagePattern({ cmd: 'get-question-answer-record' })
async getQuestionAnswersRecord(payload: IQuestionAnswerPayload): Promise<object> {
const { tenantId, orgId } = payload;
return this.connectionService.getQuestionAnswersRecord(tenantId, orgId);
}
}
Loading

0 comments on commit 2d3c593

Please sign in to comment.