Skip to content

Commit

Permalink
feat: create cloud wallet (#852)
Browse files Browse the repository at this point in the history
* feat: creat cloud wallet

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

* fix: added error handling logic in common file

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

* fix: changed nkey veriable

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

---------

Signed-off-by: tipusinghaw <[email protected]>
Signed-off-by: KulkarniShashank <[email protected]>
  • Loading branch information
tipusinghaw authored and KulkarniShashank committed Sep 12, 2024
1 parent 53a3c19 commit f46ab27
Show file tree
Hide file tree
Showing 26 changed files with 627 additions and 55 deletions.
4 changes: 3 additions & 1 deletion apps/api-gateway/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { WebhookModule } from './webhook/webhook.module';
import { UtilitiesModule } from './utilities/utilities.module';
import { NotificationModule } from './notification/notification.module';
import { CommonConstants } from '@credebl/common/common.constant';
import { CloudWalletModule } from './cloud-wallet/cloud-wallet.module';

@Module({
imports: [
Expand Down Expand Up @@ -55,7 +56,8 @@ import { CommonConstants } from '@credebl/common/common.constant';
WebhookModule,
NotificationModule,
CacheModule.register({ store: redisStore, host: process.env.REDIS_HOST, port: process.env.REDIS_PORT }),
GeoLocationModule
GeoLocationModule,
CloudWalletModule
],
controllers: [AppController],
providers: [AppService]
Expand Down
48 changes: 48 additions & 0 deletions apps/api-gateway/src/cloud-wallet/cloud-wallet.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { IResponse } from '@credebl/common/interfaces/response.interface';
import { ResponseMessages } from '@credebl/common/response-messages';
import { Controller, Post, Logger, Body, HttpStatus, Res, UseFilters } from '@nestjs/common';
import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger';
import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto';
import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto';
import { CloudWalletService } from './cloud-wallet.service';
import { CreateCloudWalletDto } from './dtos/cloudWallet.dto';
import { Response } from 'express';
import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler';
import { ApiResponseDto } from '../dtos/apiResponse.dto';

@UseFilters(CustomExceptionFilter)
@Controller()
@ApiTags('cloud-wallet')
@ApiBearerAuth()
@ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto })
@ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto })
export class CloudWalletController {

private readonly logger = new Logger('cloud-wallet');
constructor(private readonly cloudWalletService: CloudWalletService
) { }

/**
* Create cloud wallet
* @param cloudWalletDetails
* @param res
* @returns sucess message
*/
@Post('/create-wallet')
@ApiOperation({ summary: 'Create outbound out-of-band connection invitation', description: 'Create outbound out-of-band connection invitation' })
@ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto })
async createCloudWallet(
@Res() res: Response,
@Body() cloudWalletDetails: CreateCloudWalletDto
): Promise<Response> {

const cloudWalletData = await this.cloudWalletService.createCloudWallet(cloudWalletDetails);
const finalResponse: IResponse = {
statusCode: HttpStatus.CREATED,
message: ResponseMessages.cloudWallet.success.create,
data: cloudWalletData
};
return res.status(HttpStatus.CREATED).json(finalResponse);

}
}
24 changes: 24 additions & 0 deletions apps/api-gateway/src/cloud-wallet/cloud-wallet.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getNatsOptions } from '@credebl/common/nats.config';
import { CloudWalletController } from './cloud-wallet.controller';
import { CloudWalletService } from './cloud-wallet.service';
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { CommonConstants } from '@credebl/common/common.constant';

@Module({
imports: [

ClientsModule.register([
{
name: 'NATS_CLIENT',
transport: Transport.NATS,
options: getNatsOptions(CommonConstants.CLOUD_WALLET_SERVICE, process.env.API_GATEWAY_NKEY_SEED)
}
])
],
controllers: [CloudWalletController],
providers: [CloudWalletService]
})

export class CloudWalletModule {
}
20 changes: 20 additions & 0 deletions apps/api-gateway/src/cloud-wallet/cloud-wallet.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

import { ICreateCloudWallet, IStoredWalletDetails } from '@credebl/common/interfaces/cloud-wallet.interface';
import { Inject, Injectable} from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { BaseService } from 'libs/service/base.service';

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

createCloudWallet(
cloudWalletDetails: ICreateCloudWallet
): Promise<IStoredWalletDetails> {
return this.sendNatsMessage(this.cloudWalletServiceProxy, 'create-cloud-wallet', cloudWalletDetails);
}


}
20 changes: 20 additions & 0 deletions apps/api-gateway/src/cloud-wallet/dtos/cloudWallet.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';

import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsNotSQLInjection } from '@credebl/common/cast.helper';

export class CreateCloudWalletDto {
@ApiPropertyOptional()
@IsString({ message: 'label must be a string' })
@IsNotEmpty({ message: 'please provide valid label' })
@IsNotSQLInjection({ message: 'label is required.' })
label: string;

@ApiPropertyOptional()
@IsString({ message: 'Image URL must be a string' })
@IsOptional()
@IsNotEmpty({ message: 'please provide valid image URL' })
@IsNotSQLInjection({ message: 'Image URL is required.' })
connectionImageUrl?: string;

}
4 changes: 4 additions & 0 deletions apps/api-gateway/src/cloud-wallet/enums/connections.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export declare enum HandshakeProtocol {
Connections = "https://didcomm.org/connections/1.0",
DidExchange = "https://didcomm.org/didexchange/1.0"
}
16 changes: 16 additions & 0 deletions apps/cloud-wallet/src/cloud-wallet.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Controller } from '@nestjs/common'; // Import the common service in the library
import { CloudWalletService } from './cloud-wallet.service'; // Import the common service in connection module
import { MessagePattern } from '@nestjs/microservices'; // Import the nestjs microservices package
import { ICreateCloudWallet, IStoredWalletDetails } from '@credebl/common/interfaces/cloud-wallet.interface';

@Controller()
export class CloudWalletController {
constructor(private readonly cloudWalletService: CloudWalletService) {}


@MessagePattern({ cmd: 'create-cloud-wallet' })
async createConnectionInvitation(cloudWalletDetails: ICreateCloudWallet): Promise<IStoredWalletDetails> {
return this.cloudWalletService.createCloudWallet(cloudWalletDetails);
}
}
27 changes: 27 additions & 0 deletions apps/cloud-wallet/src/cloud-wallet.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Logger, Module } from '@nestjs/common';
import { CloudWalletController } from './cloud-wallet.controller';
import { CloudWalletService } from './cloud-wallet.service';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { CommonModule } from '@credebl/common';
import { CacheModule } from '@nestjs/cache-manager';
import { getNatsOptions } from '@credebl/common/nats.config';
import { PrismaService } from '@credebl/prisma-service';
import { CloudWalletRepository } from './cloud-wallet.repository';

@Module({
imports: [
ClientsModule.register([
{
name: 'NATS_CLIENT',
transport: Transport.NATS,
options: getNatsOptions(process.env.CLOUD_WALLET_NKEY_SEED)
}
]),

CommonModule,
CacheModule.register()
],
controllers: [CloudWalletController],
providers: [CloudWalletService, CloudWalletRepository, PrismaService, Logger]
})
export class CloudWalletModule {}
67 changes: 67 additions & 0 deletions apps/cloud-wallet/src/cloud-wallet.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '@credebl/prisma-service';
import { CloudWalletType } from '@credebl/enum/enum';
// eslint-disable-next-line camelcase
import { cloud_wallet_user_info } from '@prisma/client';
import { ICloudWalletDetails, IStoredWalletDetails } from '@credebl/common/interfaces/cloud-wallet.interface';


@Injectable()
export class CloudWalletRepository {
constructor(
private readonly prisma: PrismaService,
private readonly logger: Logger
) {}


// eslint-disable-next-line camelcase
async getCloudWalletDetails(type: CloudWalletType): Promise<cloud_wallet_user_info> {
try {
const agentDetails = await this.prisma.cloud_wallet_user_info.findFirstOrThrow({
where: {
type
}
});
return agentDetails;
} catch (error) {
this.logger.error(`Error in getCloudWalletBaseAgentDetails: ${error.message}`);
throw error;
}
}
// eslint-disable-next-line camelcase
async storeCloudWalletDetails(cloudWalletDetails: ICloudWalletDetails): Promise<IStoredWalletDetails> {
try {
const {createdBy, label, lastChangedBy, tenantId, type, userId, agentApiKey, agentEndpoint, email, key, connectionImageUrl} = cloudWalletDetails;

return await this.prisma.cloud_wallet_user_info.create({
data: {
label,
tenantId,
email,
type,
createdBy,
lastChangedBy,
userId,
agentEndpoint,
agentApiKey,
key,
connectionImageUrl
},
select: {
email: true,
connectionImageUrl: true,
createDateTime: true,
id: true,
tenantId: true,
label: true,
lastChangedDateTime: true

}
});
} catch (error) {
this.logger.error(`Error in storeCloudWalletDetails: ${error.message}`);
throw error;
}
}

}
82 changes: 82 additions & 0 deletions apps/cloud-wallet/src/cloud-wallet.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/* eslint-disable camelcase */
import { CommonService } from '@credebl/common';
import { BadRequestException, Inject, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { Cache } from 'cache-manager';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { ICloudWalletDetails, ICreateCloudWallet, IStoredWalletDetails } from '@credebl/common/interfaces/cloud-wallet.interface';
import { CloudWalletRepository } from './cloud-wallet.repository';
import { ResponseMessages } from '@credebl/common/response-messages';
import { CloudWalletType } from '@credebl/enum/enum';
import { CommonConstants } from '@credebl/common/common.constant';

@Injectable()
export class CloudWalletService {
constructor(
private readonly commonService: CommonService,
@Inject('NATS_CLIENT') private readonly cloudWalletServiceProxy: ClientProxy,
private readonly cloudWalletRepository: CloudWalletRepository,
private readonly logger: Logger,
@Inject(CACHE_MANAGER) private cacheService: Cache
) {}


/**
* Create clous wallet
* @param cloudWalletDetails
* @returns cloud wallet details
*/
async createCloudWallet(cloudWalletDetails: ICreateCloudWallet): Promise<IStoredWalletDetails> {
try {
// TODO - Add userId fetch logic
const {label, connectionImageUrl, email, userId} = cloudWalletDetails;
const agentPayload = {
config: {
label,
connectionImageUrl
}
};
const baseWalletDetails = await this.cloudWalletRepository.getCloudWalletDetails(CloudWalletType.BASE_WALLET);
if (!baseWalletDetails) {
throw new NotFoundException(ResponseMessages.cloudWallet.error.baseWalletNotFound);
}
const {agentEndpoint, agentApiKey} = baseWalletDetails;
const decryptedApiKey = await this.commonService.decryptPassword(agentApiKey);
const url = `${agentEndpoint}${CommonConstants.URL_SHAGENT_CREATE_TENANT}`;
const createCloudWalletResponse = await this.commonService.httpPost(url, agentPayload, { headers: { authorization: decryptedApiKey } });
if (!createCloudWalletResponse && !createCloudWalletResponse.id) {
throw new InternalServerErrorException(ResponseMessages.cloudWallet.error.createCloudWallet, {
cause: new Error(),
description: ResponseMessages.errorMessages.serverError
});
}

const walletKey = await this.commonService.dataEncryption(createCloudWalletResponse.config.walletConfig.key);

if (!walletKey) {
throw new BadRequestException(ResponseMessages.cloudWallet.error.encryptCloudWalletKey, {
cause: new Error(),
description: ResponseMessages.errorMessages.serverError
});
}
const cloudWalletResponse: ICloudWalletDetails = {
createdBy: userId,
label,
lastChangedBy: userId,
tenantId: createCloudWalletResponse.id,
type: CloudWalletType.SUB_WALLET,
userId,
agentApiKey,
agentEndpoint,
email,
key: walletKey,
connectionImageUrl
};
const storeCloudWalletDetails = await this.cloudWalletRepository.storeCloudWalletDetails(cloudWalletResponse);
return storeCloudWalletDetails;
} catch (error) {
this.logger.error(`[createCloudWallet] - error in create cloud wallet: ${error}`);
await this.commonService.handleError(error);
}
}
}
23 changes: 23 additions & 0 deletions apps/cloud-wallet/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NestFactory } from '@nestjs/core';
import { CloudWalletModule } from './cloud-wallet.module';
import { HttpExceptionFilter } from 'libs/http-exception.filter';
import { Logger } from '@nestjs/common';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { getNatsOptions } from '@credebl/common/nats.config';
import { CommonConstants } from '@credebl/common/common.constant';

const logger = new Logger();

async function bootstrap(): Promise<void> {

const app = await NestFactory.createMicroservice<MicroserviceOptions>(CloudWalletModule, {
transport: Transport.NATS,
options: getNatsOptions(CommonConstants.CLOUD_WALLET_SERVICE, process.env.CLOUD_WALLET_NKEY_SEED)
});

app.useGlobalFilters(new HttpExceptionFilter());

await app.listen();
logger.log('Cloud-wallet Microservice is listening to NATS ');
}
bootstrap();
9 changes: 9 additions & 0 deletions apps/cloud-wallet/test/jest-e2e.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}
9 changes: 9 additions & 0 deletions apps/cloud-wallet/tsconfig.app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": false,
"outDir": "../../dist/apps/cloud-wallet"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
}
3 changes: 2 additions & 1 deletion libs/common/src/common.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ VERIFICATION_SERVICE = 'verification',
ECOSYSTEM_SERVICE = 'ecosystem',
WEBHOOK_SERVICE = 'webhook',
NOTIFICATION_SERVICE = 'notification',
GEO_LOCATION_SERVICE = 'geo-location'
GEO_LOCATION_SERVICE = 'geo-location',
CLOUD_WALLET_SERVICE = 'cloud-wallet'
}

export const postgresqlErrorCodes = [];
Expand Down
Loading

0 comments on commit f46ab27

Please sign in to comment.