diff --git a/.gitignore b/.gitignore index 0515c2a0a..5718a8931 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ libs/prisma-service/prisma/data/credebl-master-table.json uploadedFles/exports uploadedFles/import uploadedFles/export -nats-server.conf \ No newline at end of file +nats-server.conf diff --git a/Dockerfiles/Dockerfile.agnet-provisioning b/Dockerfiles/Dockerfile.agent-provisioning similarity index 69% rename from Dockerfiles/Dockerfile.agnet-provisioning rename to Dockerfiles/Dockerfile.agent-provisioning index fffb90839..5c9c4f19d 100644 --- a/Dockerfiles/Dockerfile.agnet-provisioning +++ b/Dockerfiles/Dockerfile.agent-provisioning @@ -1,6 +1,15 @@ # Stage 1: Build the application FROM node:18-alpine as build -RUN npm install -g pnpm +# RUN npm install -g pnpm +# Install AWS CLI +# RUN apk update +# RUN apk add openssh-client +# RUN apk update +# RUN apk add aws-cli +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssh-client \ + && apk add aws-cli # Set the working directory WORKDIR /app @@ -23,24 +32,35 @@ RUN pnpm run build agent-provisioning # Stage 2: Create the final image FROM node:18-alpine as prod +# Install AWS CLI +# RUN apk update +# RUN apk add openssh-client +# RUN apk update +# RUN apk add aws-cli +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssh-client \ + && apk add aws-cli + WORKDIR /app -RUN npm install -g pnpm RUN mkdir -p ./agent-provisioning/AFJ/endpoints RUN mkdir -p ./agent-provisioning/AFJ/agent-config +RUN mkdir -p ./agent-provisioning/AFJ/port-file +RUN mkdir -p ./agent-provisioning/AFJ/token # Copy the compiled code COPY --from=build /app/dist/apps/agent-provisioning/ ./dist/apps/agent-provisioning/ COPY --from=build /app/node_modules ./node_modules COPY --from=build /app/apps/agent-provisioning/AFJ/scripts ./agent-provisioning/AFJ/scripts -COPY --from=build /app/apps/agent-provisioning/AFJ/port-files ./agent-provisioning/AFJ/port-file +COPY --from=build /app/apps/agent-provisioning/AFJ/port-file ./agent-provisioning/AFJ/port-file # Set permissions RUN chmod +x /app/agent-provisioning/AFJ/scripts/start_agent.sh RUN chmod +x /app/agent-provisioning/AFJ/scripts/start_agent_ecs.sh RUN chmod 777 /app/agent-provisioning/AFJ/endpoints RUN chmod 777 /app/agent-provisioning/AFJ/agent-config - +RUN chmod 777 /app/agent-provisioning/AFJ/token # Copy the libs folder COPY libs/ ./libs/ diff --git a/apps/agent-provisioning/AFJ/scripts/start_agent.sh b/apps/agent-provisioning/AFJ/scripts/start_agent.sh index 4a793ea12..519bb138e 100755 --- a/apps/agent-provisioning/AFJ/scripts/start_agent.sh +++ b/apps/agent-provisioning/AFJ/scripts/start_agent.sh @@ -1,3 +1,5 @@ +#!/bin/bash + START_TIME=$(date +%s) AGENCY=$1 @@ -76,12 +78,25 @@ else mkdir ${PWD}/apps/agent-provisioning/AFJ/endpoints fi -docker build . -t $AFJ_VERSION -f apps/agent-provisioning/AFJ/afj-controller/Dockerfile +if [ -d "${PWD}/apps/agent-provisioning/AFJ/agent-config" ]; then + echo "Endpoints directory exists." +else + echo "Error: Endpoints directory does not exists." + mkdir ${PWD}/apps/agent-provisioning/AFJ/agent-config +fi AGENT_ENDPOINT="${PROTOCOL}://${EXTERNAL_IP}:${INBOUND_PORT}" echo "-----$AGENT_ENDPOINT----" -cat <>${PWD}/apps/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINER_NAME}.json +CONFIG_FILE="${PWD}/apps/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINER_NAME}.json" + +# Check if the file exists +if [ -f "$CONFIG_FILE" ]; then + # If it exists, remove the file + rm "$CONFIG_FILE" +fi + +cat <>${CONFIG_FILE} { "label": "${AGENCY}_${CONTAINER_NAME}", "walletId": "$WALLET_NAME", @@ -117,7 +132,15 @@ cat <>${PWD}/apps/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINE EOF FILE_NAME="docker-compose_${AGENCY}_${CONTAINER_NAME}.yaml" -cat <>${PWD}/apps/agent-provisioning/AFJ/${FILE_NAME} + +DOCKER_COMPOSE="${PWD}/apps/agent-provisioning/AFJ/${FILE_NAME}" + +# Check if the file exists +if [ -f "$DOCKER_COMPOSE" ]; then + # If it exists, remove the file + rm "$DOCKER_COMPOSE" +fi +cat <>${DOCKER_COMPOSE} version: '3' services: @@ -152,7 +175,7 @@ if [ $? -eq 0 ]; then echo "container-name::::::${CONTAINER_NAME}" echo "file-name::::::$FILE_NAME" - docker-compose -f $FILE_NAME --project-name ${AGENCY}_${CONTAINER_NAME} up -d + docker compose -f $FILE_NAME up -d if [ $? -eq 0 ]; then n=0 @@ -177,10 +200,22 @@ if [ $? -eq 0 ]; then done echo "Creating agent config" - cat <>${PWD}/endpoints/${AGENCY}_${CONTAINER_NAME}.json + ENDPOINT="${PWD}/endpoints/${AGENCY}_${CONTAINER_NAME}.json" + + # Check if the file exists + if [ -f "$ENDPOINT" ]; then + # If it exists, remove the file + rm "$ENDPOINT" + fi + cat <>${ENDPOINT} + { + "CONTROLLER_ENDPOINT":"${EXTERNAL_IP}:${ADMIN_PORT}" + } +EOF + + cat <>${PWD}/token/${AGENCY}_${CONTAINER_NAME}.json { - "CONTROLLER_ENDPOINT":"${EXTERNAL_IP}:${ADMIN_PORT}", - "AGENT_ENDPOINT" : "${INTERNAL_IP}:${ADMIN_PORT}" + "token" : "$token" } EOF echo "Agent config created" diff --git a/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh b/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh index 2c1c1879a..86c89ed85 100644 --- a/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh +++ b/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh @@ -23,11 +23,22 @@ S3_BUCKET_ARN=${18} CLUSTER_NAME=${19} TESKDEFINITION_FAMILY=${20} -SERVICE_NAME="${AGENCY}-${CONTAINER_NAME}-service" DESIRED_COUNT=1 + +generate_random_string() { + echo "$(date +%s%N | sha256sum | base64 | head -c 12)" +} + +# Call the function to generate a random string +random_string=$(generate_random_string) + +# Print the generated random string +echo "Random String: $random_string" + +SERVICE_NAME="${AGENCY}-${CONTAINER_NAME}-service-${random_string}" EXTERNAL_IP=$(echo "$2" | tr -d '[:space:]') -ADMIN_PORT_FILE="$PWD/apps/agent-provisioning/AFJ/port-file/last-admin-port.txt" -INBOUND_PORT_FILE="$PWD/apps/agent-provisioning/AFJ/port-file/last-inbound-port.txt" +ADMIN_PORT_FILE="$PWD/agent-provisioning/AFJ/port-file/last-admin-port.txt" +INBOUND_PORT_FILE="$PWD/agent-provisioning/AFJ/port-file/last-inbound-port.txt" ADMIN_PORT=8001 INBOUND_PORT=9001 @@ -80,7 +91,7 @@ echo "AGENT SPIN-UP STARTED" AGENT_ENDPOINT="${PROTOCOL}://${EXTERNAL_IP}:${INBOUND_PORT}" -cat <>/app/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINER_NAME}.json +cat </app/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINER_NAME}.json { "label": "${AGENCY}_${CONTAINER_NAME}", "walletId": "$WALLET_NAME", @@ -233,12 +244,19 @@ if [ $? -eq 0 ]; then done echo "Creating agent config" - cat <>${PWD}/agent-provisioning/AFJ/endpoints/${AGENCY}_${CONTAINER_NAME}.json + cat <${PWD}/agent-provisioning/AFJ/endpoints/${AGENCY}_${CONTAINER_NAME}.json { "CONTROLLER_ENDPOINT":"${EXTERNAL_IP}:${ADMIN_PORT}", "AGENT_ENDPOINT" : "${INTERNAL_IP}:${ADMIN_PORT}" } EOF + + cat <${PWD}/agent-provisioning/AFJ/token/${AGENCY}_${CONTAINER_NAME}.json + { + "token" : "" + } +EOF + echo "Agent config created" else echo "===============" diff --git a/apps/agent-provisioning/src/agent-provisioning.service.ts b/apps/agent-provisioning/src/agent-provisioning.service.ts index a0163741c..465e4087d 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.ts @@ -23,12 +23,9 @@ export class AgentProvisioningService { try { const { containerName, externalIp, orgId, seed, walletName, walletPassword, walletStorageHost, walletStoragePassword, walletStoragePort, walletStorageUser, webhookEndpoint, agentType, protocol, afjVersion, tenant, indyLedger } = payload; - if (agentType === AgentType.AFJ) { // The wallet provision command is used to invoke a shell script - const walletProvision = `${process.cwd() + process.env.AFJ_AGENT_SPIN_UP - } ${orgId} "${externalIp}" "${walletName}" "${walletPassword}" ${seed} ${webhookEndpoint} ${walletStorageHost} ${walletStoragePort} ${walletStorageUser} ${walletStoragePassword} ${containerName} ${protocol} ${tenant} ${afjVersion} ${indyLedger} ${process.env.AGENT_HOST} ${process.env.AWS_ACCOUNT_ID} ${process.env.S3_BUCKET_ARN} ${process.env.CLUSTER_NAME} ${process.env.TESKDEFINITION_FAMILY}`; - + const walletProvision = `${process.cwd() + process.env.AFJ_AGENT_SPIN_UP} ${orgId} "${externalIp}" "${walletName}" "${walletPassword}" ${seed} ${webhookEndpoint} ${walletStorageHost} ${walletStoragePort} ${walletStorageUser} ${walletStoragePassword} ${containerName} ${protocol} ${tenant} ${afjVersion} ${indyLedger} ${process.env.AGENT_HOST} ${process.env.AWS_ACCOUNT_ID} ${process.env.S3_BUCKET_ARN} ${process.env.CLUSTER_NAME} ${process.env.TESKDEFINITION_FAMILY}`; const spinUpResponse: object = new Promise(async (resolve) => { await exec(walletProvision, async (err, stdout, stderr) => { @@ -36,8 +33,14 @@ export class AgentProvisioningService { if (stderr) { this.logger.log(`shell script error: ${stderr}`); } - const agentEndPoint: string = await fs.readFileSync(`${process.env.PWD}${process.env.AFJ_AGENT_ENDPOINT_PATH}${orgId}_${containerName}.json`, 'utf8'); - resolve(agentEndPoint); + + const agentEndPoint = await fs.readFileSync(`${process.cwd()}${process.env.AFJ_AGENT_ENDPOINT_PATH}${orgId}_${containerName}.json`, 'utf8'); + const agentToken = await fs.readFileSync(`${process.cwd()}${process.env.AFJ_AGENT_TOKEN_PATH}${orgId}_${containerName}.json`, 'utf8'); + + resolve({ + agentEndPoint: JSON.parse(agentEndPoint).CONTROLLER_ENDPOINT, + agentToken: JSON.parse(agentToken).token + }); }); }); return spinUpResponse; diff --git a/apps/agent-provisioning/src/interface/agent-provisioning.interfaces.ts b/apps/agent-provisioning/src/interface/agent-provisioning.interfaces.ts index 105dd6bb6..5f18b6cbb 100644 --- a/apps/agent-provisioning/src/interface/agent-provisioning.interfaces.ts +++ b/apps/agent-provisioning/src/interface/agent-provisioning.interfaces.ts @@ -20,6 +20,7 @@ export interface IWalletProvision { protocol: string; afjVersion: string; tenant: boolean; + apiKey?:string; } export interface IAgentSpinUp { diff --git a/apps/agent-service/src/agent-service.controller.ts b/apps/agent-service/src/agent-service.controller.ts index 9fc94f190..0cedb7b1b 100644 --- a/apps/agent-service/src/agent-service.controller.ts +++ b/apps/agent-service/src/agent-service.controller.ts @@ -1,140 +1,195 @@ import { Controller } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; import { AgentServiceService } from './agent-service.service'; -import { GetCredDefAgentRedirection, GetSchemaAgentRedirection, IAgentSpinupDto, IIssuanceCreateOffer, ITenantCredDef, ITenantDto, ITenantSchema, OutOfBandCredentialOffer } from './interface/agent-service.interface'; +import { IAgentStatus, IAgentSpinUpSatus, IGetCredDefAgentRedirection, IGetSchemaAgentRedirection, IAgentSpinupDto, IIssuanceCreateOffer, ITenantCredDef, ITenantDto, ITenantSchema, IOutOfBandCredentialOffer } from './interface/agent-service.interface'; import { IConnectionDetails, IUserRequestInterface } from './interface/agent-service.interface'; import { ISendProofRequestPayload } 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'; @Controller() export class AgentServiceController { - constructor(private readonly agentServiceService: AgentServiceService) { } + constructor(private readonly agentServiceService: AgentServiceService) {} + /** + * Spinup the agent by organization + * @param payload + * @returns Get agent status + */ @MessagePattern({ cmd: 'agent-spinup' }) - async walletProvision(payload: { agentSpinupDto: IAgentSpinupDto, user: IUserRequestInterface }): Promise { + async walletProvision(payload: { agentSpinupDto: IAgentSpinupDto, user: IUserRequestInterface }): Promise { return this.agentServiceService.walletProvision(payload.agentSpinupDto, payload.user); } + + //DONE @MessagePattern({ cmd: 'create-tenant' }) - async createTenant(payload: { createTenantDto: ITenantDto, user: IUserRequestInterface }): Promise<{ - agentSpinupStatus: number; - }> { + async createTenant(payload: { createTenantDto: ITenantDto, user: IUserRequestInterface }): Promise { return this.agentServiceService.createTenant(payload.createTenantDto, payload.user); } + //DONE @MessagePattern({ cmd: 'agent-create-schema' }) async createSchema(payload: ITenantSchema): Promise { return this.agentServiceService.createSchema(payload); } + //DONE @MessagePattern({ cmd: 'agent-get-schema' }) - async getSchemaById(payload: GetSchemaAgentRedirection): Promise { + async getSchemaById(payload: IGetSchemaAgentRedirection): Promise { return this.agentServiceService.getSchemaById(payload); } + //DONE @MessagePattern({ cmd: 'agent-create-credential-definition' }) async createCredentialDefinition(payload: ITenantCredDef): Promise { - return this.agentServiceService.createCredentialDefinition(payload); } + // DONE @MessagePattern({ cmd: 'agent-get-credential-definition' }) - async getCredentialDefinitionById(payload: GetCredDefAgentRedirection): Promise { + async getCredentialDefinitionById(payload: IGetCredDefAgentRedirection): Promise { return this.agentServiceService.getCredentialDefinitionById(payload); } - + //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); } @MessagePattern({ cmd: 'agent-send-credential-create-offer' }) - async sendCredentialCreateOffer(payload: { issueData: IIssuanceCreateOffer, url: string, apiKey: string }): Promise { + async sendCredentialCreateOffer(payload: { + issueData: IIssuanceCreateOffer; + url: string; + apiKey: string; + }): Promise { return this.agentServiceService.sendCredentialCreateOffer(payload.issueData, payload.url, payload.apiKey); } + //DONE @MessagePattern({ cmd: 'agent-get-all-issued-credentials' }) - async getIssueCredentials(payload: { url: string, apiKey: string }): Promise { + async getIssueCredentials(payload: { url: string; apiKey: string }): Promise { return this.agentServiceService.getIssueCredentials(payload.url, payload.apiKey); } + //DONE @MessagePattern({ cmd: 'agent-get-issued-credentials-by-credentialDefinitionId' }) - async getIssueCredentialsbyCredentialRecordId(payload: { url: string, apiKey: string }): Promise { + async getIssueCredentialsbyCredentialRecordId(payload: { url: string; apiKey: string }): Promise { return this.agentServiceService.getIssueCredentialsbyCredentialRecordId(payload.url, payload.apiKey); } + //DONE @MessagePattern({ cmd: 'agent-get-proof-presentations' }) - async getProofPresentations(payload: { url: string, apiKey: string }): Promise { + async getProofPresentations(payload: { url: string; apiKey: string }): Promise { return this.agentServiceService.getProofPresentations(payload.url, payload.apiKey); } + //DONE @MessagePattern({ cmd: 'agent-get-proof-presentation-by-id' }) - async getProofPresentationById(payload: { url: string, apiKey: string }): Promise { + async getProofPresentationById(payload: { url: string; apiKey: string }): Promise { return this.agentServiceService.getProofPresentationById(payload.url, payload.apiKey); } + //DONE @MessagePattern({ cmd: 'agent-send-proof-request' }) - async sendProofRequest(payload: { proofRequestPayload: ISendProofRequestPayload, url: string, apiKey: string }): Promise { + async sendProofRequest(payload: { + proofRequestPayload: ISendProofRequestPayload; + url: string; + apiKey: string; + }): Promise { return this.agentServiceService.sendProofRequest(payload.proofRequestPayload, payload.url, payload.apiKey); } - +//DONE @MessagePattern({ cmd: 'agent-verify-presentation' }) - async verifyPresentation(payload: { url: string, apiKey: string }): Promise { + async verifyPresentation(payload: { url: string; apiKey: string }): Promise { return this.agentServiceService.verifyPresentation(payload.url, payload.apiKey); } + //DONE @MessagePattern({ cmd: 'agent-get-all-connections' }) - async getConnections(payload: { url: string, apiKey: string }): Promise { + async getConnections(payload: { url: string; apiKey: string }): Promise { return this.agentServiceService.getConnections(payload.url, payload.apiKey); } - - @MessagePattern({ cmd: 'agent-get-connections-by-connectionId' }) - async getConnectionsByconnectionId(payload: { url: string, apiKey: string }): Promise { + + @MessagePattern({ cmd: 'agent-get-connection-details-by-connectionId' }) + async getConnectionsByconnectionId(payload: { url: string, apiKey: string }): Promise { return this.agentServiceService.getConnectionsByconnectionId(payload.url, payload.apiKey); } + /** + * Get agent health + * @param payload + * @returns Get agent health + */ @MessagePattern({ cmd: 'agent-health' }) - async getAgentHealth(payload: { user: user, orgId: string }): Promise { + async getAgentHealth(payload: { user: user, orgId: string }): Promise { return this.agentServiceService.getAgentHealthDetails(payload.orgId); } + //DONE @MessagePattern({ cmd: 'agent-send-out-of-band-proof-request' }) - async sendOutOfBandProofRequest(payload: { proofRequestPayload: ISendProofRequestPayload, url: string, apiKey: string }): Promise { + async sendOutOfBandProofRequest(payload: { + proofRequestPayload: ISendProofRequestPayload; + url: string; + apiKey: string; + }): Promise { return this.agentServiceService.sendOutOfBandProofRequest(payload.proofRequestPayload, payload.url, payload.apiKey); } + //DONE @MessagePattern({ cmd: 'agent-proof-form-data' }) - async getProofFormData(payload: { url: string, apiKey: string }): Promise { - + async getProofFormData(payload: { url: string; apiKey: string }): Promise { return this.agentServiceService.getProofFormData(payload.url, payload.apiKey); } @MessagePattern({ cmd: 'agent-schema-endorsement-request' }) - async schemaEndorsementRequest(payload: { url: string, apiKey: string, requestSchemaPayload: object }): Promise { + async schemaEndorsementRequest(payload: { + url: string; + apiKey: string; + requestSchemaPayload: object; + }): Promise { return this.agentServiceService.schemaEndorsementRequest(payload.url, payload.apiKey, payload.requestSchemaPayload); } @MessagePattern({ cmd: 'agent-credDef-endorsement-request' }) - async credDefEndorsementRequest(payload: { url: string, apiKey: string, requestSchemaPayload: object }): Promise { - return this.agentServiceService.credDefEndorsementRequest(payload.url, payload.apiKey, payload.requestSchemaPayload); - } - + async credDefEndorsementRequest(payload: { + url: string; + apiKey: string; + requestSchemaPayload: object; + }): Promise { + return this.agentServiceService.credDefEndorsementRequest( + payload.url, + payload.apiKey, + payload.requestSchemaPayload + ); + } + + //DONE @MessagePattern({ cmd: 'agent-sign-transaction' }) - async signTransaction(payload: { url: string, apiKey: string, signEndorsementPayload: object }): Promise { + async signTransaction(payload: { url: string; apiKey: string; signEndorsementPayload: object }): Promise { return this.agentServiceService.signTransaction(payload.url, payload.apiKey, payload.signEndorsementPayload); } + + //DONE @MessagePattern({ cmd: 'agent-submit-transaction' }) - async submitTransaction(payload: { url: string, apiKey: string, submitEndorsementPayload: object }): Promise { + async submitTransaction(payload: { url: string; apiKey: string; submitEndorsementPayload: object }): Promise { return this.agentServiceService.sumbitTransaction(payload.url, payload.apiKey, payload.submitEndorsementPayload); } + //DONE @MessagePattern({ cmd: 'agent-out-of-band-credential-offer' }) - async outOfBandCredentialOffer(payload: { outOfBandIssuancePayload: OutOfBandCredentialOffer, url: string, apiKey: string }): Promise { + async outOfBandCredentialOffer(payload: { outOfBandIssuancePayload: IOutOfBandCredentialOffer, url: string, apiKey: string }): Promise { return this.agentServiceService.outOfBandCredentialOffer(payload.outOfBandIssuancePayload, payload.url, payload.apiKey); } @MessagePattern({ cmd: 'delete-wallet' }) - async deleteWallet(payload: { url, apiKey }): Promise { + async deleteWallet(payload: { url; apiKey }): Promise { return this.agentServiceService.deleteWallet(payload.url, payload.apiKey); } + + @MessagePattern({ cmd: 'get-org-agent-api-key' }) + async getOrgAgentApiKey(payload: { orgId: string }): Promise { + return this.agentServiceService.getOrgAgentApiKey(payload.orgId); + } + } diff --git a/apps/agent-service/src/agent-service.module.ts b/apps/agent-service/src/agent-service.module.ts index 56c20192c..87bcb642a 100644 --- a/apps/agent-service/src/agent-service.module.ts +++ b/apps/agent-service/src/agent-service.module.ts @@ -8,6 +8,7 @@ import { AgentServiceRepository } from './repositories/agent-service.repository' import { ConfigModule } from '@nestjs/config'; import { ConnectionService } from 'apps/connection/src/connection.service'; import { ConnectionRepository } from 'apps/connection/src/connection.repository'; +import { CacheModule } from '@nestjs/cache-manager'; import { getNatsOptions } from '@credebl/common/nats.config'; @Module({ @@ -20,9 +21,18 @@ import { getNatsOptions } from '@credebl/common/nats.config'; options: getNatsOptions(process.env.AGENT_SERVICE_NKEY_SEED) } ]), - CommonModule + CommonModule, + CacheModule.register() ], controllers: [AgentServiceController], - providers: [AgentServiceService, AgentServiceRepository, PrismaService, Logger, ConnectionService, ConnectionRepository] + providers: [ + AgentServiceService, + AgentServiceRepository, + PrismaService, + Logger, + ConnectionService, + ConnectionRepository + ], + exports: [AgentServiceService, AgentServiceRepository, AgentServiceModule] }) -export class AgentServiceModule { } +export class AgentServiceModule {} diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 8659486c1..f10f36ddf 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -6,19 +6,21 @@ /* eslint-disable camelcase */ import { BadRequestException, + ConflictException, HttpException, Inject, Injectable, InternalServerErrorException, Logger, - NotFoundException + NotFoundException, + forwardRef } from '@nestjs/common'; import { ClientProxy, RpcException } from '@nestjs/microservices'; import * as dotenv from 'dotenv'; import * as fs from 'fs'; import { catchError, map } from 'rxjs/operators'; dotenv.config(); -import { GetCredDefAgentRedirection, IAgentSpinupDto, IStoreOrgAgentDetails, ITenantCredDef, ITenantDto, ITenantSchema, IWalletProvision, ISendProofRequestPayload, IIssuanceCreateOffer, OutOfBandCredentialOffer } from './interface/agent-service.interface'; +import { IGetCredDefAgentRedirection, IAgentSpinupDto, IStoreOrgAgentDetails, ITenantCredDef, ITenantDto, ITenantSchema, IWalletProvision, ISendProofRequestPayload, IIssuanceCreateOffer, IOutOfBandCredentialOffer, IAgentSpinUpSatus, ICreateTenant, IAgentStatus, ICreateOrgAgent, IOrgAgentsResponse } from './interface/agent-service.interface'; import { AgentSpinUpStatus, AgentType, Ledgers, OrgAgentType } from '@credebl/enum/enum'; import { IConnectionDetails, IUserRequestInterface } from './interface/agent-service.interface'; import { AgentServiceRepository } from './repositories/agent-service.repository'; @@ -31,6 +33,10 @@ import { ResponseMessages } from '@credebl/common/response-messages'; import { Socket, io } from 'socket.io-client'; import { WebSocketGateway } from '@nestjs/websockets'; import * as retry from 'async-retry'; +import { Cache } from 'cache-manager'; +import { CACHE_MANAGER } from '@nestjs/cache-manager'; +import { ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface'; +import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; @Injectable() @WebSocketGateway() @@ -42,8 +48,8 @@ export class AgentServiceService { private readonly agentServiceRepository: AgentServiceRepository, private readonly commonService: CommonService, private readonly connectionService: ConnectionService, - @Inject('NATS_CLIENT') private readonly agentServiceProxy: ClientProxy - + @Inject('NATS_CLIENT') private readonly agentServiceProxy: ClientProxy, + @Inject(CACHE_MANAGER) private cacheService: Cache ) { } async ReplaceAt(input, search, replace, start, end): Promise { @@ -126,18 +132,26 @@ export class AgentServiceService { } } - - async walletProvision(agentSpinupDto: IAgentSpinupDto, user: IUserRequestInterface): Promise<{ agentSpinupStatus: number }> { - let agentProcess: org_agents; + /** + * Spinup the agent by organization + * @param agentSpinupDto + * @param user + * @returns Get agent status + */ + async walletProvision(agentSpinupDto: IAgentSpinupDto, user: IUserRequestInterface): Promise { + let agentProcess: ICreateOrgAgent; try { - this.processWalletProvision(agentSpinupDto, user); + // Invoke an internal function to create wallet + await this.processWalletProvision(agentSpinupDto, user); const agentStatusResponse = { agentSpinupStatus: AgentSpinUpStatus.PROCESSED }; return agentStatusResponse; } catch (error) { + + // Invoke an internal function to handle error to create wallet this.handleErrorOnWalletProvision(agentSpinupDto, error, agentProcess); throw new RpcException(error.response ? error.response : error); } @@ -146,38 +160,67 @@ export class AgentServiceService { private async processWalletProvision(agentSpinupDto: IAgentSpinupDto, user: IUserRequestInterface): Promise { let platformAdminUser; let userId: string; - let agentProcess: org_agents; + let agentProcess: ICreateOrgAgent; + let getOrgAgent; try { - const [getOrgAgent, platformConfig, getAgentType, ledgerIdData, orgData] = await Promise.all([ - this.agentServiceRepository.getAgentDetails(agentSpinupDto.orgId), + + const [platformConfig, getAgentType, ledgerIdData] = await Promise.all([ this.agentServiceRepository.getPlatformConfigDetails(), this.agentServiceRepository.getAgentTypeDetails(), - this.agentServiceRepository.getLedgerDetails(agentSpinupDto.ledgerName ? agentSpinupDto.ledgerName : [Ledgers.Indicio_Demonet]), - this.agentServiceRepository.getOrgDetails(agentSpinupDto.orgId) + this.agentServiceRepository.getLedgerDetails(agentSpinupDto.ledgerName ? agentSpinupDto.ledgerName : [Ledgers.Indicio_Demonet]) ]); + let orgData; if (!user?.userId && agentSpinupDto?.platformAdminEmail) { + + // Get Platform admin user by platform admin email platformAdminUser = await this.agentServiceRepository.getPlatfomAdminUser(agentSpinupDto?.platformAdminEmail); userId = platformAdminUser?.id; } else { - userId = user?.userId; + userId = user?.id; } - agentSpinupDto.ledgerId = agentSpinupDto.ledgerId?.length ? agentSpinupDto.ledgerId : ledgerIdData.map(ledger => ledger.id); - const ledgerDetails = await this.agentServiceRepository.getGenesisUrl(agentSpinupDto.ledgerId); + // Get platform org + const platformAdminOrgDetails = await this.agentServiceRepository.getPlatfomOrg(agentSpinupDto?.orgName); - if (AgentSpinUpStatus.COMPLETED === getOrgAgent?.agentSpinUpStatus) { - throw new BadRequestException('Your wallet has already been created.'); + if (agentSpinupDto.orgId) { + + // Get organization details + getOrgAgent = await this.agentServiceRepository.getAgentDetails(agentSpinupDto.orgId); + + // Get organization data by orgId + orgData = await this.agentServiceRepository.getOrgDetails(agentSpinupDto.orgId); + } else { + + // Get platform organization details + getOrgAgent = await this.agentServiceRepository.getAgentDetails(platformAdminOrgDetails); + + // Get platform organization data by orgId + orgData = await this.agentServiceRepository.getOrgDetails(platformAdminOrgDetails); } + agentSpinupDto.ledgerId = agentSpinupDto.ledgerId?.length ? agentSpinupDto.ledgerId : ledgerIdData.map(ledger => ledger?.id); + + // Get genesis URL and ledger details + const ledgerDetails = await this.agentServiceRepository.getGenesisUrl(agentSpinupDto.ledgerId); + if (AgentSpinUpStatus.PROCESSED === getOrgAgent?.agentSpinUpStatus) { - throw new BadRequestException('Your wallet is already processing.'); + throw new BadRequestException( + ResponseMessages.agent.error.walletAlreadyProcessing, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); + } + + if (AgentSpinUpStatus.COMPLETED === getOrgAgent?.agentSpinUpStatus) { + throw new BadRequestException( + ResponseMessages.agent.error.walletAlreadyCreated, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } if (!agentSpinupDto.orgId) { - const platformAdminOrgDetails = await this.agentServiceRepository.getPlatfomOrg(agentSpinupDto?.orgName); if (platformAdminOrgDetails) { agentSpinupDto.orgId = platformAdminOrgDetails; @@ -188,21 +231,26 @@ export class AgentServiceService { agentSpinupDto.tenant = agentSpinupDto.tenant || false; agentSpinupDto.ledgerName = agentSpinupDto.ledgerName?.length ? agentSpinupDto.ledgerName : [Ledgers.Indicio_Demonet]; + + // Invoke function for validate platform configuration this.validatePlatformConfig(platformConfig); const externalIp = platformConfig?.externalIp; const controllerIp = platformConfig?.lastInternalId !== 'false' ? platformConfig?.lastInternalId : ''; const apiEndpoint = platformConfig?.apiEndpoint; + // Create payload for the wallet create and store payload const walletProvisionPayload = await this.prepareWalletProvisionPayload(agentSpinupDto, externalIp, apiEndpoint, controllerIp, ledgerDetails, platformConfig, orgData); + + + // Socket connection const socket: Socket = await this.initSocketConnection(`${process.env.SOCKET_HOST}`); this.emitAgentSpinupInitiatedEvent(agentSpinupDto, socket); const agentSpinUpStatus = AgentSpinUpStatus.PROCESSED; - /* eslint-disable no-param-reassign */ agentProcess = await this.createOrgAgent(agentSpinUpStatus, userId); - this.validateAgentProcess(agentProcess); + // AFJ agent spin-up this._agentSpinup(walletProvisionPayload, agentSpinupDto, platformConfig?.sgApiKey, orgData, user, socket, agentSpinupDto.ledgerId, agentProcess); } catch (error) { @@ -213,26 +261,46 @@ export class AgentServiceService { validatePlatformConfig(platformConfig: platform_config): void { if (!platformConfig) { - throw new BadRequestException('Platform configuration is missing or invalid.'); + this.logger.error(`Platform configuration is missing or invalid`); + throw new BadRequestException( + ResponseMessages.agent.error.platformConfiguration, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } if (!platformConfig.apiEndpoint) { - throw new BadRequestException('API endpoint is missing in the platform configuration.'); + this.logger.error(`API endpoint is missing in the platform configuration`); + throw new BadRequestException( + ResponseMessages.agent.error.apiEndpoint, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } if (!platformConfig.externalIp) { - throw new BadRequestException('External IP is missing in the platform configuration.'); + this.logger.error(`External IP is missing in the platform configuration`); + throw new BadRequestException( + ResponseMessages.agent.error.externalIp, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } if (typeof platformConfig.externalIp !== 'string') { - throw new BadRequestException('External IP must be a string.'); + this.logger.error(`External IP must be a string`); + throw new BadRequestException( + ResponseMessages.agent.error.externalIp, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } } - validateAgentProcess(agentProcess: org_agents): void { + validateAgentProcess(agentProcess: ICreateOrgAgent): void { try { if (!agentProcess) { - throw new BadRequestException('Agent process is invalid or not in a completed state.'); + this.logger.error(`Agent process is invalid or not in a completed state`); + throw new BadRequestException( + ResponseMessages.agent.error.externalIp, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } } catch (error) { this.logger.error(`Error validating agent process: ${error.message}`); @@ -272,11 +340,11 @@ export class AgentServiceService { const escapedJsonString = JSON.stringify(ledgerArray).replace(/"/g, '\\"'); const walletProvisionPayload: IWalletProvision = { - orgId: orgData.id, + orgId: orgData?.id, externalIp, - walletName: agentSpinupDto.walletName, - walletPassword: agentSpinupDto.walletPassword, - seed: agentSpinupDto.seed, + walletName: agentSpinupDto?.walletName, + walletPassword: agentSpinupDto?.walletPassword, + seed: agentSpinupDto?.seed, webhookEndpoint: apiEndpoint, walletStorageHost: process.env.WALLET_STORAGE_HOST || '', walletStoragePort: process.env.WALLET_STORAGE_PORT || '', @@ -285,11 +353,12 @@ export class AgentServiceService { internalIp: await this._validateInternalIp(platformConfig, controllerIp), containerName: orgData.name.split(' ').join('_'), agentType: AgentType.AFJ, - orgName: orgData.name, + orgName: orgData?.name, indyLedger: escapedJsonString, afjVersion: process.env.AFJ_VERSION || '', protocol: process.env.AGENT_PROTOCOL || '', - tenant: agentSpinupDto.tenant || false + tenant: agentSpinupDto.tenant || false, + apiKey: agentSpinupDto.apiKey }; return walletProvisionPayload; @@ -307,7 +376,7 @@ export class AgentServiceService { return socket; } - async createOrgAgent(agentSpinUpStatus: AgentSpinUpStatus, userId: string): Promise { + async createOrgAgent(agentSpinUpStatus: AgentSpinUpStatus, userId: string): Promise { try { const agentProcess = await this.agentServiceRepository.createOrgAgent(agentSpinUpStatus, userId); this.logger.log(`Organization agent created with status: ${agentSpinUpStatus}`); @@ -319,7 +388,7 @@ export class AgentServiceService { } } - private async handleErrorOnWalletProvision(agentSpinupDto: IAgentSpinupDto, error: Error, agentProcess: org_agents): Promise { + private async handleErrorOnWalletProvision(agentSpinupDto: IAgentSpinupDto, error: Error, agentProcess: ICreateOrgAgent): Promise { if (agentProcess) { const socket = await this.initSocketConnection(`${process.env.SOCKET_HOST}`); @@ -342,18 +411,27 @@ export class AgentServiceService { } } - async _agentSpinup(walletProvisionPayload: IWalletProvision, agentSpinupDto: IAgentSpinupDto, orgApiKey: string, orgData: organisation, user: IUserRequestInterface, socket: Socket, ledgerId: string[], agentProcess: org_agents): Promise { + async _agentSpinup(walletProvisionPayload: IWalletProvision, agentSpinupDto: IAgentSpinupDto, orgApiKey: string, orgData: organisation, user: IUserRequestInterface, socket: Socket, ledgerId: string[], agentProcess: ICreateOrgAgent): Promise { try { + + /** + * Invoke wallet create and provision with agent + */ const walletProvision = await this._walletProvision(walletProvisionPayload); if (!walletProvision?.response) { - throw new BadRequestException('Agent not able to spin-up'); + this.logger.error(`Agent not able to spin-up`); + throw new BadRequestException( + ResponseMessages.agent.error.notAbleToSpinup, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } + const agentDetails = walletProvision.response; + const agentEndPoint = `${process.env.API_GATEWAY_PROTOCOL}://${agentDetails.agentEndPoint}`; - const agentDetails = JSON.parse(walletProvision.response); - - const agentEndPoint = `${process.env.API_GATEWAY_PROTOCOL}://${agentDetails.CONTROLLER_ENDPOINT}`; - + /** + * Socket connection + */ const socket = await this.initSocketConnection(`${process.env.SOCKET_HOST}`); if (agentEndPoint && agentSpinupDto.clientSocketId) { @@ -365,32 +443,52 @@ export class AgentServiceService { const agentPayload: IStoreOrgAgentDetails = { agentEndPoint, seed: agentSpinupDto.seed, - apiKey: orgApiKey, + apiKey: agentDetails.agentToken, agentsTypeId: agentSpinupDto?.agentType, orgId: orgData.id, walletName: agentSpinupDto.walletName, clientSocketId: agentSpinupDto.clientSocketId, ledgerId, did: agentSpinupDto.did, - id: agentProcess.id + id: agentProcess?.id }; + /** + * Store organization agent details + */ const storeAgentDetails = await this._storeOrgAgentDetails(agentPayload); if (storeAgentDetails) { + + const filePath = `${process.cwd()}${process.env.AFJ_AGENT_TOKEN_PATH}${orgData.id}_${orgData.name.split(' ').join('_')}.json`; + if (agentDetails?.agentToken) { + fs.unlink(filePath, (err) => { + if (err) { + this.logger.error(`Error removing file: ${err.message}`); + throw new InternalServerErrorException(err.message); + } else { + this.logger.log(`File ${filePath} has been removed successfully`); + } + }); + } + if (agentSpinupDto.clientSocketId) { socket.emit('did-publish-process-completed', { clientId: agentSpinupDto.clientSocketId }); } - const getOrganization = await this.agentServiceRepository.getOrgDetails(orgData.id); + const getOrganization = await this.agentServiceRepository.getOrgDetails(orgData?.id); - await this._createLegacyConnectionInvitation(orgData.id, user, getOrganization.name); + await this._createLegacyConnectionInvitation(orgData?.id, user, getOrganization.name); if (agentSpinupDto.clientSocketId) { socket.emit('invitation-url-creation-success', { clientId: agentSpinupDto.clientSocketId }); } } else { - throw new BadRequestException('Agent not able to spin-up'); + this.logger.error(`Agent not able to spin-up`); + throw new BadRequestException( + ResponseMessages.agent.error.notAbleToSpinup, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } } catch (error) { if (agentSpinupDto.clientSocketId) { @@ -398,6 +496,9 @@ export class AgentServiceService { } if (agentProcess && agentProcess?.id) { + /** + * If getting error remove organization agent + */ await this.agentServiceRepository.removeOrgAgent(agentProcess?.id); } this.logger.error(`[_agentSpinup] - Error in Agent spin up : ${JSON.stringify(error)}`); @@ -406,13 +507,28 @@ export class AgentServiceService { async _storeOrgAgentDetails(payload: IStoreOrgAgentDetails): Promise { try { + + /** + * Get orgaization agent type and agent details + */ const [agentDid, orgAgentTypeId] = await Promise.all([ this._getAgentDid(payload), this.agentServiceRepository.getOrgAgentTypeDetails(OrgAgentType.DEDICATED) ]); + /** + * Get DID method by agent + */ const getDidMethod = await this._getDidMethod(payload, agentDid); + + /** + * Organization storage data + */ const storeOrgAgentData = await this._buildStoreOrgAgentData(payload, getDidMethod, orgAgentTypeId); + + /** + * Store org agent details + */ const storeAgentDid = await this.agentServiceRepository.storeOrgAgentDetails(storeOrgAgentData); return storeAgentDid; @@ -422,6 +538,7 @@ export class AgentServiceService { } } + private async _getAgentDid(payload: IStoreOrgAgentDetails): Promise { const { agentEndPoint, apiKey, seed, ledgerId, did } = payload; const writeDid = 'write-did'; @@ -449,7 +566,8 @@ export class AgentServiceService { agentId: payload.agentId, orgAgentTypeId, ledgerId: payload.ledgerId, - id: payload.id + id: payload.id, + apiKey: payload.apiKey }; } @@ -480,9 +598,9 @@ export class AgentServiceService { try { return retry(async () => { if (agentApiState === 'write-did') { - return this.commonService.httpPost(agentUrl, { seed, method: indyNamespace, did }, { headers: { 'x-api-key': apiKey } }); + return this.commonService.httpPost(agentUrl, { seed, method: indyNamespace, did }, { headers: { 'authorization': apiKey } }); } else if (agentApiState === 'get-did-doc') { - return this.commonService.httpGet(agentUrl, { headers: { 'x-api-key': apiKey } }); + return this.commonService.httpGet(agentUrl, { headers: { 'authorization': apiKey } }); } }, retryOptions); } catch (error) { @@ -490,7 +608,7 @@ export class AgentServiceService { } } - + async _createLegacyConnectionInvitation(orgId: string, user: IUserRequestInterface, label: string): Promise<{ response; }> { @@ -550,53 +668,109 @@ export class AgentServiceService { } } - async createTenant(payload: ITenantDto, user: IUserRequestInterface): Promise<{ - agentSpinupStatus: number; - }> { + /** + * Create tenant (Shared agent) + * @param payload + * @param user + * @returns Get agent status + */ + async createTenant(payload: ITenantDto, user: IUserRequestInterface): Promise { + try { - const agentStatusResponse = { - agentSpinupStatus: AgentSpinUpStatus.PROCESSED - }; + const agentStatusResponse = { + agentSpinupStatus: AgentSpinUpStatus.PROCESSED + }; - this._createTenant(payload, user); - return agentStatusResponse; + const getOrgAgent = await this.agentServiceRepository.getAgentDetails(payload.orgId); + + if (AgentSpinUpStatus.COMPLETED === getOrgAgent?.agentSpinUpStatus) { + this.logger.error(`Your wallet is already been created.`); + throw new ConflictException( + ResponseMessages.agent.error.walletAlreadyCreated, + { cause: new Error(), description: ResponseMessages.errorMessages.conflict } + ); + } + + if (AgentSpinUpStatus.PROCESSED === getOrgAgent?.agentSpinUpStatus) { + this.logger.error(`Your wallet is already processing.`); + throw new ConflictException( + ResponseMessages.agent.error.walletAlreadyProcessing, + { cause: new Error(), description: ResponseMessages.errorMessages.conflict } + ); + } + + // Create tenant + this._createTenant(payload, user); + return agentStatusResponse; + } catch (error) { + this.logger.error(`error in create tenant : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } } - async _createTenant(payload: ITenantDto, user: IUserRequestInterface): Promise { + /** + * Create tenant (Shared agent) + * @param payload + * @param user + * @returns Get agent status + */ + async _createTenant(payload: ITenantDto, user: IUserRequestInterface): Promise { let agentProcess; try { + + // Get orgaization agent details by orgId const getOrgAgent = await this.agentServiceRepository.getAgentDetails(payload.orgId); if (AgentSpinUpStatus.COMPLETED === getOrgAgent?.agentSpinUpStatus) { this.logger.error(`Your wallet has already been created.`); - throw new BadRequestException('Your wallet has already been created.'); + throw new BadRequestException( + ResponseMessages.agent.error.walletAlreadyCreated, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } if (AgentSpinUpStatus.PROCESSED === getOrgAgent?.agentSpinUpStatus) { this.logger.error(`Your wallet has already processing.`); - throw new BadRequestException('Your wallet has already processing.'); + throw new BadRequestException( + ResponseMessages.agent.error.walletAlreadyProcessing, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } + // Get ledgers details const ledgerIdData = await this.agentServiceRepository.getLedgerDetails(Ledgers.Indicio_Demonet); - const ledgerIds = ledgerIdData.map(ledger => ledger.id); + const ledgerIds = ledgerIdData.map(ledger => ledger?.id); payload.ledgerId = !payload.ledgerId || 0 === payload.ledgerId?.length ? ledgerIds : payload.ledgerId; const agentSpinUpStatus = AgentSpinUpStatus.PROCESSED; - agentProcess = await this.agentServiceRepository.createOrgAgent(agentSpinUpStatus, user.id); + // Create and stored agent details + agentProcess = await this.agentServiceRepository.createOrgAgent(agentSpinUpStatus, user?.id); + // Get platform admin details const platformAdminSpinnedUp = await this.getPlatformAdminAndNotify(payload.clientSocketId); + + // Get genesis URLs by ledger Id const ledgerDetails: ledgers[] = await this.agentServiceRepository.getGenesisUrl(payload.ledgerId); for (const iterator of ledgerDetails) { + + // Create tenant in agent controller const tenantDetails = await this.createTenantAndNotify(payload, iterator, platformAdminSpinnedUp); if (AgentSpinUpStatus.COMPLETED !== platformAdminSpinnedUp.org_agents[0].agentSpinUpStatus) { - throw new NotFoundException('Platform-admin agent is not spun-up'); + this.logger.error(`Platform-admin agent is not spun-up`); + throw new NotFoundException( + ResponseMessages.agent.error.platformAdminNotAbleToSpinp, + { cause: new Error(), description: ResponseMessages.errorMessages.notFound } + ); } + // Get org agent type details by shared agent const orgAgentTypeId = await this.agentServiceRepository.getOrgAgentTypeDetails(OrgAgentType.SHARED); + + // Get agent type details by AFJ agent const agentTypeId = await this.agentServiceRepository.getAgentTypeId(AgentType.AFJ); const storeOrgAgentData: IStoreOrgAgentDetails = { @@ -611,20 +785,21 @@ export class AgentServiceService { tenantId: tenantDetails['tenantRecord']['id'], walletName: payload.label, ledgerId: payload.ledgerId, - id: agentProcess.id + id: agentProcess?.id }; + // Get organization data const getOrganization = await this.agentServiceRepository.getOrgDetails(payload.orgId); this.notifyClientSocket('agent-spinup-process-completed', payload.clientSocketId); - const saveTenant = await this.agentServiceRepository.storeOrgAgentDetails(storeOrgAgentData); + await this.agentServiceRepository.storeOrgAgentDetails(storeOrgAgentData); this.notifyClientSocket('invitation-url-creation-started', payload.clientSocketId); + + // Create the legacy connection invitation this._createLegacyConnectionInvitation(payload.orgId, user, getOrganization.name); this.notifyClientSocket('invitation-url-creation-success', payload.clientSocketId); - - return saveTenant; } } catch (error) { this.handleError(error, payload.clientSocketId); @@ -632,12 +807,11 @@ export class AgentServiceService { if (agentProcess && agentProcess?.id) { this.agentServiceRepository.removeOrgAgent(agentProcess?.id); } - - throw new RpcException(error.response ? error.response : error); + throw error; } } - private async getPlatformAdminAndNotify(clientSocketId: string | undefined): Promise { + private async getPlatformAdminAndNotify(clientSocketId: string | undefined): Promise { const socket = await this.createSocketInstance(); if (clientSocketId) { socket.emit('agent-spinup-process-initiated', { clientId: clientSocketId }); @@ -646,13 +820,24 @@ export class AgentServiceService { const platformAdminSpinnedUp = await this.agentServiceRepository.platformAdminAgent(CommonConstants.PLATFORM_ADMIN_ORG); if (!platformAdminSpinnedUp) { - throw new InternalServerErrorException('Agent not able to spin-up'); + this.logger.error(`Agent not able to spin-up`); + throw new BadRequestException( + ResponseMessages.agent.error.notAbleToSpinp, + { cause: new Error(), description: ResponseMessages.errorMessages.serverError } + ); } return platformAdminSpinnedUp; } - private async createTenantAndNotify(payload: ITenantDto, ledgerIds: ledgers, platformAdminSpinnedUp: organisation & { org_agents: org_agents[] }): Promise { + /** + * Create tenant on the agent + * @param payload + * @param ledgerIds + * @param platformAdminSpinnedUp + * @returns Get tanant status + */ + private async createTenantAndNotify(payload: ITenantDto, ledgerIds: ledgers, platformAdminSpinnedUp: IOrgAgentsResponse): Promise { const socket = await this.createSocketInstance(); if (payload.clientSocketId) { socket.emit('agent-spinup-process-initiated', { clientId: payload.clientSocketId }); @@ -666,11 +851,12 @@ export class AgentServiceService { method: ledgerIds.indyNamespace }; - const apiKey = ''; + + // Invoke an API request from the agent to create multi-tenant agent const tenantDetails = await this.commonService.httpPost( `${platformAdminSpinnedUp.org_agents[0].agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_TENANT}`, createTenantOptions, - { headers: { 'x-api-key': apiKey } } + { headers: { 'authorization': platformAdminSpinnedUp.org_agents[0].apiKey } } ); this.logger.debug(`API Response Data: ${JSON.stringify(tenantDetails)}`); @@ -716,10 +902,16 @@ export class AgentServiceService { name: payload.name, issuerId: payload.issuerId }; - schemaResponse = await this.commonService.httpPost(url, schemaPayload, { headers: { 'x-api-key': payload.apiKey } }) + schemaResponse = await this.commonService.httpPost(url, schemaPayload, { headers: { 'authorization': payload.apiKey } }) .then(async (schema) => { this.logger.debug(`API Response Data: ${JSON.stringify(schema)}`); return schema; + }) + .catch(error => { + throw new InternalServerErrorException( + ResponseMessages.agent.error.agentDown, + { cause: new Error(), description: ResponseMessages.errorMessages.serverError } + ); }); } else if (OrgAgentType.SHARED === payload.agentType) { @@ -731,16 +923,22 @@ export class AgentServiceService { name: payload.payload.name, issuerId: payload.payload.issuerId }; - schemaResponse = await this.commonService.httpPost(url, schemaPayload, { headers: { 'x-api-key': payload.apiKey } }) + schemaResponse = await this.commonService.httpPost(url, schemaPayload, { headers: { 'authorization': payload.apiKey } }) .then(async (schema) => { this.logger.debug(`API Response Data: ${JSON.stringify(schema)}`); return schema; + }) + .catch(error => { + throw new InternalServerErrorException( + ResponseMessages.agent.error.agentDown, + { cause: new Error(), description: ResponseMessages.errorMessages.serverError } + ); }); } return schemaResponse; } catch (error) { this.logger.error(`Error in creating schema: ${error}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } @@ -759,7 +957,7 @@ export class AgentServiceService { } else if (OrgAgentType.SHARED === payload.agentType) { const url = `${payload.agentEndPoint}${CommonConstants.URL_SHAGENT_GET_SCHEMA}`.replace('@', `${payload.payload.schemaId}`).replace('#', `${payload.tenantId}`); - schemaResponse = await this.commonService.httpGet(url, { headers: { 'x-api-key': payload.apiKey } }) + schemaResponse = await this.commonService.httpGet(url, { headers: { 'authorization': payload.apiKey } }) .then(async (schema) => { this.logger.debug(`API Response Data: ${JSON.stringify(schema)}`); return schema; @@ -785,7 +983,7 @@ export class AgentServiceService { issuerId: payload.issuerId }; - credDefResponse = await this.commonService.httpPost(url, credDefPayload, { headers: { 'x-api-key': payload.apiKey } }) + credDefResponse = await this.commonService.httpPost(url, credDefPayload, { headers: { 'authorization': payload.apiKey } }) .then(async (credDef) => { this.logger.debug(`API Response Data: ${JSON.stringify(credDef)}`); return credDef; @@ -798,7 +996,7 @@ export class AgentServiceService { schemaId: payload.payload.schemaId, issuerId: payload.payload.issuerId }; - credDefResponse = await this.commonService.httpPost(url, credDefPayload, { headers: { 'x-api-key': payload.apiKey } }) + credDefResponse = await this.commonService.httpPost(url, credDefPayload, { headers: { 'authorization': payload.apiKey } }) .then(async (credDef) => { this.logger.debug(`API Response Data: ${JSON.stringify(credDef)}`); return credDef; @@ -812,7 +1010,7 @@ export class AgentServiceService { } } - async getCredentialDefinitionById(payload: GetCredDefAgentRedirection): Promise { + async getCredentialDefinitionById(payload: IGetCredDefAgentRedirection): Promise { try { let credDefResponse; @@ -826,7 +1024,7 @@ export class AgentServiceService { } else if (OrgAgentType.SHARED === payload.agentType) { const url = `${payload.agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CRED_DEF}`.replace('@', `${payload.payload.credentialDefinitionId}`).replace('#', `${payload.tenantId}`); - credDefResponse = await this.commonService.httpGet(url, { headers: { 'x-api-key': payload.apiKey } }) + credDefResponse = await this.commonService.httpGet(url, { headers: { 'authorization': payload.apiKey } }) .then(async (credDef) => { this.logger.debug(`API Response Data: ${JSON.stringify(credDef)}`); return credDef; @@ -839,11 +1037,12 @@ export class AgentServiceService { } } - async createLegacyConnectionInvitation(connectionPayload: IConnectionDetails, url: string, apiKey: string): Promise { + async createLegacyConnectionInvitation(connectionPayload: IConnectionDetails, url: string, apiKey: string): Promise { try { + const data = await this.commonService - .httpPost(url, connectionPayload, { headers: { 'x-api-key': apiKey } }) + .httpPost(url, connectionPayload, { headers: { 'authorization': apiKey } }) .then(async response => response); return data; @@ -856,7 +1055,7 @@ export class AgentServiceService { async sendCredentialCreateOffer(issueData: IIssuanceCreateOffer, url: string, apiKey: string): Promise { try { const data = await this.commonService - .httpPost(url, issueData, { headers: { 'x-api-key': apiKey } }) + .httpPost(url, issueData, { headers: { 'authorization': apiKey } }) .then(async response => response); return data; } catch (error) { @@ -867,7 +1066,7 @@ export class AgentServiceService { async getProofPresentations(url: string, apiKey: string): Promise { try { const getProofPresentationsData = await this.commonService - .httpGet(url, { headers: { 'x-api-key': apiKey } }) + .httpGet(url, { headers: { 'authorization': apiKey } }) .then(async response => response); return getProofPresentationsData; @@ -880,7 +1079,7 @@ export class AgentServiceService { async getIssueCredentials(url: string, apiKey: string): Promise { try { const data = await this.commonService - .httpGet(url, { headers: { 'x-api-key': apiKey } }) + .httpGet(url, { headers: { 'authorization': apiKey } }) .then(async response => response); return data; } catch (error) { @@ -891,7 +1090,7 @@ export class AgentServiceService { async getProofPresentationById(url: string, apiKey: string): Promise { try { const getProofPresentationById = await this.commonService - .httpGet(url, { headers: { 'x-api-key': apiKey } }) + .httpGet(url, { headers: { 'authorization': apiKey } }) .then(async response => response); return getProofPresentationById; } catch (error) { @@ -903,7 +1102,7 @@ export class AgentServiceService { async getIssueCredentialsbyCredentialRecordId(url: string, apiKey: string): Promise { try { const data = await this.commonService - .httpGet(url, { headers: { 'x-api-key': apiKey } }) + .httpGet(url, { headers: { 'authorization': apiKey } }) .then(async response => response); return data; } catch (error) { @@ -915,7 +1114,7 @@ export class AgentServiceService { async sendProofRequest(proofRequestPayload: ISendProofRequestPayload, url: string, apiKey: string): Promise { try { const sendProofRequest = await this.commonService - .httpPost(url, proofRequestPayload, { headers: { 'x-api-key': apiKey } }) + .httpPost(url, proofRequestPayload, { headers: { 'authorization': apiKey } }) .then(async response => response); return sendProofRequest; } catch (error) { @@ -927,7 +1126,7 @@ export class AgentServiceService { async verifyPresentation(url: string, apiKey: string): Promise { try { const verifyPresentation = await this.commonService - .httpPost(url, '', { headers: { 'x-api-key': apiKey } }) + .httpPost(url, '', { headers: { 'authorization': apiKey } }) .then(async response => response); return verifyPresentation; } catch (error) { @@ -939,7 +1138,7 @@ export class AgentServiceService { async getConnections(url: string, apiKey: string): Promise { try { const data = await this.commonService - .httpGet(url, { headers: { 'x-api-key': apiKey } }) + .httpGet(url, { headers: { 'authorization': apiKey } }) .then(async response => response); return data; } catch (error) { @@ -948,45 +1147,87 @@ export class AgentServiceService { } } - async getConnectionsByconnectionId(url: string, apiKey: string): Promise { + async getConnectionsByconnectionId(url: string, apiKey: string): Promise { + try { const data = await this.commonService .httpGet(url, { headers: { 'x-api-key': apiKey } }) - .then(async response => response); - + .then(async response => response) + .catch(error => { + this.logger.error(`Error in getConnectionsByconnectionId in agent service : ${JSON.stringify(error)}`); + + if (error && Object.keys(error).length === 0) { + throw new InternalServerErrorException( + ResponseMessages.agent.error.agentDown, + { cause: new Error(), description: ResponseMessages.errorMessages.serverError } + ); + } else { + throw error; + } + }); return data; } catch (error) { this.logger.error(`Error in getConnectionsByconnectionId in agent service : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } + } - async getAgentHealthDetails(orgId: string): Promise { + /** + * Get agent health + * @param orgId + * @returns agent status + */ + async getAgentHealthDetails(orgId: string): Promise { try { + + // Get organization agent details const orgAgentDetails: org_agents = await this.agentServiceRepository.getOrgAgentDetails(orgId); + let agentApiKey; + if (orgAgentDetails) { + + agentApiKey = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + if (!agentApiKey || null === agentApiKey || undefined === agentApiKey) { + agentApiKey = await this.getOrgAgentApiKey(orgId); + } + + if (agentApiKey === undefined || null) { + agentApiKey = await this.getOrgAgentApiKey(orgId); + } + } + if (!orgAgentDetails) { - throw new NotFoundException(ResponseMessages.agent.error.agentNotExists); + throw new NotFoundException( + ResponseMessages.agent.error.agentNotExists, + { cause: new Error(), description: ResponseMessages.errorMessages.notFound } + ); } - if (orgAgentDetails.agentEndPoint) { - const data = await this.commonService - .httpGet(`${orgAgentDetails.agentEndPoint}/agent`, { headers: { 'x-api-key': '' } }) - .then(async response => response); - return data; - } else { - throw new NotFoundException(ResponseMessages.agent.error.agentUrl); + + if (!orgAgentDetails?.agentEndPoint) { + throw new NotFoundException( + ResponseMessages.agent.error.agentUrl, + { cause: new Error(), description: ResponseMessages.errorMessages.notFound } + ); } + // Invoke an API request from the agent to assess its current status + const agentHealthData = await this.commonService + .httpGet(`${orgAgentDetails.agentEndPoint}${CommonConstants.URL_AGENT_STATUS}`, { headers: { 'authorization': agentApiKey } }) + .then(async response => response); + + return agentHealthData; + } catch (error) { this.logger.error(`Agent health details : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } async sendOutOfBandProofRequest(proofRequestPayload: ISendProofRequestPayload, url: string, apiKey: string): Promise { try { const sendProofRequest = await this.commonService - .httpPost(url, proofRequestPayload, { headers: { 'x-api-key': apiKey } }) + .httpPost(url, proofRequestPayload, { headers: { 'authorization': apiKey } }) .then(async response => response); return sendProofRequest; } catch (error) { @@ -998,7 +1239,7 @@ export class AgentServiceService { async getProofFormData(url: string, apiKey: string): Promise { try { const getProofFormData = await this.commonService - .httpGet(url, { headers: { 'x-api-key': apiKey } }) + .httpGet(url, { headers: { 'authorization': apiKey } }) .then(async response => response); return getProofFormData; } catch (error) { @@ -1010,7 +1251,7 @@ export class AgentServiceService { async schemaEndorsementRequest(url: string, apiKey: string, requestSchemaPayload: object): Promise { try { const schemaRequest = await this.commonService - .httpPost(url, requestSchemaPayload, { headers: { 'x-api-key': apiKey } }) + .httpPost(url, requestSchemaPayload, { headers: { 'authorization': apiKey } }) .then(async response => response); return schemaRequest; } catch (error) { @@ -1022,7 +1263,7 @@ export class AgentServiceService { async credDefEndorsementRequest(url: string, apiKey: string, requestSchemaPayload: object): Promise { try { const credDefRequest = await this.commonService - .httpPost(url, requestSchemaPayload, { headers: { 'x-api-key': apiKey } }) + .httpPost(url, requestSchemaPayload, { headers: { 'authorization': apiKey } }) .then(async response => response); return credDefRequest; } catch (error) { @@ -1034,7 +1275,7 @@ export class AgentServiceService { async signTransaction(url: string, apiKey: string, signEndorsementPayload: object): Promise { try { const signEndorsementTransaction = await this.commonService - .httpPost(url, signEndorsementPayload, { headers: { 'x-api-key': apiKey } }) + .httpPost(url, signEndorsementPayload, { headers: { 'authorization': apiKey } }) .then(async response => response); return signEndorsementTransaction; @@ -1048,7 +1289,7 @@ export class AgentServiceService { try { const signEndorsementTransaction = await this.commonService - .httpPost(url, submitEndorsementPayload, { headers: { 'x-api-key': apiKey } }) + .httpPost(url, submitEndorsementPayload, { headers: { 'authorization': apiKey } }) .then(async response => response); return signEndorsementTransaction; @@ -1058,10 +1299,10 @@ export class AgentServiceService { } } - async outOfBandCredentialOffer(outOfBandIssuancePayload: OutOfBandCredentialOffer, url: string, apiKey: string): Promise { + async outOfBandCredentialOffer(outOfBandIssuancePayload: IOutOfBandCredentialOffer, url: string, apiKey: string): Promise { try { const sendOutOfbandCredentialOffer = await this.commonService - .httpPost(url, outOfBandIssuancePayload, { headers: { 'x-api-key': apiKey } }) + .httpPost(url, outOfBandIssuancePayload, { headers: { 'authorization': apiKey } }) .then(async response => response); return sendOutOfbandCredentialOffer; } catch (error) { @@ -1076,7 +1317,7 @@ export class AgentServiceService { ): Promise { try { const deleteWallet = await this.commonService - .httpDelete(url, { headers: { 'x-api-key': apiKey } }) + .httpDelete(url, { headers: { 'authorization': apiKey } }) .then(async response => response); return deleteWallet; } catch (error) { @@ -1084,5 +1325,37 @@ export class AgentServiceService { throw new RpcException(error); } } + + async getOrgAgentApiKey(orgId: string): Promise { + try { + let agentApiKey; + const orgAgentApiKey = await this.agentServiceRepository.getAgentApiKey(orgId); + + const orgAgentId = await this.agentServiceRepository.getOrgAgentTypeDetails(OrgAgentType.SHARED); + if (orgAgentApiKey?.orgAgentTypeId === orgAgentId) { + const platformAdminSpinnedUp = await this.agentServiceRepository.platformAdminAgent(CommonConstants.PLATFORM_ADMIN_ORG); + + const [orgAgentData] = platformAdminSpinnedUp.org_agents; + const { apiKey } = orgAgentData; + if (!platformAdminSpinnedUp) { + throw new InternalServerErrorException('Agent not able to spin-up'); + } + + agentApiKey = apiKey; + } else { + agentApiKey = orgAgentApiKey?.apiKey; + } + + if (!agentApiKey) { + throw new NotFoundException(ResponseMessages.agent.error.apiKeyNotExist); + } + await this.cacheService.set(CommonConstants.CACHE_APIKEY_KEY, agentApiKey, CommonConstants.CACHE_TTL_SECONDS); + return agentApiKey; + + } catch (error) { + this.logger.error(`Agent api key details : ${JSON.stringify(error)}`); + throw error; + } + } } diff --git a/apps/agent-service/src/interface/agent-service.interface.ts b/apps/agent-service/src/interface/agent-service.interface.ts index 32cb5e82a..38debe437 100644 --- a/apps/agent-service/src/interface/agent-service.interface.ts +++ b/apps/agent-service/src/interface/agent-service.interface.ts @@ -15,11 +15,12 @@ export interface IAgentSpinupDto { tenant?: boolean; ledgerName?: string[]; platformAdminEmail?: string; + apiKey?:string; } -export interface OutOfBandCredentialOffer { +export interface IOutOfBandCredentialOffer { emailId: string; - attributes: Attributes[]; + attributes: IAttributes[]; credentialDefinitionId: string; comment: string; protocolVersion?: string; @@ -57,17 +58,17 @@ export interface ITenantSchemaDto { issuerId: string; } -export interface GetSchemaAgentRedirection { +export interface IGetSchemaAgentRedirection { schemaId?: string; tenantId?: string; - payload?: GetSchemaFromTenantPayload; + payload?: IGetSchemaFromTenantPayload; apiKey?: string; agentEndPoint?: string; agentType?: string; method?: string; } -export interface GetSchemaFromTenantPayload { +export interface IGetSchemaFromTenantPayload { schemaId: string; } @@ -89,17 +90,17 @@ export interface ITenantCredDefDto { issuerId: string; } -export interface GetCredDefAgentRedirection { +export interface IGetCredDefAgentRedirection { credentialDefinitionId?: string; tenantId?: string; - payload?: GetCredDefFromTenantPayload; + payload?: IGetCredDefFromTenantPayload; apiKey?: string; agentEndPoint?: string; agentType?: string; method?: string; } -export interface GetCredDefFromTenantPayload { +export interface IGetCredDefFromTenantPayload { credentialDefinitionId: string; } @@ -122,6 +123,7 @@ export interface IWalletProvision { afjVersion: string; protocol: string; tenant: boolean; + apiKey?:string; } export interface IPlatformConfigDto { @@ -220,17 +222,17 @@ export interface ITenantCredDefDto { issuerId: string; } -export interface GetCredDefAgentRedirection { +export interface IGetCredDefAgentRedirection { credentialDefinitionId?: string; tenantId?: string; - payload?: GetCredDefFromTenantPayload; + payload?: IGetCredDefFromTenantPayload; apiKey?: string; agentEndPoint?: string; agentType?: string; method?: string; } -export interface GetCredDefFromTenantPayload { +export interface IGetCredDefFromTenantPayload { credentialDefinitionId: string; } @@ -247,10 +249,10 @@ export interface ICredentialFormats { } export interface IIndy { - attributes: Attributes[]; + attributes: IAttributes[]; } -export interface Attributes { +export interface IAttributes { name: string; value: string; } @@ -261,6 +263,12 @@ export interface ISendProofRequestPayload { autoAcceptProof: string; } +export interface IAgentStatus { + label: string; + endpoints: string[]; + isInitialized: boolean; +} + interface IProofFormats { indy: IndyProof } @@ -292,4 +300,61 @@ interface IRequestedPredicatesName { interface IRequestedRestriction { cred_def_id: string; +} + +export interface IAgentSpinUpSatus { + agentSpinupStatus: number; +} + +interface IWalletConfig { + id: string; + key: string; + keyDerivationMethod: string; +} + +interface IConfig { + label: string; + walletConfig: IWalletConfig; +} + +interface ITenantRecord { + _tags: string; + metadata: string; + id: string; + createdAt: string; + config: IConfig; + updatedAt: string; +} + +export interface ICreateTenant { + tenantRecord: ITenantRecord; + did: string; + verkey: string; +} + +export interface IOrgAgent { + agentSpinUpStatus: number; +} + +export interface IOrgLedgers { + id: string; +} + +export interface ICreateOrgAgent { + id: string; +} + +interface IOrgAgentEndPoint { + agentSpinUpStatus: number; + agentEndPoint: string; + apiKey; +} + +export interface IOrgAgentsResponse { + org_agents: IOrgAgentEndPoint[]; +} + + +export interface IStoreAgent { + id: string; } \ No newline at end of file diff --git a/apps/agent-service/src/repositories/agent-service.repository.ts b/apps/agent-service/src/repositories/agent-service.repository.ts index 8d96907a0..a19074a2a 100644 --- a/apps/agent-service/src/repositories/agent-service.repository.ts +++ b/apps/agent-service/src/repositories/agent-service.repository.ts @@ -1,24 +1,25 @@ import { PrismaService } from '@credebl/prisma-service'; import { Injectable, Logger } from '@nestjs/common'; // eslint-disable-next-line camelcase -import { Prisma, ledgers, org_agents, organisation, platform_config, user } from '@prisma/client'; -import { IStoreOrgAgentDetails } from '../interface/agent-service.interface'; +import { ledgers, org_agents, organisation, platform_config, user } from '@prisma/client'; +import { ICreateOrgAgent, IStoreOrgAgentDetails, IOrgAgent, IOrgAgentsResponse, IOrgLedgers, IStoreAgent } from '../interface/agent-service.interface'; import { AgentType } from '@credebl/enum/enum'; @Injectable() export class AgentServiceRepository { - constructor(private readonly prisma: PrismaService, private readonly logger: Logger) { } + constructor( + private readonly prisma: PrismaService, + private readonly logger: Logger + ) { } /** * Get platform config details - * @returns + * @returns */ // eslint-disable-next-line camelcase async getPlatformConfigDetails(): Promise { try { - return this.prisma.platform_config.findFirst(); - } catch (error) { this.logger.error(`[getPlatformConfigDetails] - error: ${JSON.stringify(error)}`); throw error; @@ -27,12 +28,11 @@ export class AgentServiceRepository { /** * Get genesis url - * @param id - * @returns + * @param id + * @returns */ async getGenesisUrl(ledgerId: string[]): Promise { try { - const genesisData = await this.prisma.ledgers.findMany({ where: { id: { @@ -50,18 +50,19 @@ export class AgentServiceRepository { /** * Get organization details - * @param id - * @returns + * @param id + * @returns */ async getOrgDetails(id: string): Promise { try { - - const oranizationDetails = await this.prisma.organisation.findFirst({ - where: { - id - } - }); - return oranizationDetails; + if (id) { + const oranizationDetails = await this.prisma.organisation.findUnique({ + where: { + id + } + }); + return oranizationDetails; + } } catch (error) { this.logger.error(`[getOrgDetails] - get organization details: ${JSON.stringify(error)}`); throw error; @@ -69,7 +70,7 @@ export class AgentServiceRepository { } // eslint-disable-next-line camelcase - async createOrgAgent(agentSpinUpStatus: number, userId: string): Promise { + async createOrgAgent(agentSpinUpStatus: number, userId: string): Promise { try { return this.prisma.org_agents.create({ @@ -77,6 +78,9 @@ export class AgentServiceRepository { agentSpinUpStatus, createdBy: userId, lastChangedBy: userId + }, + select: { + id: true } }); } catch (error) { @@ -86,27 +90,30 @@ export class AgentServiceRepository { } // eslint-disable-next-line camelcase - async removeOrgAgent(id: string): Promise { + async removeOrgAgent(id: string): Promise { try { + if (id) { - return this.prisma.org_agents.delete({ - where: { - id - } - }); + await this.prisma.org_agents.delete({ + where: { + id + } + }); + } } catch (error) { this.logger.error(`[removeOrgAgent] - remove org agent details: ${JSON.stringify(error)}`); throw error; } + } /** * Store agent details - * @param storeAgentDetails - * @returns + * @param storeAgentDetails + * @returns */ // eslint-disable-next-line camelcase - async storeOrgAgentDetails(storeOrgAgentDetails: IStoreOrgAgentDetails): Promise { + async storeOrgAgentDetails(storeOrgAgentDetails: IStoreOrgAgentDetails): Promise { try { return this.prisma.org_agents.update({ @@ -126,6 +133,9 @@ export class AgentServiceRepository { orgAgentTypeId: storeOrgAgentDetails.orgAgentTypeId ? storeOrgAgentDetails.orgAgentTypeId : null, tenantId: storeOrgAgentDetails.tenantId ? storeOrgAgentDetails.tenantId : null, ledgerId: storeOrgAgentDetails.ledgerId[0] + }, + select: { + id: true } }); } catch (error) { @@ -136,65 +146,53 @@ export class AgentServiceRepository { /** * Get agent details - * @param orgId - * @returns + * @param orgId + * @returns */ // eslint-disable-next-line camelcase - async getAgentDetails(orgId: string): Promise { + async getAgentDetails(orgId: string): Promise { try { - const x = await this.prisma.org_agents.findFirst({ - where: { - orgId - } - }); + if (orgId) { - return x; + return this.prisma.org_agents.findUnique({ + where: { + orgId + }, + select: { + agentSpinUpStatus: true + } + }); + } } catch (error) { - this.logger.error(`[getAgentDetails] - get agent details: ${JSON.stringify(error)}`); throw error; } } - // eslint-disable-next-line camelcase - async platformAdminAgent(platformOrg: string): Promise { - const platformAdminSpinnedUp = await this.prisma.organisation.findFirst({ + // eslint-disable-next-line camelcase + async platformAdminAgent(platformOrg: string): Promise { + return this.prisma.organisation.findFirstOrThrow({ where: { name: platformOrg }, - include: { + select: { // eslint-disable-next-line camelcase - org_agents: true + org_agents: { + select: { + agentSpinUpStatus: true, + agentEndPoint: true, + apiKey: true + } + } } }); - return platformAdminSpinnedUp; - } - - /** - * Get agent details - * @param orgId - * @returns Agent health details - */ - // eslint-disable-next-line camelcase - async getOrgAgentDetails(orgId: string): Promise { - try { - const oranizationAgentDetails = await this.prisma.org_agents.findFirst({ - where: { - orgId - } - }); - return oranizationAgentDetails; - } catch (error) { - this.logger.error(`[getOrgAgentDetails] - get org agent health details: ${JSON.stringify(error)}`); - throw error; - } } async getAgentTypeDetails(): Promise { try { - const { id } = await this.prisma.agents_type.findFirst({ + const { id } = await this.prisma.agents_type.findFirstOrThrow({ where: { agent: AgentType.AFJ } @@ -206,19 +204,7 @@ export class AgentServiceRepository { } } - async getLedgerDetails(name: string[] | string): Promise<{ - id: string; - createDateTime: Date; - lastChangedDateTime: Date; - name: string; - networkType: string; - poolConfig: string; - isActive: boolean; - networkString: string; - registerDIDEndpoint: string; - registerDIDPayload: Prisma.JsonValue; - indyNamespace: string; - }[]> { + async getLedgerDetails(name: string[] | string): Promise { try { let whereClause; @@ -235,7 +221,10 @@ export class AgentServiceRepository { } const ledgersDetails = await this.prisma.ledgers.findMany({ - where: whereClause + where: whereClause, + select: { + id: true + } }); return ledgersDetails; } catch (error) { @@ -246,7 +235,7 @@ export class AgentServiceRepository { async getOrgAgentTypeDetails(agentType: string): Promise { try { - const { id } = await this.prisma.org_agents_type.findFirst({ + const { id } = await this.prisma.org_agents_type.findFirstOrThrow({ where: { agent: agentType } @@ -260,7 +249,7 @@ export class AgentServiceRepository { async getPlatfomOrg(orgName: string): Promise { try { - const { id } = await this.prisma.organisation.findFirst({ + const { id } = await this.prisma.organisation.findFirstOrThrow({ where: { name: orgName } @@ -272,45 +261,86 @@ export class AgentServiceRepository { } } - async getPlatfomAdminUser(platformAdminUserEmail: string): Promise { + async getAgentType(id: string): Promise { try { - const platformAdminUser = await this.prisma.user.findUnique({ + const { agent } = await this.prisma.agents_type.findUnique({ where: { - email: platformAdminUserEmail + id } }); - return platformAdminUser; + return agent; } catch (error) { - this.logger.error(`[getPlatfomAdminUser] - get platform admin user: ${JSON.stringify(error)}`); + this.logger.error(`[getAgentType] - get agent type details: ${JSON.stringify(error)}`); throw error; } } - async getAgentType(id: string): Promise { + async getAgentTypeId(agentType: string): Promise { try { - const { agent } = await this.prisma.agents_type.findUnique({ + const { id } = await this.prisma.agents_type.findFirstOrThrow({ where: { - id + agent: agentType } }); - return agent; + return id; } catch (error) { this.logger.error(`[getAgentType] - get agent type details: ${JSON.stringify(error)}`); throw error; } } - async getAgentTypeId(agentType: string): Promise { + /** + * Get agent details + * @param orgId + * @returns Agent health details + */ + // eslint-disable-next-line camelcase + async getOrgAgentDetails(orgId: string): Promise { try { - const { id } = await this.prisma.agents_type.findFirst({ + if (orgId) { + + const oranizationAgentDetails = await this.prisma.org_agents.findUnique({ + where: { + orgId + } + }); + return oranizationAgentDetails; + } + } catch (error) { + this.logger.error(`[getOrgAgentDetails] - get org agent health details: ${JSON.stringify(error)}`); + throw error; + } + } + + async getPlatfomAdminUser(platformAdminUserEmail: string): Promise { + try { + const platformAdminUser = await this.prisma.user.findUnique({ where: { - agent: agentType + email: platformAdminUserEmail } }); - return id; + return platformAdminUser; } catch (error) { - this.logger.error(`[getAgentType] - get agent type details: ${JSON.stringify(error)}`); + this.logger.error(`[getPlatfomAdminUser] - get platform admin user: ${JSON.stringify(error)}`); throw error; } } -} \ No newline at end of file + + // eslint-disable-next-line camelcase + async getAgentApiKey(orgId: string): Promise { + try { + if (orgId) { + const agent = await this.prisma.org_agents.findUnique({ + where: { + orgId + } + }); + return agent; + } + + } catch (error) { + this.logger.error(`[getAgentApiKey] - get api key: ${JSON.stringify(error)}`); + throw error; + } + } +} diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index b8a21f30a..df5928a20 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -1,6 +1,7 @@ import { Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common'; import { BaseExceptionFilter } from '@nestjs/core'; -import { isArray } from 'class-validator'; +import { ExceptionResponse } from './interface'; +import { ResponseMessages } from '@credebl/common/response-messages'; @Catch() export class CustomExceptionFilter extends BaseExceptionFilter { @@ -8,93 +9,41 @@ export class CustomExceptionFilter extends BaseExceptionFilter { catch(exception: HttpException, host: ArgumentsHost): void { const ctx = host.switchToHttp(); const response = ctx.getResponse(); - + + let errorResponse; let status = HttpStatus.INTERNAL_SERVER_ERROR; - if (exception instanceof HttpException) { - status = exception.getStatus(); - } - + this.logger.error(`exception ::: ${JSON.stringify(exception)}`); - if ("Cannot read properties of undefined (reading 'response')" === exception.message) { - exception.message = 'Oops! Something went wrong. Please try again'; - } - - let errorResponse; - if (isArray(exception)) { + if (!exception || '{}' === JSON.stringify(exception)) { errorResponse = { statusCode: status, - message: exception[0], - error: exception[0] - }; - } else if (exception && exception['statusCode'] === HttpStatus.INTERNAL_SERVER_ERROR) { - if (exception.message && exception.message['message']) { - errorResponse = { - statusCode: status, - message: exception.message['message'], - error: exception.message['message'] - }; - } else { - errorResponse = { - statusCode: status, - message: 'Oops! Something went wrong. Please try again', - error: 'Oops! Something went wrong. Please try again' - }; - } - } else if ( - exception && - exception['error'] && - exception['error'].message && - (exception['error'].statusCode || exception['error'].code) - ) { - const statusCode = exception['error'].statusCode || exception['error'].code || status; - errorResponse = { - statusCode, - message: exception['error'].message || 'Internal server error', - error: exception['error'].message || 'Internal server error' - }; - } else if (exception && exception['statusCode'] === undefined && status === HttpStatus.INTERNAL_SERVER_ERROR) { - errorResponse = { - statusCode: status, - message: 'Oops! Something went wrong. Please try again', - error: 'Oops! Something went wrong. Please try again' + message: 'Something went wrong!', + error: ResponseMessages.errorMessages.serverError }; + } + if (exception instanceof HttpException) { + status = exception.getStatus(); + } + + let exceptionResponse: ExceptionResponse; + + if (exception['response']) { + exceptionResponse = exception['response']; } else { - if (exception && exception['response'] && exception.message) { - if (Array.isArray(exception['response'].message)) { - errorResponse = { - statusCode: exception['statusCode'] ? exception['statusCode'] : status, - message: exception.message ? exception.message : 'Internal server error', - error: exception['response'].message - ? exception['response'].message - : exception['response'] - ? exception['response'] - : 'Internal server error' - }; - } else { - errorResponse = { - statusCode: exception['statusCode'] ? exception['statusCode'] : status, - message: exception['response'].message - ? exception['response'].message - : exception['response'] - ? exception['response'] - : 'Internal server error', - error: exception['response'].message - ? exception['response'].message - : exception['response'] - ? exception['response'] - : 'Internal server error' - }; - } - } else if (exception && exception.message) { - errorResponse = { - statusCode: exception['statusCode'] ? exception['statusCode'] : status, - message: exception.message || 'Internal server error', - error: exception.message || 'Internal server error' - }; - } + exceptionResponse = exception as unknown as ExceptionResponse; } + errorResponse = { + statusCode: exceptionResponse.statusCode ? exceptionResponse.statusCode : status, + message: exceptionResponse.message + ? exceptionResponse.message + : 'Something went wrong!', + error: exceptionResponse.error + ? exceptionResponse.error + : ResponseMessages.errorMessages.serverError + }; + response.status(errorResponse.statusCode).json(errorResponse); } } \ No newline at end of file diff --git a/apps/api-gateway/common/interface.ts b/apps/api-gateway/common/interface.ts index a64429f45..d3869d75f 100644 --- a/apps/api-gateway/common/interface.ts +++ b/apps/api-gateway/common/interface.ts @@ -4,3 +4,9 @@ export interface ResponseType { data?: Record | string; error?: Record | string; } + +export interface ExceptionResponse { + message: string | string[] + error: string + statusCode: number +} diff --git a/apps/api-gateway/src/agent-service/agent-service.controller.ts b/apps/api-gateway/src/agent-service/agent-service.controller.ts index dcb33d9fb..c9e50bc59 100644 --- a/apps/api-gateway/src/agent-service/agent-service.controller.ts +++ b/apps/api-gateway/src/agent-service/agent-service.controller.ts @@ -21,7 +21,7 @@ import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; import { ResponseMessages } from '@credebl/common/response-messages'; import { AgentService } from './agent-service.service'; -import IResponseType from '@credebl/common/interfaces/response.interface'; +import IResponseType, { IResponse } from '@credebl/common/interfaces/response.interface'; import { AgentSpinupDto } from './dto/agent-service.dto'; import { Response } from 'express'; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -44,19 +44,31 @@ export class AgentController { constructor(private readonly agentService: AgentService) { } private readonly logger = new Logger(); + /** + * Get Organization agent health + * @param orgId + * @param reqUser + * @param res + * @returns Get agent details + */ @Get('/orgs/:orgId/agents/health') @ApiOperation({ summary: 'Get the agent health details', description: 'Get the agent health details' }) @UseGuards(AuthGuard('jwt')) - async getAgentHealth(@User() reqUser: user, @Param('orgId') orgId: string, @Res() res: Response): Promise { + async getAgentHealth( + @Param('orgId') orgId: string, + @User() reqUser: user, + @Res() res: Response + ): Promise { + const agentData = await this.agentService.getAgentHealth(reqUser, orgId); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.agent.success.health, - data: agentData.response + data: agentData }; return res.status(HttpStatus.OK).json(finalResponse); @@ -64,10 +76,10 @@ export class AgentController { } /** - * + * Spinup the agent by organization * @param agentSpinupDto * @param user - * @returns + * @returns Get agent status */ @Post('/orgs/:orgId/agents/spinup') @ApiOperation({ @@ -78,37 +90,53 @@ export class AgentController { @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) async agentSpinup( - @Body() agentSpinupDto: AgentSpinupDto, @Param('orgId') orgId: string, + @Body() agentSpinupDto: AgentSpinupDto, @User() user: user, @Res() res: Response - ): Promise>> { + ): Promise { if (seedLength !== agentSpinupDto.seed.length) { - throw new BadRequestException(`seed must be at most 32 characters.`); + this.logger.error(`seed must be at most 32 characters.`); + throw new BadRequestException( + ResponseMessages.agent.error.seedChar, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } const regex = new RegExp('^[a-zA-Z0-9]+$'); if (!regex.test(agentSpinupDto.walletName)) { - this.logger.error(`Wallet name in wrong format.`); - throw new BadRequestException(`Please enter valid wallet name, It allows only alphanumeric values`); + this.logger.error(`Please enter valid wallet name, It allows only alphanumeric values`); + throw new BadRequestException( + ResponseMessages.agent.error.seedChar, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } + this.logger.log(`**** Spin up the agent...${JSON.stringify(agentSpinupDto)}`); agentSpinupDto.orgId = orgId; const agentDetails = await this.agentService.agentSpinup(agentSpinupDto, user); - + const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, message: ResponseMessages.agent.success.create, - data: agentDetails.response + data: agentDetails }; return res.status(HttpStatus.CREATED).json(finalResponse); } + /** + * Create wallet for shared agent + * @param orgId + * @param createTenantDto + * @param user + * @param res + * @returns wallet initialization status + */ @Post('/orgs/:orgId/agents/wallet') @ApiOperation({ summary: 'Shared Agent', @@ -122,20 +150,24 @@ export class AgentController { @Body() createTenantDto: CreateTenantDto, @User() user: user, @Res() res: Response - ): Promise { + ): Promise { createTenantDto.orgId = orgId; if (seedLength !== createTenantDto.seed.length) { - throw new BadRequestException(`seed must be at most 32 characters.`); + this.logger.error(`seed must be at most 32 characters`); + throw new BadRequestException( + ResponseMessages.agent.error.seedCharCount, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } const tenantDetails = await this.agentService.createTenant(createTenantDto, user); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, message: ResponseMessages.agent.success.create, - data: tenantDetails.response + data: tenantDetails }; return res.status(HttpStatus.CREATED).json(finalResponse); diff --git a/apps/api-gateway/src/agent-service/agent-service.service.ts b/apps/api-gateway/src/agent-service/agent-service.service.ts index 2ede160b7..417512bed 100644 --- a/apps/api-gateway/src/agent-service/agent-service.service.ts +++ b/apps/api-gateway/src/agent-service/agent-service.service.ts @@ -4,6 +4,8 @@ import { user } from '@prisma/client'; import { BaseService } from 'libs/service/base.service'; import { AgentSpinupDto } from './dto/agent-service.dto'; import { CreateTenantDto } from './dto/create-tenant.dto'; +import { AgentSpinUpSatus } from './interface/agent-service.interface'; +import { AgentStatus } from './interface/agent-service.interface'; @Injectable() export class AgentService extends BaseService { @@ -13,19 +15,32 @@ export class AgentService extends BaseService { super('AgentService'); } - async agentSpinup(agentSpinupDto: AgentSpinupDto, user: user): Promise<{ response: object }> { + /** + * Spinup the agent by organization + * @param agentSpinupDto + * @param user + * @returns Get agent status + */ + async agentSpinup(agentSpinupDto: AgentSpinupDto, user: user): Promise { const payload = { agentSpinupDto, user }; - return this.sendNats(this.agentServiceProxy, 'agent-spinup', payload); + + // NATS call + return this.sendNatsMessage(this.agentServiceProxy, 'agent-spinup', payload); } - async createTenant(createTenantDto: CreateTenantDto, user: user): Promise<{ response: object }> { + async createTenant(createTenantDto: CreateTenantDto, user: user): Promise { const payload = { createTenantDto, user }; - return this.sendNats(this.agentServiceProxy, 'create-tenant', payload); + + // NATS call + return this.sendNatsMessage(this.agentServiceProxy, 'create-tenant', payload); } - async getAgentHealth(user: user, orgId:string): Promise<{ response: object }> { + async getAgentHealth(user: user, orgId:string): Promise { const payload = { user, orgId }; - return this.sendNats(this.agentServiceProxy, 'agent-health', payload); + + // NATS call + return this.sendNatsMessage(this.agentServiceProxy, 'agent-health', payload); + } } diff --git a/apps/api-gateway/src/agent-service/dto/agent-service.dto.ts b/apps/api-gateway/src/agent-service/dto/agent-service.dto.ts index 4fd6f1447..a668a0025 100644 --- a/apps/api-gateway/src/agent-service/dto/agent-service.dto.ts +++ b/apps/api-gateway/src/agent-service/dto/agent-service.dto.ts @@ -5,8 +5,6 @@ import { IsBoolean, IsNotEmpty, IsOptional, IsString, Matches, MaxLength, MinLen const regex = /^[a-zA-Z0-9 ]*$/; export class AgentSpinupDto { - orgId: string; - @ApiProperty() @Transform(({ value }) => trim(value)) @IsNotEmpty({ message: 'walletName is required' }) @@ -21,10 +19,11 @@ export class AgentSpinupDto { @ApiProperty() @Transform(({ value }) => trim(value)) + @IsString({ message: 'walletPassword must be in string format.' }) @IsNotEmpty({ message: 'Password is required.' }) walletPassword: string; - @ApiProperty() + @ApiProperty({ example: 'dfuhgfklskmjngrjekjfgjjfkoekfdad' }) @Transform(({ value }) => trim(value)) @IsNotEmpty({ message: 'seed is required' }) @MaxLength(32, { message: 'seed must be at most 32 characters.' }) @@ -34,45 +33,31 @@ export class AgentSpinupDto { }) seed: string; - @ApiProperty() + @ApiProperty({ example: 'XzFjo1RTZ2h9UVFCnPUyaQ' }) @IsOptional() + @ApiPropertyOptional() @IsString({ message: 'did must be in string format.' }) did?: string; - @ApiProperty({ example: [1] }) + @ApiProperty({ example: ['6ba7b810-9dad-11d1-80b4-00c04fd430c8'] }) @IsOptional() + @ApiPropertyOptional() @IsArray({ message: 'ledgerId must be an array' }) + @IsString({ each: true, message: 'Each ledgerId must be a string' }) + @MaxLength(36, { each: true, message: 'ledgerId must be at most 36 characters.' }) @IsNotEmpty({ message: 'please provide valid ledgerId' }) ledgerId?: string[]; - @ApiProperty() + @ApiProperty({ example: 'ojIckSD2jqNzOqIrAGzL' }) @IsOptional() @ApiPropertyOptional() clientSocketId?: string; - @ApiProperty() + @ApiProperty({ example: true }) @IsOptional() @IsBoolean() @ApiPropertyOptional() tenant?: boolean; - @ApiProperty() - @IsOptional() - @ApiPropertyOptional() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'agentType is required' }) - @MinLength(2, { message: 'agentType must be at least 2 characters.' }) - @MaxLength(50, { message: 'agentType must be at most 50 characters.' }) - @IsString({ message: 'agentType must be in string format.' }) - agentType?: string; - - @ApiProperty() - @IsOptional() - @ApiPropertyOptional() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'transactionApproval is required' }) - @MinLength(2, { message: 'transactionApproval must be at least 2 characters.' }) - @MaxLength(50, { message: 'transactionApproval must be at most 50 characters.' }) - @IsString({ message: 'transactionApproval must be in string format.' }) - transactionApproval?: string; + orgId: string; } \ No newline at end of file diff --git a/apps/api-gateway/src/agent-service/dto/create-tenant.dto.ts b/apps/api-gateway/src/agent-service/dto/create-tenant.dto.ts index 34938eef9..9204d6370 100644 --- a/apps/api-gateway/src/agent-service/dto/create-tenant.dto.ts +++ b/apps/api-gateway/src/agent-service/dto/create-tenant.dto.ts @@ -7,7 +7,7 @@ export class CreateTenantDto { @ApiProperty() @MaxLength(25, { message: 'Maximum length for label must be 25 characters.' }) @IsString({ message: 'label must be in string format.' }) - @Transform(({ value }) => value.trim()) + @Transform(({ value }) => trim(value)) @MinLength(2, { message: 'Minimum length for label must be 2 characters.' }) @Matches(labelRegex, { message: 'Label must not contain special characters.' }) @Matches(/^\S*$/, { @@ -15,7 +15,7 @@ export class CreateTenantDto { }) label: string; - @ApiProperty() + @ApiProperty({ example: 'dfuhgfklskmjngrjekjfgjjfkoekfdad' }) @MaxLength(32, { message: 'seed must be at most 32 characters.' }) @Transform(({ value }) => trim(value)) @IsNotEmpty({ message: 'seed is required' }) @@ -26,20 +26,23 @@ export class CreateTenantDto { seed: string; @ApiProperty({ example: [1] }) + @ApiPropertyOptional() @IsOptional() @IsArray({ message: 'ledgerId must be an array' }) @IsNotEmpty({ message: 'please provide valid ledgerId' }) ledgerId?: string[]; - orgId: string; - - @ApiProperty() + @ApiProperty({ example: 'XzFjo1RTZ2h9UVFCnPUyaQ' }) @IsOptional() + @ApiPropertyOptional() @IsString({ message: 'did must be in string format.' }) did?: string; - @ApiProperty() + @ApiProperty({ example: 'ojIckSD2jqNzOqIrAGzL' }) @IsOptional() @ApiPropertyOptional() + @IsString({ message: 'did must be in string format.' }) clientSocketId?: string; + + orgId: string; } \ No newline at end of file diff --git a/apps/api-gateway/src/agent-service/interface/agent-service.interface.ts b/apps/api-gateway/src/agent-service/interface/agent-service.interface.ts new file mode 100644 index 000000000..6ced1ed42 --- /dev/null +++ b/apps/api-gateway/src/agent-service/interface/agent-service.interface.ts @@ -0,0 +1,8 @@ +export interface AgentSpinUpSatus { + agentSpinupStatus: number; +} +export interface AgentStatus { + label: string; + endpoints: string[]; + isInitialized: boolean; +} \ No newline at end of file diff --git a/apps/api-gateway/src/authz/authz.controller.ts b/apps/api-gateway/src/authz/authz.controller.ts index 3e35edf4f..fa8307c13 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -21,7 +21,7 @@ import { Response } from 'express'; import { EmailVerificationDto } from '../user/dto/email-verify.dto'; import { AuthTokenResponse } from './dtos/auth-token-res.dto'; import { LoginUserDto } from '../user/dto/login-user.dto'; -import { AddUserDetails } from '../user/dto/add-user.dto'; +import { AddUserDetailsDto } from '../user/dto/add-user.dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @@ -35,13 +35,12 @@ export class AuthzController { private readonly commonService: CommonService) { } /** - * - * @param query - * @param res - * @returns User email verified + * @param email + * @param verificationcode + * @returns User's email verification status */ @Get('/verify') - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Verify user’s email', description: 'Verify user’s email' }) async verifyEmail(@Query() query: EmailVerificationDto, @Res() res: Response): Promise { await this.authzService.verifyEmail(query); @@ -55,16 +54,14 @@ export class AuthzController { } /** - * * @param email - * @param res - * @returns Email sent success + * @returns User's verification email sent status */ @Post('/verification-mail') - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) @ApiOperation({ summary: 'Send verification email', description: 'Send verification email to new user' }) - async create(@Body() userEmailVerificationDto: UserEmailVerificationDto, @Res() res: Response): Promise { - await this.authzService.sendVerificationMail(userEmailVerificationDto); + async create(@Body() userEmailVerification: UserEmailVerificationDto, @Res() res: Response): Promise { + await this.authzService.sendVerificationMail(userEmailVerification); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, message: ResponseMessages.user.success.sendVerificationCode @@ -74,37 +71,30 @@ export class AuthzController { /** * - * @param email - * @param userInfo - * @param res - * @returns Add new user + * @Body userInfo + * @returns User's registration status */ @Post('/signup') @ApiOperation({ summary: 'Register new user to platform', description: 'Register new user to platform' }) - async addUserDetails(@Body() userInfo: AddUserDetails, @Res() res: Response): Promise { - const userDetails = await this.authzService.addUserDetails(userInfo); + async addUserDetails(@Body() userInfo: AddUserDetailsDto, @Res() res: Response): Promise { + await this.authzService.addUserDetails(userInfo); const finalResponse = { statusCode: HttpStatus.CREATED, - message: ResponseMessages.user.success.create, - data: userDetails.response + message: ResponseMessages.user.success.create }; return res.status(HttpStatus.CREATED).json(finalResponse); } - - /** - * - * @param loginUserDto - * @param res - * @returns User access token details + * @Body loginUserDto + * @returns User's access token details */ @Post('/signin') @ApiOperation({ summary: 'Authenticate the user for the access', description: 'Authenticate the user for the access' }) - @ApiResponse({ status: 200, description: 'Success', type: AuthTokenResponse }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: AuthTokenResponse }) @ApiBody({ type: LoginUserDto }) async login(@Body() loginUserDto: LoginUserDto, @Res() res: Response): Promise { @@ -113,7 +103,7 @@ export class AuthzController { const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.user.success.login, - data: userData.response + data: userData }; return res.status(HttpStatus.OK).json(finalResponse); diff --git a/apps/api-gateway/src/authz/authz.service.ts b/apps/api-gateway/src/authz/authz.service.ts index 317712669..413d942cb 100644 --- a/apps/api-gateway/src/authz/authz.service.ts +++ b/apps/api-gateway/src/authz/authz.service.ts @@ -8,8 +8,8 @@ import { } from '@nestjs/websockets'; import { UserEmailVerificationDto } from '../user/dto/create-user.dto'; import { EmailVerificationDto } from '../user/dto/email-verify.dto'; -import { AddUserDetails } from '../user/dto/add-user.dto'; - +import { AddUserDetailsDto } from '../user/dto/add-user.dto'; +import { ISendVerificationEmail, ISignInUser, IVerifyUserEmail } from '@credebl/common/interfaces/user.interface'; @Injectable() @WebSocketGateway() @@ -28,24 +28,23 @@ export class AuthzService extends BaseService { return this.sendNats(this.authServiceProxy, 'get-user-by-keycloakUserId', keycloakUserId); } - async sendVerificationMail(userEmailVerificationDto: UserEmailVerificationDto): Promise { - const payload = { userEmailVerificationDto }; - return this.sendNats(this.authServiceProxy, 'send-verification-mail', payload); + async sendVerificationMail(userEmailVerification: UserEmailVerificationDto): Promise { + const payload = { userEmailVerification }; + return this.sendNatsMessage(this.authServiceProxy, 'send-verification-mail', payload); } - async verifyEmail(param: EmailVerificationDto): Promise { + async verifyEmail(param: EmailVerificationDto): Promise { const payload = { param }; - return this.sendNats(this.authServiceProxy, 'user-email-verification', payload); - + return this.sendNatsMessage(this.authServiceProxy, 'user-email-verification', payload); } - async login(email: string, password?: string, isPasskey = false): Promise<{ response: object }> { + async login(email: string, password?: string, isPasskey = false): Promise { const payload = { email, password, isPasskey }; - return this.sendNats(this.authServiceProxy, 'user-holder-login', payload); + return this.sendNatsMessage(this.authServiceProxy, 'user-holder-login', payload); } - async addUserDetails(userInfo: AddUserDetails): Promise<{ response: string }> { + async addUserDetails(userInfo: AddUserDetailsDto): Promise { const payload = { userInfo }; - return this.sendNats(this.authServiceProxy, 'add-user', payload); + return this.sendNatsMessage(this.authServiceProxy, 'add-user', payload); } } \ No newline at end of file diff --git a/apps/api-gateway/src/authz/guards/org-roles.guard.ts b/apps/api-gateway/src/authz/guards/org-roles.guard.ts index 803a7a229..4520f046b 100644 --- a/apps/api-gateway/src/authz/guards/org-roles.guard.ts +++ b/apps/api-gateway/src/authz/guards/org-roles.guard.ts @@ -1,4 +1,4 @@ -import { CanActivate, ExecutionContext, Logger } from '@nestjs/common'; +import { CanActivate, ExecutionContext, ForbiddenException, Logger } from '@nestjs/common'; import { HttpException } from '@nestjs/common'; import { HttpStatus } from '@nestjs/common'; @@ -6,6 +6,7 @@ import { Injectable } from '@nestjs/common'; import { OrgRoles } from 'libs/org-roles/enums'; import { ROLES_KEY } from '../decorators/roles.decorator'; import { Reflector } from '@nestjs/core'; +import { ResponseMessages } from '@credebl/common/response-messages'; @Injectable() export class OrgRolesGuard implements CanActivate { @@ -40,7 +41,7 @@ export class OrgRolesGuard implements CanActivate { }); if (!specificOrg) { - throw new HttpException('Organization does not match', HttpStatus.FORBIDDEN); + throw new ForbiddenException(ResponseMessages.organisation.error.orgNotMatch, { cause: new Error(), description: ResponseMessages.errorMessages.forbidden }); } user.selectedOrg = specificOrg; diff --git a/apps/api-gateway/src/authz/jwt.strategy.ts b/apps/api-gateway/src/authz/jwt.strategy.ts index c435799d2..e4dcdae29 100644 --- a/apps/api-gateway/src/authz/jwt.strategy.ts +++ b/apps/api-gateway/src/authz/jwt.strategy.ts @@ -27,12 +27,12 @@ export class JwtStrategy extends PassportStrategy(Strategy) { const userDetails = await this.usersService.findUserinSupabase(payload.sub); - if (!userDetails.response) { + if (!userDetails) { throw new NotFoundException('User not found'); } return { - ...userDetails.response, + ...userDetails, ...payload }; } diff --git a/apps/api-gateway/src/connection/connection.controller.ts b/apps/api-gateway/src/connection/connection.controller.ts index 850355101..366ad88a5 100644 --- a/apps/api-gateway/src/connection/connection.controller.ts +++ b/apps/api-gateway/src/connection/connection.controller.ts @@ -1,10 +1,9 @@ -import IResponseType from '@credebl/common/interfaces/response.interface'; +import {IResponse} from '@credebl/common/interfaces/response.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; import { Controller, Logger, Post, Body, UseGuards, HttpStatus, Res, Get, Param, UseFilters, Query } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ApiBearerAuth, ApiExcludeEndpoint, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { User } from '../authz/decorators/user.decorator'; -import { AuthTokenResponse } from '../authz/dtos/auth-token-res.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ConnectionService } from './connection.service'; @@ -17,14 +16,16 @@ 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 { IConnectionSearchinterface } from '../interfaces/ISchemaSearch.interface'; +import { ApiResponseDto } from '../dtos/apiResponse.dto'; +import { IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface'; +import { SortFields } from 'apps/connection/src/enum/connection.enum'; @UseFilters(CustomExceptionFilter) @Controller() @ApiTags('connections') @ApiBearerAuth() -@ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) -@ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) +@ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) export class ConnectionController { private readonly logger = new Logger('Connection'); @@ -32,11 +33,10 @@ export class ConnectionController { ) { } /** - * Description: Get connection by connectionId - * @param user + * Get connection details by connectionId * @param connectionId * @param orgId - * + * @returns connection details by connection Id */ @Get('orgs/:orgId/connections/:connectionId') @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @@ -45,7 +45,7 @@ export class ConnectionController { summary: `Get connections by connection Id`, description: `Get connections by connection Id` }) - @ApiResponse({ status: 200, description: 'Success', type: AuthTokenResponse }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async getConnectionsById( @User() user: IUserRequest, @Param('connectionId') connectionId: string, @@ -53,11 +53,10 @@ export class ConnectionController { @Res() res: Response ): Promise { const connectionsDetails = await this.connectionService.getConnectionsById(user, connectionId, orgId); - - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.OK, - message: ResponseMessages.connection.success.fetch, - data: connectionsDetails.response + message: ResponseMessages.connection.success.fetchConnection, + data: connectionsDetails }; return res.status(HttpStatus.OK).json(finalResponse); } @@ -72,56 +71,36 @@ export class ConnectionController { @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @ApiOperation({ - summary: `Fetch all connection details`, - description: `Fetch all connection details` + summary: `Fetch all connections by orgId`, + description: `Fetch all connections by orgId` }) @ApiQuery({ - name: 'pageNumber', - type: Number, - required: false - }) - @ApiQuery({ - name: 'searchByText', - type: String, - required: false - }) - @ApiQuery({ - name: 'pageSize', - type: Number, - required: false - }) - @ApiQuery({ - name: 'sorting', - type: String, - required: false - }) - @ApiQuery({ - name: 'sortByValue', - type: String, + name: 'sortField', + enum: SortFields, required: false - }) - @ApiResponse({ status: 200, description: 'Success', type: AuthTokenResponse }) + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async getConnections( @Query() getAllConnectionsDto: GetAllConnectionsDto, @User() user: IUserRequest, @Param('orgId') orgId: string, @Res() res: Response ): Promise { - - const { pageSize, searchByText, pageNumber, sorting, sortByValue } = getAllConnectionsDto; - const connectionSearchCriteria: IConnectionSearchinterface = { + + const { pageSize, searchByText, pageNumber, sortField, sortBy } = getAllConnectionsDto; + const connectionSearchCriteria: IConnectionSearchCriteria = { pageNumber, searchByText, pageSize, - sorting, - sortByValue + sortField, + sortBy }; const connectionDetails = await this.connectionService.getConnections(connectionSearchCriteria, user, orgId); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.connection.success.fetch, - data: connectionDetails.response + data: connectionDetails }; return res.status(HttpStatus.OK).json(finalResponse); } @@ -136,7 +115,7 @@ export class ConnectionController { @ApiOperation({ summary: 'Create outbound out-of-band connection (Legacy Invitation)', description: 'Create outbound out-of-band connection (Legacy Invitation)' }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) - @ApiResponse({ status: 201, description: 'Success', type: AuthTokenResponse }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) async createLegacyConnectionInvitation( @Param('orgId') orgId: string, @Body() connectionDto: CreateConnectionDto, @@ -146,38 +125,37 @@ export class ConnectionController { connectionDto.orgId = orgId; const connectionData = await this.connectionService.createLegacyConnectionInvitation(connectionDto, reqUser); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, message: ResponseMessages.connection.success.create, - data: connectionData.response + data: connectionData }; return res.status(HttpStatus.CREATED).json(finalResponse); } - /** * Catch connection webhook responses. * @Body connectionDto - * @param id - * @param res + * @param orgId + * @returns Callback URL for connection and created connections details */ - @Post('wh/:id/connections/') + @Post('wh/:orgId/connections/') @ApiExcludeEndpoint() @ApiOperation({ summary: 'Catch connection webhook responses', description: 'Callback URL for connection' }) - @ApiResponse({ status: 200, description: 'Success', type: AuthTokenResponse }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) async getConnectionWebhook( @Body() connectionDto: ConnectionDto, - @Param('id') id: string, + @Param('orgId') orgId: string, @Res() res: Response - ): Promise { - this.logger.debug(`connectionDto ::: ${JSON.stringify(connectionDto)} ${id}`); - const connectionData = await this.connectionService.getConnectionWebhook(connectionDto, id); - const finalResponse: IResponseType = { + ): Promise { + this.logger.debug(`connectionDto ::: ${JSON.stringify(connectionDto)} ${orgId}`); + const connectionData = await this.connectionService.getConnectionWebhook(connectionDto, orgId); + const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, message: ResponseMessages.connection.success.create, data: connectionData diff --git a/apps/api-gateway/src/connection/connection.service.ts b/apps/api-gateway/src/connection/connection.service.ts index 214a5c013..5bc8162d0 100644 --- a/apps/api-gateway/src/connection/connection.service.ts +++ b/apps/api-gateway/src/connection/connection.service.ts @@ -4,7 +4,8 @@ import { ClientProxy, RpcException } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { ConnectionDto, CreateConnectionDto } from './dtos/connection.dto'; import { IUserRequestInterface } from './interfaces'; -import { IConnectionSearchinterface } from '../interfaces/ISchemaSearch.interface'; +import { IConnectionList, ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface'; +import { IConnectionDetailsById, IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface'; @Injectable() export class ConnectionService extends BaseService { @@ -15,9 +16,7 @@ export class ConnectionService extends BaseService { createLegacyConnectionInvitation( connectionDto: CreateConnectionDto, user: IUserRequestInterface - ): Promise<{ - response: object; - }> { + ): Promise { try { const connectionDetails = { orgId: connectionDto.orgId, @@ -29,7 +28,7 @@ export class ConnectionService extends BaseService { user }; - return this.sendNats(this.connectionServiceProxy, 'create-connection', connectionDetails); + return this.sendNatsMessage(this.connectionServiceProxy, 'create-connection', connectionDetails); } catch (error) { throw new RpcException(error.response); } @@ -37,12 +36,10 @@ export class ConnectionService extends BaseService { getConnectionWebhook( connectionDto: ConnectionDto, - id: string - ): Promise<{ - response: object; - }> { - const payload = { connectionDto, orgId: id }; - return this.sendNats(this.connectionServiceProxy, 'webhook-get-connection', payload); + orgId: string + ): Promise { + const payload = { connectionDto, orgId }; + return this.sendNatsMessage(this.connectionServiceProxy, 'webhook-get-connection', payload); } getUrl(referenceId: string): Promise<{ @@ -57,24 +54,20 @@ export class ConnectionService extends BaseService { } getConnections( - connectionSearchCriteria: IConnectionSearchinterface, + connectionSearchCriteria: IConnectionSearchCriteria, user: IUserRequest, orgId: string - ): Promise<{ - response: object; - }> { + ): Promise { const payload = { connectionSearchCriteria, user, orgId }; - return this.sendNats(this.connectionServiceProxy, 'get-all-connections', payload); + return this.sendNatsMessage(this.connectionServiceProxy, 'get-all-connections', payload); } getConnectionsById( user: IUserRequest, connectionId: string, orgId: string - ): Promise<{ - response: object; - }> { + ): Promise { const payload = { user, connectionId, orgId }; - return this.sendNats(this.connectionServiceProxy, 'get-all-connections-by-connectionId', payload); + return this.sendNatsMessage(this.connectionServiceProxy, 'get-connection-details-by-connectionId', payload); } } diff --git a/apps/api-gateway/src/connection/dtos/connection.dto.ts b/apps/api-gateway/src/connection/dtos/connection.dto.ts index fe225792b..4122d6755 100644 --- a/apps/api-gateway/src/connection/dtos/connection.dto.ts +++ b/apps/api-gateway/src/connection/dtos/connection.dto.ts @@ -1,100 +1,90 @@ import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; +import { ApiPropertyOptional } from '@nestjs/swagger'; export class CreateConnectionDto { - @ApiProperty() + @ApiPropertyOptional() @IsOptional() - @IsString({ message: 'alias must be a string' }) @IsNotEmpty({ message: 'please provide valid alias' }) + @IsString({ message: 'alias must be a string' }) + @IsNotEmpty({ message: 'please provide valid alias' }) alias: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() - @IsString({ message: 'label must be a string' }) @IsNotEmpty({ message: 'please provide valid label' }) + @IsString({ message: 'label must be a string' }) + @IsNotEmpty({ message: 'please provide valid label' }) label: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() @IsNotEmpty({ message: 'please provide valid imageUrl' }) imageUrl: string; - @ApiProperty() + @ApiPropertyOptional() @IsBoolean() @IsOptional() @IsNotEmpty({ message: 'please provide multiUseInvitation' }) multiUseInvitation: boolean; - @ApiProperty() + @ApiPropertyOptional() @IsBoolean() @IsOptional() - @IsNotEmpty({ message: 'autoAcceptConnection should boolean' }) + @IsNotEmpty({ message: 'autoAcceptConnection should be boolean' }) autoAcceptConnection: boolean; orgId: string; } export class ConnectionDto { - @ApiProperty() - @IsOptional() - _tags?: object; - - @ApiProperty() - @IsOptional() - metadata: object; - - @ApiProperty() - @IsOptional() - connectionTypes: object[]; - - @ApiProperty() + @ApiPropertyOptional() @IsOptional() id: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() createdAt: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() did: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() theirDid: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() theirLabel: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() state: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() role: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() autoAcceptConnection: boolean; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() threadId: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() protocol: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() outOfBandId: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() updatedAt: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() contextCorrelationId: string; } \ No newline at end of file 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 5f02fc395..553a8a5a2 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,27 +1,41 @@ import { ApiProperty } from "@nestjs/swagger"; -import { Type } from "class-transformer"; -import { IsOptional } from "class-validator"; +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 }) - @IsOptional() - @Type(() => String) - searchByText: string = ''; - - @ApiProperty({ required: false }) + + @ApiProperty({ required: false, example: '1' }) @IsOptional() pageNumber: number = 1; - @ApiProperty({ required: false }) + @ApiProperty({ required: false, example: '10' }) @IsOptional() pageSize: number = 10; @ApiProperty({ required: false }) @IsOptional() - sorting: string = 'id'; + @Transform(({ value }) => trim(value)) + @Type(() => String) + searchByText: string = ''; - @ApiProperty({ required: false }) + @ApiProperty({ + required: false + }) + @Transform(({ value }) => trim(value)) @IsOptional() - sortByValue: string = SortValue.DESC; + @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; } + diff --git a/apps/api-gateway/src/dtos/create-schema.dto.ts b/apps/api-gateway/src/dtos/create-schema.dto.ts index c776de70e..7b83fbd9f 100644 --- a/apps/api-gateway/src/dtos/create-schema.dto.ts +++ b/apps/api-gateway/src/dtos/create-schema.dto.ts @@ -1,36 +1,45 @@ -import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Transform, Type } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; class AttributeValue { + @ApiProperty() @IsString() - @IsNotEmpty({ message: 'attributeName is required.' }) - @Transform(({ value }) => value.trim()) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'attributeName is required' }) attributeName: string; + @ApiProperty() @IsString() - @IsNotEmpty({ message: 'schemaDataType is required.' }) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'schemaDataType is required' }) schemaDataType: string; + @ApiProperty() @IsString() - @IsNotEmpty({ message: 'displayName is required.' }) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'displayName is required' }) displayName: string; } export class CreateSchemaDto { @ApiProperty() - @IsString({ message: 'schema version must be a string' }) - @IsNotEmpty({ message: 'please provide valid schema version' }) + @IsString({ message: 'schemaVersion must be a string' }) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'schemaVersion is required' }) schemaVersion: string; @ApiProperty() - @IsString({ message: 'schema name must be a string' }) - @IsNotEmpty({ message: 'please provide valid schema name' }) + @IsString({ message: 'schemaName must be a string' }) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'schemaName is required' }) schemaName: string; @ApiProperty({ + type: [AttributeValue], 'example': [ { attributeName: 'name', @@ -40,13 +49,17 @@ export class CreateSchemaDto { ] }) @IsArray({ message: 'attributes must be an array' }) - @IsNotEmpty({ message: 'please provide valid attributes' }) + @IsNotEmpty({ message: 'attributes are required' }) + @ValidateNested({ each: true }) + @Type(() => AttributeValue) attributes: AttributeValue[]; orgId: string; - @ApiProperty() + @ApiPropertyOptional() + @Transform(({ value }) => trim(value)) @IsOptional() + @IsNotEmpty({ message: 'orgDid should not be empty' }) @IsString({ message: 'orgDid must be a string' }) orgDid: string; } diff --git a/apps/api-gateway/src/dtos/email-validator.dto.ts b/apps/api-gateway/src/dtos/email-validator.dto.ts index c41ad0ef5..802e062e0 100644 --- a/apps/api-gateway/src/dtos/email-validator.dto.ts +++ b/apps/api-gateway/src/dtos/email-validator.dto.ts @@ -5,8 +5,9 @@ import { IsEmail, IsNotEmpty, MaxLength } from 'class-validator'; export class EmailValidator { @ApiProperty() - @IsNotEmpty({ message: 'Email is required.' }) - @MaxLength(256, { message: 'Email must be at most 256 character.' }) + @IsEmail({}, { message: 'Please provide a valid email' }) + @IsNotEmpty({ message: 'Email is required' }) + @MaxLength(256, { message: 'Email must be at most 256 character' }) @IsEmail() email: string; } \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index b993b7997..77d8d8555 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -370,7 +370,7 @@ export class EcosystemController { @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) async SumbitEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: string, @Res() res: Response): Promise { - await this.ecosystemService.submitTransaction(endorsementId, ecosystemId); + await this.ecosystemService.submitTransaction(endorsementId, ecosystemId, orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, message: ResponseMessages.ecosystem.success.submit diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 5db865d82..db5555393 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -34,7 +34,7 @@ export class EcosystemService extends BaseService { * @param editEcosystemDto * @returns Ecosystem creation success */ - async editEcosystem(editEcosystemDto: EditEcosystemDto, ecosystemId:string): Promise { + async editEcosystem(editEcosystemDto: EditEcosystemDto, ecosystemId: string): Promise { const payload = { editEcosystemDto, ecosystemId }; return this.sendNats(this.serviceProxy, 'edit-ecosystem', payload); } @@ -59,16 +59,19 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'get-ecosystem-dashboard-details', payload); } - /** - * - * @param bulkInvitationDto - * @param userId - * @returns + * + * @param bulkInvitationDto + * @param userId + * @returns */ - async createInvitation(bulkInvitationDto: BulkEcosystemInvitationDto, userId: string, userEmail: string): Promise { + async createInvitation( + bulkInvitationDto: BulkEcosystemInvitationDto, + userId: string, + userEmail: string + ): Promise { const payload = { bulkInvitationDto, userId, userEmail }; - + return this.sendNats(this.serviceProxy, 'send-ecosystem-invitation', payload); } @@ -109,10 +112,7 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'get-ecosystem-invitations', payload); } - - async deleteEcosystemInvitations( - invitationId: string - ): Promise { + async deleteEcosystemInvitations(invitationId: string): Promise { const payload = { invitationId }; return this.sendNats(this.serviceProxy, 'delete-ecosystem-invitations', payload); } @@ -124,11 +124,7 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'accept-reject-ecosystem-invitations', payload); } - - async fetchEcosystemOrg( - ecosystemId: string, - orgId: string - ): Promise<{ response: object }> { + async fetchEcosystemOrg(ecosystemId: string, orgId: string): Promise<{ response: object }> { const payload = { ecosystemId, orgId }; return this.sendNats(this.serviceProxy, 'fetch-ecosystem-org-data', payload); } @@ -153,13 +149,20 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'get-all-ecosystem-schemas', payload); } - - async schemaEndorsementRequest(requestSchemaPayload: RequestSchemaDto, orgId: string, ecosystemId: string): Promise { + async schemaEndorsementRequest( + requestSchemaPayload: RequestSchemaDto, + orgId: string, + ecosystemId: string + ): Promise { const payload = { requestSchemaPayload, orgId, ecosystemId }; return this.sendNats(this.serviceProxy, 'schema-endorsement-request', payload); } - async credDefEndorsementRequest(requestCredDefPayload: RequestCredDefDto, orgId: string, ecosystemId: string): Promise { + async credDefEndorsementRequest( + requestCredDefPayload: RequestCredDefDto, + orgId: string, + ecosystemId: string + ): Promise { const payload = { requestCredDefPayload, orgId, ecosystemId }; return this.sendNats(this.serviceProxy, 'credDef-endorsement-request', payload); } @@ -169,8 +172,8 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'sign-endorsement-transaction', payload); } - async submitTransaction(endorsementId: string, ecosystemId: string): Promise { - const payload = { endorsementId, ecosystemId }; + async submitTransaction(endorsementId: string, ecosystemId: string, orgId: string): Promise { + const payload = { endorsementId, ecosystemId, orgId }; return this.sendNats(this.serviceProxy, 'sumbit-endorsement-transaction', payload); } @@ -187,5 +190,4 @@ export class EcosystemService extends BaseService { const payload = { ecosystemId, endorsementId, orgId }; return this.sendNats(this.serviceProxy, 'decline-endorsement-transaction', payload); } - } diff --git a/apps/api-gateway/src/interfaces/IConnectionSearch.interface.ts b/apps/api-gateway/src/interfaces/IConnectionSearch.interface.ts new file mode 100644 index 000000000..35d79949d --- /dev/null +++ b/apps/api-gateway/src/interfaces/IConnectionSearch.interface.ts @@ -0,0 +1,25 @@ +import { IUserRequestInterface } from './IUserRequestInterface'; + +export interface IConnectionSearchCriteria { + 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; + } diff --git a/apps/api-gateway/src/interfaces/ISchemaSearch.interface.ts b/apps/api-gateway/src/interfaces/ISchemaSearch.interface.ts index 13e0c77e6..2f83d26cb 100644 --- a/apps/api-gateway/src/interfaces/ISchemaSearch.interface.ts +++ b/apps/api-gateway/src/interfaces/ISchemaSearch.interface.ts @@ -1,27 +1,12 @@ import { IUserRequestInterface } from '../schema/interfaces'; -export interface ISchemaSearchInterface { +export interface ISchemaSearchPayload { ledgerId?: string; pageNumber: number; pageSize: number; - sorting: string; - sortByValue: string; - searchByText: string; - user?: IUserRequestInterface -} - -export interface ICredDeffSchemaSearchInterface { - pageNumber: number; - pageSize: number; - sorting: string; - sortByValue: string; - user?: IUserRequestInterface -} -export interface IConnectionSearchinterface { - pageNumber: number; - pageSize: number; - sorting: string; - sortByValue: string; - searchByText: string; + sortField: string; + sortBy: string; + searchByText?: string; user?: IUserRequestInterface } + diff --git a/apps/api-gateway/src/issuance/dtos/get-all-issued-credentials.dto.ts b/apps/api-gateway/src/issuance/dtos/get-all-issued-credentials.dto.ts index 1112de27a..9e07e5ecf 100644 --- a/apps/api-gateway/src/issuance/dtos/get-all-issued-credentials.dto.ts +++ b/apps/api-gateway/src/issuance/dtos/get-all-issued-credentials.dto.ts @@ -1,27 +1,39 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { Type } from "class-transformer"; -import { IsOptional } from "class-validator"; -import { SortValue } from "../../enum"; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform, Type } from 'class-transformer'; +import { IsEnum, IsInt, IsOptional, IsString } from 'class-validator'; +import { SortValue } from '../../enum'; +import { trim } from '@credebl/common/cast.helper'; +import { SortFields } from 'apps/issuance/enum/issuance.enum'; -export class GetAllIssuedCredentialsDto { - @ApiProperty({ required: false }) - @IsOptional() - pageNumber: number = 1; +export class IGetAllIssuedCredentialsDto { + @ApiProperty({ required: false, default: 1 }) + @IsOptional() + @Type(() => Number) + @IsInt({ message: 'Page Number should be a number' }) + pageNumber: number = 1; - @ApiProperty({ required: false }) - @IsOptional() - pageSize: number = 10; + @ApiProperty({ required: false, default: 10 }) + @IsOptional() + @Type(() => Number) + @IsInt({ message: 'Page size should be a number' }) + pageSize: number; - @ApiProperty({ required: false }) - @IsOptional() - @Type(() => String) - searchByText: string = ''; + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => String) + @IsString({ message: 'Search text should be a string' }) + @Transform(({ value }) => trim(value)) + searchByText: string; - @ApiProperty({ required: false }) - @IsOptional() - sorting: string = 'id'; + @ApiProperty({ required: false, enum: SortFields }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsEnum(SortFields) + sortField: string = SortFields.CREATED_DATE_TIME; - @ApiProperty({ required: false }) - @IsOptional() - sortByValue: string = SortValue.DESC; + @ApiProperty({ required: false, enum: SortValue }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsEnum(SortValue) + sortBy: string = SortValue.DESC; } diff --git a/apps/api-gateway/src/issuance/interfaces/index.ts b/apps/api-gateway/src/issuance/interfaces/index.ts index 1b3eb5bf5..834a33b4d 100644 --- a/apps/api-gateway/src/issuance/interfaces/index.ts +++ b/apps/api-gateway/src/issuance/interfaces/index.ts @@ -67,11 +67,11 @@ export interface RequestPayload { fileKey: string; fileName: string; } -export interface IIssuedCredentialSearchinterface { +export interface IIssuedCredentialSearchParams { pageNumber: number; pageSize: number; - sorting: string; - sortByValue: string; + sortField: string; + sortBy: string; searchByText: string; } diff --git a/apps/api-gateway/src/issuance/issuance.controller.ts b/apps/api-gateway/src/issuance/issuance.controller.ts index 774915802..727461254 100644 --- a/apps/api-gateway/src/issuance/issuance.controller.ts +++ b/apps/api-gateway/src/issuance/issuance.controller.ts @@ -36,7 +36,7 @@ import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; import { CommonService } from '@credebl/common/common.service'; import { Response } from 'express'; -import IResponseType from '@credebl/common/interfaces/response.interface'; +import IResponseType, { IResponse } from '@credebl/common/interfaces/response.interface'; import { IssuanceService } from './issuance.service'; import { ClientDetails, @@ -54,14 +54,14 @@ import { OrgRoles } from 'libs/org-roles/enums'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; import { ImageServiceService } from '@credebl/image-service'; -import { FileExportResponse, IIssuedCredentialSearchinterface, RequestPayload } from './interfaces'; +import { FileExportResponse, IIssuedCredentialSearchParams, RequestPayload } from './interfaces'; import { AwsService } from '@credebl/aws'; import { FileInterceptor } from '@nestjs/platform-express'; import { v4 as uuidv4 } from 'uuid'; import { RpcException } from '@nestjs/microservices'; /* eslint-disable @typescript-eslint/no-unused-vars */ import { user } from '@prisma/client'; -import { GetAllIssuedCredentialsDto } from './dtos/get-all-issued-credentials.dto'; +import { IGetAllIssuedCredentialsDto } from './dtos/get-all-issued-credentials.dto'; @Controller() @UseFilters(CustomExceptionFilter) @@ -79,87 +79,61 @@ export class IssuanceController { private readonly PAGE: number = 1; /** - * Description: Get all issued credentials - * @param user * @param orgId - * + * @returns List of issued credentials for a specific organization */ - @Get('/orgs/:orgId/credentials') - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiOperation({ - summary: `Get all issued credentials for a specific organization`, - description: `Get all issued credentials for a specific organization` - }) - @ApiQuery({ - name: 'pageNumber', - type: Number, - required: false - }) - @ApiQuery({ - name: 'searchByText', - type: String, - required: false - }) - @ApiQuery({ - name: 'pageSize', - type: Number, - required: false - }) - @ApiQuery({ - name: 'sortByValue', - type: String, - required: false - }) - @ApiQuery({ - name: 'sorting', - type: String, - required: false - }) - - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) - async getIssueCredentials( - @Query() getAllIssuedCredentials: GetAllIssuedCredentialsDto, - @User() user: IUserRequest, - @Param('orgId') orgId: string, - @Res() res: Response - ): Promise { - const { pageSize, searchByText, pageNumber, sorting, sortByValue } = getAllIssuedCredentials; - const issuedCredentialsSearchCriteria: IIssuedCredentialSearchinterface = { - pageNumber, - searchByText, - pageSize, - sorting, - sortByValue - }; + @Get('/orgs/:orgId/credentials') + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + @ApiOperation({ + summary: `Get all issued credentials for a specific organization`, + description: `Get all issued credentials for a specific organization` + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) + async getIssueCredentials( + @Query() getAllIssuedCredentials: IGetAllIssuedCredentialsDto, + @User() user: IUserRequest, + @Param('orgId') orgId: string, + @Res() res: Response + ): Promise { + const { pageSize, searchByText, pageNumber, sortField, sortBy } = getAllIssuedCredentials; + const issuedCredentialsSearchCriteria: IIssuedCredentialSearchParams = { + pageNumber, + searchByText, + pageSize, + sortField, + sortBy + }; - const getCredentialDetails = await this.issueCredentialService.getIssueCredentials(issuedCredentialsSearchCriteria, user, orgId); + const getCredentialDetails = await this.issueCredentialService.getIssueCredentials( + issuedCredentialsSearchCriteria, + user, + orgId + ); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.issuance.success.fetch, - data: getCredentialDetails.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.issuance.success.fetch, + data: getCredentialDetails + }; + return res.status(HttpStatus.OK).json(finalResponse); + } /** - * Description: Get all issued credentials - * @param user * @param credentialRecordId * @param orgId - * + * @returns Details of specific credential */ + @Get('/orgs/:orgId/credentials/:credentialRecordId') @ApiBearerAuth() @ApiOperation({ - summary: `Get credential by credentialRecordId`, - description: `Get credential credentialRecordId` + summary: `Fetch credentials by credentialRecordId`, + description: `Fetch credentials credentialRecordId` }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @@ -168,7 +142,6 @@ export class IssuanceController { @User() user: IUserRequest, @Param('credentialRecordId') credentialRecordId: string, @Param('orgId') orgId: string, - @Res() res: Response ): Promise { const getCredentialDetails = await this.issueCredentialService.getIssueCredentialsbyCredentialRecordId( diff --git a/apps/api-gateway/src/issuance/issuance.service.ts b/apps/api-gateway/src/issuance/issuance.service.ts index 1f9611a6c..907bc2a14 100644 --- a/apps/api-gateway/src/issuance/issuance.service.ts +++ b/apps/api-gateway/src/issuance/issuance.service.ts @@ -4,7 +4,8 @@ import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { ClientDetails, FileParameter, IssuanceDto, IssueCredentialDto, OutOfBandCredentialDto, PreviewFileDetails } from './dtos/issuance.dto'; -import { FileExportResponse, IIssuedCredentialSearchinterface, RequestPayload } from './interfaces'; +import { FileExportResponse, IIssuedCredentialSearchParams, RequestPayload } from './interfaces'; +import { IIssuedCredential } from '@credebl/common/interfaces/issuance.interface'; @Injectable() export class IssuanceService extends BaseService { @@ -32,12 +33,10 @@ export class IssuanceService extends BaseService { return this.sendNats(this.issuanceProxy, 'send-credential-create-offer-oob', payload); } - getIssueCredentials(issuedCredentialsSearchCriteria: IIssuedCredentialSearchinterface, user: IUserRequest, orgId: string): Promise<{ - response: object; - }> { + getIssueCredentials(issuedCredentialsSearchCriteria: IIssuedCredentialSearchParams, user: IUserRequest, orgId: string): Promise { const payload = { issuedCredentialsSearchCriteria, user, orgId }; - return this.sendNats(this.issuanceProxy, 'get-all-issued-credentials', payload); - } + return this.sendNatsMessage(this.issuanceProxy, 'get-all-issued-credentials', payload); + } getIssueCredentialsbyCredentialRecordId(user: IUserRequest, credentialRecordId: string, orgId: string): Promise<{ diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index a0b591fba..959cba04b 100644 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -31,7 +31,7 @@ async function bootstrap(): Promise { app.use(helmet({ xssFilter:true })); - app.useGlobalPipes(new ValidationPipe()); + const options = new DocumentBuilder() .setTitle(`${process.env.PLATFORM_NAME}`) .setDescription(`${process.env.PLATFORM_NAME} Platform APIs`) diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 865ec93ef..060a8e663 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -3,7 +3,7 @@ import { CommonService } from '@credebl/common'; import { Controller, Get, Put, Param, UseGuards, UseFilters, Post, Body, Res, HttpStatus, Query, Delete } from '@nestjs/common'; import { OrganizationService } from './organization.service'; import { CreateOrganizationDto } from './dtos/create-organization-dto'; -import IResponseType from '@credebl/common/interfaces/response.interface'; +import IResponseType, { IResponse } from '@credebl/common/interfaces/response.interface'; import { Response } from 'express'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; @@ -41,10 +41,10 @@ export class OrganizationController { @Get('/profile/:orgId') @ApiOperation({ summary: 'Organization Profile', description: 'Update an organization' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - async getOgPofile(@Param('orgId') orgId: string, @Res() res: Response): Promise { + async getOgPofile(@Param('orgId') orgId: string, @Res() res: Response): Promise { const orgProfile = await this.organizationService.getOgPofile(orgId); - const base64Data = orgProfile.response["logoUrl"]; + const base64Data = orgProfile['logoUrl']; const getImageBuffer = await this.imageServiceService.getBase64Image(base64Data); res.setHeader('Content-Type', 'image/png'); @@ -76,13 +76,13 @@ export class OrganizationController { type: String, required: false }) - async get(@Query() getAllUsersDto: GetAllOrganizationsDto, @Res() res: Response): Promise { + async get(@Query() getAllUsersDto: GetAllOrganizationsDto, @Res() res: Response): Promise { const users = await this.organizationService.getPublicOrganizations(getAllUsersDto); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.organisation.success.getOrganizations, - data: users.response + data: users }; return res.status(HttpStatus.OK).json(finalResponse); @@ -96,7 +96,7 @@ export class OrganizationController { @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async getOrgRoles(@Res() res: Response): Promise { + async getOrgRoles(@Res() res: Response): Promise { const orgRoles = await this.organizationService.getOrgRoles(); @@ -119,15 +119,15 @@ export class OrganizationController { @ApiParam({ name: 'orgSlug', type: String, - required: false + required: true }) - async getPublicProfile(@Param('orgSlug') orgSlug: string, @Res() res: Response): Promise { + async getPublicProfile(@Param('orgSlug') orgSlug: string, @Res() res: Response): Promise { const userData = await this.organizationService.getPublicProfile(orgSlug); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.organisation.success.fetchProfile, - data: userData.response + data: userData }; return res.status(HttpStatus.OK).json(finalResponse); @@ -141,14 +141,14 @@ export class OrganizationController { @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async getOrganizationDashboard(@Param('orgId') orgId: string, @Res() res: Response, @User() reqUser: user): Promise { + async getOrganizationDashboard(@Param('orgId') orgId: string, @Res() res: Response, @User() reqUser: user): Promise { const getOrganization = await this.organizationService.getOrganizationDashboard(orgId, reqUser.id); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.organisation.success.getOrgDashboard, - data: getOrganization.response + data: getOrganization }; return res.status(HttpStatus.OK).json(finalResponse); @@ -175,14 +175,14 @@ export class OrganizationController { required: false }) @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) - async getInvitationsByOrgId(@Param('orgId') orgId: string, @Query() getAllInvitationsDto: GetAllSentInvitationsDto, @Res() res: Response): Promise { + async getInvitationsByOrgId(@Param('orgId') orgId: string, @Query() getAllInvitationsDto: GetAllSentInvitationsDto, @Res() res: Response): Promise { const getInvitationById = await this.organizationService.getInvitationsByOrgId(orgId, getAllInvitationsDto); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.organisation.success.getInvitation, - data: getInvitationById.response + data: getInvitationById }; return res.status(HttpStatus.OK).json(finalResponse); @@ -211,14 +211,14 @@ export class OrganizationController { type: String, required: false }) - async getOrganizations(@Query() getAllOrgsDto: GetAllOrganizationsDto, @Res() res: Response, @User() reqUser: user): Promise { + async getOrganizations(@Query() getAllOrgsDto: GetAllOrganizationsDto, @Res() res: Response, @User() reqUser: user): Promise { const getOrganizations = await this.organizationService.getOrganizations(getAllOrgsDto, reqUser.id); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.organisation.success.getOrganizations, - data: getOrganizations.response + data: getOrganizations }; return res.status(HttpStatus.OK).json(finalResponse); @@ -230,14 +230,14 @@ export class OrganizationController { @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) - async getOrganization(@Param('orgId') orgId: string, @Res() res: Response, @User() reqUser: user): Promise { + async getOrganization(@Param('orgId') orgId: string, @Res() res: Response, @User() reqUser: user): Promise { const getOrganization = await this.organizationService.getOrganization(orgId, reqUser.id); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.organisation.success.getOrganization, - data: getOrganization.response + data: getOrganization }; return res.status(HttpStatus.OK).json(finalResponse); @@ -271,12 +271,12 @@ export class OrganizationController { type: String, required: false }) - async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Param('orgId') orgId: string, @Res() res: Response): Promise { + async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Param('orgId') orgId: string, @Res() res: Response): Promise { const users = await this.organizationService.getOrgUsers(orgId, getAllUsersDto); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.user.success.fetchUsers, - data: users?.response + data: users }; return res.status(HttpStatus.OK).json(finalResponse); @@ -287,7 +287,7 @@ export class OrganizationController { @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async createOrganization(@Body() createOrgDto: CreateOrganizationDto, @Res() res: Response, @User() reqUser: user): Promise { + async createOrganization(@Body() createOrgDto: CreateOrganizationDto, @Res() res: Response, @User() reqUser: user): Promise { await this.organizationService.createOrganization(createOrgDto, reqUser.id); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -305,7 +305,7 @@ export class OrganizationController { @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() - async createInvitation(@Body() bulkInvitationDto: BulkSendInvitationDto, @Param('orgId') orgId: string, @User() user: user, @Res() res: Response): Promise { + async createInvitation(@Body() bulkInvitationDto: BulkSendInvitationDto, @Param('orgId') orgId: string, @User() user: user, @Res() res: Response): Promise { bulkInvitationDto.orgId = orgId; await this.organizationService.createInvitation(bulkInvitationDto, user.id, user.email); @@ -325,7 +325,7 @@ export class OrganizationController { @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Update user roles', description: 'update user roles' }) - async updateUserRoles(@Body() updateUserDto: UpdateUserRolesDto, @Param('orgId') orgId: string, @Param('userId') userId: string, @Res() res: Response): Promise { + async updateUserRoles(@Body() updateUserDto: UpdateUserRolesDto, @Param('orgId') orgId: string, @Param('userId') userId: string, @Res() res: Response): Promise { updateUserDto.orgId = orgId; updateUserDto.userId = userId; @@ -345,7 +345,7 @@ export class OrganizationController { @ApiBearerAuth() @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async updateOrganization(@Body() updateOrgDto: UpdateOrganizationDto, @Param('orgId') orgId: string, @Res() res: Response, @User() reqUser: user): Promise { + async updateOrganization(@Body() updateOrgDto: UpdateOrganizationDto, @Param('orgId') orgId: string, @Res() res: Response, @User() reqUser: user): Promise { updateOrgDto.orgId = orgId; await this.organizationService.updateOrganization(updateOrgDto, reqUser.id, orgId); @@ -362,7 +362,7 @@ export class OrganizationController { @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) - async deleteOrganization(@Param('orgId') orgId: number, @Res() res: Response): Promise { + async deleteOrganization(@Param('orgId') orgId: number, @Res() res: Response): Promise { await this.organizationService.deleteOrganization(orgId); @@ -372,4 +372,23 @@ export class OrganizationController { }; return res.status(HttpStatus.ACCEPTED).json(finalResponse); } + + @Delete('/:orgId/invitations/:invitationId') + @ApiOperation({ summary: 'Delete organization invitation', description: 'Delete organization invitation' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiBearerAuth() + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + async deleteOrganizationInvitation( + @Param('orgId') orgId: string, + @Param('invitationId') invitationId: string, + @Res() res: Response + ): Promise { + await this.organizationService.deleteOrganizationInvitation(orgId, invitationId); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.organisation.success.orgInvitationDeleted + }; + return res.status(HttpStatus.OK).json(finalResponse); + } } \ No newline at end of file diff --git a/apps/api-gateway/src/organization/organization.service.ts b/apps/api-gateway/src/organization/organization.service.ts index c1d44ac64..24b17e18a 100644 --- a/apps/api-gateway/src/organization/organization.service.ts +++ b/apps/api-gateway/src/organization/organization.service.ts @@ -9,6 +9,10 @@ import { BulkSendInvitationDto } from './dtos/send-invitation.dto'; import { UpdateUserRolesDto } from './dtos/update-user-roles.dto'; import { UpdateOrganizationDto } from './dtos/update-organization-dto'; import { GetAllUsersDto } from '../user/dto/get-all-users.dto'; +import { IOrgRoles } from 'libs/org-roles/interfaces/org-roles.interface'; +import { organisation } from '@prisma/client'; +import { IGetOrgById, IGetOrgs, IOrgInvitationsPagination, IOrganizationDashboard } from 'apps/organization/interfaces/organization.interface'; +import { IOrgUsers } from 'apps/user/interfaces/user.interface'; @Injectable() export class OrganizationService extends BaseService { @@ -21,9 +25,9 @@ export class OrganizationService extends BaseService { * @param createOrgDto * @returns Organization creation Success */ - async createOrganization(createOrgDto: CreateOrganizationDto, userId: string): Promise { + async createOrganization(createOrgDto: CreateOrganizationDto, userId: string): Promise { const payload = { createOrgDto, userId }; - return this.sendNats(this.serviceProxy, 'create-organization', payload); + return this.sendNatsMessage(this.serviceProxy, 'create-organization', payload); } /** @@ -31,9 +35,9 @@ export class OrganizationService extends BaseService { * @param updateOrgDto * @returns Organization update Success */ - async updateOrganization(updateOrgDto: UpdateOrganizationDto, userId: string, orgId: string): Promise { + async updateOrganization(updateOrgDto: UpdateOrganizationDto, userId: string, orgId: string): Promise { const payload = { updateOrgDto, userId, orgId }; - return this.sendNats(this.serviceProxy, 'update-organization', payload); + return this.sendNatsMessage(this.serviceProxy, 'update-organization', payload); } /** @@ -41,25 +45,28 @@ export class OrganizationService extends BaseService { * @param * @returns Organizations details */ - async getOrganizations(getAllOrgsDto: GetAllOrganizationsDto, userId: string): Promise<{ response: object }> { + + async getOrganizations(getAllOrgsDto: GetAllOrganizationsDto, userId: string): Promise { const payload = { userId, ...getAllOrgsDto }; - return this.sendNats(this.serviceProxy, 'get-organizations', payload); + const fetchOrgs = await this.sendNatsMessage(this.serviceProxy, 'get-organizations', payload); + return fetchOrgs; } - + /** * * @param * @returns Public organizations list */ - async getPublicOrganizations(getAllOrgsDto: GetAllOrganizationsDto): Promise<{ response: object }> { + async getPublicOrganizations(getAllOrgsDto: GetAllOrganizationsDto): Promise { const payload = { ...getAllOrgsDto }; - return this.sendNats(this.serviceProxy, 'get-public-organizations', payload); + const PublicOrg = this.sendNatsMessage(this.serviceProxy, 'get-public-organizations', payload); + return PublicOrg; } - async getPublicProfile(orgSlug: string): Promise<{ response: object }> { + async getPublicProfile(orgSlug: string): Promise { const payload = { orgSlug }; try { - return this.sendNats(this.serviceProxy, 'get-organization-public-profile', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-organization-public-profile', payload); } catch (error) { this.logger.error(`Error in get user:${JSON.stringify(error)}`); } @@ -70,9 +77,9 @@ export class OrganizationService extends BaseService { * @param orgId * @returns Organization get Success */ - async getOrganization(orgId: string, userId: string): Promise<{ response: object }> { + async getOrganization(orgId: string, userId: string): Promise { const payload = { orgId, userId }; - return this.sendNats(this.serviceProxy, 'get-organization-by-id', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-organization-by-id', payload); } /** @@ -83,15 +90,15 @@ export class OrganizationService extends BaseService { async getInvitationsByOrgId( orgId: string, getAllInvitationsDto: GetAllSentInvitationsDto - ): Promise<{ response: object }> { + ): Promise { const { pageNumber, pageSize, search } = getAllInvitationsDto; const payload = { orgId, pageNumber, pageSize, search }; - return this.sendNats(this.serviceProxy, 'get-invitations-by-orgId', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-invitations-by-orgId', payload); } - async getOrganizationDashboard(orgId: string, userId: string): Promise<{ response: object }> { + async getOrganizationDashboard(orgId: string, userId: string): Promise { const payload = { orgId, userId }; - return this.sendNats(this.serviceProxy, 'get-organization-dashboard', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-organization-dashboard', payload); } /** @@ -99,9 +106,10 @@ export class OrganizationService extends BaseService { * @param * @returns get organization roles */ - async getOrgRoles(): Promise { + + async getOrgRoles(): Promise { const payload = {}; - return this.sendNats(this.serviceProxy, 'get-org-roles', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-org-roles', payload); } /** @@ -109,9 +117,9 @@ export class OrganizationService extends BaseService { * @param sendInvitationDto * @returns Organization invitation creation Success */ - async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: string, userEmail: string): Promise { + async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: string, userEmail: string): Promise { const payload = { bulkInvitationDto, userId, userEmail }; - return this.sendNats(this.serviceProxy, 'send-invitation', payload); + return this.sendNatsMessage(this.serviceProxy, 'send-invitation', payload); } /** @@ -120,34 +128,42 @@ export class OrganizationService extends BaseService { * @param userId * @returns User roles update response */ - async updateUserRoles(updateUserDto: UpdateUserRolesDto, userId: string): Promise<{ response: boolean }> { + async updateUserRoles(updateUserDto: UpdateUserRolesDto, userId: string): Promise { const payload = { orgId: updateUserDto.orgId, roleIds: updateUserDto.orgRoleId, userId }; - return this.sendNats(this.serviceProxy, 'update-user-roles', payload); + return this.sendNatsMessage(this.serviceProxy, 'update-user-roles', payload); } async getOrgUsers( orgId: string, getAllUsersDto: GetAllUsersDto - ): Promise<{ response: object }> { + ): Promise { const { pageNumber, pageSize, search } = getAllUsersDto; const payload = { orgId, pageNumber, pageSize, search }; - return this.sendNats(this.serviceProxy, 'fetch-organization-user', payload); + return this.sendNatsMessage(this.serviceProxy, 'fetch-organization-user', payload); } async getOgPofile( orgId: string - ): Promise<{ response: object }> { + ): Promise { const payload = { orgId }; - return this.sendNats(this.serviceProxy, 'fetch-organization-profile', payload); + return this.sendNatsMessage(this.serviceProxy, 'fetch-organization-profile', payload); } async deleteOrganization( orgId: number - ): Promise<{ response: object }> { + ): Promise { const payload = { orgId }; - return this.sendNats(this.serviceProxy, 'delete-organization', payload); + return this.sendNatsMessage(this.serviceProxy, 'delete-organization', payload); + } + + async deleteOrganizationInvitation( + orgId: string, + invitationId: string + ): Promise { + const payload = {orgId, invitationId}; + return this.sendNatsMessage(this.serviceProxy, 'delete-organization-invitation', payload); } } diff --git a/apps/api-gateway/src/platform/platform.controller.ts b/apps/api-gateway/src/platform/platform.controller.ts index 7f11d05e9..1de4f4aa5 100644 --- a/apps/api-gateway/src/platform/platform.controller.ts +++ b/apps/api-gateway/src/platform/platform.controller.ts @@ -6,7 +6,7 @@ import { GetAllSchemaByPlatformDto } from '../schema/dtos/get-all-schema.dto'; import { IUserRequestInterface } from '../interfaces/IUserRequestInterface'; import { User } from '../authz/decorators/user.decorator'; import { Response } from 'express'; -import { ISchemaSearchInterface } from '../interfaces/ISchemaSearch.interface'; +import { ISchemaSearchPayload } from '../interfaces/ISchemaSearch.interface'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @@ -34,13 +34,13 @@ export class PlatformController { @User() user: IUserRequestInterface ): Promise { const { ledgerId, pageSize, searchByText, pageNumber, sorting, sortByValue } = getAllSchemaDto; - const schemaSearchCriteria: ISchemaSearchInterface = { + const schemaSearchCriteria: ISchemaSearchPayload = { ledgerId, pageNumber, searchByText, pageSize, - sorting, - sortByValue + sortField: sorting, + sortBy: sortByValue }; const schemasResponse = await this.platformService.getAllSchema(schemaSearchCriteria, user); const finalResponse: IResponseType = { diff --git a/apps/api-gateway/src/platform/platform.service.ts b/apps/api-gateway/src/platform/platform.service.ts index 1d91103ca..7f03786f8 100644 --- a/apps/api-gateway/src/platform/platform.service.ts +++ b/apps/api-gateway/src/platform/platform.service.ts @@ -1,7 +1,7 @@ import { Injectable, Inject } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from '../../../../libs/service/base.service'; -import { ISchemaSearchInterface } from '../interfaces/ISchemaSearch.interface'; +import { ISchemaSearchPayload } from '../interfaces/ISchemaSearch.interface'; import { IUserRequestInterface } from '../interfaces/IUserRequestInterface'; @Injectable() @@ -12,7 +12,7 @@ export class PlatformService extends BaseService { super('PlatformService'); } - async getAllSchema(schemaSearchCriteria: ISchemaSearchInterface, user: IUserRequestInterface): Promise<{ + async getAllSchema(schemaSearchCriteria: ISchemaSearchPayload, user: IUserRequestInterface): Promise<{ response: object; }> { const schemaSearch = { schemaSearchCriteria, user }; diff --git a/apps/api-gateway/src/schema/dtos/get-all-schema.dto.ts b/apps/api-gateway/src/schema/dtos/get-all-schema.dto.ts index c0e955d43..e16ec07c2 100644 --- a/apps/api-gateway/src/schema/dtos/get-all-schema.dto.ts +++ b/apps/api-gateway/src/schema/dtos/get-all-schema.dto.ts @@ -2,8 +2,10 @@ /* eslint-disable camelcase */ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { SortValue } from '../../enum'; -import { Type } from 'class-transformer'; -import { IsOptional } from 'class-validator'; +import { Transform, Type } from 'class-transformer'; +import { IsEnum, IsOptional } from 'class-validator'; +import { trim } from '@credebl/common/cast.helper'; +import { CredDefSortFields, SortFields } from 'apps/ledger/src/schema/enum/schema.enum'; export class GetAllSchemaDto { @ApiProperty({ required: false }) @@ -12,6 +14,7 @@ export class GetAllSchemaDto { @ApiProperty({ required: false }) @IsOptional() + @Transform(({ value }) => trim(value)) @Type(() => String) searchByText: string = ''; @@ -19,13 +22,22 @@ export class GetAllSchemaDto { @IsOptional() pageSize: number = 10; - @ApiProperty({ required: false }) + @ApiProperty({ + required: false + }) + @Transform(({ value }) => trim(value)) @IsOptional() - sorting: string = 'id'; + @IsEnum(SortFields) + sortField: string = SortFields.CREATED_DATE_TIME; - @ApiProperty({ required: false }) + @ApiProperty({ + enum: [SortValue.DESC, SortValue.ASC], + required: false + }) + @Transform(({ value }) => trim(value)) @IsOptional() - sortByValue: string = SortValue.DESC; + @IsEnum(SortValue) + sortBy: string = SortValue.DESC; } export class GetCredentialDefinitionBySchemaIdDto { @@ -39,13 +51,26 @@ export class GetCredentialDefinitionBySchemaIdDto { @Type(() => Number) pageSize: number = 10; - @ApiProperty({ required: false }) + @ApiProperty({ + required: false + }) + @Transform(({ value }) => trim(value)) @IsOptional() - sorting: string = 'id'; + @IsEnum(CredDefSortFields) + sortField: string = SortFields.CREATED_DATE_TIME; - @ApiProperty({ required: false }) + @ApiProperty({ + enum: [SortValue.DESC, SortValue.ASC], + required: false + }) + @Transform(({ value }) => trim(value)) @IsOptional() - sortByValue: string = SortValue.DESC; + @IsEnum(SortValue) + sortBy: string = SortValue.DESC; + + schemaId: string; + + orgId: string; } export class GetAllSchemaByPlatformDto { diff --git a/apps/api-gateway/src/schema/schema.controller.ts b/apps/api-gateway/src/schema/schema.controller.ts index f69f3d068..d530e9d8f 100644 --- a/apps/api-gateway/src/schema/schema.controller.ts +++ b/apps/api-gateway/src/schema/schema.controller.ts @@ -10,7 +10,7 @@ import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { Response } from 'express'; import { User } from '../authz/decorators/user.decorator'; -import { ICredDeffSchemaSearchInterface, ISchemaSearchInterface } from '../interfaces/ISchemaSearch.interface'; +import { ISchemaSearchPayload } from '../interfaces/ISchemaSearch.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; import { GetAllSchemaDto, GetCredentialDefinitionBySchemaIdDto } from './dtos/get-all-schema.dto'; import { OrgRoles } from 'libs/org-roles/enums'; @@ -19,6 +19,7 @@ import { IUserRequestInterface } from './interfaces'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { CreateSchemaDto } from '../dtos/create-schema.dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; +import { CredDefSortFields, SortFields } from 'apps/ledger/src/schema/enum/schema.enum'; @UseFilters(CustomExceptionFilter) @Controller('orgs') @@ -38,7 +39,6 @@ export class SchemaController { summary: 'Get schema information from the ledger using its schema ID.', description: 'Get schema information from the ledger using its schema ID.' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) async getSchemaById( @Res() res: Response, @@ -60,28 +60,13 @@ export class SchemaController { @Get('/:orgId/schemas/:schemaId/cred-defs') @ApiOperation({ - summary: 'Get credential definition list by schema Id', + summary: 'Credential definitions by schema Id', description: 'Get credential definition list by schema Id' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @ApiQuery({ - name: 'pageNumber', - type: Number, - required: false - }) - @ApiQuery({ - name: 'pageSize', - type: Number, - required: false - }) - @ApiQuery({ - name: 'sorting', - type: String, - required: false - }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiQuery({ - name: 'sortByValue', - type: String, + name: 'sortField', + enum: CredDefSortFields, required: false }) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @@ -89,77 +74,61 @@ export class SchemaController { async getcredDeffListBySchemaId( @Param('orgId') orgId: string, @Param('schemaId') schemaId: string, - @Query() GetCredentialDefinitionBySchemaIdDto: GetCredentialDefinitionBySchemaIdDto, + @Query() getCredentialDefinitionBySchemaIdDto: GetCredentialDefinitionBySchemaIdDto, @Res() res: Response, - @User() user: IUserRequestInterface): Promise { + @User() user: IUserRequestInterface): Promise { if (!schemaId) { throw new BadRequestException(ResponseMessages.schema.error.invalidSchemaId); } - const credentialDefinitionList = await this.appService.getcredDeffListBySchemaId(schemaId, GetCredentialDefinitionBySchemaIdDto, user, orgId); + getCredentialDefinitionBySchemaIdDto.schemaId = schemaId; + getCredentialDefinitionBySchemaIdDto.orgId = orgId; + + const credentialDefinitionList = await this.appService.getcredDeffListBySchemaId(getCredentialDefinitionBySchemaIdDto, user); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.schema.success.fetch, - data: credentialDefinitionList.response + data: credentialDefinitionList }; + return res.status(HttpStatus.OK).json(finalResponse); } @Get('/:orgId/schemas') @ApiOperation({ - summary: 'Get all schemas by org id.', + summary: 'Schemas by org id.', description: 'Get all schemas by org id.' }) @ApiQuery({ - name: 'pageNumber', - type: Number, - required: false - }) - @ApiQuery({ - name: 'searchByText', - type: String, - required: false - }) - @ApiQuery({ - name: 'pageSize', - type: Number, - required: false - }) - @ApiQuery({ - name: 'sorting', - type: String, - required: false - }) - @ApiQuery({ - name: 'sortByValue', - type: String, + name: 'sortField', + enum: SortFields, required: false }) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async getSchemas( @Query() getAllSchemaDto: GetAllSchemaDto, @Param('orgId') orgId: string, @Res() res: Response, @User() user: IUserRequestInterface - ): Promise { + ): Promise { - const { pageSize, searchByText, pageNumber, sorting, sortByValue } = getAllSchemaDto; - const schemaSearchCriteria: ISchemaSearchInterface = { + const { pageSize, searchByText, pageNumber, sortField, sortBy } = getAllSchemaDto; + const schemaSearchCriteria: ISchemaSearchPayload = { pageNumber, searchByText, pageSize, - sorting, - sortByValue + sortField, + sortBy }; const schemasResponse = await this.appService.getSchemas(schemaSearchCriteria, user, orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.schema.success.fetch, - data: schemasResponse.response + data: schemasResponse }; return res.status(HttpStatus.OK).json(finalResponse); } @@ -174,31 +143,12 @@ export class SchemaController { @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) async createSchema(@Res() res: Response, @Body() schema: CreateSchemaDto, @Param('orgId') orgId: string, @User() user: IUserRequestInterface): Promise { - schema.attributes.forEach((attribute) => { - if (attribute.hasOwnProperty('attributeName') && attribute.hasOwnProperty('schemaDataType') && attribute.hasOwnProperty('displayName')) { - if (attribute.hasOwnProperty('attributeName') && '' === attribute?.attributeName) { - throw new BadRequestException('Attribute must not be empty'); - } else if (attribute.hasOwnProperty('attributeName') && '' === attribute?.attributeName?.trim()) { - throw new BadRequestException('Attributes should not contain space'); - } else if (attribute.hasOwnProperty('schemaDataType') && '' === attribute?.schemaDataType) { - throw new BadRequestException('Schema Data Type should not contain space'); - } else if (attribute.hasOwnProperty('schemaDataType') && '' === attribute?.schemaDataType?.trim()) { - throw new BadRequestException('Schema Data Type should not contain space'); - } else if (attribute.hasOwnProperty('displayName') && '' === attribute?.displayName) { - throw new BadRequestException('Display Name Type should not contain space'); - } - } else { - throw new BadRequestException('Please provide a valid attributes'); - } - }); - schema.orgId = orgId; const schemaDetails = await this.appService.createSchema(schema, user, schema.orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, - message: 'Schema created successfully', - data: schemaDetails.response + message: schemaDetails.response }; return res.status(HttpStatus.CREATED).json(finalResponse); } diff --git a/apps/api-gateway/src/schema/schema.service.ts b/apps/api-gateway/src/schema/schema.service.ts index 84a2854b5..5991b41f8 100644 --- a/apps/api-gateway/src/schema/schema.service.ts +++ b/apps/api-gateway/src/schema/schema.service.ts @@ -2,8 +2,10 @@ import { Injectable, Inject } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from '../../../../libs/service/base.service'; import { CreateSchemaDto } from '../dtos/create-schema.dto'; -import { ICredDeffSchemaSearchInterface, ISchemaSearchInterface } from '../interfaces/ISchemaSearch.interface'; +import { ISchemaSearchPayload } from '../interfaces/ISchemaSearch.interface'; import { IUserRequestInterface } from './interfaces'; +import { ICredDefWithPagination, ISchemasWithPagination } from '@credebl/common/interfaces/schema.interface'; +import { GetCredentialDefinitionBySchemaIdDto } from './dtos/get-all-schema.dto'; @Injectable() export class SchemaService extends BaseService { @@ -13,7 +15,7 @@ export class SchemaService extends BaseService { ) { super(`Schema Service`); } createSchema(schema: CreateSchemaDto, user: IUserRequestInterface, orgId: string): Promise<{ - response: object; + response: string; }> { const payload = { schema, user, orgId }; return this.sendNats(this.schemaServiceProxy, 'create-schema', payload); @@ -26,17 +28,13 @@ export class SchemaService extends BaseService { return this.sendNats(this.schemaServiceProxy, 'get-schema-by-id', payload); } - getSchemas(schemaSearchCriteria: ISchemaSearchInterface, user: IUserRequestInterface, orgId: string): Promise<{ - response: object; - }> { + getSchemas(schemaSearchCriteria: ISchemaSearchPayload, user: IUserRequestInterface, orgId: string): Promise { const schemaSearch = { schemaSearchCriteria, user, orgId }; - return this.sendNats(this.schemaServiceProxy, 'get-schemas', schemaSearch); + return this.sendNatsMessage(this.schemaServiceProxy, 'get-schemas', schemaSearch); } - getcredDeffListBySchemaId(schemaId: string, schemaSearchCriteria: ICredDeffSchemaSearchInterface, user: IUserRequestInterface, orgId: string): Promise<{ - response: object; - }> { - const payload = { schemaId, schemaSearchCriteria, user, orgId }; - return this.sendNats(this.schemaServiceProxy, 'get-cred-deff-list-by-schemas-id', payload); + getcredDeffListBySchemaId(schemaSearchCriteria: GetCredentialDefinitionBySchemaIdDto, user: IUserRequestInterface): Promise { + const payload = { schemaSearchCriteria, user }; + return this.sendNatsMessage(this.schemaServiceProxy, 'get-cred-deff-list-by-schemas-id', payload); } } \ No newline at end of file diff --git a/apps/api-gateway/src/user/dto/add-user.dto.ts b/apps/api-gateway/src/user/dto/add-user.dto.ts index 4007d8629..f25b4ae25 100644 --- a/apps/api-gateway/src/user/dto/add-user.dto.ts +++ b/apps/api-gateway/src/user/dto/add-user.dto.ts @@ -1,9 +1,9 @@ import { trim } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; -export class AddUserDetails { +export class AddUserDetailsDto { @ApiProperty({ example: 'awqx@getnada.com' }) @IsEmail({}, { message: 'Please provide a valid email' }) @@ -13,19 +13,22 @@ export class AddUserDetails { @ApiProperty({ example: 'Alen' }) @IsNotEmpty({ message: 'First name is required' }) + @MinLength(2, { message: 'First name must be at least 2 characters' }) + @MaxLength(50, { message: 'First name must be at most 50 characters' }) @IsString({ message: 'First name should be a string' }) firstName: string; @ApiProperty({ example: 'Harvey' }) @IsNotEmpty({ message: 'Last name is required' }) + @MinLength(2, { message: 'Last name must be at least 2 characters' }) + @MaxLength(50, { message: 'Last name must be at most 50 characters' }) @IsString({ message: 'Last name should be a string' }) lastName: string; @ApiProperty() @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'Password is required.' }) - @IsOptional() - password?: string; + @IsNotEmpty({ message: 'Password is required' }) + password: string; @ApiProperty({ example: 'false' }) @IsOptional() @@ -33,10 +36,10 @@ export class AddUserDetails { isPasskey?: boolean; } -export class AddPasskeyDetails { +export class AddPasskeyDetailsDto { @ApiProperty() @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'Password is required.' }) + @IsNotEmpty({ message: 'Password is required' }) password: string; } diff --git a/apps/api-gateway/src/user/dto/create-user.dto.ts b/apps/api-gateway/src/user/dto/create-user.dto.ts index 16c097e9c..68bafd310 100644 --- a/apps/api-gateway/src/user/dto/create-user.dto.ts +++ b/apps/api-gateway/src/user/dto/create-user.dto.ts @@ -8,8 +8,9 @@ export class UserEmailVerificationDto { @ApiProperty() @Transform(({ value }) => trim(value)) @Transform(({ value }) => toLowerCase(value)) - @IsNotEmpty({ message: 'Email is required.' }) - @MaxLength(256, { message: 'Email must be at most 256 character.' }) + @IsEmail({}, { message: 'Please provide a valid email' }) + @IsNotEmpty({ message: 'Email is required' }) + @MaxLength(256, { message: 'Email must be at most 256 character' }) @IsEmail() email: string; } diff --git a/apps/api-gateway/src/user/dto/email-verify.dto.ts b/apps/api-gateway/src/user/dto/email-verify.dto.ts index 23c4acd5a..b74ed9bfa 100644 --- a/apps/api-gateway/src/user/dto/email-verify.dto.ts +++ b/apps/api-gateway/src/user/dto/email-verify.dto.ts @@ -1,4 +1,4 @@ -import { IsEmail, IsNotEmpty, MaxLength } from 'class-validator'; +import { IsEmail, IsNotEmpty, IsString, MaxLength } from 'class-validator'; import { toLowerCase, trim } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; @@ -9,13 +9,15 @@ export class EmailVerificationDto { @ApiProperty() @Transform(({ value }) => trim(value)) @Transform(({ value }) => toLowerCase(value)) - @IsNotEmpty({ message: 'Email is required.' }) - @MaxLength(256, { message: 'Email must be at most 256 character.' }) + @IsEmail({}, { message: 'Please provide a valid email' }) + @IsNotEmpty({ message: 'Email is required' }) + @MaxLength(256, { message: 'Email must be at most 256 character' }) @IsEmail() email: string; @ApiProperty() @Transform(({ value }) => trim(value)) @IsNotEmpty({ message: 'Verification code is required.' }) + @IsString({ message: 'Verification code should be string' }) verificationCode: string; } diff --git a/apps/api-gateway/src/user/dto/login-user.dto.ts b/apps/api-gateway/src/user/dto/login-user.dto.ts index f62980e6e..a9ae0cc9d 100644 --- a/apps/api-gateway/src/user/dto/login-user.dto.ts +++ b/apps/api-gateway/src/user/dto/login-user.dto.ts @@ -6,8 +6,8 @@ import { trim } from '@credebl/common/cast.helper'; export class LoginUserDto { @ApiProperty({ example: 'awqx@getnada.com' }) - @IsEmail() - @IsNotEmpty({ message: 'Please provide valid email' }) + @IsEmail({}, { message: 'Please provide a valid email' }) + @IsNotEmpty({ message: 'Email is required' }) @IsString({ message: 'email should be string' }) email: string; diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 9cf602c33..ae1f92896 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -15,6 +15,7 @@ import { import { UserService } from './user.service'; import { ApiBearerAuth, + ApiExcludeEndpoint, ApiForbiddenResponse, ApiOperation, ApiParam, @@ -28,7 +29,7 @@ import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; import { Response } from 'express'; import { CommonService } from '@credebl/common'; -import IResponseType from '@credebl/common/interfaces/response.interface'; +import IResponse from '@credebl/common/interfaces/response.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; import { user } from '@prisma/client'; import { AuthGuard } from '@nestjs/passport'; @@ -40,7 +41,7 @@ import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; import { GetAllUsersDto } from './dto/get-all-users.dto'; import { UpdateUserProfileDto } from './dto/update-user-profile.dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; -import { AddPasskeyDetails } from './dto/add-user.dto'; +import { AddPasskeyDetailsDto } from './dto/add-user.dto'; import { EmailValidator } from '../dtos/email-validator.dto'; import { UpdatePlatformSettingsDto } from './dto/update-platform-settings.dto'; import { Roles } from '../authz/decorators/roles.decorator'; @@ -69,7 +70,8 @@ export class UserController { * @returns Users list of organization */ @Get('/public-profiles') - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiExcludeEndpoint() + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Get users list', description: 'Get users list.' }) @ApiQuery({ name: 'pageNumber', @@ -92,16 +94,17 @@ export class UserController { @Res() res: Response ): Promise { const users = await this.userService.get(getAllUsersDto); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.user.success.fetchUsers, - data: users.response + data: users }; return res.status(HttpStatus.OK).json(finalResponse); } @Get('public-profiles/:username') + @ApiExcludeEndpoint() @ApiOperation({ summary: 'Fetch user details', description: 'Fetch user details' @@ -114,10 +117,10 @@ export class UserController { async getPublicProfile(@Param('username') username: string, @Res() res: Response): Promise { const userData = await this.userService.getPublicProfile(username); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.user.success.fetchProfile, - data: userData.response + data: userData }; return res.status(HttpStatus.OK).json(finalResponse); @@ -130,18 +133,21 @@ export class UserController { }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async getProfile(@User() reqUser: user, @Res() res: Response): Promise { + async getProfile(@User() reqUser: user, @Res() res: Response): Promise { const userData = await this.userService.getProfile(reqUser.id); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.user.success.fetchProfile, - data: userData.response + data: userData }; return res.status(HttpStatus.OK).json(finalResponse); } + /** + * @returns platform and ecosystem settings + */ @Get('/platform-settings') @ApiOperation({ summary: 'Get all platform and ecosystem settings', @@ -156,7 +162,7 @@ export class UserController { const finalResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.user.success.fetchPlatformSettings, - data: settings.response + data: settings }; return res.status(HttpStatus.OK).json(finalResponse); @@ -164,8 +170,8 @@ export class UserController { @Get('/activity') @ApiOperation({ - summary: 'organization invitations', - description: 'Fetch organization invitations' + summary: 'users activity', + description: 'Fetch users activity' }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() @@ -177,14 +183,17 @@ export class UserController { ): Promise { const userDetails = await this.userService.getUserActivities(reqUser.id, limit); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.user.success.userActivity, - data: userDetails.response + data: userDetails }; return res.status(HttpStatus.OK).json(finalResponse); } + /** + * @returns Organization invitation data + */ @Get('/org-invitations') @ApiOperation({ @@ -217,7 +226,7 @@ export class UserController { @Query() getAllInvitationsDto: GetAllInvitationsDto, @User() reqUser: user, @Res() res: Response - ): Promise { + ): Promise { if (!Object.values(Invitation).includes(getAllInvitationsDto.status)) { throw new BadRequestException(ResponseMessages.user.error.invalidInvitationStatus); } @@ -228,10 +237,10 @@ export class UserController { getAllInvitationsDto ); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.user.success.fetchInvitations, - data: invitations.response + data: invitations }; return res.status(HttpStatus.OK).json(finalResponse); @@ -240,33 +249,35 @@ export class UserController { /** * * @param email - * @param res - * @returns User email check + * @returns User's email exist status */ @Get('/:email') - @ApiOperation({ summary: 'Check user exist', description: 'check user existence' }) + @ApiOperation({ summary: 'Check if user exist', description: 'check user existence' }) async checkUserExist(@Param() emailParam: EmailValidator, @Res() res: Response): Promise { const userDetails = await this.userService.checkUserExist(emailParam.email); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.user.success.checkEmail, - data: userDetails.response + data: userDetails }; return res.status(HttpStatus.OK).json(finalResponse); } - + /** + * @param credentialId + * @returns User credentials + */ @Get('/user-credentials/:credentialId') @ApiOperation({ summary: 'Get user credentials by Id', description: 'Get user credentials by Id' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async getUserCredentialsById(@Param('credentialId') credentialId: string, @Res() res: Response): Promise { const getUserCrdentialsById = await this.userService.getUserCredentialsById(credentialId); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.user.success.userCredentials, - data: getUserCrdentialsById.response + data: getUserCrdentialsById }; return res.status(HttpStatus.OK).json(finalResponse); } @@ -294,43 +305,50 @@ export class UserController { acceptRejectInvitation.invitationId = invitationId; const invitationRes = await this.userService.acceptRejectInvitaion(acceptRejectInvitation, reqUser.id); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, - message: invitationRes.response + message: invitationRes }; return res.status(HttpStatus.CREATED).json(finalResponse); } - + /** + * @Body shareUserCredentials + * @returns User certificate URL + */ @Post('/certificate') @ApiOperation({ summary: 'Share user certificate', description: 'Share user certificate' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async shareUserCertificate( @Body() shareUserCredentials: CreateUserCertificateDto, @Res() res: Response - ): Promise { + ): Promise { const schemaIdParts = shareUserCredentials.schemaId.split(':'); // eslint-disable-next-line prefer-destructuring const title = schemaIdParts[2]; const imageBuffer = await this.userService.shareUserCertificate(shareUserCredentials); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, - message: 'Certificate url generated successfully', + message: ResponseMessages.user.success.shareUserCertificate, label: title, - data: imageBuffer.response + data: imageBuffer }; return res.status(HttpStatus.CREATED).json(finalResponse); } + /** + * @Body updateUserProfileDto + * @returns User details + */ @Put('/') @ApiOperation({ summary: 'Update user profile', description: 'Update user profile' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) async updateUserProfile( @@ -342,19 +360,24 @@ export class UserController { updateUserProfileDto.id = userId; await this.userService.updateUserProfile(updateUserProfileDto); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.user.success.update }; return res.status(HttpStatus.OK).json(finalResponse); } + /** + * @Body userInfo + * @returns User's profile update status + */ + @Put('/password/:email') @ApiOperation({ summary: 'Store user password details', description: 'Store user password details' }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() async addPasskey( - @Body() userInfo: AddPasskeyDetails, + @Body() userInfo: AddPasskeyDetailsDto, @Param('email') email: string, @Res() res: Response ): Promise { @@ -362,12 +385,17 @@ export class UserController { const finalResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.user.success.update, - data: userDetails.response + data: userDetails }; return res.status(HttpStatus.OK).json(finalResponse); } + /** + * @Body platformSettings + * @returns platform and ecosystem settings updated status + */ + @Put('/platform-settings') @ApiOperation({ summary: 'Update platform and ecosystem settings', @@ -384,7 +412,7 @@ export class UserController { const finalResponse = { statusCode: HttpStatus.OK, - message: result.response + message: result }; return res.status(HttpStatus.OK).json(finalResponse); diff --git a/apps/api-gateway/src/user/user.service.ts b/apps/api-gateway/src/user/user.service.ts index e6e98658e..77889dc39 100644 --- a/apps/api-gateway/src/user/user.service.ts +++ b/apps/api-gateway/src/user/user.service.ts @@ -6,9 +6,13 @@ import { AcceptRejectInvitationDto } from './dto/accept-reject-invitation.dto'; import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; import { GetAllUsersDto } from './dto/get-all-users.dto'; import { UpdateUserProfileDto } from './dto/update-user-profile.dto'; -import { AddPasskeyDetails } from './dto/add-user.dto'; +import { AddPasskeyDetailsDto } from './dto/add-user.dto'; import { UpdatePlatformSettingsDto } from './dto/update-platform-settings.dto'; import { CreateUserCertificateDto } from './dto/share-certificate.dto'; +import { IUsersProfile, ICheckUserDetails } from 'apps/user/interfaces/user.interface'; +import { IUsersActivity } from 'libs/user-activity/interface'; +import { IUserInvitations } from '@credebl/common/interfaces/user.interface'; +import { user } from '@prisma/client'; @Injectable() export class UserService extends BaseService { @@ -16,83 +20,83 @@ export class UserService extends BaseService { super('User Service'); } - async getProfile(id: string): Promise<{ response: object }> { + async getProfile(id: string): Promise { const payload = { id }; - return this.sendNats(this.serviceProxy, 'get-user-profile', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-user-profile', payload); } - async getPublicProfile(username: string): Promise<{ response: object }> { - const payload = { username }; - return this.sendNats(this.serviceProxy, 'get-user-public-profile', payload); + async getPublicProfile(username: string): Promise { + const payload = { username }; + return this.sendNatsMessage(this.serviceProxy, 'get-user-public-profile', payload); } - async getUserCredentialsById(credentialId: string): Promise<{ response: object }> { + async getUserCredentialsById(credentialId: string): Promise { const payload = { credentialId }; - return this.sendNats(this.serviceProxy, 'get-user-credentials-by-id', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-user-credentials-by-id', payload); } - async updateUserProfile(updateUserProfileDto: UpdateUserProfileDto): Promise<{ response: object }> { + async updateUserProfile(updateUserProfileDto: UpdateUserProfileDto): Promise { const payload = { updateUserProfileDto }; - return this.sendNats(this.serviceProxy, 'update-user-profile', payload); + return this.sendNatsMessage(this.serviceProxy, 'update-user-profile', payload); } - async findUserinSupabase(id: string): Promise<{ response: object }> { + async findUserinSupabase(id: string): Promise { const payload = { id }; - return this.sendNats(this.serviceProxy, 'get-user-by-supabase', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-user-by-supabase', payload); } - async invitations(id: string, status: string, getAllInvitationsDto: GetAllInvitationsDto): Promise<{ response: object }> { + async invitations(id: string, status: string, getAllInvitationsDto: GetAllInvitationsDto): Promise { const { pageNumber, pageSize, search } = getAllInvitationsDto; const payload = { id, status, pageNumber, pageSize, search }; - return this.sendNats(this.serviceProxy, 'get-org-invitations', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-org-invitations', payload); } async acceptRejectInvitaion( acceptRejectInvitation: AcceptRejectInvitationDto, userId: string - ): Promise<{ response: string }> { + ): Promise { const payload = { acceptRejectInvitation, userId }; - return this.sendNats(this.serviceProxy, 'accept-reject-invitations', payload); + return this.sendNatsMessage(this.serviceProxy, 'accept-reject-invitations', payload); } async shareUserCertificate( shareUserCredentials: CreateUserCertificateDto - ): Promise<{ response: Buffer }> { + ): Promise { const payload = { shareUserCredentials}; - return this.sendNats(this.serviceProxy, 'share-user-certificate', payload); + return this.sendNatsMessage(this.serviceProxy, 'share-user-certificate', payload); } async get( getAllUsersDto: GetAllUsersDto - ): Promise<{ response: object }> { + ): Promise { const { pageNumber, pageSize, search } = getAllUsersDto; const payload = { pageNumber, pageSize, search }; - return this.sendNats(this.serviceProxy, 'fetch-users', payload); + return this.sendNatsMessage(this.serviceProxy, 'fetch-users', payload); } - async checkUserExist(userEmail: string): Promise<{ response: string }> { + async checkUserExist(userEmail: string): Promise { const payload = { userEmail }; - return this.sendNats(this.serviceProxy, 'check-user-exist', payload); + return this.sendNatsMessage(this.serviceProxy, 'check-user-exist', payload); } - async getUserActivities(userId: string, limit: number): Promise<{ response: object }> { + async getUserActivities(userId: string, limit: number): Promise { const payload = { userId, limit }; - return this.sendNats(this.serviceProxy, 'get-user-activity', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-user-activity', payload); } - async addPasskey(userEmail: string, userInfo: AddPasskeyDetails): Promise<{ response: string }> { + async addPasskey(userEmail: string, userInfo: AddPasskeyDetailsDto): Promise { const payload = { userEmail, userInfo }; - return this.sendNats(this.serviceProxy, 'add-passkey', payload); + return this.sendNatsMessage(this.serviceProxy, 'add-passkey', payload); } - async updatePlatformSettings(platformSettings: UpdatePlatformSettingsDto): Promise<{ response: string }> { + async updatePlatformSettings(platformSettings: UpdatePlatformSettingsDto): Promise { const payload = { platformSettings }; - return this.sendNats(this.serviceProxy, 'update-platform-settings', payload); + return this.sendNatsMessage(this.serviceProxy, 'update-platform-settings', payload); } - async getPlatformSettings(): Promise<{ response: object }> { - return this.sendNats(this.serviceProxy, 'fetch-platform-settings', ''); + async getPlatformSettings(): Promise { + return this.sendNatsMessage(this.serviceProxy, 'fetch-platform-settings', ''); } } diff --git a/apps/api-gateway/src/verification/dto/get-all-proof-requests.dto.ts b/apps/api-gateway/src/verification/dto/get-all-proof-requests.dto.ts index 5ed8cc376..6c8a8920d 100644 --- a/apps/api-gateway/src/verification/dto/get-all-proof-requests.dto.ts +++ b/apps/api-gateway/src/verification/dto/get-all-proof-requests.dto.ts @@ -1,27 +1,41 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { Type } from "class-transformer"; -import { IsOptional } from "class-validator"; -import { SortValue } from "../../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 '../enum/verification.enum'; export class GetAllProofRequestsDto { - @ApiProperty({ required: false }) + @ApiProperty({ required: false, example: '1' }) @IsOptional() pageNumber: number = 1; - + @ApiProperty({ required: false }) @IsOptional() + @Transform(({ value }) => trim(value)) @Type(() => String) searchByText: string = ''; - - @ApiProperty({ required: false }) + + @ApiProperty({ required: false, example: '10' }) @IsOptional() pageSize: number = 10; - - @ApiProperty({ required: false }) + + @ApiProperty({ + enum: [SortValue.DESC, SortValue.ASC], + required: false + }) + @Transform(({ value }) => trim(value)) @IsOptional() - sortByValue: string = SortValue.DESC; - - @ApiProperty({ required: false }) + @IsEnum(SortValue) + sortBy: string = SortValue.DESC; + + @ApiProperty({ + required: false + }) + @Transform(({ value }) => trim(value)) @IsOptional() - sorting: string = 'id'; + @IsEnum(SortFields) + sortField: string = SortFields.CREATED_DATE_TIME; + } + diff --git a/apps/api-gateway/src/verification/dto/webhook-proof.dto.ts b/apps/api-gateway/src/verification/dto/webhook-proof.dto.ts index 7a871e5eb..5b547c85a 100644 --- a/apps/api-gateway/src/verification/dto/webhook-proof.dto.ts +++ b/apps/api-gateway/src/verification/dto/webhook-proof.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from "@nestjs/swagger"; +import { ApiPropertyOptional } from "@nestjs/swagger"; import { IsOptional } from "class-validator"; interface IWebhookPresentationProof { @@ -7,57 +7,57 @@ interface IWebhookPresentationProof { connectionId } -export class WebhookPresentationProof { +export class WebhookPresentationProofDto { - @ApiProperty() + @ApiPropertyOptional() @IsOptional() metadata: object; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() _tags: IWebhookPresentationProof; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() id: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() createdAt: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() protocolVersion: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() state: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() connectionId: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() threadId: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() presentationId: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() autoAcceptProof: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() updatedAt: string; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() isVerified: boolean; - @ApiProperty() + @ApiPropertyOptional() @IsOptional() contextCorrelationId: string; } \ No newline at end of file diff --git a/apps/api-gateway/src/verification/enum/verification.enum.ts b/apps/api-gateway/src/verification/enum/verification.enum.ts new file mode 100644 index 000000000..bcb8ab6fc --- /dev/null +++ b/apps/api-gateway/src/verification/enum/verification.enum.ts @@ -0,0 +1,6 @@ +export enum SortFields { + CREATED_DATE_TIME = 'createDateTime', + STATUS = 'state', + CONNECTION_ID = 'connectionId', + PRESENTATION_ID = 'presentationId' +} \ No newline at end of file diff --git a/apps/api-gateway/src/verification/interfaces/verification.interface.ts b/apps/api-gateway/src/verification/interfaces/verification.interface.ts index b23294011..118b2a977 100644 --- a/apps/api-gateway/src/verification/interfaces/verification.interface.ts +++ b/apps/api-gateway/src/verification/interfaces/verification.interface.ts @@ -8,11 +8,12 @@ export interface IProofRequestAttribute { credentialName: string; } -export interface IProofRequestsSearchCriteria { +export interface IProofRequestSearchCriteria { pageNumber: number; pageSize: number; - sorting: string; - sortByValue: string; + sortField: string; + sortBy: string; searchByText: string; user?: IUserRequestInterface } + diff --git a/apps/api-gateway/src/verification/verification.controller.ts b/apps/api-gateway/src/verification/verification.controller.ts index 86746f412..34b5edf86 100644 --- a/apps/api-gateway/src/verification/verification.controller.ts +++ b/apps/api-gateway/src/verification/verification.controller.ts @@ -18,7 +18,7 @@ import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; import { OutOfBandRequestProof, RequestProof } from './dto/request-proof.dto'; import { VerificationService } from './verification.service'; -import IResponseType from '@credebl/common/interfaces/response.interface'; +import IResponseType, { IResponse } from '@credebl/common/interfaces/response.interface'; import { Response } from 'express'; import { ResponseMessages } from '@credebl/common/response-messages'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; @@ -26,12 +26,13 @@ import { Roles } from '../authz/decorators/roles.decorator'; import { OrgRoles } from 'libs/org-roles/enums'; import { AuthGuard } from '@nestjs/passport'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; -import { WebhookPresentationProof } from './dto/webhook-proof.dto'; +import { WebhookPresentationProofDto } from './dto/webhook-proof.dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; import { ImageServiceService } from '@credebl/image-service'; import { User } from '../authz/decorators/user.decorator'; import { GetAllProofRequestsDto } from './dto/get-all-proof-requests.dto'; -import { IProofRequestsSearchCriteria } from './interfaces/verification.interface'; +import { IProofRequestSearchCriteria } from './interfaces/verification.interface'; +import { SortFields } from './enum/verification.enum'; @UseFilters(CustomExceptionFilter) @Controller() @@ -107,43 +108,21 @@ export class VerificationController { * Get all proof presentations * @param user * @param orgId - * @returns Get all proof presentation + * @returns All proof presentations details */ @Get('/orgs/:orgId/proofs') @ApiOperation({ - summary: `Get all proof presentations`, - description: `Get all proof presentations` - }) - - @ApiQuery({ - name: 'pageNumber', - type: Number, - required: false + summary: `Get all proof presentations by orgId`, + description: `Get all proof presentations by orgId` }) @ApiQuery({ - name: 'pageSize', - type: Number, + name: 'sortField', + enum: SortFields, required: false - }) - @ApiQuery({ - name: 'searchByText', - type: String, - required: false - }) - @ApiQuery({ - name: 'sorting', - type: String, - required: false - }) - @ApiQuery({ - name: 'sortByValue', - type: String, - required: false - }) - - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiBearerAuth() @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @@ -152,21 +131,21 @@ export class VerificationController { @Res() res: Response, @User() user: IUserRequest, @Param('orgId') orgId: string - ): Promise { - const { pageSize, searchByText, pageNumber, sorting, sortByValue } = getAllProofRequests; - const proofRequestsSearchCriteria: IProofRequestsSearchCriteria = { + ): Promise { + const { pageSize, searchByText, pageNumber, sortField, sortBy } = getAllProofRequests; + const proofRequestsSearchCriteria: IProofRequestSearchCriteria = { pageNumber, searchByText, pageSize, - sorting, - sortByValue + sortField, + sortBy }; const proofPresentationDetails = await this.verificationService.getProofPresentations(proofRequestsSearchCriteria, user, orgId); - const finalResponse: IResponseType = { + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.verification.success.fetch, - data: proofPresentationDetails.response + data: proofPresentationDetails }; return res.status(HttpStatus.OK).json(finalResponse); } @@ -282,26 +261,31 @@ export class VerificationController { return res.status(HttpStatus.CREATED).json(finalResponse); } - @Post('wh/:id/proofs') + /** + * + * @param orgId + * @returns Proof presentation details + */ + @Post('wh/:orgId/proofs') @ApiOperation({ - summary: `Webhook proof presentation`, - description: `Webhook proof presentation` + summary: `Receive webhook proof presentation`, + description: `Handle proof presentations for a specified organization via a webhook` }) @ApiExcludeEndpoint() - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) async webhookProofPresentation( - @Param('id') id: string, - @Body() proofPresentationPayload: WebhookPresentationProof, + @Param('orgId') orgId: string, + @Body() proofPresentationPayload: WebhookPresentationProofDto, @Res() res: Response - ): Promise { + ): Promise { this.logger.debug(`proofPresentationPayload ::: ${JSON.stringify(proofPresentationPayload)}`); - const webhookProofPresentation = await this.verificationService.webhookProofPresentation(id, proofPresentationPayload); - const finalResponse: IResponseType = { + const webhookProofPresentation = await this.verificationService.webhookProofPresentation(orgId, proofPresentationPayload); + const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, - message: ResponseMessages.verification.success.fetch, - data: webhookProofPresentation.response + message: ResponseMessages.verification.success.create, + data: webhookProofPresentation }; return res.status(HttpStatus.CREATED).json(finalResponse); } diff --git a/apps/api-gateway/src/verification/verification.service.ts b/apps/api-gateway/src/verification/verification.service.ts index 3148fcd64..66cc67016 100644 --- a/apps/api-gateway/src/verification/verification.service.ts +++ b/apps/api-gateway/src/verification/verification.service.ts @@ -3,8 +3,9 @@ import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { OutOfBandRequestProof, RequestProof } from './dto/request-proof.dto'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; -import { WebhookPresentationProof } from './dto/webhook-proof.dto'; -import { IProofRequestsSearchCriteria } from './interfaces/verification.interface'; +import { WebhookPresentationProofDto } from './dto/webhook-proof.dto'; +import { IProofRequestSearchCriteria } from './interfaces/verification.interface'; +import { IProofPresentationList } from '@credebl/common/interfaces/verification.interface'; @Injectable() @@ -18,13 +19,11 @@ export class VerificationService extends BaseService { /** * Get all proof presentations * @param orgId - * @param user - * @returns Get all proof presentation + * @returns All proof presentations details */ - - getProofPresentations(proofRequestsSearchCriteria: IProofRequestsSearchCriteria, user: IUserRequest, orgId: string): Promise<{ response: object }> { + getProofPresentations(proofRequestsSearchCriteria: IProofRequestSearchCriteria, user: IUserRequest, orgId: string): Promise { const payload = { proofRequestsSearchCriteria, user, orgId }; - return this.sendNats(this.verificationServiceProxy, 'get-proof-presentations', payload); + return this.sendNatsMessage(this.verificationServiceProxy, 'get-all-proof-presentations', payload); } /** @@ -62,9 +61,9 @@ export class VerificationService extends BaseService { return this.sendNats(this.verificationServiceProxy, 'verify-presentation', payload); } - webhookProofPresentation(id: string, proofPresentationPayload: WebhookPresentationProof): Promise<{ response: object }> { - const payload = { id, proofPresentationPayload }; - return this.sendNats(this.verificationServiceProxy, 'webhook-proof-presentation', payload); + webhookProofPresentation(orgId: string, proofPresentationPayload: WebhookPresentationProofDto): Promise { + const payload = { orgId, proofPresentationPayload }; + return this.sendNatsMessage(this.verificationServiceProxy, 'webhook-proof-presentation', payload); } /** diff --git a/apps/connection/src/connection.controller.ts b/apps/connection/src/connection.controller.ts index 3bb9a2572..73f20e676 100644 --- a/apps/connection/src/connection.controller.ts +++ b/apps/connection/src/connection.controller.ts @@ -3,42 +3,34 @@ import { ConnectionService } from './connection.service'; // Import the common s import { MessagePattern } from '@nestjs/microservices'; // Import the nestjs microservices package import { IConnection, - IConnectionInterface, + ICreateConnection, IFetchConnectionById, - IFetchConnectionInterface + IFetchConnections } from './interfaces/connection.interfaces'; +import { IConnectionList, ICreateConnectionUrl } from '@credebl/common/interfaces/connection.interface'; +import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; @Controller() export class ConnectionController { constructor(private readonly connectionService: ConnectionService) {} /** - * Description: Create out-of-band connection legacy invitation + * Create connection legacy invitation URL * @param payload - * @returns Created connection invitation for out-of-band + * @returns connection invitation URL */ @MessagePattern({ cmd: 'create-connection' }) - async createLegacyConnectionInvitation(payload: IConnection): Promise { - const { orgId, user, multiUseInvitation, autoAcceptConnection, alias, imageUrl, label } = payload; - - return this.connectionService.createLegacyConnectionInvitation( - orgId, - user, - multiUseInvitation, - autoAcceptConnection, - alias, - imageUrl, - label - ); + async createLegacyConnectionInvitation(payload: IConnection): Promise { + return this.connectionService.createLegacyConnectionInvitation(payload); } /** - * Description: Catch connection webhook responses and save details in connection table - * @param payload + * Receive connection webhook responses and save details in connection table + * @param orgId * @returns Callback URL for connection and created connections details */ @MessagePattern({ cmd: 'webhook-get-connection' }) - async getConnectionWebhook(payload: IConnectionInterface): Promise { + async getConnectionWebhook(payload: ICreateConnection): Promise { return this.connectionService.getConnectionWebhook(payload); } @@ -53,13 +45,19 @@ export class ConnectionController { } @MessagePattern({ cmd: 'get-all-connections' }) - async getConnections(payload: IFetchConnectionInterface): Promise { + async getConnections(payload: IFetchConnections): Promise { const { user, orgId, connectionSearchCriteria } = payload; return this.connectionService.getConnections(user, orgId, connectionSearchCriteria); } - @MessagePattern({ cmd: 'get-all-connections-by-connectionId' }) - async getConnectionsById(payload: IFetchConnectionById): Promise { + /** + * + * @param connectionId + * @param orgId + * @returns connection details by connection Id + */ + @MessagePattern({ cmd: 'get-connection-details-by-connectionId' }) + async getConnectionsById(payload: IFetchConnectionById): Promise { const { user, connectionId, orgId } = payload; return this.connectionService.getConnectionsById(user, connectionId, orgId); } diff --git a/apps/connection/src/connection.module.ts b/apps/connection/src/connection.module.ts index 076be8311..5182aa12d 100644 --- a/apps/connection/src/connection.module.ts +++ b/apps/connection/src/connection.module.ts @@ -1,3 +1,4 @@ +/* eslint-disable array-bracket-spacing */ import { Logger, Module } from '@nestjs/common'; import { ConnectionController } from './connection.controller'; import { ConnectionService } from './connection.service'; @@ -5,6 +6,7 @@ import { ClientsModule, Transport } from '@nestjs/microservices'; import { CommonModule } from '@credebl/common'; import { ConnectionRepository } from './connection.repository'; import { PrismaService } from '@credebl/prisma-service'; +import { CacheModule } from '@nestjs/cache-manager'; import { getNatsOptions } from '@credebl/common/nats.config'; // import { nkeyAuthenticator } from 'nats'; @@ -18,7 +20,8 @@ import { getNatsOptions } from '@credebl/common/nats.config'; } ]), - CommonModule + CommonModule, + CacheModule.register() ], controllers: [ConnectionController], providers: [ConnectionService, ConnectionRepository, PrismaService, Logger] diff --git a/apps/connection/src/connection.repository.ts b/apps/connection/src/connection.repository.ts index 2465982c6..d231e8a91 100644 --- a/apps/connection/src/connection.repository.ts +++ b/apps/connection/src/connection.repository.ts @@ -2,8 +2,10 @@ import { Injectable, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase import { agent_invitations, org_agents, platform_config, shortening_url } from '@prisma/client'; -import { IConnectionInterface, IConnectionSearchCriteria, OrgAgent } from './interfaces/connection.interfaces'; +import { IConnectionSearchCriteria, ICreateConnection, OrgAgent } from './interfaces/connection.interfaces'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; +import { IConnectionsListCount } from '@credebl/common/interfaces/connection.interface'; +import { SortValue } from '@credebl/enum/enum'; // import { OrgAgent } from './interfaces/connection.interfaces'; @Injectable() export class ConnectionRepository { @@ -93,7 +95,7 @@ export class ConnectionRepository { * @returns Get connection details */ // eslint-disable-next-line camelcase - async saveConnectionWebhook(payload: IConnectionInterface): Promise { + async saveConnectionWebhook(payload: ICreateConnection): Promise { try { let organisationId: string; @@ -245,17 +247,7 @@ export class ConnectionRepository { user: IUserRequest, orgId: string, connectionSearchCriteria: IConnectionSearchCriteria - ): Promise<{ - connectionCount: number; - connectionsList: { - createDateTime: Date; - createdBy: string; - connectionId: string; - theirLabel: string; - state: string; - orgId: string; - }[]; - }> { + ): Promise { try { const connectionsList = await this.prisma.connections.findMany({ where: { @@ -274,10 +266,7 @@ export class ConnectionRepository { connectionId: true }, orderBy: { - [connectionSearchCriteria?.sorting || 'createDateTime']: - 'DESC' === connectionSearchCriteria?.sortByValue - ? 'desc' - : 'asc' + [connectionSearchCriteria.sortField]: SortValue.ASC === connectionSearchCriteria.sortBy ? 'asc' : 'desc' }, take: Number(connectionSearchCriteria.pageSize), skip: (connectionSearchCriteria.pageNumber - 1) * connectionSearchCriteria.pageSize diff --git a/apps/connection/src/connection.service.ts b/apps/connection/src/connection.service.ts index c613768e3..ced271cca 100644 --- a/apps/connection/src/connection.service.ts +++ b/apps/connection/src/connection.service.ts @@ -5,16 +5,19 @@ import { HttpException, Inject, Injectable, Logger, NotFoundException } from '@n import { ClientProxy, RpcException } from '@nestjs/microservices'; import { map } from 'rxjs'; import { - ConnectionInvitationResponse, - IConnectionInterface, + IConnection, + IConnectionInvitation, IConnectionSearchCriteria, - IUserRequestInterface + ICreateConnection } from './interfaces/connection.interfaces'; import { ConnectionRepository } from './connection.repository'; import { ResponseMessages } from '@credebl/common/response-messages'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { OrgAgentType } from '@credebl/enum/enum'; -import { platform_config } from '@prisma/client'; +import { Cache } from 'cache-manager'; +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'; @Injectable() export class ConnectionService { @@ -22,34 +25,26 @@ export class ConnectionService { private readonly commonService: CommonService, @Inject('NATS_CLIENT') private readonly connectionServiceProxy: ClientProxy, private readonly connectionRepository: ConnectionRepository, - private readonly logger: Logger - ) {} + private readonly logger: Logger, + @Inject(CACHE_MANAGER) private cacheService: Cache + ) { } /** - * Description: create connection legacy invitation + * Create connection legacy invitation URL * @param orgId * @param user * @returns Connection legacy invitation URL */ - async createLegacyConnectionInvitation( - orgId: string, - user: IUserRequestInterface, - multiUseInvitation: boolean, - autoAcceptConnection: boolean, - alias: string, - imageUrl: string, - label: string - ): Promise { + async createLegacyConnectionInvitation(payload: IConnection): Promise { + + const {orgId, multiUseInvitation, autoAcceptConnection, alias, label} = payload; try { const connectionInvitationExist = await this.connectionRepository.getConnectionInvitationByOrgId(orgId); - if (connectionInvitationExist) { return connectionInvitationExist; } const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); - - const platformConfig: platform_config = await this.connectionRepository.getPlatformConfigDetails(); const { agentEndPoint, id, organisation } = agentDetails; const agentId = id; if (!agentDetails) { @@ -72,12 +67,16 @@ export class ConnectionService { const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); const url = await this.getAgentUrl(orgAgentType, agentEndPoint, agentDetails?.tenantId); - const apiKey = platformConfig?.sgApiKey; + // const apiKey = platformConfig?.sgApiKey; + const apiKey = await this._getOrgAgentApiKey(orgId); + // let apiKey:string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + // this.logger.log(`cachedApiKey----getConnections,${apiKey}`); + //if(!apiKey || apiKey === null || apiKey === undefined) { + // apiKey = await this._getOrgAgentApiKey(orgId); + // } const createConnectionInvitation = await this._createConnectionInvitation(connectionPayload, url, apiKey); - const invitationObject = createConnectionInvitation?.message?.invitation['@id']; - let shortenedUrl; if (agentDetails?.tenantId) { shortenedUrl = `${agentEndPoint}/multi-tenancy/url/${agentDetails?.tenantId}/${invitationObject}`; @@ -90,7 +89,6 @@ export class ConnectionService { agentId, orgId ); - return saveConnectionDetails; } catch (error) { this.logger.error(`[createLegacyConnectionInvitation] - error in connection invitation: ${error}`); @@ -108,12 +106,11 @@ export class ConnectionService { } /** - * Description: create connection legacy invitation + * Description: Catch connection webhook responses and save details in connection table * @param orgId - * @param user - * @returns Connection legacy invitation URL + * @returns Callback URL for connection and created connections details */ - async getConnectionWebhook(payload: IConnectionInterface): Promise { + async getConnectionWebhook(payload: ICreateConnection): Promise { try { const saveConnectionDetails = await this.connectionRepository.saveConnectionWebhook(payload); return saveConnectionDetails; @@ -124,16 +121,17 @@ export class ConnectionService { } /** - * Description: Store shortening URL - * @param referenceId - * @param url + * Store shortening URL + * @param orgId * @returns connection invitation URL */ async _createConnectionInvitation( 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 }; @@ -188,28 +186,18 @@ export class ConnectionService { user: IUserRequest, orgId: string, connectionSearchCriteria: IConnectionSearchCriteria - ): Promise<{ - totalItems: number; - hasNextPage: boolean; - hasPreviousPage: boolean; - nextPage: number; - previousPage: number; - lastPage: number; - data: { - createDateTime: Date; - createdBy: string; - connectionId: string; - theirLabel: string; - state: string; - orgId: string; - }[]; - }> { + ): Promise { try { const getConnectionList = await this.connectionRepository.getAllConnections( user, orgId, connectionSearchCriteria ); + + if (0 === getConnectionList.connectionCount) { + throw new NotFoundException(ResponseMessages.connection.error.connectionNotFound); + } + const connectionResponse: { totalItems: number; hasNextPage: boolean; @@ -235,20 +223,15 @@ export class ConnectionService { lastPage: Math.ceil(getConnectionList.connectionCount / connectionSearchCriteria.pageSize), data: getConnectionList.connectionsList }; - - if (0 !== getConnectionList.connectionCount) { return connectionResponse; - } else { - throw new NotFoundException(ResponseMessages.connection.error.connectionNotFound); - } } catch (error) { -; if (404 === error.status) { - throw new NotFoundException(error.response.message); - } - throw new RpcException( + + this.logger.error( `[getConnections] [NATS call]- error in fetch connections details : ${JSON.stringify(error)}` ); - } + + throw new RpcException(error.response ? error.response : error); + } } async _getAllConnections( @@ -286,11 +269,11 @@ export class ConnectionService { } } - async getConnectionsById(user: IUserRequest, connectionId: string, orgId: string): Promise { + async getConnectionsById(user: IUserRequest, connectionId: string, orgId: string): Promise { try { const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); - const platformConfig: platform_config = await this.connectionRepository.getPlatformConfigDetails(); + // const platformConfig: platform_config = await this.connectionRepository.getPlatformConfigDetails(); const { agentEndPoint } = agentDetails; if (!agentDetails) { @@ -308,18 +291,25 @@ export class ConnectionService { throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); } - const apiKey = platformConfig?.sgApiKey; + + // const apiKey = await this._getOrgAgentApiKey(orgId); + let apiKey:string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + this.logger.log(`cachedApiKey----getConnectionsById,${apiKey}`); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } const createConnectionInvitation = await this._getConnectionsByConnectionId(url, apiKey); - return createConnectionInvitation?.response; + return createConnectionInvitation; + + } catch (error) { this.logger.error(`[getConnectionsById] - error in get connections : ${JSON.stringify(error)}`); - if (error && error?.status && error?.status?.message && error?.status?.message?.error) { + if (error?.response?.error?.reason) { throw new RpcException({ - message: error?.status?.message?.error?.reason - ? error?.status?.message?.error?.reason - : error?.status?.message?.error, - statusCode: error?.status?.code + message: ResponseMessages.connection.error.connectionNotFound, + statusCode: error?.response?.status, + error: error?.response?.error?.reason }); } else { throw new RpcException(error.response ? error.response : error); @@ -330,37 +320,27 @@ export class ConnectionService { async _getConnectionsByConnectionId( url: string, apiKey: string - ): Promise<{ - response: string; - }> { - try { - const pattern = { cmd: 'agent-get-connections-by-connectionId' }; + ): 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) - .pipe( - map((response) => ({ - response - })) - ) + .send(pattern, payload) .toPromise() - .catch((error) => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( + .catch(error => { + this.logger.error( + `[_getConnectionsByConnectionId] [NATS call]- error in fetch connections : ${JSON.stringify(error)}` + ); + throw new HttpException( { - status: error.statusCode, - error: error.message - }, - error.error - ); + status: error.statusCode, + error: error.error?.message?.error ? error.error?.message?.error : error.error, + message: error.message + }, error.error); }); - } catch (error) { - this.logger.error( - `[_getConnectionsByConnectionId] [NATS call]- error in fetch connections : ${JSON.stringify(error)}` - ); - throw error; - } } + /** * Description: Fetch agent url * @param referenceId @@ -382,4 +362,22 @@ export class ConnectionService { throw error; } } + + async _getOrgAgentApiKey(orgId: string): Promise { + const pattern = { cmd: 'get-org-agent-api-key' }; + const payload = { orgId }; + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.connectionServiceProxy.send(pattern, payload).toPromise(); + return message; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException({ + status: error.status, + error: error.message + }, error.status); + } + } } + diff --git a/apps/connection/src/enum/connection.enum.ts b/apps/connection/src/enum/connection.enum.ts new file mode 100644 index 000000000..6e42e5d6d --- /dev/null +++ b/apps/connection/src/enum/connection.enum.ts @@ -0,0 +1,5 @@ +export enum SortFields { + CREATED_DATE_TIME = 'createDateTime', + USER_LABEL = 'theirLabel', + CONNECTION_ID = 'connectionId' +} \ No newline at end of file diff --git a/apps/connection/src/interfaces/connection.interfaces.ts b/apps/connection/src/interfaces/connection.interfaces.ts index 2bbedae60..4b2ac1420 100644 --- a/apps/connection/src/interfaces/connection.interfaces.ts +++ b/apps/connection/src/interfaces/connection.interfaces.ts @@ -51,12 +51,12 @@ export interface IOrgAgentInterface { orgId: string; } -export class IConnectionInterface { - connectionDto: ConnectionPayload; +export interface ICreateConnection { + connectionDto: ICreateConnectionPayload; orgId: string; } -export class ConnectionPayload { +export interface ICreateConnectionPayload { createDateTime: string; lastChangedDateTime: string; id: string; @@ -69,7 +69,7 @@ export class ConnectionPayload { contextCorrelationId: string; } -export class IFetchConnectionInterface { +export interface IFetchConnections { connectionSearchCriteria: IConnectionSearchCriteria; user: IUserRequest; orgId: string; @@ -87,12 +87,13 @@ export interface IFetchConnectionUrlById { orgId: string; } -export interface ConnectionInvitationResponse { - message: { - invitation: object; - }; +export interface IConnectionInvitation { + message: IInvitation; } +interface IInvitation { + invitation: string; +} export interface OrgAgent { organisation: organisation; id: string; @@ -109,17 +110,11 @@ export interface OrgAgent { orgAgentTypeId: string; tenantId: string; } -export interface IConnectionSearchInterface { - schemaSearchCriteria: IConnectionSearchCriteria, - user: IUserRequestInterface, - orgId: string -} export interface IConnectionSearchCriteria { pageNumber: number; pageSize: number; - sorting: string; - sortByValue: string; + sortField: string; + sortBy: string; searchByText: string; user: IUserRequestInterface -} - +} \ No newline at end of file diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts index 438091d0f..41902cb3d 100644 --- a/apps/ecosystem/interfaces/ecosystem.interfaces.ts +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -1,4 +1,4 @@ -import { Prisma } from "@prisma/client"; +import { Prisma } from '@prisma/client'; export interface AttributeValue { attributeName: string; schemaDataType: string; @@ -15,7 +15,7 @@ export interface RequestSchemaEndorsement { } export interface RequestCredDeffEndorsement { - schemaId: string + schemaId: string; tag: string; endorse?: boolean; schemaDetails?: object; @@ -25,7 +25,7 @@ export interface RequestCredDeffEndorsement { export interface IAttributeValue { attributeName: string; schemaDataType: string; - displayName: string + displayName: string; } export interface SchemaTransactionPayload { @@ -96,7 +96,7 @@ export interface EndorsementTransactionPayload { authorDid: string; requestPayload: string; responsePayload: string; - requestBody: Prisma.JsonValue + requestBody: Prisma.JsonValue; status: string; ecosystemOrgId: string; createDateTime: Date; @@ -132,7 +132,6 @@ export interface submitTransactionPayload { credentialDefinition?: CredentialDefinitionPayload; } - export interface SaveSchema { name: string; version: string; @@ -165,7 +164,7 @@ export interface EndorsementTransactionPayloadDetails { responsePayload: string; type: string; createDateTime: Date; - createdBy:string; + createdBy: string; lastChangedDateTime: Date; lastChangedBy: string; deletedAt: Date | null; @@ -280,3 +279,9 @@ export interface EcoInvitationsPagination { totalPages: number; } +export interface TransactionPayload { + endorsementId: string; + ecosystemId: string; + ecosystemLeadAgentEndPoint?: string; + orgId?: string; +} diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 8dc75f5b3..09dff889d 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -12,7 +12,7 @@ import { RequestCredDeffEndorsement, RequestSchemaEndorsement } from '../interfa @Controller() export class EcosystemController { - constructor(private readonly ecosystemService: EcosystemService) { } + constructor(private readonly ecosystemService: EcosystemService) {} private readonly logger = new Logger('EcosystemController'); /** @@ -32,7 +32,7 @@ export class EcosystemController { * @returns Get updated ecosystem details */ @MessagePattern({ cmd: 'edit-ecosystem' }) - async editEcosystem(@Body() payload: { editEcosystemDto, ecosystemId }): Promise { + async editEcosystem(@Body() payload: { editEcosystemDto; ecosystemId }): Promise { return this.ecosystemService.editEcosystem(payload.editEcosystemDto, payload.ecosystemId); } @@ -42,9 +42,7 @@ export class EcosystemController { * @returns Get all ecosystem details */ @MessagePattern({ cmd: 'get-all-ecosystem' }) - async getAllEcosystems( - @Body() payload: { orgId: string } - ): Promise { + async getAllEcosystems(@Body() payload: { orgId: string }): Promise { return this.ecosystemService.getAllEcosystem(payload); } @@ -53,19 +51,17 @@ export class EcosystemController { * @returns Get ecosystem dashboard details */ @MessagePattern({ cmd: 'get-ecosystem-dashboard-details' }) - async getEcosystemDashboardDetails( - payload: { ecosystemId: string; orgId: string }): Promise { + async getEcosystemDashboardDetails(payload: { ecosystemId: string; orgId: string }): Promise { return this.ecosystemService.getEcosystemDashboardDetails(payload.ecosystemId); } - /** * Description: get ecosystem invitations * @returns Get sent invitation details */ @MessagePattern({ cmd: 'get-ecosystem-invitations' }) async getEcosystemInvitations( - @Body() payload: { userEmail: string, status: string; pageNumber: number; pageSize: number; search: string } + @Body() payload: { userEmail: string; status: string; pageNumber: number; pageSize: number; search: string } ): Promise { return this.ecosystemService.getEcosystemInvitations( payload.userEmail, @@ -82,23 +78,21 @@ export class EcosystemController { * @returns ecosystem members list */ @MessagePattern({ cmd: 'fetch-ecosystem-members' }) - async getEcosystemMembers( - @Body() payload: EcosystemMembersPayload - ): Promise { - return this.ecosystemService.getEcoystemMembers( - payload - ); + async getEcosystemMembers(@Body() payload: EcosystemMembersPayload): Promise { + return this.ecosystemService.getEcoystemMembers(payload); } /** - * - * @param payload + * + * @param payload * @returns Sent ecosystem invitations status */ @MessagePattern({ cmd: 'send-ecosystem-invitation' }) - async createInvitation( - payload: { bulkInvitationDto: BulkSendInvitationDto; userId: string, userEmail: string } - ): Promise { + async createInvitation(payload: { + bulkInvitationDto: BulkSendInvitationDto; + userId: string; + userEmail: string; + }): Promise { return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId, payload.userEmail); } @@ -114,117 +108,107 @@ export class EcosystemController { return this.ecosystemService.acceptRejectEcosystemInvitations(payload.acceptRejectInvitation); } - @MessagePattern({ cmd: 'get-sent-invitations-ecosystemId' }) - async getInvitationsByOrgId( - @Body() payload: FetchInvitationsPayload - ): Promise { - return this.ecosystemService.getInvitationsByEcosystemId( - payload - ); + async getInvitationsByOrgId(@Body() payload: FetchInvitationsPayload): Promise { + return this.ecosystemService.getInvitationsByEcosystemId(payload); } @MessagePattern({ cmd: 'get-endorsement-transactions' }) - async getEndorsementTransactions( - @Body() payload: GetEndorsementsPayload - ): Promise { - return this.ecosystemService.getEndorsementTransactions( - payload - ); + async getEndorsementTransactions(@Body() payload: GetEndorsementsPayload): Promise { + return this.ecosystemService.getEndorsementTransactions(payload); } - @MessagePattern({ cmd: 'get-all-ecosystem-schemas' }) - async getAllEcosystemSchemas( - @Body() payload: GetEndorsementsPayload - ): Promise { - return this.ecosystemService.getAllEcosystemSchemas( - payload - ); + async getAllEcosystemSchemas(@Body() payload: GetEndorsementsPayload): Promise { + return this.ecosystemService.getAllEcosystemSchemas(payload); } @MessagePattern({ cmd: 'delete-ecosystem-invitations' }) - async deleteInvitation( - @Body() payload: { invitationId: string } - ): Promise { - return this.ecosystemService.deleteEcosystemInvitations( - payload.invitationId - ); + async deleteInvitation(@Body() payload: { invitationId: string }): Promise { + return this.ecosystemService.deleteEcosystemInvitations(payload.invitationId); } @MessagePattern({ cmd: 'fetch-ecosystem-org-data' }) - async fetchEcosystemOrg( - @Body() payload: { ecosystemId: string, orgId: string } - ): Promise { - return this.ecosystemService.fetchEcosystemOrg( - payload - ); + async fetchEcosystemOrg(@Body() payload: { ecosystemId: string; orgId: string }): Promise { + return this.ecosystemService.fetchEcosystemOrg(payload); } /** - * - * @param payload - * @returns Schema endorsement request - */ + * + * @param payload + * @returns Schema endorsement request + */ @MessagePattern({ cmd: 'schema-endorsement-request' }) - async schemaEndorsementRequest(payload: { requestSchemaPayload: RequestSchemaEndorsement; orgId: string, ecosystemId: string } - ): Promise { - return this.ecosystemService.requestSchemaEndorsement(payload.requestSchemaPayload, payload.orgId, payload.ecosystemId); + async schemaEndorsementRequest(payload: { + requestSchemaPayload: RequestSchemaEndorsement; + orgId: string; + ecosystemId: string; + }): Promise { + return this.ecosystemService.requestSchemaEndorsement( + payload.requestSchemaPayload, + payload.orgId, + payload.ecosystemId + ); } /** - * - * @param payload - * @returns Schema endorsement request - */ + * + * @param payload + * @returns Schema endorsement request + */ @MessagePattern({ cmd: 'credDef-endorsement-request' }) - async credDefEndorsementRequest(payload: { requestCredDefPayload: RequestCredDeffEndorsement; orgId: string; ecosystemId: string } - ): Promise { - return this.ecosystemService.requestCredDeffEndorsement(payload.requestCredDefPayload, payload.orgId, payload.ecosystemId); + async credDefEndorsementRequest(payload: { + requestCredDefPayload: RequestCredDeffEndorsement; + orgId: string; + ecosystemId: string; + }): Promise { + return this.ecosystemService.requestCredDeffEndorsement( + payload.requestCredDefPayload, + payload.orgId, + payload.ecosystemId + ); } /** - * - * @param payload - * @returns sign endorsement request - */ + * + * @param payload + * @returns sign endorsement request + */ @MessagePattern({ cmd: 'sign-endorsement-transaction' }) - async signTransaction(payload: { endorsementId: string, ecosystemId: string } - ): Promise { + async signTransaction(payload: { endorsementId: string; ecosystemId: string }): Promise { return this.ecosystemService.signTransaction(payload.endorsementId, payload.ecosystemId); } /** - * - * @param payload - * @returns submit endorsement request - */ + * + * @param payload + * @returns submit endorsement request + */ @MessagePattern({ cmd: 'sumbit-endorsement-transaction' }) - async submitTransaction(payload: { endorsementId: string, ecosystemId: string } - ): Promise { - return this.ecosystemService.submitTransaction(payload.endorsementId, payload.ecosystemId); + async submitTransaction(payload: { endorsementId: string; ecosystemId: string; orgId: string }): Promise { + return this.ecosystemService.submitTransaction({ + endorsementId: payload.endorsementId, + ecosystemId: payload.ecosystemId, + orgId: payload.orgId + }); } /** - * - * @param payload - * @returns auto sign and submit endorsement request - */ + * + * @param payload + * @returns auto sign and submit endorsement request + */ @MessagePattern({ cmd: 'auto-endorsement-transaction' }) async autoSignAndSubmitTransaction(): Promise { return this.ecosystemService.autoSignAndSubmitTransaction(); } /** - * - * @param payload - * @returns Declien Endorsement Transaction status - */ + * + * @param payload + * @returns Declien Endorsement Transaction status + */ @MessagePattern({ cmd: 'decline-endorsement-transaction' }) - async declineEndorsementRequestByLead(payload: { - ecosystemId: string, endorsementId: string - }): Promise { + async declineEndorsementRequestByLead(payload: { ecosystemId: string; endorsementId: string }): Promise { return this.ecosystemService.declineEndorsementRequestByLead(payload.ecosystemId, payload.endorsementId); } - - } diff --git a/apps/ecosystem/src/ecosystem.module.ts b/apps/ecosystem/src/ecosystem.module.ts index 74ee61499..1f11e9f7d 100644 --- a/apps/ecosystem/src/ecosystem.module.ts +++ b/apps/ecosystem/src/ecosystem.module.ts @@ -2,9 +2,10 @@ import { Logger, Module } from '@nestjs/common'; import { EcosystemController } from './ecosystem.controller'; import { EcosystemService } from './ecosystem.service'; import { ClientsModule, Transport } from '@nestjs/microservices'; -import { CommonModule } from '@credebl/common'; +import { CommonModule} from '@credebl/common'; import { EcosystemRepository } from './ecosystem.repository'; import { PrismaService } from '@credebl/prisma-service'; +import { CacheModule } from '@nestjs/cache-manager'; import { getNatsOptions } from '@credebl/common/nats.config'; @Module({ @@ -17,7 +18,8 @@ import { getNatsOptions } from '@credebl/common/nats.config'; } ]), - CommonModule + CommonModule, + CacheModule.register() ], controllers: [EcosystemController], providers: [EcosystemService, PrismaService, Logger, EcosystemRepository] diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 435fcbef8..d4ac164a8 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -1,6 +1,17 @@ /* eslint-disable prefer-destructuring */ // eslint-disable-next-line camelcase -import { BadRequestException, ConflictException, ForbiddenException, HttpException, Inject, Injectable, InternalServerErrorException, Logger, NotAcceptableException, NotFoundException } from '@nestjs/common'; +import { + BadRequestException, + ConflictException, + ForbiddenException, + HttpException, + Inject, + Injectable, + InternalServerErrorException, + Logger, + NotAcceptableException, + NotFoundException +} from '@nestjs/common'; import { EcosystemRepository } from './ecosystem.repository'; import { ResponseMessages } from '@credebl/common/response-messages'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; @@ -11,14 +22,22 @@ import { EmailDto } from '@credebl/common/dtos/email.dto'; import { sendEmail } from '@credebl/common/send-grid-helper-file'; import { AcceptRejectEcosystemInvitationDto } from '../dtos/accept-reject-ecosysteminvitation.dto'; import { EcosystemConfigSettings, Invitation, OrgAgentType } from '@credebl/enum/enum'; -import { EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; +import { + EcosystemOrgStatus, + EcosystemRoles, + endorsementTransactionStatus, + endorsementTransactionType +} from '../enums/ecosystem.enum'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; import { EcosystemMembersPayload } from '../interfaces/ecosystemMembers.interface'; -import { CreateEcosystem, CredDefMessage, LedgerDetails, OrganizationData, RequestCredDeffEndorsement, RequestSchemaEndorsement, SaveSchema, SchemaMessage, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; +import { CreateEcosystem, CredDefMessage, LedgerDetails, OrganizationData, RequestCredDeffEndorsement, RequestSchemaEndorsement, SaveSchema, SchemaMessage, SignedTransactionMessage, TransactionPayload, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; import { GetAllSchemaList, GetEndorsementsPayload } from '../interfaces/endorsements.interface'; import { CommonConstants } from '@credebl/common/common.constant'; // eslint-disable-next-line camelcase import { credential_definition, org_agents, platform_config, schema, user } from '@prisma/client'; +// import { CommonService } from '@credebl/common/common.service'; +import { Cache } from 'cache-manager'; +import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; @@ -28,9 +47,9 @@ export class EcosystemService { @Inject('NATS_CLIENT') private readonly ecosystemServiceProxy: ClientProxy, private readonly ecosystemRepository: EcosystemRepository, private readonly logger: Logger, - private readonly prisma: PrismaService - - ) { } + private readonly prisma: PrismaService, + @Inject(CACHE_MANAGER) private cacheService: Cache + ) {} /** * @@ -40,14 +59,15 @@ export class EcosystemService { // eslint-disable-next-line camelcase async createEcosystem(createEcosystemDto: CreateEcosystem): Promise { - const ecosystemExist = await this.ecosystemRepository.checkEcosystemNameExist(createEcosystemDto.name); if (ecosystemExist) { throw new ConflictException(ResponseMessages.ecosystem.error.exists); } - const isMultiEcosystemEnabled = await this.ecosystemRepository.getSpecificEcosystemConfig(EcosystemConfigSettings.MULTI_ECOSYSTEM); + const isMultiEcosystemEnabled = await this.ecosystemRepository.getSpecificEcosystemConfig( + EcosystemConfigSettings.MULTI_ECOSYSTEM + ); if (isMultiEcosystemEnabled && 'false' === isMultiEcosystemEnabled.value) { const ecoOrganizationList = await this.ecosystemRepository.checkEcosystemOrgs(createEcosystemDto.orgId); @@ -101,10 +121,10 @@ export class EcosystemService { /** - * - * @param editEcosystemDto - * @returns - */ + * + * @param editEcosystemDto + * @returns + */ // eslint-disable-next-line camelcase async editEcosystem(editEcosystemDto: CreateEcosystem, ecosystemId: string): Promise { @@ -163,9 +183,7 @@ export class EcosystemService { } }; - const ecosystemDetails = await this.ecosystemRepository.fetchEcosystemOrg( - query - ); + const ecosystemDetails = await this.ecosystemRepository.fetchEcosystemOrg(query); const dashboardDetails = { ecosystem: ecosystemDetails['ecosystem'], @@ -206,19 +224,21 @@ export class EcosystemService { /** - * Description: get an ecosystem invitation - * @returns Get sent ecosystem invitation details - */ + * Description: get an ecosystem invitation + * @returns Get sent ecosystem invitation details + */ // eslint-disable-next-line camelcase - async getEcosystemInvitations(userEmail: string, status: string, pageNumber: number, pageSize: number, search: string): Promise { - + async getEcosystemInvitations( + userEmail: string, + status: string, + pageNumber: number, + pageSize: number, + search: string + ): Promise { try { const query = { - AND: [ - { email: userEmail }, - { status: { contains: search, mode: 'insensitive' } } - ] + AND: [{ email: userEmail }, { status: { contains: search, mode: 'insensitive' } }] }; const ecosystemInvitations = await this.ecosystemRepository.getEcosystemInvitationsPagination(query, pageNumber, pageSize); @@ -247,12 +267,11 @@ export class EcosystemService { } } - /** - * - * @param bulkInvitationDto - * @param userId - * @returns + * + * @param bulkInvitationDto + * @param userId + * @returns */ async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: string, userEmail: string): Promise { const { invitations, ecosystemId } = bulkInvitationDto; @@ -295,7 +314,6 @@ export class EcosystemService { } return ResponseMessages.ecosystem.success.createInvitation; } catch (error) { - this.logger.error(`In send Invitation : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } @@ -327,17 +345,21 @@ export class EcosystemService { */ async acceptRejectEcosystemInvitations(acceptRejectInvitation: AcceptRejectEcosystemInvitationDto): Promise { try { - const isMultiEcosystemEnabled = await this.ecosystemRepository.getSpecificEcosystemConfig(EcosystemConfigSettings.MULTI_ECOSYSTEM); - if (isMultiEcosystemEnabled - && 'false' === isMultiEcosystemEnabled.value - && acceptRejectInvitation.status !== Invitation.REJECTED) { + const isMultiEcosystemEnabled = await this.ecosystemRepository.getSpecificEcosystemConfig( + EcosystemConfigSettings.MULTI_ECOSYSTEM + ); + if ( + isMultiEcosystemEnabled && + 'false' === isMultiEcosystemEnabled.value && + acceptRejectInvitation.status !== Invitation.REJECTED + ) { const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(acceptRejectInvitation.orgId); if (0 < checkOrganization.length) { throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); - }; + } } - const { orgId, status, invitationId, userId } = acceptRejectInvitation; + const { orgId, status, invitationId, orgName, orgDid, userId } = acceptRejectInvitation; const invitation = await this.ecosystemRepository.getEcosystemInvitationById(invitationId); if (!invitation) { @@ -370,20 +392,33 @@ export class EcosystemService { } const ecosystemRole = await this.ecosystemRepository.getEcosystemRole(EcosystemRoles.ECOSYSTEM_MEMBER); - const updateEcosystemOrgs = await this.updatedEcosystemOrgs(orgId, invitation.ecosystemId, ecosystemRole.id, userId); + const updateEcosystemOrgs = await this.updatedEcosystemOrgs( + orgId, + orgName, + orgDid, + invitation.ecosystemId, + ecosystemRole.id, + userId + ); if (!updateEcosystemOrgs) { throw new NotFoundException(ResponseMessages.ecosystem.error.orgsNotUpdate); } return ResponseMessages.ecosystem.success.invitationAccept; - } catch (error) { this.logger.error(`acceptRejectEcosystemInvitations: ${error}`); throw new RpcException(error.response ? error.response : error); } } - async updatedEcosystemOrgs(orgId: string, ecosystemId: string, ecosystemRoleId: string, userId: string): Promise { + async updatedEcosystemOrgs( + orgId: string, + orgName: string, + orgDid: string, + ecosystemId: string, + ecosystemRoleId: string, + userId: string + ): Promise { try { const data: updateEcosystemOrgsDto = { orgId, @@ -401,19 +436,17 @@ export class EcosystemService { } /** - * - * @param payload + * + * @param payload * @returns Updated invitation response */ async updateEcosystemInvitation(invitationId: string, orgId: string, status: string): Promise { try { - const data = { status, orgId: String(orgId) }; return this.ecosystemRepository.updateEcosystemInvitation(invitationId, data); - } catch (error) { this.logger.error(`In updateOrgInvitation : ${error}`); throw new RpcException(error.response ? error.response : error); @@ -421,17 +454,13 @@ export class EcosystemService { } /** - * - * @param email - * @param ecosystemId + * + * @param email + * @param ecosystemId * @returns Returns boolean status for invitation */ - async checkInvitationExist( - email: string, - ecosystemId: string - ): Promise { + async checkInvitationExist(email: string, ecosystemId: string): Promise { try { - const query = { email, ecosystemId @@ -462,16 +491,12 @@ export class EcosystemService { } /** - * - * @param email - * @param ecosystemName - * @returns Send invitation mail + * + * @param email + * @param ecosystemName + * @returns Send invitation mail */ - async sendInviteEmailTemplate( - email: string, - ecosystemName: string, - isUserExist: boolean - ): Promise { + async sendInviteEmailTemplate(email: string, ecosystemName: string, isUserExist: boolean): Promise { const platformConfigData = await this.prisma.platform_config.findMany(); const urlEmailTemplate = new EcosystemInviteTemplate(); @@ -513,11 +538,15 @@ export class EcosystemService { } /** - * - * @param RequestSchemaEndorsement - * @returns + * + * @param RequestSchemaEndorsement + * @returns */ - async requestSchemaEndorsement(requestSchemaPayload: RequestSchemaEndorsement, orgId: string, ecosystemId: string): Promise { + async requestSchemaEndorsement( + requestSchemaPayload: RequestSchemaEndorsement, + orgId: string, + ecosystemId: string + ): Promise { try { const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); @@ -572,14 +601,18 @@ export class EcosystemService { } if (!getEcosystemOrgDetailsByOrgId) { - throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemOrgNotFound); } const orgAgentType = await this.ecosystemRepository.getOrgAgentType(ecosystemMemberDetails.orgAgentTypeId); - const url = await this.getAgentUrl(orgAgentType, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.SCHEMA, ecosystemMemberDetails.tenantId); - - const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); + const url = await this.getAgentUrl( + orgAgentType, + ecosystemMemberDetails.agentEndPoint, + endorsementTransactionType.SCHEMA, + ecosystemMemberDetails.tenantId + ); + const apiKey = await this._getOrgAgentApiKey(orgId); + const attributeArray = requestSchemaPayload.attributes.map((item) => item.attributeName); const schemaTransactionPayload = { endorserDid: ecosystemLeadAgentDetails.orgDid, @@ -590,7 +623,11 @@ export class EcosystemService { issuerId: ecosystemMemberDetails.orgDid }; - const schemaTransactionRequest: SchemaMessage = await this._requestSchemaEndorsement(schemaTransactionPayload, url, platformConfig?.sgApiKey); + const schemaTransactionRequest: SchemaMessage = await this._requestSchemaEndorsement( + schemaTransactionPayload, + url, + apiKey + ); const schemaTransactionResponse = { endorserDid: ecosystemLeadAgentDetails.orgDid, @@ -601,32 +638,45 @@ export class EcosystemService { userId: requestSchemaPayload.userId }; - if ('failed' === schemaTransactionRequest.message.schemaState.state) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestSchemaTransaction); } - return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, requestSchemaPayload, endorsementTransactionType.SCHEMA); + return this.ecosystemRepository.storeTransactionRequest( + schemaTransactionResponse, + requestSchemaPayload, + endorsementTransactionType.SCHEMA + ); } catch (error) { this.logger.error(`In request schema endorsement : ${JSON.stringify(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, + 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 requestCredDeffEndorsement(requestCredDefPayload: RequestCredDeffEndorsement, orgId: string, ecosystemId: string): Promise { + async requestCredDeffEndorsement( + requestCredDefPayload: RequestCredDeffEndorsement, + orgId: string, + ecosystemId: string + ): Promise { try { - const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); - const [credDefRequestExist, ecosystemMemberDetails, platformConfig, ecosystemLeadAgentDetails, getEcosystemOrgDetailsByOrgId] = await Promise.all([ + const [ + credDefRequestExist, + ecosystemMemberDetails, + platformConfig, + ecosystemLeadAgentDetails, + getEcosystemOrgDetailsByOrgId + ] = await Promise.all([ this.ecosystemRepository.findRecordsByCredDefTag(requestCredDefPayload?.tag), this.ecosystemRepository.getAgentDetails(orgId), this.ecosystemRepository.getPlatformConfigDetails(), @@ -659,8 +709,13 @@ export class EcosystemService { } const orgAgentType = await this.ecosystemRepository.getOrgAgentType(ecosystemMemberDetails.orgAgentTypeId); - const url = await this.getAgentUrl(orgAgentType, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.CREDENTIAL_DEFINITION, ecosystemMemberDetails.tenantId); - + const url = await this.getAgentUrl( + orgAgentType, + ecosystemMemberDetails.agentEndPoint, + endorsementTransactionType.CREDENTIAL_DEFINITION, + ecosystemMemberDetails.tenantId + ); + const apiKey = await this._getOrgAgentApiKey(orgId); const credDefTransactionPayload = { endorserDid: ecosystemLeadAgentDetails.orgDid, endorse: requestCredDefPayload.endorse, @@ -669,7 +724,11 @@ export class EcosystemService { issuerId: ecosystemMemberDetails.orgDid }; - const credDefTransactionRequest: CredDefMessage = await this._requestCredDeffEndorsement(credDefTransactionPayload, url, platformConfig?.sgApiKey); + const credDefTransactionRequest: CredDefMessage = await this._requestCredDeffEndorsement( + credDefTransactionPayload, + url, + apiKey + ); if ('failed' === credDefTransactionRequest.message.credentialDefinitionState.state) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); @@ -691,29 +750,35 @@ export class EcosystemService { userId: requestCredDefPayload.userId }; - return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, requestCredDefPayload, endorsementTransactionType.CREDENTIAL_DEFINITION); + return this.ecosystemRepository.storeTransactionRequest( + schemaTransactionResponse, + requestCredDefPayload, + endorsementTransactionType.CREDENTIAL_DEFINITION + ); } catch (error) { this.logger.error(`In request cred-def endorsement: ${JSON.stringify(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, + 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 getInvitationsByEcosystemId( - payload: FetchInvitationsPayload - ): Promise { + async getInvitationsByEcosystemId(payload: FetchInvitationsPayload): Promise { try { - const { ecosystemId, pageNumber, pageSize, search } = payload; - const ecosystemInvitations = await this.ecosystemRepository.getInvitationsByEcosystemId(ecosystemId, pageNumber, pageSize, search); + const ecosystemInvitations = await this.ecosystemRepository.getInvitationsByEcosystemId( + ecosystemId, + pageNumber, + pageSize, + search + ); return ecosystemInvitations; } catch (error) { this.logger.error(`In getInvitationsByEcosystemId : ${JSON.stringify(error)}`); @@ -731,10 +796,13 @@ export class EcosystemService { 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 + ); } } @@ -748,10 +816,13 @@ export class EcosystemService { 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 + ); } } @@ -782,15 +853,24 @@ export class EcosystemService { } const orgAgentType = await this.ecosystemRepository.getOrgAgentType(ecosystemLeadAgentDetails?.orgAgentTypeId); - const url = await this.getAgentUrl(orgAgentType, ecosystemLeadAgentDetails.agentEndPoint, endorsementTransactionType.SIGN, ecosystemLeadAgentDetails?.tenantId); - + const url = await this.getAgentUrl( + orgAgentType, + ecosystemLeadAgentDetails.agentEndPoint, + endorsementTransactionType.SIGN, + ecosystemLeadAgentDetails?.tenantId + ); + let apiKey:string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + this.logger.log(`cachedApiKey----${apiKey}`); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(ecosystemLeadDetails.orgId); + } const jsonString = endorsementTransactionPayload.requestPayload.toString(); const payload = { transaction: jsonString, endorserDid: endorsementTransactionPayload.endorserDid }; - const schemaTransactionRequest: SignedTransactionMessage = await this._signTransaction(payload, url, platformConfig.sgApiKey); + const schemaTransactionRequest: SignedTransactionMessage = await this._signTransaction(payload, url, apiKey); if (!schemaTransactionRequest) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.signRequestError); @@ -802,15 +882,21 @@ export class EcosystemService { throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemNotFound); } - const updateSignedTransaction = await this.ecosystemRepository.updateTransactionDetails(endorsementId, schemaTransactionRequest.message.signedTransaction); + const updateSignedTransaction = await this.ecosystemRepository.updateTransactionDetails( + endorsementId, + schemaTransactionRequest.message.signedTransaction + ); if (!updateSignedTransaction) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.updateTransactionError); } if (updateSignedTransaction && true === ecosystemDetails.autoEndorsement) { - - const submitTxn = await this.submitTransaction(endorsementId, ecosystemId, ecosystemLeadAgentDetails.agentEndPoint); + const submitTxn = await this.submitTransaction({ + endorsementId, + ecosystemId, + ecosystemLeadAgentEndPoint: ecosystemLeadAgentDetails.agentEndPoint + }); if (!submitTxn) { await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.REQUESTED); throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); @@ -823,10 +909,11 @@ export class EcosystemService { this.logger.error(`In sign transaction: ${JSON.stringify(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, + 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); } @@ -834,12 +921,10 @@ export class EcosystemService { } /** - * - * @returns Ecosystem members list - */ - async getEcoystemMembers( - payload: EcosystemMembersPayload - ): Promise { + * + * @returns Ecosystem members list + */ + async getEcoystemMembers(payload: EcosystemMembersPayload): Promise { try { const { ecosystemId, pageNumber, pageSize, search } = payload; return await this.ecosystemRepository.findEcosystemMembers(ecosystemId, pageNumber, pageSize, search); @@ -852,18 +937,17 @@ export class EcosystemService { async deleteEcosystemInvitations(invitationId: string): Promise { try { return await this.ecosystemRepository.deleteInvitations(invitationId); - } catch (error) { this.logger.error(`In error deleteEcosystemInvitation: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } } /** - * Description: Store shortening URL - * @param signEndorsementPayload - * @param url - * @returns sign message - */ + * Description: Store shortening URL + * @param signEndorsementPayload + * @param url + * @returns sign message + */ async _signTransaction(signEndorsementPayload: object, url: string, apiKey: string): Promise { const pattern = { cmd: 'agent-sign-transaction' }; const payload = { signEndorsementPayload, url, apiKey }; @@ -874,10 +958,13 @@ export class EcosystemService { 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 + ); } } @@ -898,7 +985,11 @@ export class EcosystemService { return this.ecosystemRepository.getPlatformConfigDetails(); } - async submitTransactionPayload(endorsementTransactionPayload, ecosystemMemberDetails, ecosystemLeadAgentDetails): Promise { + async submitTransactionPayload( + endorsementTransactionPayload, + ecosystemMemberDetails, + ecosystemLeadAgentDetails + ): Promise { const parsedRequestPayload = JSON.parse(endorsementTransactionPayload.responsePayload); const jsonString = endorsementTransactionPayload.responsePayload.toString(); const payload: submitTransactionPayload = { @@ -914,7 +1005,6 @@ export class EcosystemService { issuerId: ecosystemMemberDetails.orgDid }; } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { - payload.credentialDefinition = { tag: parsedRequestPayload.operation.tag, issuerId: ecosystemMemberDetails.orgDid, @@ -927,7 +1017,11 @@ export class EcosystemService { return payload; } - async handleSchemaSubmission(endorsementTransactionPayload, ecosystemMemberDetails, submitTransactionRequest): Promise { + async handleSchemaSubmission( + endorsementTransactionPayload, + ecosystemMemberDetails, + submitTransactionRequest + ): Promise { const regex = /[^:]+$/; const match = ecosystemMemberDetails.orgDid.match(regex); let extractedDidValue; @@ -935,7 +1029,6 @@ export class EcosystemService { if (match) { // eslint-disable-next-line prefer-destructuring extractedDidValue = match[0]; - } const saveSchemaPayload: SaveSchema = { name: endorsementTransactionPayload.requestBody['name'], @@ -957,8 +1050,15 @@ export class EcosystemService { } // eslint-disable-next-line camelcase - async handleCredDefSubmission(endorsementTransactionPayload, ecosystemMemberDetails, submitTransactionRequest): Promise { - const schemaDetails = await this.ecosystemRepository.getSchemaDetailsById(endorsementTransactionPayload.requestBody['schemaId']); + async handleCredDefSubmission( + endorsementTransactionPayload, + ecosystemMemberDetails, + submitTransactionRequest + // eslint-disable-next-line camelcase + ): Promise { + const schemaDetails = await this.ecosystemRepository.getSchemaDetailsById( + endorsementTransactionPayload.requestBody['schemaId'] + ); if (!schemaDetails) { throw new NotFoundException(ResponseMessages.ecosystem.error.schemaNotFound); @@ -986,9 +1086,13 @@ export class EcosystemService { return this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SUBMITED); } - async submitTransaction(endorsementId, ecosystemId, ecosystemLeadAgentEndPoint?): Promise { + async submitTransaction(transactionPayload: TransactionPayload): Promise { try { - const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.SIGNED); + const { endorsementId, ecosystemId, ecosystemLeadAgentEndPoint, orgId} = transactionPayload; + const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById( + endorsementId, + endorsementTransactionStatus.SIGNED + ); if (!endorsementTransactionPayload) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidTransaction); } @@ -999,15 +1103,30 @@ export class EcosystemService { const ecosystemMemberDetails = await this.getEcosystemMemberDetails(endorsementTransactionPayload); const ecosystemLeadAgentDetails = await this.getEcosystemLeadAgentDetails(ecosystemId); - const platformConfig = await this.getPlatformConfig(); - const agentEndPoint = ecosystemLeadAgentEndPoint ? ecosystemLeadAgentEndPoint : ecosystemMemberDetails.agentEndPoint; + const agentEndPoint = ecosystemLeadAgentEndPoint + ? ecosystemLeadAgentEndPoint + : ecosystemMemberDetails.agentEndPoint; const orgAgentType = await this.ecosystemRepository.getOrgAgentType(ecosystemMemberDetails?.orgAgentTypeId); - const url = await this.getAgentUrl(orgAgentType, agentEndPoint, endorsementTransactionType.SUBMIT, ecosystemMemberDetails?.tenantId); - const payload = await this.submitTransactionPayload(endorsementTransactionPayload, ecosystemMemberDetails, ecosystemLeadAgentDetails); - - const submitTransactionRequest = await this._submitTransaction(payload, url, platformConfig.sgApiKey); + const url = await this.getAgentUrl( + orgAgentType, + agentEndPoint, + endorsementTransactionType.SUBMIT, + ecosystemMemberDetails?.tenantId + ); + const payload = await this.submitTransactionPayload( + endorsementTransactionPayload, + ecosystemMemberDetails, + ecosystemLeadAgentDetails + ); + // const apiKey = await this._getOrgAgentApiKey(orgId); + let apiKey:string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + this.logger.log(`cachedApiKey----${apiKey}`); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } + const submitTransactionRequest = await this._submitTransaction(payload, url, apiKey); if ('failed' === submitTransactionRequest['message'].state) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); @@ -1016,59 +1135,71 @@ export class EcosystemService { await this.updateTransactionStatus(endorsementId); if (endorsementTransactionPayload.type === endorsementTransactionType.SCHEMA) { - - const updateSchemaId = await this._updateResourceId(endorsementId, endorsementTransactionType.SCHEMA, submitTransactionRequest); + const updateSchemaId = await this._updateResourceId( + endorsementId, + endorsementTransactionType.SCHEMA, + submitTransactionRequest + ); if (!updateSchemaId) { - throw new InternalServerErrorException(ResponseMessages.ecosystem.error.updateSchemaId); } - return this.handleSchemaSubmission(endorsementTransactionPayload, ecosystemMemberDetails, submitTransactionRequest); + return this.handleSchemaSubmission( + endorsementTransactionPayload, + ecosystemMemberDetails, + submitTransactionRequest + ); } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { - if ('undefined' === submitTransactionRequest['message'].credentialDefinitionId.split(':')[3]) { - const ecosystemDetails = await this.ecosystemRepository.getEcosystemDetails(ecosystemId); if (true === ecosystemDetails.autoEndorsement) { - - await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.REQUESTED); + await this.ecosystemRepository.updateTransactionStatus( + endorsementId, + endorsementTransactionStatus.REQUESTED + ); } else { - await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SIGNED); } throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); } - const updateCredDefId = await this._updateResourceId(endorsementId, endorsementTransactionType.CREDENTIAL_DEFINITION, submitTransactionRequest); + const updateCredDefId = await this._updateResourceId( + endorsementId, + endorsementTransactionType.CREDENTIAL_DEFINITION, + submitTransactionRequest + ); if (!updateCredDefId) { - throw new InternalServerErrorException(ResponseMessages.ecosystem.error.updateCredDefId); } - return this.handleCredDefSubmission(endorsementTransactionPayload, ecosystemMemberDetails, submitTransactionRequest); + return this.handleCredDefSubmission( + endorsementTransactionPayload, + ecosystemMemberDetails, + submitTransactionRequest + ); } } catch (error) { this.logger.error(`In submit transaction: ${JSON.stringify(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, + 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); } } } - /** - * Description: Store shortening URL - * @param signEndorsementPayload - * @param url - * @returns sign message - */ + * Description: Store shortening URL + * @param signEndorsementPayload + * @param url + * @returns sign message + */ async _submitTransaction(submitEndorsementPayload: object, url: string, apiKey: string): Promise { const pattern = { cmd: 'agent-submit-transaction' }; const payload = { submitEndorsementPayload, url, apiKey }; @@ -1079,14 +1210,21 @@ export class EcosystemService { 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 _updateResourceId(endorsementId: string, transactionType: endorsementTransactionType, transactionDetails: object): Promise { + async _updateResourceId( + endorsementId: string, + transactionType: endorsementTransactionType, + transactionDetails: object + ): Promise { try { // eslint-disable-next-line prefer-destructuring const message = transactionDetails['message']; @@ -1094,10 +1232,15 @@ export class EcosystemService { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidMessage); } - const resourceId = message[transactionType === endorsementTransactionType.SCHEMA ? 'schemaId' : 'credentialDefinitionId']; + const resourceId = + message[transactionType === endorsementTransactionType.SCHEMA ? 'schemaId' : 'credentialDefinitionId']; if (!resourceId) { - throw new Error(`${ResponseMessages.ecosystem.error.invalidTransactionMessage} Missing "${transactionType === endorsementTransactionType.SCHEMA ? 'schemaId' : 'credentialDefinitionId'}" property.`); + throw new Error( + `${ResponseMessages.ecosystem.error.invalidTransactionMessage} Missing "${ + transactionType === endorsementTransactionType.SCHEMA ? 'schemaId' : 'credentialDefinitionId' + }" property.` + ); } return await this.ecosystemRepository.updateResourse(endorsementId, resourceId); @@ -1106,13 +1249,7 @@ export class EcosystemService { } } - - async getAgentUrl( - orgAgentTypeId: string, - agentEndPoint: string, - type: string, - tenantId?: string - ): Promise { + async getAgentUrl(orgAgentTypeId: string, agentEndPoint: string, type: string, tenantId?: string): Promise { try { let url; @@ -1154,34 +1291,26 @@ export class EcosystemService { } } - async fetchEcosystemOrg( - payload: { ecosystemId: string, orgId: string } - ): Promise { - + async fetchEcosystemOrg(payload: { ecosystemId: string; orgId: string }): Promise { const isEcosystemEnabled = await this.checkEcosystemEnableFlag(); if (!isEcosystemEnabled) { throw new ForbiddenException(ResponseMessages.ecosystem.error.ecosystemNotEnabled); } - return this.ecosystemRepository.fetchEcosystemOrg( - payload - ); + return this.ecosystemRepository.fetchEcosystemOrg(payload); } /** - * + * * @returns Returns ecosystem flag from settings */ - async checkEcosystemEnableFlag( - ): Promise { - const ecosystemDetails = await this.prisma.ecosystem_config.findFirst( - { - where: { - key: 'enableEcosystem' - } + async checkEcosystemEnableFlag(): Promise { + const ecosystemDetails = await this.prisma.ecosystem_config.findFirst({ + where: { + key: 'enableEcosystem' } - ); + }); if ('true' === ecosystemDetails.value) { return true; @@ -1193,7 +1322,6 @@ export class EcosystemService { async getEndorsementTransactions(payload: GetEndorsementsPayload): Promise { const { ecosystemId, orgId, pageNumber, pageSize, search, type } = payload; try { - const queryEcoOrgs = { ecosystemId, orgId @@ -1226,13 +1354,11 @@ export class EcosystemService { } } - async getAllEcosystemSchemas(ecosystemSchemas: GetAllSchemaList): Promise { try { - const response = await this.ecosystemRepository.getAllEcosystemSchemasDetails(ecosystemSchemas); this.logger.error(`In error getAllEcosystemSchemas1: ${JSON.stringify(response)}`); - const schemasDetails = response?.schemasResult.map(schemaAttributeItem => { + const schemasDetails = response?.schemasResult.map((schemaAttributeItem) => { const attributes = JSON.parse(schemaAttributeItem.attributes); return { ...schemaAttributeItem, attributes }; }); @@ -1255,12 +1381,11 @@ export class EcosystemService { } /** - * @returns EndorsementTransaction Status message - */ + * @returns EndorsementTransaction Status message + */ async autoSignAndSubmitTransaction(): Promise { try { - return await this.ecosystemRepository.updateAutoSignAndSubmitTransaction(); } catch (error) { this.logger.error(`error in decline endorsement request: ${error}`); @@ -1269,16 +1394,15 @@ export class EcosystemService { } /** - * - * @param ecosystemId - * @param endorsementId - * @param orgId - * @returns EndorsementTransactionRequest Status message - */ + * + * @param ecosystemId + * @param endorsementId + * @param orgId + * @returns EndorsementTransactionRequest Status message + */ async declineEndorsementRequestByLead(ecosystemId: string, endorsementId: string): Promise { try { - return await this.ecosystemRepository.updateEndorsementRequestStatus(ecosystemId, endorsementId); } catch (error) { this.logger.error(`error in decline endorsement request: ${error}`); @@ -1286,4 +1410,21 @@ export class EcosystemService { } } + + async _getOrgAgentApiKey(orgId: string): Promise { + const pattern = { cmd: 'get-org-agent-api-key' }; + const payload = { orgId }; + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.ecosystemServiceProxy.send(pattern, payload).toPromise(); + return message; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException({ + status: error.status, + error: error.message + }, error.status); + } + } } diff --git a/apps/issuance/enum/issuance.enum.ts b/apps/issuance/enum/issuance.enum.ts new file mode 100644 index 000000000..aa09cabc9 --- /dev/null +++ b/apps/issuance/enum/issuance.enum.ts @@ -0,0 +1,6 @@ +export enum SortFields { + CREATED_DATE_TIME = 'createDateTime', + SCHEMA_ID = 'schemaId', + CONNECTION_ID = 'connectionId', + STATE = 'state' +} \ No newline at end of file diff --git a/apps/issuance/interfaces/issuance.interfaces.ts b/apps/issuance/interfaces/issuance.interfaces.ts index 088bb841c..ebf87d72e 100644 --- a/apps/issuance/interfaces/issuance.interfaces.ts +++ b/apps/issuance/interfaces/issuance.interfaces.ts @@ -134,8 +134,8 @@ export interface IIssuedCredentialsSearchInterface { export interface IIssuedCredentialsSearchCriteria { pageNumber: number; pageSize: number; - sorting: string; - sortByValue: string; + sortField: string; + sortBy: string; searchByText: string; user?: IUserRequestInterface; } diff --git a/apps/issuance/src/issuance.controller.ts b/apps/issuance/src/issuance.controller.ts index d535d6c69..7ecfd3432 100644 --- a/apps/issuance/src/issuance.controller.ts +++ b/apps/issuance/src/issuance.controller.ts @@ -2,6 +2,7 @@ import { Controller, Logger } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; import { ClientDetails, IIssuance, IIssueCredentials, IIssueCredentialsDefinitions, ImportFileDetails, IssueCredentialWebhookPayload, OutOfBandCredentialOffer, PreviewRequest } from '../interfaces/issuance.interfaces'; import { IssuanceService } from './issuance.service'; +import { IIssuedCredential } from '@credebl/common/interfaces/issuance.interface'; @Controller() export class IssuanceController { @@ -23,7 +24,7 @@ export class IssuanceController { } @MessagePattern({ cmd: 'get-all-issued-credentials' }) - async getIssueCredentials(payload: IIssueCredentials): Promise { + async getIssueCredentials(payload: IIssueCredentials): Promise { const { user, orgId, issuedCredentialsSearchCriteria} = payload; return this.issuanceService.getIssueCredentials(user, orgId, issuedCredentialsSearchCriteria); } diff --git a/apps/issuance/src/issuance.repository.ts b/apps/issuance/src/issuance.repository.ts index 0c4870222..d8fd75f40 100644 --- a/apps/issuance/src/issuance.repository.ts +++ b/apps/issuance/src/issuance.repository.ts @@ -21,7 +21,8 @@ import { } from '../interfaces/issuance.interfaces'; import { FileUploadStatus } from 'apps/api-gateway/src/enum'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; -import { IIssuedCredentialSearchinterface } from 'apps/api-gateway/src/issuance/interfaces'; +import { IIssuedCredentialSearchParams } from 'apps/api-gateway/src/issuance/interfaces'; +import { SortValue } from '@credebl/enum/enum'; @Injectable() export class IssuanceRepository { constructor( @@ -70,7 +71,7 @@ export class IssuanceRepository { async getAllIssuedCredentials( user: IUserRequest, orgId: string, - issuedCredentialsSearchCriteria: IIssuedCredentialSearchinterface + issuedCredentialsSearchCriteria: IIssuedCredentialSearchParams ): Promise<{ issuedCredentialsCount: number; issuedCredentialsList: { @@ -100,10 +101,8 @@ export class IssuanceRepository { connectionId: true }, orderBy: { - [issuedCredentialsSearchCriteria?.sorting || 'createDateTime']: - 'DESC' === issuedCredentialsSearchCriteria?.sortByValue - ? 'desc' - : 'asc' + [issuedCredentialsSearchCriteria?.sortField]: + SortValue.DESC === issuedCredentialsSearchCriteria?.sortBy?.toLocaleUpperCase() ? 'desc' : 'asc' }, take: Number(issuedCredentialsSearchCriteria.pageSize), skip: (issuedCredentialsSearchCriteria.pageNumber - 1) * issuedCredentialsSearchCriteria.pageSize diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index ab5f06189..8d8720e46 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -8,9 +8,10 @@ import { CommonConstants } from '@credebl/common/common.constant'; import { ResponseMessages } from '@credebl/common/response-messages'; import { ClientProxy, RpcException } from '@nestjs/microservices'; import { map } from 'rxjs'; +// import { ClientDetails, FileUploadData, ICredentialAttributesInterface, ImportFileDetails, OutOfBandCredentialOfferPayload, PreviewRequest, SchemaDetails } from '../interfaces/issuance.interfaces'; import { ClientDetails, FileUploadData, ImportFileDetails, IssueCredentialWebhookPayload, OutOfBandCredentialOfferPayload, PreviewRequest, SchemaDetails } from '../interfaces/issuance.interfaces'; import { OrgAgentType } from '@credebl/enum/enum'; -import { platform_config } from '@prisma/client'; +// import { platform_config } from '@prisma/client'; import * as QRCode from 'qrcode'; import { OutOfBandIssuance } from '../templates/out-of-band-issuance.template'; import { EmailDto } from '@credebl/common/dtos/email.dto'; @@ -28,7 +29,9 @@ import { Queue } from 'bull'; import { FileUploadStatus, FileUploadType } from 'apps/api-gateway/src/enum'; import { AwsService } from '@credebl/aws'; import { io } from 'socket.io-client'; -import { IIssuedCredentialSearchinterface } from 'apps/api-gateway/src/issuance/interfaces'; +import { IIssuedCredentialSearchParams } from 'apps/api-gateway/src/issuance/interfaces'; +import { IIssuedCredential } from '@credebl/common/interfaces/issuance.interface'; + @Injectable() export class IssuanceService { @@ -41,15 +44,15 @@ export class IssuanceService { private readonly outOfBandIssuance: OutOfBandIssuance, private readonly emailData: EmailDto, private readonly awsService: AwsService, - @InjectQueue('bulk-issuance') private bulkIssuanceQueue: Queue - + @InjectQueue('bulk-issuance') private bulkIssuanceQueue: Queue, + @Inject(CACHE_MANAGER) private cacheService: Cache ) { } async sendCredentialCreateOffer(orgId: string, user: IUserRequest, credentialDefinitionId: string, comment: string, connectionId: string, attributes: object[]): Promise { try { const agentDetails = await this.issuanceRepository.getAgentEndPoint(orgId); - const platformConfig: platform_config = await this.issuanceRepository.getPlatformConfigDetails(); + // const platformConfig: platform_config = await this.issuanceRepository.getPlatformConfigDetails(); const { agentEndPoint } = agentDetails; if (!agentDetails) { @@ -60,7 +63,14 @@ export class IssuanceService { const issuanceMethodLabel = 'create-offer'; const url = await this.getAgentUrl(issuanceMethodLabel, orgAgentType, agentEndPoint, agentDetails?.tenantId); - const apiKey = platformConfig?.sgApiKey; + // const apiKey = platformConfig?.sgApiKey; + // let apiKey = await this._getOrgAgentApiKey(orgId); + + let apiKey; + apiKey = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } const issueData = { protocolVersion: 'v1', connectionId, @@ -96,7 +106,7 @@ export class IssuanceService { try { const agentDetails = await this.issuanceRepository.getAgentEndPoint(orgId); // eslint-disable-next-line camelcase - const platformConfig: platform_config = await this.issuanceRepository.getPlatformConfigDetails(); + // const platformConfig: platform_config = await this.issuanceRepository.getPlatformConfigDetails(); const { agentEndPoint } = agentDetails; if (!agentDetails) { @@ -107,7 +117,16 @@ export class IssuanceService { const issuanceMethodLabel = 'create-offer-oob'; const url = await this.getAgentUrl(issuanceMethodLabel, orgAgentType, agentEndPoint, agentDetails?.tenantId); - const apiKey = platformConfig?.sgApiKey; + // const apiKey = platformConfig?.sgApiKey; + + // const apiKey = await this._getOrgAgentApiKey(orgId); + let apiKey; + apiKey = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + this.logger.log(`cachedApiKey---${apiKey}`); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } + const issueData = { connectionId, credentialFormats: { @@ -178,48 +197,18 @@ export class IssuanceService { async getIssueCredentials( user: IUserRequest, orgId: string, - issuedCredentialsSearchCriteria: IIssuedCredentialSearchinterface - ): Promise<{ - totalItems: number; - hasNextPage: boolean; - hasPreviousPage: boolean; - nextPage: number; - previousPage: number; - lastPage: number; - data: { - createDateTime: Date; - createdBy: string; - connectionId: string; - schemaId: string; - state: string; - orgId: string; - }[]; - }> { + issuedCredentialsSearchCriteria: IIssuedCredentialSearchParams + ): Promise { try { const getIssuedCredentialsList = await this.issuanceRepository.getAllIssuedCredentials( user, orgId, issuedCredentialsSearchCriteria ); - const issuedCredentialsResponse: { - totalItems: number; - hasNextPage: boolean; - hasPreviousPage: boolean; - nextPage: number; - previousPage: number; - lastPage: number; - data: { - createDateTime: Date; - createdBy: string; - connectionId: string; - schemaId: string; - state: string; - orgId: string; - }[]; - } = { + const issuedCredentialsResponse: IIssuedCredential = { totalItems: getIssuedCredentialsList.issuedCredentialsCount, hasNextPage: - issuedCredentialsSearchCriteria.pageSize * issuedCredentialsSearchCriteria.pageNumber < getIssuedCredentialsList.issuedCredentialsCount, + issuedCredentialsSearchCriteria.pageSize * issuedCredentialsSearchCriteria.pageNumber < getIssuedCredentialsList.issuedCredentialsCount, hasPreviousPage: 1 < issuedCredentialsSearchCriteria.pageNumber, nextPage: Number(issuedCredentialsSearchCriteria.pageNumber) + 1, previousPage: issuedCredentialsSearchCriteria.pageNumber - 1, @@ -227,18 +216,14 @@ export class IssuanceService { data: getIssuedCredentialsList.issuedCredentialsList }; - if (0 !== getIssuedCredentialsList.issuedCredentialsCount) { - return issuedCredentialsResponse; - } else { + if (0 === getIssuedCredentialsList?.issuedCredentialsCount) { throw new NotFoundException(ResponseMessages.issuance.error.credentialsNotFound); } + + return issuedCredentialsResponse; } catch (error) { - if (404 === error.status) { - throw new NotFoundException(error.response.message); - } - throw new RpcException( - `[getConnections] [NATS call]- error in fetch connections details : ${JSON.stringify(error)}` - ); + this.logger.error(`Error in fetching issued credentials by org id: ${error}`); + throw new RpcException(error.response ? error.response : error); } } @@ -259,7 +244,7 @@ export class IssuanceService { try { const agentDetails = await this.issuanceRepository.getAgentEndPoint(orgId); - const platformConfig: platform_config = await this.issuanceRepository.getPlatformConfigDetails(); + // const platformConfig: platform_config = await this.issuanceRepository.getPlatformConfigDetails(); const { agentEndPoint } = agentDetails; if (!agentDetails) { @@ -270,7 +255,13 @@ export class IssuanceService { const issuanceMethodLabel = 'get-issue-credential-by-credential-id'; const url = await this.getAgentUrl(issuanceMethodLabel, orgAgentType, agentEndPoint, agentDetails?.tenantId, credentialRecordId); - const apiKey = platformConfig?.sgApiKey; + // const apiKey = platformConfig?.sgApiKey; + // const apiKey = await this._getOrgAgentApiKey(orgId); + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + this.logger.log(`cachedApiKey----${apiKey}`); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } const createConnectionInvitation = await this._getIssueCredentialsbyCredentialRecordId(url, apiKey); return createConnectionInvitation?.response; } catch (error) { @@ -338,7 +329,13 @@ export class IssuanceService { throw new NotFoundException(ResponseMessages.issuance.error.organizationNotFound); } - const { apiKey } = agentDetails; + // const { apiKey } = agentDetails; + // const apiKey = await this._getOrgAgentApiKey(orgId); + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + this.logger.log(`cachedApiKey----${apiKey}`); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } const errors = []; const emailPromises = []; @@ -354,8 +351,7 @@ export class IssuanceService { } }, autoAcceptCredential: 'always', - comment, - label: organizationDetails?.name + comment }; const credentialCreateOfferDetails = await this._outOfBandCredentialOffer(outOfBandIssuancePayload, url, apiKey); @@ -415,7 +411,7 @@ export class IssuanceService { if (credentialOffer) { - for (let i = 0; i < credentialOffer.length; i += Number(process.env.OOB_BATCH_SIZE)) { + for (let i = 0; i < credentialOffer.length; i += Number(process.env.OOB_BATCH_SIZE)) { const batch = credentialOffer.slice(i, i + Number(process.env.OOB_BATCH_SIZE)); // Process each batch in parallel @@ -1063,4 +1059,22 @@ export class IssuanceService { } } -} \ No newline at end of file + async _getOrgAgentApiKey(orgId: string): Promise { + const pattern = { cmd: 'get-org-agent-api-key' }; + const payload = { orgId }; + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.issuanceServiceProxy.send(pattern, payload).toPromise(); + return message; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException({ + status: error.status, + error: error.message + }, error.status); + } + } + +} + diff --git a/apps/ledger/src/credential-definition/credential-definition.module.ts b/apps/ledger/src/credential-definition/credential-definition.module.ts index 035e4d041..f65424046 100644 --- a/apps/ledger/src/credential-definition/credential-definition.module.ts +++ b/apps/ledger/src/credential-definition/credential-definition.module.ts @@ -7,6 +7,7 @@ import { CredentialDefinitionRepository } from './repositories/credential-defini import { CredentialDefinitionService } from './credential-definition.service'; import { HttpModule } from '@nestjs/axios'; import { PrismaService } from '@credebl/prisma-service'; +import { CacheModule } from '@nestjs/cache-manager'; import { getNatsOptions } from '@credebl/common/nats.config'; @Module({ imports: [ @@ -18,7 +19,8 @@ import { getNatsOptions } from '@credebl/common/nats.config'; } ]), HttpModule, - CommonModule + CommonModule, + CacheModule.register() ], providers: [ CredentialDefinitionService, diff --git a/apps/ledger/src/credential-definition/credential-definition.service.ts b/apps/ledger/src/credential-definition/credential-definition.service.ts index fea699d65..9364f6286 100644 --- a/apps/ledger/src/credential-definition/credential-definition.service.ts +++ b/apps/ledger/src/credential-definition/credential-definition.service.ts @@ -15,12 +15,15 @@ import { ResponseMessages } from '@credebl/common/response-messages'; import { CreateCredDefAgentRedirection, CredDefSchema, GetCredDefAgentRedirection } from './interfaces/credential-definition.interface'; import { map } from 'rxjs/operators'; import { OrgAgentType } from '@credebl/enum/enum'; - +import { Cache } from 'cache-manager'; +import { CACHE_MANAGER } from '@nestjs/cache-manager'; +import { CommonConstants } from '@credebl/common/common.constant'; @Injectable() export class CredentialDefinitionService extends BaseService { constructor( private readonly credentialDefinitionRepository: CredentialDefinitionRepository, - @Inject('NATS_CLIENT') private readonly credDefServiceProxy: ClientProxy + @Inject('NATS_CLIENT') private readonly credDefServiceProxy: ClientProxy, + @Inject(CACHE_MANAGER) private cacheService: Cache ) { super('CredentialDefinitionService'); @@ -33,7 +36,12 @@ export class CredentialDefinitionService extends BaseService { // eslint-disable-next-line yoda const did = credDef.orgDid?.split(':').length >= 4 ? credDef.orgDid : orgDid; const getAgentDetails = await this.credentialDefinitionRepository.getAgentType(credDef.orgId); - const apiKey = ''; + // const apiKey = await this._getOrgAgentApiKey(credDef.orgId); + let apiKey:string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + this.logger.log(`cachedApiKey----${apiKey}`); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(credDef.orgId); + } const { userId } = user.selectedOrg; credDef.tag = credDef.tag.trim(); const dbResult: credential_definition = await this.credentialDefinitionRepository.getByAttribute( @@ -170,8 +178,13 @@ export class CredentialDefinitionService extends BaseService { const { agentEndPoint } = await this.credentialDefinitionRepository.getAgentDetailsByOrgId(String(orgId)); const getAgentDetails = await this.credentialDefinitionRepository.getAgentType(String(orgId)); const orgAgentType = await this.credentialDefinitionRepository.getOrgAgentType(getAgentDetails.org_agents[0].orgAgentTypeId); - const apiKey = ''; - let credDefResponse; + // const apiKey = await this._getOrgAgentApiKey(String(orgId)); + let apiKey:string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + this.logger.log(`cachedApiKey----${apiKey}`); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(String(orgId)); + } + let credDefResponse; if (OrgAgentType.DEDICATED === orgAgentType) { const getSchemaPayload = { credentialDefinitionId, @@ -316,4 +329,20 @@ export class CredentialDefinitionService extends BaseService { } } + async _getOrgAgentApiKey(orgId: string): Promise { + const pattern = { cmd: 'get-org-agent-api-key' }; + const payload = { orgId }; + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.credDefServiceProxy.send(pattern, payload).toPromise(); + return message; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException({ + status: error.status, + error: error.message + }, error.status); + } + } } \ No newline at end of file diff --git a/apps/ledger/src/schema/enum/schema.enum.ts b/apps/ledger/src/schema/enum/schema.enum.ts new file mode 100644 index 000000000..cafabff1d --- /dev/null +++ b/apps/ledger/src/schema/enum/schema.enum.ts @@ -0,0 +1,19 @@ + + +export enum SortFields { + ID = 'id', + CREATED_DATE_TIME = 'createDateTime', + NAME = 'name', + VERSION = 'version', + LEDGER_ID = 'schemaLedgerId', + PUBLISHER_DID = 'publisherDid', + ISSUER_ID = 'issuerId' +} + +export enum CredDefSortFields { + ID = 'id', + CREATED_DATE_TIME = 'createDateTime', + TAG = 'tag', + LEDGER_ID = 'schemaLedgerId', + CRED_DEF_ID= 'credentialDefinitionId' +} \ No newline at end of file diff --git a/apps/ledger/src/schema/interfaces/schema-payload.interface.ts b/apps/ledger/src/schema/interfaces/schema-payload.interface.ts index 35f59ea71..1b0c2797e 100644 --- a/apps/ledger/src/schema/interfaces/schema-payload.interface.ts +++ b/apps/ledger/src/schema/interfaces/schema-payload.interface.ts @@ -41,7 +41,7 @@ export interface ISchemaPayload { schemaSortBy?: string; } -export interface ISchemaSearchInterface { +export interface ISchemaSearchPayload { schemaSearchCriteria: ISchemaSearchCriteria, user: IUserRequestInterface, orgId: string @@ -51,16 +51,17 @@ export interface ISchemaSearchCriteria { ledgerId?: string; pageNumber: number; pageSize: number; - sorting: string; - sortByValue: string; - searchByText: string; - user: IUserRequestInterface + sortField: string; + sortBy: string; + searchByText?: string; + user?: IUserRequestInterface + schemaId?: string; + orgId?: string; } export interface ISchemaCredDeffSearchInterface { schemaId: string; schemaSearchCriteria?: ISchemaSearchCriteria, user: IUserRequestInterface, - orgId?: string } diff --git a/apps/ledger/src/schema/interfaces/schema.interface.ts b/apps/ledger/src/schema/interfaces/schema.interface.ts index 60122decd..1968f8a20 100644 --- a/apps/ledger/src/schema/interfaces/schema.interface.ts +++ b/apps/ledger/src/schema/interfaces/schema.interface.ts @@ -38,4 +38,27 @@ export interface IOrgAgentInterface { walletName: string; agentsTypeId: string; orgId: string; -} \ No newline at end of file +} + +export interface AgentDetails { + orgDid: string; + agentEndPoint: string; + tenantId: string +} + +export interface ISchemaData { + createDateTime: Date; + createdBy: string; + name: string; + version: string; + attributes: string; + schemaLedgerId: string; + publisherDid: string; + issuerId: string; + orgId: string; +} + +export interface ISchemasWithCount { + schemasCount: number; + schemasResult: ISchemaData[]; +} diff --git a/apps/ledger/src/schema/repositories/schema.repository.ts b/apps/ledger/src/schema/repositories/schema.repository.ts index aa55d1652..84102155b 100644 --- a/apps/ledger/src/schema/repositories/schema.repository.ts +++ b/apps/ledger/src/schema/repositories/schema.repository.ts @@ -1,9 +1,12 @@ /* eslint-disable camelcase */ -import { BadRequestException, Injectable, Logger } from '@nestjs/common'; +import { ConflictException, Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; import { ledgers, org_agents, org_agents_type, organisation, schema } from '@prisma/client'; import { ISchema, ISchemaSearchCriteria } from '../interfaces/schema-payload.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; +import { AgentDetails, ISchemasWithCount } from '../interfaces/schema.interface'; +import { SortValue } from '@credebl/enum/enum'; +import { ICredDefWithCount } from '@credebl/common/interfaces/schema.interface'; @Injectable() export class SchemaRepository { @@ -21,9 +24,10 @@ export class SchemaRepository { ); const schemaLength = 0; - if (schema.length !== schemaLength) { - throw new BadRequestException( - ResponseMessages.schema.error.exists + if (schema.length !== schemaLength) { + throw new ConflictException( + ResponseMessages.schema.error.exists, + { cause: new Error(), description: ResponseMessages.errorMessages.conflict } ); } const saveResult = await this.prisma.schema.create({ @@ -68,20 +72,7 @@ export class SchemaRepository { } } - async getSchemas(payload: ISchemaSearchCriteria, orgId: string): Promise<{ - schemasCount: number; - schemasResult: { - createDateTime: Date; - createdBy: string; - name: string; - version: string; - attributes: string; - schemaLedgerId: string; - publisherDid: string; - issuerId: string; - orgId: string; - }[]; - }> { + async getSchemas(payload: ISchemaSearchCriteria, orgId: string): Promise { try { const schemasResult = await this.prisma.schema.findMany({ where: { @@ -105,7 +96,7 @@ export class SchemaRepository { issuerId: true }, orderBy: { - [payload.sorting]: 'DESC' === payload.sortByValue ? 'desc' : 'ASC' === payload.sortByValue ? 'asc' : 'desc' + [payload.sortField]: SortValue.ASC === payload.sortBy ? 'asc' : 'desc' }, take: Number(payload.pageSize), skip: (payload.pageNumber - 1) * payload.pageSize @@ -120,15 +111,15 @@ export class SchemaRepository { return { schemasCount, schemasResult }; } catch (error) { this.logger.error(`Error in getting schemas: ${error}`); - throw error; + throw new InternalServerErrorException( + ResponseMessages.schema.error.failedFetchSchema, + { cause: new Error(), description: error.message } + ); + } } - async getAgentDetailsByOrgId(orgId: string): Promise<{ - orgDid: string; - agentEndPoint: string; - tenantId: string - }> { + async getAgentDetailsByOrgId(orgId: string): Promise { try { const schemasResult = await this.prisma.org_agents.findFirst({ where: { @@ -172,14 +163,12 @@ export class SchemaRepository { } } - async getSchemasCredDeffList(payload: ISchemaSearchCriteria, orgId: string, schemaId: string): Promise<{ - tag: string; - credentialDefinitionId: string; - schemaLedgerId: string; - revocable: boolean; - }[]> { + async getSchemasCredDeffList(payload: ISchemaSearchCriteria): Promise { + + const {orgId, schemaId} = payload; + try { - return this.prisma.credential_definition.findMany({ + const credDefResult = await this.prisma.credential_definition.findMany({ where: { AND: [ { orgId }, @@ -194,9 +183,20 @@ export class SchemaRepository { createDateTime: true }, orderBy: { - [payload.sorting]: 'DESC' === payload.sortByValue ? 'desc' : 'ASC' === payload.sortByValue ? 'asc' : 'desc' + [payload.sortField]: SortValue.ASC === payload.sortBy ? 'asc' : 'desc' + }, + take: Number(payload.pageSize), + skip: (payload.pageNumber - 1) * payload.pageSize + }); + const credDefCount = await this.prisma.credential_definition.count({ + where: { + AND: [ + { orgId }, + { schemaLedgerId: schemaId } + ] } }); + return { credDefResult, credDefCount }; } catch (error) { this.logger.error(`Error in getting agent DID: ${error}`); throw error; @@ -240,7 +240,7 @@ export class SchemaRepository { issuerId: true }, orderBy: { - [payload.sorting]: 'DESC' === payload.sortByValue ? 'desc' : 'ASC' === payload.sortByValue ? 'asc' : 'desc' + [payload.sortField]: 'DESC' === payload.sortBy ? 'desc' : 'ASC' === payload.sortBy ? 'asc' : 'desc' }, take: Number(payload.pageSize), skip: (payload.pageNumber - 1) * payload.pageSize @@ -288,7 +288,7 @@ export class SchemaRepository { } } - async getLedgerByLedger(LedgerName: string): Promise { + async getLedgerByNamespace(LedgerName: string): Promise { try { return this.prisma.ledgers.findFirst({ where: { diff --git a/apps/ledger/src/schema/schema.controller.ts b/apps/ledger/src/schema/schema.controller.ts index 6683ce899..f17d197f4 100644 --- a/apps/ledger/src/schema/schema.controller.ts +++ b/apps/ledger/src/schema/schema.controller.ts @@ -1,8 +1,10 @@ import { Controller } from '@nestjs/common'; import { SchemaService } from './schema.service'; import { MessagePattern } from '@nestjs/microservices'; -import { ISchema, ISchemaCredDeffSearchInterface, ISchemaSearchInterface } from './interfaces/schema-payload.interface'; +import { ISchema, ISchemaCredDeffSearchInterface, ISchemaSearchPayload } from './interfaces/schema-payload.interface'; import { schema } from '@prisma/client'; +import { ResponseMessages } from '@credebl/common/response-messages'; +import { ICredDefWithPagination, ISchemasWithPagination } from '@credebl/common/interfaces/schema.interface'; @Controller('schema') @@ -10,9 +12,10 @@ export class SchemaController { constructor(private readonly schemaService: SchemaService) { } @MessagePattern({ cmd: 'create-schema' }) - async createSchema(payload: ISchema): Promise { + async createSchema(payload: ISchema): Promise { const { schema, user, orgId } = payload; - return this.schemaService.createSchema(schema, user, orgId); + await this.schemaService.createSchema(schema, user, orgId); + return ResponseMessages.schema.success.create; } @MessagePattern({ cmd: 'get-schema-by-id' }) @@ -22,50 +25,18 @@ export class SchemaController { } @MessagePattern({ cmd: 'get-schemas' }) - async getSchemas(schemaSearch: ISchemaSearchInterface): Promise<{ - totalItems: number; - hasNextPage: boolean; - hasPreviousPage: boolean; - nextPage: number; - previousPage: number; - lastPage: number; - data: { - createDateTime: Date; - createdBy: string; - name: string; - version: string; - attributes: string; - schemaLedgerId: string; - publisherDid: string; - issuerId: string; - orgId: string; - }[]; - }> { - const { schemaSearchCriteria, user, orgId } = schemaSearch; - return this.schemaService.getSchemas(schemaSearchCriteria, user, orgId); + async getSchemas(schemaSearch: ISchemaSearchPayload): Promise { + const { schemaSearchCriteria, orgId } = schemaSearch; + return this.schemaService.getSchemas(schemaSearchCriteria, orgId); } @MessagePattern({ cmd: 'get-cred-deff-list-by-schemas-id' }) - async getcredDeffListBySchemaId(payload: ISchemaCredDeffSearchInterface): Promise<{ - totalItems: number; - hasNextPage: boolean; - hasPreviousPage: boolean; - nextPage: number; - previousPage: number; - lastPage: number; - data: { - tag: string; - credentialDefinitionId: string; - schemaLedgerId: string; - revocable: boolean; - }[]; - }> { - const { schemaId, schemaSearchCriteria, user, orgId } = payload; - return this.schemaService.getcredDeffListBySchemaId(schemaId, schemaSearchCriteria, user, orgId); + async getcredDeffListBySchemaId(payload: ISchemaCredDeffSearchInterface): Promise { + return this.schemaService.getcredDeffListBySchemaId(payload); } @MessagePattern({ cmd: 'get-all-schemas' }) - async getAllSchema(schemaSearch: ISchemaSearchInterface): Promise<{ + async getAllSchema(schemaSearch: ISchemaSearchPayload): Promise<{ totalItems: number; hasNextPage: boolean; hasPreviousPage: boolean; diff --git a/apps/ledger/src/schema/schema.module.ts b/apps/ledger/src/schema/schema.module.ts index 2d5bacba8..49b99df38 100644 --- a/apps/ledger/src/schema/schema.module.ts +++ b/apps/ledger/src/schema/schema.module.ts @@ -7,6 +7,7 @@ import { SchemaRepository } from './repositories/schema.repository'; import { SchemaService } from './schema.service'; import { HttpModule } from '@nestjs/axios'; import { PrismaService } from '@credebl/prisma-service'; +import { CacheModule } from '@nestjs/cache-manager'; import { getNatsOptions } from '@credebl/common/nats.config'; @Module({ imports: [ @@ -19,7 +20,8 @@ import { getNatsOptions } from '@credebl/common/nats.config'; ]), HttpModule, - CommonModule + CommonModule, + CacheModule.register() ], providers: [ SchemaService, diff --git a/apps/ledger/src/schema/schema.service.ts b/apps/ledger/src/schema/schema.service.ts index 0086fde1a..a8e1ba829 100644 --- a/apps/ledger/src/schema/schema.service.ts +++ b/apps/ledger/src/schema/schema.service.ts @@ -11,18 +11,23 @@ import { ClientProxy, RpcException } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { SchemaRepository } from './repositories/schema.repository'; import { schema } from '@prisma/client'; -import { ISchema, ISchemaPayload, ISchemaSearchCriteria } from './interfaces/schema-payload.interface'; +import { ISchema, ISchemaCredDeffSearchInterface, ISchemaPayload, ISchemaSearchCriteria } from './interfaces/schema-payload.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; import { IUserRequestInterface } from './interfaces/schema.interface'; import { CreateSchemaAgentRedirection, GetSchemaAgentRedirection } from './schema.interface'; import { map } from 'rxjs/operators'; import { OrgAgentType } from '@credebl/enum/enum'; +import { Cache } from 'cache-manager'; +import { CACHE_MANAGER } from '@nestjs/cache-manager'; +import { CommonConstants } from '@credebl/common/common.constant'; +import { ICredDefWithPagination, ISchemasWithPagination } from '@credebl/common/interfaces/schema.interface'; @Injectable() export class SchemaService extends BaseService { constructor( private readonly schemaRepository: SchemaRepository, - @Inject('NATS_CLIENT') private readonly schemaServiceProxy: ClientProxy + @Inject('NATS_CLIENT') private readonly schemaServiceProxy: ClientProxy, + @Inject(CACHE_MANAGER) private cacheService: Cache ) { super('SchemaService'); } @@ -32,10 +37,15 @@ export class SchemaService extends BaseService { user: IUserRequestInterface, orgId: string ): Promise { - const apiKey = ''; + // const apiKey = ''; + // const apiKey = await this._getOrgAgentApiKey(orgId); + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } const { userId } = user.selectedOrg; try { - + const schemaExists = await this.schemaRepository.schemaExists( schema.schemaName, schema.schemaVersion @@ -43,7 +53,10 @@ export class SchemaService extends BaseService { if (0 !== schemaExists.length) { this.logger.error(ResponseMessages.schema.error.exists); - throw new ConflictException(ResponseMessages.schema.error.exists); + throw new ConflictException( + ResponseMessages.schema.error.exists, + { cause: new Error(), description: ResponseMessages.errorMessages.conflict } + ); } if (null !== schema || schema !== undefined) { @@ -54,14 +67,16 @@ export class SchemaService extends BaseService { schemaVersionIndexOf ) { throw new NotAcceptableException( - ResponseMessages.schema.error.invalidVersion + ResponseMessages.schema.error.invalidVersion, + { cause: new Error(), description: ResponseMessages.errorMessages.notAcceptable } ); } const schemaAttributeLength = 0; if (schema.attributes.length === schemaAttributeLength) { - throw new NotAcceptableException( - ResponseMessages.schema.error.insufficientAttributes + throw new NotAcceptableException( + ResponseMessages.schema.error.insufficientAttributes, + { cause: new Error(), description: ResponseMessages.errorMessages.notAcceptable } ); } else if (schema.attributes.length > schemaAttributeLength) { @@ -72,24 +87,37 @@ export class SchemaService extends BaseService { })); - const attributeNamesLowerCase = trimmedAttributes.map(attribute => attribute.attributeName.toLowerCase()); - const duplicateAttributeNames = attributeNamesLowerCase - .filter((value, index, element) => element.indexOf(value) !== index); + const attributeNamesLowerCase = trimmedAttributes.map(attribute => attribute.attributeName.toLowerCase()); + const duplicateAttributeNames = attributeNamesLowerCase + .filter((value, index, element) => element.indexOf(value) !== index); if (0 < duplicateAttributeNames.length) { - throw new ConflictException(ResponseMessages.schema.error.uniqueAttributesnames); + throw new ConflictException( + ResponseMessages.schema.error.uniqueAttributesnames, + { cause: new Error(), description: ResponseMessages.errorMessages.conflict } + ); } - const attributeDisplayNamesLowerCase = trimmedAttributes.map(attribute => attribute.displayName.toLocaleLowerCase()); - const duplicateAttributeDisplayNames = attributeDisplayNamesLowerCase - .filter((value, index, element) => element.indexOf(value) !== index); + const attributeDisplayNamesLowerCase = trimmedAttributes.map(attribute => attribute.displayName.toLocaleLowerCase()); + const duplicateAttributeDisplayNames = attributeDisplayNamesLowerCase + .filter((value, index, element) => element.indexOf(value) !== index); if (0 < duplicateAttributeDisplayNames.length) { - throw new ConflictException(ResponseMessages.schema.error.uniqueAttributesDisplaynames); + throw new ConflictException( + ResponseMessages.schema.error.uniqueAttributesDisplaynames, + { cause: new Error(), description: ResponseMessages.errorMessages.conflict } + ); } schema.schemaName = schema.schemaName.trim(); - const { agentEndPoint, orgDid } = await this.schemaRepository.getAgentDetailsByOrgId(orgId); + const agentDetails = await this.schemaRepository.getAgentDetailsByOrgId(orgId); + if (!agentDetails) { + throw new NotFoundException( + ResponseMessages.schema.error.agentDetailsNotFound, + { cause: new Error(), description: ResponseMessages.errorMessages.notFound } + ); + } + const { agentEndPoint, orgDid } = agentDetails; const getAgentDetails = await this.schemaRepository.getAgentType(orgId); // eslint-disable-next-line yoda const did = schema.orgDid?.split(':').length >= 4 ? schema.orgDid : orgDid; @@ -133,7 +161,7 @@ export class SchemaService extends BaseService { const responseObj = JSON.parse(JSON.stringify(schemaResponseFromAgentService.response)); const indyNamespace = `${did.split(':')[2]}:${did.split(':')[3]}`; - const getLedgerId = await this.schemaRepository.getLedgerByLedger(indyNamespace); + const getLedgerId = await this.schemaRepository.getLedgerByNamespace(indyNamespace); const schemaDetails: ISchema = { schema: { schemaName: '', attributes: [], schemaVersion: '', id: '' }, createdBy: `0`, @@ -178,43 +206,35 @@ export class SchemaService extends BaseService { return saveResponse; } else { - throw new NotFoundException(ResponseMessages.schema.error.notCreated); + throw new NotFoundException( + ResponseMessages.schema.error.notCreated, + { cause: new Error(), description: ResponseMessages.errorMessages.notFound } + ); } } else { - throw new RpcException( - new BadRequestException( - ResponseMessages.schema.error.emptyData - ) + throw new BadRequestException( + ResponseMessages.schema.error.emptyData, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } ); } - } else { - throw new RpcException( - new BadRequestException( - ResponseMessages.schema.error.emptyData - ) + } else { + throw new BadRequestException( + ResponseMessages.schema.error.emptyData, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } ); } - } catch (error) { this.logger.error( `[createSchema] - outer Error: ${JSON.stringify(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); - } + throw new RpcException(error.response ? error.response : error); } } async _createSchema(payload: CreateSchemaAgentRedirection): Promise<{ response: string; }> { - try { const pattern = { cmd: 'agent-create-schema' }; @@ -227,18 +247,15 @@ export class SchemaService extends BaseService { })) ).toPromise() .catch(error => { - this.logger.error(`Catch : ${JSON.stringify(error)}`); + this.logger.error(`Error in creating schema : ${JSON.stringify(error)}`); throw new HttpException( { - status: error.statusCode, - error: error.message + status: error.statusCode, + error: error.error, + message: error.message }, error.error); }); - return schemaResponse; - } catch (error) { - this.logger.error(`Error in creating schema : ${JSON.stringify(error)}`); - throw error; - } + return schemaResponse; } @@ -247,7 +264,15 @@ export class SchemaService extends BaseService { const { agentEndPoint } = await this.schemaRepository.getAgentDetailsByOrgId(orgId); const getAgentDetails = await this.schemaRepository.getAgentType(orgId); const orgAgentType = await this.schemaRepository.getOrgAgentType(getAgentDetails.org_agents[0].orgAgentTypeId); - const apiKey = ''; + // const apiKey = ''; + + let apiKey; + apiKey = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + + } + let schemaResponse; if (OrgAgentType.DEDICATED === orgAgentType) { const getSchemaPayload = { @@ -264,7 +289,8 @@ export class SchemaService extends BaseService { method: 'getSchemaById', payload: { schemaId }, agentType: OrgAgentType.SHARED, - agentEndPoint + agentEndPoint, + apiKey }; schemaResponse = await this._getSchemaById(getSchemaPayload); } @@ -312,49 +338,32 @@ export class SchemaService extends BaseService { } } - async getSchemas(schemaSearchCriteria: ISchemaSearchCriteria, user: IUserRequestInterface, orgId: string): Promise<{ - totalItems: number; - hasNextPage: boolean; - hasPreviousPage: boolean; - nextPage: number; - previousPage: number; - lastPage: number; - data: { - createDateTime: Date; - createdBy: string; - name: string; - version: string; - attributes: string; - schemaLedgerId: string; - publisherDid: string; - issuerId: string; - orgId: string; - }[]; - }> { + async getSchemas(schemaSearchCriteria: ISchemaSearchCriteria, orgId: string): Promise { try { const response = await this.schemaRepository.getSchemas(schemaSearchCriteria, orgId); + if (0 === response.schemasCount) { + throw new NotFoundException(ResponseMessages.schema.error.notFound); + } + const schemasDetails = response?.schemasResult.map(schemaAttributeItem => { const attributes = JSON.parse(schemaAttributeItem.attributes); return { ...schemaAttributeItem, attributes }; }); - const schemasResponse = { + const nextPage:number = Number(schemaSearchCriteria.pageNumber) + 1; + + const schemasResponse: ISchemasWithPagination = { totalItems: response.schemasCount, hasNextPage: schemaSearchCriteria.pageSize * schemaSearchCriteria.pageNumber < response.schemasCount, hasPreviousPage: 1 < schemaSearchCriteria.pageNumber, - nextPage: schemaSearchCriteria.pageNumber + 1, + nextPage, previousPage: schemaSearchCriteria.pageNumber - 1, lastPage: Math.ceil(response.schemasCount / schemaSearchCriteria.pageSize), data: schemasDetails }; - if (0 !== response.schemasCount) { - return schemasResponse; - } else { - throw new NotFoundException(ResponseMessages.schema.error.notFound); - } - + return schemasResponse; } catch (error) { this.logger.error(`Error in retrieving schemas by org id: ${error}`); @@ -362,38 +371,29 @@ export class SchemaService extends BaseService { } } - async getcredDeffListBySchemaId(schemaId: string, schemaSearchCriteria: ISchemaSearchCriteria, user: IUserRequestInterface, orgId: string): Promise<{ - totalItems: number; - hasNextPage: boolean; - hasPreviousPage: boolean; - nextPage: number; - previousPage: number; - lastPage: number; - data: { - tag: string; - credentialDefinitionId: string; - schemaLedgerId: string; - revocable: boolean; - }[]; - }> { + async getcredDeffListBySchemaId( + payload: ISchemaCredDeffSearchInterface + ): Promise { + const { schemaSearchCriteria } = payload; + try { - const response = await this.schemaRepository.getSchemasCredDeffList(schemaSearchCriteria, orgId, schemaId); + const response = await this.schemaRepository.getSchemasCredDeffList(schemaSearchCriteria); + + if (0 === response.credDefCount) { + throw new NotFoundException(ResponseMessages.schema.error.credentialDefinitionNotFound); + } + const schemasResponse = { - totalItems: response.length, - hasNextPage: schemaSearchCriteria.pageSize * schemaSearchCriteria.pageNumber < response.length, + totalItems: response.credDefCount, + hasNextPage: schemaSearchCriteria.pageSize * schemaSearchCriteria.pageNumber < response.credDefCount, hasPreviousPage: 1 < schemaSearchCriteria.pageNumber, nextPage: schemaSearchCriteria.pageNumber + 1, previousPage: schemaSearchCriteria.pageNumber - 1, - lastPage: Math.ceil(response.length / schemaSearchCriteria.pageSize), - data: response + lastPage: Math.ceil(response.credDefCount / schemaSearchCriteria.pageSize), + data: response.credDefResult }; - if (0 !== response.length) { - return schemasResponse; - } else { - throw new NotFoundException(ResponseMessages.schema.error.credentialDefinitionNotFound); - } - + return schemasResponse; } catch (error) { this.logger.error(`Error in retrieving credential definition: ${error}`); @@ -449,4 +449,23 @@ export class SchemaService extends BaseService { throw new RpcException(error.response ? error.response : error); } } + + async _getOrgAgentApiKey(orgId: string): Promise { + const pattern = { cmd: 'get-org-agent-api-key' }; + const payload = { orgId }; + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.schemaServiceProxy.send(pattern, payload).toPromise(); + return message; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException({ + status: error.status, + error: error.message + }, error.status); + } + } + + } diff --git a/apps/organization/interfaces/organization.interface.ts b/apps/organization/interfaces/organization.interface.ts index 7781994e0..804b27b65 100644 --- a/apps/organization/interfaces/organization.interface.ts +++ b/apps/organization/interfaces/organization.interface.ts @@ -3,13 +3,7 @@ export interface IUserOrgRoles { userId: string orgRoleId: string orgId: string | null, - orgRole: OrgRole -} - -export interface OrgRole { - id: string - name: string - description: string + orgRole: IOrgRole } export interface IUpdateOrganization { @@ -24,7 +18,116 @@ export interface IUpdateOrganization { } -export interface OrgAgent { +export interface IOrgAgent { url: string; apiKey: string; +} + + +export interface IGetOrgById { + id: string; + name: string; + description: string; + orgSlug: string; + logoUrl: string; + website: string; + publicProfile: boolean; + schema: ISchema[]; + org_agents: IOrgAgents[]; +} + +interface ISchema { + id: string; + name: string; +} + +interface IOrgAgents { + agent_invitations: IAgentInvitation[]; + ledgers: ILedgers; + org_agent_type: IOrgAgentType; +} + +interface IAgentInvitation { + id: string; + connectionInvitation: string; + multiUse: boolean; +} + +export interface IUserOrgRole { + user: string; + orgRole: string; +} + +interface IOrgAgentType { + id: string; + createDateTime: Date; + lastChangedDateTime: Date; + agent: string; +} + +interface ILedgers { + id: string; + name: string; + networkType: string +} + +export interface IGetOrgs { + totalPages:number; + organizations : IAllOrganizations[]; +} + +interface IAllOrganizations { + id: string, + name: string, + description: string, + logoUrl: string, + orgSlug: string, + userOrgRoles: IUserOrganizationRoles[]; +} + +interface IUserOrganizationRoles { + id: string, + orgRole :IOrgRole; +} + +export interface IOrgRole { + id: string + name: string + description: string +} + +export interface IOrgInvitationsPagination { + totalPages: number; + invitations: IInvitation[]; +} + +interface IInvitation { + id: string, + orgId: string, + email: string, + userId: string, + status: string, + orgRoles: string[], + createDateTime: Date, + createdBy:string, + organisation: IOrganizationPagination; +} + +interface IOrganizationPagination { + id: string; + name: string; + logoUrl: string; +} + +export interface IOrganizationDashboard { + usersCount: number, + schemasCount: number, + credentialsCount: number, + presentationsCount:number +} + +export interface Payload { + pageNumber: number; + pageSize: number; + search: string; } \ No newline at end of file diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 5e56ceb56..9bc7183d9 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -6,7 +6,7 @@ import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { org_agents, org_invitations, user_org_roles } from '@prisma/client'; import { CreateOrganizationDto } from '../dtos/create-organization.dto'; -import { IUpdateOrganization } from '../interfaces/organization.interface'; +import { IGetOrgById, IGetOrgs, IOrgInvitationsPagination, IOrganizationDashboard, IUpdateOrganization } from '../interfaces/organization.interface'; import { InternalServerErrorException } from '@nestjs/common'; import { Invitation } from '@credebl/enum/enum'; import { PrismaService } from '@credebl/prisma-service'; @@ -49,7 +49,7 @@ export class OrganizationRepository { async createOrganization(createOrgDto: CreateOrganizationDto): Promise { try { - return this.prisma.organisation.create({ + const orgData = this.prisma.organisation.create({ data: { name: createOrgDto.name, logoUrl: createOrgDto.logo, @@ -61,6 +61,7 @@ export class OrganizationRepository { lastChangedBy: createOrgDto.lastChangedBy } }); + return orgData; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); @@ -176,7 +177,7 @@ export class OrganizationRepository { pageNumber: number, pageSize: number, search = '' - ): Promise { + ): Promise { this.logger.log(search); const query = { @@ -204,21 +205,29 @@ export class OrganizationRepository { } } - async getOrgInvitationsPagination(queryObject: object, pageNumber: number, pageSize: number): Promise { + async getOrgInvitationsPagination(queryObject: object, pageNumber: number, pageSize: number): Promise { try { const result = await this.prisma.$transaction([ this.prisma.org_invitations.findMany({ where: { ...queryObject }, - include: { + select: { + id: true, + orgId: true, + email: true, + userId: true, + status: true, + createDateTime: true, + createdBy: true, organisation: { select: { id: true, name: true, logoUrl: true } - } + }, + orgRoles: true }, take: pageSize, skip: (pageNumber - 1) * pageSize, @@ -244,7 +253,7 @@ export class OrganizationRepository { } } - async getInvitationsByOrgId(orgId: string, pageNumber: number, pageSize: number, search = ''): Promise { + async getInvitationsByOrgId(orgId: string, pageNumber: number, pageSize: number, search = ''): Promise { try { const query = { orgId, @@ -261,7 +270,7 @@ export class OrganizationRepository { } } - async getOrganization(queryObject: object): Promise { + async getOrganization(queryObject: object): Promise { try { return this.prisma.organisation.findFirst({ where: { @@ -283,20 +292,19 @@ export class OrganizationRepository { }, org_agents: { select: { - orgDid: true, id: true, + orgDid: true, walletName: true, + agentEndPoint: true, agentSpinUpStatus: true, agentsTypeId: true, + orgAgentTypeId: true, createDateTime: true, - orgAgentTypeId:true, agent_invitations: { select: { id: true, connectionInvitation: true, - multiUse: true, - createDateTime: true, - lastChangedDateTime:true + multiUse: true } }, org_agent_type: true, @@ -309,6 +317,7 @@ export class OrganizationRepository { } } } + } }); } catch (error) { @@ -317,7 +326,7 @@ export class OrganizationRepository { } } - async getOrgDashboard(orgId: string): Promise { + async getOrgDashboard(orgId: string): Promise { const query = { where: { @@ -413,7 +422,7 @@ export class OrganizationRepository { filterOptions: object, pageNumber: number, pageSize: number - ): Promise { + ): Promise { try { const sortByName = 'asc'; const result = await this.prisma.$transaction([ @@ -433,7 +442,8 @@ export class OrganizationRepository { orgRole: { select: { id: true, - name: true + name: true, + description: true } } }, @@ -447,7 +457,7 @@ export class OrganizationRepository { skip: (pageNumber - 1) * pageSize, orderBy: { name: sortByName - + } }), this.prisma.organisation.count({ @@ -575,4 +585,23 @@ export class OrganizationRepository { } } + + /** + * + * @param id + * @returns Delete Invitation + */ + async deleteOrganizationInvitation(id: string): Promise { + try { + return await this.prisma.org_invitations.delete({ + where: { + id + } + }); + } catch (error) { + this.logger.error(`Delete Org Invitation Error: ${JSON.stringify(error)}`); + throw error; + } + } + } diff --git a/apps/organization/src/organization.controller.ts b/apps/organization/src/organization.controller.ts index f77ef73ee..90a0b3981 100644 --- a/apps/organization/src/organization.controller.ts +++ b/apps/organization/src/organization.controller.ts @@ -6,7 +6,9 @@ import { Body } from '@nestjs/common'; import { CreateOrganizationDto } from '../dtos/create-organization.dto'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; import { UpdateInvitationDto } from '../dtos/update-invitation.dt'; -import { IUpdateOrganization } from '../interfaces/organization.interface'; +import { IGetOrgById, IGetOrgs, IOrgInvitationsPagination, IOrganizationDashboard, IUpdateOrganization, Payload } from '../interfaces/organization.interface'; +import { organisation } from '@prisma/client'; +import { IOrgRoles } from 'libs/org-roles/interfaces/org-roles.interface'; @Controller() export class OrganizationController { @@ -20,7 +22,7 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'create-organization' }) - async createOrganization(@Body() payload: { createOrgDto: CreateOrganizationDto; userId: string }): Promise { + async createOrganization(@Body() payload: { createOrgDto: CreateOrganizationDto; userId: string }): Promise { return this.organizationService.createOrganization(payload.createOrgDto, payload.userId); } @@ -31,7 +33,7 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'update-organization' }) - async updateOrganization(payload: { updateOrgDto: IUpdateOrganization; userId: string, orgId: string }): Promise { + async updateOrganization(payload: { updateOrgDto: IUpdateOrganization; userId: string, orgId: string }): Promise { return this.organizationService.updateOrganization(payload.updateOrgDto, payload.userId, payload.orgId); } @@ -42,8 +44,8 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'get-organizations' }) async getOrganizations( - @Body() payload: { userId: string; pageNumber: number; pageSize: number; search: string } - ): Promise { + @Body() payload: { userId: string} & Payload + ): Promise { const { userId, pageNumber, pageSize, search } = payload; return this.organizationService.getOrganizations(userId, pageNumber, pageSize, search); } @@ -55,8 +57,8 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'get-public-organizations' }) async getPublicOrganizations( - @Body() payload: { pageNumber: number; pageSize: number; search: string } - ): Promise { + @Body() payload: Payload + ): Promise { const { pageNumber, pageSize, search } = payload; return this.organizationService.getPublicOrganizations(pageNumber, pageSize, search); } @@ -67,12 +69,12 @@ export class OrganizationController { * @returns Get created organization details */ @MessagePattern({ cmd: 'get-organization-by-id' }) - async getOrganization(@Body() payload: { orgId: string; userId: string}): Promise { + async getOrganization(@Body() payload: { orgId: string; userId: string}): Promise { return this.organizationService.getOrganization(payload.orgId); } @MessagePattern({ cmd: 'get-organization-public-profile' }) - async getPublicProfile(payload: { orgSlug }): Promise { + async getPublicProfile(payload: { orgSlug }): Promise { return this.organizationService.getPublicProfile(payload); } @@ -83,8 +85,8 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'get-invitations-by-orgId' }) async getInvitationsByOrgId( - @Body() payload: { orgId: string; pageNumber: number; pageSize: number; search: string } - ): Promise { + @Body() payload: { orgId: string } & Payload + ): Promise { return this.organizationService.getInvitationsByOrgId( payload.orgId, payload.pageNumber, @@ -99,7 +101,7 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'get-org-roles' }) - async getOrgRoles(): Promise { + async getOrgRoles(): Promise { return this.organizationService.getOrgRoles(); } @@ -117,8 +119,8 @@ export class OrganizationController { @MessagePattern({ cmd: 'fetch-user-invitations' }) async fetchUserInvitation( - @Body() payload: { email: string; status: string; pageNumber: number; pageSize: number; search: string } - ): Promise { + @Body() payload: { email: string; status: string } & Payload + ): Promise { return this.organizationService.fetchUserInvitation( payload.email, payload.status, @@ -150,12 +152,12 @@ export class OrganizationController { } @MessagePattern({ cmd: 'get-organization-dashboard' }) - async getOrgDashboard(payload: { orgId: string; userId: string }): Promise { + async getOrgDashboard(payload: { orgId: string; userId: string }): Promise { return this.organizationService.getOrgDashboard(payload.orgId); } @MessagePattern({ cmd: 'fetch-organization-profile' }) - async getOgPofile(payload: { orgId: string }): Promise { + async getOgPofile(payload: { orgId: string }): Promise { return this.organizationService.getOgPofile(payload.orgId); } @@ -163,4 +165,9 @@ export class OrganizationController { async deleteOrganization(payload: { orgId: string }): Promise { return this.organizationService.deleteOrganization(payload.orgId); } + + @MessagePattern({ cmd: 'delete-organization-invitation' }) + async deleteOrganizationInvitation(payload: { orgId: string; invitationId: string; }): Promise { + return this.organizationService.deleteOrganizationInvitation(payload.orgId, payload.invitationId); + } } diff --git a/apps/organization/src/organization.module.ts b/apps/organization/src/organization.module.ts index 62eb05f7a..c50a16d8f 100644 --- a/apps/organization/src/organization.module.ts +++ b/apps/organization/src/organization.module.ts @@ -13,6 +13,7 @@ import { UserActivityService } from '@credebl/user-activity'; import { UserOrgRolesRepository } from 'libs/user-org-roles/repositories'; import { UserOrgRolesService } from '@credebl/user-org-roles'; import { UserRepository } from 'apps/user/repositories/user.repository'; +import { CacheModule } from '@nestjs/cache-manager'; import { getNatsOptions } from '@credebl/common/nats.config'; @Module({ @@ -24,7 +25,8 @@ import { getNatsOptions } from '@credebl/common/nats.config'; options: getNatsOptions(process.env.ORGANIZATION_NKEY_SEED) } ]), - CommonModule + CommonModule, + CacheModule.register() ], controllers: [OrganizationController], providers: [ diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index d5755864e..a5d79c42f 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -1,6 +1,5 @@ -// eslint-disable-next-line camelcase -import { organisation, org_roles, user } from '@prisma/client'; -import { Injectable, Logger, ConflictException, InternalServerErrorException, HttpException } from '@nestjs/common'; +import { organisation, user } from '@prisma/client'; +import { Injectable, Logger, ConflictException, InternalServerErrorException, HttpException, BadRequestException, ForbiddenException } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; import { CommonService } from '@credebl/common'; import { OrganizationRepository } from '../repositories/organization.repository'; @@ -18,10 +17,13 @@ import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; import { UpdateInvitationDto } from '../dtos/update-invitation.dt'; import { NotFoundException } from '@nestjs/common'; import { Invitation, OrgAgentType } from '@credebl/enum/enum'; -import { IUpdateOrganization, OrgAgent } from '../interfaces/organization.interface'; +import { IGetOrgById, IGetOrgs, IOrgInvitationsPagination, IOrganizationDashboard, IUpdateOrganization, IOrgAgent } from '../interfaces/organization.interface'; import { UserActivityService } from '@credebl/user-activity'; import { CommonConstants } from '@credebl/common/common.constant'; import { map } from 'rxjs/operators'; +import { Cache } from 'cache-manager'; +import { CACHE_MANAGER } from '@nestjs/cache-manager'; +import { IOrgRoles } from 'libs/org-roles/interfaces/org-roles.interface'; @Injectable() export class OrganizationService { constructor( @@ -32,7 +34,8 @@ export class OrganizationService { private readonly orgRoleService: OrgRolesService, private readonly userOrgRoleService: UserOrgRolesService, private readonly userActivityService: UserActivityService, - private readonly logger: Logger + private readonly logger: Logger, + @Inject(CACHE_MANAGER) private cacheService: Cache ) { } /** @@ -103,7 +106,7 @@ export class OrganizationService { const orgSlug = await this.createOrgSlug(updateOrgDto.name); updateOrgDto.orgSlug = orgSlug; - updateOrgDto.userId = userId; + updateOrgDto.userId = userId; const organizationDetails = await this.organizationRepository.updateOrganization(updateOrgDto); await this.userActivityService.createActivity(userId, organizationDetails.id, `${organizationDetails.name} organization updated`, 'Organization details updated successfully'); return organizationDetails; @@ -118,8 +121,8 @@ export class OrganizationService { * @param * @returns Get created organizations details */ - // eslint-disable-next-line camelcase - async getOrganizations(userId: string, pageNumber: number, pageSize: number, search: string): Promise { + + async getOrganizations(userId: string, pageNumber: number, pageSize: number, search: string): Promise { try { const query = { @@ -136,12 +139,13 @@ export class OrganizationService { userId }; - return this.organizationRepository.getOrganizations( + const getOrgs = await this.organizationRepository.getOrganizations( query, filterOptions, pageNumber, pageSize ); + return getOrgs; } catch (error) { this.logger.error(`In fetch getOrganizations : ${JSON.stringify(error)}`); @@ -154,8 +158,8 @@ export class OrganizationService { * @param * @returns Get public organizations details */ - // eslint-disable-next-line camelcase - async getPublicOrganizations(pageNumber: number, pageSize: number, search: string): Promise { + + async getPublicOrganizations(pageNumber: number, pageSize: number, search: string): Promise { try { const query = { @@ -181,10 +185,10 @@ export class OrganizationService { } } - async getPublicProfile(payload: { orgSlug: string }): Promise { + async getPublicProfile(payload: { orgSlug: string }): Promise { const { orgSlug } = payload; try { - + const query = { orgSlug, publicProfile: true @@ -195,8 +199,8 @@ export class OrganizationService { throw new NotFoundException(ResponseMessages.organisation.error.profileNotFound); } - const credentials = await this.organizationRepository.getCredDefByOrg(organizationDetails['id']); - organizationDetails['credential_definitions'] = credentials; + const credDefs = await this.organizationRepository.getCredDefByOrg(organizationDetails.id); + organizationDetails['credential_definitions'] = credDefs; return organizationDetails; } catch (error) { @@ -210,8 +214,8 @@ export class OrganizationService { * @param orgId Registration Details * @returns Get created organization details */ - // eslint-disable-next-line camelcase - async getOrganization(orgId: string): Promise { + + async getOrganization(orgId: string): Promise { try { const query = { @@ -231,12 +235,12 @@ export class OrganizationService { * @param orgId Registration Details * @returns Get created invitation details */ - // eslint-disable-next-line camelcase - async getInvitationsByOrgId(orgId: string, pageNumber: number, pageSize: number, search: string): Promise { + + async getInvitationsByOrgId(orgId: string, pageNumber: number, pageSize: number, search: string): Promise { try { const getOrganization = await this.organizationRepository.getInvitationsByOrgId(orgId, pageNumber, pageSize, search); for await (const item of getOrganization['invitations']) { - const getOrgRoles = await this.orgRoleService.getOrgRolesByIds(item.orgRoles); + const getOrgRoles = await this.orgRoleService.getOrgRolesByIds(item['orgRoles']); (item['orgRoles'] as object) = getOrgRoles; }; return getOrganization; @@ -252,8 +256,8 @@ export class OrganizationService { * @returns */ - // eslint-disable-next-line camelcase - async getOrgRoles(): Promise { + + async getOrgRoles(): Promise { try { return this.orgRoleService.getOrgRoles(); } catch (error) { @@ -309,7 +313,7 @@ export class OrganizationService { * @returns createInvitation */ - // eslint-disable-next-line camelcase + async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: string, userEmail: string): Promise { const { invitations, orgId } = bulkInvitationDto; @@ -397,7 +401,7 @@ export class OrganizationService { return false; } - async fetchUserInvitation(email: string, status: string, pageNumber: number, pageSize: number, search = ''): Promise { + async fetchUserInvitation(email: string, status: string, pageNumber: number, pageSize: number, search = ''): Promise { try { return this.organizationRepository.getAllOrgInvitations(email, status, pageNumber, pageSize, search); } catch (error) { @@ -478,7 +482,7 @@ export class OrganizationService { } } - async getOrgDashboard(orgId: string): Promise { + async getOrgDashboard(orgId: string): Promise { try { return this.organizationRepository.getOrgDashboard(orgId); } catch (error) { @@ -503,7 +507,12 @@ export class OrganizationService { async deleteOrganization(orgId: string): Promise { try { const getAgent = await this.organizationRepository.getAgentEndPoint(orgId); - + // const apiKey = await this._getOrgAgentApiKey(orgId); + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + this.logger.log(`cachedApiKey----${apiKey}`); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } let url; if (getAgent.orgAgentTypeId === OrgAgentType.DEDICATED) { url = `${getAgent.agentEndPoint}${CommonConstants.URL_DELETE_WALLET}`; @@ -514,7 +523,7 @@ export class OrganizationService { const payload = { url, - apiKey: getAgent.apiKey + apiKey }; const deleteWallet = await this._deleteWallet(payload); @@ -534,7 +543,7 @@ export class OrganizationService { } } - async _deleteWallet(payload: OrgAgent): Promise<{ + async _deleteWallet(payload: IOrgAgent): Promise<{ response; }> { try { @@ -563,4 +572,50 @@ export class OrganizationService { throw error; } } + + + async _getOrgAgentApiKey(orgId: string): Promise { + const pattern = { cmd: 'get-org-agent-api-key' }; + const payload = { orgId }; + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.organizationServiceProxy.send(pattern, payload).toPromise(); + return message; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException({ + status: error.status, + error: error.message + }, error.status); + } + } + + async deleteOrganizationInvitation(orgId: string, invitationId: string): Promise { + try { + const invitationDetails = await this.organizationRepository.getInvitationById(invitationId); + + // Check invitation is present + if (!invitationDetails) { + throw new NotFoundException(ResponseMessages.user.error.invitationNotFound); + } + + // Check if delete process initiated by the org who has created invitation + if (orgId !== invitationDetails.orgId) { + throw new ForbiddenException(ResponseMessages.organisation.error.deleteOrgInvitation); + } + + // Check if invitation is already accepted/rejected + if (Invitation.PENDING !== invitationDetails.status) { + throw new BadRequestException(ResponseMessages.organisation.error.invitationStatusInvalid); + } + + await this.organizationRepository.deleteOrganizationInvitation(invitationId); + + return true; + } catch (error) { + this.logger.error(`delete organization invitation: ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } } \ No newline at end of file diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index a7b6af44c..9e2c420de 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -1,36 +1,36 @@ -export interface UserInvitations { - totalPages:number; - userInvitationsData:UserInvitationsData[]; -} -export interface UserInvitationsData { - orgRoles: OrgRole[]; - status: string; - id: string; - orgId: string; - organisation: Organisation; - userId: string; -} -export interface OrgRole { +export interface IUsersProfile { id: string; - name: string; - description: string; + username?: string; + email?: string; + firstName?: string; + lastName?: string; + supabaseUserId?: string; + userOrgRoles?: IUserOrgRole[]; } -export interface Organisation { - id: string; - name: string; - logoUrl: string; +interface IUserOrgRole { + id: string; + userId: string; + orgRoleId: string; + orgId: string; + orgRole :IOrgRole; + organisation:IOrganisation; } + export interface IOrgRole{ + id: string; + name: string; + description: string; + }; + export interface IOrganisation{ + id: string; + name: string; + description: string; + orgSlug: string; + logoUrl: string; + website: string; + publicProfile: boolean; + }; -export interface UsersProfile { - id?: string; - username?: string; - email?: string; - firstName?: string; - lastName?: string; - supabaseUserId?: string; - userOrgRoles?: object; - } export interface OrgInvitations { id: string; @@ -43,12 +43,12 @@ export interface UsersProfile { orgRoles: string[]; } - export interface UserEmailVerificationDto { +export interface ISendVerificationEmail { email: string; username?: string; } - export interface userInfo { + export interface IUserInformation { email: string; password: string; firstName: string; @@ -88,18 +88,82 @@ export interface UsersProfile { label: string; } - export interface CheckUserDetails { + export interface ICheckUserDetails { + isExist: boolean; isEmailVerified?: boolean; isFidoVerified?: boolean; isSupabase?: boolean; - isExist?: boolean; } - export interface UserCredentials { + export interface IUserCredentials { id: string; imageUrl?: string; credentialId?: string; createDateTime: Date; lastChangedDateTime: Date; deletedAt: Date; - } \ No newline at end of file + } + + export interface IOrgUsers { + totalPages: number, + users: OrgUser[] + } + + interface OrgUser { + id: string; + username: string; + email: string; + firstName: string; + lastName: string; + isEmailVerified: boolean; + userOrgRoles: UserOrgRoles[]; + } + + interface UserOrgRoles { + id: string; + orgId: string; + orgRoleId: string; + orgRole: OrgRole; + organisation: Organization + } + interface OrgRole { + id: string; + name: string; + description: string; + } + + interface Organization { + id: string, + name: string, + description: string, + orgSlug: string, + logoUrl: string, + org_agents: OrgAgents[]; + } + + interface OrgAgents { + id: string, + orgDid: string, + walletName: string, + agentSpinUpStatus: number, + agentsTypeId: string, + createDateTime: Date, + orgAgentTypeId:string + } + + export interface Payload { + pageNumber: number; + pageSize: number; + search: string; + } + +export interface IVerifyUserEmail{ + email: string; + verificationCode: string; +} + +export interface IUserSignIn{ + email: string; + password: string; + isPasskey: boolean; +} \ No newline at end of file diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 6964109ba..940dd67e5 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -2,15 +2,16 @@ import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { + IOrgUsers, PlatformSettings, ShareUserCertificate, UpdateUserProfile, - UserCredentials, - UserEmailVerificationDto, - UsersProfile, - userInfo + IUserCredentials, + ISendVerificationEmail, + IUsersProfile, + IUserInformation, + IVerifyUserEmail } from '../interfaces/user.interface'; - import { InternalServerErrorException } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase @@ -26,21 +27,21 @@ interface UserQueryOptions { @Injectable() export class UserRepository { constructor( - private readonly prisma: PrismaService, + private readonly prisma: PrismaService, private readonly logger: Logger ) {} /** * - * @param userEmailVerificationDto - * @returns user email + * @param userEmailVerification + * @returns user's email */ - async createUser(userEmailVerificationDto: UserEmailVerificationDto, verifyCode: string): Promise { + async createUser(userEmailVerification:ISendVerificationEmail, verifyCode: string): Promise { try { const saveResponse = await this.prisma.user.create({ data: { - username: userEmailVerificationDto.username, - email: userEmailVerificationDto.email, + username: userEmailVerification.username, + email: userEmailVerification.email, verificationCode: verifyCode.toString(), publicProfile: true } @@ -96,7 +97,7 @@ export class UserRepository { * @param id * @returns User profile data */ - async getUserById(id: string): Promise { + async getUserById(id: string): Promise { const queryOptions: UserQueryOptions = { id }; @@ -109,7 +110,7 @@ export class UserRepository { * @param id * @returns User profile data */ - async getUserCredentialsById(credentialId: string): Promise { + async getUserCredentialsById(credentialId: string): Promise { return this.prisma.user_credentials.findUnique({ where: { credentialId @@ -122,7 +123,7 @@ export class UserRepository { * @param id * @returns User profile data */ - async getUserPublicProfile(username: string): Promise { + async getUserPublicProfile(username: string): Promise { const queryOptions: UserQueryOptions = { username }; @@ -202,7 +203,7 @@ export class UserRepository { return this.findUser(queryOptions); } - async findUser(queryOptions: UserQueryOptions): Promise { + async findUser(queryOptions: UserQueryOptions): Promise { return this.prisma.user.findFirst({ where: { OR: [ @@ -246,14 +247,14 @@ export class UserRepository { website: true, publicProfile: true } + } } - } } } }); } - async findUserForPublicProfile(queryOptions: UserQueryOptions): Promise { + async findUserForPublicProfile(queryOptions: UserQueryOptions): Promise { return this.prisma.user.findFirst({ where: { publicProfile: true, @@ -278,18 +279,26 @@ export class UserRepository { isEmailVerified: true, publicProfile: true, userOrgRoles: { - include: { - orgRole: true, + select:{ + id: true, + userId:true, + orgRoleId:true, + orgId:true, + orgRole: { + select:{ + id: true, + name: true, + description: true + } + }, organisation: { select: { id: true, name: true, description: true, + orgSlug:true, logoUrl: true, website: true, - orgSlug: true - }, - where: { publicProfile: true } } @@ -329,7 +338,7 @@ export class UserRepository { * @returns Updates user details */ // eslint-disable-next-line camelcase - async updateUserInfo(email: string, userInfo: userInfo): Promise { + async updateUserInfo(email: string, userInfo: IUserInformation): Promise { try { const updateUserDetails = await this.prisma.user.update({ where: { @@ -358,7 +367,7 @@ export class UserRepository { pageNumber: number, pageSize: number, filterOptions?: object - ): Promise { + ): Promise { const result = await this.prisma.$transaction([ this.prisma.user.findMany({ where: { @@ -366,28 +375,44 @@ export class UserRepository { }, select: { id: true, -username: true, + username: true, email: true, firstName: true, lastName: true, -isEmailVerified: true, - clientId: true, - clientSecret: true, - supabaseUserId: true, + isEmailVerified: true, userOrgRoles: { - where: { + where: { ...filterOptions -// Additional filtering conditions if needed + // Additional filtering conditions if needed }, - include: { - orgRole: true, + select: { + id: true, + orgId: true, + orgRoleId: true, + orgRole: { + select: { + id: true, + name: true, + description: true + } + }, organisation: { - include: { + select: { + id: true, + name: true, + description: true, + orgSlug: true, + logoUrl: true, // eslint-disable-next-line camelcase org_agents: { - include: { - // eslint-disable-next-line camelcase - agents_type: true + select: { + id: true, + orgDid: true, + walletName: true, + agentSpinUpStatus: true, + agentsTypeId: true, + createDateTime: true, + orgAgentTypeId:true } } } @@ -501,7 +526,7 @@ isEmailVerified: true, } } - async verifyUser(email: string): Promise { + async verifyUser(email: string): Promise { try { const updateUserDetails = await this.prisma.user.update({ where: { diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index fedfd998e..401e93e7c 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -1,13 +1,15 @@ -import { AddPasskeyDetails, CheckUserDetails, PlatformSettings, ShareUserCertificate, UserInvitations, UpdateUserProfile, UserCredentials, UserEmailVerificationDto, userInfo, UsersProfile } from '../interfaces/user.interface'; +import { ICheckUserDetails, PlatformSettings, ShareUserCertificate, UpdateUserProfile, IUserCredentials, IUsersProfile, IUserInformation, IUserSignIn} from '../interfaces/user.interface'; +import {IOrgUsers, Payload} from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { Controller } from '@nestjs/common'; -import { LoginUserDto } from '../dtos/login-user.dto'; import { MessagePattern } from '@nestjs/microservices'; import { UserService } from './user.service'; import { VerifyEmailTokenDto } from '../dtos/verify-email.dto'; import { user } from '@prisma/client'; -import { UsersActivity } from 'libs/user-activity/interface'; +import { IUsersActivity } from 'libs/user-activity/interface'; +import { ISendVerificationEmail, ISignInUser, IVerifyUserEmail, IUserInvitations } from '@credebl/common/interfaces/user.interface'; +import { AddPasskeyDetailsDto } from 'apps/api-gateway/src/user/dto/add-user.dto'; @Controller() export class UserController { @@ -15,38 +17,46 @@ export class UserController { /** * Description: Registers new user - * @param payload Registration Details - * @returns Get registered user response + * @param email + * @returns User's verification email sent status */ @MessagePattern({ cmd: 'send-verification-mail' }) - async sendVerificationMail(payload: { userEmailVerificationDto: UserEmailVerificationDto }): Promise { - return this.userService.sendVerificationMail(payload.userEmailVerificationDto); + async sendVerificationMail(payload: { userEmailVerification: ISendVerificationEmail }): Promise { + return this.userService.sendVerificationMail(payload.userEmailVerification); } /** * Description: Verify user's email - * @param param - * @returns Get user's email verified + * @param email + * @param verificationcode + * @returns User's email verification status */ @MessagePattern({ cmd: 'user-email-verification' }) - async verifyEmail(payload: { param: VerifyEmailTokenDto }): Promise { + async verifyEmail(payload: { param: VerifyEmailTokenDto }): Promise { return this.userService.verifyEmail(payload.param); } + /** + * @Body loginUserDto + * @returns User's access token details + */ @MessagePattern({ cmd: 'user-holder-login' }) - async login(payload: LoginUserDto): Promise { + async login(payload: IUserSignIn): Promise { return this.userService.login(payload); } @MessagePattern({ cmd: 'get-user-profile' }) - async getProfile(payload: { id }): Promise { + async getProfile(payload: { id }): Promise { return this.userService.getProfile(payload); } @MessagePattern({ cmd: 'get-user-public-profile' }) - async getPublicProfile(payload: { username }): Promise { + async getPublicProfile(payload: { username }): Promise { return this.userService.getPublicProfile(payload); } + /** + * @returns User details + */ @MessagePattern({ cmd: 'update-user-profile' }) async updateUserProfile(payload: { updateUserProfileDto: UpdateUserProfile }): Promise { return this.userService.updateUserProfile(payload.updateUserProfileDto); @@ -62,16 +72,21 @@ export class UserController { async findUserByEmail(payload: { email }): Promise { return this.userService.findUserByEmail(payload); } - - + /** + * @param credentialId + * @returns User credentials + */ @MessagePattern({ cmd: 'get-user-credentials-by-id' }) - async getUserCredentialsById(payload: { credentialId }): Promise { + async getUserCredentialsById(payload: { credentialId }): Promise { return this.userService.getUserCredentialsById(payload); } + /** + * @returns Organization invitation data + */ @MessagePattern({ cmd: 'get-org-invitations' }) - async invitations(payload: { id; status; pageNumber; pageSize; search; }): Promise { - return this.userService.invitations(payload); + async invitations(payload: { id; status; pageNumber; pageSize; search; }): Promise { + return this.userService.invitations(payload); } /** @@ -88,9 +103,8 @@ export class UserController { } /** - * * @param payload - * @returns Share user certificate + * @returns User certificate URL */ @MessagePattern({ cmd: 'share-user-certificate' }) async shareUserCertificate(payload: { @@ -105,12 +119,11 @@ export class UserController { * @returns organization users list */ @MessagePattern({ cmd: 'fetch-organization-user' }) - async getOrganizationUsers(payload: { orgId: string, pageNumber: number, pageSize: number, search: string }): Promise { + async getOrganizationUsers(payload: {orgId:string} & Payload): Promise { return this.userService.getOrgUsers(payload.orgId, payload.pageNumber, payload.pageSize, payload.search); } /** - * * @param payload * @returns organization users list */ @@ -119,32 +132,44 @@ export class UserController { const users = this.userService.get(payload.pageNumber, payload.pageSize, payload.search); return users; } - + + /** + * @param email + * @returns User's email exist status + * */ @MessagePattern({ cmd: 'check-user-exist' }) - async checkUserExist(payload: { userEmail: string }): Promise { + async checkUserExist(payload: { userEmail: string }): Promise { return this.userService.checkUserExist(payload.userEmail); } + /** + * @Body userInfo + * @returns User's registration status + */ @MessagePattern({ cmd: 'add-user' }) - async addUserDetailsInKeyCloak(payload: { userInfo: userInfo }): Promise { + async addUserDetailsInKeyCloak(payload: { userInfo: IUserInformation }): Promise { return this.userService.createUserForToken(payload.userInfo); } // Fetch Users recent activities @MessagePattern({ cmd: 'get-user-activity' }) - async getUserActivity(payload: { userId: string, limit: number }): Promise { + async getUserActivity(payload: { userId: string, limit: number }): Promise { return this.userService.getUserActivity(payload.userId, payload.limit); } @MessagePattern({ cmd: 'add-passkey' }) - async addPasskey(payload: { userEmail: string, userInfo: AddPasskeyDetails }): Promise { + async addPasskey(payload: { userEmail: string, userInfo: AddPasskeyDetailsDto }): Promise { return this.userService.addPasskey(payload.userEmail, payload.userInfo); } - + /** + * @returns platform and ecosystem settings updated status + */ @MessagePattern({ cmd: 'update-platform-settings' }) async updatePlatformSettings(payload: { platformSettings: PlatformSettings }): Promise { return this.userService.updatePlatformSettings(payload.platformSettings); } - + /** + * @returns platform and ecosystem settings + */ @MessagePattern({ cmd: 'fetch-platform-settings' }) async getPlatformEcosystemSettings(): Promise { return this.userService.getPlatformEcosystemSettings(); diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 14f3c5358..e81546bc7 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -27,18 +27,16 @@ import { VerifyEmailTokenDto } from '../dtos/verify-email.dto'; import { sendEmail } from '@credebl/common/send-grid-helper-file'; import { user } from '@prisma/client'; import { - AddPasskeyDetails, Attribute, - CheckUserDetails, + ICheckUserDetails, OrgInvitations, PlatformSettings, ShareUserCertificate, - UserInvitations, + IOrgUsers, UpdateUserProfile, - UserCredentials, - UserEmailVerificationDto, - userInfo, - UsersProfile + IUserCredentials, + IUserInformation, + IUsersProfile } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; @@ -54,7 +52,9 @@ import { DISALLOWED_EMAIL_DOMAIN } from '@credebl/common/common.constant'; import { AwsService } from '@credebl/aws'; import puppeteer from 'puppeteer'; import { WorldRecordTemplate } from '../templates/world-record-template'; -import { UsersActivity } from 'libs/user-activity/interface'; +import { IUsersActivity } from 'libs/user-activity/interface'; +import { ISendVerificationEmail, ISignInUser, IVerifyUserEmail, IUserInvitations } from '@credebl/common/interfaces/user.interface'; +import { AddPasskeyDetailsDto } from 'apps/api-gateway/src/user/dto/add-user.dto'; @Injectable() export class UserService { @@ -75,12 +75,12 @@ export class UserService { /** * - * @param userEmailVerificationDto + * @param userEmailVerification * @returns */ - async sendVerificationMail(userEmailVerificationDto: UserEmailVerificationDto): Promise { + async sendVerificationMail(userEmailVerification: ISendVerificationEmail): Promise { try { - const { email } = userEmailVerificationDto; + const { email } = userEmailVerification; if ('PROD' === process.env.PLATFORM_PROFILE_MODE) { // eslint-disable-next-line prefer-destructuring @@ -90,7 +90,7 @@ export class UserService { throw new BadRequestException(ResponseMessages.user.error.InvalidEmailDomain); } } - const userDetails = await this.userRepository.checkUserExist(userEmailVerificationDto.email); + const userDetails = await this.userRepository.checkUserExist(userEmailVerification.email); if (userDetails && userDetails.isEmailVerified) { throw new ConflictException(ResponseMessages.user.error.exists); @@ -101,12 +101,12 @@ export class UserService { } const verifyCode = uuidv4(); - const uniqueUsername = await this.createUsername(userEmailVerificationDto.email, verifyCode); - userEmailVerificationDto.username = uniqueUsername; - const resUser = await this.userRepository.createUser(userEmailVerificationDto, verifyCode); + const uniqueUsername = await this.createUsername(userEmailVerification.email, verifyCode); + userEmailVerification.username = uniqueUsername; + const resUser = await this.userRepository.createUser(userEmailVerification, verifyCode); try { - await this.sendEmailForVerification(userEmailVerificationDto.email, resUser.verificationCode); + await this.sendEmailForVerification(userEmailVerification.email, resUser.verificationCode); } catch (error) { throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); } @@ -177,7 +177,7 @@ export class UserService { * @returns Email verification succcess */ - async verifyEmail(param: VerifyEmailTokenDto): Promise { + async verifyEmail(param: VerifyEmailTokenDto): Promise { try { const invalidMessage = ResponseMessages.user.error.invalidEmailUrl; @@ -196,10 +196,8 @@ export class UserService { } if (param.verificationCode === userDetails.verificationCode) { - await this.userRepository.verifyUser(param.email); - return { - message: 'User Verified sucessfully' - }; + const verifiedEmail = await this.userRepository.verifyUser(param.email); + return verifiedEmail; } } catch (error) { this.logger.error(`error in verifyEmail: ${JSON.stringify(error)}`); @@ -207,7 +205,7 @@ export class UserService { } } - async createUserForToken(userInfo: userInfo): Promise { + async createUserForToken(userInfo: IUserInformation): Promise { try { const { email } = userInfo; if (!userInfo.email) { @@ -267,14 +265,14 @@ export class UserService { const holderRoleData = await this.orgRoleService.getRole(OrgRoles.HOLDER); await this.userOrgRoleService.createUserOrgRole(userDetails.id, holderRoleData.id); - return 'User created successfully'; + return ResponseMessages.user.success.signUpUser; } catch (error) { this.logger.error(`Error in createUserForToken: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } } - async addPasskey(email: string, userInfo: AddPasskeyDetails): Promise { + async addPasskey(email: string, userInfo: AddPasskeyDetailsDto): Promise { try { if (!email) { throw new UnauthorizedException(ResponseMessages.user.error.invalidEmail); @@ -294,7 +292,7 @@ export class UserService { throw new NotFoundException(ResponseMessages.user.error.invalidEmail); } - return 'User updated successfully'; + return ResponseMessages.user.success.updateUserProfile; } catch (error) { this.logger.error(`Error in createUserForToken: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -312,7 +310,7 @@ export class UserService { * @param loginUserDto * @returns User access token details */ - async login(loginUserDto: LoginUserDto): Promise { + async login(loginUserDto: LoginUserDto): Promise { const { email, password, isPasskey } = loginUserDto; try { @@ -344,7 +342,7 @@ export class UserService { } } - async generateToken(email: string, password: string): Promise { + async generateToken(email: string, password: string): Promise { try { const supaInstance = await this.supabaseService.getClient(); this.logger.error(`supaInstance::`, supaInstance); @@ -361,13 +359,14 @@ export class UserService { } const token = data?.session; + return token; } catch (error) { throw new RpcException(error.response ? error.response : error); } } - async getProfile(payload: { id }): Promise { + async getProfile(payload: { id }): Promise { try { const userData = await this.userRepository.getUserById(payload.id); const ecosystemSettingsList = await this.prisma.ecosystem_config.findMany({ @@ -387,7 +386,7 @@ export class UserService { } } - async getPublicProfile(payload: { username }): Promise { + async getPublicProfile(payload: { username }): Promise { try { const userProfile = await this.userRepository.getUserPublicProfile(payload.username); @@ -402,7 +401,7 @@ export class UserService { } } - async getUserCredentialsById(payload: { credentialId }): Promise { + async getUserCredentialsById(payload: { credentialId }): Promise { try { const userCredentials = await this.userRepository.getUserCredentialsById(payload.credentialId); if (!userCredentials) { @@ -451,26 +450,27 @@ export class UserService { } } - async invitations(payload: { id; status; pageNumber; pageSize; search }): Promise { + async invitations(payload: { id; status; pageNumber; pageSize; search }): Promise { try { const userData = await this.userRepository.getUserById(payload.id); if (!userData) { throw new NotFoundException(ResponseMessages.user.error.notFound); } + const invitationsData = await this.getOrgInvitations( userData.email, payload.status, payload.pageNumber, payload.pageSize, payload.search - ); + ); + + const invitations: OrgInvitations[] = await this.updateOrgInvitations(invitationsData['invitations']); + invitationsData['invitations'] = invitations; - const invitations: OrgInvitations[] = await this.updateOrgInvitations(invitationsData['invitations']); - invitationsData['invitations'] = invitations; - // console.log("{-----------------}",invitationsData); - return invitationsData; + } catch (error) { this.logger.error(`Error in get invitations: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -483,7 +483,7 @@ export class UserService { pageNumber: number, pageSize: number, search = '' - ): Promise { + ): Promise { const pattern = { cmd: 'fetch-user-invitations' }; const payload = { email, @@ -511,6 +511,8 @@ export class UserService { } async updateOrgInvitations(invitations: OrgInvitations[]): Promise { + + const updatedInvitations = []; for (const invitation of invitations) { @@ -547,10 +549,6 @@ export class UserService { } } - /** - * - * @returns - */ async shareUserCertificate(shareUserCertificate: ShareUserCertificate): Promise { const attributeArray = []; @@ -680,7 +678,7 @@ export class UserService { * @param orgId * @returns users list */ - async getOrgUsers(orgId: string, pageNumber: number, pageSize: number, search: string): Promise { + async getOrgUsers(orgId: string, pageNumber: number, pageSize: number, search: string): Promise { try { const query = { @@ -727,7 +725,7 @@ export class UserService { } } - async checkUserExist(email: string): Promise { + async checkUserExist(email: string): Promise { try { const userDetails = await this.userRepository.checkUniqueUserExist(email); if (userDetails && !userDetails.isEmailVerified) { @@ -754,7 +752,7 @@ export class UserService { } - async getUserActivity(userId: string, limit: number): Promise { + async getUserActivity(userId: string, limit: number): Promise { try { return this.userActivityService.getUserActivity(userId, limit); } catch (error) { diff --git a/apps/verification/src/interfaces/verification.interface.ts b/apps/verification/src/interfaces/verification.interface.ts index 44a3bfc57..d00672def 100644 --- a/apps/verification/src/interfaces/verification.interface.ts +++ b/apps/verification/src/interfaces/verification.interface.ts @@ -99,10 +99,6 @@ interface IWebhookPresentationProof { connectionId } -export interface IWebhookProofPresentationPayload { - proofPresentationDto: IWebhookProofPresentation; - id: string; -} export interface IWebhookProofPresentation { metadata: object; _tags: IWebhookPresentationProof; @@ -119,22 +115,22 @@ export interface IWebhookProofPresentation { contextCorrelationId: string; } -export interface ProofPresentationPayload { +export interface IProofPresentation { proofPresentationPayload: IWebhookProofPresentation; - id: string; + orgId: string; } export interface IProofRequests { - proofRequestsSearchCriteria: IProofRequestsSearchCriteria; + proofRequestsSearchCriteria: IProofRequestSearchCriteria; user: IUserRequest; orgId: string; } -export interface IProofRequestsSearchCriteria { +export interface IProofRequestSearchCriteria { pageNumber: number; pageSize: number; - sorting: string; - sortByValue: string; + sortField: string; + sortBy: string; searchByText: string; } diff --git a/apps/verification/src/repositories/verification.repository.ts b/apps/verification/src/repositories/verification.repository.ts index 5ed4bbd30..31b32be28 100644 --- a/apps/verification/src/repositories/verification.repository.ts +++ b/apps/verification/src/repositories/verification.repository.ts @@ -3,9 +3,11 @@ import { PrismaService } from '@credebl/prisma-service'; import { Injectable, Logger, NotFoundException } from '@nestjs/common'; // eslint-disable-next-line camelcase import { org_agents, organisation, platform_config, presentations } from '@prisma/client'; -import { ProofPresentationPayload } from '../interfaces/verification.interface'; +import { IProofPresentation } from '../interfaces/verification.interface'; +import { IProofRequestSearchCriteria } from '../interfaces/verification.interface'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; -import { IProofRequestsSearchCriteria } from 'apps/api-gateway/src/verification/interfaces/verification.interface'; +import { IProofPresentationsListCount } from '@credebl/common/interfaces/verification.interface'; +import { SortValue } from '@credebl/enum/enum'; @Injectable() export class VerificationRepository { @@ -56,24 +58,16 @@ export class VerificationRepository { async getAllProofRequests( user: IUserRequest, orgId: string, - proofRequestsSearchCriteria: IProofRequestsSearchCriteria - ): Promise<{ - proofRequestsCount: number; - proofRequestsList: { - createDateTime: Date; - createdBy: string; - connectionId: string; - state: string; - orgId: string; - }[]; - }> { + proofRequestsSearchCriteria: IProofRequestSearchCriteria + ): Promise { try { const proofRequestsList = await this.prisma.presentations.findMany({ where: { orgId, OR: [ { connectionId: { contains: proofRequestsSearchCriteria.searchByText, mode: 'insensitive' } }, - { state: { contains: proofRequestsSearchCriteria.searchByText, mode: 'insensitive' } } + { state: { contains: proofRequestsSearchCriteria.searchByText, mode: 'insensitive' } }, + { presentationId: { contains: proofRequestsSearchCriteria.searchByText, mode: 'insensitive' } } ] }, select: { @@ -86,11 +80,9 @@ export class VerificationRepository { presentationId: true }, orderBy: { - [proofRequestsSearchCriteria?.sorting || 'createDateTime']: - 'DESC' === proofRequestsSearchCriteria?.sortByValue - ? 'desc' - : 'asc' + [proofRequestsSearchCriteria.sortField]: SortValue.ASC === proofRequestsSearchCriteria.sortBy ? 'asc' : 'desc' }, + take: Number(proofRequestsSearchCriteria.pageSize), skip: (proofRequestsSearchCriteria.pageNumber - 1) * proofRequestsSearchCriteria.pageSize }); @@ -99,8 +91,9 @@ export class VerificationRepository { orgId, OR: [ { connectionId: { contains: proofRequestsSearchCriteria.searchByText, mode: 'insensitive' } }, - { state: { contains: proofRequestsSearchCriteria.searchByText, mode: 'insensitive' } } - ] + { state: { contains: proofRequestsSearchCriteria.searchByText, mode: 'insensitive' } }, + { presentationId: { contains: proofRequestsSearchCriteria.searchByText, mode: 'insensitive' } } + ] } }); @@ -111,16 +104,16 @@ export class VerificationRepository { } } - async storeProofPresentation(payload: ProofPresentationPayload): Promise { + async storeProofPresentation(payload: IProofPresentation): Promise { try { let organisationId: string; - const { proofPresentationPayload, id } = payload; + const { proofPresentationPayload, orgId } = payload; if (proofPresentationPayload?.contextCorrelationId) { const getOrganizationId = await this.getOrganizationByTenantId(proofPresentationPayload?.contextCorrelationId); organisationId = getOrganizationId?.orgId; } else { - organisationId = id; + organisationId = orgId; } const proofPresentationsDetails = await this.prisma.presentations.upsert({ @@ -146,7 +139,7 @@ export class VerificationRepository { }); return proofPresentationsDetails; } catch (error) { - this.logger.error(`Error in get saveIssuedCredentialDetails: ${error.message} `); + this.logger.error(`Error in get saveProofPresentationDetails: ${error.message} `); throw error; } } diff --git a/apps/verification/src/verification.controller.ts b/apps/verification/src/verification.controller.ts index 23946706e..63bc2a4d8 100644 --- a/apps/verification/src/verification.controller.ts +++ b/apps/verification/src/verification.controller.ts @@ -1,9 +1,10 @@ import { Controller } from '@nestjs/common'; import { VerificationService } from './verification.service'; import { MessagePattern } from '@nestjs/microservices'; -import { IProofRequests, IRequestProof, ProofFormData, ProofPresentationPayload } from './interfaces/verification.interface'; +import { IProofPresentation, IProofRequests, IRequestProof, ProofFormData } from './interfaces/verification.interface'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { presentations } from '@prisma/client'; +import { IProofPresentationList } from '@credebl/common/interfaces/verification.interface'; @Controller() export class VerificationController { @@ -14,8 +15,8 @@ export class VerificationController { * @param payload * @returns Get all proof presentation */ - @MessagePattern({ cmd: 'get-proof-presentations' }) - async getProofPresentations(payload: IProofRequests): Promise { + @MessagePattern({ cmd: 'get-all-proof-presentations' }) + async getProofPresentations(payload: IProofRequests): Promise { const { user, orgId, proofRequestsSearchCriteria} = payload; return this.verificationService.getProofPresentations(user, orgId, proofRequestsSearchCriteria); } @@ -50,8 +51,12 @@ export class VerificationController { return this.verificationService.verifyPresentation(payload.id, payload.orgId); } + /** + * @param orgId + * @returns proof presentation details + */ @MessagePattern({ cmd: 'webhook-proof-presentation' }) - async webhookProofPresentation(payload: ProofPresentationPayload): Promise { + async webhookProofPresentation(payload: IProofPresentation): Promise { return this.verificationService.webhookProofPresentation(payload); } diff --git a/apps/verification/src/verification.module.ts b/apps/verification/src/verification.module.ts index 9c65becb2..6c490c541 100644 --- a/apps/verification/src/verification.module.ts +++ b/apps/verification/src/verification.module.ts @@ -8,7 +8,7 @@ import { PrismaService } from '@credebl/prisma-service'; import { getNatsOptions } from '@credebl/common/nats.config'; import { OutOfBandVerification } from '../templates/out-of-band-verification.template'; import { EmailDto } from '@credebl/common/dtos/email.dto'; - +import { CacheModule } from '@nestjs/cache-manager'; @Module({ imports: [ ClientsModule.register([ @@ -20,7 +20,8 @@ import { EmailDto } from '@credebl/common/dtos/email.dto'; } ]), - CommonModule + CommonModule, + CacheModule.register() ], controllers: [VerificationController], providers: [VerificationService, VerificationRepository, PrismaService, Logger, OutOfBandVerification, EmailDto] diff --git a/apps/verification/src/verification.service.ts b/apps/verification/src/verification.service.ts index 476f9a582..5b713a25b 100644 --- a/apps/verification/src/verification.service.ts +++ b/apps/verification/src/verification.service.ts @@ -2,7 +2,7 @@ import { BadRequestException, HttpException, Inject, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; import { ClientProxy, RpcException } from '@nestjs/microservices'; import { map } from 'rxjs/operators'; -import { IGetAllProofPresentations, IGetProofPresentationById, IProofRequestPayload, IRequestProof, ISendProofRequestPayload, IVerifyPresentation, ProofFormDataPayload, ProofPresentationPayload } from './interfaces/verification.interface'; +import { IGetAllProofPresentations, IProofRequestSearchCriteria, IGetProofPresentationById, IProofPresentation, IProofRequestPayload, IRequestProof, ISendProofRequestPayload, IVerifyPresentation, ProofFormDataPayload } from './interfaces/verification.interface'; import { VerificationRepository } from './repositories/verification.repository'; import { CommonConstants } from '@credebl/common/common.constant'; import { org_agents, organisation, presentations } from '@prisma/client'; @@ -12,8 +12,10 @@ import * as QRCode from 'qrcode'; import { OutOfBandVerification } from '../templates/out-of-band-verification.template'; import { EmailDto } from '@credebl/common/dtos/email.dto'; import { sendEmail } from '@credebl/common/send-grid-helper-file'; +import { Cache } from 'cache-manager'; +import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; -import { IProofRequestsSearchCriteria } from 'apps/api-gateway/src/verification/interfaces/verification.interface'; +import { IProofPresentationList } from '@credebl/common/interfaces/verification.interface'; @Injectable() export class VerificationService { @@ -24,7 +26,8 @@ export class VerificationService { @Inject('NATS_CLIENT') private readonly verificationServiceProxy: ClientProxy, private readonly verificationRepository: VerificationRepository, private readonly outOfBandVerification: OutOfBandVerification, - private readonly emailData: EmailDto + private readonly emailData: EmailDto, + @Inject(CACHE_MANAGER) private cacheService: Cache ) { } @@ -38,29 +41,20 @@ export class VerificationService { async getProofPresentations( user: IUserRequest, orgId: string, - proofRequestsSearchCriteria: IProofRequestsSearchCriteria - ): Promise<{ - totalItems: number; - hasNextPage: boolean; - hasPreviousPage: boolean; - nextPage: number; - previousPage: number; - lastPage: number; - data: { - createDateTime: Date; - createdBy: string; - connectionId: string; - state: string; - orgId: string; - }[]; - }> { + proofRequestsSearchCriteria: IProofRequestSearchCriteria + ): Promise { try { const getProofRequestsList = await this.verificationRepository.getAllProofRequests( user, orgId, proofRequestsSearchCriteria ); - const issuedCredentialsResponse: { + + if (0 === getProofRequestsList.proofRequestsCount) { + throw new NotFoundException(ResponseMessages.verification.error.proofPresentationNotFound); + } + + const proofPresentationsResponse: { totalItems: number; hasNextPage: boolean; hasPreviousPage: boolean; @@ -73,11 +67,13 @@ export class VerificationService { connectionId: string; state: string; orgId: string; + presentationId: string; + id: string; }[]; } = { totalItems: getProofRequestsList.proofRequestsCount, hasNextPage: - proofRequestsSearchCriteria.pageSize * proofRequestsSearchCriteria.pageNumber < getProofRequestsList.proofRequestsCount, + proofRequestsSearchCriteria.pageSize * proofRequestsSearchCriteria.pageNumber < getProofRequestsList.proofRequestsCount, hasPreviousPage: 1 < proofRequestsSearchCriteria.pageNumber, nextPage: Number(proofRequestsSearchCriteria.pageNumber) + 1, previousPage: proofRequestsSearchCriteria.pageNumber - 1, @@ -85,20 +81,15 @@ export class VerificationService { data: getProofRequestsList.proofRequestsList }; - if (0 !== getProofRequestsList.proofRequestsCount) { - return issuedCredentialsResponse; - } else { - throw new NotFoundException(ResponseMessages.verification.error.proofPresentationNotFound); - } - } catch (error) { - if (404 === error.status) { - throw new NotFoundException(error.response.message); - } - throw new RpcException( - `[getConnections] [NATS call]- error in fetch proof requests details : ${JSON.stringify(error)}` - ); - } - } + return proofPresentationsResponse; + } catch (error) { + + this.logger.error( + `[getProofRequests] [NATS call]- error in fetch proof requests details : ${JSON.stringify(error)}` + ); + throw new RpcException(error.response ? error.response : error); + } +} /** * Consume agent API for get all proof presentations @@ -109,27 +100,8 @@ export class VerificationService { response: string; }> { try { - - const pattern = { - cmd: 'agent-get-proof-presentations' - }; - - return this.verificationServiceProxy - .send(pattern, payload) - .pipe( - map((response) => ( - { - response - })) - ).toPromise() - .catch(error => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.statusCode, - error: error.message - }, error.error); - }); + const pattern = { cmd: 'agent-get-proof-presentations' }; + return await this.natsCall(pattern, payload); } catch (error) { this.logger.error(`[_getProofPresentations] - error in get proof presentations : ${JSON.stringify(error)}`); throw error; @@ -147,25 +119,20 @@ export class VerificationService { try { const getAgentDetails = await this.verificationRepository.getAgentEndPoint(orgId); - const orgAgentType = await this.verificationRepository.getOrgAgentType(getAgentDetails?.orgAgentTypeId); const verificationMethodLabel = 'get-proof-presentation-by-id'; + const orgAgentType = await this.verificationRepository.getOrgAgentType(getAgentDetails?.orgAgentTypeId); + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); const url = await this.getAgentUrl(verificationMethodLabel, orgAgentType, getAgentDetails?.agentEndPoint, getAgentDetails?.tenantId, '', id); - - const payload = { apiKey: '', url }; + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } + const payload = { apiKey, url }; const getProofPresentationById = await this._getProofPresentationById(payload); return getProofPresentationById?.response; } catch (error) { this.logger.error(`[getProofPresentationById] - error in get proof presentation by id : ${JSON.stringify(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); - } + this.verificationErrorHandling(error); } } @@ -183,22 +150,7 @@ export class VerificationService { cmd: 'agent-get-proof-presentation-by-id' }; - return this.verificationServiceProxy - .send(pattern, payload) - .pipe( - map((response) => ( - { - response - })) - ).toPromise() - .catch(error => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.statusCode, - error: error.message - }, error.error); - }); + return await this.natsCall(pattern, payload); } catch (error) { this.logger.error(`[_getProofPresentationById] - error in get proof presentation by id : ${JSON.stringify(error)}`); throw error; @@ -254,22 +206,18 @@ export class VerificationService { const orgAgentType = await this.verificationRepository.getOrgAgentType(getAgentDetails?.orgAgentTypeId); const verificationMethodLabel = 'request-proof'; const url = await this.getAgentUrl(verificationMethodLabel, orgAgentType, getAgentDetails?.agentEndPoint, getAgentDetails?.tenantId); - - const payload = { apiKey: '', url, proofRequestPayload }; + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + this.logger.log(`cachedApiKey----${apiKey}`); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(requestProof.orgId); + } + const payload = { apiKey, url, proofRequestPayload }; const getProofPresentationById = await this._sendProofRequest(payload); return getProofPresentationById?.response; } catch (error) { this.logger.error(`[verifyPresentation] - error in verify presentation : ${JSON.stringify(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); - } + this.verificationErrorHandling(error); } } @@ -287,22 +235,7 @@ export class VerificationService { cmd: 'agent-send-proof-request' }; - return this.verificationServiceProxy - .send(pattern, payload) - .pipe( - map((response) => ( - { - response - })) - ).toPromise() - .catch(error => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.statusCode, - error: error.message - }, error.error); - }); + return await this.natsCall(pattern, payload); } catch (error) { this.logger.error(`[_sendProofRequest] - error in verify presentation : ${JSON.stringify(error)}`); throw error; @@ -318,26 +251,22 @@ export class VerificationService { */ async verifyPresentation(id: string, orgId: string): Promise { try { - const getAgentDetails = await this.verificationRepository.getAgentEndPoint(orgId); - const verificationMethodLabel = 'accept-presentation'; - - const orgAgentType = await this.verificationRepository.getOrgAgentType(getAgentDetails?.orgAgentTypeId); - const url = await this.getAgentUrl(verificationMethodLabel, orgAgentType, getAgentDetails?.agentEndPoint, getAgentDetails?.tenantId, '', id); - - const payload = { apiKey: '', url }; + const getAgentData = await this.verificationRepository.getAgentEndPoint(orgId); + const orgAgentTypeData = await this.verificationRepository.getOrgAgentType(getAgentData?.orgAgentTypeId); + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + + const verificationMethod = 'accept-presentation'; + + const url = await this.getAgentUrl(verificationMethod, orgAgentTypeData, getAgentData?.agentEndPoint, getAgentData?.tenantId, '', id); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } + const payload = { apiKey, url }; const getProofPresentationById = await this._verifyPresentation(payload); return getProofPresentationById?.response; } catch (error) { this.logger.error(`[verifyPresentation] - error in verify presentation : ${JSON.stringify(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); - } + this.verificationErrorHandling(error); } } @@ -355,31 +284,16 @@ export class VerificationService { cmd: 'agent-verify-presentation' }; - return this.verificationServiceProxy - .send(pattern, payload) - .pipe( - map((response) => ( - { - response - })) - ).toPromise() - .catch(error => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.statusCode, - error: error.message - }, error.error); - }); + return await this.natsCall(pattern, payload); + } catch (error) { this.logger.error(`[_verifyPresentation] - error in verify presentation : ${JSON.stringify(error)}`); throw error; } } - async webhookProofPresentation(proofPresentationPayload: ProofPresentationPayload): Promise { + async webhookProofPresentation(proofPresentationPayload: IProofPresentation): Promise { try { - const proofPresentation = await this.verificationRepository.storeProofPresentation(proofPresentationPayload); return proofPresentation; @@ -409,12 +323,16 @@ export class VerificationService { ]); const orgAgentType = await this.verificationRepository.getOrgAgentType(getAgentDetails?.orgAgentTypeId); + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); const verificationMethodLabel = 'create-request-out-of-band'; const url = await this.getAgentUrl(verificationMethodLabel, orgAgentType, getAgentDetails?.agentEndPoint, getAgentDetails?.tenantId); - + this.logger.log(`cachedApiKey----${apiKey}`); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(outOfBandRequestProof.orgId); + } const payload: IProofRequestPayload = { - apiKey: '', + apiKey, url, proofRequestPayload: { protocolVersion, @@ -438,15 +356,7 @@ export class VerificationService { return true; } catch (error) { this.logger.error(`[sendOutOfBandPresentationRequest] - error in out of band proof request : ${error.message}`); - 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); - } + this.verificationErrorHandling(error); } } @@ -479,6 +389,12 @@ export class VerificationService { async sendOutOfBandProofRequest(payload: IProofRequestPayload, email: string, getAgentDetails: org_agents, organizationDetails: organisation): Promise { + let agentApiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + this.logger.log(`cachedApiKey----${agentApiKey}`); + if (!agentApiKey || null === agentApiKey || undefined === agentApiKey) { + agentApiKey = await this._getOrgAgentApiKey(getAgentDetails.orgId); + } + payload.apiKey = agentApiKey; const getProofPresentation = await this._sendOutOfBandProofRequest(payload); if (!getProofPresentation) { @@ -540,22 +456,8 @@ export class VerificationService { cmd: 'agent-send-out-of-band-proof-request' }; - return this.verificationServiceProxy - .send(pattern, payload) - .pipe( - map((response) => ( - { - response - })) - ).toPromise() - .catch(error => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.statusCode, - error: error.message - }, error.error); - }); + return await this.natsCall(pattern, payload); + } catch (error) { this.logger.error(`[_sendOutOfBandProofRequest] - error in Out Of Band Presentation : ${JSON.stringify(error)}`); throw error; @@ -747,8 +649,12 @@ export class VerificationService { const orgAgentType = await this.verificationRepository.getOrgAgentType(getAgentDetails?.orgAgentTypeId); const url = await this.getAgentUrl(verificationMethodLabel, orgAgentType, getAgentDetails?.agentEndPoint, getAgentDetails?.tenantId, '', id); - - const payload = { apiKey: '', url }; + let apiKey: string = await this.cacheService.get(CommonConstants.CACHE_APIKEY_KEY); + this.logger.log(`cachedApiKey----${apiKey}`); + if (!apiKey || null === apiKey || undefined === apiKey) { + apiKey = await this._getOrgAgentApiKey(orgId); + } + const payload = { apiKey, url }; const getProofPresentationById = await this._getProofFormData(payload); if (!getProofPresentationById?.response?.presentation) { @@ -766,10 +672,10 @@ export class VerificationService { for (const key in requestedAttributes) { if (requestedAttributes.hasOwnProperty(key)) { - const attribute = requestedAttributes[key]; - const attributeName = attribute.name; - const credDefId = attribute?.restrictions[0]?.cred_def_id; - const schemaId = attribute?.restrictions[0]?.schema_id; + const requestedAttributeKey = requestedAttributes[key]; + const attributeName = requestedAttributeKey.name; + const credDefId = requestedAttributeKey?.restrictions[0]?.cred_def_id; + const schemaId = requestedAttributeKey?.restrictions[0]?.schema_id; if (revealedAttrs.hasOwnProperty(key)) { const extractedData = { @@ -841,15 +747,7 @@ export class VerificationService { return extractedDataArray; } catch (error) { this.logger.error(`[getProofFormData] - error in get proof form data : ${JSON.stringify(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); - } + this.verificationErrorHandling(error); } } @@ -864,6 +762,47 @@ export class VerificationService { cmd: 'agent-proof-form-data' }; + return await this.natsCall(pattern, payload); + } catch (error) { + this.logger.error(`[_getProofFormData] - error in proof form data : ${JSON.stringify(error)}`); + throw error; + } + } + + + async _getOrgAgentApiKey(orgId: string): Promise { + const pattern = { cmd: 'get-org-agent-api-key' }; + const payload = { orgId }; + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.verificationServiceProxy.send(pattern, payload).toPromise(); + return message; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException({ + status: error.status, + error: error.message + }, error.status); + } + } + + verificationErrorHandling(error): void { + if (!error && !error?.status && !error?.status?.message && !error?.status?.message?.error) { + + throw new RpcException(error.response ? error.response : error); + } else { + throw new RpcException({ + message: error?.status?.message?.error?.reason ? error?.status?.message?.error?.reason : error?.status?.message?.error, + statusCode: error?.status?.code + }); + } + } + + async natsCall(pattern: object, payload: object): Promise<{ + response: string; + }> { + try { return this.verificationServiceProxy .send(pattern, payload) .pipe( @@ -881,8 +820,8 @@ export class VerificationService { }, error.error); }); } catch (error) { - this.logger.error(`[_getProofFormData] - error in proof form data : ${JSON.stringify(error)}`); - throw error; + this.logger.error(`[natsCall] - error in nats call : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); } } } diff --git a/libs/common/src/cast.helper.ts b/libs/common/src/cast.helper.ts index 5ade7f52f..4b886c1ef 100644 --- a/libs/common/src/cast.helper.ts +++ b/libs/common/src/cast.helper.ts @@ -9,7 +9,7 @@ export function toLowerCase(value: string): string { } export function trim(value: string): string { - return value.trim(); + if ('string' === typeof value) { return value.trim(); } } export function toDate(value: string): Date { diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index d94cf5380..75dad43c2 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -219,6 +219,9 @@ export enum CommonConstants { // delete wallet URL_DELETE_WALLET = '/agent/wallet', URL_DELETE_SHARED_WALLET = '/multi-tenancy/#', + + // agent status + URL_AGENT_STATUS = '/agent', // Tenant Status PENDING_STATE = 0, @@ -295,7 +298,10 @@ export enum CommonConstants { TRANSACTION_MULTITENANT_SIGN = '/multi-tenancy/transactions/endorse/#', TRANSACTION_MULTITENANT_SUMBIT = '/multi-tenancy/transactions/write/#', - + + //CacheInfo +CACHE_APIKEY_KEY = "apiKey", +CACHE_TTL_SECONDS = 604800 } export const postgresqlErrorCodes = []; diff --git a/libs/common/src/common.service.ts b/libs/common/src/common.service.ts index 0c2a0a602..e0f790d51 100644 --- a/libs/common/src/common.service.ts +++ b/libs/common/src/common.service.ts @@ -1,3 +1,9 @@ +/* eslint-disable arrow-body-style */ +/* eslint-disable implicit-arrow-linebreak */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable space-in-parens */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import * as CryptoJS from 'crypto-js'; import { @@ -11,8 +17,6 @@ import { import { CommonConstants } from './common.constant'; import { HttpService } from '@nestjs/axios/dist'; import { ResponseService } from '@credebl/response'; -import { readFileSync } from 'fs'; -import { RpcException } from '@nestjs/microservices'; @Injectable() export class CommonService { @@ -390,4 +394,5 @@ export class CommonService { throw new BadRequestException('Invalid Credentials'); } } + } diff --git a/libs/common/src/interfaces/connection.interface.ts b/libs/common/src/interfaces/connection.interface.ts new file mode 100644 index 000000000..0d4a34ca5 --- /dev/null +++ b/libs/common/src/interfaces/connection.interface.ts @@ -0,0 +1,34 @@ +export interface IConnectionsListCount { + connectionCount: number; + connectionsList: IConnectionItem[]; + } + export interface IConnectionItem { + createDateTime: Date; + createdBy: string; + connectionId: string; + theirLabel: string; + state: string; + orgId: string; + } + export interface IConnectionList { + totalItems: number; + hasNextPage: boolean; + hasPreviousPage: boolean; + nextPage: number; + previousPage: number; + lastPage: number; + data: IConnectionItem[]; + } + + export interface ICreateConnectionUrl { + id: string; + orgId: string; + agentId: string; + connectionInvitation: string; + multiUse: boolean; + createDateTime: Date; + createdBy: number; + lastChangedDateTime: Date; + lastChangedBy: number; +} + \ No newline at end of file diff --git a/libs/common/src/interfaces/issuance.interface.ts b/libs/common/src/interfaces/issuance.interface.ts new file mode 100644 index 000000000..fe7783064 --- /dev/null +++ b/libs/common/src/interfaces/issuance.interface.ts @@ -0,0 +1,18 @@ +export interface IIssuedCredentialResponse { + createDateTime: Date; + createdBy: string; + connectionId: string; + schemaId: string; + state: string; + orgId: string; + } + +export interface IIssuedCredential { + totalItems: number; + hasNextPage: boolean; + hasPreviousPage: boolean; + nextPage: number; + previousPage: number; + lastPage: number; + data: IIssuedCredentialResponse[]; + } \ No newline at end of file diff --git a/libs/common/src/interfaces/response.interface.ts b/libs/common/src/interfaces/response.interface.ts index 52639580f..6c1b4160a 100644 --- a/libs/common/src/interfaces/response.interface.ts +++ b/libs/common/src/interfaces/response.interface.ts @@ -5,3 +5,9 @@ export default interface IResponseType { data?: unknown; error?: unknown; }; +export interface IResponse { + statusCode: number; + message: string; + label?: string; + data?: unknown; +}; diff --git a/libs/common/src/interfaces/schema.interface.ts b/libs/common/src/interfaces/schema.interface.ts new file mode 100644 index 000000000..c773c206a --- /dev/null +++ b/libs/common/src/interfaces/schema.interface.ts @@ -0,0 +1,44 @@ + +export interface IPaginationDetails { + totalItems: number; + hasNextPage: boolean; + hasPreviousPage: boolean; + nextPage: number; + previousPage: number; + lastPage: number; +} + + +export interface ISchemasWithPagination extends IPaginationDetails{ + data: ISchemaData[]; + } + + export interface ISchemaData { + createDateTime: Date; + createdBy: string; + name: string; + version: string; + attributes: string; + schemaLedgerId: string; + publisherDid: string; + issuerId: string; + orgId: string; + } + + export interface ICredDefData { + tag: string; + credentialDefinitionId: string; + schemaLedgerId: string; + revocable: boolean; + createDateTime?: Date; + } + + export interface ICredDefWithPagination extends IPaginationDetails{ + data: ICredDefData[]; + } + + export interface ICredDefWithCount { + credDefCount: number; + credDefResult: ICredDefData[]; + } + \ No newline at end of file diff --git a/libs/common/src/interfaces/user.interface.ts b/libs/common/src/interfaces/user.interface.ts new file mode 100644 index 000000000..6bcf7a6ea --- /dev/null +++ b/libs/common/src/interfaces/user.interface.ts @@ -0,0 +1,39 @@ +export interface ISignInUser { + access_token: string; + token_type?: string; + expires_in?: number; + expires_at?: number; + refresh_token?: string; + } + export interface IVerifyUserEmail{ + email: string; + verificationCode: string; + } + export interface ISendVerificationEmail { + email: string; + username?: string; + } + + export interface IUserInvitations { + totalPages:number; + userInvitationsData:IUserInvitationsData[]; + } + export interface IUserInvitationsData { + orgRoles: IOrgRole[]; + status: string; + id: string; + orgId: string; + organisation: IOrganisation; + userId: string; + } + export interface IOrgRole { + id: string; + name: string; + description: string; + } + + export interface IOrganisation { + id: string; + name: string; + logoUrl: string; + } \ No newline at end of file diff --git a/libs/common/src/interfaces/verification.interface.ts b/libs/common/src/interfaces/verification.interface.ts new file mode 100644 index 000000000..97fd0d0cb --- /dev/null +++ b/libs/common/src/interfaces/verification.interface.ts @@ -0,0 +1,23 @@ +export interface IProofPresentationsListCount { + proofRequestsCount: number; + proofRequestsList: IProofPresentationItem[]; + } + export interface IProofPresentationItem { + id: string, + createDateTime: Date; + createdBy: string; + connectionId: string; + state: string; + orgId: string; + presentationId: string; + } + export interface IProofPresentationList { + totalItems: number; + hasNextPage: boolean; + hasPreviousPage: boolean; + nextPage: number; + previousPage: number; + lastPage: number; + data: IProofPresentationItem[]; + } + diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index c45eb1181..fd2711509 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -13,11 +13,14 @@ export const ResponseMessages = { fetchUsers: 'Users fetched successfully', newUser: 'User not found', checkEmail: 'User email checked successfully.', - sendVerificationCode: 'Verification code has been sent sucessfully to the mail. Please verify', + sendVerificationCode: 'Verification link has been successfully sent on the email. Please verify', userActivity: 'User activities fetched successfully', userCredentials: 'User credentials fetched successfully', platformEcosystemettings: 'Platform and ecosystem settings updated', - fetchPlatformSettings: 'Platform settings fetched' + fetchPlatformSettings: 'Platform settings fetched', + signUpUser:'User created successfully', + shareUserCertificate:'Certificate URL generated successfully', + updateUserProfile:'User profile updated successfully' }, error: { exists: 'User already exists', @@ -58,7 +61,8 @@ export const ResponseMessages = { getOrgDashboard: 'Organization dashboard details fetched', getOrganizations: 'Organizations details fetched successfully', updateUserRoles: 'User roles updated successfully', - delete: 'Organization deleted successfully' + delete: 'Organization deleted successfully', + orgInvitationDeleted: 'Organization invitation deleted successfully' }, error: { exists: 'An organization name is already exist', @@ -68,9 +72,12 @@ export const ResponseMessages = { userNotFound: 'User not found for the given organization', updateUserRoles: 'Unable to update user roles', deleteOrg: 'Organization not found', - notFound: 'Organization agent not found' + deleteOrgInvitation: 'Organization does not have access to delete this invitation', + notFound: 'Organization agent not found', + orgNotFound: 'Organization not found', + orgNotMatch: 'Organization does not have access', + invitationStatusInvalid: 'Unable to delete invitation with accepted/rejected status' } - }, fido: { @@ -103,6 +110,7 @@ export const ResponseMessages = { }, error: { invalidSchemaId: 'Invalid schema Id provided.', + invalidData: 'Invalid data provided.', nameNotEmpty: 'Schema name is required', versionNotEmpty: 'Schema version is required', invalidVersion: 'Invalid schema version provided.', @@ -115,7 +123,9 @@ export const ResponseMessages = { notFound: 'Schema records not found', schemaIdNotFound: 'SchemaLedgerId not found', credentialDefinitionNotFound: 'No credential definition exist', - notStoredCredential: 'User credential not stored' + notStoredCredential: 'User credential not stored', + agentDetailsNotFound: 'Agent details not found', + failedFetchSchema: 'Failed to fetch schema data' } }, credentialDefinition: { @@ -149,16 +159,33 @@ export const ResponseMessages = { exists: 'An agent name is already exist', orgNotFound: 'Organization not found', apiEndpointNotFound: 'apiEndpoint not found', - notAbleToSpinUpAgent: 'Agent not able to spin-up', - alreadySpinUp: 'Agent already spin-up', + notAbleToSpinUpAgent: 'Agent not able to spin up', + alreadySpinUp: 'Agent already spun up', agentUrl: 'Agent url not exist', - agentNotExists: 'Agent not spinned up for this organization' + apiKeyNotExist:'API key is not found', + seedChar: 'seed must be at most 32 characters', + validWalletName: 'Please enter valid wallet name. It allows only alphanumeric values', + platformConfiguration: 'Platform configuration is missing or invalid', + apiEndpoint: 'API endpoint is missing in the platform configuration', + externalIp: 'External IP is missing in the platform configuration', + stringExternalIp: 'External IP must be a string', + agentProcess: 'Agent process is invalid or not in a completed state', + notAbleToSpinup: 'Agent not able to spun up', + ledgerNotFound: 'Ledgers not found', + agentNotExists: 'Agent not spun up for this organization', + agentDown: 'Agent is down or not spun up', + walletAlreadyCreated: 'Your wallet is already been created', + walletAlreadyProcessing: 'Your wallet is already processing', + notAbleToSpinp: 'Agent not able to spun up', + platformAdminNotAbleToSpinp: 'Platform admin agent is not spun up', + seedCharCount: 'seed must be at most 32 characters' } }, connection: { success: { create: 'Connection created successfully', - fetch: 'Connection fetched successfully' + fetchConnection: 'Connection details fetched successfully', + fetch: 'Connections details fetched successfully' }, error: { exists: 'Connection is already exist', @@ -179,7 +206,7 @@ export const ResponseMessages = { error: { exists: 'Credentials is already exist', credentialsNotFound: 'Credentials not found', - agentEndPointNotFound: 'agentEndPoint Not Found', + agentEndPointNotFound: 'agent end point Not Found', organizationNotFound: 'organization Not Found', agentUrlNotFound: 'agent url not found', notFound: 'History not found', @@ -202,6 +229,7 @@ export const ResponseMessages = { verification: { success: { fetch: 'Proof presentations details fetched successfully.', + create: 'Presentation of proof details created successfully.', proofFormData: 'Proof presentation form data received successfully.', send: 'Proof request send successfully.', verified: 'Proof presentation verified successfully.' @@ -292,5 +320,13 @@ export const ResponseMessages = { mismatchedAttributes: 'Schema attributes are mismatched in the file header.', fileDetailsNotFound: 'File details not found.' } + }, + errorMessages: { + forbidden: 'Forbidden Resource', + badRequest: 'Bad Request', + conflict: 'Conflict', + notAcceptable: 'Not Acceptable', + notFound: 'Not Found', + serverError: 'Internal Server error' } }; \ No newline at end of file diff --git a/libs/org-roles/interfaces/org-roles.interface.ts b/libs/org-roles/interfaces/org-roles.interface.ts new file mode 100644 index 000000000..b170d93af --- /dev/null +++ b/libs/org-roles/interfaces/org-roles.interface.ts @@ -0,0 +1,9 @@ +export interface IOrgRoles { + id: string; + name: string; + description: string; + createDateTime: Date; + createdBy: string; + lastChangedDateTime: Date; + lastChangedBy: string; +} \ No newline at end of file diff --git a/libs/org-roles/repositories/index.ts b/libs/org-roles/repositories/index.ts index 863b7fbe8..0488fd24e 100644 --- a/libs/org-roles/repositories/index.ts +++ b/libs/org-roles/repositories/index.ts @@ -4,6 +4,7 @@ import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase import { org_roles } from '@prisma/client'; import { OrgRoles } from '../enums'; +import { IOrgRoles } from '../interfaces/org-roles.interface'; @Injectable() export class OrgRolesRepository { @@ -24,9 +25,8 @@ export class OrgRolesRepository { } } - - // eslint-disable-next-line camelcase - async getOrgRoles(): Promise { + + async getOrgRoles(): Promise { try { const roleDetails = await this.prisma.org_roles.findMany(); const filteredRoles = roleDetails.filter(role => role.name !== OrgRoles.PLATFORM_ADMIN); diff --git a/libs/org-roles/src/org-roles.service.ts b/libs/org-roles/src/org-roles.service.ts index b48315ad9..a7ce01cdf 100644 --- a/libs/org-roles/src/org-roles.service.ts +++ b/libs/org-roles/src/org-roles.service.ts @@ -3,7 +3,7 @@ import { Logger } from '@nestjs/common'; import { OrgRolesRepository } from '../repositories'; // eslint-disable-next-line camelcase import { org_roles } from '@prisma/client'; - +import { IOrgRoles } from '../interfaces/org-roles.interface'; @Injectable() export class OrgRolesService { @@ -14,8 +14,7 @@ export class OrgRolesService { return this.orgRoleRepository.getRole(roleName); } - // eslint-disable-next-line camelcase - async getOrgRoles(): Promise { + async getOrgRoles(): Promise< IOrgRoles[]> { return this.orgRoleRepository.getOrgRoles(); } diff --git a/libs/prisma-service/prisma/migrations/20231227105815_org_id_unique_org_agent/migration.sql b/libs/prisma-service/prisma/migrations/20231227105815_org_id_unique_org_agent/migration.sql new file mode 100644 index 000000000..241cb06ea --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231227105815_org_id_unique_org_agent/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[orgId]` on the table `org_agents` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "org_agents_orgId_key" ON "org_agents"("orgId"); diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index d8885aa6e..06c201e7f 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -168,7 +168,7 @@ model org_agents { tenantId String? apiKey String? agentsTypeId String? @db.Uuid - orgId String? @db.Uuid + orgId String? @unique @db.Uuid orgAgentTypeId String? @db.Uuid ledgerId String? @db.Uuid agent_invitations agent_invitations[] @@ -455,4 +455,4 @@ model file_data { credDefId String? status Boolean @default(false) credential_data Json? -} +} \ No newline at end of file diff --git a/libs/service/base.service.ts b/libs/service/base.service.ts index 5d723b2b2..9fade96be 100644 --- a/libs/service/base.service.ts +++ b/libs/service/base.service.ts @@ -3,6 +3,8 @@ import { Logger } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { map } from 'rxjs/operators'; +import { firstValueFrom } from 'rxjs'; + export class BaseService { protected logger; @@ -24,4 +26,13 @@ export class BaseService { ) .toPromise(); } + + +sendNatsMessage(serviceProxy: ClientProxy, cmd: string, payload: any): Promise { + const pattern = { cmd }; + + const result = serviceProxy.send(pattern, payload); + + return firstValueFrom(result); +} } \ No newline at end of file diff --git a/libs/user-activity/interface/index.ts b/libs/user-activity/interface/index.ts index d6bc0c120..87ba1685b 100644 --- a/libs/user-activity/interface/index.ts +++ b/libs/user-activity/interface/index.ts @@ -1,4 +1,4 @@ -export interface UsersActivity { +export interface IUsersActivity { id: string, orgId: string, userId: string, diff --git a/libs/user-activity/repositories/index.ts b/libs/user-activity/repositories/index.ts index 3618f188c..7ab76edfc 100644 --- a/libs/user-activity/repositories/index.ts +++ b/libs/user-activity/repositories/index.ts @@ -1,6 +1,6 @@ /* eslint-disable camelcase */ import { Injectable, Logger } from '@nestjs/common'; -import { UsersActivity} from '../interface'; +import { IUsersActivity} from '../interface'; import { PrismaService } from '@credebl/prisma-service'; import { user_activity } from '@prisma/client'; @@ -23,7 +23,7 @@ export class UserActivityRepository { } - async getRecentActivities(userId: string, limit: number): Promise { + async getRecentActivities(userId: string, limit: number): Promise { return this.prisma.user_activity.findMany({ where: { userId diff --git a/libs/user-activity/src/user-activity.service.ts b/libs/user-activity/src/user-activity.service.ts index a84554d0a..f90f4f00a 100644 --- a/libs/user-activity/src/user-activity.service.ts +++ b/libs/user-activity/src/user-activity.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { UserActivityRepository } from '../repositories'; import { user_activity } from '@prisma/client'; -import { UsersActivity } from '../interface'; +import { IUsersActivity } from '../interface'; @Injectable() export class UserActivityService { @@ -16,7 +16,7 @@ export class UserActivityService { return this.userActivityRepository.logActivity(userId, orgId, action, details); } - async getUserActivity(userId: string, limit: number): Promise { + async getUserActivity(userId: string, limit: number): Promise { return this.userActivityRepository.getRecentActivities(userId, limit); } }