From 20d8e0ff0b365b385c5afc62d85d21496cac8cd5 Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Fri, 1 Sep 2023 19:17:30 +0530 Subject: [PATCH 001/162] fix: Changed the passkey approch Signed-off-by: KulkarniShashank --- apps/api-gateway/src/user/user.controller.ts | 175 ++++++++++-------- .../repositories/user-device.repository.ts | 24 +++ apps/user/src/user.module.ts | 4 +- apps/user/src/user.service.ts | 47 +++-- .../migration.sql | 2 + libs/prisma-service/prisma/schema.prisma | 1 + 6 files changed, 160 insertions(+), 93 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20230831142029_user_device_password/migration.sql diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 345922989..bc79004a6 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -76,77 +76,77 @@ export class UserController { * @param res * @returns Users list of organization */ -@Get() -@Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) -@ApiBearerAuth() -@UseGuards(AuthGuard('jwt'), OrgRolesGuard) -@ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) -@ApiOperation({ summary: 'Get organization users list', description: 'Get organization users list.' }) -@ApiQuery({ - name: 'pageNumber', - type: Number, - required: false -}) -@ApiQuery({ - name: 'pageSize', - type: Number, - required: false -}) -@ApiQuery({ - name: 'search', - type: String, - required: false -}) -async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Query('orgId') orgId: number, @Res() res: Response): Promise { - - const org = user.selectedOrg?.orgId; - const users = await this.userService.getOrgUsers(org, getAllUsersDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchUsers, - data: users.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); -} - - -/** - * - * @param user - * @param orgId - * @param res - * @returns Users list of organization - */ -@Get('/public') -@ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) -@ApiOperation({ summary: 'Get users list', description: 'Get users list.' }) -@ApiQuery({ - name: 'pageNumber', - type: Number, - required: false -}) -@ApiQuery({ - name: 'pageSize', - type: Number, - required: false -}) -@ApiQuery({ - name: 'search', - type: String, - required: false -}) -async get(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Res() res: Response): Promise { - - const users = await this.userService.get(getAllUsersDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchUsers, - data: users.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); -} + @Get() + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Get organization users list', description: 'Get organization users list.' }) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Query('orgId') orgId: number, @Res() res: Response): Promise { + + const org = user.selectedOrg?.orgId; + const users = await this.userService.getOrgUsers(org, getAllUsersDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.fetchUsers, + data: users.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } + + + /** + * + * @param user + * @param orgId + * @param res + * @returns Users list of organization + */ + @Get('/public') + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Get users list', description: 'Get users list.' }) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async get(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Res() res: Response): Promise { + + const users = await this.userService.get(getAllUsersDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.fetchUsers, + data: users.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } /** @@ -328,21 +328,34 @@ async get(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUs @Post('/add/:email') @ApiOperation({ summary: 'Add user information', description: 'Add user information' }) async addUserDetailsInKeyCloak(@Body() userInfo: AddUserDetails, @Param('email') email: string, @Res() res: Response): Promise { - const decryptedPassword = this.commonService.decryptPassword(userInfo.password); - if (8 <= decryptedPassword.length && 50 >= decryptedPassword.length) { - this.commonService.passwordValidation(decryptedPassword); - userInfo.password = decryptedPassword; - const userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); - const finalResponse: IResponseType = { + let finalResponse; + let userDetails; + + if (false === userInfo.isPasskey) { + + const decryptedPassword = this.commonService.decryptPassword(userInfo.password); + if (8 <= decryptedPassword.length && 50 >= decryptedPassword.length) { + this.commonService.passwordValidation(decryptedPassword); + userInfo.password = decryptedPassword; + userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); + finalResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userDetails.response + }; + } else { + throw new BadRequestException('Password name must be between 8 to 50 Characters'); + } + } else { + + userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); + finalResponse = { statusCode: HttpStatus.CREATED, message: ResponseMessages.user.success.create, data: userDetails.response }; - return res.status(HttpStatus.OK).json(finalResponse); - - } else { - throw new BadRequestException('Password name must be between 8 to 50 Characters'); } + return res.status(HttpStatus.OK).json(finalResponse); } diff --git a/apps/user/repositories/user-device.repository.ts b/apps/user/repositories/user-device.repository.ts index 5e4422905..9d289f5a5 100644 --- a/apps/user/repositories/user-device.repository.ts +++ b/apps/user/repositories/user-device.repository.ts @@ -286,5 +286,29 @@ async addCredentialIdAndNameById(id:number, credentialId:string, deviceFriendlyN throw new InternalServerErrorException(error); } } + +/** + * + * @param password + * @param userId + * @returns Update password + */ +async updateUserDeviceDetails(password: string, userId: number): Promise { + try { + return await this.prisma.user_devices.updateMany({ + where: { + userId + }, + data: { + password + } + }); + + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } +} + } diff --git a/apps/user/src/user.module.ts b/apps/user/src/user.module.ts index 42362c279..865dc6e04 100644 --- a/apps/user/src/user.module.ts +++ b/apps/user/src/user.module.ts @@ -16,6 +16,7 @@ import { UserOrgRolesRepository } from 'libs/user-org-roles/repositories'; import { UserOrgRolesService } from '@credebl/user-org-roles'; import { UserRepository } from '../repositories/user.repository'; import { UserService } from './user.service'; +import { UserDevicesRepository } from '../repositories/user-device.repository'; @Module({ imports: [ @@ -46,7 +47,8 @@ import { UserService } from './user.service'; OrgRolesRepository, UserOrgRolesRepository, UserActivityService, - UserActivityRepository + UserActivityRepository, + UserDevicesRepository ] }) export class UserModule {} diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index b9e42fcd4..5d1358549 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -31,7 +31,8 @@ import { InvitationsI, UpdateUserProfile, UserEmailVerificationDto, userInfo } f import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; - +import { UserDevicesRepository } from '../repositories/user-device.repository'; +import { v4 as uuidv4 } from 'uuid'; @Injectable() export class UserService { @@ -44,6 +45,7 @@ export class UserService { private readonly userOrgRoleService: UserOrgRolesService, private readonly userActivityService: UserActivityService, private readonly userRepository: UserRepository, + private readonly userDevicesRepository: UserDevicesRepository, private readonly logger: Logger, @Inject('NATS_CLIENT') private readonly userServiceProxy: ClientProxy ) { } @@ -174,10 +176,35 @@ export class UserService { if (!userDetails) { throw new NotFoundException(ResponseMessages.user.error.adduser); } - const supaUser = await this.supabaseService.getClient().auth.signUp({ - email, - password: userInfo.password - }); + + let supaUser; + + if (userInfo.isPasskey) { + const password: string = uuidv4(); + + supaUser = await this.supabaseService.getClient().auth.signUp({ + email, + password + }); + + if (supaUser.error) { + throw new InternalServerErrorException(supaUser.error?.message); + } + + const getUserDetails = await this.userRepository.getUserDetails(userDetails.email); + await this.userDevicesRepository.updateUserDeviceDetails( + password, + getUserDetails.id + ); + + } else { + + + supaUser = await this.supabaseService.getClient().auth.signUp({ + email, + password: userInfo.password + }); + } if (supaUser.error) { throw new InternalServerErrorException(supaUser.error?.message); @@ -225,9 +252,9 @@ export class UserService { } if (true === isPasskey && userData?.username && true === userData?.isFidoVerified) { - - return this.generateToken(email, password); - + const getUserDetails = await this.userRepository.getUserDetails(userData.email); + const getFidoUserPassword = await this.userDevicesRepository.checkUserDevice(getUserDetails.id); + return this.generateToken(email, getFidoUserPassword.password); } return this.generateToken(email, password); @@ -238,6 +265,7 @@ export class UserService { } async generateToken(email: string, password: string): Promise { + const supaInstance = await this.supabaseService.getClient(); this.logger.error(`supaInstance::`, supaInstance); @@ -246,7 +274,6 @@ export class UserService { email, password }); - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); if (error) { @@ -254,11 +281,9 @@ export class UserService { } const token = data?.session; - return token; } - async getProfile(payload: { id }): Promise { try { return this.userRepository.getUserById(payload.id); diff --git a/libs/prisma-service/prisma/migrations/20230831142029_user_device_password/migration.sql b/libs/prisma-service/prisma/migrations/20230831142029_user_device_password/migration.sql new file mode 100644 index 000000000..de04d62e7 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20230831142029_user_device_password/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "user_devices" ADD COLUMN "password" VARCHAR; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 23fb8d161..3fe696cb3 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -119,6 +119,7 @@ model user_devices { userId Int? deletedAt DateTime? @db.Timestamp(6) authCounter Int @default(0) + password String? @db.VarChar user user? @relation(fields: [userId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_e12ac4f8016243ac71fd2e415af") } From 5f431b3b6fa80c60f05c96874994b360b267365a Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Wed, 6 Sep 2023 23:27:52 +0530 Subject: [PATCH 002/162] feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw --- apps/api-gateway/src/user/dto/add-user.dto.ts | 8 ++ apps/api-gateway/src/user/user.controller.ts | 18 +++- apps/api-gateway/src/user/user.service.ts | 7 ++ apps/user/interfaces/user.interface.ts | 4 + .../repositories/user-device.repository.ts | 23 ----- apps/user/repositories/user.repository.ts | 23 +++++ apps/user/src/user.controller.ts | 7 +- apps/user/src/user.service.ts | 87 ++++++++++++------- libs/common/src/response-messages/index.ts | 3 +- 9 files changed, 122 insertions(+), 58 deletions(-) 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 51a78d2b6..a72eb45eb 100644 --- a/apps/api-gateway/src/user/dto/add-user.dto.ts +++ b/apps/api-gateway/src/user/dto/add-user.dto.ts @@ -25,3 +25,11 @@ export class AddUserDetails { @IsBoolean({ message: 'isPasskey should be boolean' }) isPasskey?: boolean; } + +export class AddPasskeyDetails { + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Password is required.' }) + password: string; + +} diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index bc79004a6..0677ad8eb 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -39,7 +39,7 @@ import { OrgRoles } from 'libs/org-roles/enums'; import { IUserRequestInterface } from './interfaces'; import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; import { GetAllUsersDto } from './dto/get-all-users.dto'; -import { AddUserDetails } from './dto/add-user.dto'; +import { AddPasskeyDetails, AddUserDetails } from './dto/add-user.dto'; import { UpdateUserProfileDto } from './dto/update-user-profile.dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @@ -399,4 +399,20 @@ export class UserController { return res.status(HttpStatus.OK).json(finalResponse); } + + @Post('/password/:email') + @ApiOperation({ summary: 'Add user information', description: 'Add user information' }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async addPasskey(@Body() userInfo: AddPasskeyDetails, @Param('email') email: string, @Res() res: Response): Promise { + const userDetails = await this.userService.addPasskey(email, userInfo); + const finalResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userDetails.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } } \ No newline at end of file diff --git a/apps/api-gateway/src/user/user.service.ts b/apps/api-gateway/src/user/user.service.ts index fbc39fd23..6dfa2b0f5 100644 --- a/apps/api-gateway/src/user/user.service.ts +++ b/apps/api-gateway/src/user/user.service.ts @@ -9,6 +9,8 @@ import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; import { AddUserDetails } from './dto/login-user.dto'; import { GetAllUsersDto } from './dto/get-all-users.dto'; import { UpdateUserProfileDto } from './dto/update-user-profile.dto'; +import { AddPasskeyDetails } from './dto/add-user.dto'; + @Injectable() export class UserService extends BaseService { @@ -125,4 +127,9 @@ export class UserService extends BaseService { const payload = { userId, limit }; return this.sendNats(this.serviceProxy, 'get-user-activity', payload); } + + async addPasskey(userEmail: string, userInfo:AddPasskeyDetails): Promise<{ response: string }> { + const payload = { userEmail, userInfo }; + return this.sendNats(this.serviceProxy, 'add-passkey', payload); + } } diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index b93da4280..051ba3bfb 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -35,6 +35,10 @@ export interface userInfo{ isPasskey: boolean } +export interface AddPasskeyDetails{ + password: string, +} + export interface UserWhereUniqueInput{ id?: number } diff --git a/apps/user/repositories/user-device.repository.ts b/apps/user/repositories/user-device.repository.ts index 9d289f5a5..9e3c559d2 100644 --- a/apps/user/repositories/user-device.repository.ts +++ b/apps/user/repositories/user-device.repository.ts @@ -287,28 +287,5 @@ async addCredentialIdAndNameById(id:number, credentialId:string, deviceFriendlyN } } -/** - * - * @param password - * @param userId - * @returns Update password - */ -async updateUserDeviceDetails(password: string, userId: number): Promise { - try { - return await this.prisma.user_devices.updateMany({ - where: { - userId - }, - data: { - password - } - }); - - } catch (error) { - this.logger.error(`Not Found: ${JSON.stringify(error)}`); - throw new NotFoundException(error); - } -} - } diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 860e4a784..cfdfc92e7 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -452,4 +452,27 @@ export class UserRepository { } } + /** + * + * @param userInfo + * @returns Updates user credentials + */ + // eslint-disable-next-line camelcase + async addUserPassword(email: string, userInfo: string): Promise { + try { + const updateUserDetails = await this.prisma.user.update({ + where: { + email + }, + data: { + password: userInfo + } + }); + return updateUserDetails; + } catch (error) { + this.logger.error(`Error in update isEmailVerified: ${error.message} `); + throw error; + } + } + } diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 952e84dcb..629bb9dcf 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -5,7 +5,7 @@ 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 { UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; +import { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; @Controller() @@ -116,4 +116,9 @@ export class UserController { return this.userService.getUserActivity(payload.userId, payload.limit); } + @MessagePattern({ cmd: 'add-passkey' }) + async addPasskey(payload: { userEmail: string, userInfo: AddPasskeyDetails }): Promise { + return this.userService.addPasskey(payload.userEmail, payload.userInfo); + } + } diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 5d1358549..596b3bd31 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -27,12 +27,12 @@ import { sendEmail } from '@credebl/common/send-grid-helper-file'; import { user } from '@prisma/client'; import { Inject } from '@nestjs/common'; import { HttpException } from '@nestjs/common'; -import { InvitationsI, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; +import { AddPasskeyDetails, InvitationsI, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; import { UserDevicesRepository } from '../repositories/user-device.repository'; -import { v4 as uuidv4 } from 'uuid'; + @Injectable() export class UserService { @@ -58,7 +58,7 @@ export class UserService { async sendVerificationMail(userEmailVerificationDto: UserEmailVerificationDto): Promise { try { const userDetails = await this.userRepository.checkUserExist(userEmailVerificationDto.email); - + if (userDetails && userDetails.isEmailVerified) { throw new ConflictException(ResponseMessages.user.error.exists); } @@ -180,26 +180,18 @@ export class UserService { let supaUser; if (userInfo.isPasskey) { - const password: string = uuidv4(); - + const resUser = await this.userRepository.addUserPassword(email, userInfo.password); + const userDetails = await this.userRepository.getUserDetails(email); + const decryptedPassword = await this.commonService.decryptPassword(userDetails.password); + if (!resUser) { + throw new NotFoundException(ResponseMessages.user.error.invalidEmail); + } supaUser = await this.supabaseService.getClient().auth.signUp({ email, - password + password: decryptedPassword }); - if (supaUser.error) { - throw new InternalServerErrorException(supaUser.error?.message); - } - - const getUserDetails = await this.userRepository.getUserDetails(userDetails.email); - await this.userDevicesRepository.updateUserDeviceDetails( - password, - getUserDetails.id - ); - } else { - - supaUser = await this.supabaseService.getClient().auth.signUp({ email, password: userInfo.password @@ -227,6 +219,33 @@ export class UserService { } } + async addPasskey(email: string, userInfo: AddPasskeyDetails): Promise { + try { + if (!email) { + throw new UnauthorizedException(ResponseMessages.user.error.invalidEmail); + } + const checkUserDetails = await this.userRepository.getUserDetails(email); + if (!checkUserDetails) { + throw new NotFoundException(ResponseMessages.user.error.invalidEmail); + } + if (!checkUserDetails.supabaseUserId) { + throw new ConflictException(ResponseMessages.user.error.notFound); + } + if (false === checkUserDetails.isEmailVerified) { + throw new NotFoundException(ResponseMessages.user.error.emailNotVerified); + } + const resUser = await this.userRepository.addUserPassword(email, userInfo.password); + if (!resUser) { + throw new NotFoundException(ResponseMessages.user.error.invalidEmail); + } + + return 'User updated successfully'; + } catch (error) { + this.logger.error(`Error in createUserForToken: ${JSON.stringify(error)}`); + throw new RpcException(error.response); + } + } + /** * @@ -253,8 +272,8 @@ export class UserService { if (true === isPasskey && userData?.username && true === userData?.isFidoVerified) { const getUserDetails = await this.userRepository.getUserDetails(userData.email); - const getFidoUserPassword = await this.userDevicesRepository.checkUserDevice(getUserDetails.id); - return this.generateToken(email, getFidoUserPassword.password); + const decryptedPassword = await this.commonService.decryptPassword(getUserDetails.password); + return this.generateToken(email, decryptedPassword); } return this.generateToken(email, password); @@ -265,23 +284,27 @@ export class UserService { } async generateToken(email: string, password: string): Promise { + try { + const supaInstance = await this.supabaseService.getClient(); - const supaInstance = await this.supabaseService.getClient(); + this.logger.error(`supaInstance::`, supaInstance); - this.logger.error(`supaInstance::`, supaInstance); + const { data, error } = await supaInstance.auth.signInWithPassword({ + email, + password + }); - const { data, error } = await supaInstance.auth.signInWithPassword({ - email, - password - }); - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); + if (error) { + this.logger.error(`Supa Login Error::`, JSON.stringify(error)); + throw new BadRequestException(error?.message); + } - if (error) { - throw new BadRequestException(error?.message); + const token = data?.session; + return token; + } catch (error) { + this.logger.error(`An unexpected error occurred::`, error.message); + throw new InternalServerErrorException('An unexpected error occurred.'); } - - const token = data?.session; - return token; } async getProfile(payload: { id }): Promise { diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 2d3a95f64..1d265cf7a 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -31,7 +31,8 @@ export const ResponseMessages = { invalidKeycloakId: 'keycloakId is invalid', invalidEmail: 'Invalid Email Id!', adduser: 'Unable to add user details', - verifyEmail: 'The verification link has already been sent to your email address. please verify' + verifyEmail: 'The verification link has already been sent to your email address. please verify', + emailNotVerified: 'The verification link has already been sent to your email address. please verify' } }, organisation: { From 8e4a02ca13ad23f7c0814b200730e36842fc7507 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Thu, 7 Sep 2023 16:08:44 +0530 Subject: [PATCH 003/162] feat:implemented add passke Signed-off-by: tipusinghaw --- .../migration.sql | 11 ++ libs/prisma-service/prisma/schema.prisma | 118 +++++++++--------- 2 files changed, 70 insertions(+), 59 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20230905103154_password_user/migration.sql diff --git a/libs/prisma-service/prisma/migrations/20230905103154_password_user/migration.sql b/libs/prisma-service/prisma/migrations/20230905103154_password_user/migration.sql new file mode 100644 index 000000000..f63b1a510 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20230905103154_password_user/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the column `password` on the `user_devices` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "user" ADD COLUMN "password" VARCHAR; + +-- AlterTable +ALTER TABLE "user_devices" DROP COLUMN "password"; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 3fe696cb3..82c551246 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -22,41 +22,42 @@ model user { supabaseUserId String? @db.VarChar(500) clientId String? @db.VarChar(500) clientSecret String? @db.VarChar(500) - profileImg String? + profileImg String? fidoUserId String? @db.VarChar(1000) isFidoVerified Boolean @default(false) - userOrgRoles user_org_roles[] - userDevices user_devices[] + publicProfile Boolean @default(false) + password String? @db.VarChar orgInvitations org_invitations[] user_activities user_activity[] - publicProfile Boolean @default(false) + userDevices user_devices[] + userOrgRoles user_org_roles[] } model user_activity { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) userId Int - user user @relation(fields: [userId], references: [id]) orgId Int - organisation organisation? @relation(fields: [orgId], references: [id]) action String details String - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - deletedAt DateTime? @db.Timestamp(6) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) + organisation organisation @relation(fields: [orgId], references: [id]) + user user @relation(fields: [userId], references: [id]) } model org_roles { id Int @id @default(autoincrement()) name String @unique description String - userOrgRoles user_org_roles[] createDateTime DateTime @default(now()) @db.Timestamptz(6) createdBy Int @default(1) lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) lastChangedBy Int @default(1) deletedAt DateTime? @db.Timestamp(6) + userOrgRoles user_org_roles[] } model user_org_roles { @@ -80,15 +81,15 @@ model organisation { orgSlug String? @unique logoUrl String? website String? @db.VarChar - userOrgRoles user_org_roles[] - orgInvitations org_invitations[] - org_agents org_agents[] + publicProfile Boolean @default(false) connections connections[] credentials credentials[] + org_agents org_agents[] + orgInvitations org_invitations[] presentations presentations[] schema schema[] user_activities user_activity[] - publicProfile Boolean @default(false) + userOrgRoles user_org_roles[] } model org_invitations { @@ -101,10 +102,10 @@ model org_invitations { userId Int orgId Int status String - user user @relation(fields: [userId], references: [id]) - organisation organisation @relation(fields: [orgId], references: [id]) orgRoles Int[] email String? + organisation organisation @relation(fields: [orgId], references: [id]) + user user @relation(fields: [userId], references: [id]) } model user_devices { @@ -119,7 +120,6 @@ model user_devices { userId Int? deletedAt DateTime? @db.Timestamp(6) authCounter Int @default(0) - password String? @db.VarChar user user? @relation(fields: [userId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_e12ac4f8016243ac71fd2e415af") } @@ -159,12 +159,12 @@ model org_agents { orgId Int orgAgentTypeId Int ledgerId Int? - ledgers ledgers? @relation(fields: [ledgerId], references: [id]) - agents_type agents_type? @relation(fields: [agentsTypeId], references: [id]) - org_agent_type org_agents_type? @relation(fields: [orgAgentTypeId], references: [id]) - organisation organisation? @relation(fields: [orgId], references: [id]) - agents agents? @relation(fields: [agentId], references: [id]) agent_invitations agent_invitations[] + agents agents? @relation(fields: [agentId], references: [id]) + agents_type agents_type @relation(fields: [agentsTypeId], references: [id]) + ledgers ledgers? @relation(fields: [ledgerId], references: [id]) + org_agent_type org_agents_type @relation(fields: [orgAgentTypeId], references: [id]) + organisation organisation @relation(fields: [orgId], references: [id]) } model org_agents_type { @@ -214,20 +214,20 @@ model agents { } model schema { - id Int @id(map: "PK_c9e7e648903a9e537347aba4372") @default(autoincrement()) - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - name String @db.VarChar - version String @db.VarChar + id Int @id(map: "PK_c9e7e648903a9e537347aba4372") @default(autoincrement()) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + name String @db.VarChar + version String @db.VarChar attributes String - schemaLedgerId String @db.VarChar - publisherDid String @db.VarChar - ledgerId Int @default(1) - issuerId String @db.VarChar + schemaLedgerId String @db.VarChar + publisherDid String @db.VarChar + ledgerId Int @default(1) + issuerId String @db.VarChar orgId Int - organisation organisation? @relation(fields: [orgId], references: [id]) + organisation organisation @relation(fields: [orgId], references: [id]) } model credential_definition { @@ -252,46 +252,46 @@ model shortening_url { } model agent_invitations { - id Int @unique @default(autoincrement()) - orgId Int @default(1) - agentId Int @default(1) + id Int @unique @default(autoincrement()) + orgId Int @default(1) + agentId Int @default(1) connectionInvitation String multiUse Boolean - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - org_agents org_agents? @relation(fields: [agentId], references: [id]) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + org_agents org_agents @relation(fields: [agentId], references: [id]) } model connections { - id Int @id @default(autoincrement()) - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - connectionId String @unique + id Int @id @default(autoincrement()) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + connectionId String @unique state String orgDid String theirLabel String autoAcceptConnection Boolean outOfBandId String orgId Int - organisation organisation? @relation(fields: [orgId], references: [id]) + organisation organisation @relation(fields: [orgId], references: [id]) } model credentials { - id Int @id @default(autoincrement()) - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - connectionId String @unique + id Int @id @default(autoincrement()) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + connectionId String @unique threadId String protocolVersion String credentialAttributes Json[] orgId Int - organisation organisation? @relation(fields: [orgId], references: [id]) + organisation organisation @relation(fields: [orgId], references: [id]) } model presentations { From 15e580201b0db7155ae61b14920b2bc2e321cdc9 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Mon, 11 Sep 2023 13:07:46 +0530 Subject: [PATCH 004/162] fix: login error message Signed-off-by: tipusinghaw --- apps/api-gateway/src/fido/fido.controller.ts | 2 +- apps/user/src/user.service.ts | 31 +++++++++----------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/apps/api-gateway/src/fido/fido.controller.ts b/apps/api-gateway/src/fido/fido.controller.ts index d41adc608..88b4d0fdd 100644 --- a/apps/api-gateway/src/fido/fido.controller.ts +++ b/apps/api-gateway/src/fido/fido.controller.ts @@ -157,7 +157,7 @@ export class FidoController { const fidoUserDetails = await this.fidoService.fetchFidoUserDetails(req.params.userName); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.login, + message: ResponseMessages.user.success.fetchUsers, data: fidoUserDetails.response }; return res.status(HttpStatus.OK).json(finalResponse); diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 596b3bd31..f18f55843 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -276,7 +276,7 @@ export class UserService { return this.generateToken(email, decryptedPassword); } - return this.generateToken(email, password); + return this.generateToken(email, password); } catch (error) { this.logger.error(`In Login User : ${JSON.stringify(error)}`); throw new RpcException(error.response); @@ -284,27 +284,24 @@ export class UserService { } async generateToken(email: string, password: string): Promise { - try { - const supaInstance = await this.supabaseService.getClient(); + const supaInstance = await this.supabaseService.getClient(); - this.logger.error(`supaInstance::`, supaInstance); + this.logger.error(`supaInstance::`, supaInstance); - const { data, error } = await supaInstance.auth.signInWithPassword({ - email, - password - }); + const { data, error } = await supaInstance.auth.signInWithPassword({ + email, + password + }); - if (error) { - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); - throw new BadRequestException(error?.message); - } + this.logger.error(`Supa Login Error::`, JSON.stringify(error)); - const token = data?.session; - return token; - } catch (error) { - this.logger.error(`An unexpected error occurred::`, error.message); - throw new InternalServerErrorException('An unexpected error occurred.'); + if (error) { + throw new BadRequestException(error?.message); } + + const token = data?.session; + + return token; } async getProfile(payload: { id }): Promise { From 729d73b73fdd0c550e2f1ca388a568d04ca96bf2 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Thu, 14 Sep 2023 20:14:22 +0530 Subject: [PATCH 005/162] worked on the username & orgslug feature for public profiles Signed-off-by: @nishad.shirsat --- .../dtos/update-organization-dto.ts | 7 +++- .../organization/organization.controller.ts | 15 +++---- .../src/organization/organization.service.ts | 4 +- .../src/user/dto/update-user-profile.dto.ts | 8 +++- apps/api-gateway/src/user/user.controller.ts | 13 +++--- apps/api-gateway/src/user/user.service.ts | 4 +- .../dtos/create-organization.dto.ts | 1 + .../interfaces/organization.interface.ts | 2 + .../repositories/organization.repository.ts | 14 +++++-- .../src/organization.controller.ts | 2 +- apps/organization/src/organization.service.ts | 42 ++++++++++++++++--- apps/user/interfaces/user.interface.ts | 2 + apps/user/repositories/user.repository.ts | 38 +++++++++++------ apps/user/src/user.controller.ts | 5 +-- apps/user/src/user.service.ts | 34 +++++++++++++-- 15 files changed, 143 insertions(+), 48 deletions(-) diff --git a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts index 44569b38d..1229ddc58 100644 --- a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts +++ b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts @@ -1,5 +1,5 @@ import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumber, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; @@ -37,4 +37,9 @@ export class UpdateOrganizationDto { @IsOptional() website: string; + @ApiPropertyOptional({ example: true }) + @IsBoolean({ message: 'isPublic should be boolean' }) + @IsOptional() + isPublic? = false; + } \ No newline at end of file diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 05fafc1da..25934cfe1 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -1,4 +1,4 @@ -import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { CommonService } from '@credebl/common'; import { Controller, Get, Put, Param, UseGuards, UseFilters } from '@nestjs/common'; import { OrganizationService } from './organization.service'; @@ -109,18 +109,19 @@ export class OrganizationController { return res.status(HttpStatus.OK).json(finalResponse); } - @Get('public-profile') + @Get('public-profiles/:orgSlug') @ApiOperation({ summary: 'Fetch user details', description: 'Fetch user details' }) - @ApiQuery({ - name: 'id', - type: Number, + + @ApiParam({ + name: 'orgSlug', + type: String, required: false }) - async getPublicProfile(@User() reqUser: user, @Query('id') id: number, @Res() res: Response): Promise { - const userData = await this.organizationService.getPublicProfile(id); + async getPublicProfile(@Param('orgSlug') orgSlug: string, @Res() res: Response): Promise { + const userData = await this.organizationService.getPublicProfile(orgSlug); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, diff --git a/apps/api-gateway/src/organization/organization.service.ts b/apps/api-gateway/src/organization/organization.service.ts index eb626e6d1..ba0d6f96d 100644 --- a/apps/api-gateway/src/organization/organization.service.ts +++ b/apps/api-gateway/src/organization/organization.service.ts @@ -65,8 +65,8 @@ export class OrganizationService extends BaseService { return this.sendNats(this.serviceProxy, 'get-public-organizations', payload); } - async getPublicProfile(id: number): Promise<{ response: object }> { - const payload = { id }; + async getPublicProfile(orgSlug: string): Promise<{ response: object }> { + const payload = {orgSlug }; try { return this.sendNats(this.serviceProxy, 'get-organization-public-profile', payload); } catch (error) { diff --git a/apps/api-gateway/src/user/dto/update-user-profile.dto.ts b/apps/api-gateway/src/user/dto/update-user-profile.dto.ts index 88d1c28b5..41484636c 100644 --- a/apps/api-gateway/src/user/dto/update-user-profile.dto.ts +++ b/apps/api-gateway/src/user/dto/update-user-profile.dto.ts @@ -1,6 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumber, IsOptional, IsString} from 'class-validator'; - +import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; export class UpdateUserProfileDto { @ApiProperty() @@ -22,4 +21,9 @@ export class UpdateUserProfileDto { @IsString({ message: 'lastName should be string' }) @IsOptional() lastName?: string; + + @ApiPropertyOptional({ example: true }) + @IsBoolean({ message: 'isPublic should be boolean' }) + @IsOptional() + isPublic? = false; } \ No newline at end of file diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 0677ad8eb..6c18391e8 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -6,6 +6,7 @@ import { ApiBody, ApiForbiddenResponse, ApiOperation, + ApiParam, ApiQuery, ApiResponse, ApiTags, @@ -222,18 +223,18 @@ export class UserController { } - @Get('public-profile') + @Get('public-profiles/:username') @ApiOperation({ summary: 'Fetch user details', description: 'Fetch user details' }) - @ApiQuery({ - name: 'id', - type: Number, + @ApiParam({ + name: 'username', + type: String, required: false }) - async getPublicProfile(@User() reqUser: user, @Query('id') id: number, @Res() res: Response): Promise { - const userData = await this.userService.getPublicProfile(id); + async getPublicProfile(@Param('username') username: string, @Res() res: Response): Promise { + const userData = await this.userService.getPublicProfile(username); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, diff --git a/apps/api-gateway/src/user/user.service.ts b/apps/api-gateway/src/user/user.service.ts index 6dfa2b0f5..9e97e50ac 100644 --- a/apps/api-gateway/src/user/user.service.ts +++ b/apps/api-gateway/src/user/user.service.ts @@ -53,8 +53,8 @@ export class UserService extends BaseService { } } - async getPublicProfile(id: number): Promise<{ response: object }> { - const payload = { id }; + async getPublicProfile(username: string): Promise<{ response: object }> { + const payload = { username }; try { return this.sendNats(this.serviceProxy, 'get-user-public-profile', payload); } catch (error) { diff --git a/apps/organization/dtos/create-organization.dto.ts b/apps/organization/dtos/create-organization.dto.ts index c2fd5249e..2d54b1ef7 100644 --- a/apps/organization/dtos/create-organization.dto.ts +++ b/apps/organization/dtos/create-organization.dto.ts @@ -6,6 +6,7 @@ export class CreateOrganizationDto { description?: string; logo?: string; website?: string; + orgSlug?:string; } export class CreateUserRoleOrgDto { diff --git a/apps/organization/interfaces/organization.interface.ts b/apps/organization/interfaces/organization.interface.ts index 5a1d9667f..f8cbb9e9c 100644 --- a/apps/organization/interfaces/organization.interface.ts +++ b/apps/organization/interfaces/organization.interface.ts @@ -18,4 +18,6 @@ export interface IUpdateOrganization { orgId: string; logo?: string; website?: string; + orgSlug?: string; + isPublic?:boolean } \ No newline at end of file diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 1f44ebcf9..8339b220a 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -53,7 +53,8 @@ export class OrganizationRepository { name: createOrgDto.name, logoUrl: createOrgDto.logo, description: createOrgDto.description, - website: createOrgDto.website + website: createOrgDto.website, + orgSlug: createOrgDto.orgSlug } }); } catch (error) { @@ -78,10 +79,11 @@ export class OrganizationRepository { name: updateOrgDto.name, logoUrl: updateOrgDto.logo, description: updateOrgDto.description, - website: updateOrgDto.website + website: updateOrgDto.website, + orgSlug: updateOrgDto.orgSlug, + publicProfile: updateOrgDto.isPublic } }); - } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); @@ -257,6 +259,12 @@ export class OrganizationRepository { org_agent_type: true, ledgers: true } + }, + userOrgRoles: { + include:{ + user: true, + orgRole:true + } } } }); diff --git a/apps/organization/src/organization.controller.ts b/apps/organization/src/organization.controller.ts index 6302aa0cb..85edbe3c9 100644 --- a/apps/organization/src/organization.controller.ts +++ b/apps/organization/src/organization.controller.ts @@ -72,7 +72,7 @@ export class OrganizationController { } @MessagePattern({ cmd: 'get-organization-public-profile' }) - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: { orgSlug }): Promise { return this.organizationService.getPublicProfile(payload); } diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index ec5ddfa96..ddafd90d8 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -48,6 +48,9 @@ export class OrganizationService { throw new ConflictException(ResponseMessages.organisation.error.exists); } + const orgSlug = await this.createOrgSlug(createOrgDto.name); + createOrgDto.orgSlug = orgSlug; + const organizationDetails = await this.organizationRepository.createOrganization(createOrgDto); const ownerRoleData = await this.orgRoleService.getRole(OrgRoles.OWNER); @@ -61,6 +64,21 @@ export class OrganizationService { } } + + /** + * + * @param orgName + * @returns OrgSlug + */ + createOrgSlug(orgName: string): string { + return orgName + .toLowerCase() // Convert the input to lowercase + .replace(/\s+/g, '-') // Replace spaces with hyphens + .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens + .replace(/--+/g, '-') // Replace multiple consecutive hyphens with a single hyphen + .replace(/^-+|-+$/g, ''); // Trim hyphens from the beginning and end of the string +} + /** * * @param registerOrgDto @@ -70,6 +88,16 @@ export class OrganizationService { // eslint-disable-next-line camelcase async updateOrganization(updateOrgDto: IUpdateOrganization, userId: number): Promise { try { + + const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); + + if (organizationExist) { + throw new ConflictException(ResponseMessages.organisation.error.exists); + } + + const orgSlug = await this.createOrgSlug(updateOrgDto.name); + updateOrgDto.orgSlug = orgSlug; + const organizationDetails = await this.organizationRepository.updateOrganization(updateOrgDto); await this.userActivityService.createActivity(userId, organizationDetails.id, `${organizationDetails.name} organization updated`, 'Organization details updated successfully'); return organizationDetails; @@ -147,13 +175,17 @@ export class OrganizationService { } } - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: { orgSlug }): Promise { try { - const query = { - id: payload.id, - publicProfile: true - }; + let query = {}; + + if (payload.orgSlug) { + query = { + orgSlug: payload.orgSlug, + publicProfile: true + }; + } const organizationDetails = await this.organizationRepository.getOrganization(query); if (!organizationDetails) { diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index 051ba3bfb..ed5febe00 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -26,6 +26,7 @@ export interface InvitationsI { export interface UserEmailVerificationDto{ email:string + username?:string } export interface userInfo{ @@ -51,4 +52,5 @@ export interface UpdateUserProfile { profileImg?: string; firstName: string, lastName: string, + isPublic: boolean, } \ No newline at end of file diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index cfdfc92e7..c0e070b36 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -7,11 +7,11 @@ import { InternalServerErrorException } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase import { user } from '@prisma/client'; -import { v4 as uuidv4 } from 'uuid'; interface UserQueryOptions { id?: number; // Use the appropriate type based on your data model email?: string; // Use the appropriate type based on your data model + username?: string // Add more properties if needed for other unique identifier fields }; @@ -24,12 +24,11 @@ export class UserRepository { * @param userEmailVerificationDto * @returns user email */ - async createUser(userEmailVerificationDto: UserEmailVerificationDto): Promise { + async createUser(userEmailVerificationDto: UserEmailVerificationDto, verifyCode: string): Promise { try { - const verifyCode = uuidv4(); const saveResponse = await this.prisma.user.create({ data: { - username: userEmailVerificationDto.email, + username: userEmailVerificationDto.username, email: userEmailVerificationDto.email, verificationCode: verifyCode.toString() } @@ -99,19 +98,26 @@ export class UserRepository { * @param id * @returns User profile data */ - async getUserPublicProfile(id: number): Promise { - const queryOptions: UserQueryOptions = { - id - }; - return this.findUserForPublicProfile(queryOptions); - } + async getUserPublicProfile(username: string): Promise { + + + let queryOptions: UserQueryOptions = {}; + if (username) { + queryOptions = { + username + }; + + } + return this.findUserForPublicProfile(queryOptions); + } /** * * @Body updateUserProfile * @returns Update user profile data */ - async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { + async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { + try { const userdetails = await this.prisma.user.update({ where: { @@ -120,7 +126,8 @@ export class UserRepository { data: { profileImg: updateUserProfile.profileImg, firstName: updateUserProfile.firstName, - lastName: updateUserProfile.lastName + lastName: updateUserProfile.lastName, + publicProfile: updateUserProfile?.isPublic } }); return userdetails; @@ -233,6 +240,9 @@ export class UserRepository { }, { email: queryOptions.email + }, + { + username: queryOptions.username } ] }, @@ -253,7 +263,8 @@ export class UserRepository { name: true, description: true, logoUrl: true, - website: true + website: true, + orgSlug: true }, where:{ publicProfile: true @@ -397,6 +408,7 @@ export class UserRepository { email: true, firstName: true, lastName: true, + profileImg: true, isEmailVerified: true, clientId: false, clientSecret: false, diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 629bb9dcf..0fa931ccb 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -1,3 +1,4 @@ +import { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { Controller } from '@nestjs/common'; @@ -5,8 +6,6 @@ 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 { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; - @Controller() export class UserController { @@ -43,7 +42,7 @@ export class UserController { } @MessagePattern({ cmd: 'get-user-public-profile' }) - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: { username }): Promise { return this.userService.getPublicProfile(payload); } @MessagePattern({ cmd: 'update-user-profile' }) diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index f18f55843..5535cc342 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -32,6 +32,7 @@ import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto' import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; import { UserDevicesRepository } from '../repositories/user-device.repository'; +import { v4 as uuidv4 } from 'uuid'; @Injectable() @@ -67,7 +68,10 @@ export class UserService { throw new ConflictException(ResponseMessages.user.error.verificationAlreadySent); } - const resUser = await this.userRepository.createUser(userEmailVerificationDto); + const verifyCode = uuidv4(); + const uniqueUsername = await this.createUsername(userEmailVerificationDto.email, verifyCode); + userEmailVerificationDto.username = uniqueUsername; + const resUser = await this.userRepository.createUser(userEmailVerificationDto, verifyCode); try { await this.sendEmailForVerification(userEmailVerificationDto.email, resUser.verificationCode); @@ -82,6 +86,30 @@ export class UserService { } } + async createUsername(email: string, verifyCode: string): Promise { + + try { + // eslint-disable-next-line prefer-destructuring + const emailTrim = email.split('@')[0]; + + // Replace special characters with hyphens + const cleanedUsername = emailTrim.toLowerCase().replace(/[^a-zA-Z0-9_]/g, '-'); + + // Generate a 5-digit UUID + // eslint-disable-next-line prefer-destructuring + const uuid = verifyCode.split('-')[0]; + + // Combine cleaned username and UUID + const uniqueUsername = `${cleanedUsername}-${uuid}`; + + return uniqueUsername; + + } catch (error) { + this.logger.error(`Error in createUsername: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error.message); + } + } + /** * * @param email @@ -313,9 +341,9 @@ export class UserService { } } - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: {username }): Promise { try { - const userProfile = await this.userRepository.getUserPublicProfile(payload.id); + const userProfile = await this.userRepository.getUserPublicProfile(payload.username); if (!userProfile) { throw new NotFoundException(ResponseMessages.user.error.profileNotFound); From ff795b7163d030420ed22630a5ac3025df735628 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 12:04:32 +0530 Subject: [PATCH 006/162] refractored the GET API endoints for public profiles Signed-off-by: @nishad.shirsat --- apps/api-gateway/src/organization/organization.controller.ts | 2 +- apps/api-gateway/src/user/user.controller.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 25934cfe1..e463cb3c0 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -79,7 +79,7 @@ export class OrganizationController { * @param res * @returns Users list of organization */ - @Get('/public') + @Get() @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Get public organization list', description: 'Get users list.' }) @ApiQuery({ diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 6c18391e8..953c57cf4 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -119,7 +119,7 @@ export class UserController { * @param res * @returns Users list of organization */ - @Get('/public') + @Get('/public-profiles') @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Get users list', description: 'Get users list.' }) @ApiQuery({ From 2538ec6fbc056514fa35186d579870153d4924d7 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 12:11:13 +0530 Subject: [PATCH 007/162] refactored the organization GET API public profiles Signed-off-by: @nishad.shirsat --- apps/api-gateway/src/organization/organization.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index e463cb3c0..32d4cec98 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -79,7 +79,7 @@ export class OrganizationController { * @param res * @returns Users list of organization */ - @Get() + @Get('/public-profiles') @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Get public organization list', description: 'Get users list.' }) @ApiQuery({ From 1edeeaf8af0b516aa3d9648530bd00fd84d0f563 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 12:28:28 +0530 Subject: [PATCH 008/162] solved the regix expression grouping issue. Signed-off-by: @nishad.shirsat --- apps/organization/src/organization.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index ddafd90d8..10ed3e6cc 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -48,7 +48,7 @@ export class OrganizationService { throw new ConflictException(ResponseMessages.organisation.error.exists); } - const orgSlug = await this.createOrgSlug(createOrgDto.name); + const orgSlug = this.createOrgSlug(createOrgDto.name); createOrgDto.orgSlug = orgSlug; const organizationDetails = await this.organizationRepository.createOrganization(createOrgDto); @@ -76,7 +76,7 @@ export class OrganizationService { .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens .replace(/--+/g, '-') // Replace multiple consecutive hyphens with a single hyphen - .replace(/^-+|-+$/g, ''); // Trim hyphens from the beginning and end of the string + .replace(/[^-+|-+$]/g, ''); // Trim hyphens from the beginning and end of the string } /** From 1446b1b8b981e2acf2789e86da1a9f37a8108f47 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 13:55:32 +0530 Subject: [PATCH 009/162] resolved the comments on the PR Signed-off-by: @nishad.shirsat --- .../repositories/organization.repository.ts | 2 +- apps/organization/src/organization.service.ts | 15 ++++++--------- apps/user/repositories/user.repository.ts | 10 +++------- apps/user/src/user.controller.ts | 4 ++-- apps/user/src/user.service.ts | 6 +++--- 5 files changed, 15 insertions(+), 22 deletions(-) diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 8339b220a..f20c85219 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -245,7 +245,7 @@ export class OrganizationRepository { } } - async getOrganization(queryObject: object): Promise { + async getOrganization(queryObject: object): Promise { try { return this.prisma.organisation.findFirst({ where: { diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 10ed3e6cc..5f1daee51 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -175,17 +175,14 @@ export class OrganizationService { } } - async getPublicProfile(payload: { orgSlug }): Promise { + async getPublicProfile(payload: { orgSlug: string }): Promise { + const {orgSlug} = payload; try { - let query = {}; - - if (payload.orgSlug) { - query = { - orgSlug: payload.orgSlug, - publicProfile: true - }; - } + const query = { + orgSlug, + publicProfile: true + }; const organizationDetails = await this.organizationRepository.getOrganization(query); if (!organizationDetails) { diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index c0e070b36..0743b628b 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -99,15 +99,11 @@ export class UserRepository { * @returns User profile data */ async getUserPublicProfile(username: string): Promise { - - - let queryOptions: UserQueryOptions = {}; - if (username) { - queryOptions = { + + const queryOptions: UserQueryOptions = { username }; - } return this.findUserForPublicProfile(queryOptions); } @@ -116,7 +112,7 @@ export class UserRepository { * @Body updateUserProfile * @returns Update user profile data */ - async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { + async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { try { const userdetails = await this.prisma.user.update({ diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 0fa931ccb..9d94f00e0 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -1,4 +1,4 @@ -import { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; +import { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, UserI, userInfo } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { Controller } from '@nestjs/common'; @@ -42,7 +42,7 @@ export class UserController { } @MessagePattern({ cmd: 'get-user-public-profile' }) - async getPublicProfile(payload: { username }): Promise { + async getPublicProfile(payload: { username }): Promise { return this.userService.getPublicProfile(payload); } @MessagePattern({ cmd: 'update-user-profile' }) diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 5535cc342..96753ee0c 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -27,7 +27,7 @@ import { sendEmail } from '@credebl/common/send-grid-helper-file'; import { user } from '@prisma/client'; import { Inject } from '@nestjs/common'; import { HttpException } from '@nestjs/common'; -import { AddPasskeyDetails, InvitationsI, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; +import { AddPasskeyDetails, InvitationsI, UpdateUserProfile, UserEmailVerificationDto, UserI, userInfo } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; @@ -341,7 +341,7 @@ export class UserService { } } - async getPublicProfile(payload: {username }): Promise { + async getPublicProfile(payload: { username }): Promise { try { const userProfile = await this.userRepository.getUserPublicProfile(payload.username); @@ -356,7 +356,7 @@ export class UserService { } } - async updateUserProfile(updateUserProfileDto: UpdateUserProfile): Promise { + async updateUserProfile(updateUserProfileDto: UpdateUserProfile): Promise { try { return this.userRepository.updateUserProfile(updateUserProfileDto); } catch (error) { From 574004a7110855df6c937a06f9bc17c4ed7eb137 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Fri, 15 Sep 2023 14:24:09 +0530 Subject: [PATCH 010/162] feat: implemented username & orgslug feature for public profiles (#85) * worked on the username & orgslug feature for public profiles Signed-off-by: @nishad.shirsat * refractored the GET API endoints for public profiles Signed-off-by: @nishad.shirsat * refactored the organization GET API public profiles Signed-off-by: @nishad.shirsat * solved the regix expression grouping issue. Signed-off-by: @nishad.shirsat * resolved the comments on the PR Signed-off-by: @nishad.shirsat --------- Signed-off-by: @nishad.shirsat --- .../dtos/update-organization-dto.ts | 7 +++- .../organization/organization.controller.ts | 17 +++++---- .../src/organization/organization.service.ts | 4 +- .../src/user/dto/update-user-profile.dto.ts | 8 +++- apps/api-gateway/src/user/user.controller.ts | 15 ++++---- apps/api-gateway/src/user/user.service.ts | 4 +- .../dtos/create-organization.dto.ts | 1 + .../interfaces/organization.interface.ts | 2 + .../repositories/organization.repository.ts | 16 ++++++-- .../src/organization.controller.ts | 2 +- apps/organization/src/organization.service.ts | 33 +++++++++++++++- apps/user/interfaces/user.interface.ts | 2 + apps/user/repositories/user.repository.ts | 34 ++++++++++------- apps/user/src/user.controller.ts | 5 +-- apps/user/src/user.service.ts | 38 ++++++++++++++++--- 15 files changed, 138 insertions(+), 50 deletions(-) diff --git a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts index 44569b38d..1229ddc58 100644 --- a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts +++ b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts @@ -1,5 +1,5 @@ import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumber, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; @@ -37,4 +37,9 @@ export class UpdateOrganizationDto { @IsOptional() website: string; + @ApiPropertyOptional({ example: true }) + @IsBoolean({ message: 'isPublic should be boolean' }) + @IsOptional() + isPublic? = false; + } \ No newline at end of file diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 05fafc1da..32d4cec98 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -1,4 +1,4 @@ -import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { CommonService } from '@credebl/common'; import { Controller, Get, Put, Param, UseGuards, UseFilters } from '@nestjs/common'; import { OrganizationService } from './organization.service'; @@ -79,7 +79,7 @@ export class OrganizationController { * @param res * @returns Users list of organization */ - @Get('/public') + @Get('/public-profiles') @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Get public organization list', description: 'Get users list.' }) @ApiQuery({ @@ -109,18 +109,19 @@ export class OrganizationController { return res.status(HttpStatus.OK).json(finalResponse); } - @Get('public-profile') + @Get('public-profiles/:orgSlug') @ApiOperation({ summary: 'Fetch user details', description: 'Fetch user details' }) - @ApiQuery({ - name: 'id', - type: Number, + + @ApiParam({ + name: 'orgSlug', + type: String, required: false }) - async getPublicProfile(@User() reqUser: user, @Query('id') id: number, @Res() res: Response): Promise { - const userData = await this.organizationService.getPublicProfile(id); + async getPublicProfile(@Param('orgSlug') orgSlug: string, @Res() res: Response): Promise { + const userData = await this.organizationService.getPublicProfile(orgSlug); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, diff --git a/apps/api-gateway/src/organization/organization.service.ts b/apps/api-gateway/src/organization/organization.service.ts index eb626e6d1..ba0d6f96d 100644 --- a/apps/api-gateway/src/organization/organization.service.ts +++ b/apps/api-gateway/src/organization/organization.service.ts @@ -65,8 +65,8 @@ export class OrganizationService extends BaseService { return this.sendNats(this.serviceProxy, 'get-public-organizations', payload); } - async getPublicProfile(id: number): Promise<{ response: object }> { - const payload = { id }; + async getPublicProfile(orgSlug: string): Promise<{ response: object }> { + const payload = {orgSlug }; try { return this.sendNats(this.serviceProxy, 'get-organization-public-profile', payload); } catch (error) { diff --git a/apps/api-gateway/src/user/dto/update-user-profile.dto.ts b/apps/api-gateway/src/user/dto/update-user-profile.dto.ts index 88d1c28b5..41484636c 100644 --- a/apps/api-gateway/src/user/dto/update-user-profile.dto.ts +++ b/apps/api-gateway/src/user/dto/update-user-profile.dto.ts @@ -1,6 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumber, IsOptional, IsString} from 'class-validator'; - +import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; export class UpdateUserProfileDto { @ApiProperty() @@ -22,4 +21,9 @@ export class UpdateUserProfileDto { @IsString({ message: 'lastName should be string' }) @IsOptional() lastName?: string; + + @ApiPropertyOptional({ example: true }) + @IsBoolean({ message: 'isPublic should be boolean' }) + @IsOptional() + isPublic? = false; } \ No newline at end of file diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 0677ad8eb..953c57cf4 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -6,6 +6,7 @@ import { ApiBody, ApiForbiddenResponse, ApiOperation, + ApiParam, ApiQuery, ApiResponse, ApiTags, @@ -118,7 +119,7 @@ export class UserController { * @param res * @returns Users list of organization */ - @Get('/public') + @Get('/public-profiles') @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Get users list', description: 'Get users list.' }) @ApiQuery({ @@ -222,18 +223,18 @@ export class UserController { } - @Get('public-profile') + @Get('public-profiles/:username') @ApiOperation({ summary: 'Fetch user details', description: 'Fetch user details' }) - @ApiQuery({ - name: 'id', - type: Number, + @ApiParam({ + name: 'username', + type: String, required: false }) - async getPublicProfile(@User() reqUser: user, @Query('id') id: number, @Res() res: Response): Promise { - const userData = await this.userService.getPublicProfile(id); + async getPublicProfile(@Param('username') username: string, @Res() res: Response): Promise { + const userData = await this.userService.getPublicProfile(username); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, diff --git a/apps/api-gateway/src/user/user.service.ts b/apps/api-gateway/src/user/user.service.ts index 6dfa2b0f5..9e97e50ac 100644 --- a/apps/api-gateway/src/user/user.service.ts +++ b/apps/api-gateway/src/user/user.service.ts @@ -53,8 +53,8 @@ export class UserService extends BaseService { } } - async getPublicProfile(id: number): Promise<{ response: object }> { - const payload = { id }; + async getPublicProfile(username: string): Promise<{ response: object }> { + const payload = { username }; try { return this.sendNats(this.serviceProxy, 'get-user-public-profile', payload); } catch (error) { diff --git a/apps/organization/dtos/create-organization.dto.ts b/apps/organization/dtos/create-organization.dto.ts index c2fd5249e..2d54b1ef7 100644 --- a/apps/organization/dtos/create-organization.dto.ts +++ b/apps/organization/dtos/create-organization.dto.ts @@ -6,6 +6,7 @@ export class CreateOrganizationDto { description?: string; logo?: string; website?: string; + orgSlug?:string; } export class CreateUserRoleOrgDto { diff --git a/apps/organization/interfaces/organization.interface.ts b/apps/organization/interfaces/organization.interface.ts index 5a1d9667f..f8cbb9e9c 100644 --- a/apps/organization/interfaces/organization.interface.ts +++ b/apps/organization/interfaces/organization.interface.ts @@ -18,4 +18,6 @@ export interface IUpdateOrganization { orgId: string; logo?: string; website?: string; + orgSlug?: string; + isPublic?:boolean } \ No newline at end of file diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 1f44ebcf9..f20c85219 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -53,7 +53,8 @@ export class OrganizationRepository { name: createOrgDto.name, logoUrl: createOrgDto.logo, description: createOrgDto.description, - website: createOrgDto.website + website: createOrgDto.website, + orgSlug: createOrgDto.orgSlug } }); } catch (error) { @@ -78,10 +79,11 @@ export class OrganizationRepository { name: updateOrgDto.name, logoUrl: updateOrgDto.logo, description: updateOrgDto.description, - website: updateOrgDto.website + website: updateOrgDto.website, + orgSlug: updateOrgDto.orgSlug, + publicProfile: updateOrgDto.isPublic } }); - } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); @@ -243,7 +245,7 @@ export class OrganizationRepository { } } - async getOrganization(queryObject: object): Promise { + async getOrganization(queryObject: object): Promise { try { return this.prisma.organisation.findFirst({ where: { @@ -257,6 +259,12 @@ export class OrganizationRepository { org_agent_type: true, ledgers: true } + }, + userOrgRoles: { + include:{ + user: true, + orgRole:true + } } } }); diff --git a/apps/organization/src/organization.controller.ts b/apps/organization/src/organization.controller.ts index 6302aa0cb..85edbe3c9 100644 --- a/apps/organization/src/organization.controller.ts +++ b/apps/organization/src/organization.controller.ts @@ -72,7 +72,7 @@ export class OrganizationController { } @MessagePattern({ cmd: 'get-organization-public-profile' }) - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: { orgSlug }): Promise { return this.organizationService.getPublicProfile(payload); } diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index ec5ddfa96..5f1daee51 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -48,6 +48,9 @@ export class OrganizationService { throw new ConflictException(ResponseMessages.organisation.error.exists); } + const orgSlug = this.createOrgSlug(createOrgDto.name); + createOrgDto.orgSlug = orgSlug; + const organizationDetails = await this.organizationRepository.createOrganization(createOrgDto); const ownerRoleData = await this.orgRoleService.getRole(OrgRoles.OWNER); @@ -61,6 +64,21 @@ export class OrganizationService { } } + + /** + * + * @param orgName + * @returns OrgSlug + */ + createOrgSlug(orgName: string): string { + return orgName + .toLowerCase() // Convert the input to lowercase + .replace(/\s+/g, '-') // Replace spaces with hyphens + .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens + .replace(/--+/g, '-') // Replace multiple consecutive hyphens with a single hyphen + .replace(/[^-+|-+$]/g, ''); // Trim hyphens from the beginning and end of the string +} + /** * * @param registerOrgDto @@ -70,6 +88,16 @@ export class OrganizationService { // eslint-disable-next-line camelcase async updateOrganization(updateOrgDto: IUpdateOrganization, userId: number): Promise { try { + + const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); + + if (organizationExist) { + throw new ConflictException(ResponseMessages.organisation.error.exists); + } + + const orgSlug = await this.createOrgSlug(updateOrgDto.name); + updateOrgDto.orgSlug = orgSlug; + const organizationDetails = await this.organizationRepository.updateOrganization(updateOrgDto); await this.userActivityService.createActivity(userId, organizationDetails.id, `${organizationDetails.name} organization updated`, 'Organization details updated successfully'); return organizationDetails; @@ -147,11 +175,12 @@ export class OrganizationService { } } - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: { orgSlug: string }): Promise { + const {orgSlug} = payload; try { const query = { - id: payload.id, + orgSlug, publicProfile: true }; diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index 051ba3bfb..ed5febe00 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -26,6 +26,7 @@ export interface InvitationsI { export interface UserEmailVerificationDto{ email:string + username?:string } export interface userInfo{ @@ -51,4 +52,5 @@ export interface UpdateUserProfile { profileImg?: string; firstName: string, lastName: string, + isPublic: boolean, } \ No newline at end of file diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index cfdfc92e7..0743b628b 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -7,11 +7,11 @@ import { InternalServerErrorException } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase import { user } from '@prisma/client'; -import { v4 as uuidv4 } from 'uuid'; interface UserQueryOptions { id?: number; // Use the appropriate type based on your data model email?: string; // Use the appropriate type based on your data model + username?: string // Add more properties if needed for other unique identifier fields }; @@ -24,12 +24,11 @@ export class UserRepository { * @param userEmailVerificationDto * @returns user email */ - async createUser(userEmailVerificationDto: UserEmailVerificationDto): Promise { + async createUser(userEmailVerificationDto: UserEmailVerificationDto, verifyCode: string): Promise { try { - const verifyCode = uuidv4(); const saveResponse = await this.prisma.user.create({ data: { - username: userEmailVerificationDto.email, + username: userEmailVerificationDto.username, email: userEmailVerificationDto.email, verificationCode: verifyCode.toString() } @@ -99,19 +98,22 @@ export class UserRepository { * @param id * @returns User profile data */ - async getUserPublicProfile(id: number): Promise { - const queryOptions: UserQueryOptions = { - id - }; - return this.findUserForPublicProfile(queryOptions); - } + async getUserPublicProfile(username: string): Promise { + + const queryOptions: UserQueryOptions = { + username + }; + + return this.findUserForPublicProfile(queryOptions); + } /** * * @Body updateUserProfile * @returns Update user profile data */ - async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { + async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { + try { const userdetails = await this.prisma.user.update({ where: { @@ -120,7 +122,8 @@ export class UserRepository { data: { profileImg: updateUserProfile.profileImg, firstName: updateUserProfile.firstName, - lastName: updateUserProfile.lastName + lastName: updateUserProfile.lastName, + publicProfile: updateUserProfile?.isPublic } }); return userdetails; @@ -233,6 +236,9 @@ export class UserRepository { }, { email: queryOptions.email + }, + { + username: queryOptions.username } ] }, @@ -253,7 +259,8 @@ export class UserRepository { name: true, description: true, logoUrl: true, - website: true + website: true, + orgSlug: true }, where:{ publicProfile: true @@ -397,6 +404,7 @@ export class UserRepository { email: true, firstName: true, lastName: true, + profileImg: true, isEmailVerified: true, clientId: false, clientSecret: false, diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 629bb9dcf..9d94f00e0 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -1,3 +1,4 @@ +import { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, UserI, userInfo } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { Controller } from '@nestjs/common'; @@ -5,8 +6,6 @@ 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 { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; - @Controller() export class UserController { @@ -43,7 +42,7 @@ export class UserController { } @MessagePattern({ cmd: 'get-user-public-profile' }) - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: { username }): Promise { return this.userService.getPublicProfile(payload); } @MessagePattern({ cmd: 'update-user-profile' }) diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index f18f55843..96753ee0c 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -27,11 +27,12 @@ import { sendEmail } from '@credebl/common/send-grid-helper-file'; import { user } from '@prisma/client'; import { Inject } from '@nestjs/common'; import { HttpException } from '@nestjs/common'; -import { AddPasskeyDetails, InvitationsI, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; +import { AddPasskeyDetails, InvitationsI, UpdateUserProfile, UserEmailVerificationDto, UserI, userInfo } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; import { UserDevicesRepository } from '../repositories/user-device.repository'; +import { v4 as uuidv4 } from 'uuid'; @Injectable() @@ -67,7 +68,10 @@ export class UserService { throw new ConflictException(ResponseMessages.user.error.verificationAlreadySent); } - const resUser = await this.userRepository.createUser(userEmailVerificationDto); + const verifyCode = uuidv4(); + const uniqueUsername = await this.createUsername(userEmailVerificationDto.email, verifyCode); + userEmailVerificationDto.username = uniqueUsername; + const resUser = await this.userRepository.createUser(userEmailVerificationDto, verifyCode); try { await this.sendEmailForVerification(userEmailVerificationDto.email, resUser.verificationCode); @@ -82,6 +86,30 @@ export class UserService { } } + async createUsername(email: string, verifyCode: string): Promise { + + try { + // eslint-disable-next-line prefer-destructuring + const emailTrim = email.split('@')[0]; + + // Replace special characters with hyphens + const cleanedUsername = emailTrim.toLowerCase().replace(/[^a-zA-Z0-9_]/g, '-'); + + // Generate a 5-digit UUID + // eslint-disable-next-line prefer-destructuring + const uuid = verifyCode.split('-')[0]; + + // Combine cleaned username and UUID + const uniqueUsername = `${cleanedUsername}-${uuid}`; + + return uniqueUsername; + + } catch (error) { + this.logger.error(`Error in createUsername: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error.message); + } + } + /** * * @param email @@ -313,9 +341,9 @@ export class UserService { } } - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: { username }): Promise { try { - const userProfile = await this.userRepository.getUserPublicProfile(payload.id); + const userProfile = await this.userRepository.getUserPublicProfile(payload.username); if (!userProfile) { throw new NotFoundException(ResponseMessages.user.error.profileNotFound); @@ -328,7 +356,7 @@ export class UserService { } } - async updateUserProfile(updateUserProfileDto: UpdateUserProfile): Promise { + async updateUserProfile(updateUserProfileDto: UpdateUserProfile): Promise { try { return this.userRepository.updateUserProfile(updateUserProfileDto); } catch (error) { From 6f870c612681e70a2d37a935ce4ece85f08d51bf Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 16:03:29 +0530 Subject: [PATCH 011/162] chnaged regular expression for orgSLug Signed-off-by: @nishad.shirsat --- apps/organization/src/organization.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 5f1daee51..802d9473f 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -75,8 +75,7 @@ export class OrganizationService { .toLowerCase() // Convert the input to lowercase .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens - .replace(/--+/g, '-') // Replace multiple consecutive hyphens with a single hyphen - .replace(/[^-+|-+$]/g, ''); // Trim hyphens from the beginning and end of the string + .replace(/--+/g, '-'); // Replace multiple consecutive hyphens with a single hyphen } /** From 9f5ca3197e6a699292704f40a911492ac8427053 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Fri, 15 Sep 2023 19:25:28 +0530 Subject: [PATCH 012/162] implemented org and user to bydefault public (#87) Signed-off-by: @nishad.shirsat --- apps/organization/repositories/organization.repository.ts | 4 +++- apps/organization/src/organization.service.ts | 4 ++-- apps/user/repositories/user.repository.ts | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index f20c85219..0ebb10fb3 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -54,7 +54,9 @@ export class OrganizationRepository { logoUrl: createOrgDto.logo, description: createOrgDto.description, website: createOrgDto.website, - orgSlug: createOrgDto.orgSlug + orgSlug: createOrgDto.orgSlug, + publicProfile: true + } }); } catch (error) { diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 802d9473f..fce88b3bc 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -89,8 +89,8 @@ export class OrganizationService { try { const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); - - if (organizationExist) { + + if (organizationExist && organizationExist.id !== Number(updateOrgDto.orgId)) { throw new ConflictException(ResponseMessages.organisation.error.exists); } diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 0743b628b..e64f5033a 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -30,7 +30,8 @@ export class UserRepository { data: { username: userEmailVerificationDto.username, email: userEmailVerificationDto.email, - verificationCode: verifyCode.toString() + verificationCode: verifyCode.toString(), + publicProfile: true } }); From 32654c6b3fc96d343f59a0a1bbeb8d4a97802c6d Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:51:55 +0530 Subject: [PATCH 013/162] refactor: API refactor in user, organization, auth and agent module (#82) * docs: add CONTRIBUTING guidelines Signed-off-by: KulkarniShashank * docs: add company name in LICESE file Signed-off-by: Ajay Jadhav Signed-off-by: KulkarniShashank * merge dev branch to main (#77) * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw * feat:implemented add passke Signed-off-by: tipusinghaw * fix: login error message Signed-off-by: tipusinghaw --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank * Included credebl-master-table json file in the .gitignore Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank * fix: API refactor in user, organization, auth and agent module Signed-off-by: KulkarniShashank * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * feat:implemented add passke Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * fix: login error message Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * Set Validation for the user login Signed-off-by: KulkarniShashank * Removed unnecessarily conditions in user dto Signed-off-by: KulkarniShashank * added the role matrix in the organization, user and agent module Signed-off-by: KulkarniShashank * Added the error handling in the API-gateway Signed-off-by: KulkarniShashank * Added role guard in user and organization Signed-off-by: KulkarniShashank * Error handling in the user signin functionality Signed-off-by: KulkarniShashank * Function name changes in the singin functionality Signed-off-by: KulkarniShashank * Added activity log success in user Signed-off-by: KulkarniShashank * Solved the bug regarding in organization update functionality Signed-off-by: KulkarniShashank * added role guard in user invitation Signed-off-by: KulkarniShashank * Error handling on api-gateway dto Signed-off-by: KulkarniShashank * Added veriable for the seed in agent-service Signed-off-by: KulkarniShashank * Added veriable for the seed on globaly in agent-service Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: Ajay Jadhav Signed-off-by: tipusinghaw Signed-off-by: @nishad.shirsat Co-authored-by: Ajay Jadhav Co-authored-by: Ajay Jadhav Co-authored-by: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Co-authored-by: @nishad.shirsat Co-authored-by: tipusinghaw --- .env.sample | 58 +--- .gitignore | 2 +- .../src/agent-service.service.ts | 20 +- apps/api-gateway/common/exception-handler.ts | 48 ++- .../agent-service/agent-service.controller.ts | 83 +++-- .../agent-service/dto/agent-service.dto.ts | 19 +- .../agent-service/dto/create-tenant.dto.ts | 15 +- .../api-gateway/src/authz/authz.controller.ts | 121 ++++++- apps/api-gateway/src/authz/authz.service.ts | 25 +- .../src/authz/guards/org-roles.guard.ts | 17 +- .../src/dtos/email-validator.dto.ts | 12 + .../organization/dtos/send-invitation.dto.ts | 6 +- .../dtos/update-organization-dto.ts | 8 +- .../dtos/update-user-roles.dto.ts | 14 +- .../organization/organization.controller.ts | 265 +++++++++------ .../src/organization/organization.service.ts | 53 ++- .../user/dto/accept-reject-invitation.dto.ts | 6 +- apps/api-gateway/src/user/dto/add-user.dto.ts | 10 +- .../src/user/dto/login-user.dto.ts | 31 +- .../src/user/dto/update-user-profile.dto.ts | 10 +- apps/api-gateway/src/user/user.controller.ts | 305 +++++------------- apps/api-gateway/src/user/user.service.ts | 83 +---- .../repositories/organization.repository.ts | 24 +- .../src/organization.controller.ts | 4 +- apps/organization/src/organization.service.ts | 55 ++-- apps/user/interfaces/user.interface.ts | 1 + apps/user/src/user.controller.ts | 30 +- apps/user/src/user.service.ts | 97 +++--- libs/common/src/response-messages/index.ts | 12 +- .../prisma/data/credebl-master-table.json | 85 +++++ 30 files changed, 816 insertions(+), 703 deletions(-) create mode 100644 apps/api-gateway/src/dtos/email-validator.dto.ts create mode 100644 libs/prisma-service/prisma/data/credebl-master-table.json diff --git a/.env.sample b/.env.sample index e86573a16..1739e22e2 100644 --- a/.env.sample +++ b/.env.sample @@ -1,5 +1,10 @@ MODE=DEV + +SUPABASE_URL= // Please specify your Supabase Url +SUPABASE_KEY= // Please specify your Supabase Anon key +SUPABASE_JWT_SECRET= // Please specify your Supabase jwt secret + API_GATEWAY_PROTOCOL=http API_GATEWAY_HOST='0.0.0.0' API_GATEWAY_PORT=5000 @@ -10,7 +15,6 @@ AGENT_HOST=username@0.0.0.0 // Please specify your agent host VM and IP addres AWS_ACCOUNT_ID=xxxxx // Please provide your AWS account Id S3_BUCKET_ARN=arn:aws:s3:::xxxxx // Please provide your AWS bucket arn -TENANT_EMAIL_LOGO=credebl.jpg API_ENDPOINT=localhost:5000 #Use your local machine IP Address & PORT API_ENDPOINT_PORT=5000 @@ -20,68 +24,20 @@ NATS_HOST='0.0.0.0' NATS_PORT=4222 NATS_URL=nats://0.0.0.0:4222 -REDIS_HOST='localhost' # Use IP Address -REDIS_PORT=6379 - -POSTGRES_HOST=localhost # Use IP Address -POSTGRES_PORT=5432 -POSTGRES_USER=postgres -POSTGRES_PASSWORD=xxxxxxxx -POSTGRES_DATABASE=postgres - -POSTGRES_MEDIATOR_DATABASE='mediator_agent' -POSTGRES_MEDIATOR_PORT=5431 - -MEDIATOR_AGENT_LABEL=MediatorAgent -MEDIATOR_AGENT_ENDPOINT='' - SENDGRID_API_KEY=xxxxxxxxxxxxxx // Please provide your sendgrid API key FRONT_END_URL=http://localhost:3000 -FILE_SERVER=credebl-dev-mediator-indypool -FILE_SERVER_PORT=8081 -FILE_SERVER_USER=credebl -FILE_SERVER_HOST=0.0.0.0 - -REMOTE_FILE_DIR='/opt/cb-tails-file-server/tails/tails-files/' -ACCESSIBLE_FILE_LOCATION='tails-files' - -LOCAL_FILE_SERVER=/opt/credebl-platform/tails-files/ -GCLOUD_ENGINE_PATH=/home/credebl/.ssh/google_compute_engine - AFJ_AGENT_SPIN_UP=/apps/agent-provisioning/AFJ/scripts/start_agent.sh -AGENT_SPIN_UP_FILE=/agent-spinup/scripts/start_agent.sh -LIBINDY_KEY=CE7709D068DB5E88 - -AGENT_RESTART_SCRIPT=/agent-spinup/scripts/manage_agent.sh -AGENT_STATUS_SCRIPT=/agent-spinup/scripts/status_agent.sh - -WALLET_PROVISION_SCRIPT=/agent-spinup/scripts/wallet_provision.sh WALLET_STORAGE_HOST=localhost # Use IP Address WALLET_STORAGE_PORT=5432 WALLET_STORAGE_USER=postgres WALLET_STORAGE_PASSWORD=xxxxxx -KEYCLOAK_DOMAIN=http://localhost:8089/auth/ -KEYCLOAK_ADMIN_URL=http://localhost:8089 -KEYCLOAK_MASTER_REALM=master -KEYCLOAK_CREDEBL_REALM=credebl-platform -KEYCLOAK_MANAGEMENT_CLIENT_ID=adminClient -KEYCLOAK_MANAGEMENT_CLIENT_SECRET=xxxxxx-xxxx-xxxx-xxxx-xxxxxx #Refer from ADMIN CONSOLE of your Keycloak -KEYCLOAK_MANAGEMENT_ADEYA_CLIENT_ID=adeyaClient -KEYCLOAK_MANAGEMENT_ADEYA_CLIENT_SECRET=xxxxxx-xxxx-xxxx-xxxx-xxxxxx #Refer from ADMIN CONSOLE of your Keycloak - -FILE_UPLOAD_PATH_TENANT= /uploadedFiles/tenant-logo/ - -CRYPTO_PRIVATE_KEY=xxxxx-xxxxx-xxxxx-xxxxx -PLATFORM_URL=https://dev.credebl.com -KEYCLOAK_URL=http://localhost:8089 -PLATFORM_PROFILE_MODE=DEV +CRYPTO_PRIVATE_KEY=xxxxx-xxxxx-xxxxx-xxxxx #It should be same as studio UI AFJ_VERSION=afj-0.4.0:latest -INVOICE_PDF_URL=./invoice-pdf FIDO_API_ENDPOINT=http://localhost:8000 # Host:port of your FIDO (WebAuthn) Server @@ -91,6 +47,7 @@ PLATFORM_SEED= // The seed should consist of 32 characters. PLATFORM_ID= AFJ_AGENT_ENDPOINT_PATH=/apps/agent-provisioning/AFJ/endpoints/ +DATABASE_URL="postgresql://postgres:xxxxxx@localhost:5432/postgres?schema=public" #Provide supabase postgres url and Use the correct user/pwd, IP Address # This was inserted by prisma init: # Environment variables declared in this file are automatically made available to Prisma. @@ -99,7 +56,6 @@ AFJ_AGENT_ENDPOINT_PATH=/apps/agent-provisioning/AFJ/endpoints/ # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB (Preview). # See the documentation for all the connection string options: https://pris.ly/d/connection-strings -DATABASE_URL="postgresql://postgres:xxxxxx@localhost:5432/postgres?schema=public" #Use the correct user/pwd, IP Address # enable only prisma:engine-level debugging output export DEBUG="prisma:engine" diff --git a/.gitignore b/.gitignore index 4281aedfc..77d23f742 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ uploadedFiles sonar-project.properties .scannerwork/* coverage -libs/prisma-service/prisma/data/credebl-master-table.json +libs/prisma-service/prisma/data/credebl-master-table.json \ No newline at end of file diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index b1a818b26..893124ff0 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -122,7 +122,7 @@ export class AgentServiceService { return internalIp; } catch (error) { this.logger.error(`error in valid internal ip : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -573,7 +573,7 @@ export class AgentServiceService { }); socket.emit('error-in-wallet-creation-process', { clientId: payload.clientSocketId, error }); } - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -739,7 +739,7 @@ export class AgentServiceService { return data; } catch (error) { this.logger.error(`Error in connection Invitation in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -761,7 +761,7 @@ export class AgentServiceService { return getProofPresentationsData; } catch (error) { this.logger.error(`Error in proof presentations in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -783,7 +783,7 @@ export class AgentServiceService { return getProofPresentationById; } catch (error) { this.logger.error(`Error in proof presentation by id in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -805,7 +805,7 @@ export class AgentServiceService { return sendProofRequest; } catch (error) { this.logger.error(`Error in send proof request in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -817,7 +817,7 @@ export class AgentServiceService { return verifyPresentation; } catch (error) { this.logger.error(`Error in verify proof presentation in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -860,7 +860,7 @@ export class AgentServiceService { } catch (error) { this.logger.error(`Agent health details : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -872,7 +872,7 @@ export class AgentServiceService { return sendProofRequest; } catch (error) { this.logger.error(`Error in send out of band proof request in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -884,7 +884,7 @@ export class AgentServiceService { return getProofFormData; } catch (error) { this.logger.error(`Error in get proof form data in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } } diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index 45a87a1cb..13c102539 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -16,12 +16,48 @@ export class CustomExceptionFilter extends BaseExceptionFilter { exception.message = 'Oops! Something went wrong. Please try again'; } - const errorResponse = { - statusCode: status, - message: exception.message || 'Internal server error', - error: exception.message - }; + let errorResponse; + if (exception && exception["statusCode"] === HttpStatus.INTERNAL_SERVER_ERROR) { + errorResponse = { + statusCode: status, + message: 'Oops! Something went wrong. Please try again', + error: 'Oops! Something went wrong. Please try again' + }; + } 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' + }; + } else { + if (exception && exception["response"] && exception.message) { - response.status(status).json(errorResponse); + if (Array.isArray(exception["response"].message)) { + exception["response"].message.forEach((msg) => { + errorResponse = { + statusCode: exception["statusCode"] ? exception["statusCode"] : status, + message: msg || 'Internal server error', + error: msg || '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' + }; + + } + } + + response.status(errorResponse.statusCode).json(errorResponse); } } \ No newline at end of file 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 55a083404..d4b2890b6 100644 --- a/apps/api-gateway/src/agent-service/agent-service.controller.ts +++ b/apps/api-gateway/src/agent-service/agent-service.controller.ts @@ -11,10 +11,10 @@ import { HttpStatus, Res, Get, - Query, - UseFilters + UseFilters, + Param } from '@nestjs/common'; -import { ApiTags, ApiResponse, ApiOperation, ApiUnauthorizedResponse, ApiForbiddenResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger'; +import { ApiTags, ApiResponse, ApiOperation, ApiUnauthorizedResponse, ApiForbiddenResponse, ApiBearerAuth } from '@nestjs/swagger'; import { GetUser } from '../authz/decorators/get-user.decorator'; import { AuthGuard } from '@nestjs/passport'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; @@ -30,43 +30,74 @@ import { user } from '@prisma/client'; import { CreateTenantDto } from './dto/create-tenant.dto'; import { User } from '../authz/decorators/user.decorator'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; +import { Roles } from '../authz/decorators/roles.decorator'; +import { OrgRoles } from 'libs/org-roles/enums'; +import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; +const seedLength = 32; @UseFilters(CustomExceptionFilter) -@Controller('agent-service') +@Controller() @ApiTags('agents') -@UseGuards(AuthGuard('jwt')) @ApiBearerAuth() @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) export class AgentController { constructor(private readonly agentService: AgentService) { } - private readonly logger = new Logger(); + @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: number, @Res() res: Response): Promise { + const agentData = await this.agentService.getAgentHealth(reqUser, orgId); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.agent.success.health, + data: agentData.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } + /** * * @param agentSpinupDto * @param user * @returns */ - @Post('/spinup') + @Post('/orgs/:orgId/agents/spinup') @ApiOperation({ summary: 'Agent spinup', description: 'Create a new agent spin up.' }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) async agentSpinup( @Body() agentSpinupDto: AgentSpinupDto, + @Param('orgId') orgId: number, @GetUser() user: user, @Res() res: Response ): Promise>> { + if (seedLength !== agentSpinupDto.seed.length) { + throw new BadRequestException(`seed must be at most 32 characters.`); + } + 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.log(`**** Spin up the agent...${JSON.stringify(agentSpinupDto)}`); + + agentSpinupDto.orgId = orgId; const agentDetails = await this.agentService.agentSpinup(agentSpinupDto, user); const finalResponse: IResponseType = { @@ -78,17 +109,27 @@ export class AgentController { return res.status(HttpStatus.CREATED).json(finalResponse); } - @Post('/tenant') + @Post('/orgs/:orgId/agents/wallet') @ApiOperation({ summary: 'Shared Agent', description: 'Create a shared agent.' }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) async createTenant( + @Param('orgId') orgId: number, @Body() createTenantDto: CreateTenantDto, @GetUser() user: user, @Res() res: Response ): Promise { + + createTenantDto.orgId = orgId; + + if (seedLength !== createTenantDto.seed.length) { + throw new BadRequestException(`seed must be at most 32 characters.`); + } + const tenantDetails = await this.agentService.createTenant(createTenantDto, user); const finalResponse: IResponseType = { @@ -99,28 +140,4 @@ export class AgentController { return res.status(HttpStatus.CREATED).json(finalResponse); } - - @Get('/health') - @ApiOperation({ - summary: 'Fetch agent details', - description: 'Fetch agent health details' - }) - @ApiQuery({ - name: 'orgId', - type: Number, - required: false - }) - async getAgentHealth(@User() reqUser: user, @Query('orgId') orgId: number, @Res() res: Response): Promise { - const agentData = await this.agentService.getAgentHealth(reqUser, orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.agent.success.health, - data: agentData.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - - } - -} +} \ No newline at end of file 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 c05383e9e..d8c449095 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 @@ -1,10 +1,12 @@ import { trim } from '@credebl/common/cast.helper'; -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, Matches, MaxLength, MinLength } from 'class-validator'; const regex = /^[a-zA-Z0-9 ]*$/; export class AgentSpinupDto { + orgId: number; + @ApiProperty() @Transform(({ value }) => trim(value)) @IsNotEmpty({ message: 'walletName is required'}) @@ -31,28 +33,28 @@ export class AgentSpinupDto { @Matches(/^\S*$/, { message: 'Spaces are not allowed in seed' }) - seed: string; - - @ApiProperty() - @IsNumber() - orgId: number; + seed: string; @ApiProperty() + @ApiPropertyOptional() @IsOptional() @IsNumber() ledgerId?: number; @ApiProperty() @IsOptional() + @ApiPropertyOptional() clientSocketId?: string; @ApiProperty() @IsOptional() - @IsBoolean() + @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.' }) @@ -62,10 +64,11 @@ export class AgentSpinupDto { @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; -} +} \ 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 6ed0aa6ad..73161436e 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 @@ -1,13 +1,13 @@ import { trim } from '@credebl/common/cast.helper'; -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { IsNotEmpty, IsNumber, IsString, Matches, MaxLength, MinLength, IsOptional } from 'class-validator'; +import { MaxLength, IsString, MinLength, Matches, IsNotEmpty, IsOptional } from 'class-validator'; const labelRegex = /^[a-zA-Z0-9 ]*$/; export class CreateTenantDto { @ApiProperty() - @IsString() - @Transform(({ value }) => value.trim()) @MaxLength(25, { message: 'Maximum length for label must be 25 characters.' }) + @IsString({ message: 'label must be in string format.' }) + @Transform(({ value }) => value.trim()) @MinLength(2, { message: 'Minimum length for label must be 2 characters.' }) @Matches(labelRegex, { message: 'Label must not contain special characters.' }) @Matches(/^\S*$/, { @@ -16,20 +16,19 @@ export class CreateTenantDto { label: string; @ApiProperty() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'seed is required' }) @MaxLength(32, { message: 'seed must be at most 32 characters.' }) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'seed is required'}) @IsString({ message: 'seed must be in string format.' }) @Matches(/^\S*$/, { message: 'Spaces are not allowed in seed' }) seed: string; - @ApiProperty() - @IsNumber() orgId: number; @ApiProperty() @IsOptional() + @ApiPropertyOptional() clientSocketId?: string; } \ 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 63b26cfa5..e1b9c3585 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -1,17 +1,130 @@ import { + Body, Controller, - Logger + Get, + HttpStatus, + Logger, + Post, + Query, + Res, + UnauthorizedException, + UseFilters } from '@nestjs/common'; import { AuthzService } from './authz.service'; -// import { CommonService } from "@credebl/common"; import { CommonService } from '../../../../libs/common/src/common.service'; +import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiResponseDto } from '../dtos/apiResponse.dto'; +import { UserEmailVerificationDto } from '../user/dto/create-user.dto'; +import IResponseType from '@credebl/common/interfaces/response.interface'; +import { ResponseMessages } from '@credebl/common/response-messages'; +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 { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; -@Controller('authz') +@Controller('auth') +@ApiTags('auth') +@UseFilters(CustomExceptionFilter) export class AuthzController { private logger = new Logger('AuthzController'); constructor(private readonly authzService: AuthzService, private readonly commonService: CommonService) { } -} + /** + * + * @param query + * @param res + * @returns User email verified + */ + @Get('/verify') + @ApiResponse({ status: 200, 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); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.emaiVerified + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } + + /** + * + * @param email + * @param res + * @returns Email sent success + */ + @Post('/verification-mail') + @ApiResponse({ status: 201, description: 'Success', 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); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.sendVerificationCode + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + /** + * + * @param email + * @param userInfo + * @param res + * @returns Add new user + */ + @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 decryptedPassword = this.commonService.decryptPassword(userInfo.password); + + userInfo.password = decryptedPassword; + const userDetails = await this.authzService.addUserDetails(userInfo); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userDetails.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + /** + * + * @param loginUserDto + * @param res + * @returns User 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 }) + @ApiBody({ type: LoginUserDto }) + async login(@Body() loginUserDto: LoginUserDto, @Res() res: Response): Promise { + + if (loginUserDto.email) { + let decryptedPassword; + if (loginUserDto.password) { + decryptedPassword = this.commonService.decryptPassword(loginUserDto.password); + } + const userData = await this.authzService.login(loginUserDto.email, decryptedPassword, loginUserDto.isPasskey); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.login, + data: userData.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } else { + throw new UnauthorizedException(`Please provide valid credentials`); + } + } + +} \ No newline at end of file diff --git a/apps/api-gateway/src/authz/authz.service.ts b/apps/api-gateway/src/authz/authz.service.ts index ab5cd587c..317712669 100644 --- a/apps/api-gateway/src/authz/authz.service.ts +++ b/apps/api-gateway/src/authz/authz.service.ts @@ -6,6 +6,9 @@ import { WebSocketServer } 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'; @Injectable() @@ -25,4 +28,24 @@ 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 verifyEmail(param: EmailVerificationDto): Promise { + const payload = { param }; + return this.sendNats(this.authServiceProxy, 'user-email-verification', payload); + + } + + async login(email: string, password?: string, isPasskey = false): Promise<{ response: object }> { + const payload = { email, password, isPasskey }; + return this.sendNats(this.authServiceProxy, 'user-holder-login', payload); + } + + async addUserDetails(userInfo: AddUserDetails): Promise<{ response: string }> { + const payload = { userInfo }; + return this.sendNats(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 0c6dd349e..46c60aec7 100644 --- a/apps/api-gateway/src/authz/guards/org-roles.guard.ts +++ b/apps/api-gateway/src/authz/guards/org-roles.guard.ts @@ -9,7 +9,7 @@ import { Reflector } from '@nestjs/core'; @Injectable() export class OrgRolesGuard implements CanActivate { - constructor(private reflector: Reflector) { } // eslint-disable-next-line array-callback-return + constructor(private reflector: Reflector) { } // eslint-disable-next-line array-callback-return private logger = new Logger('Org Role Guard'); @@ -29,8 +29,8 @@ export class OrgRolesGuard implements CanActivate { const { user } = req; - if (req.query.orgId || req.body.orgId) { - const orgId = req.query.orgId || req.body.orgId; + if (req.params.orgId || req.query.orgId || req.body.orgId) { + const orgId = req.params.orgId || req.query.orgId || req.body.orgId; const specificOrg = user.userOrgRoles.find((orgDetails) => { if (!orgDetails.orgId) { @@ -44,12 +44,17 @@ export class OrgRolesGuard implements CanActivate { } user.selectedOrg = specificOrg; - user.selectedOrg.orgRoles = user.userOrgRoles.map(roleItem => roleItem.orgRole.name); - + // eslint-disable-next-line array-callback-return + user.selectedOrg.orgRoles = user.userOrgRoles.map((orgRoleItem) => { + if (orgRoleItem.orgId && orgRoleItem.orgId.toString() === orgId.toString()) { + return orgRoleItem.orgRole.name; + } + }); + } else { throw new HttpException('organization is required', HttpStatus.BAD_REQUEST); } return requiredRoles.some((role) => user.selectedOrg?.orgRoles.includes(role)); } -} +} \ No newline at end of file diff --git a/apps/api-gateway/src/dtos/email-validator.dto.ts b/apps/api-gateway/src/dtos/email-validator.dto.ts new file mode 100644 index 000000000..c41ad0ef5 --- /dev/null +++ b/apps/api-gateway/src/dtos/email-validator.dto.ts @@ -0,0 +1,12 @@ + +import { ApiProperty } from '@nestjs/swagger'; +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() + email: string; +} \ No newline at end of file diff --git a/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts b/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts index a6c1b43c8..df6b85aae 100644 --- a/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts +++ b/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts @@ -1,5 +1,5 @@ import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsEmail, IsNotEmpty, IsNumber, IsString, ValidateNested } from 'class-validator'; +import { IsArray, IsEmail, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; import { Transform, Type } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; @@ -29,9 +29,5 @@ export class BulkSendInvitationDto { @ValidateNested({ each: true }) @Type(() => SendInvitationDto) invitations: SendInvitationDto[]; - - @ApiProperty({ example: 1 }) - @IsNotEmpty({ message: 'Please provide valid orgId' }) - @IsNumber() orgId: number; } \ No newline at end of file diff --git a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts index 1229ddc58..c3fb2d6aa 100644 --- a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts +++ b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts @@ -1,5 +1,5 @@ import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; +import { IsNotEmpty, IsOptional, IsString, IsBoolean, MaxLength, MinLength } from 'class-validator'; import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; @@ -7,9 +7,7 @@ import { trim } from '@credebl/common/cast.helper'; @ApiExtraModels() export class UpdateOrganizationDto { - @ApiProperty() - @IsNotEmpty({ message: 'orgId is required.' }) - @IsNumber() + orgId: number; @ApiProperty() @@ -40,6 +38,6 @@ export class UpdateOrganizationDto { @ApiPropertyOptional({ example: true }) @IsBoolean({ message: 'isPublic should be boolean' }) @IsOptional() - isPublic? = false; + isPublic?: boolean = false; } \ No newline at end of file diff --git a/apps/api-gateway/src/organization/dtos/update-user-roles.dto.ts b/apps/api-gateway/src/organization/dtos/update-user-roles.dto.ts index 5ddb6a9f8..f1270f88b 100644 --- a/apps/api-gateway/src/organization/dtos/update-user-roles.dto.ts +++ b/apps/api-gateway/src/organization/dtos/update-user-roles.dto.ts @@ -1,21 +1,11 @@ -import { IsArray, IsNotEmpty, IsNumber } from 'class-validator'; +import { IsArray, IsNotEmpty } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { toNumber } from '@credebl/common/cast.helper'; export class UpdateUserRolesDto { - @ApiProperty({ example: '2' }) - @IsNotEmpty({ message: 'Please provide valid orgId' }) - @Transform(({ value }) => toNumber(value)) - @IsNumber() - orgId: number; - @ApiProperty({ example: '3' }) - @IsNotEmpty({ message: 'Please provide valid userId' }) - @Transform(({ value }) => toNumber(value)) - @IsNumber() + orgId: number; userId: number; @ApiProperty({ example: [2, 1, 3] }) diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 32d4cec98..667c7bb26 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -26,9 +26,11 @@ import { GetAllOrganizationsDto } from './dtos/get-all-organizations.dto'; import { GetAllSentInvitationsDto } from './dtos/get-all-sent-invitations.dto'; import { UpdateOrganizationDto } from './dtos/update-organization-dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; +import { IUserRequestInterface } from '../interfaces/IUserRequestInterface'; +import { GetAllUsersDto } from '../user/dto/get-all-users.dto'; @UseFilters(CustomExceptionFilter) -@Controller('organization') +@Controller('orgs') @ApiTags('organizations') @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @@ -39,39 +41,6 @@ export class OrganizationController { private readonly commonService: CommonService ) { } - @Post('/') - @ApiOperation({ summary: 'Create a new Organization', description: 'Create an organization' }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - 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, - message: ResponseMessages.organisation.success.create - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - - @Get('/') - @ApiOperation({ summary: 'Get all organizations', description: 'Get all organizations' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - 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 - }; - return res.status(HttpStatus.OK).json(finalResponse); - - } - /** * * @param user @@ -79,9 +48,9 @@ export class OrganizationController { * @param res * @returns Users list of organization */ - @Get('/public-profiles') + @Get('/public-profile') @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ summary: 'Get public organization list', description: 'Get users list.' }) + @ApiOperation({ summary: 'Get all public profile of organizations', description: 'Get all public profile of organizations.' }) @ApiQuery({ name: 'pageNumber', type: Number, @@ -109,6 +78,28 @@ export class OrganizationController { return res.status(HttpStatus.OK).json(finalResponse); } + @Get('/roles') + @ApiOperation({ + summary: 'Fetch org-roles details', + description: 'Fetch org-roles details' + }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async getOrgRoles(@Res() res: Response): Promise { + + const orgRoles = await this.organizationService.getOrgRoles(); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.organisation.success.fetchOrgRoles, + data: orgRoles + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } + @Get('public-profiles/:orgSlug') @ApiOperation({ summary: 'Fetch user details', @@ -133,135 +124,221 @@ export class OrganizationController { } - @Get('/roles') - @ApiOperation({ - summary: 'Fetch org-roles details', - description: 'Fetch org-roles details' - }) + + @Get('/dashboard/:orgId') + @ApiOperation({ summary: 'Get an organization', description: 'Get an organization' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async getOrgRoles(@Res() res: Response): Promise { - const orgRoles = await this.organizationService.getOrgRoles(); + async getOrganizationDashboard(@Param('orgId') orgId: number, @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.fetchOrgRoles, - data: orgRoles + message: ResponseMessages.organisation.success.getOrgDashboard, + data: getOrganization.response }; - return res.status(HttpStatus.OK).json(finalResponse); } - @Post('/invitations') - @ApiOperation({ - summary: 'Create organization invitation', - description: 'Create send invitation' - }) + @Get('/:orgId/invitations') + @ApiOperation({ summary: 'Get an invitations', description: 'Get an invitations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() - async createInvitation(@Body() bulkInvitationDto: BulkSendInvitationDto, @User() user: user, @Res() res: Response): Promise { - await this.organizationService.createInvitation(bulkInvitationDto, user.id); + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + async getInvitationsByOrgId(@Param('orgId') orgId: number, @Query() getAllInvitationsDto: GetAllSentInvitationsDto, @Res() res: Response): Promise { + + const getInvitationById = await this.organizationService.getInvitationsByOrgId(orgId, getAllInvitationsDto); const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.organisation.success.createInvitation + statusCode: HttpStatus.OK, + message: ResponseMessages.organisation.success.getInvitation, + data: getInvitationById.response }; - - return res.status(HttpStatus.CREATED).json(finalResponse); + return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/invitations/:id') - @ApiOperation({ summary: 'Get an invitations', description: 'Get an invitations' }) + @Get('/') + @ApiOperation({ summary: 'Get all organizations', description: 'Get all organizations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async getInvitationsByOrgId(@Param('id') orgId: number, @Query() getAllInvitationsDto: GetAllSentInvitationsDto, @Res() res: Response): Promise { + @ApiQuery({ + name: 'pageNumber', + example: '1', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + example: '10', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + example: '', + type: String, + required: false + }) + async getOrganizations(@Query() getAllOrgsDto: GetAllOrganizationsDto, @Res() res: Response, @User() reqUser: user): Promise { - const getInvitationById = await this.organizationService.getInvitationsByOrgId(orgId, getAllInvitationsDto); + const getOrganizations = await this.organizationService.getOrganizations(getAllOrgsDto, reqUser.id); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.organisation.success.getInvitation, - data: getInvitationById.response + message: ResponseMessages.organisation.success.getOrganizations, + data: getOrganizations.response }; return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/dashboard') + @Get('/:orgId') @ApiOperation({ summary: 'Get an organization', description: 'Get an organization' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() - @ApiQuery( - { name: 'orgId', required: true } - ) - async getOrganizationDashboard(@Query('orgId') orgId: number, @Res() res: Response, @User() reqUser: user): Promise { + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + async getOrganization(@Param('orgId') orgId: number, @Res() res: Response, @User() reqUser: user): Promise { - const getOrganization = await this.organizationService.getOrganizationDashboard(orgId, reqUser.id); + const getOrganization = await this.organizationService.getOrganization(orgId, reqUser.id); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.organisation.success.getOrgDashboard, + message: ResponseMessages.organisation.success.getOrganization, data: getOrganization.response }; return res.status(HttpStatus.OK).json(finalResponse); } - - @Put('user-roles') - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + /** + * + * @param user + * @param orgId + * @param res + * @returns Users list of organization + */ + @Get('/:orgId/users') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) @ApiBearerAuth() @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, @Res() res: Response): Promise { - - await this.organizationService.updateUserRoles(updateUserDto, updateUserDto.userId); - + @ApiOperation({ summary: 'Get organization users list', description: 'Get organization users list.' }) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Param('orgId') orgId: number, @Res() res: Response): Promise { + const users = await this.organizationService.getOrgUsers(orgId, getAllUsersDto); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.organisation.success.updateUserRoles + message: ResponseMessages.user.success.fetchUsers, + data: users?.response }; return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/:id') - @ApiOperation({ summary: 'Get an organization', description: 'Get an organization' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @Post('/') + @ApiOperation({ summary: 'Create a new Organization', description: 'Create an organization' }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async getOrganization(@Param('id') orgId: number, @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, + message: ResponseMessages.organisation.success.create + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } - const getOrganization = await this.organizationService.getOrganization(orgId, reqUser.id); + @Post('/:orgId/invitations') + @ApiOperation({ + summary: 'Create organization invitation', + description: 'Create send invitation' + }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiBearerAuth() + async createInvitation(@Body() bulkInvitationDto: BulkSendInvitationDto, @Param('orgId') orgId: number, @User() user: user, @Res() res: Response): Promise { + + bulkInvitationDto.orgId = orgId; + await this.organizationService.createInvitation(bulkInvitationDto, user.id); const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.organisation.success.getOrganization, - data: getOrganization.response + statusCode: HttpStatus.CREATED, + message: ResponseMessages.organisation.success.createInvitation }; - return res.status(HttpStatus.OK).json(finalResponse); + + return res.status(HttpStatus.CREATED).json(finalResponse); } + @Put('/:orgId/user-roles/:userId') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @ApiBearerAuth() + @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: number, @Param('userId') userId: number, @Res() res: Response): Promise { + + updateUserDto.orgId = orgId; + updateUserDto.userId = userId; + await this.organizationService.updateUserRoles(updateUserDto, updateUserDto.userId); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.organisation.success.updateUserRoles + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } - @Put('/') + @Put('/:orgId') @ApiOperation({ summary: 'Update Organization', description: 'Update an organization' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async updateOrganization(@Body() updateOrgDto: UpdateOrganizationDto, @Res() res: Response, @User() reqUser: user): Promise { + async updateOrganization(@Body() updateOrgDto: UpdateOrganizationDto, @Param('orgId') orgId: number, @Res() res: Response, @User() reqUser: user): Promise { - await this.organizationService.updateOrganization(updateOrgDto, reqUser.id); + updateOrgDto.orgId = orgId; + await this.organizationService.updateOrganization(updateOrgDto, reqUser.id, orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, @@ -269,4 +346,4 @@ export class OrganizationController { }; 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 ba0d6f96d..c7579dcf5 100644 --- a/apps/api-gateway/src/organization/organization.service.ts +++ b/apps/api-gateway/src/organization/organization.service.ts @@ -1,6 +1,6 @@ import { Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; -import { ClientProxy, RpcException } from '@nestjs/microservices'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { CreateOrganizationDto } from './dtos/create-organization-dto'; import { GetAllOrganizationsDto } from './dtos/get-all-organizations.dto'; @@ -8,6 +8,7 @@ import { GetAllSentInvitationsDto } from './dtos/get-all-sent-invitations.dto'; 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'; @Injectable() export class OrganizationService extends BaseService { @@ -21,13 +22,8 @@ export class OrganizationService extends BaseService { * @returns Organization creation Success */ async createOrganization(createOrgDto: CreateOrganizationDto, userId: number): Promise { - try { - const payload = { createOrgDto, userId }; - return this.sendNats(this.serviceProxy, 'create-organization', payload); - } catch (error) { - this.logger.error(`In service Error: ${error}`); - throw new RpcException(error.response); - } + const payload = { createOrgDto, userId }; + return this.sendNats(this.serviceProxy, 'create-organization', payload); } /** @@ -35,14 +31,9 @@ export class OrganizationService extends BaseService { * @param updateOrgDto * @returns Organization update Success */ - async updateOrganization(updateOrgDto: UpdateOrganizationDto, userId: number): Promise { - try { - const payload = { updateOrgDto, userId }; - return this.sendNats(this.serviceProxy, 'update-organization', payload); - } catch (error) { - this.logger.error(`In service Error: ${error}`); - throw new RpcException(error.response); - } + async updateOrganization(updateOrgDto: UpdateOrganizationDto, userId: number, orgId: number): Promise { + const payload = { updateOrgDto, userId, orgId }; + return this.sendNats(this.serviceProxy, 'update-organization', payload); } /** @@ -66,7 +57,7 @@ export class OrganizationService extends BaseService { } async getPublicProfile(orgSlug: string): Promise<{ response: object }> { - const payload = {orgSlug }; + const payload = { orgSlug }; try { return this.sendNats(this.serviceProxy, 'get-organization-public-profile', payload); } catch (error) { @@ -109,13 +100,8 @@ export class OrganizationService extends BaseService { * @returns get organization roles */ async getOrgRoles(): Promise { - try { - const payload = {}; - return this.sendNats(this.serviceProxy, 'get-org-roles', payload); - } catch (error) { - this.logger.error(`In service Error: ${error}`); - throw new RpcException(error.response); - } + const payload = {}; + return this.sendNats(this.serviceProxy, 'get-org-roles', payload); } /** @@ -124,13 +110,8 @@ export class OrganizationService extends BaseService { * @returns Organization invitation creation Success */ async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: number): Promise { - try { - const payload = { bulkInvitationDto, userId }; - return this.sendNats(this.serviceProxy, 'send-invitation', payload); - } catch (error) { - this.logger.error(`In service Error: ${error}`); - throw new RpcException(error.response); - } + const payload = { bulkInvitationDto, userId }; + return this.sendNats(this.serviceProxy, 'send-invitation', payload); } /** @@ -143,4 +124,14 @@ export class OrganizationService extends BaseService { const payload = { orgId: updateUserDto.orgId, roleIds: updateUserDto.orgRoleId, userId }; return this.sendNats(this.serviceProxy, 'update-user-roles', payload); } + + async getOrgUsers( + orgId: number, + getAllUsersDto: GetAllUsersDto + ): Promise<{ response: object }> { + const { pageNumber, pageSize, search } = getAllUsersDto; + const payload = { orgId, pageNumber, pageSize, search }; + + return this.sendNats(this.serviceProxy, 'fetch-organization-user', payload); + } } diff --git a/apps/api-gateway/src/user/dto/accept-reject-invitation.dto.ts b/apps/api-gateway/src/user/dto/accept-reject-invitation.dto.ts index e3d40e7ea..6d16ed0d9 100644 --- a/apps/api-gateway/src/user/dto/accept-reject-invitation.dto.ts +++ b/apps/api-gateway/src/user/dto/accept-reject-invitation.dto.ts @@ -6,10 +6,6 @@ import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; export class AcceptRejectInvitationDto { - - @ApiProperty({ example: 1 }) - @IsNotEmpty({ message: 'Please provide valid invitationId' }) - @IsNumber() invitationId: number; @ApiProperty({ example: 1 }) @@ -25,4 +21,4 @@ export class AcceptRejectInvitationDto { @IsEnum(Invitation) status: Invitation.ACCEPTED | Invitation.REJECTED; -} +} \ 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 a72eb45eb..05983a924 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,17 @@ import { trim } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { IsBoolean, IsNotEmpty, IsOptional, IsString} from 'class-validator'; +import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class AddUserDetails { + + @ApiProperty({ example: 'awqx@getnada.com' }) + @IsEmail() + @IsNotEmpty({ message: 'Please provide valid email' }) + @IsString({ message: 'email should be string' }) + @IsOptional() + email?: string; + @ApiProperty({ example: 'Alen' }) @IsString({ message: 'firstName should be string' }) @IsOptional() 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 09c5cafaf..f62980e6e 100644 --- a/apps/api-gateway/src/user/dto/login-user.dto.ts +++ b/apps/api-gateway/src/user/dto/login-user.dto.ts @@ -1,8 +1,8 @@ -import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; +import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; +import { trim } from '@credebl/common/cast.helper'; export class LoginUserDto { @ApiProperty({ example: 'awqx@getnada.com' }) @@ -21,29 +21,4 @@ export class LoginUserDto { @IsOptional() @IsBoolean({ message: 'isPasskey should be boolean' }) isPasskey: boolean; -} - -export class AddUserDetails { - @ApiProperty({ example: 'Alen' }) - @IsString({ message: 'firstName should be string' }) - @IsOptional() - firstName?: string; - - @ApiProperty({ example: 'Harvey' }) - @IsString({ message: 'lastName should be string' }) - @IsOptional() - lastName?: string; - - @ApiProperty() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'Password is required.' }) - @MinLength(8, { message: 'Password must be at least 8 characters.' }) - @MaxLength(50, { message: 'Password must be at most 50 characters.' }) - @IsOptional() - password?: string; - - @ApiProperty({ example: 'false' }) - @IsOptional() - @IsBoolean({ message: 'isPasskey should be boolean' }) - isPasskey?: boolean; -} +} \ No newline at end of file diff --git a/apps/api-gateway/src/user/dto/update-user-profile.dto.ts b/apps/api-gateway/src/user/dto/update-user-profile.dto.ts index 41484636c..e4f39fef5 100644 --- a/apps/api-gateway/src/user/dto/update-user-profile.dto.ts +++ b/apps/api-gateway/src/user/dto/update-user-profile.dto.ts @@ -1,15 +1,13 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsOptional, IsString, IsBoolean } from 'class-validator'; + export class UpdateUserProfileDto { - @ApiProperty() - @IsNotEmpty({ message: 'userId is required.' }) - @IsNumber() id: number; @ApiPropertyOptional() @IsOptional() - @IsString({message:'ProfileLogoUrl should be string'}) + @IsString({ message: 'ProfileLogoUrl should be string' }) profileImg?: string; @ApiProperty({ example: 'Alen' }) @@ -25,5 +23,5 @@ export class UpdateUserProfileDto { @ApiPropertyOptional({ example: true }) @IsBoolean({ message: 'isPublic should be boolean' }) @IsOptional() - isPublic? = false; + isPublic?: boolean = false; } \ No newline at end of file diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 953c57cf4..8c9a25640 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -1,9 +1,7 @@ import { Controller, Post, Put, Body, Param, UseFilters } from '@nestjs/common'; import { UserService } from './user.service'; -import { UserEmailVerificationDto } from './dto/create-user.dto'; import { ApiBearerAuth, - ApiBody, ApiForbiddenResponse, ApiOperation, ApiParam, @@ -21,11 +19,7 @@ import { HttpStatus } from '@nestjs/common'; import { CommonService } from '@credebl/common'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { BadRequestException } from '@nestjs/common'; -import { AuthTokenResponse } from '../authz/dtos/auth-token-res.dto'; -import { LoginUserDto } from './dto/login-user.dto'; -import { UnauthorizedException } from '@nestjs/common'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { EmailVerificationDto } from './dto/email-verify.dto'; import { Get } from '@nestjs/common'; import { Query } from '@nestjs/common'; import { user } from '@prisma/client'; @@ -34,15 +28,13 @@ import { AuthGuard } from '@nestjs/passport'; import { User } from '../authz/decorators/user.decorator'; import { AcceptRejectInvitationDto } from './dto/accept-reject-invitation.dto'; import { Invitation } from '@credebl/enum/enum'; -import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; -import { Roles } from '../authz/decorators/roles.decorator'; -import { OrgRoles } from 'libs/org-roles/enums'; import { IUserRequestInterface } from './interfaces'; import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; import { GetAllUsersDto } from './dto/get-all-users.dto'; -import { AddPasskeyDetails, AddUserDetails } from './dto/add-user.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 { EmailValidator } from '../dtos/email-validator.dto'; @UseFilters(CustomExceptionFilter) @Controller('users') @@ -52,66 +44,6 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler export class UserController { constructor(private readonly userService: UserService, private readonly commonService: CommonService) { } - /** - * - * @param email - * @param res - * @returns Email sent success - */ - @Post('/send-mail') - @ApiResponse({ status: 201, description: 'Success', 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.userService.sendVerificationMail(userEmailVerificationDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.sendVerificationCode - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - - /** - * - * @param user - * @param orgId - * @param res - * @returns Users list of organization - */ - @Get() - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ summary: 'Get organization users list', description: 'Get organization users list.' }) - @ApiQuery({ - name: 'pageNumber', - type: Number, - required: false - }) - @ApiQuery({ - name: 'pageSize', - type: Number, - required: false - }) - @ApiQuery({ - name: 'search', - type: String, - required: false - }) - async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Query('orgId') orgId: number, @Res() res: Response): Promise { - - const org = user.selectedOrg?.orgId; - const users = await this.userService.getOrgUsers(org, getAllUsersDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchUsers, - data: users.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } - - /** * * @param user @@ -149,60 +81,30 @@ export class UserController { return res.status(HttpStatus.OK).json(finalResponse); } + @Get('public-profiles/:username') + @ApiOperation({ + summary: 'Fetch user details', + description: 'Fetch user details' + }) + @ApiParam({ + name: 'username', + type: String, + required: false + }) + async getPublicProfile(@Param('username') username: string, @Res() res: Response): Promise { + const userData = await this.userService.getPublicProfile(username); - /** - * - * @param query - * @param res - * @returns User email verified - */ - @Get('/verify') - @ApiOperation({ summary: 'Verify new users email', description: 'Email verification for new users' }) - async verifyEmail(@Query() query: EmailVerificationDto, @Res() res: Response): Promise { - await this.userService.verifyEmail(query); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.emaiVerified + message: ResponseMessages.user.success.fetchProfile, + data: userData.response }; return res.status(HttpStatus.OK).json(finalResponse); } - /** - * - * @param loginUserDto - * @param res - * @returns User access token details - */ - @Post('/login') - @ApiOperation({ - summary: 'Login API for web portal', - description: 'Password should be AES encrypted.' - }) - @ApiResponse({ status: 200, description: 'Success', type: AuthTokenResponse }) - @ApiBody({ type: LoginUserDto }) - async login(@Body() loginUserDto: LoginUserDto, @Res() res: Response): Promise { - - if (loginUserDto.email) { - let decryptedPassword; - if (loginUserDto.password) { - decryptedPassword = this.commonService.decryptPassword(loginUserDto.password); - } - const userData = await this.userService.login(loginUserDto.email, decryptedPassword, loginUserDto.isPasskey); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.login, - data: userData.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } else { - throw new UnauthorizedException(`Please provide valid credentials`); - } - } - - @Get('profile') + @Get('/profile') @ApiOperation({ summary: 'Fetch login user details', description: 'Fetch login user details' @@ -223,37 +125,56 @@ export class UserController { } - @Get('public-profiles/:username') + @Get('/activity') @ApiOperation({ - summary: 'Fetch user details', - description: 'Fetch user details' - }) - @ApiParam({ - name: 'username', - type: String, - required: false + summary: 'organization invitations', + description: 'Fetch organization invitations' }) - async getPublicProfile(@Param('username') username: string, @Res() res: Response): Promise { - const userData = await this.userService.getPublicProfile(username); + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + @ApiQuery({ name: 'limit', required: true }) + async getUserActivities(@Query('limit') limit: number, @Res() res: Response, @User() reqUser: user): Promise { + + const userDetails = await this.userService.getUserActivities(reqUser.id, limit); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchProfile, - data: userData.response + message: ResponseMessages.user.success.userActivity, + data: userDetails.response }; return res.status(HttpStatus.OK).json(finalResponse); - } - @Get('invitations') + + @Get('/org-invitations') @ApiOperation({ summary: 'organization invitations', description: 'Fetch organization invitations' }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async invitations(@User() reqUser: user, @Query() getAllInvitationsDto: GetAllInvitationsDto, @Res() res: Response): Promise { + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + @ApiQuery({ + name: 'status', + type: String, + required: false + }) + async invitations(@Query() getAllInvitationsDto: GetAllInvitationsDto, @User() reqUser: user, @Res() res: Response): Promise { if (!Object.values(Invitation).includes(getAllInvitationsDto.status)) { throw new BadRequestException(ResponseMessages.user.error.invalidInvitationStatus); @@ -271,6 +192,26 @@ export class UserController { } + /** + * + * @param email + * @param res + * @returns User email check + */ + @Get('/:email') + @ApiOperation({ summary: 'Check 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 = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.checkEmail, + data: userDetails.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } /** * @@ -279,84 +220,23 @@ export class UserController { * @param res * @returns Organization invitation status */ - @Post('invitations') + @Post('/org-invitations/:invitationId') @ApiOperation({ summary: 'accept/reject organization invitation', description: 'Accept or Reject organization invitations' }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async acceptRejectInvitaion(@Body() acceptRejectInvitation: AcceptRejectInvitationDto, @User() reqUser: user, @Res() res: Response): Promise { + async acceptRejectInvitaion(@Body() acceptRejectInvitation: AcceptRejectInvitationDto, @Param('invitationId') invitationId: string, @User() reqUser: user, @Res() res: Response): Promise { + acceptRejectInvitation.invitationId = parseInt(invitationId); const invitationRes = await this.userService.acceptRejectInvitaion(acceptRejectInvitation, reqUser.id); const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, + statusCode: HttpStatus.CREATED, message: invitationRes.response }; - return res.status(HttpStatus.OK).json(finalResponse); - - } - - /** - * - * @param email - * @param res - * @returns User email check - */ - @Get('/check-user/:email') - @ApiOperation({ summary: 'Check user exist', description: 'check user existence' }) - async checkUserExist(@Param('email') email: string, @Res() res: Response): Promise { - const userDetails = await this.userService.checkUserExist(email); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.checkEmail, - data: userDetails.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - - } - - /** - * - * @param email - * @param userInfo - * @param res - * @returns Add new user - */ - @Post('/add/:email') - @ApiOperation({ summary: 'Add user information', description: 'Add user information' }) - async addUserDetailsInKeyCloak(@Body() userInfo: AddUserDetails, @Param('email') email: string, @Res() res: Response): Promise { - let finalResponse; - let userDetails; - - if (false === userInfo.isPasskey) { - - const decryptedPassword = this.commonService.decryptPassword(userInfo.password); - if (8 <= decryptedPassword.length && 50 >= decryptedPassword.length) { - this.commonService.passwordValidation(decryptedPassword); - userInfo.password = decryptedPassword; - userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); - finalResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.user.success.create, - data: userDetails.response - }; - } else { - throw new BadRequestException('Password name must be between 8 to 50 Characters'); - } - } else { - - userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); - finalResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.user.success.create, - data: userDetails.response - }; - } - return res.status(HttpStatus.OK).json(finalResponse); + return res.status(HttpStatus.CREATED).json(finalResponse); } @@ -368,8 +248,10 @@ export class UserController { @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) - async updateUserProfile(@Body() updateUserProfileDto: UpdateUserProfileDto, @Res() res: Response): Promise { + async updateUserProfile(@Body() updateUserProfileDto: UpdateUserProfileDto, @User() reqUser: user, @Res() res: Response): Promise { + const userId = reqUser.id; + updateUserProfileDto.id = userId; await this.userService.updateUserProfile(updateUserProfileDto); const finalResponse: IResponseType = { @@ -380,36 +262,15 @@ export class UserController { } - @Get('/activity') - @ApiOperation({ - summary: 'organization invitations', - description: 'Fetch organization invitations' - }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiQuery({ name: 'limit', required: true }) - async getUserActivities(@Query('limit') limit: number, @Res() res: Response, @User() reqUser: user): Promise { - - const userDetails = await this.userService.getUserActivities(reqUser.id, limit); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: 'User activities fetched successfully', - data: userDetails.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } - - @Post('/password/:email') - @ApiOperation({ summary: 'Add user information', description: 'Add user information' }) + @Put('/password/:email') + @ApiOperation({ summary: 'Store user password details', description: 'Store user password details' }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() async addPasskey(@Body() userInfo: AddPasskeyDetails, @Param('email') email: string, @Res() res: Response): Promise { const userDetails = await this.userService.addPasskey(email, userInfo); const finalResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.user.success.create, + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.update, data: userDetails.response }; diff --git a/apps/api-gateway/src/user/user.service.ts b/apps/api-gateway/src/user/user.service.ts index 9e97e50ac..63e8ecc9e 100644 --- a/apps/api-gateway/src/user/user.service.ts +++ b/apps/api-gateway/src/user/user.service.ts @@ -1,89 +1,42 @@ import { Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; -import { ClientProxy, RpcException } from '@nestjs/microservices'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { AcceptRejectInvitationDto } from './dto/accept-reject-invitation.dto'; -import { UserEmailVerificationDto } from './dto/create-user.dto'; -import { EmailVerificationDto } from './dto/email-verify.dto'; import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; -import { AddUserDetails } from './dto/login-user.dto'; import { GetAllUsersDto } from './dto/get-all-users.dto'; import { UpdateUserProfileDto } from './dto/update-user-profile.dto'; import { AddPasskeyDetails } from './dto/add-user.dto'; - @Injectable() export class UserService extends BaseService { constructor(@Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy) { super('User Service'); } - async sendVerificationMail(userEmailVerificationDto: UserEmailVerificationDto): Promise { - try { - const payload = { userEmailVerificationDto }; - return this.sendNats(this.serviceProxy, 'send-verification-mail', payload); - } catch (error) { - throw new RpcException(error.response); - } - } - - async login(email: string, password?: string, isPasskey = false): Promise<{ response: object }> { - try { - const payload = { email, password, isPasskey }; - return this.sendNats(this.serviceProxy, 'user-holder-login', payload); - } catch (error) { - throw new RpcException(error.response); - } - } - async verifyEmail(param: EmailVerificationDto): Promise { - try { - const payload = { param }; - return this.sendNats(this.serviceProxy, 'user-email-verification', payload); - } catch (error) { - throw new RpcException(error.response); - } - } - async getProfile(id: number): Promise<{ response: object }> { const payload = { id }; - try { - return this.sendNats(this.serviceProxy, 'get-user-profile', payload); - } catch (error) { - this.logger.error(`Error in get user:${JSON.stringify(error)}`); - } + return this.sendNats(this.serviceProxy, 'get-user-profile', payload); } async getPublicProfile(username: string): Promise<{ response: object }> { const payload = { username }; - try { - return this.sendNats(this.serviceProxy, 'get-user-public-profile', payload); - } catch (error) { - this.logger.error(`Error in get user:${JSON.stringify(error)}`); - } + return this.sendNats(this.serviceProxy, 'get-user-public-profile', payload); } async updateUserProfile(updateUserProfileDto: UpdateUserProfileDto): Promise<{ response: object }> { - const payload = {updateUserProfileDto }; - try { - return this.sendNats(this.serviceProxy, 'update-user-profile', payload); - } catch (error) { - throw new RpcException(error.response); - } + const payload = { updateUserProfileDto }; + return this.sendNats(this.serviceProxy, 'update-user-profile', payload); } - + async findUserinSupabase(id: string): Promise<{ response: object }> { const payload = { id }; - - try { - return this.sendNats(this.serviceProxy, 'get-user-by-supabase', payload); - } catch (error) { - this.logger.error(`Error in get user:${JSON.stringify(error)}`); - } + return this.sendNats(this.serviceProxy, 'get-user-by-supabase', payload); } async invitations(id: number, status: string, getAllInvitationsDto: GetAllInvitationsDto): Promise<{ response: object }> { - const {pageNumber, pageSize, search} = getAllInvitationsDto; + const { pageNumber, pageSize, search } = getAllInvitationsDto; const payload = { id, status, pageNumber, pageSize, search }; return this.sendNats(this.serviceProxy, 'get-org-invitations', payload); } @@ -96,39 +49,25 @@ export class UserService extends BaseService { return this.sendNats(this.serviceProxy, 'accept-reject-invitations', payload); } - async getOrgUsers( - orgId: number, - getAllUsersDto: GetAllUsersDto - ): Promise<{ response: object }> { - const {pageNumber, pageSize, search} = getAllUsersDto; - const payload = { orgId, pageNumber, pageSize, search }; - return this.sendNats(this.serviceProxy, 'fetch-organization-users', payload); - } - async get( getAllUsersDto: GetAllUsersDto ): Promise<{ response: object }> { - const {pageNumber, pageSize, search} = getAllUsersDto; + const { pageNumber, pageSize, search } = getAllUsersDto; const payload = { pageNumber, pageSize, search }; return this.sendNats(this.serviceProxy, 'fetch-users', payload); } - + async checkUserExist(userEmail: string): Promise<{ response: string }> { const payload = { userEmail }; return this.sendNats(this.serviceProxy, 'check-user-exist', payload); } - async addUserDetailsInKeyCloak(userEmail: string, userInfo:AddUserDetails): Promise<{ response: string }> { - const payload = { userEmail, userInfo }; - return this.sendNats(this.serviceProxy, 'add-user', payload); - } - async getUserActivities(userId: number, limit: number): Promise<{ response: object }> { const payload = { userId, limit }; return this.sendNats(this.serviceProxy, 'get-user-activity', payload); } - async addPasskey(userEmail: string, userInfo:AddPasskeyDetails): Promise<{ response: string }> { + async addPasskey(userEmail: string, userInfo: AddPasskeyDetails): Promise<{ response: string }> { const payload = { userEmail, userInfo }; return this.sendNats(this.serviceProxy, 'add-passkey', payload); } diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 0ebb10fb3..4ad0bd967 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -263,9 +263,9 @@ export class OrganizationRepository { } }, userOrgRoles: { - include:{ + include: { user: true, - orgRole:true + orgRole: true } } } @@ -413,4 +413,24 @@ export class OrganizationRepository { throw new InternalServerErrorException(error); } } + + /** + * + * @param name + * @returns Organization exist details + */ + + async checkOrganizationExist(name: string, orgId: number): Promise { + try { + return this.prisma.organisation.findMany({ + where: { + id: orgId, + name + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } } diff --git a/apps/organization/src/organization.controller.ts b/apps/organization/src/organization.controller.ts index 85edbe3c9..bbe5bfd8b 100644 --- a/apps/organization/src/organization.controller.ts +++ b/apps/organization/src/organization.controller.ts @@ -31,8 +31,8 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'update-organization' }) - async updateOrganization(payload: { updateOrgDto: IUpdateOrganization; userId: number }): Promise { - return this.organizationService.updateOrganization(payload.updateOrgDto, payload.userId); + async updateOrganization(payload: { updateOrgDto: IUpdateOrganization; userId: number, orgId: number }): Promise { + return this.organizationService.updateOrganization(payload.updateOrgDto, payload.userId, payload.orgId); } /** diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index fce88b3bc..1ba6eea59 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -60,7 +60,7 @@ export class OrganizationService { return organizationDetails; } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -71,12 +71,12 @@ export class OrganizationService { * @returns OrgSlug */ createOrgSlug(orgName: string): string { - return orgName - .toLowerCase() // Convert the input to lowercase - .replace(/\s+/g, '-') // Replace spaces with hyphens - .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens - .replace(/--+/g, '-'); // Replace multiple consecutive hyphens with a single hyphen -} + return orgName + .toLowerCase() // Convert the input to lowercase + .replace(/\s+/g, '-') // Replace spaces with hyphens + .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens + .replace(/--+/g, '-'); // Replace multiple consecutive hyphens with a single hyphen + } /** * @@ -85,13 +85,16 @@ export class OrganizationService { */ // eslint-disable-next-line camelcase - async updateOrganization(updateOrgDto: IUpdateOrganization, userId: number): Promise { + async updateOrganization(updateOrgDto: IUpdateOrganization, userId: number, orgId: number): Promise { try { - const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); - - if (organizationExist && organizationExist.id !== Number(updateOrgDto.orgId)) { - throw new ConflictException(ResponseMessages.organisation.error.exists); + const organizationExist = await this.organizationRepository.checkOrganizationExist(updateOrgDto.name, orgId); + + if (0 === organizationExist.length) { + const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); + if (organizationExist) { + throw new ConflictException(ResponseMessages.organisation.error.exists); + } } const orgSlug = await this.createOrgSlug(updateOrgDto.name); @@ -102,7 +105,7 @@ export class OrganizationService { return organizationDetails; } catch (error) { this.logger.error(`In update organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -138,7 +141,7 @@ export class OrganizationService { } catch (error) { this.logger.error(`In fetch getOrganizations : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -170,12 +173,12 @@ export class OrganizationService { } catch (error) { this.logger.error(`In fetch getPublicOrganizations : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } async getPublicProfile(payload: { orgSlug: string }): Promise { - const {orgSlug} = payload; + const { orgSlug } = payload; try { const query = { @@ -191,7 +194,7 @@ export class OrganizationService { } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -212,7 +215,7 @@ export class OrganizationService { return organizationDetails; } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -232,7 +235,7 @@ export class OrganizationService { return getOrganization; } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -248,7 +251,7 @@ export class OrganizationService { return this.orgRoleService.getOrgRoles(); } catch (error) { this.logger.error(`In getOrgRoles : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -276,7 +279,7 @@ export class OrganizationService { return false; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -316,7 +319,7 @@ export class OrganizationService { return ResponseMessages.organisation.success.createInvitation; } catch (error) { this.logger.error(`In send Invitation : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -379,7 +382,7 @@ export class OrganizationService { return this.organizationRepository.getAllOrgInvitations(email, status, pageNumber, pageSize, search); } catch (error) { this.logger.error(`In fetchUserInvitation : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -415,7 +418,7 @@ export class OrganizationService { } catch (error) { this.logger.error(`In updateOrgInvitation : ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -451,7 +454,7 @@ export class OrganizationService { } catch (error) { this.logger.error(`Error in updateUserRoles: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -460,7 +463,7 @@ export class OrganizationService { return this.organizationRepository.getOrgDashboard(orgId); } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index ed5febe00..f42a9b5a8 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -30,6 +30,7 @@ export interface UserEmailVerificationDto{ } export interface userInfo{ + email: string, password: string, firstName: string, lastName: string, diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 9d94f00e0..026da028c 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -50,7 +50,7 @@ export class UserController { return this.userService.updateUserProfile(payload.updateUserProfileDto); } - @MessagePattern({ cmd: 'get-user-by-supabase' }) + @MessagePattern({ cmd: 'get-user-by-supabase' }) async findSupabaseUser(payload: { id }): Promise { return this.userService.findSupabaseUser(payload); } @@ -58,7 +58,7 @@ export class UserController { @MessagePattern({ cmd: 'get-user-by-mail' }) async findUserByEmail(payload: { email }): Promise { - return this.userService.findUserByEmail(payload); + return this.userService.findUserByEmail(payload); } @MessagePattern({ cmd: 'get-org-invitations' }) @@ -84,29 +84,29 @@ export class UserController { * @param payload * @returns organization users list */ - @MessagePattern({ cmd: 'fetch-organization-users' }) + @MessagePattern({ cmd: 'fetch-organization-user' }) async getOrganizationUsers(payload: { orgId: number, pageNumber: number, pageSize: number, search: string }): Promise { return this.userService.getOrgUsers(payload.orgId, payload.pageNumber, payload.pageSize, payload.search); } - /** - * - * @param payload - * @returns organization users list - */ - @MessagePattern({ cmd: 'fetch-users' }) - async get(payload: { pageNumber: number, pageSize: number, search: string }): Promise { - const users = this.userService.get(payload.pageNumber, payload.pageSize, payload.search); - return users; - } + /** + * + * @param payload + * @returns organization users list + */ + @MessagePattern({ cmd: 'fetch-users' }) + async get(payload: { pageNumber: number, pageSize: number, search: string }): Promise { + const users = this.userService.get(payload.pageNumber, payload.pageSize, payload.search); + return users; + } @MessagePattern({ cmd: 'check-user-exist' }) async checkUserExist(payload: { userEmail: string }): Promise { return this.userService.checkUserExist(payload.userEmail); } @MessagePattern({ cmd: 'add-user' }) - async addUserDetailsInKeyCloak(payload: { userEmail: string, userInfo: userInfo }): Promise { - return this.userService.createUserForToken(payload.userEmail, payload.userInfo); + async addUserDetailsInKeyCloak(payload: { userInfo: userInfo }): Promise { + return this.userService.createUserForToken(payload.userInfo); } // Fetch Users recent activities diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 96753ee0c..0f5a84c9a 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -34,7 +34,6 @@ import { SupabaseService } from '@credebl/supabase'; import { UserDevicesRepository } from '../repositories/user-device.repository'; import { v4 as uuidv4 } from 'uuid'; - @Injectable() export class UserService { constructor( @@ -59,7 +58,7 @@ export class UserService { async sendVerificationMail(userEmailVerificationDto: UserEmailVerificationDto): Promise { try { const userDetails = await this.userRepository.checkUserExist(userEmailVerificationDto.email); - + if (userDetails && userDetails.isEmailVerified) { throw new ConflictException(ResponseMessages.user.error.exists); } @@ -82,7 +81,7 @@ export class UserService { return resUser; } catch (error) { this.logger.error(`In Create User : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -106,7 +105,7 @@ export class UserService { } catch (error) { this.logger.error(`Error in createUsername: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error.message); + throw new RpcException(error.response ? error.response : error); } } @@ -138,7 +137,7 @@ export class UserService { } catch (error) { this.logger.error(`Error in sendEmailForVerification: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error.message); + throw new RpcException(error.response ? error.response : error); } } @@ -175,17 +174,18 @@ export class UserService { } } catch (error) { this.logger.error(`error in verifyEmail: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } - async createUserForToken(email: string, userInfo: userInfo): Promise { + async createUserForToken(userInfo: userInfo): Promise { try { - if (!email) { + const { email } = userInfo; + if (!userInfo.email) { throw new UnauthorizedException(ResponseMessages.user.error.invalidEmail); } - const checkUserDetails = await this.userRepository.getUserDetails(email); + const checkUserDetails = await this.userRepository.getUserDetails(userInfo.email); if (!checkUserDetails) { throw new NotFoundException(ResponseMessages.user.error.invalidEmail); } @@ -195,11 +195,11 @@ export class UserService { if (false === checkUserDetails.isEmailVerified) { throw new NotFoundException(ResponseMessages.user.error.verifyEmail); } - const resUser = await this.userRepository.updateUserInfo(email, userInfo); + const resUser = await this.userRepository.updateUserInfo(userInfo.email, userInfo); if (!resUser) { throw new NotFoundException(ResponseMessages.user.error.invalidEmail); } - const userDetails = await this.userRepository.getUserDetails(email); + const userDetails = await this.userRepository.getUserDetails(userInfo.email); if (!userDetails) { throw new NotFoundException(ResponseMessages.user.error.adduser); @@ -208,7 +208,7 @@ export class UserService { let supaUser; if (userInfo.isPasskey) { - const resUser = await this.userRepository.addUserPassword(email, userInfo.password); + const resUser = await this.userRepository.addUserPassword(email, userInfo.password); const userDetails = await this.userRepository.getUserDetails(email); const decryptedPassword = await this.commonService.decryptPassword(userDetails.password); if (!resUser) { @@ -243,7 +243,7 @@ export class UserService { return 'User created successfully'; } catch (error) { this.logger.error(`Error in createUserForToken: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -270,7 +270,7 @@ export class UserService { return 'User updated successfully'; } catch (error) { this.logger.error(`Error in createUserForToken: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -304,32 +304,36 @@ export class UserService { return this.generateToken(email, decryptedPassword); } - return this.generateToken(email, password); + return this.generateToken(email, password); } catch (error) { this.logger.error(`In Login User : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } async generateToken(email: string, password: string): Promise { - const supaInstance = await this.supabaseService.getClient(); + try { + const supaInstance = await this.supabaseService.getClient(); - this.logger.error(`supaInstance::`, supaInstance); + this.logger.error(`supaInstance::`, supaInstance); - const { data, error } = await supaInstance.auth.signInWithPassword({ - email, - password - }); + const { data, error } = await supaInstance.auth.signInWithPassword({ + email, + password + }); - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); + this.logger.error(`Supa Login Error::`, JSON.stringify(error)); - if (error) { - throw new BadRequestException(error?.message); - } + if (error) { + throw new BadRequestException(error?.message); + } - const token = data?.session; + const token = data?.session; - return token; + return token; + } catch (error) { + throw new RpcException(error.response ? error.response : error); + } } async getProfile(payload: { id }): Promise { @@ -337,7 +341,7 @@ export class UserService { return this.userRepository.getUserById(payload.id); } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -352,7 +356,7 @@ export class UserService { return userProfile; } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -361,7 +365,7 @@ export class UserService { return this.userRepository.updateUserProfile(updateUserProfileDto); } catch (error) { this.logger.error(`update user profile: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -370,7 +374,7 @@ export class UserService { return this.userRepository.getUserBySupabaseId(payload.id); } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -379,7 +383,7 @@ export class UserService { return this.userRepository.getUserBySupabaseId(payload.id); } catch (error) { this.logger.error(`Error in findSupabaseUser: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -388,7 +392,7 @@ export class UserService { return this.userRepository.findUserByEmail(payload.email); } catch (error) { this.logger.error(`findUserByEmail: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -413,7 +417,7 @@ export class UserService { return invitationsData; } catch (error) { this.logger.error(`Error in get invitations: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -473,7 +477,7 @@ export class UserService { return this.fetchInvitationsStatus(acceptRejectInvitation, userId, userData.email); } catch (error) { this.logger.error(`acceptRejectInvitations: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -513,7 +517,7 @@ export class UserService { return invitationsData; } catch (error) { this.logger.error(`Error In fetchInvitationsStatus: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -524,6 +528,7 @@ export class UserService { */ async getOrgUsers(orgId: number, pageNumber: number, pageSize: number, search: string): Promise { try { + const query = { userOrgRoles: { some: { orgId } @@ -538,10 +543,11 @@ export class UserService { const filterOptions = { orgId }; + return this.userRepository.findOrgUsers(query, pageNumber, pageSize, filterOptions); } catch (error) { this.logger.error(`get Org Users: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -563,7 +569,7 @@ export class UserService { return this.userRepository.findUsers(query, pageNumber, pageSize); } catch (error) { this.logger.error(`get Users: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -576,19 +582,22 @@ export class UserService { } else if (userDetails && userDetails.supabaseUserId) { throw new ConflictException(ResponseMessages.user.error.exists); } else if (null === userDetails) { - return 'New User'; + return { + isExist: false + }; } else { const userVerificationDetails = { isEmailVerified: userDetails.isEmailVerified, isFidoVerified: userDetails.isFidoVerified, - isSupabase: null !== userDetails.supabaseUserId && undefined !== userDetails.supabaseUserId + isSupabase: null !== userDetails.supabaseUserId && undefined !== userDetails.supabaseUserId, + isExist: true }; return userVerificationDetails; } } catch (error) { this.logger.error(`In check User : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -600,7 +609,7 @@ export class UserService { } catch (error) { this.logger.error(`In getUserActivity : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } -} +} \ No newline at end of file diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 1d265cf7a..e7cba4251 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -13,14 +13,15 @@ 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 code has been sent sucessfully to the mail. Please verify', + userActivity: 'User activities fetched successfully' }, error: { exists: 'User already exists', profileNotFound: 'User public profile not found', verificationAlreadySent: 'The verification link has already been sent to your email address', emailSend: 'Unable to send email to the user', - invalidEmailUrl: 'Invalid token or EmailId!', + invalidEmailUrl: 'Invalid verification code or EmailId!', verifiedEmail: 'Email already verified', notFound: 'User not found', verifyMail: 'Please verify your email', @@ -32,7 +33,8 @@ export const ResponseMessages = { invalidEmail: 'Invalid Email Id!', adduser: 'Unable to add user details', verifyEmail: 'The verification link has already been sent to your email address. please verify', - emailNotVerified: 'The verification link has already been sent to your email address. please verify' + emailNotVerified: 'The verification link has already been sent to your email address. please verify', + userNotRegisterd: 'The user has not yet completed the registration process' } }, organisation: { @@ -115,7 +117,7 @@ export const ResponseMessages = { }, agent: { success: { - create: 'Agent spin-up up successfully', + create: 'Agent spin-up successfully', health: 'Agent health details retrieved successfully.' }, error: { @@ -172,4 +174,4 @@ export const ResponseMessages = { emailSend: 'Unable to send email to the user' } } -}; +}; \ No newline at end of file diff --git a/libs/prisma-service/prisma/data/credebl-master-table.json b/libs/prisma-service/prisma/data/credebl-master-table.json new file mode 100644 index 000000000..35a44c6c3 --- /dev/null +++ b/libs/prisma-service/prisma/data/credebl-master-table.json @@ -0,0 +1,85 @@ +{ + "platformConfigData": { + "externalIp": "##Machine Ip Address for agent setup##", + "lastInternalId": "##Docker Network Ip Address##", + "username": "credebl", + "sgApiKey": "###Sendgrid Key###", + "emailFrom": "##Senders Mail ID##", + "apiEndpoint": "## Platform API Ip Address##", + "tailsFileServer": "##Machine Ip Address for agent setup##" + }, + "platformAdminData": { + "firstName": "CREDEBL", + "lastName": "CREDEBL", + "email": "platform.admin@yopmail.com", + "username": "platform.admin@yopmail.com", + "password": "####Please provide encrypted password using crypto-js###", + "verificationCode": "", + "isEmailVerified": true, + "supabaseUserId": "96cef763-e106-46c1-ac78-fadf2803b11f" + }, + "platformAdminOrganizationData": { + "name": "Platform-admin", + "description": "Platform-admin", + "logoUrl": "", + "website": "" + }, + "userOrgRoleData": { + "userId": 1, + "orgRoleId": 1, + "orgId": 1 + }, + "orgRoleData": [ + { + "name": "owner", + "description": "Organization Owner" + }, + { + "name": "admin", + "description": "Organization Admin" + }, + { + "name": "issuer", + "description": "Organization Credential Issuer" + }, + { + "name": "verifier", + "description": "Organization Credential Verifier" + }, + { + "name": "holder", + "description": "Receives credentials issued by organization" + }, + { + "name": "member", + "description": "Joins the organization as member" + } + ], + "agentTypeData": [ + { + "agent": "AFJ" + }, + { + "agent": "ACAPY" + } + ], + "orgAgentTypeData": [ + { + "agent": "DEDICATED" + }, + { + "agent": "SHARED" + } + ], + "ledgerData": [ + { + "name": "Bcovrin Testnet", + "networkType": "testnet", + "poolConfig": "http://test.bcovrin.vonx.io/genesis", + "isActive": true, + "networkString": "testnet", + "registerDIDEndpoint": "http://test.bcovrin.vonx.io/register", + "registerDIDPayload": "" + } + ] +} \ No newline at end of file From 8f90b4d489630b825d4851d64f7a8e8b4030bda9 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:46:10 +0530 Subject: [PATCH 014/162] refactor: schema APIs standardized (#84) * refactor: Schema APIs standardized Signed-off-by: KulkarniShashank * Resolved sonar lint checks Signed-off-by: KulkarniShashank * API refactor in schema module Signed-off-by: KulkarniShashank * API refactor in credential-definition Signed-off-by: KulkarniShashank * Added the error handling in the platform and credential-definition module Signed-off-by: KulkarniShashank * Error handling and solved the pagination Signed-off-by: KulkarniShashank * Added logger in the global exception handling Signed-off-by: KulkarniShashank * Changes as per comment on the Schema controller Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank --- apps/api-gateway/common/exception-handler.ts | 5 +- apps/api-gateway/src/app.module.ts | 2 - .../credential-definition.controller.ts | 94 +++++--- .../dto/create-cred-defs.dto.ts | 6 +- .../dto/get-all-cred-defs.dto.ts | 15 +- .../api-gateway/src/dtos/create-schema.dto.ts | 5 +- .../dtos/get-all-organizations.dto.ts | 3 +- .../dtos/get-all-sent-invitations.dto.ts | 3 +- .../src/platform/platform.controller.ts | 45 +++- .../src/platform/platform.service.ts | 59 +---- .../src/schema/dtos/get-all-schema.dto.ts | 23 +- .../src/schema/schema.controller.ts | 221 +++++++++--------- apps/api-gateway/src/schema/schema.service.ts | 13 -- .../src/user/dto/get-all-invitations.dto.ts | 3 +- .../src/user/dto/get-all-users.dto.ts | 3 +- .../credential-definition.service.ts | 6 +- apps/ledger/src/schema/schema.service.ts | 10 +- 17 files changed, 241 insertions(+), 275 deletions(-) diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index 13c102539..162712d52 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -1,8 +1,9 @@ -import { Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'; +import { Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common'; import { BaseExceptionFilter } from '@nestjs/core'; @Catch() export class CustomExceptionFilter extends BaseExceptionFilter { + private readonly logger = new Logger(); catch(exception: HttpException, host: ArgumentsHost): void { const ctx = host.switchToHttp(); const response = ctx.getResponse(); @@ -12,10 +13,12 @@ export class CustomExceptionFilter extends BaseExceptionFilter { 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 (exception && exception["statusCode"] === HttpStatus.INTERNAL_SERVER_ERROR) { errorResponse = { diff --git a/apps/api-gateway/src/app.module.ts b/apps/api-gateway/src/app.module.ts index d710b05fb..ee0657282 100644 --- a/apps/api-gateway/src/app.module.ts +++ b/apps/api-gateway/src/app.module.ts @@ -11,7 +11,6 @@ import { CredentialDefinitionModule } from './credential-definition/credential-d import { FidoModule } from './fido/fido.module'; import { IssuanceModule } from './issuance/issuance.module'; import { OrganizationModule } from './organization/organization.module'; -import { PlatformController } from './platform/platform.controller'; import { PlatformModule } from './platform/platform.module'; import { VerificationModule } from './verification/verification.module'; import { RevocationController } from './revocation/revocation.controller'; @@ -80,7 +79,6 @@ export class AppModule { ) .forRoutes( AgentController, - PlatformController, RevocationController ); } diff --git a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts index be8e25a0f..9cd7d0494 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Logger, Post, Body, UseGuards, Get, Query, HttpStatus, Res } from '@nestjs/common'; +import { Controller, Logger, Post, Body, UseGuards, Get, Query, HttpStatus, Res, Param, UseFilters } from '@nestjs/common'; import { CredentialDefinitionService } from './credential-definition.service'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiUnauthorizedResponse, ApiForbiddenResponse, ApiQuery } from '@nestjs/swagger'; import { ApiResponseDto } from 'apps/api-gateway/src/dtos/apiResponse.dto'; @@ -15,55 +15,31 @@ import { IUserRequestInterface } from './interfaces'; import { CreateCredentialDefinitionDto } from './dto/create-cred-defs.dto'; import { OrgRoles } from 'libs/org-roles/enums'; import { Roles } from '../authz/decorators/roles.decorator'; +import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @ApiBearerAuth() -@UseGuards(AuthGuard('jwt'), OrgRolesGuard) -@Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER) @ApiTags('credential-definitions') - @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) -@Controller('credential-definitions') +@Controller('orgs') +@UseFilters(CustomExceptionFilter) export class CredentialDefinitionController { constructor(private readonly credentialDefinitionService: CredentialDefinitionService) { } private readonly logger = new Logger('CredentialDefinitionController'); - @Post('/') - @ApiOperation({ - summary: 'Sends a credential definition to the ledger', - description: 'Create and sends a credential definition to the ledger.' - }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async createCredentialDefinition( - @User() user: IUserRequestInterface, - @Body() credDef: CreateCredentialDefinitionDto, - @Res() res: Response - ): Promise { - const credentialsDefinitionDetails = await this.credentialDefinitionService.createCredentialDefinition(credDef, user); - const credDefResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.credentialDefinition.success.create, - data: credentialsDefinitionDetails.response - }; - return res.status(HttpStatus.OK).json(credDefResponse); - } - @Get('/id') + @Get('/:orgId/cred-defs/:credDefId') @ApiOperation({ summary: 'Get an existing credential definition by Id', description: 'Get an existing credential definition by Id' }) - @ApiQuery( - { name: 'credentialDefinitionId', required: true } - ) - @ApiQuery( - { name: 'orgId', required: true } - ) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getCredentialDefinitionById( - @Query('credentialDefinitionId') credentialDefinitionId: string, - @Query('orgId') orgId: number, + @Param('orgId') orgId: number, + @Param('credDefId') credentialDefinitionId: string, @Res() res: Response ): Promise { const credentialsDefinitionDetails = await this.credentialDefinitionService.getCredentialDefinitionById(credentialDefinitionId, orgId); @@ -75,20 +51,39 @@ export class CredentialDefinitionController { return res.status(HttpStatus.OK).json(credDefResponse); } - @Get('/') + @Get('/:orgId/cred-defs') @ApiOperation({ summary: 'Fetch all credential definitions of provided organization id with pagination', description: 'Fetch all credential definitions from metadata saved in database of provided organization id.' }) + @ApiQuery( + { name: 'pageNumber', required: false } + ) + @ApiQuery( + { name: 'searchByText', required: false } + ) + @ApiQuery( + { name: 'pageSize', required: false } + ) + @ApiQuery( + { name: 'sorting', required: false } + ) + @ApiQuery( + { name: 'sortByValue', required: false } + ) + @ApiQuery( + { name: 'revocable', required: false } + ) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getAllCredDefs( + @Param('orgId') orgId: number, @Query() getAllCredDefs: GetAllCredDefsDto, @User() user: IUserRequestInterface, @Res() res: Response ): Promise { - const { pageSize, pageNumber, sortByValue, sorting, orgId, searchByText, revocable } = getAllCredDefs; - const credDefSearchCriteria = { pageSize, pageNumber, searchByText, sorting, sortByValue, revocable }; const credentialsDefinitionDetails = await this.credentialDefinitionService.getAllCredDefs( - credDefSearchCriteria, + getAllCredDefs, user, orgId ); @@ -99,4 +94,29 @@ export class CredentialDefinitionController { }; return res.status(HttpStatus.OK).json(credDefResponse); } + + @Post('/:orgId/cred-defs') + @ApiOperation({ + summary: 'Sends a credential definition to ledger', + description: 'Sends a credential definition to ledger' + }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + async createCredentialDefinition( + @User() user: IUserRequestInterface, + @Body() credDef: CreateCredentialDefinitionDto, + @Param('orgId') orgId: number, + @Res() res: Response + ): Promise { + + credDef.orgId = orgId; + const credentialsDefinitionDetails = await this.credentialDefinitionService.createCredentialDefinition(credDef, user); + const credDefResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.credentialDefinition.success.create, + data: credentialsDefinitionDetails.response + }; + return res.status(HttpStatus.OK).json(credDefResponse); + } } \ No newline at end of file diff --git a/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts b/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts index 0754ebfd8..3fa2024d6 100644 --- a/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts +++ b/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts @@ -1,4 +1,4 @@ -import { IsBoolean, IsDefined, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsBoolean, IsDefined, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; @@ -13,10 +13,6 @@ export class CreateCredentialDefinitionDto { @IsNotEmpty({ message: 'Please provide a schema id' }) @IsString({ message: 'Schema id should be string' }) schemaLedgerId: string; - - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'Please provide orgId' }) orgId: number; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/credential-definition/dto/get-all-cred-defs.dto.ts b/apps/api-gateway/src/credential-definition/dto/get-all-cred-defs.dto.ts index 76172f182..0de995ad7 100644 --- a/apps/api-gateway/src/credential-definition/dto/get-all-cred-defs.dto.ts +++ b/apps/api-gateway/src/credential-definition/dto/get-all-cred-defs.dto.ts @@ -2,32 +2,27 @@ /* eslint-disable camelcase */ import { ApiProperty } from '@nestjs/swagger'; import { SortValue } from '../../enum'; -import { Transform, Type } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; -import { IsNotEmpty, IsNumber, IsOptional } from 'class-validator'; +import { Type } from 'class-transformer'; +import { IsOptional } from 'class-validator'; export class GetAllCredDefsDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => Number) - @Transform(({ value }) => trim(value)) pageNumber: number = 1; @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) searchByText: string = ''; @ApiProperty({ required: false }) @IsOptional() @Type(() => Number) - @Transform(({ value }) => trim(value)) pageSize: number = 10; @ApiProperty({ required: false }) @IsOptional() - @Transform(({ value }) => trim(value)) sorting: string = 'id'; @ApiProperty({ required: false }) @@ -37,11 +32,5 @@ export class GetAllCredDefsDto { @ApiProperty({ required: false }) @IsOptional() revocable: boolean = true; - - @ApiProperty({ required: true }) - @Type(() => Number) - @IsNumber() - @IsNotEmpty() - orgId: number; } diff --git a/apps/api-gateway/src/dtos/create-schema.dto.ts b/apps/api-gateway/src/dtos/create-schema.dto.ts index 810228959..232af5a4f 100644 --- a/apps/api-gateway/src/dtos/create-schema.dto.ts +++ b/apps/api-gateway/src/dtos/create-schema.dto.ts @@ -1,4 +1,4 @@ -import { IsArray, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; @@ -22,9 +22,6 @@ export class CreateSchemaDto { @IsNotEmpty({ message: 'please provide valid attributes' }) attributes: IAttributeValue[]; - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; @ApiProperty() diff --git a/apps/api-gateway/src/organization/dtos/get-all-organizations.dto.ts b/apps/api-gateway/src/organization/dtos/get-all-organizations.dto.ts index 53d0a082f..6a3cedf44 100644 --- a/apps/api-gateway/src/organization/dtos/get-all-organizations.dto.ts +++ b/apps/api-gateway/src/organization/dtos/get-all-organizations.dto.ts @@ -1,6 +1,6 @@ import { Transform, Type } from 'class-transformer'; // import { SortValue } from '../../enum'; -import { toNumber, trim } from '@credebl/common/cast.helper'; +import { toNumber } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { IsOptional } from 'class-validator'; @@ -15,7 +15,6 @@ export class GetAllOrganizationsDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) search = ''; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/organization/dtos/get-all-sent-invitations.dto.ts b/apps/api-gateway/src/organization/dtos/get-all-sent-invitations.dto.ts index edc9eaf1c..eaa15380d 100644 --- a/apps/api-gateway/src/organization/dtos/get-all-sent-invitations.dto.ts +++ b/apps/api-gateway/src/organization/dtos/get-all-sent-invitations.dto.ts @@ -1,5 +1,5 @@ import { Transform, Type } from 'class-transformer'; -import { toNumber, trim } from '@credebl/common/cast.helper'; +import { toNumber } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { IsOptional } from 'class-validator'; @@ -14,7 +14,6 @@ export class GetAllSentInvitationsDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) search = ''; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/platform/platform.controller.ts b/apps/api-gateway/src/platform/platform.controller.ts index 9cfc655c4..26c43bbfd 100644 --- a/apps/api-gateway/src/platform/platform.controller.ts +++ b/apps/api-gateway/src/platform/platform.controller.ts @@ -1,13 +1,52 @@ -import { Controller, Logger } from '@nestjs/common'; +import { Controller, Get, HttpStatus, Logger, Query, Res, UseFilters } from '@nestjs/common'; import { PlatformService } from './platform.service'; -import { ApiBearerAuth } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiResponseDto } from '../dtos/apiResponse.dto'; +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 IResponseType from '@credebl/common/interfaces/response.interface'; +import { ResponseMessages } from '@credebl/common/response-messages'; +import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @ApiBearerAuth() -@Controller('connections') +@Controller() +@UseFilters(CustomExceptionFilter) export class PlatformController { constructor(private readonly platformService: PlatformService) { } private readonly logger = new Logger('PlatformController'); + @Get('/platform/schemas') + @ApiTags('schemas') + @ApiOperation({ + summary: 'Get all schemas from platform.', + description: 'Get all schemas from platform.' + }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + async getAllSchema( + @Query() getAllSchemaDto: GetAllSchemaByPlatformDto, + @Res() res: Response, + @User() user: IUserRequestInterface + ): Promise { + const { pageSize, searchByText, pageNumber, sorting, sortByValue } = getAllSchemaDto; + const schemaSearchCriteria: ISchemaSearchInterface = { + pageNumber, + searchByText, + pageSize, + sorting, + sortByValue + }; + const schemasResponse = await this.platformService.getAllSchema(schemaSearchCriteria, user); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.schema.success.fetch, + data: schemasResponse.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } } diff --git a/apps/api-gateway/src/platform/platform.service.ts b/apps/api-gateway/src/platform/platform.service.ts index 54a3bb353..6443fc11a 100644 --- a/apps/api-gateway/src/platform/platform.service.ts +++ b/apps/api-gateway/src/platform/platform.service.ts @@ -1,10 +1,8 @@ -import { Injectable, Inject, Logger, HttpException } from '@nestjs/common'; +import { Injectable, Inject } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from '../../../../libs/service/base.service'; -import { map } from 'rxjs/operators'; -import { CredentialListPayload, GetCredentialListByConnectionId, IConnectedHolderList, SortValue } from './platform.interface'; -import { ConnectionDto } from '../dtos/connection.dto'; -import { credentialSortBy } from '../enum'; +import { ISchemaSearchInterface } from '../interfaces/ISchemaSearch.interface'; +import { IUserRequestInterface } from '../interfaces/IUserRequestInterface'; @Injectable() export class PlatformService extends BaseService { @@ -14,52 +12,11 @@ export class PlatformService extends BaseService { super('PlatformService'); } + async getAllSchema(schemaSearchCriteria: ISchemaSearchInterface, user: IUserRequestInterface): Promise<{ + response: object; + }> { + const schemaSearch = { schemaSearchCriteria, user }; + return this.sendNats(this.platformServiceProxy, 'get-all-schemas', schemaSearch); - /** - * Description: Calling platform service for connection-invitation - * @param alias - * @param auto_accept - * @param _public - * @param multi_use - */ - createConnectionInvitation(alias: string, auto_accept: boolean, _public: boolean, multi_use: boolean) { - this.logger.log('**** createConnectionInvitation called...'); - const payload = { alias, auto_accept, _public, multi_use }; - return this.sendNats(this.platformServiceProxy, 'default-connection-invitation', payload); - } - - /** - * Description: Calling platform service for connection-list - * @param alias - * @param initiator - * @param invitation_key - * @param my_did - * @param state - * @param their_did - * @param their_role - */ - getConnections(alias: string, initiator: string, invitation_key: string, my_did: string, state: string, their_did: string, their_role: string, user: any) { - this.logger.log('**** getConnections called...'); - const payload = { alias, initiator, invitation_key, my_did, state, their_did, their_role, user }; - return this.sendNats(this.platformServiceProxy, 'connection-list', payload); - } - - pingServicePlatform() { - this.logger.log('**** pingServicePlatform called...'); - const payload = {}; - return this.sendNats(this.platformServiceProxy, 'ping-platform', payload); - } - - - connectedHolderList(itemsPerPage: number, page: number, searchText: string, orgId: number, connectionSortBy: string, sortValue: string) { - this.logger.log('**** connectedHolderList called...'); - const payload: IConnectedHolderList = { itemsPerPage, page, searchText, orgId, connectionSortBy, sortValue }; - return this.sendNats(this.platformServiceProxy, 'connected-holder-list', payload); - } - - getCredentialListByConnectionId(connectionId: string, items_per_page: number, page: number, search_text: string, sortValue: SortValue, sortBy: credentialSortBy) { - this.logger.log('**** getCredentialListByConnectionId called...'); - const payload:GetCredentialListByConnectionId = { connectionId, items_per_page, page, search_text, sortValue, sortBy }; - return this.sendNats(this.platformServiceProxy, 'get-credential-by-connection-id', payload); } } 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 af4a49838..0004e716d 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,9 +2,8 @@ /* eslint-disable camelcase */ import { ApiProperty } from '@nestjs/swagger'; import { SortValue } from '../../enum'; -import { Transform, Type } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; -import { IsNotEmpty, IsNumber, IsOptional } from 'class-validator'; +import { Type } from 'class-transformer'; +import { IsOptional } from 'class-validator'; export class GetAllSchemaDto { @ApiProperty({ required: false }) @@ -14,7 +13,6 @@ export class GetAllSchemaDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) searchByText: string = ''; @ApiProperty({ required: false }) @@ -23,19 +21,11 @@ export class GetAllSchemaDto { @ApiProperty({ required: false }) @IsOptional() - @Transform(({ value }) => trim(value)) sorting: string = 'id'; @ApiProperty({ required: false }) @IsOptional() sortByValue: string = SortValue.DESC; - - @ApiProperty({ required: true }) - @Type(() => Number) - @IsNumber() - @IsNotEmpty() - @IsOptional() - orgId?: number; } export class GetCredentialDefinitionBySchemaIdDto { @@ -56,13 +46,6 @@ export class GetCredentialDefinitionBySchemaIdDto { @ApiProperty({ required: false }) @IsOptional() sortByValue: string = SortValue.DESC; - - @ApiProperty({ required: true }) - @Type(() => Number) - @IsNumber() - @IsNotEmpty() - @IsOptional() - orgId?: number; } export class GetAllSchemaByPlatformDto { @@ -73,7 +56,6 @@ export class GetAllSchemaByPlatformDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) searchByText: string = ''; @ApiProperty({ required: false }) @@ -82,7 +64,6 @@ export class GetAllSchemaByPlatformDto { @ApiProperty({ required: false }) @IsOptional() - @Transform(({ value }) => trim(value)) sorting: string = 'id'; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/schema/schema.controller.ts b/apps/api-gateway/src/schema/schema.controller.ts index 00f4ecacc..98e829e4b 100644 --- a/apps/api-gateway/src/schema/schema.controller.ts +++ b/apps/api-gateway/src/schema/schema.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Logger, Post, Body, HttpStatus, UseGuards, Get, Query, BadRequestException, Res, UseFilters } from '@nestjs/common'; +import { Controller, Logger, Post, Body, HttpStatus, UseGuards, Get, Query, BadRequestException, Res, UseFilters, Param } from '@nestjs/common'; /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable camelcase */ import { ApiOperation, ApiResponse, ApiTags, ApiBearerAuth, ApiForbiddenResponse, ApiUnauthorizedResponse, ApiQuery } from '@nestjs/swagger'; @@ -12,17 +12,16 @@ import { Response } from 'express'; import { User } from '../authz/decorators/user.decorator'; import { ICredDeffSchemaSearchInterface, ISchemaSearchInterface } from '../interfaces/ISchemaSearch.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { GetAllSchemaByPlatformDto, GetAllSchemaDto, GetCredentialDefinitionBySchemaIdDto } from './dtos/get-all-schema.dto'; +import { GetAllSchemaDto, GetCredentialDefinitionBySchemaIdDto } from './dtos/get-all-schema.dto'; import { OrgRoles } from 'libs/org-roles/enums'; import { Roles } from '../authz/decorators/roles.decorator'; import { IUserRequestInterface } from './interfaces'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { CreateSchemaDto } from '../dtos/create-schema.dto'; -import { TransformStreamDefaultController } from 'node:stream/web'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @UseFilters(CustomExceptionFilter) -@Controller('schemas') +@Controller('orgs') @ApiTags('schemas') @ApiBearerAuth() @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @@ -32,94 +31,19 @@ export class SchemaController { ) { } private readonly logger = new Logger('SchemaController'); - @Post('/') - @ApiOperation({ - summary: 'Sends a schema to the ledger', - description: 'Create and sends a schema to the ledger.' - }) - @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER) + @Get('/:orgId/schemas/:schemaId') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async createSchema(@Res() res: Response, @Body() schema: CreateSchemaDto, @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'); - } - }); - const schemaDetails = await this.appService.createSchema(schema, user, schema.orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: 'Schema created successfully', - data: schemaDetails.response - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - - @Get('/') @ApiOperation({ - summary: 'Get all schemas by org id.', - description: 'Get all schemas by org id.' + summary: 'Get schema information from the ledger using its schema ID.', + description: 'Get schema information from the ledger using its schema ID.' }) - @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - async getSchemas( - @Query() getAllSchemaDto: GetAllSchemaDto, - @Res() res: Response, - @User() user: IUserRequestInterface - ): Promise { - - const { orgId, pageSize, searchByText, pageNumber, sorting, sortByValue } = getAllSchemaDto; - const schemaSearchCriteria: ISchemaSearchInterface = { - pageNumber, - searchByText, - pageSize, - sorting, - sortByValue - }; - const schemasResponse = await this.appService.getSchemas(schemaSearchCriteria, user, orgId); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.schema.success.fetch, - data: schemasResponse.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - - @Get('/id') - @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiOperation({ - summary: 'Retrieve an existing schema from the ledger using its schemaId', - description: 'Retrieve an existing schema from the ledger using its schemaId' - }) - @ApiQuery( - { name: 'schemaId', required: true } - ) - - @ApiQuery( - { name: 'orgId', required: true } - ) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) async getSchemaById( @Res() res: Response, - @Query('schemaId') schemaId: string, - @Query('orgId') orgId: number + @Param('orgId') orgId: number, + @Param('schemaId') schemaId: string ): Promise { if (!schemaId) { @@ -134,20 +58,37 @@ export class SchemaController { return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/credential-definitions') + @Get('/:orgId/schemas/:schemaId/cred-defs') @ApiOperation({ - summary: 'Get an existing credential definition list by schemaId', - description: 'Get an existing credential definition list by schemaId' + summary: 'Get credential definition list by schema Id', + description: 'Get credential definition list by schema Id' }) - @ApiQuery( - { name: 'schemaId', required: true } - ) - @ApiQuery( - { name: 'orgId', required: false } - ) @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 + }) + @ApiQuery({ + name: 'sortByValue', + type: String, + required: false + }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getcredDeffListBySchemaId( - @Query('schemaId') schemaId: string, + @Param('orgId') orgId: number, + @Param('schemaId') schemaId: string, @Query() GetCredentialDefinitionBySchemaIdDto: GetCredentialDefinitionBySchemaIdDto, @Res() res: Response, @User() user: IUserRequestInterface): Promise { @@ -155,14 +96,8 @@ export class SchemaController { if (!schemaId) { throw new BadRequestException(ResponseMessages.schema.error.invalidSchemaId); } - const { orgId, pageSize, pageNumber, sorting, sortByValue } = GetCredentialDefinitionBySchemaIdDto; - const schemaSearchCriteria: ICredDeffSchemaSearchInterface = { - pageNumber, - pageSize, - sorting, - sortByValue - }; - const credentialDefinitionList = await this.appService.getcredDeffListBySchemaId(schemaId, schemaSearchCriteria, user, orgId); + + const credentialDefinitionList = await this.appService.getcredDeffListBySchemaId(schemaId, GetCredentialDefinitionBySchemaIdDto, user, orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.schema.success.fetch, @@ -171,17 +106,46 @@ export class SchemaController { return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/platform') + @Get('/:orgId/schemas') @ApiOperation({ - summary: 'Get all schemas from platform.', - description: 'Get all schemas from platform.' + summary: 'Get all 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, + required: false + }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - async getAllSchema( - @Query() getAllSchemaDto: GetAllSchemaByPlatformDto, + async getSchemas( + @Query() getAllSchemaDto: GetAllSchemaDto, + @Param('orgId') orgId: number, @Res() res: Response, @User() user: IUserRequestInterface ): Promise { + const { pageSize, searchByText, pageNumber, sorting, sortByValue } = getAllSchemaDto; const schemaSearchCriteria: ISchemaSearchInterface = { pageNumber, @@ -190,7 +154,7 @@ export class SchemaController { sorting, sortByValue }; - const schemasResponse = await this.appService.getAllSchema(schemaSearchCriteria, user); + const schemasResponse = await this.appService.getSchemas(schemaSearchCriteria, user, orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, @@ -199,4 +163,43 @@ export class SchemaController { }; return res.status(HttpStatus.OK).json(finalResponse); } + + @Post('/:orgId/schemas') + @ApiOperation({ + summary: 'Create and sends a schema to the ledger.', + description: 'Create and sends a schema to the ledger.' + }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + async createSchema(@Res() res: Response, @Body() schema: CreateSchemaDto, @Param('orgId') orgId: number, @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 + }; + 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 3a260f706..ed038952e 100644 --- a/apps/api-gateway/src/schema/schema.service.ts +++ b/apps/api-gateway/src/schema/schema.service.ts @@ -59,17 +59,4 @@ export class SchemaService extends BaseService { } } - - getAllSchema(schemaSearchCriteria: ISchemaSearchInterface, user: IUserRequestInterface): Promise<{ - response: object; - }> { - try { - const schemaSearch = { schemaSearchCriteria, user }; - return this.sendNats(this.schemaServiceProxy, 'get-all-schemas', schemaSearch); - } catch (error) { - throw new RpcException(error.response); - - } - } - } \ No newline at end of file diff --git a/apps/api-gateway/src/user/dto/get-all-invitations.dto.ts b/apps/api-gateway/src/user/dto/get-all-invitations.dto.ts index 42e50d3d7..89f079f19 100644 --- a/apps/api-gateway/src/user/dto/get-all-invitations.dto.ts +++ b/apps/api-gateway/src/user/dto/get-all-invitations.dto.ts @@ -1,6 +1,6 @@ import { IsOptional, IsString } from 'class-validator'; import { Transform, Type } from 'class-transformer'; -import { toNumber, trim } from '@credebl/common/cast.helper'; +import { toNumber } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { Invitation } from '@credebl/enum/enum'; @@ -15,7 +15,6 @@ export class GetAllInvitationsDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) search = ''; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/user/dto/get-all-users.dto.ts b/apps/api-gateway/src/user/dto/get-all-users.dto.ts index 47b98aeb8..8a9bbd8b2 100644 --- a/apps/api-gateway/src/user/dto/get-all-users.dto.ts +++ b/apps/api-gateway/src/user/dto/get-all-users.dto.ts @@ -1,5 +1,5 @@ import { Transform, Type } from 'class-transformer'; -import { toNumber, trim } from '@credebl/common/cast.helper'; +import { toNumber } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { IsOptional } from 'class-validator'; @@ -15,7 +15,6 @@ export class GetAllUsersDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) search = ''; @ApiProperty({ required: false }) diff --git a/apps/ledger/src/credential-definition/credential-definition.service.ts b/apps/ledger/src/credential-definition/credential-definition.service.ts index eb8831fcf..66341747f 100644 --- a/apps/ledger/src/credential-definition/credential-definition.service.ts +++ b/apps/ledger/src/credential-definition/credential-definition.service.ts @@ -114,7 +114,7 @@ export class CredentialDefinitionService extends BaseService { this.logger.error( `Error in creating credential definition: ${JSON.stringify(error)}` ); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -180,7 +180,7 @@ export class CredentialDefinitionService extends BaseService { return credDefResponse; } catch (error) { this.logger.error(`Error retrieving credential definition with id ${payload.credentialDefinitionId}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -252,7 +252,7 @@ export class CredentialDefinitionService extends BaseService { } catch (error) { this.logger.error(`Error in retrieving credential definitions: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } diff --git a/apps/ledger/src/schema/schema.service.ts b/apps/ledger/src/schema/schema.service.ts index ae76b2129..c1816f35d 100644 --- a/apps/ledger/src/schema/schema.service.ts +++ b/apps/ledger/src/schema/schema.service.ts @@ -180,7 +180,7 @@ export class SchemaService extends BaseService { this.logger.error( `[createSchema] - outer Error: ${JSON.stringify(error)}` ); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -244,7 +244,7 @@ export class SchemaService extends BaseService { } catch (error) { this.logger.error(`Error in getting schema by id: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -322,7 +322,7 @@ export class SchemaService extends BaseService { } catch (error) { this.logger.error(`Error in retrieving schemas by org id: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -361,7 +361,7 @@ export class SchemaService extends BaseService { } catch (error) { this.logger.error(`Error in retrieving credential definition: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -410,7 +410,7 @@ export class SchemaService extends BaseService { } catch (error) { this.logger.error(`Error in retrieving all schemas: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } } From ae0ae19e3b48d8ce086da8973ad9dc100d4c1616 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Thu, 21 Sep 2023 19:58:41 +0530 Subject: [PATCH 015/162] refactor: Standardize API in connection, issuance and verification (#100) * refactor: connection, issuance, veriication API refactor Signed-off-by: KulkarniShashank * Added error handling and role guard in issuance and verification Signed-off-by: KulkarniShashank * Remove the duplication code in verification Signed-off-by: KulkarniShashank * Remove the duplication code in verification controller Signed-off-by: KulkarniShashank * Changes as per comment in PR Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank --- apps/api-gateway/common/exception-handler.ts | 7 +- .../src/connection/connection.controller.ts | 156 ++++++-------- .../src/connection/dtos/connection.dto.ts | 7 +- .../credential-definition.controller.ts | 2 +- .../credential-definition.service.ts | 29 +-- .../api-gateway/src/dtos/create-schema.dto.ts | 23 +- .../src/issuance/dtos/issuance.dto.ts | 7 +- .../src/issuance/issuance.controller.ts | 180 ++++++++-------- apps/api-gateway/src/schema/schema.service.ts | 38 +--- .../src/verification/dto/request-proof.dto.ts | 31 ++- .../verification/verification.controller.ts | 197 ++++++++++-------- apps/connection/src/connection.service.ts | 12 +- apps/issuance/src/issuance.service.ts | 11 +- apps/verification/src/verification.service.ts | 139 +++--------- libs/common/src/response-messages/index.ts | 2 +- 15 files changed, 362 insertions(+), 479 deletions(-) diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index 162712d52..5df61a442 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -18,7 +18,6 @@ export class CustomExceptionFilter extends BaseExceptionFilter { exception.message = 'Oops! Something went wrong. Please try again'; } - let errorResponse; if (exception && exception["statusCode"] === HttpStatus.INTERNAL_SERVER_ERROR) { errorResponse = { @@ -26,6 +25,12 @@ export class CustomExceptionFilter extends BaseExceptionFilter { 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) { + errorResponse = { + statusCode: exception["error"].statusCode ? exception["error"].statusCode : status, + 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, diff --git a/apps/api-gateway/src/connection/connection.controller.ts b/apps/api-gateway/src/connection/connection.controller.ts index d9ad6bf9a..d05e52845 100644 --- a/apps/api-gateway/src/connection/connection.controller.ts +++ b/apps/api-gateway/src/connection/connection.controller.ts @@ -14,41 +14,53 @@ import { Response } from 'express'; import { Connections } from './enums/connections.enum'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; +import { OrgRoles } from 'libs/org-roles/enums'; +import { Roles } from '../authz/decorators/roles.decorator'; +import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; @UseFilters(CustomExceptionFilter) @Controller() @ApiTags('connections') +@ApiBearerAuth() @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) export class ConnectionController { private readonly logger = new Logger('Connection'); constructor(private readonly connectionService: ConnectionService - ) { - /** - * Create out-of-band connection legacy invitation - * @param connectionDto - * @param res - * @returns Created out-of-band connection invitation url - */ - } - @Post('/connections') - @ApiOperation({ summary: 'Create outbound out-of-band connection (Legacy Invitation)', description: 'Create outbound out-of-band connection (Legacy Invitation)' }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() + ) { } + + /** + * Description: Get connection by connectionId + * @param user + * @param connectionId + * @param orgId + * + */ + @Get('orgs/:orgId/connections/:connectionId') + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @ApiOperation({ + summary: `Get connections by connection Id`, + description: `Get connections by connection Id` + }) @ApiResponse({ status: 200, description: 'Success', type: AuthTokenResponse }) - async createLegacyConnectionInvitation(@Body() connectionDto: CreateConnectionDto, @User() reqUser: IUserRequestInterface, @Res() res: Response): Promise { - const connectionData = await this.connectionService.createLegacyConnectionInvitation(connectionDto, reqUser); + async getConnectionsById( + @User() user: IUserRequest, + @Param('connectionId') connectionId: string, + @Param('orgId') orgId: number, + @Res() res: Response + ): Promise { + const connectionsDetails = await this.connectionService.getConnectionsById(user, connectionId, orgId); + const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.connection.success.create, - data: connectionData.response + message: ResponseMessages.connection.success.fetch, + data: connectionsDetails.response }; return res.status(HttpStatus.OK).json(finalResponse); - } - /** * Description: Get all connections * @param user @@ -58,14 +70,14 @@ export class ConnectionController { * @param orgId * */ - @Get('/connections') - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() + @Get('/orgs/:orgId/connections') + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @ApiOperation({ - summary: `Fetch all connections details`, - description: `Fetch all connections details` + summary: `Fetch all connection details`, + description: `Fetch all connection details` }) - @ApiResponse({ status: 201, description: 'Success', type: AuthTokenResponse }) + @ApiResponse({ status: 200, description: 'Success', type: AuthTokenResponse }) @ApiQuery( { name: 'outOfBandId', required: false } ) @@ -84,10 +96,6 @@ export class ConnectionController { @ApiQuery( { name: 'theirLabel', required: false } ) - @ApiQuery( - { name: 'orgId', required: true } - ) - async getConnections( @User() user: IUserRequest, @Query('outOfBandId') outOfBandId: string, @@ -96,7 +104,7 @@ export class ConnectionController { @Query('myDid') myDid: string, @Query('theirDid') theirDid: string, @Query('theirLabel') theirLabel: string, - @Query('orgId') orgId: number, + @Param('orgId') orgId: number, @Res() res: Response ): Promise { @@ -112,6 +120,34 @@ export class ConnectionController { return res.status(HttpStatus.OK).json(finalResponse); } + /** + * Create out-of-band connection legacy invitation + * @param connectionDto + * @param res + * @returns Created out-of-band connection invitation url + */ + @Post('/orgs/:orgId/connections') + @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) + @ApiResponse({ status: 201, description: 'Success', type: AuthTokenResponse }) + async createLegacyConnectionInvitation( + @Param('orgId') orgId: number, + @Body() connectionDto: CreateConnectionDto, + @User() reqUser: IUserRequestInterface, + @Res() res: Response + ): Promise { + + connectionDto.orgId = orgId; + const connectionData = await this.connectionService.createLegacyConnectionInvitation(connectionDto, reqUser); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.connection.success.create, + data: connectionData.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + + } /** * Catch connection webhook responses. @@ -134,68 +170,10 @@ export class ConnectionController { ): Promise { const connectionData = await this.connectionService.getConnectionWebhook(connectionDto, id); const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, + statusCode: HttpStatus.CREATED, message: ResponseMessages.connection.success.create, data: connectionData }; - return res.status(HttpStatus.OK).json(finalResponse); - } - - /** - * Shortening url based on reference Id. - * @param referenceId The referenceId is set as a request parameter. - * @param res The current url is set as a header in the response parameter. - */ - @Get('connections/url/:referenceId') - @ApiOperation({ - summary: 'Shortening url based on reference Id', - description: 'Shortening url based on reference Id' - }) - async getPresentproofRequestUrl( - @Param('referenceId') referenceId: string, - @Res() res: Response - ): Promise { - const originalUrlData = await this.connectionService.getUrl(referenceId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.connection.success.create, - data: originalUrlData.response - }; - return res.status(HttpStatus.OK).json(finalResponse.data); - } - - /** -* Description: Get all connections by connectionId -* @param user -* @param connectionId -* @param orgId -* -*/ - @Get('connections/:connectionId') - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiOperation({ - summary: `Fetch all connections details by connectionId`, - description: `Fetch all connections details by connectionId` - }) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiResponse({ status: 201, description: 'Success', type: AuthTokenResponse }) - async getConnectionsById( - @User() user: IUserRequest, - @Param('connectionId') connectionId: string, - @Query('orgId') orgId: number, - @Res() res: Response - ): Promise { - const connectionsDetails = await this.connectionService.getConnectionsById(user, connectionId, orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.connection.success.fetch, - data: connectionsDetails.response - }; - return res.status(HttpStatus.OK).json(finalResponse); + return res.status(HttpStatus.CREATED).json(finalResponse); } } diff --git a/apps/api-gateway/src/connection/dtos/connection.dto.ts b/apps/api-gateway/src/connection/dtos/connection.dto.ts index e8bb7c55a..14d5d8f6e 100644 --- a/apps/api-gateway/src/connection/dtos/connection.dto.ts +++ b/apps/api-gateway/src/connection/dtos/connection.dto.ts @@ -1,4 +1,4 @@ -import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; @@ -27,12 +27,9 @@ export class CreateConnectionDto { @ApiProperty() @IsBoolean() @IsOptional() - @IsNotEmpty({ message: 'please provide valid autoAcceptConnection' }) + @IsNotEmpty({ message: 'autoAcceptConnection should boolean' }) autoAcceptConnection: boolean; - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; } diff --git a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts index 9cd7d0494..6ce6c3d57 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts @@ -117,6 +117,6 @@ export class CredentialDefinitionController { message: ResponseMessages.credentialDefinition.success.create, data: credentialsDefinitionDetails.response }; - return res.status(HttpStatus.OK).json(credDefResponse); + return res.status(HttpStatus.CREATED).json(credDefResponse); } } \ No newline at end of file diff --git a/apps/api-gateway/src/credential-definition/credential-definition.service.ts b/apps/api-gateway/src/credential-definition/credential-definition.service.ts index 36f1dbf91..b66b607f0 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.service.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.service.ts @@ -1,5 +1,5 @@ import { Injectable, Inject } from '@nestjs/common'; -import { ClientProxy, RpcException } from '@nestjs/microservices'; +import { ClientProxy } from '@nestjs/microservices'; import { CreateCredentialDefinitionDto } from './dto/create-cred-defs.dto'; import { BaseService } from '../../../../libs/service/base.service'; import { IUserRequestInterface } from '../interfaces/IUserRequestInterface'; @@ -15,32 +15,17 @@ export class CredentialDefinitionService extends BaseService { } createCredentialDefinition(credDef: CreateCredentialDefinitionDto, user: IUserRequestInterface): Promise<{ response: object }> { - try { - const payload = { credDef, user }; - return this.sendNats(this.credDefServiceProxy, 'create-credential-definition', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { credDef, user }; + return this.sendNats(this.credDefServiceProxy, 'create-credential-definition', payload); } getCredentialDefinitionById(credentialDefinitionId: string, orgId: number): Promise<{ response: object }> { - try { - const payload = { credentialDefinitionId, orgId }; - return this.sendNats(this.credDefServiceProxy, 'get-credential-definition-by-id', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { credentialDefinitionId, orgId }; + return this.sendNats(this.credDefServiceProxy, 'get-credential-definition-by-id', payload); } getAllCredDefs(credDefSearchCriteria: GetAllCredDefsDto, user: IUserRequestInterface, orgId: number): Promise<{ response: object }> { - try { - const payload = { credDefSearchCriteria, user, orgId }; - return this.sendNats(this.credDefServiceProxy, 'get-all-credential-definitions', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { credDefSearchCriteria, user, orgId }; + return this.sendNats(this.credDefServiceProxy, 'get-all-credential-definitions', payload); } } diff --git a/apps/api-gateway/src/dtos/create-schema.dto.ts b/apps/api-gateway/src/dtos/create-schema.dto.ts index 232af5a4f..bedcf23a3 100644 --- a/apps/api-gateway/src/dtos/create-schema.dto.ts +++ b/apps/api-gateway/src/dtos/create-schema.dto.ts @@ -2,9 +2,18 @@ import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -interface IAttributeValue{ +class AttributeValue { + + @IsString() + @IsNotEmpty({ message: 'attributeName is required.' }) attributeName: string; + + @IsString() + @IsNotEmpty({ message: 'schemaDataType is required.' }) schemaDataType: string; + + @IsString() + @IsNotEmpty({ message: 'displayName is required.' }) displayName: string; } @@ -17,10 +26,18 @@ export class CreateSchemaDto { @IsString({ message: 'schema name must be a string' }) @IsNotEmpty({ message: 'please provide valid schema name' }) schemaName: string; - @ApiProperty() + @ApiProperty({ + 'example': [ + { + attributeName: 'name', + schemaDataType: 'string', + displayName: 'Name' + } + ] + }) @IsArray({ message: 'attributes must be an array' }) @IsNotEmpty({ message: 'please provide valid attributes' }) - attributes: IAttributeValue[]; + attributes: AttributeValue[]; orgId: number; diff --git a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts index 5deff2558..e2f6a4c37 100644 --- a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts +++ b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts @@ -1,4 +1,4 @@ -import { IsArray, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; interface attribute { @@ -29,15 +29,10 @@ export class IssueCredentialDto { @IsString({ message: 'connectionId should be string' }) connectionId: string; - @ApiProperty({ example: 'v1' }) @IsOptional() @IsNotEmpty({ message: 'Please provide valid protocol-version' }) @IsString({ message: 'protocol-version should be string' }) protocolVersion?: string; - - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; } diff --git a/apps/api-gateway/src/issuance/issuance.controller.ts b/apps/api-gateway/src/issuance/issuance.controller.ts index f0f4d9817..3bbf6b559 100644 --- a/apps/api-gateway/src/issuance/issuance.controller.ts +++ b/apps/api-gateway/src/issuance/issuance.controller.ts @@ -45,7 +45,7 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler @Controller() @UseFilters(CustomExceptionFilter) -@ApiTags('issuances') +@ApiTags('credentials') @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @@ -57,25 +57,110 @@ export class IssuanceController { ) { } private readonly logger = new Logger('IssuanceController'); + /** + * Description: Get all issued credentials + * @param user + * @param threadId + * @param connectionId + * @param state + * @param orgId + * + */ + @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: 200, description: 'Success', type: ApiResponseDto }) + @ApiQuery( + { name: 'threadId', required: false } + ) + @ApiQuery( + { name: 'connectionId', required: false } + ) + @ApiQuery( + { name: 'state', enum: IssueCredential, required: false } + ) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) + async getIssueCredentials( + @User() user: IUserRequest, + @Query('threadId') threadId: string, + @Query('connectionId') connectionId: string, + @Query('state') state: string, + @Param('orgId') orgId: number, + @Res() res: Response + ): Promise { + + const getCredentialDetails = await this.issueCredentialService.getIssueCredentials(user, threadId, connectionId, state, orgId); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.issuance.success.fetch, + data: getCredentialDetails.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** + * Description: Get all issued credentials + * @param user + * @param credentialRecordId + * @param orgId + * + */ + @Get('/orgs/:orgId/credentials/:credentialRecordId') + @ApiBearerAuth() + @ApiOperation({ + summary: `Get credential by credentialRecordId`, + description: `Get credential credentialRecordId` + }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) + async getIssueCredentialsbyCredentialRecordId( + @User() user: IUserRequest, + @Param('credentialRecordId') credentialRecordId: string, + @Param('orgId') orgId: number, + + @Res() res: Response + ): Promise { + + const getCredentialDetails = await this.issueCredentialService.getIssueCredentialsbyCredentialRecordId(user, credentialRecordId, orgId); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.issuance.success.fetch, + data: getCredentialDetails.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + /** * Description: Issuer send credential to create offer * @param user * @param issueCredentialDto */ - @Post('issue-credentials/create-offer') - @UseGuards(AuthGuard('jwt')) + @Post('/orgs/:orgId/credentials/offer') @ApiBearerAuth() @ApiOperation({ summary: `Send credential details to create-offer`, description: `Send credential details to create-offer` }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) async sendCredential( @User() user: IUserRequest, + @Param('orgId') orgId: number, @Body() issueCredentialDto: IssueCredentialDto, @Res() res: Response ): Promise { + issueCredentialDto.orgId = orgId; const attrData = issueCredentialDto.attributes; attrData.forEach((data) => { @@ -124,93 +209,4 @@ export class IssuanceController { } - /** - * Description: Get all issued credentials - * @param user - * @param threadId - * @param connectionId - * @param state - * @param orgId - * - */ - @Get('/issue-credentials') - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiOperation({ - summary: `Fetch all issued credentials`, - description: `Fetch all issued credentials` - }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @ApiQuery( - { name: 'threadId', required: false } - ) - @ApiQuery( - { name: 'connectionId', required: false } - ) - @ApiQuery( - { name: 'state', enum: IssueCredential, required: false } - ) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiBearerAuth() - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async getIssueCredentials( - @User() user: IUserRequest, - @Query('threadId') threadId: string, - @Query('connectionId') connectionId: string, - @Query('state') state: string, - @Query('orgId') orgId: number, - @Res() res: Response - ): Promise { - - state = state || undefined; - const getCredentialDetails = await this.issueCredentialService.getIssueCredentials(user, threadId, connectionId, state, orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.issuance.success.fetch, - data: getCredentialDetails.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - - - /** - * Description: Get all issued credentials - * @param user - * @param credentialRecordId - * @param orgId - * - */ - @Get('issue-credentials/:credentialRecordId') - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiOperation({ - summary: `Fetch all issued credentials by credentialRecordId`, - description: `Fetch all issued credentials by credentialRecordId` - }) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async getIssueCredentialsbyCredentialRecordId( - @User() user: IUserRequest, - @Param('credentialRecordId') credentialRecordId: string, - @Query('orgId') orgId: number, - - @Res() res: Response - ): Promise { - - const getCredentialDetails = await this.issueCredentialService.getIssueCredentialsbyCredentialRecordId(user, credentialRecordId, orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.issuance.success.fetch, - data: getCredentialDetails.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - } diff --git a/apps/api-gateway/src/schema/schema.service.ts b/apps/api-gateway/src/schema/schema.service.ts index ed038952e..333f4b5ff 100644 --- a/apps/api-gateway/src/schema/schema.service.ts +++ b/apps/api-gateway/src/schema/schema.service.ts @@ -1,5 +1,5 @@ import { Injectable, Inject } from '@nestjs/common'; -import { ClientProxy, RpcException } from '@nestjs/microservices'; +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'; @@ -15,48 +15,28 @@ export class SchemaService extends BaseService { createSchema(schema: CreateSchemaDto, user: IUserRequestInterface, orgId: number): Promise<{ response: object; }> { - try { - const payload = { schema, user, orgId }; - return this.sendNats(this.schemaServiceProxy, 'create-schema', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { schema, user, orgId }; + return this.sendNats(this.schemaServiceProxy, 'create-schema', payload); } getSchemaById(schemaId: string, orgId: number): Promise<{ response: object; }> { - try { - const payload = { schemaId, orgId }; - return this.sendNats(this.schemaServiceProxy, 'get-schema-by-id', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { schemaId, orgId }; + return this.sendNats(this.schemaServiceProxy, 'get-schema-by-id', payload); } getSchemas(schemaSearchCriteria: ISchemaSearchInterface, user: IUserRequestInterface, orgId: number): Promise<{ response: object; }> { - try { - const schemaSearch = { schemaSearchCriteria, user, orgId }; - return this.sendNats(this.schemaServiceProxy, 'get-schemas', schemaSearch); - } catch (error) { - throw new RpcException(error.response); - - } + const schemaSearch = { schemaSearchCriteria, user, orgId }; + return this.sendNats(this.schemaServiceProxy, 'get-schemas', schemaSearch); } getcredDeffListBySchemaId(schemaId: string, schemaSearchCriteria: ICredDeffSchemaSearchInterface, user: IUserRequestInterface, orgId: number): Promise<{ response: object; }> { - try { - const payload = { schemaId, schemaSearchCriteria, user, orgId }; - return this.sendNats(this.schemaServiceProxy, 'get-cred-deff-list-by-schemas-id', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { schemaId, schemaSearchCriteria, user, orgId }; + return this.sendNats(this.schemaServiceProxy, 'get-cred-deff-list-by-schemas-id', payload); } } \ No newline at end of file diff --git a/apps/api-gateway/src/verification/dto/request-proof.dto.ts b/apps/api-gateway/src/verification/dto/request-proof.dto.ts index 4a5ee350e..7081a3f58 100644 --- a/apps/api-gateway/src/verification/dto/request-proof.dto.ts +++ b/apps/api-gateway/src/verification/dto/request-proof.dto.ts @@ -1,24 +1,31 @@ -import { IsArray, IsEmail, IsNotEmpty, IsNumber, IsObject, IsOptional, IsString, MaxLength } from 'class-validator'; +import { IsArray, IsEmail, IsNotEmpty, IsObject, IsOptional, IsString, MaxLength } from 'class-validator'; import { toLowerCase, trim } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -// import { IProofRequestAttribute } from '../interfaces/verification.interface'; -class IProofRequestAttribute { +export class ProofRequestAttribute { @IsString() + @IsNotEmpty({ message: 'attributeName is required.' }) attributeName: string; @IsString() + @IsNotEmpty({ message: 'schemaId is required.' }) + schemaId: string; + + @IsString() + @IsOptional() + @IsNotEmpty({ message: 'condition is required.' }) condition?: string; @IsString() + @IsOptional() + @IsNotEmpty({ message: 'value is required.' }) value?: string; @IsString() + @IsOptional() + @IsNotEmpty({ message: 'credDefId is required.' }) credDefId?: string; - - @IsString() - schemaId: string; } export class RequestProof { @@ -43,15 +50,11 @@ export class RequestProof { @IsArray({ message: 'attributes must be in array' }) @IsObject({ each: true }) @IsNotEmpty({ message: 'please provide valid attributes' }) - attributes: IProofRequestAttribute[]; + attributes: ProofRequestAttribute[]; @ApiProperty() @IsOptional() comment: string; - - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; @IsString({ message: 'auto accept proof must be in string' }) @@ -80,7 +83,7 @@ export class OutOfBandRequestProof { @IsArray({ message: 'attributes must be in array' }) @IsObject({ each: true }) @IsNotEmpty({ message: 'please provide valid attributes' }) - attributes: IProofRequestAttribute[]; + attributes: ProofRequestAttribute[]; @ApiProperty({ example: 'string' }) @IsNotEmpty({ message: 'Please provide valid emailId' }) @@ -94,10 +97,6 @@ export class OutOfBandRequestProof { @ApiProperty() @IsOptional() comment: string; - - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; @IsString({ message: 'autoAcceptProof must be in string' }) diff --git a/apps/api-gateway/src/verification/verification.controller.ts b/apps/api-gateway/src/verification/verification.controller.ts index 328bd2540..672e8bbba 100644 --- a/apps/api-gateway/src/verification/verification.controller.ts +++ b/apps/api-gateway/src/verification/verification.controller.ts @@ -12,7 +12,7 @@ import { ApiQuery, ApiExcludeEndpoint } from '@nestjs/swagger'; -import { Controller, Logger, Post, Body, Get, Query, HttpStatus, Res, UseGuards, Param, UseFilters } from '@nestjs/common'; +import { Controller, Logger, Post, Body, Get, Query, HttpStatus, Res, UseGuards, Param, UseFilters, BadRequestException } from '@nestjs/common'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; @@ -33,33 +33,27 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler @UseFilters(CustomExceptionFilter) @ApiBearerAuth() @Controller() +@ApiTags('verifications') export class VerificationController { constructor(private readonly verificationService: VerificationService) { } private readonly logger = new Logger('VerificationController'); - @Get('/proofs/form-data') - @ApiTags('verifications') + @Get('/orgs/:orgId/proofs/:proofId/form') @ApiOperation({ summary: `Get a proof form data`, description: `Get a proof form data` }) - @ApiQuery( - { name: 'id', required: true } - ) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getProofFormData( @Res() res: Response, @GetUser() user: IUserRequest, - @Query('id') id: string, - @Query('orgId') orgId: number + @Param('proofId') id: string, + @Param('orgId') orgId: number ): Promise { const sendProofRequest = await this.verificationService.getProofFormData(id, orgId, user); const finalResponse: IResponseType = { @@ -71,75 +65,67 @@ export class VerificationController { } /** - * Get all proof presentations + * Get proof presentation by id * @param user + * @param id * @param orgId - * @returns Get all proof presentation + * @returns Get proof presentation details */ - @Get('/proofs') - @ApiTags('verifications') + @Get('/orgs/:orgId/proofs/:proofId') @ApiOperation({ - summary: `Get all proof-presentation`, - description: `Get all proof-presentation` + summary: `Get all proof presentation by proof Id`, + description: `Get all proof presentation by proof Id` }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiQuery( - { name: 'threadId', required: false } - ) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async getProofPresentations( + async getProofPresentationById( @Res() res: Response, @GetUser() user: IUserRequest, - @Query('orgId') orgId: number, - @Query('threadId') threadId: string + @Param('proofId') id: string, + @Param('orgId') orgId: number ): Promise { - const proofPresentationDetails = await this.verificationService.getProofPresentations(orgId, threadId, user); + const getProofPresentationById = await this.verificationService.getProofPresentationById(id, orgId, user); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.verification.success.fetch, - data: proofPresentationDetails.response + data: getProofPresentationById.response }; return res.status(HttpStatus.OK).json(finalResponse); } /** - * Get proof presentation by id - * @param user - * @param id - * @param orgId - * @returns Get proof presentation details - */ - @Get('/proofs/:id') - @ApiTags('verifications') + * Get all proof presentations + * @param user + * @param orgId + * @returns Get all proof presentation + */ + @Get('/orgs/:orgId/proofs') @ApiOperation({ - summary: `Get proof-presentation by Id`, - description: `Get proof-presentation by Id` + summary: `Get all proof presentations`, + description: `Get all proof presentations` }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiQuery( - { name: 'orgId', required: true } + { name: 'threadId', required: false } ) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async getProofPresentationById( + async getProofPresentations( @Res() res: Response, @GetUser() user: IUserRequest, - @Param('id') id: string, - @Query('orgId') orgId: number + @Param('orgId') orgId: number, + @Query('threadId') threadId: string ): Promise { - const getProofPresentationById = await this.verificationService.getProofPresentationById(id, orgId, user); + const proofPresentationDetails = await this.verificationService.getProofPresentations(orgId, threadId, user); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.verification.success.fetch, - data: getProofPresentationById.response + data: proofPresentationDetails.response }; return res.status(HttpStatus.OK).json(finalResponse); } @@ -150,8 +136,7 @@ export class VerificationController { * @param requestProof * @returns Get requested proof presentation details */ - @Post('/proofs/request-proof') - @ApiTags('verifications') + @Post('/orgs/:orgId/proofs') @ApiOperation({ summary: `Sends a proof request`, description: `Sends a proof request` @@ -160,13 +145,20 @@ export class VerificationController { @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiBody({ type: RequestProof }) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) async sendPresentationRequest( @Res() res: Response, @GetUser() user: IUserRequest, + @Param('orgId') orgId: number, @Body() requestProof: RequestProof ): Promise { + + for (const attrData of requestProof.attributes) { + await this.validateAttribute(attrData); + } + + requestProof.orgId = orgId; const sendProofRequest = await this.verificationService.sendProofRequest(requestProof, user); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -183,8 +175,7 @@ export class VerificationController { * @param orgId * @returns Get verified proof presentation details */ - @Post('proofs/verify-presentation') - @ApiTags('verifications') + @Post('/orgs/:orgId/proofs/:proofId/verify') @ApiOperation({ summary: `Verify presentation`, description: `Verify presentation` @@ -192,19 +183,13 @@ export class VerificationController { @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiQuery( - { name: 'id', required: true } - ) - @ApiQuery( - { name: 'orgId', required: true } - ) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async verifyPresentation( @Res() res: Response, @GetUser() user: IUserRequest, - @Query('id') id: string, - @Query('orgId') orgId: number + @Param('proofId') id: string, + @Param('orgId') orgId: number ): Promise { const verifyPresentation = await this.verificationService.verifyPresentation(id, orgId, user); const finalResponse: IResponseType = { @@ -215,39 +200,13 @@ export class VerificationController { return res.status(HttpStatus.CREATED).json(finalResponse); } - @Post('wh/:id/proofs') - @ApiTags('verifications') - @ApiOperation({ - summary: `Webhook proof presentation`, - description: `Webhook proof presentation` - }) - @ApiExcludeEndpoint() - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) - async webhookProofPresentation( - @Param('id') id: string, - @Body() proofPresentationPayload: WebhookPresentationProof, - @Res() res: Response - ): Promise { - - const webhookProofPresentation = await this.verificationService.webhookProofPresentation(id, proofPresentationPayload); - const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.verification.success.fetch, - data: webhookProofPresentation.response - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - /** * Out-Of-Band Proof Presentation * @param user * @param outOfBandRequestProof * @returns Get out-of-band requested proof presentation details */ - @Post('/proofs/create-request-oob') - @ApiTags('verifications') + @Post('/orgs/:orgId/proofs/oob') @ApiOperation({ summary: `Sends a out-of-band proof request`, description: `Sends a out-of-band proof request` @@ -261,8 +220,15 @@ export class VerificationController { async sendOutOfBandPresentationRequest( @Res() res: Response, @GetUser() user: IUserRequest, - @Body() outOfBandRequestProof: OutOfBandRequestProof + @Body() outOfBandRequestProof: OutOfBandRequestProof, + @Param('orgId') orgId: number ): Promise { + + for (const attrData of outOfBandRequestProof.attributes) { + await this.validateAttribute(attrData); + } + + outOfBandRequestProof.orgId = orgId; const sendProofRequest = await this.verificationService.sendOutOfBandPresentationRequest(outOfBandRequestProof, user); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -271,5 +237,52 @@ export class VerificationController { }; return res.status(HttpStatus.CREATED).json(finalResponse); } + + @Post('wh/:id/proofs') + @ApiOperation({ + summary: `Webhook proof presentation`, + description: `Webhook proof presentation` + }) + @ApiExcludeEndpoint() + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) + async webhookProofPresentation( + @Param('id') id: string, + @Body() proofPresentationPayload: WebhookPresentationProof, + @Res() res: Response + ): Promise { + + const webhookProofPresentation = await this.verificationService.webhookProofPresentation(id, proofPresentationPayload); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.verification.success.fetch, + data: webhookProofPresentation.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + async validateAttribute( + attrData: object + ): Promise { + + if (!attrData['attributeName']) { + throw new BadRequestException('attributeName must be required'); + } else if (!attrData['schemaId']) { + throw new BadRequestException('schemaId must be required'); + } + + if (undefined !== attrData['credDefId'] && '' === attrData['credDefId'].trim()) { + throw new BadRequestException('credDefId cannot be empty'); + } + + if (undefined !== attrData['condition'] && '' === attrData['condition'].trim()) { + throw new BadRequestException('condition cannot be empty'); + } + + if (undefined !== attrData['value'] && '' === attrData['value'].trim()) { + throw new BadRequestException('value cannot be empty'); + } + } } diff --git a/apps/connection/src/connection.service.ts b/apps/connection/src/connection.service.ts index 51756a352..78b149be1 100644 --- a/apps/connection/src/connection.service.ts +++ b/apps/connection/src/connection.service.ts @@ -78,7 +78,7 @@ export class ConnectionService { return saveConnectionDetails; } catch (error) { this.logger.error(`[createLegacyConnectionInvitation] - error in connection invitation: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -97,7 +97,7 @@ export class ConnectionService { return saveConnectionDetails; } catch (error) { this.logger.error(`[getConnectionWebhook] - error in fetch connection webhook: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -148,8 +148,7 @@ export class ConnectionService { return urlDetails.url; } catch (error) { this.logger.error(`Error in get url in connection service: ${JSON.stringify(error)}`); - throw error; - + throw new RpcException(error.response ? error.response : error); } } @@ -209,8 +208,7 @@ export class ConnectionService { return connectionsDetails?.response; } catch (error) { this.logger.error(`Error in get url in connection service: ${JSON.stringify(error)}`); - throw error; - + throw new RpcException(error.response ? error.response : error); } } @@ -271,7 +269,7 @@ export class ConnectionService { return createConnectionInvitation?.response; } catch (error) { this.logger.error(`[getConnectionsById] - error in get connections : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index f6caddcd5..1760c97fc 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -55,7 +55,7 @@ export class IssuanceService { return credentialCreateOfferDetails?.response; } catch (error) { this.logger.error(`[sendCredentialCreateOffer] - error in create credentials : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } @@ -90,7 +90,7 @@ export class IssuanceService { return credentialCreateOfferDetails?.response; } catch (error) { this.logger.error(`[sendCredentialCreateOffer] - error in create credentials : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } @@ -152,7 +152,7 @@ export class IssuanceService { return issueCredentialsDetails?.response; } catch (error) { this.logger.error(`[sendCredentialCreateOffer] - error in create credentials : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -203,7 +203,7 @@ export class IssuanceService { return createConnectionInvitation?.response; } catch (error) { this.logger.error(`[getIssueCredentialsbyCredentialRecordId] - error in get credentials : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } @@ -213,7 +213,7 @@ export class IssuanceService { return agentDetails; } catch (error) { this.logger.error(`[getIssueCredentialsbyCredentialRecordId] - error in get credentials : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } @@ -311,7 +311,6 @@ export class IssuanceService { } catch (error) { this.logger.error(`Error in get agent url: ${JSON.stringify(error)}`); throw error; - } } } diff --git a/apps/verification/src/verification.service.ts b/apps/verification/src/verification.service.ts index c0b57c04f..b7fbad1e0 100644 --- a/apps/verification/src/verification.service.ts +++ b/apps/verification/src/verification.service.ts @@ -49,7 +49,7 @@ export class VerificationService { } catch (error) { this.logger.error(`[getProofPresentations] - error in get proof presentation : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -109,7 +109,7 @@ export class VerificationService { return getProofPresentationById?.response; } catch (error) { this.logger.error(`[getProofPresentationById] - error in get proof presentation by id : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -157,8 +157,6 @@ export class VerificationService { */ async sendProofRequest(requestProof: IRequestProof): Promise { try { - let requestedAttributes = {}; - const requestedPredicates = {}; const comment = requestProof.comment ? requestProof.comment : ''; let proofRequestPayload: ISendProofRequestPayload = { @@ -176,73 +174,7 @@ export class VerificationService { autoAcceptProof: '' }; - const attributeWithSchemaIdExists = requestProof.attributes.some(attribute => attribute.schemaId); - if (attributeWithSchemaIdExists) { - requestedAttributes = Object.fromEntries(requestProof.attributes.map((attribute, index) => { - - const attributeElement = attribute.attributeName; - const attributeReferent = `additionalProp${index + 1}`; - - if (!attribute.condition && !attribute.value) { - const keys = Object.keys(requestedAttributes); - - if (0 < keys.length) { - let attributeFound = false; - - for (const attr of keys) { - if ( - requestedAttributes[attr].restrictions.some(res => res.schema_id) === - requestProof.attributes[index].schemaId - ) { - requestedAttributes[attr].name.push(attributeElement); - attributeFound = true; - } - - if (attr === keys[keys.length - 1] && !attributeFound) { - requestedAttributes[attributeReferent] = { - name: attributeElement, - restrictions: [ - { - cred_def_id: requestProof.attributes[index].credDefId ? requestProof.attributes[index].credDefId : undefined, - schema_id: requestProof.attributes[index].schemaId - } - ] - }; - } - } - } else { - return [ - attributeReferent, - { - name: attributeElement, - restrictions: [ - { - cred_def_id: requestProof.attributes[index].credDefId ? requestProof.attributes[index].credDefId : undefined, - schema_id: requestProof.attributes[index].schemaId - } - ] - } - ]; - } - } else { - requestedPredicates[attributeReferent] = { - p_type: attribute.condition, - restrictions: [ - { - cred_def_id: requestProof.attributes[index].credDefId ? requestProof.attributes[index].credDefId : undefined, - schema_id: requestProof.attributes[index].schemaId - } - ], - name: attributeElement, - p_value: parseInt(attribute.value) - }; - } - - return [attributeReferent]; - })); - } else { - throw new BadRequestException(ResponseMessages.verification.error.schemaIdNotFound); - } + const { requestedAttributes, requestedPredicates } = await this._proofRequestPayload(requestProof); proofRequestPayload = { protocolVersion: requestProof.protocolVersion ? requestProof.protocolVersion : 'v1', @@ -272,7 +204,7 @@ export class VerificationService { return getProofPresentationById?.response; } catch (error) { this.logger.error(`[verifyPresentation] - error in verify presentation : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -330,7 +262,7 @@ export class VerificationService { return getProofPresentationById?.response; } catch (error) { this.logger.error(`[verifyPresentation] - error in verify presentation : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -378,7 +310,7 @@ export class VerificationService { } catch (error) { this.logger.error(`[webhookProofPresentation] - error in webhook proof presentation : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -487,7 +419,7 @@ export class VerificationService { } catch (error) { this.logger.error(`[sendOutOfBandPresentationRequest] - error in out of band proof request : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -536,9 +468,8 @@ export class VerificationService { const requestedPredicates = {}; const attributeWithSchemaIdExists = proofRequestpayload.attributes.some(attribute => attribute.schemaId); if (attributeWithSchemaIdExists) { + requestedAttributes = Object.fromEntries(proofRequestpayload.attributes.map((attribute, index) => { - requestedAttributes = {}; - for (const [index, attribute] of proofRequestpayload.attributes.entries()) { const attributeElement = attribute.attributeName; const attributeReferent = `additionalProp${index + 1}`; @@ -550,9 +481,8 @@ export class VerificationService { for (const attr of keys) { if ( - requestedAttributes[attr].restrictions.some( - res => res.schema_id === proofRequestpayload.attributes[index].schemaId - ) + requestedAttributes[attr].restrictions.some(res => res.schema_id) === + proofRequestpayload.attributes[index].schemaId ) { requestedAttributes[attr].name.push(attributeElement); attributeFound = true; @@ -563,9 +493,7 @@ export class VerificationService { name: attributeElement, restrictions: [ { - cred_def_id: proofRequestpayload.attributes[index].credDefId - ? proofRequestpayload.attributes[index].credDefId - : undefined, + cred_def_id: proofRequestpayload.attributes[index].credDefId ? proofRequestpayload.attributes[index].credDefId : undefined, schema_id: proofRequestpayload.attributes[index].schemaId } ] @@ -573,32 +501,25 @@ export class VerificationService { } } } else { - requestedAttributes[attributeReferent] = { - name: attributeElement, - restrictions: [ - { - cred_def_id: proofRequestpayload.attributes[index].credDefId - ? proofRequestpayload.attributes[index].credDefId - : undefined, - schema_id: proofRequestpayload.attributes[index].schemaId - } - ] - }; + return [ + attributeReferent, + { + name: attributeElement, + restrictions: [ + { + cred_def_id: proofRequestpayload.attributes[index].credDefId ? proofRequestpayload.attributes[index].credDefId : undefined, + schema_id: proofRequestpayload.attributes[index].schemaId + } + ] + } + ]; } } else { - if (isNaN(parseInt(attribute.value))) { - throw new BadRequestException( - ResponseMessages.verification.error.predicatesValueNotNumber - ); - } - requestedPredicates[attributeReferent] = { p_type: attribute.condition, restrictions: [ { - cred_def_id: proofRequestpayload.attributes[index].credDefId - ? proofRequestpayload.attributes[index].credDefId - : undefined, + cred_def_id: proofRequestpayload.attributes[index].credDefId ? proofRequestpayload.attributes[index].credDefId : undefined, schema_id: proofRequestpayload.attributes[index].schemaId } ], @@ -606,20 +527,20 @@ export class VerificationService { p_value: parseInt(attribute.value) }; } - } + + return [attributeReferent]; + })); return { requestedAttributes, requestedPredicates }; } else { - throw new BadRequestException( - ResponseMessages.verification.error.schemaIdNotFound - ); + throw new BadRequestException(ResponseMessages.verification.error.schemaIdNotFound); } } catch (error) { this.logger.error(`[proofRequestPayload] - error in proof request payload : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -815,7 +736,7 @@ export class VerificationService { return extractedDataArray; } catch (error) { this.logger.error(`[getProofFormData] - error in get proof form data : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index e7cba4251..4f9dd4b5e 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -133,7 +133,7 @@ export const ResponseMessages = { connection: { success: { create: 'Connection created successfully', - fetch: 'Connection Details fetched successfully' + fetch: 'Connection fetched successfully' }, error: { exists: 'Connection is already exist', From 31ba4f7bf275fa4527a8836e26e4222f4f3d639b Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Tue, 26 Sep 2023 19:46:07 +0530 Subject: [PATCH 016/162] feat: support afj-0.4.1 (#102) Signed-off-by: KulkarniShashank --- .env.sample | 2 +- apps/agent-provisioning/AFJ/scripts/start_agent.sh | 2 +- apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh | 2 +- apps/api-gateway/src/connection/connection.controller.ts | 1 + apps/api-gateway/src/issuance/issuance.controller.ts | 1 + apps/api-gateway/src/verification/verification.controller.ts | 2 +- 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.env.sample b/.env.sample index 1739e22e2..9e9e35b11 100644 --- a/.env.sample +++ b/.env.sample @@ -37,7 +37,7 @@ WALLET_STORAGE_PASSWORD=xxxxxx CRYPTO_PRIVATE_KEY=xxxxx-xxxxx-xxxxx-xxxxx #It should be same as studio UI -AFJ_VERSION=afj-0.4.0:latest +AFJ_VERSION=afj-0.4.1:latest FIDO_API_ENDPOINT=http://localhost:8000 # Host:port of your FIDO (WebAuthn) Server diff --git a/apps/agent-provisioning/AFJ/scripts/start_agent.sh b/apps/agent-provisioning/AFJ/scripts/start_agent.sh index 881ed70d6..899229c80 100755 --- a/apps/agent-provisioning/AFJ/scripts/start_agent.sh +++ b/apps/agent-provisioning/AFJ/scripts/start_agent.sh @@ -48,7 +48,7 @@ cat <>${PWD}/apps/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINE "label": "${AGENCY}_${CONTAINER_NAME}", "walletId": "$WALLET_NAME", "walletKey": "$WALLET_PASSWORD", - "walletType": "postgres_storage", + "walletType": "postgres", "walletUrl": "$WALLET_STORAGE_HOST:$WALLET_STORAGE_PORT", "walletAccount": "$WALLET_STORAGE_USER", "walletPassword": "$WALLET_STORAGE_PASSWORD", diff --git a/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh b/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh index f757e53bb..db3de117f 100644 --- a/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh +++ b/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh @@ -38,7 +38,7 @@ cat <>/app/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINER_NAME} "label": "${AGENCY}_${CONTAINER_NAME}", "walletId": "$WALLET_NAME", "walletKey": "$WALLET_PASSWORD", - "walletType": "postgres_storage", + "walletType": "postgres", "walletUrl": "$WALLET_STORAGE_HOST:$WALLET_STORAGE_PORT", "walletAccount": "$WALLET_STORAGE_USER", "walletPassword": "$WALLET_STORAGE_PASSWORD", diff --git a/apps/api-gateway/src/connection/connection.controller.ts b/apps/api-gateway/src/connection/connection.controller.ts index d05e52845..d87f58721 100644 --- a/apps/api-gateway/src/connection/connection.controller.ts +++ b/apps/api-gateway/src/connection/connection.controller.ts @@ -168,6 +168,7 @@ export class ConnectionController { @Param('id') id: number, @Res() res: Response ): Promise { + this.logger.debug(`connectionDto ::: ${JSON.stringify(connectionDto)}`); const connectionData = await this.connectionService.getConnectionWebhook(connectionDto, id); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, diff --git a/apps/api-gateway/src/issuance/issuance.controller.ts b/apps/api-gateway/src/issuance/issuance.controller.ts index 3bbf6b559..ba130ad24 100644 --- a/apps/api-gateway/src/issuance/issuance.controller.ts +++ b/apps/api-gateway/src/issuance/issuance.controller.ts @@ -199,6 +199,7 @@ export class IssuanceController { @Param('id') id: number, @Res() res: Response ): Promise { + this.logger.debug(`issueCredentialDto ::: ${issueCredentialDto}`); const getCredentialDetails = await this.issueCredentialService.getIssueCredentialWebhook(issueCredentialDto, id); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, diff --git a/apps/api-gateway/src/verification/verification.controller.ts b/apps/api-gateway/src/verification/verification.controller.ts index 672e8bbba..f9f555c52 100644 --- a/apps/api-gateway/src/verification/verification.controller.ts +++ b/apps/api-gateway/src/verification/verification.controller.ts @@ -252,7 +252,7 @@ export class VerificationController { @Body() proofPresentationPayload: WebhookPresentationProof, @Res() res: Response ): Promise { - + this.logger.debug(`proofPresentationPayload ::: ${JSON.stringify(proofPresentationPayload)}`); const webhookProofPresentation = await this.verificationService.webhookProofPresentation(id, proofPresentationPayload); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, From 47d5dfed82b3660aaff10eb1322e7b7c70b0131b Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Wed, 27 Sep 2023 16:09:21 +0530 Subject: [PATCH 017/162] Fix bugs platform module (#103) * feat: support afj-0.4.1 Signed-off-by: KulkarniShashank * fix: Add nats configuration in platform Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank --- apps/api-gateway/src/platform/platform.module.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/api-gateway/src/platform/platform.module.ts b/apps/api-gateway/src/platform/platform.module.ts index ba0ffe2ec..692fe9918 100644 --- a/apps/api-gateway/src/platform/platform.module.ts +++ b/apps/api-gateway/src/platform/platform.module.ts @@ -3,7 +3,6 @@ import { PlatformController } from './platform.controller'; import { PlatformService } from './platform.service'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { ConfigModule } from '@nestjs/config'; -import { commonNatsOptions } from 'libs/service/nats.options'; @Module({ imports: [ @@ -11,10 +10,12 @@ import { commonNatsOptions } from 'libs/service/nats.options'; ClientsModule.register([ { name: 'NATS_CLIENT', - ...commonNatsOptions('AGENT_SERVICE:REQUESTER') + transport: Transport.NATS, + options: { + servers: [`${process.env.NATS_URL}`] + } } ]) - ], controllers: [PlatformController], providers: [PlatformService] From 6ce9b213e8e083762889e2e01e766c3f8c2a9f9a Mon Sep 17 00:00:00 2001 From: tipusinghaw <126460794+tipusinghaw@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:33:45 +0530 Subject: [PATCH 018/162] feat: ecosystem ms setup (#104) * worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat * merge dev branch to main (#77) * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw * feat:implemented add passke Signed-off-by: tipusinghaw * fix: login error message Signed-off-by: tipusinghaw --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw * Included credebl-master-table json file in the .gitignore Signed-off-by: @nishad.shirsat * Create ecosystem monorepo Signed-off-by: KulkarniShashank * feat: Implemented ecosystem microservice Signed-off-by: tipusinghaw * feat: changed controller name Signed-off-by: tipusinghaw --------- Signed-off-by: tipusinghaw Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank Co-authored-by: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Co-authored-by: @nishad.shirsat Co-authored-by: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Co-authored-by: KulkarniShashank --- apps/api-gateway/src/app.module.ts | 4 +- .../ecosystem/dtos/create-organization-dto.ts | 36 ++++++++++++++++ .../src/ecosystem/ecosystem.controller.ts | 43 +++++++++++++++++++ .../src/ecosystem/ecosystem.module.ts | 29 +++++++++++++ .../src/ecosystem/ecosystem.service.ts | 23 ++++++++++ .../src/ecosystem.controller.spec.ts | 22 ++++++++++ apps/ecosystem/src/ecosystem.controller.ts | 24 +++++++++++ apps/ecosystem/src/ecosystem.module.ts | 26 +++++++++++ apps/ecosystem/src/ecosystem.service.ts | 20 +++++++++ apps/ecosystem/src/main.ts | 23 ++++++++++ apps/ecosystem/test/app.e2e-spec.ts | 24 +++++++++++ apps/ecosystem/test/jest-e2e.json | 9 ++++ apps/ecosystem/tsconfig.app.json | 9 ++++ libs/common/src/response-messages/index.ts | 11 ++++- nest-cli.json | 9 ++++ 15 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts create mode 100644 apps/api-gateway/src/ecosystem/ecosystem.controller.ts create mode 100644 apps/api-gateway/src/ecosystem/ecosystem.module.ts create mode 100644 apps/api-gateway/src/ecosystem/ecosystem.service.ts create mode 100644 apps/ecosystem/src/ecosystem.controller.spec.ts create mode 100644 apps/ecosystem/src/ecosystem.controller.ts create mode 100644 apps/ecosystem/src/ecosystem.module.ts create mode 100644 apps/ecosystem/src/ecosystem.service.ts create mode 100644 apps/ecosystem/src/main.ts create mode 100644 apps/ecosystem/test/app.e2e-spec.ts create mode 100644 apps/ecosystem/test/jest-e2e.json create mode 100644 apps/ecosystem/tsconfig.app.json diff --git a/apps/api-gateway/src/app.module.ts b/apps/api-gateway/src/app.module.ts index ee0657282..792999579 100644 --- a/apps/api-gateway/src/app.module.ts +++ b/apps/api-gateway/src/app.module.ts @@ -19,6 +19,7 @@ import { SchemaModule } from './schema/schema.module'; import { commonNatsOptions } from 'libs/service/nats.options'; import { UserModule } from './user/user.module'; import { ConnectionModule } from './connection/connection.module'; +import { EcosystemModule } from './ecosystem/ecosystem.module'; @Module({ imports: [ @@ -40,7 +41,8 @@ import { ConnectionModule } from './connection/connection.module'; OrganizationModule, UserModule, ConnectionModule, - IssuanceModule + IssuanceModule, + EcosystemModule ], controllers: [AppController], providers: [AppService] diff --git a/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts b/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts new file mode 100644 index 000000000..d584c5832 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts @@ -0,0 +1,36 @@ +import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; + +import { Transform } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; + +@ApiExtraModels() +export class CreateEcosystemDto { + + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Organization name is required.' }) + @MinLength(2, { message: 'Organization name must be at least 2 characters.' }) + @MaxLength(50, { message: 'Organization name must be at most 50 characters.' }) + @IsString({ message: 'Organization name must be in string format.' }) + name: string; + + @ApiPropertyOptional() + @Transform(({ value }) => trim(value)) + @MinLength(2, { message: 'Description must be at least 2 characters.' }) + @MaxLength(255, { message: 'Description must be at most 255 characters.' }) + @IsString({ message: 'Description must be in string format.' }) + description: string; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'logo must be in string format.' }) + logo: string; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + website?: 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 new file mode 100644 index 000000000..6cf86ee4f --- /dev/null +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -0,0 +1,43 @@ +import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; +import { Controller, UseGuards, UseFilters } from '@nestjs/common'; +import { EcosystemService } from './ecosystem.service'; +import { Post } from '@nestjs/common'; +import { Body } from '@nestjs/common'; +import { Res } from '@nestjs/common'; +import { CreateEcosystemDto } from './dtos/create-organization-dto'; +import IResponseType from '@credebl/common/interfaces/response.interface'; +import { HttpStatus } from '@nestjs/common'; +import { Response } from 'express'; +import { ApiResponseDto } from '../dtos/apiResponse.dto'; +import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; +import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; +import { AuthGuard } from '@nestjs/passport'; +import { ResponseMessages } from '@credebl/common/response-messages'; +import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; + + +@UseFilters(CustomExceptionFilter) +@Controller('ecosystem') +@ApiTags('ecosystem') +@ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) +export class EcosystemController { + constructor( + private readonly ecosystemService: EcosystemService + ) { } + + + @Post('/') + @ApiOperation({ summary: 'Create a new ecosystem', description: 'Create an ecosystem' }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async createOrganization(@Body() createOrgDto: CreateEcosystemDto, @Res() res: Response): Promise { + await this.ecosystemService.createEcosystem(createOrgDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.create + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } +} \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/ecosystem.module.ts b/apps/api-gateway/src/ecosystem/ecosystem.module.ts new file mode 100644 index 000000000..24a59ec00 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/ecosystem.module.ts @@ -0,0 +1,29 @@ +import { CommonModule, CommonService } from '@credebl/common'; + +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { ConfigModule } from '@nestjs/config'; +import { HttpModule } from '@nestjs/axios'; +import { Module } from '@nestjs/common'; +import { EcosystemController } from './ecosystem.controller'; +import { EcosystemService } from './ecosystem.service'; + +@Module({ + imports: [ + HttpModule, + ConfigModule.forRoot(), + ClientsModule.register([ + { + name: 'NATS_CLIENT', + transport: Transport.NATS, + options: { + servers: [`${process.env.NATS_URL}`] + } + }, + CommonModule + ]) + ], + controllers: [EcosystemController], + providers: [EcosystemService, CommonService] +}) +export class EcosystemModule { } + diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts new file mode 100644 index 000000000..4ce9cba9a --- /dev/null +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -0,0 +1,23 @@ +import { Inject } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; +import { BaseService } from 'libs/service/base.service'; + + +@Injectable() +export class EcosystemService extends BaseService { + constructor(@Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy) { + super('EcosystemService'); + } + + /** + * + * @param createEcosystemDto + * @returns Ecosystem creation success + */ + async createEcosystem(createEcosystemDto): Promise { + const payload = { createEcosystemDto }; + return this.sendNats(this.serviceProxy, 'create-ecosystem', payload); + } + +} diff --git a/apps/ecosystem/src/ecosystem.controller.spec.ts b/apps/ecosystem/src/ecosystem.controller.spec.ts new file mode 100644 index 000000000..653289261 --- /dev/null +++ b/apps/ecosystem/src/ecosystem.controller.spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { EcosystemController } from './ecosystem.controller'; +import { EcosystemService } from './ecosystem.service'; + +describe('EcosystemController', () => { + let ecosystemController: EcosystemController; + + beforeEach(async () => { + const app: TestingModule = await Test.createTestingModule({ + controllers: [EcosystemController], + providers: [EcosystemService] + }).compile(); + + ecosystemController = app.get(EcosystemController); + }); + + describe('root', () => { + it('should return "Hello World!"', () => { + expect(ecosystemController.getHello()).toBe('Hello World!'); + }); + }); +}); diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts new file mode 100644 index 000000000..55a91e22d --- /dev/null +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -0,0 +1,24 @@ +import { Controller, Logger } from '@nestjs/common'; + +import { MessagePattern } from '@nestjs/microservices'; +import { EcosystemService } from './ecosystem.service'; +import { Body } from '@nestjs/common'; + +@Controller() +export class EcosystemController { + constructor(private readonly ecosystemService: EcosystemService) {} + private readonly logger = new Logger('EcosystemController'); + + /** + * Description: create new ecosystem + * @param payload Registration Details + * @returns Get created ecosystem details + */ + + @MessagePattern({ cmd: 'create-ecosystem' }) + async createEcosystem(@Body() payload: { createOrgDto; userId }): Promise { + this.logger.log(`EcosystemPayload : ${payload}`); + return this.ecosystemService.createEcosystem(); + } + +} diff --git a/apps/ecosystem/src/ecosystem.module.ts b/apps/ecosystem/src/ecosystem.module.ts new file mode 100644 index 000000000..c749922c7 --- /dev/null +++ b/apps/ecosystem/src/ecosystem.module.ts @@ -0,0 +1,26 @@ +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 { ConnectionRepository } from './connection.repository'; +import { PrismaService } from '@credebl/prisma-service'; + +@Module({ + imports: [ + ClientsModule.register([ + { + name: 'NATS_CLIENT', + transport: Transport.NATS, + options: { + servers: [`${process.env.NATS_URL}`] + } + } + ]), + + CommonModule + ], + controllers: [EcosystemController], + providers: [EcosystemService, PrismaService, Logger] +}) +export class EcosystemModule { } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts new file mode 100644 index 000000000..f68e93329 --- /dev/null +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -0,0 +1,20 @@ +// eslint-disable-next-line camelcase +import { Injectable} from '@nestjs/common'; + +@Injectable() +export class EcosystemService { + constructor( + ) { } + + /** + * + * @param registerOrgDto + * @returns + */ + + // eslint-disable-next-line camelcase + async createEcosystem():Promise { + return "test"; + } + +} diff --git a/apps/ecosystem/src/main.ts b/apps/ecosystem/src/main.ts new file mode 100644 index 000000000..6bd3f0259 --- /dev/null +++ b/apps/ecosystem/src/main.ts @@ -0,0 +1,23 @@ +import { NestFactory } from '@nestjs/core'; +import { EcosystemModule } from './ecosystem.module'; +import { HttpExceptionFilter } from 'libs/http-exception.filter'; +import { Logger } from '@nestjs/common'; +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; + +const logger = new Logger(); + +async function bootstrap(): Promise { + + const app = await NestFactory.createMicroservice(EcosystemModule, { + transport: Transport.NATS, + options: { + servers: [`${process.env.NATS_URL}`] + } + }); + + app.useGlobalFilters(new HttpExceptionFilter()); + + await app.listen(); + logger.log('Ecosystem microservice is listening to NATS '); +} +bootstrap(); diff --git a/apps/ecosystem/test/app.e2e-spec.ts b/apps/ecosystem/test/app.e2e-spec.ts new file mode 100644 index 000000000..1f1f79169 --- /dev/null +++ b/apps/ecosystem/test/app.e2e-spec.ts @@ -0,0 +1,24 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { EcosystemModule } from './../src/ecosystem.module'; + +describe('EcosystemController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [EcosystemModule] + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/ (GET)', () => { + return request(app.getHttpServer()) + .get('/') + .expect(200) + .expect('Hello World!'); + }); +}); diff --git a/apps/ecosystem/test/jest-e2e.json b/apps/ecosystem/test/jest-e2e.json new file mode 100644 index 000000000..e9d912f3e --- /dev/null +++ b/apps/ecosystem/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/apps/ecosystem/tsconfig.app.json b/apps/ecosystem/tsconfig.app.json new file mode 100644 index 000000000..ac6e9e1bb --- /dev/null +++ b/apps/ecosystem/tsconfig.app.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": false, + "outDir": "../../dist/apps/ecosystem" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 4f9dd4b5e..3d16195bc 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -173,5 +173,14 @@ export const ResponseMessages = { platformConfigNotFound: 'Platform config not found', emailSend: 'Unable to send email to the user' } - } + }, + ecosystem: { + success: { + create: 'Ecosystem created successfully', + }, + error: { + + } + + }, }; \ No newline at end of file diff --git a/nest-cli.json b/nest-cli.json index 68dc6c90e..c4cae4482 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -232,6 +232,15 @@ "compilerOptions": { "tsConfigPath": "libs/supabase/tsconfig.lib.json" } + }, + "ecosystem": { + "type": "application", + "root": "apps/ecosystem", + "entryFile": "main", + "sourceRoot": "apps/ecosystem/src", + "compilerOptions": { + "tsConfigPath": "apps/ecosystem/tsconfig.app.json" + } } } } \ No newline at end of file From 4a65334d6f126447f84764b2a3696e0e491c6643 Mon Sep 17 00:00:00 2001 From: tipusinghaw <126460794+tipusinghaw@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:58:46 +0530 Subject: [PATCH 019/162] feat: ecosystem ms setup (#105) * worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat * merge dev branch to main (#77) * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw * feat:implemented add passke Signed-off-by: tipusinghaw * fix: login error message Signed-off-by: tipusinghaw --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw * Included credebl-master-table json file in the .gitignore Signed-off-by: @nishad.shirsat * Create ecosystem monorepo Signed-off-by: KulkarniShashank * feat: Implemented ecosystem microservice Signed-off-by: tipusinghaw * feat: changed controller name Signed-off-by: tipusinghaw * fix:changed return message Signed-off-by: tipusinghaw * setup ecosystem prisma schema Signed-off-by: Nishad --------- Signed-off-by: tipusinghaw Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank Signed-off-by: Nishad Co-authored-by: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Co-authored-by: @nishad.shirsat Co-authored-by: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Co-authored-by: KulkarniShashank --- apps/ecosystem/src/ecosystem.service.ts | 2 +- .../migration.sql | 90 +++++++++++++++++++ libs/prisma-service/prisma/schema.prisma | 71 +++++++++++++++ 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 libs/prisma-service/prisma/migrations/20231002114401_ecosystem_setup/migration.sql diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index f68e93329..14795ec82 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -14,7 +14,7 @@ export class EcosystemService { // eslint-disable-next-line camelcase async createEcosystem():Promise { - return "test"; + return "test ecosystem"; } } diff --git a/libs/prisma-service/prisma/migrations/20231002114401_ecosystem_setup/migration.sql b/libs/prisma-service/prisma/migrations/20231002114401_ecosystem_setup/migration.sql new file mode 100644 index 000000000..0f1eed81d --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231002114401_ecosystem_setup/migration.sql @@ -0,0 +1,90 @@ +-- CreateTable +CREATE TABLE "ecosystem_roles" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_roles_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ecosystem" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT NOT NULL, + "tags" TEXT NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ecosystem_invitations" ( + "id" TEXT NOT NULL, + "email" TEXT NOT NULL, + "status" TEXT NOT NULL, + "ecosystemId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_invitations_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ecosystem_users" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "ecosystemId" TEXT NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_users_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ecosystem_orgs" ( + "id" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "status" TEXT NOT NULL, + "ecosystemId" TEXT NOT NULL, + "ecosystemRoleId" INTEGER NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_orgs_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "ecosystem_roles_name_key" ON "ecosystem_roles"("name"); + +-- AddForeignKey +ALTER TABLE "ecosystem_invitations" ADD CONSTRAINT "ecosystem_invitations_ecosystemId_fkey" FOREIGN KEY ("ecosystemId") REFERENCES "ecosystem"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ecosystem_users" ADD CONSTRAINT "ecosystem_users_ecosystemId_fkey" FOREIGN KEY ("ecosystemId") REFERENCES "ecosystem"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ecosystem_orgs" ADD CONSTRAINT "ecosystem_orgs_ecosystemId_fkey" FOREIGN KEY ("ecosystemId") REFERENCES "ecosystem"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ecosystem_orgs" ADD CONSTRAINT "ecosystem_orgs_ecosystemRoleId_fkey" FOREIGN KEY ("ecosystemRoleId") REFERENCES "ecosystem_roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 82c551246..9a6cf2497 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -307,3 +307,74 @@ model presentations { orgId Int organisation organisation @relation(fields: [orgId], references: [id]) } + +model ecosystem_roles { + id Int @id @default(autoincrement()) + name String @unique + description String + ecosystemOrgs ecosystem_orgs[] + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) +} + +model ecosystem { + id String @id @default(uuid()) + name String + description String + tags String + ecosystemOrgs ecosystem_orgs[] + ecosystemUsers ecosystem_users[] + ecosystemInvitations ecosystem_invitations[] + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) +} + +model ecosystem_invitations { + id String @id @default(uuid()) + email String + status String + ecosystemId String + userId String + orgId String + ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) +} + +model ecosystem_users { + id String @id @default(uuid()) // auto-increment + userId String + ecosystemId String + ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) +} + +model ecosystem_orgs { + id String @id @default(uuid()) // auto-increment + orgId String + status String + ecosystemId String + ecosystemRoleId Int + ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) + ecosystemRole ecosystem_roles @relation(fields: [ecosystemRoleId], references: [id]) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) + +} + From 55ce1dc9347902e180624113ffe75d9346b0a20c Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Tue, 3 Oct 2023 18:18:54 +0530 Subject: [PATCH 020/162] Integrated ecosystem enable disable in config, master table entry for ecosystem roles (#113) Signed-off-by: Nishad --- .../prisma/data/credebl-master-table.json | 15 ++++++++++++ .../migration.sql | 24 +++++++++++++++++++ .../migration.sql | 2 ++ libs/prisma-service/prisma/schema.prisma | 8 ++++--- libs/prisma-service/prisma/seed.ts | 14 +++++++++++ 5 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20231003080927_ecosystem_flag/migration.sql create mode 100644 libs/prisma-service/prisma/migrations/20231003114625_ecosystem_logo/migration.sql diff --git a/libs/prisma-service/prisma/data/credebl-master-table.json b/libs/prisma-service/prisma/data/credebl-master-table.json index 35a44c6c3..c1627506f 100644 --- a/libs/prisma-service/prisma/data/credebl-master-table.json +++ b/libs/prisma-service/prisma/data/credebl-master-table.json @@ -55,6 +55,21 @@ "description": "Joins the organization as member" } ], + "ecosystemRoleData": [ + { + "name": "Ecosystem Owner", + "description": "Ecosystem Owner" + }, + { + "name": "Ecosystem Lead", + "description": "Ecosystem Lead" + }, + { + "name": "Ecosystem Member", + "description": "Ecosystem Member" + } + ], + "agentTypeData": [ { "agent": "AFJ" diff --git a/libs/prisma-service/prisma/migrations/20231003080927_ecosystem_flag/migration.sql b/libs/prisma-service/prisma/migrations/20231003080927_ecosystem_flag/migration.sql new file mode 100644 index 000000000..418d645c7 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231003080927_ecosystem_flag/migration.sql @@ -0,0 +1,24 @@ +/* + Warnings: + + - The primary key for the `ecosystem_roles` table will be changed. If it partially fails, the table could be left without primary key constraint. + +*/ +-- DropForeignKey +ALTER TABLE "ecosystem_orgs" DROP CONSTRAINT "ecosystem_orgs_ecosystemRoleId_fkey"; + +-- AlterTable +ALTER TABLE "ecosystem_orgs" ALTER COLUMN "ecosystemRoleId" SET DATA TYPE TEXT; + +-- AlterTable +ALTER TABLE "ecosystem_roles" DROP CONSTRAINT "ecosystem_roles_pkey", +ALTER COLUMN "id" DROP DEFAULT, +ALTER COLUMN "id" SET DATA TYPE TEXT, +ADD CONSTRAINT "ecosystem_roles_pkey" PRIMARY KEY ("id"); +DROP SEQUENCE "ecosystem_roles_id_seq"; + +-- AlterTable +ALTER TABLE "platform_config" ADD COLUMN "enableEcosystem" BOOLEAN NOT NULL DEFAULT false; + +-- AddForeignKey +ALTER TABLE "ecosystem_orgs" ADD CONSTRAINT "ecosystem_orgs_ecosystemRoleId_fkey" FOREIGN KEY ("ecosystemRoleId") REFERENCES "ecosystem_roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/libs/prisma-service/prisma/migrations/20231003114625_ecosystem_logo/migration.sql b/libs/prisma-service/prisma/migrations/20231003114625_ecosystem_logo/migration.sql new file mode 100644 index 000000000..d4031a104 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231003114625_ecosystem_logo/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "ecosystem" ADD COLUMN "logoUrl" TEXT; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 9a6cf2497..4ba8e17e7 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -132,6 +132,7 @@ model platform_config { emailFrom String @db.VarChar apiEndpoint String @db.VarChar tailsFileServer String @db.VarChar + enableEcosystem Boolean @default(false) createDateTime DateTime @default(now()) @db.Timestamptz(6) createdBy Int @default(1) lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) @@ -309,7 +310,7 @@ model presentations { } model ecosystem_roles { - id Int @id @default(autoincrement()) + id String @id @default(uuid()) name String @unique description String ecosystemOrgs ecosystem_orgs[] @@ -324,6 +325,7 @@ model ecosystem { id String @id @default(uuid()) name String description String + logoUrl String? tags String ecosystemOrgs ecosystem_orgs[] ecosystemUsers ecosystem_users[] @@ -363,11 +365,11 @@ model ecosystem_users { } model ecosystem_orgs { - id String @id @default(uuid()) // auto-increment + id String @id @default(uuid()) orgId String status String ecosystemId String - ecosystemRoleId Int + ecosystemRoleId String ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) ecosystemRole ecosystem_roles @relation(fields: [ecosystemRoleId], references: [id]) createDateTime DateTime @default(now()) @db.Timestamptz(6) diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index 8b6b6fb51..cd50a00c6 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -113,6 +113,19 @@ const createLedger = async (): Promise => { } }; +const createEcosystemRoles = async (): Promise => { + try { + const { ecosystemRoleData } = JSON.parse(configData); + const ecosystemRoles = await prisma.ecosystem_roles.createMany({ + data: ecosystemRoleData + }); + + logger.log(ecosystemRoles); + } catch (e) { + logger.error('An error occurred seeding ecosystemRoles:', e); + } +}; + async function main(): Promise { await createPlatformConfig(); @@ -123,6 +136,7 @@ async function main(): Promise { await createPlatformUserOrgRoles(); await createOrgAgentTypes(); await createLedger(); + await createEcosystemRoles(); } From 0bb52a2aa1ac832f781ff8f191c02be270cd84a2 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Wed, 4 Oct 2023 13:25:16 +0530 Subject: [PATCH 021/162] refactor: enable disable ecosystem: 107 (#115) * Integrated ecosystem enable disable in config, master table entry for ecosystem roles Signed-off-by: Nishad * Implemented ecosystem enable flag in the user profile details Signed-off-by: Nishad --------- Signed-off-by: Nishad --- apps/user/src/user.service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 0f5a84c9a..19b057c14 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -338,7 +338,10 @@ export class UserService { async getProfile(payload: { id }): Promise { try { - return this.userRepository.getUserById(payload.id); + const userData = await this.userRepository.getUserById(payload.id); + const platformConfigData = await this.prisma.platform_config.findMany(); + userData['enableEcosystem'] = platformConfigData[0].enableEcosystem; + return userData; } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); From 1a98d3f33576b79783102a148518959a525b50b2 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Wed, 30 Aug 2023 12:08:34 +0530 Subject: [PATCH 022/162] feat: Get Proof Presentation from Data with Credentials and Attributes (#66) * fix: Added the global exception error handling for api-gateway Signed-off-by: KulkarniShashank * Added the server for call the API locally Signed-off-by: KulkarniShashank * Removed qr-code in main.js Signed-off-by: KulkarniShashank * feat: Get proof presentation form data Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- .../src/agent-service.controller.ts | 5 + .../src/agent-service.service.ts | 12 ++ .../verification/verification.controller.ts | 34 +++- .../src/verification/verification.service.ts | 7 + .../src/interfaces/verification.interface.ts | 12 ++ .../src/verification.controller.ts | 8 +- apps/verification/src/verification.service.ts | 149 +++++++++++++++++- libs/common/src/common.constant.ts | 11 +- libs/common/src/response-messages/index.ts | 5 +- 9 files changed, 232 insertions(+), 11 deletions(-) diff --git a/apps/agent-service/src/agent-service.controller.ts b/apps/agent-service/src/agent-service.controller.ts index 42c140202..e85fcd6ba 100644 --- a/apps/agent-service/src/agent-service.controller.ts +++ b/apps/agent-service/src/agent-service.controller.ts @@ -101,4 +101,9 @@ export class AgentServiceController { async sendOutOfBandProofRequest(payload: { proofRequestPayload: ISendProofRequestPayload, url: string, apiKey: string }): Promise { return this.agentServiceService.sendOutOfBandProofRequest(payload.proofRequestPayload, payload.url, payload.apiKey); } + + @MessagePattern({ cmd: 'agent-proof-form-data' }) + async getProofFormData(payload: { url: string, apiKey: string }): Promise { + return this.agentServiceService.getProofFormData(payload.url, payload.apiKey); + } } diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 0af735840..b1a818b26 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -875,5 +875,17 @@ export class AgentServiceService { throw new RpcException(error); } } + + async getProofFormData(url: string, apiKey: string): Promise { + try { + const getProofFormData = await this.commonService + .httpGet(url, { headers: { 'x-api-key': apiKey } }) + .then(async response => response); + return getProofFormData; + } catch (error) { + this.logger.error(`Error in get proof form data in agent service : ${JSON.stringify(error)}`); + throw new RpcException(error); + } + } } diff --git a/apps/api-gateway/src/verification/verification.controller.ts b/apps/api-gateway/src/verification/verification.controller.ts index 503223bb3..328bd2540 100644 --- a/apps/api-gateway/src/verification/verification.controller.ts +++ b/apps/api-gateway/src/verification/verification.controller.ts @@ -38,6 +38,38 @@ export class VerificationController { private readonly logger = new Logger('VerificationController'); + @Get('/proofs/form-data') + @ApiTags('verifications') + @ApiOperation({ + summary: `Get a proof form data`, + description: `Get a proof form data` + }) + @ApiQuery( + { name: 'id', required: true } + ) + @ApiQuery( + { name: 'orgId', required: true } + ) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + async getProofFormData( + @Res() res: Response, + @GetUser() user: IUserRequest, + @Query('id') id: string, + @Query('orgId') orgId: number + ): Promise { + const sendProofRequest = await this.verificationService.getProofFormData(id, orgId, user); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.verification.success.proofFormData, + data: sendProofRequest.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + /** * Get all proof presentations * @param user @@ -138,7 +170,7 @@ export class VerificationController { const sendProofRequest = await this.verificationService.sendProofRequest(requestProof, user); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, - message: ResponseMessages.verification.success.fetch, + message: ResponseMessages.verification.success.send, data: sendProofRequest.response }; 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 c8b6ed973..6a72f78a7 100644 --- a/apps/api-gateway/src/verification/verification.service.ts +++ b/apps/api-gateway/src/verification/verification.service.ts @@ -75,4 +75,11 @@ export class VerificationService extends BaseService { const payload = { outOfBandRequestProof, user }; return this.sendNats(this.verificationServiceProxy, 'send-out-of-band-proof-request', payload); } + + + getProofFormData(id: string, orgId: number, user: IUserRequest): Promise<{ response: object }> { + const payload = { id, orgId, user }; + return this.sendNats(this.verificationServiceProxy, 'proof-form-data', payload); + } + } diff --git a/apps/verification/src/interfaces/verification.interface.ts b/apps/verification/src/interfaces/verification.interface.ts index 45b7dad1b..4f25db0f4 100644 --- a/apps/verification/src/interfaces/verification.interface.ts +++ b/apps/verification/src/interfaces/verification.interface.ts @@ -1,3 +1,4 @@ +import { IUserRequest } from "@credebl/user-request/user-request.interface"; interface IProofRequestAttribute { attributeName: string; @@ -33,6 +34,17 @@ export interface IVerifyPresentation { apiKey: string; } +export interface ProofFormDataPayload { + url: string; + apiKey: string; +} + +export interface ProofFormData { + id: string; + orgId: number; + user: IUserRequest; +} + interface IProofFormats { indy: IndyProof } diff --git a/apps/verification/src/verification.controller.ts b/apps/verification/src/verification.controller.ts index 65dd1d852..2ad7f1d62 100644 --- a/apps/verification/src/verification.controller.ts +++ b/apps/verification/src/verification.controller.ts @@ -1,7 +1,7 @@ import { Controller } from '@nestjs/common'; import { VerificationService } from './verification.service'; import { MessagePattern } from '@nestjs/microservices'; -import { IRequestProof, IWebhookProofPresentation } from './interfaces/verification.interface'; +import { IRequestProof, IWebhookProofPresentation, ProofFormData } from './interfaces/verification.interface'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { presentations } from '@prisma/client'; @@ -58,4 +58,10 @@ export class VerificationController { async sendOutOfBandPresentationRequest(payload: { outOfBandRequestProof: IRequestProof, user: IUserRequest }): Promise { return this.verificationService.sendOutOfBandPresentationRequest(payload.outOfBandRequestProof); } + + @MessagePattern({ cmd: 'proof-form-data' }) + async getProofFormData(payload: ProofFormData): Promise { + const { id, orgId } = payload; + return this.verificationService.getProofFormData(id, orgId); + } } diff --git a/apps/verification/src/verification.service.ts b/apps/verification/src/verification.service.ts index 36a0e38d3..c0b57c04f 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, IWebhookProofPresentation } from './interfaces/verification.interface'; +import { IGetAllProofPresentations, IGetProofPresentationById, IProofRequestPayload, IRequestProof, ISendProofRequestPayload, IVerifyPresentation, IWebhookProofPresentation, ProofFormDataPayload } from './interfaces/verification.interface'; import { VerificationRepository } from './repositories/verification.repository'; import { CommonConstants } from '@credebl/common/common.constant'; import { presentations } from '@prisma/client'; @@ -238,7 +238,7 @@ export class VerificationService { }; } - return [attributeReferent, null]; + return [attributeReferent]; })); } else { throw new BadRequestException(ResponseMessages.verification.error.schemaIdNotFound); @@ -534,7 +534,6 @@ export class VerificationService { try { let requestedAttributes = {}; const requestedPredicates = {}; - const attributeWithSchemaIdExists = proofRequestpayload.attributes.some(attribute => attribute.schemaId); if (attributeWithSchemaIdExists) { @@ -690,6 +689,15 @@ export class VerificationService { break; } + case 'proof-form-data': { + url = orgAgentTypeId === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_PROOF_FORM_DATA}` + : orgAgentTypeId === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_PROOF_FORM_DATA}`.replace('@', proofPresentationId).replace('#', tenantId) + : null; + break; + } + default: { break; } @@ -706,4 +714,139 @@ export class VerificationService { } } + + async getProofFormData(id: string, orgId: number): Promise { + try { + const getAgentDetails = await this.verificationRepository.getAgentEndPoint(orgId); + const verificationMethodLabel = 'proof-form-data'; + + const url = await this.getAgentUrl(verificationMethodLabel, getAgentDetails?.orgAgentTypeId, getAgentDetails?.agentEndPoint, getAgentDetails?.tenantId, '', id); + + const payload = { apiKey: '', url }; + const getProofPresentationById = await this._getProofFormData(payload); + + if (!getProofPresentationById?.response?.presentation) { + throw new NotFoundException("Proof presentation not found!"); + } + + const requestedAttributes = getProofPresentationById?.response?.request?.indy?.requested_attributes; + const requestedPredicates = getProofPresentationById?.response?.request?.indy?.requested_predicates; + const revealedAttrs = getProofPresentationById?.response?.presentation?.indy?.requested_proof?.revealed_attrs; + + const extractedDataArray = []; + + if (requestedAttributes && requestedPredicates) { + + 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; + + if (revealedAttrs.hasOwnProperty(key)) { + const extractedData = { + [attributeName]: revealedAttrs[key]?.raw, + "credDefId": credDefId ? credDefId : null, + "schemaId": schemaId ? schemaId : null + }; + extractedDataArray.push(extractedData); + } + } + } + + for (const key in requestedPredicates) { + if (requestedPredicates.hasOwnProperty(key)) { + const attribute = requestedPredicates[key]; + const attributeName = attribute?.name; + const credDefId = attribute?.restrictions[0]?.cred_def_id; + const schemaId = attribute?.restrictions[0]?.schema_id; + + const extractedData = { + [attributeName]: `${attribute?.p_type}${attribute?.p_value}`, + "credDefId": credDefId ? credDefId : null, + "schemaId": schemaId ? schemaId : null + }; + extractedDataArray.push(extractedData); + } + } + + } else if (requestedAttributes) { + 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; + + if (revealedAttrs.hasOwnProperty(key)) { + const extractedData = { + [attributeName]: revealedAttrs[key]?.raw, + "credDefId": credDefId ? credDefId : null, + "schemaId": schemaId ? schemaId : null + }; + extractedDataArray.push(extractedData); + } + } + } + } else if (requestedPredicates) { + for (const key in requestedPredicates) { + + if (requestedPredicates.hasOwnProperty(key)) { + const attribute = requestedPredicates[key]; + const attributeName = attribute?.name; + const credDefId = attribute?.restrictions[0]?.cred_def_id; + const schemaId = attribute?.restrictions[0]?.schema_id; + + const extractedData = { + [attributeName]: `${requestedPredicates?.p_type}${requestedPredicates?.p_value}`, + "credDefId": credDefId ? credDefId : null, + "schemaId": schemaId ? schemaId : null + }; + extractedDataArray.push(extractedData); + } + } + } else { + throw new InternalServerErrorException('Something went wrong!'); + } + + return extractedDataArray; + } catch (error) { + this.logger.error(`[getProofFormData] - error in get proof form data : ${JSON.stringify(error)}`); + throw new RpcException(error); + } + } + + async _getProofFormData(payload: ProofFormDataPayload): Promise<{ + response; + }> { + try { + + const pattern = { + cmd: 'agent-proof-form-data' + }; + + 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); + }); + } catch (error) { + this.logger.error(`[_getProofFormData] - error in proof form data : ${JSON.stringify(error)}`); + throw error; + } + } } diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 20a626de5..01b366bd6 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -67,10 +67,10 @@ export enum CommonConstants { URL_PUBLISH_REVOCATION = '/issue-credential/publish-revocations', URL_CREATE_ISSUE_CREDENTIAL_OUT_OF_BAND = '/issue-credential/create', URL_CREATE_OUT_OF_BAND_INVITATION = '/out-of-band/create-invitation', - URL_ISSUE_CREATE_CRED_OFFER_AFJ= '/credentials/create-offer', + URL_ISSUE_CREATE_CRED_OFFER_AFJ = '/credentials/create-offer', // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values - URL_ISSUE_GET_CREDS_AFJ= '/credentials', - URL_ISSUE_GET_CREDS_AFJ_BY_CRED_REC_ID= '/credentials', + URL_ISSUE_GET_CREDS_AFJ = '/credentials', + URL_ISSUE_GET_CREDS_AFJ_BY_CRED_REC_ID = '/credentials', // SCHEMA & CRED DEF SERVICES URL_SCHM_CREATE_SCHEMA = '/schemas', @@ -95,13 +95,16 @@ export enum CommonConstants { URL_SHAGENT_REQUEST_PROOF = '/multi-tenancy/proofs/request-proof/#', URL_SHAGENT_ACCEPT_PRESENTATION = '/multi-tenancy/proofs/@/accept-presentation/#', URL_SHAGENT_OUT_OF_BAND_CREATE_REQUEST = '/multi-tenancy/proofs/create-request-oob/#', + URL_SHAGENT_PROOF_FORM_DATA = '/multi-tenancy/form-data/#/@', // PROOF SERVICES URL_SEND_PROOF_REQUEST = '/proofs/request-proof', URL_GET_PROOF_PRESENTATIONS = '/proofs', URL_GET_PROOF_PRESENTATION_BY_ID = '/proofs/#', URL_VERIFY_PRESENTATION = '/proofs/#/accept-presentation', - URL_SEND_OUT_OF_BAND_CREATE_REQUEST='/proofs/create-request-oob', + URL_SEND_OUT_OF_BAND_CREATE_REQUEST = '/proofs/create-request-oob', + URL_PROOF_FORM_DATA = '/proofs/#/form-data', + // server or agent URL_SERVER_STATUS = '/status', diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index f39474126..2d3a95f64 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -106,7 +106,7 @@ export const ResponseMessages = { error: { NotFound: 'No credential definitions found.', NotSaved: 'Error in saving credential definition.', - Conflict: 'Tag already exists', + Conflict: 'Credential definition already exists', schemaIdNotFound: 'SchemaLedgerId not found', OrgDidNotFound: 'OrgDid not found', credDefIdNotFound: 'Credential Definition Id not found' @@ -156,7 +156,8 @@ export const ResponseMessages = { verification: { success: { fetch: 'Proof presentation received successfully.', - create: 'Proof request created successfully.', + proofFormData: 'Proof presentation form data received successfully.', + send: 'Proof request send successfully.', verified: 'Proof presentation verified successfully.' }, error: { From 8afc3e69b439d982209bf7e5f62e7b279efcac2c Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Fri, 1 Sep 2023 19:17:30 +0530 Subject: [PATCH 023/162] fix: Changed the passkey approch Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/api-gateway/src/user/user.controller.ts | 175 ++++++++++-------- .../repositories/user-device.repository.ts | 24 +++ apps/user/src/user.module.ts | 4 +- apps/user/src/user.service.ts | 47 +++-- .../migration.sql | 2 + libs/prisma-service/prisma/schema.prisma | 1 + 6 files changed, 160 insertions(+), 93 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20230831142029_user_device_password/migration.sql diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 345922989..bc79004a6 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -76,77 +76,77 @@ export class UserController { * @param res * @returns Users list of organization */ -@Get() -@Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) -@ApiBearerAuth() -@UseGuards(AuthGuard('jwt'), OrgRolesGuard) -@ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) -@ApiOperation({ summary: 'Get organization users list', description: 'Get organization users list.' }) -@ApiQuery({ - name: 'pageNumber', - type: Number, - required: false -}) -@ApiQuery({ - name: 'pageSize', - type: Number, - required: false -}) -@ApiQuery({ - name: 'search', - type: String, - required: false -}) -async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Query('orgId') orgId: number, @Res() res: Response): Promise { - - const org = user.selectedOrg?.orgId; - const users = await this.userService.getOrgUsers(org, getAllUsersDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchUsers, - data: users.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); -} - - -/** - * - * @param user - * @param orgId - * @param res - * @returns Users list of organization - */ -@Get('/public') -@ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) -@ApiOperation({ summary: 'Get users list', description: 'Get users list.' }) -@ApiQuery({ - name: 'pageNumber', - type: Number, - required: false -}) -@ApiQuery({ - name: 'pageSize', - type: Number, - required: false -}) -@ApiQuery({ - name: 'search', - type: String, - required: false -}) -async get(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Res() res: Response): Promise { - - const users = await this.userService.get(getAllUsersDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchUsers, - data: users.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); -} + @Get() + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Get organization users list', description: 'Get organization users list.' }) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Query('orgId') orgId: number, @Res() res: Response): Promise { + + const org = user.selectedOrg?.orgId; + const users = await this.userService.getOrgUsers(org, getAllUsersDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.fetchUsers, + data: users.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } + + + /** + * + * @param user + * @param orgId + * @param res + * @returns Users list of organization + */ + @Get('/public') + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Get users list', description: 'Get users list.' }) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async get(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Res() res: Response): Promise { + + const users = await this.userService.get(getAllUsersDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.fetchUsers, + data: users.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } /** @@ -328,21 +328,34 @@ async get(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUs @Post('/add/:email') @ApiOperation({ summary: 'Add user information', description: 'Add user information' }) async addUserDetailsInKeyCloak(@Body() userInfo: AddUserDetails, @Param('email') email: string, @Res() res: Response): Promise { - const decryptedPassword = this.commonService.decryptPassword(userInfo.password); - if (8 <= decryptedPassword.length && 50 >= decryptedPassword.length) { - this.commonService.passwordValidation(decryptedPassword); - userInfo.password = decryptedPassword; - const userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); - const finalResponse: IResponseType = { + let finalResponse; + let userDetails; + + if (false === userInfo.isPasskey) { + + const decryptedPassword = this.commonService.decryptPassword(userInfo.password); + if (8 <= decryptedPassword.length && 50 >= decryptedPassword.length) { + this.commonService.passwordValidation(decryptedPassword); + userInfo.password = decryptedPassword; + userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); + finalResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userDetails.response + }; + } else { + throw new BadRequestException('Password name must be between 8 to 50 Characters'); + } + } else { + + userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); + finalResponse = { statusCode: HttpStatus.CREATED, message: ResponseMessages.user.success.create, data: userDetails.response }; - return res.status(HttpStatus.OK).json(finalResponse); - - } else { - throw new BadRequestException('Password name must be between 8 to 50 Characters'); } + return res.status(HttpStatus.OK).json(finalResponse); } diff --git a/apps/user/repositories/user-device.repository.ts b/apps/user/repositories/user-device.repository.ts index 5e4422905..9d289f5a5 100644 --- a/apps/user/repositories/user-device.repository.ts +++ b/apps/user/repositories/user-device.repository.ts @@ -286,5 +286,29 @@ async addCredentialIdAndNameById(id:number, credentialId:string, deviceFriendlyN throw new InternalServerErrorException(error); } } + +/** + * + * @param password + * @param userId + * @returns Update password + */ +async updateUserDeviceDetails(password: string, userId: number): Promise { + try { + return await this.prisma.user_devices.updateMany({ + where: { + userId + }, + data: { + password + } + }); + + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } +} + } diff --git a/apps/user/src/user.module.ts b/apps/user/src/user.module.ts index 42362c279..865dc6e04 100644 --- a/apps/user/src/user.module.ts +++ b/apps/user/src/user.module.ts @@ -16,6 +16,7 @@ import { UserOrgRolesRepository } from 'libs/user-org-roles/repositories'; import { UserOrgRolesService } from '@credebl/user-org-roles'; import { UserRepository } from '../repositories/user.repository'; import { UserService } from './user.service'; +import { UserDevicesRepository } from '../repositories/user-device.repository'; @Module({ imports: [ @@ -46,7 +47,8 @@ import { UserService } from './user.service'; OrgRolesRepository, UserOrgRolesRepository, UserActivityService, - UserActivityRepository + UserActivityRepository, + UserDevicesRepository ] }) export class UserModule {} diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index b9e42fcd4..5d1358549 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -31,7 +31,8 @@ import { InvitationsI, UpdateUserProfile, UserEmailVerificationDto, userInfo } f import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; - +import { UserDevicesRepository } from '../repositories/user-device.repository'; +import { v4 as uuidv4 } from 'uuid'; @Injectable() export class UserService { @@ -44,6 +45,7 @@ export class UserService { private readonly userOrgRoleService: UserOrgRolesService, private readonly userActivityService: UserActivityService, private readonly userRepository: UserRepository, + private readonly userDevicesRepository: UserDevicesRepository, private readonly logger: Logger, @Inject('NATS_CLIENT') private readonly userServiceProxy: ClientProxy ) { } @@ -174,10 +176,35 @@ export class UserService { if (!userDetails) { throw new NotFoundException(ResponseMessages.user.error.adduser); } - const supaUser = await this.supabaseService.getClient().auth.signUp({ - email, - password: userInfo.password - }); + + let supaUser; + + if (userInfo.isPasskey) { + const password: string = uuidv4(); + + supaUser = await this.supabaseService.getClient().auth.signUp({ + email, + password + }); + + if (supaUser.error) { + throw new InternalServerErrorException(supaUser.error?.message); + } + + const getUserDetails = await this.userRepository.getUserDetails(userDetails.email); + await this.userDevicesRepository.updateUserDeviceDetails( + password, + getUserDetails.id + ); + + } else { + + + supaUser = await this.supabaseService.getClient().auth.signUp({ + email, + password: userInfo.password + }); + } if (supaUser.error) { throw new InternalServerErrorException(supaUser.error?.message); @@ -225,9 +252,9 @@ export class UserService { } if (true === isPasskey && userData?.username && true === userData?.isFidoVerified) { - - return this.generateToken(email, password); - + const getUserDetails = await this.userRepository.getUserDetails(userData.email); + const getFidoUserPassword = await this.userDevicesRepository.checkUserDevice(getUserDetails.id); + return this.generateToken(email, getFidoUserPassword.password); } return this.generateToken(email, password); @@ -238,6 +265,7 @@ export class UserService { } async generateToken(email: string, password: string): Promise { + const supaInstance = await this.supabaseService.getClient(); this.logger.error(`supaInstance::`, supaInstance); @@ -246,7 +274,6 @@ export class UserService { email, password }); - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); if (error) { @@ -254,11 +281,9 @@ export class UserService { } const token = data?.session; - return token; } - async getProfile(payload: { id }): Promise { try { return this.userRepository.getUserById(payload.id); diff --git a/libs/prisma-service/prisma/migrations/20230831142029_user_device_password/migration.sql b/libs/prisma-service/prisma/migrations/20230831142029_user_device_password/migration.sql new file mode 100644 index 000000000..de04d62e7 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20230831142029_user_device_password/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "user_devices" ADD COLUMN "password" VARCHAR; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 23fb8d161..3fe696cb3 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -119,6 +119,7 @@ model user_devices { userId Int? deletedAt DateTime? @db.Timestamp(6) authCounter Int @default(0) + password String? @db.VarChar user user? @relation(fields: [userId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_e12ac4f8016243ac71fd2e415af") } From 79884e2ffcf0a11523f2d24648c913b4f0135e85 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Wed, 6 Sep 2023 23:27:52 +0530 Subject: [PATCH 024/162] feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw --- apps/api-gateway/src/user/dto/add-user.dto.ts | 8 ++ apps/api-gateway/src/user/user.controller.ts | 18 +++- apps/api-gateway/src/user/user.service.ts | 7 ++ apps/user/interfaces/user.interface.ts | 4 + .../repositories/user-device.repository.ts | 23 ----- apps/user/repositories/user.repository.ts | 23 +++++ apps/user/src/user.controller.ts | 7 +- apps/user/src/user.service.ts | 87 ++++++++++++------- libs/common/src/response-messages/index.ts | 3 +- 9 files changed, 122 insertions(+), 58 deletions(-) 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 51a78d2b6..a72eb45eb 100644 --- a/apps/api-gateway/src/user/dto/add-user.dto.ts +++ b/apps/api-gateway/src/user/dto/add-user.dto.ts @@ -25,3 +25,11 @@ export class AddUserDetails { @IsBoolean({ message: 'isPasskey should be boolean' }) isPasskey?: boolean; } + +export class AddPasskeyDetails { + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Password is required.' }) + password: string; + +} diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index bc79004a6..0677ad8eb 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -39,7 +39,7 @@ import { OrgRoles } from 'libs/org-roles/enums'; import { IUserRequestInterface } from './interfaces'; import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; import { GetAllUsersDto } from './dto/get-all-users.dto'; -import { AddUserDetails } from './dto/add-user.dto'; +import { AddPasskeyDetails, AddUserDetails } from './dto/add-user.dto'; import { UpdateUserProfileDto } from './dto/update-user-profile.dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @@ -399,4 +399,20 @@ export class UserController { return res.status(HttpStatus.OK).json(finalResponse); } + + @Post('/password/:email') + @ApiOperation({ summary: 'Add user information', description: 'Add user information' }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async addPasskey(@Body() userInfo: AddPasskeyDetails, @Param('email') email: string, @Res() res: Response): Promise { + const userDetails = await this.userService.addPasskey(email, userInfo); + const finalResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userDetails.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } } \ No newline at end of file diff --git a/apps/api-gateway/src/user/user.service.ts b/apps/api-gateway/src/user/user.service.ts index fbc39fd23..6dfa2b0f5 100644 --- a/apps/api-gateway/src/user/user.service.ts +++ b/apps/api-gateway/src/user/user.service.ts @@ -9,6 +9,8 @@ import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; import { AddUserDetails } from './dto/login-user.dto'; import { GetAllUsersDto } from './dto/get-all-users.dto'; import { UpdateUserProfileDto } from './dto/update-user-profile.dto'; +import { AddPasskeyDetails } from './dto/add-user.dto'; + @Injectable() export class UserService extends BaseService { @@ -125,4 +127,9 @@ export class UserService extends BaseService { const payload = { userId, limit }; return this.sendNats(this.serviceProxy, 'get-user-activity', payload); } + + async addPasskey(userEmail: string, userInfo:AddPasskeyDetails): Promise<{ response: string }> { + const payload = { userEmail, userInfo }; + return this.sendNats(this.serviceProxy, 'add-passkey', payload); + } } diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index b93da4280..051ba3bfb 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -35,6 +35,10 @@ export interface userInfo{ isPasskey: boolean } +export interface AddPasskeyDetails{ + password: string, +} + export interface UserWhereUniqueInput{ id?: number } diff --git a/apps/user/repositories/user-device.repository.ts b/apps/user/repositories/user-device.repository.ts index 9d289f5a5..9e3c559d2 100644 --- a/apps/user/repositories/user-device.repository.ts +++ b/apps/user/repositories/user-device.repository.ts @@ -287,28 +287,5 @@ async addCredentialIdAndNameById(id:number, credentialId:string, deviceFriendlyN } } -/** - * - * @param password - * @param userId - * @returns Update password - */ -async updateUserDeviceDetails(password: string, userId: number): Promise { - try { - return await this.prisma.user_devices.updateMany({ - where: { - userId - }, - data: { - password - } - }); - - } catch (error) { - this.logger.error(`Not Found: ${JSON.stringify(error)}`); - throw new NotFoundException(error); - } -} - } diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 860e4a784..cfdfc92e7 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -452,4 +452,27 @@ export class UserRepository { } } + /** + * + * @param userInfo + * @returns Updates user credentials + */ + // eslint-disable-next-line camelcase + async addUserPassword(email: string, userInfo: string): Promise { + try { + const updateUserDetails = await this.prisma.user.update({ + where: { + email + }, + data: { + password: userInfo + } + }); + return updateUserDetails; + } catch (error) { + this.logger.error(`Error in update isEmailVerified: ${error.message} `); + throw error; + } + } + } diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 952e84dcb..629bb9dcf 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -5,7 +5,7 @@ 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 { UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; +import { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; @Controller() @@ -116,4 +116,9 @@ export class UserController { return this.userService.getUserActivity(payload.userId, payload.limit); } + @MessagePattern({ cmd: 'add-passkey' }) + async addPasskey(payload: { userEmail: string, userInfo: AddPasskeyDetails }): Promise { + return this.userService.addPasskey(payload.userEmail, payload.userInfo); + } + } diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 5d1358549..596b3bd31 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -27,12 +27,12 @@ import { sendEmail } from '@credebl/common/send-grid-helper-file'; import { user } from '@prisma/client'; import { Inject } from '@nestjs/common'; import { HttpException } from '@nestjs/common'; -import { InvitationsI, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; +import { AddPasskeyDetails, InvitationsI, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; import { UserDevicesRepository } from '../repositories/user-device.repository'; -import { v4 as uuidv4 } from 'uuid'; + @Injectable() export class UserService { @@ -58,7 +58,7 @@ export class UserService { async sendVerificationMail(userEmailVerificationDto: UserEmailVerificationDto): Promise { try { const userDetails = await this.userRepository.checkUserExist(userEmailVerificationDto.email); - + if (userDetails && userDetails.isEmailVerified) { throw new ConflictException(ResponseMessages.user.error.exists); } @@ -180,26 +180,18 @@ export class UserService { let supaUser; if (userInfo.isPasskey) { - const password: string = uuidv4(); - + const resUser = await this.userRepository.addUserPassword(email, userInfo.password); + const userDetails = await this.userRepository.getUserDetails(email); + const decryptedPassword = await this.commonService.decryptPassword(userDetails.password); + if (!resUser) { + throw new NotFoundException(ResponseMessages.user.error.invalidEmail); + } supaUser = await this.supabaseService.getClient().auth.signUp({ email, - password + password: decryptedPassword }); - if (supaUser.error) { - throw new InternalServerErrorException(supaUser.error?.message); - } - - const getUserDetails = await this.userRepository.getUserDetails(userDetails.email); - await this.userDevicesRepository.updateUserDeviceDetails( - password, - getUserDetails.id - ); - } else { - - supaUser = await this.supabaseService.getClient().auth.signUp({ email, password: userInfo.password @@ -227,6 +219,33 @@ export class UserService { } } + async addPasskey(email: string, userInfo: AddPasskeyDetails): Promise { + try { + if (!email) { + throw new UnauthorizedException(ResponseMessages.user.error.invalidEmail); + } + const checkUserDetails = await this.userRepository.getUserDetails(email); + if (!checkUserDetails) { + throw new NotFoundException(ResponseMessages.user.error.invalidEmail); + } + if (!checkUserDetails.supabaseUserId) { + throw new ConflictException(ResponseMessages.user.error.notFound); + } + if (false === checkUserDetails.isEmailVerified) { + throw new NotFoundException(ResponseMessages.user.error.emailNotVerified); + } + const resUser = await this.userRepository.addUserPassword(email, userInfo.password); + if (!resUser) { + throw new NotFoundException(ResponseMessages.user.error.invalidEmail); + } + + return 'User updated successfully'; + } catch (error) { + this.logger.error(`Error in createUserForToken: ${JSON.stringify(error)}`); + throw new RpcException(error.response); + } + } + /** * @@ -253,8 +272,8 @@ export class UserService { if (true === isPasskey && userData?.username && true === userData?.isFidoVerified) { const getUserDetails = await this.userRepository.getUserDetails(userData.email); - const getFidoUserPassword = await this.userDevicesRepository.checkUserDevice(getUserDetails.id); - return this.generateToken(email, getFidoUserPassword.password); + const decryptedPassword = await this.commonService.decryptPassword(getUserDetails.password); + return this.generateToken(email, decryptedPassword); } return this.generateToken(email, password); @@ -265,23 +284,27 @@ export class UserService { } async generateToken(email: string, password: string): Promise { + try { + const supaInstance = await this.supabaseService.getClient(); - const supaInstance = await this.supabaseService.getClient(); + this.logger.error(`supaInstance::`, supaInstance); - this.logger.error(`supaInstance::`, supaInstance); + const { data, error } = await supaInstance.auth.signInWithPassword({ + email, + password + }); - const { data, error } = await supaInstance.auth.signInWithPassword({ - email, - password - }); - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); + if (error) { + this.logger.error(`Supa Login Error::`, JSON.stringify(error)); + throw new BadRequestException(error?.message); + } - if (error) { - throw new BadRequestException(error?.message); + const token = data?.session; + return token; + } catch (error) { + this.logger.error(`An unexpected error occurred::`, error.message); + throw new InternalServerErrorException('An unexpected error occurred.'); } - - const token = data?.session; - return token; } async getProfile(payload: { id }): Promise { diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 2d3a95f64..1d265cf7a 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -31,7 +31,8 @@ export const ResponseMessages = { invalidKeycloakId: 'keycloakId is invalid', invalidEmail: 'Invalid Email Id!', adduser: 'Unable to add user details', - verifyEmail: 'The verification link has already been sent to your email address. please verify' + verifyEmail: 'The verification link has already been sent to your email address. please verify', + emailNotVerified: 'The verification link has already been sent to your email address. please verify' } }, organisation: { From 6f7100c7c573f9a0c342abd1123a459f8a9311ca Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Thu, 7 Sep 2023 16:08:44 +0530 Subject: [PATCH 025/162] feat:implemented add passke Signed-off-by: tipusinghaw --- .../migration.sql | 11 ++ libs/prisma-service/prisma/schema.prisma | 118 +++++++++--------- 2 files changed, 70 insertions(+), 59 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20230905103154_password_user/migration.sql diff --git a/libs/prisma-service/prisma/migrations/20230905103154_password_user/migration.sql b/libs/prisma-service/prisma/migrations/20230905103154_password_user/migration.sql new file mode 100644 index 000000000..f63b1a510 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20230905103154_password_user/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the column `password` on the `user_devices` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "user" ADD COLUMN "password" VARCHAR; + +-- AlterTable +ALTER TABLE "user_devices" DROP COLUMN "password"; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 3fe696cb3..82c551246 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -22,41 +22,42 @@ model user { supabaseUserId String? @db.VarChar(500) clientId String? @db.VarChar(500) clientSecret String? @db.VarChar(500) - profileImg String? + profileImg String? fidoUserId String? @db.VarChar(1000) isFidoVerified Boolean @default(false) - userOrgRoles user_org_roles[] - userDevices user_devices[] + publicProfile Boolean @default(false) + password String? @db.VarChar orgInvitations org_invitations[] user_activities user_activity[] - publicProfile Boolean @default(false) + userDevices user_devices[] + userOrgRoles user_org_roles[] } model user_activity { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) userId Int - user user @relation(fields: [userId], references: [id]) orgId Int - organisation organisation? @relation(fields: [orgId], references: [id]) action String details String - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - deletedAt DateTime? @db.Timestamp(6) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) + organisation organisation @relation(fields: [orgId], references: [id]) + user user @relation(fields: [userId], references: [id]) } model org_roles { id Int @id @default(autoincrement()) name String @unique description String - userOrgRoles user_org_roles[] createDateTime DateTime @default(now()) @db.Timestamptz(6) createdBy Int @default(1) lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) lastChangedBy Int @default(1) deletedAt DateTime? @db.Timestamp(6) + userOrgRoles user_org_roles[] } model user_org_roles { @@ -80,15 +81,15 @@ model organisation { orgSlug String? @unique logoUrl String? website String? @db.VarChar - userOrgRoles user_org_roles[] - orgInvitations org_invitations[] - org_agents org_agents[] + publicProfile Boolean @default(false) connections connections[] credentials credentials[] + org_agents org_agents[] + orgInvitations org_invitations[] presentations presentations[] schema schema[] user_activities user_activity[] - publicProfile Boolean @default(false) + userOrgRoles user_org_roles[] } model org_invitations { @@ -101,10 +102,10 @@ model org_invitations { userId Int orgId Int status String - user user @relation(fields: [userId], references: [id]) - organisation organisation @relation(fields: [orgId], references: [id]) orgRoles Int[] email String? + organisation organisation @relation(fields: [orgId], references: [id]) + user user @relation(fields: [userId], references: [id]) } model user_devices { @@ -119,7 +120,6 @@ model user_devices { userId Int? deletedAt DateTime? @db.Timestamp(6) authCounter Int @default(0) - password String? @db.VarChar user user? @relation(fields: [userId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_e12ac4f8016243ac71fd2e415af") } @@ -159,12 +159,12 @@ model org_agents { orgId Int orgAgentTypeId Int ledgerId Int? - ledgers ledgers? @relation(fields: [ledgerId], references: [id]) - agents_type agents_type? @relation(fields: [agentsTypeId], references: [id]) - org_agent_type org_agents_type? @relation(fields: [orgAgentTypeId], references: [id]) - organisation organisation? @relation(fields: [orgId], references: [id]) - agents agents? @relation(fields: [agentId], references: [id]) agent_invitations agent_invitations[] + agents agents? @relation(fields: [agentId], references: [id]) + agents_type agents_type @relation(fields: [agentsTypeId], references: [id]) + ledgers ledgers? @relation(fields: [ledgerId], references: [id]) + org_agent_type org_agents_type @relation(fields: [orgAgentTypeId], references: [id]) + organisation organisation @relation(fields: [orgId], references: [id]) } model org_agents_type { @@ -214,20 +214,20 @@ model agents { } model schema { - id Int @id(map: "PK_c9e7e648903a9e537347aba4372") @default(autoincrement()) - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - name String @db.VarChar - version String @db.VarChar + id Int @id(map: "PK_c9e7e648903a9e537347aba4372") @default(autoincrement()) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + name String @db.VarChar + version String @db.VarChar attributes String - schemaLedgerId String @db.VarChar - publisherDid String @db.VarChar - ledgerId Int @default(1) - issuerId String @db.VarChar + schemaLedgerId String @db.VarChar + publisherDid String @db.VarChar + ledgerId Int @default(1) + issuerId String @db.VarChar orgId Int - organisation organisation? @relation(fields: [orgId], references: [id]) + organisation organisation @relation(fields: [orgId], references: [id]) } model credential_definition { @@ -252,46 +252,46 @@ model shortening_url { } model agent_invitations { - id Int @unique @default(autoincrement()) - orgId Int @default(1) - agentId Int @default(1) + id Int @unique @default(autoincrement()) + orgId Int @default(1) + agentId Int @default(1) connectionInvitation String multiUse Boolean - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - org_agents org_agents? @relation(fields: [agentId], references: [id]) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + org_agents org_agents @relation(fields: [agentId], references: [id]) } model connections { - id Int @id @default(autoincrement()) - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - connectionId String @unique + id Int @id @default(autoincrement()) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + connectionId String @unique state String orgDid String theirLabel String autoAcceptConnection Boolean outOfBandId String orgId Int - organisation organisation? @relation(fields: [orgId], references: [id]) + organisation organisation @relation(fields: [orgId], references: [id]) } model credentials { - id Int @id @default(autoincrement()) - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - connectionId String @unique + id Int @id @default(autoincrement()) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + connectionId String @unique threadId String protocolVersion String credentialAttributes Json[] orgId Int - organisation organisation? @relation(fields: [orgId], references: [id]) + organisation organisation @relation(fields: [orgId], references: [id]) } model presentations { From c77fcfc0f38f8bdc9a5b24e856ca8565b8204cd6 Mon Sep 17 00:00:00 2001 From: Ajay Jadhav Date: Sat, 2 Sep 2023 01:09:37 +0530 Subject: [PATCH 026/162] docs: update README Signed-off-by: tipusinghaw --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc05e7872..987d7cad2 100644 --- a/README.md +++ b/README.md @@ -95,5 +95,15 @@ nest start agent-service [--watch] http://localhost:5000/api ``` +## Credit + +The CREDEBL platform is built by Blockster Labs (Product division of AyanWorks) team. +For the core SSI capabilities, it leverages the great work from multiple open-source projects such as Hyperledger Aries, Bifold, Asker, Indy, etc. + +## Contributing + +Pull requests are welcome! Please read our [contributions guide](https://github.com/credebl/platform/blob/main/CONTRIBUTING.md) and submit your PRs. We enforce [developer certificate of origin](https://developercertificate.org/) (DCO) commit signing — [guidance](https://github.com/apps/dco) on this is available. We also welcome issues submitted about problems you encounter in using CREDEBL. + ## License - Apache 2.0 + +[Apache License Version 2.0](https://github.com/credebl/platform/blob/main/LICENSE) \ No newline at end of file From 516d0ff34a3fb65fdd3d5737107d966a3761d507 Mon Sep 17 00:00:00 2001 From: Ajay Jadhav Date: Sat, 2 Sep 2023 01:10:55 +0530 Subject: [PATCH 027/162] docs: add CONTRIBUTING guidelines Signed-off-by: tipusinghaw --- CONTRIBUTING.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..38e4fde7e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,14 @@ +## How to contribute + +You are encouraged to contribute to the repository by **forking and submitting a pull request**. + +For significant changes, please open an issue first to discuss the proposed changes to avoid re-work. + +(If you are new to GitHub, you might start with a [basic tutorial](https://help.github.com/articles/set-up-git) and check out a more detailed guide to [pull requests](https://help.github.com/articles/using-pull-requests/).) + +Pull requests will be evaluated by the repository guardians on a schedule and if deemed beneficial will be committed to the `main` branch. Pull requests should have a descriptive name, include an summary of all changes made in the pull request description, and include unit tests that provide good coverage of the feature or fix. A Continuous Integration (CI) pipeline is executed on all PRs before review and contributors are expected to address all CI issues identified. Where appropriate, PRs that impact the +end-user and developer demos (if any) in the repo should include updates or extensions to those demos to cover the new capabilities. + +If you would like to propose a significant change, please open an issue first to discuss the work with the community. + +Contributions are made pursuant to the Developer's Certificate of Origin, available at [https://developercertificate.org](https://developercertificate.org), and licensed under the Apache License, version 2.0 (Apache-2.0). \ No newline at end of file From 57f446e55ba8f7d2f7ddce5d5cf1ee6173bd5eb6 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Mon, 11 Sep 2023 13:07:46 +0530 Subject: [PATCH 028/162] fix: login error message Signed-off-by: tipusinghaw --- apps/api-gateway/src/fido/fido.controller.ts | 2 +- apps/user/src/user.service.ts | 31 +++++++++----------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/apps/api-gateway/src/fido/fido.controller.ts b/apps/api-gateway/src/fido/fido.controller.ts index d41adc608..88b4d0fdd 100644 --- a/apps/api-gateway/src/fido/fido.controller.ts +++ b/apps/api-gateway/src/fido/fido.controller.ts @@ -157,7 +157,7 @@ export class FidoController { const fidoUserDetails = await this.fidoService.fetchFidoUserDetails(req.params.userName); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.login, + message: ResponseMessages.user.success.fetchUsers, data: fidoUserDetails.response }; return res.status(HttpStatus.OK).json(finalResponse); diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 596b3bd31..f18f55843 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -276,7 +276,7 @@ export class UserService { return this.generateToken(email, decryptedPassword); } - return this.generateToken(email, password); + return this.generateToken(email, password); } catch (error) { this.logger.error(`In Login User : ${JSON.stringify(error)}`); throw new RpcException(error.response); @@ -284,27 +284,24 @@ export class UserService { } async generateToken(email: string, password: string): Promise { - try { - const supaInstance = await this.supabaseService.getClient(); + const supaInstance = await this.supabaseService.getClient(); - this.logger.error(`supaInstance::`, supaInstance); + this.logger.error(`supaInstance::`, supaInstance); - const { data, error } = await supaInstance.auth.signInWithPassword({ - email, - password - }); + const { data, error } = await supaInstance.auth.signInWithPassword({ + email, + password + }); - if (error) { - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); - throw new BadRequestException(error?.message); - } + this.logger.error(`Supa Login Error::`, JSON.stringify(error)); - const token = data?.session; - return token; - } catch (error) { - this.logger.error(`An unexpected error occurred::`, error.message); - throw new InternalServerErrorException('An unexpected error occurred.'); + if (error) { + throw new BadRequestException(error?.message); } + + const token = data?.session; + + return token; } async getProfile(payload: { id }): Promise { From 4b6be1bebf507cbdbd8eab144bfe5ddb302ff770 Mon Sep 17 00:00:00 2001 From: Ajay Jadhav Date: Fri, 8 Sep 2023 21:10:08 +0530 Subject: [PATCH 029/162] docs: add company name in LICESE file Signed-off-by: Ajay Jadhav Signed-off-by: tipusinghaw --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 261eeb9e9..d98215d8f 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright [2023] [Blockster Labs Private Limited] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 528799a9cc9565af8936b157077c0ad99e844dc5 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Wed, 13 Sep 2023 12:20:17 +0530 Subject: [PATCH 030/162] worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- .env.sample | 58 ++----------- .gitignore | 1 - .../prisma/data/credebl-master-table.json | 85 +++++++++++++++++++ 3 files changed, 92 insertions(+), 52 deletions(-) create mode 100644 libs/prisma-service/prisma/data/credebl-master-table.json diff --git a/.env.sample b/.env.sample index e86573a16..1739e22e2 100644 --- a/.env.sample +++ b/.env.sample @@ -1,5 +1,10 @@ MODE=DEV + +SUPABASE_URL= // Please specify your Supabase Url +SUPABASE_KEY= // Please specify your Supabase Anon key +SUPABASE_JWT_SECRET= // Please specify your Supabase jwt secret + API_GATEWAY_PROTOCOL=http API_GATEWAY_HOST='0.0.0.0' API_GATEWAY_PORT=5000 @@ -10,7 +15,6 @@ AGENT_HOST=username@0.0.0.0 // Please specify your agent host VM and IP addres AWS_ACCOUNT_ID=xxxxx // Please provide your AWS account Id S3_BUCKET_ARN=arn:aws:s3:::xxxxx // Please provide your AWS bucket arn -TENANT_EMAIL_LOGO=credebl.jpg API_ENDPOINT=localhost:5000 #Use your local machine IP Address & PORT API_ENDPOINT_PORT=5000 @@ -20,68 +24,20 @@ NATS_HOST='0.0.0.0' NATS_PORT=4222 NATS_URL=nats://0.0.0.0:4222 -REDIS_HOST='localhost' # Use IP Address -REDIS_PORT=6379 - -POSTGRES_HOST=localhost # Use IP Address -POSTGRES_PORT=5432 -POSTGRES_USER=postgres -POSTGRES_PASSWORD=xxxxxxxx -POSTGRES_DATABASE=postgres - -POSTGRES_MEDIATOR_DATABASE='mediator_agent' -POSTGRES_MEDIATOR_PORT=5431 - -MEDIATOR_AGENT_LABEL=MediatorAgent -MEDIATOR_AGENT_ENDPOINT='' - SENDGRID_API_KEY=xxxxxxxxxxxxxx // Please provide your sendgrid API key FRONT_END_URL=http://localhost:3000 -FILE_SERVER=credebl-dev-mediator-indypool -FILE_SERVER_PORT=8081 -FILE_SERVER_USER=credebl -FILE_SERVER_HOST=0.0.0.0 - -REMOTE_FILE_DIR='/opt/cb-tails-file-server/tails/tails-files/' -ACCESSIBLE_FILE_LOCATION='tails-files' - -LOCAL_FILE_SERVER=/opt/credebl-platform/tails-files/ -GCLOUD_ENGINE_PATH=/home/credebl/.ssh/google_compute_engine - AFJ_AGENT_SPIN_UP=/apps/agent-provisioning/AFJ/scripts/start_agent.sh -AGENT_SPIN_UP_FILE=/agent-spinup/scripts/start_agent.sh -LIBINDY_KEY=CE7709D068DB5E88 - -AGENT_RESTART_SCRIPT=/agent-spinup/scripts/manage_agent.sh -AGENT_STATUS_SCRIPT=/agent-spinup/scripts/status_agent.sh - -WALLET_PROVISION_SCRIPT=/agent-spinup/scripts/wallet_provision.sh WALLET_STORAGE_HOST=localhost # Use IP Address WALLET_STORAGE_PORT=5432 WALLET_STORAGE_USER=postgres WALLET_STORAGE_PASSWORD=xxxxxx -KEYCLOAK_DOMAIN=http://localhost:8089/auth/ -KEYCLOAK_ADMIN_URL=http://localhost:8089 -KEYCLOAK_MASTER_REALM=master -KEYCLOAK_CREDEBL_REALM=credebl-platform -KEYCLOAK_MANAGEMENT_CLIENT_ID=adminClient -KEYCLOAK_MANAGEMENT_CLIENT_SECRET=xxxxxx-xxxx-xxxx-xxxx-xxxxxx #Refer from ADMIN CONSOLE of your Keycloak -KEYCLOAK_MANAGEMENT_ADEYA_CLIENT_ID=adeyaClient -KEYCLOAK_MANAGEMENT_ADEYA_CLIENT_SECRET=xxxxxx-xxxx-xxxx-xxxx-xxxxxx #Refer from ADMIN CONSOLE of your Keycloak - -FILE_UPLOAD_PATH_TENANT= /uploadedFiles/tenant-logo/ - -CRYPTO_PRIVATE_KEY=xxxxx-xxxxx-xxxxx-xxxxx -PLATFORM_URL=https://dev.credebl.com -KEYCLOAK_URL=http://localhost:8089 -PLATFORM_PROFILE_MODE=DEV +CRYPTO_PRIVATE_KEY=xxxxx-xxxxx-xxxxx-xxxxx #It should be same as studio UI AFJ_VERSION=afj-0.4.0:latest -INVOICE_PDF_URL=./invoice-pdf FIDO_API_ENDPOINT=http://localhost:8000 # Host:port of your FIDO (WebAuthn) Server @@ -91,6 +47,7 @@ PLATFORM_SEED= // The seed should consist of 32 characters. PLATFORM_ID= AFJ_AGENT_ENDPOINT_PATH=/apps/agent-provisioning/AFJ/endpoints/ +DATABASE_URL="postgresql://postgres:xxxxxx@localhost:5432/postgres?schema=public" #Provide supabase postgres url and Use the correct user/pwd, IP Address # This was inserted by prisma init: # Environment variables declared in this file are automatically made available to Prisma. @@ -99,7 +56,6 @@ AFJ_AGENT_ENDPOINT_PATH=/apps/agent-provisioning/AFJ/endpoints/ # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB (Preview). # See the documentation for all the connection string options: https://pris.ly/d/connection-strings -DATABASE_URL="postgresql://postgres:xxxxxx@localhost:5432/postgres?schema=public" #Use the correct user/pwd, IP Address # enable only prisma:engine-level debugging output export DEBUG="prisma:engine" diff --git a/.gitignore b/.gitignore index 4281aedfc..642765a46 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,3 @@ uploadedFiles sonar-project.properties .scannerwork/* coverage -libs/prisma-service/prisma/data/credebl-master-table.json diff --git a/libs/prisma-service/prisma/data/credebl-master-table.json b/libs/prisma-service/prisma/data/credebl-master-table.json new file mode 100644 index 000000000..35a44c6c3 --- /dev/null +++ b/libs/prisma-service/prisma/data/credebl-master-table.json @@ -0,0 +1,85 @@ +{ + "platformConfigData": { + "externalIp": "##Machine Ip Address for agent setup##", + "lastInternalId": "##Docker Network Ip Address##", + "username": "credebl", + "sgApiKey": "###Sendgrid Key###", + "emailFrom": "##Senders Mail ID##", + "apiEndpoint": "## Platform API Ip Address##", + "tailsFileServer": "##Machine Ip Address for agent setup##" + }, + "platformAdminData": { + "firstName": "CREDEBL", + "lastName": "CREDEBL", + "email": "platform.admin@yopmail.com", + "username": "platform.admin@yopmail.com", + "password": "####Please provide encrypted password using crypto-js###", + "verificationCode": "", + "isEmailVerified": true, + "supabaseUserId": "96cef763-e106-46c1-ac78-fadf2803b11f" + }, + "platformAdminOrganizationData": { + "name": "Platform-admin", + "description": "Platform-admin", + "logoUrl": "", + "website": "" + }, + "userOrgRoleData": { + "userId": 1, + "orgRoleId": 1, + "orgId": 1 + }, + "orgRoleData": [ + { + "name": "owner", + "description": "Organization Owner" + }, + { + "name": "admin", + "description": "Organization Admin" + }, + { + "name": "issuer", + "description": "Organization Credential Issuer" + }, + { + "name": "verifier", + "description": "Organization Credential Verifier" + }, + { + "name": "holder", + "description": "Receives credentials issued by organization" + }, + { + "name": "member", + "description": "Joins the organization as member" + } + ], + "agentTypeData": [ + { + "agent": "AFJ" + }, + { + "agent": "ACAPY" + } + ], + "orgAgentTypeData": [ + { + "agent": "DEDICATED" + }, + { + "agent": "SHARED" + } + ], + "ledgerData": [ + { + "name": "Bcovrin Testnet", + "networkType": "testnet", + "poolConfig": "http://test.bcovrin.vonx.io/genesis", + "isActive": true, + "networkString": "testnet", + "registerDIDEndpoint": "http://test.bcovrin.vonx.io/register", + "registerDIDPayload": "" + } + ] +} \ No newline at end of file From 841264af544e98ab6c8d59c3c98608209b931616 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Wed, 13 Sep 2023 15:47:56 +0530 Subject: [PATCH 031/162] Included credebl-master-table json file in the .gitignore Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 642765a46..77d23f742 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ uploadedFiles sonar-project.properties .scannerwork/* coverage +libs/prisma-service/prisma/data/credebl-master-table.json \ No newline at end of file From 3d8534aa75b6a3c59379162ff27cda4ad0a0c7ed Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Thu, 14 Sep 2023 20:14:22 +0530 Subject: [PATCH 032/162] worked on the username & orgslug feature for public profiles Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- .../dtos/update-organization-dto.ts | 7 +++- .../organization/organization.controller.ts | 15 +++---- .../src/organization/organization.service.ts | 4 +- .../src/user/dto/update-user-profile.dto.ts | 8 +++- apps/api-gateway/src/user/user.controller.ts | 13 +++--- apps/api-gateway/src/user/user.service.ts | 4 +- .../dtos/create-organization.dto.ts | 1 + .../interfaces/organization.interface.ts | 2 + .../repositories/organization.repository.ts | 14 +++++-- .../src/organization.controller.ts | 2 +- apps/organization/src/organization.service.ts | 42 ++++++++++++++++--- apps/user/interfaces/user.interface.ts | 2 + apps/user/repositories/user.repository.ts | 38 +++++++++++------ apps/user/src/user.controller.ts | 5 +-- apps/user/src/user.service.ts | 34 +++++++++++++-- 15 files changed, 143 insertions(+), 48 deletions(-) diff --git a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts index 44569b38d..1229ddc58 100644 --- a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts +++ b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts @@ -1,5 +1,5 @@ import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumber, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; @@ -37,4 +37,9 @@ export class UpdateOrganizationDto { @IsOptional() website: string; + @ApiPropertyOptional({ example: true }) + @IsBoolean({ message: 'isPublic should be boolean' }) + @IsOptional() + isPublic? = false; + } \ No newline at end of file diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 05fafc1da..25934cfe1 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -1,4 +1,4 @@ -import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { CommonService } from '@credebl/common'; import { Controller, Get, Put, Param, UseGuards, UseFilters } from '@nestjs/common'; import { OrganizationService } from './organization.service'; @@ -109,18 +109,19 @@ export class OrganizationController { return res.status(HttpStatus.OK).json(finalResponse); } - @Get('public-profile') + @Get('public-profiles/:orgSlug') @ApiOperation({ summary: 'Fetch user details', description: 'Fetch user details' }) - @ApiQuery({ - name: 'id', - type: Number, + + @ApiParam({ + name: 'orgSlug', + type: String, required: false }) - async getPublicProfile(@User() reqUser: user, @Query('id') id: number, @Res() res: Response): Promise { - const userData = await this.organizationService.getPublicProfile(id); + async getPublicProfile(@Param('orgSlug') orgSlug: string, @Res() res: Response): Promise { + const userData = await this.organizationService.getPublicProfile(orgSlug); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, diff --git a/apps/api-gateway/src/organization/organization.service.ts b/apps/api-gateway/src/organization/organization.service.ts index eb626e6d1..ba0d6f96d 100644 --- a/apps/api-gateway/src/organization/organization.service.ts +++ b/apps/api-gateway/src/organization/organization.service.ts @@ -65,8 +65,8 @@ export class OrganizationService extends BaseService { return this.sendNats(this.serviceProxy, 'get-public-organizations', payload); } - async getPublicProfile(id: number): Promise<{ response: object }> { - const payload = { id }; + async getPublicProfile(orgSlug: string): Promise<{ response: object }> { + const payload = {orgSlug }; try { return this.sendNats(this.serviceProxy, 'get-organization-public-profile', payload); } catch (error) { diff --git a/apps/api-gateway/src/user/dto/update-user-profile.dto.ts b/apps/api-gateway/src/user/dto/update-user-profile.dto.ts index 88d1c28b5..41484636c 100644 --- a/apps/api-gateway/src/user/dto/update-user-profile.dto.ts +++ b/apps/api-gateway/src/user/dto/update-user-profile.dto.ts @@ -1,6 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumber, IsOptional, IsString} from 'class-validator'; - +import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; export class UpdateUserProfileDto { @ApiProperty() @@ -22,4 +21,9 @@ export class UpdateUserProfileDto { @IsString({ message: 'lastName should be string' }) @IsOptional() lastName?: string; + + @ApiPropertyOptional({ example: true }) + @IsBoolean({ message: 'isPublic should be boolean' }) + @IsOptional() + isPublic? = false; } \ No newline at end of file diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 0677ad8eb..6c18391e8 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -6,6 +6,7 @@ import { ApiBody, ApiForbiddenResponse, ApiOperation, + ApiParam, ApiQuery, ApiResponse, ApiTags, @@ -222,18 +223,18 @@ export class UserController { } - @Get('public-profile') + @Get('public-profiles/:username') @ApiOperation({ summary: 'Fetch user details', description: 'Fetch user details' }) - @ApiQuery({ - name: 'id', - type: Number, + @ApiParam({ + name: 'username', + type: String, required: false }) - async getPublicProfile(@User() reqUser: user, @Query('id') id: number, @Res() res: Response): Promise { - const userData = await this.userService.getPublicProfile(id); + async getPublicProfile(@Param('username') username: string, @Res() res: Response): Promise { + const userData = await this.userService.getPublicProfile(username); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, diff --git a/apps/api-gateway/src/user/user.service.ts b/apps/api-gateway/src/user/user.service.ts index 6dfa2b0f5..9e97e50ac 100644 --- a/apps/api-gateway/src/user/user.service.ts +++ b/apps/api-gateway/src/user/user.service.ts @@ -53,8 +53,8 @@ export class UserService extends BaseService { } } - async getPublicProfile(id: number): Promise<{ response: object }> { - const payload = { id }; + async getPublicProfile(username: string): Promise<{ response: object }> { + const payload = { username }; try { return this.sendNats(this.serviceProxy, 'get-user-public-profile', payload); } catch (error) { diff --git a/apps/organization/dtos/create-organization.dto.ts b/apps/organization/dtos/create-organization.dto.ts index c2fd5249e..2d54b1ef7 100644 --- a/apps/organization/dtos/create-organization.dto.ts +++ b/apps/organization/dtos/create-organization.dto.ts @@ -6,6 +6,7 @@ export class CreateOrganizationDto { description?: string; logo?: string; website?: string; + orgSlug?:string; } export class CreateUserRoleOrgDto { diff --git a/apps/organization/interfaces/organization.interface.ts b/apps/organization/interfaces/organization.interface.ts index 5a1d9667f..f8cbb9e9c 100644 --- a/apps/organization/interfaces/organization.interface.ts +++ b/apps/organization/interfaces/organization.interface.ts @@ -18,4 +18,6 @@ export interface IUpdateOrganization { orgId: string; logo?: string; website?: string; + orgSlug?: string; + isPublic?:boolean } \ No newline at end of file diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 1f44ebcf9..8339b220a 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -53,7 +53,8 @@ export class OrganizationRepository { name: createOrgDto.name, logoUrl: createOrgDto.logo, description: createOrgDto.description, - website: createOrgDto.website + website: createOrgDto.website, + orgSlug: createOrgDto.orgSlug } }); } catch (error) { @@ -78,10 +79,11 @@ export class OrganizationRepository { name: updateOrgDto.name, logoUrl: updateOrgDto.logo, description: updateOrgDto.description, - website: updateOrgDto.website + website: updateOrgDto.website, + orgSlug: updateOrgDto.orgSlug, + publicProfile: updateOrgDto.isPublic } }); - } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); @@ -257,6 +259,12 @@ export class OrganizationRepository { org_agent_type: true, ledgers: true } + }, + userOrgRoles: { + include:{ + user: true, + orgRole:true + } } } }); diff --git a/apps/organization/src/organization.controller.ts b/apps/organization/src/organization.controller.ts index 6302aa0cb..85edbe3c9 100644 --- a/apps/organization/src/organization.controller.ts +++ b/apps/organization/src/organization.controller.ts @@ -72,7 +72,7 @@ export class OrganizationController { } @MessagePattern({ cmd: 'get-organization-public-profile' }) - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: { orgSlug }): Promise { return this.organizationService.getPublicProfile(payload); } diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index ec5ddfa96..ddafd90d8 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -48,6 +48,9 @@ export class OrganizationService { throw new ConflictException(ResponseMessages.organisation.error.exists); } + const orgSlug = await this.createOrgSlug(createOrgDto.name); + createOrgDto.orgSlug = orgSlug; + const organizationDetails = await this.organizationRepository.createOrganization(createOrgDto); const ownerRoleData = await this.orgRoleService.getRole(OrgRoles.OWNER); @@ -61,6 +64,21 @@ export class OrganizationService { } } + + /** + * + * @param orgName + * @returns OrgSlug + */ + createOrgSlug(orgName: string): string { + return orgName + .toLowerCase() // Convert the input to lowercase + .replace(/\s+/g, '-') // Replace spaces with hyphens + .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens + .replace(/--+/g, '-') // Replace multiple consecutive hyphens with a single hyphen + .replace(/^-+|-+$/g, ''); // Trim hyphens from the beginning and end of the string +} + /** * * @param registerOrgDto @@ -70,6 +88,16 @@ export class OrganizationService { // eslint-disable-next-line camelcase async updateOrganization(updateOrgDto: IUpdateOrganization, userId: number): Promise { try { + + const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); + + if (organizationExist) { + throw new ConflictException(ResponseMessages.organisation.error.exists); + } + + const orgSlug = await this.createOrgSlug(updateOrgDto.name); + updateOrgDto.orgSlug = orgSlug; + const organizationDetails = await this.organizationRepository.updateOrganization(updateOrgDto); await this.userActivityService.createActivity(userId, organizationDetails.id, `${organizationDetails.name} organization updated`, 'Organization details updated successfully'); return organizationDetails; @@ -147,13 +175,17 @@ export class OrganizationService { } } - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: { orgSlug }): Promise { try { - const query = { - id: payload.id, - publicProfile: true - }; + let query = {}; + + if (payload.orgSlug) { + query = { + orgSlug: payload.orgSlug, + publicProfile: true + }; + } const organizationDetails = await this.organizationRepository.getOrganization(query); if (!organizationDetails) { diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index 051ba3bfb..ed5febe00 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -26,6 +26,7 @@ export interface InvitationsI { export interface UserEmailVerificationDto{ email:string + username?:string } export interface userInfo{ @@ -51,4 +52,5 @@ export interface UpdateUserProfile { profileImg?: string; firstName: string, lastName: string, + isPublic: boolean, } \ No newline at end of file diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index cfdfc92e7..c0e070b36 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -7,11 +7,11 @@ import { InternalServerErrorException } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase import { user } from '@prisma/client'; -import { v4 as uuidv4 } from 'uuid'; interface UserQueryOptions { id?: number; // Use the appropriate type based on your data model email?: string; // Use the appropriate type based on your data model + username?: string // Add more properties if needed for other unique identifier fields }; @@ -24,12 +24,11 @@ export class UserRepository { * @param userEmailVerificationDto * @returns user email */ - async createUser(userEmailVerificationDto: UserEmailVerificationDto): Promise { + async createUser(userEmailVerificationDto: UserEmailVerificationDto, verifyCode: string): Promise { try { - const verifyCode = uuidv4(); const saveResponse = await this.prisma.user.create({ data: { - username: userEmailVerificationDto.email, + username: userEmailVerificationDto.username, email: userEmailVerificationDto.email, verificationCode: verifyCode.toString() } @@ -99,19 +98,26 @@ export class UserRepository { * @param id * @returns User profile data */ - async getUserPublicProfile(id: number): Promise { - const queryOptions: UserQueryOptions = { - id - }; - return this.findUserForPublicProfile(queryOptions); - } + async getUserPublicProfile(username: string): Promise { + + + let queryOptions: UserQueryOptions = {}; + if (username) { + queryOptions = { + username + }; + + } + return this.findUserForPublicProfile(queryOptions); + } /** * * @Body updateUserProfile * @returns Update user profile data */ - async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { + async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { + try { const userdetails = await this.prisma.user.update({ where: { @@ -120,7 +126,8 @@ export class UserRepository { data: { profileImg: updateUserProfile.profileImg, firstName: updateUserProfile.firstName, - lastName: updateUserProfile.lastName + lastName: updateUserProfile.lastName, + publicProfile: updateUserProfile?.isPublic } }); return userdetails; @@ -233,6 +240,9 @@ export class UserRepository { }, { email: queryOptions.email + }, + { + username: queryOptions.username } ] }, @@ -253,7 +263,8 @@ export class UserRepository { name: true, description: true, logoUrl: true, - website: true + website: true, + orgSlug: true }, where:{ publicProfile: true @@ -397,6 +408,7 @@ export class UserRepository { email: true, firstName: true, lastName: true, + profileImg: true, isEmailVerified: true, clientId: false, clientSecret: false, diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 629bb9dcf..0fa931ccb 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -1,3 +1,4 @@ +import { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { Controller } from '@nestjs/common'; @@ -5,8 +6,6 @@ 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 { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; - @Controller() export class UserController { @@ -43,7 +42,7 @@ export class UserController { } @MessagePattern({ cmd: 'get-user-public-profile' }) - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: { username }): Promise { return this.userService.getPublicProfile(payload); } @MessagePattern({ cmd: 'update-user-profile' }) diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index f18f55843..5535cc342 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -32,6 +32,7 @@ import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto' import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; import { UserDevicesRepository } from '../repositories/user-device.repository'; +import { v4 as uuidv4 } from 'uuid'; @Injectable() @@ -67,7 +68,10 @@ export class UserService { throw new ConflictException(ResponseMessages.user.error.verificationAlreadySent); } - const resUser = await this.userRepository.createUser(userEmailVerificationDto); + const verifyCode = uuidv4(); + const uniqueUsername = await this.createUsername(userEmailVerificationDto.email, verifyCode); + userEmailVerificationDto.username = uniqueUsername; + const resUser = await this.userRepository.createUser(userEmailVerificationDto, verifyCode); try { await this.sendEmailForVerification(userEmailVerificationDto.email, resUser.verificationCode); @@ -82,6 +86,30 @@ export class UserService { } } + async createUsername(email: string, verifyCode: string): Promise { + + try { + // eslint-disable-next-line prefer-destructuring + const emailTrim = email.split('@')[0]; + + // Replace special characters with hyphens + const cleanedUsername = emailTrim.toLowerCase().replace(/[^a-zA-Z0-9_]/g, '-'); + + // Generate a 5-digit UUID + // eslint-disable-next-line prefer-destructuring + const uuid = verifyCode.split('-')[0]; + + // Combine cleaned username and UUID + const uniqueUsername = `${cleanedUsername}-${uuid}`; + + return uniqueUsername; + + } catch (error) { + this.logger.error(`Error in createUsername: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error.message); + } + } + /** * * @param email @@ -313,9 +341,9 @@ export class UserService { } } - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: {username }): Promise { try { - const userProfile = await this.userRepository.getUserPublicProfile(payload.id); + const userProfile = await this.userRepository.getUserPublicProfile(payload.username); if (!userProfile) { throw new NotFoundException(ResponseMessages.user.error.profileNotFound); From 4c6a38fe5ff5ef5990d3b746ec3d0a9b162feeb9 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 12:04:32 +0530 Subject: [PATCH 033/162] refractored the GET API endoints for public profiles Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/api-gateway/src/organization/organization.controller.ts | 2 +- apps/api-gateway/src/user/user.controller.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 25934cfe1..e463cb3c0 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -79,7 +79,7 @@ export class OrganizationController { * @param res * @returns Users list of organization */ - @Get('/public') + @Get() @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Get public organization list', description: 'Get users list.' }) @ApiQuery({ diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 6c18391e8..953c57cf4 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -119,7 +119,7 @@ export class UserController { * @param res * @returns Users list of organization */ - @Get('/public') + @Get('/public-profiles') @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Get users list', description: 'Get users list.' }) @ApiQuery({ From 46b5779d9af7ea6a2b79a73cc3d12f3d890d46d0 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 12:11:13 +0530 Subject: [PATCH 034/162] refactored the organization GET API public profiles Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/api-gateway/src/organization/organization.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index e463cb3c0..32d4cec98 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -79,7 +79,7 @@ export class OrganizationController { * @param res * @returns Users list of organization */ - @Get() + @Get('/public-profiles') @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Get public organization list', description: 'Get users list.' }) @ApiQuery({ From b635eee58e7d128820c2de096b85bc2d5330238a Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 12:28:28 +0530 Subject: [PATCH 035/162] solved the regix expression grouping issue. Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/organization/src/organization.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index ddafd90d8..10ed3e6cc 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -48,7 +48,7 @@ export class OrganizationService { throw new ConflictException(ResponseMessages.organisation.error.exists); } - const orgSlug = await this.createOrgSlug(createOrgDto.name); + const orgSlug = this.createOrgSlug(createOrgDto.name); createOrgDto.orgSlug = orgSlug; const organizationDetails = await this.organizationRepository.createOrganization(createOrgDto); @@ -76,7 +76,7 @@ export class OrganizationService { .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens .replace(/--+/g, '-') // Replace multiple consecutive hyphens with a single hyphen - .replace(/^-+|-+$/g, ''); // Trim hyphens from the beginning and end of the string + .replace(/[^-+|-+$]/g, ''); // Trim hyphens from the beginning and end of the string } /** From 570bbc0b6a8c443e2eac99dd5352da9439e56adc Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 13:55:32 +0530 Subject: [PATCH 036/162] resolved the comments on the PR Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- .../repositories/organization.repository.ts | 2 +- apps/organization/src/organization.service.ts | 15 ++++++--------- apps/user/repositories/user.repository.ts | 10 +++------- apps/user/src/user.controller.ts | 4 ++-- apps/user/src/user.service.ts | 6 +++--- 5 files changed, 15 insertions(+), 22 deletions(-) diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 8339b220a..f20c85219 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -245,7 +245,7 @@ export class OrganizationRepository { } } - async getOrganization(queryObject: object): Promise { + async getOrganization(queryObject: object): Promise { try { return this.prisma.organisation.findFirst({ where: { diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 10ed3e6cc..5f1daee51 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -175,17 +175,14 @@ export class OrganizationService { } } - async getPublicProfile(payload: { orgSlug }): Promise { + async getPublicProfile(payload: { orgSlug: string }): Promise { + const {orgSlug} = payload; try { - let query = {}; - - if (payload.orgSlug) { - query = { - orgSlug: payload.orgSlug, - publicProfile: true - }; - } + const query = { + orgSlug, + publicProfile: true + }; const organizationDetails = await this.organizationRepository.getOrganization(query); if (!organizationDetails) { diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index c0e070b36..0743b628b 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -99,15 +99,11 @@ export class UserRepository { * @returns User profile data */ async getUserPublicProfile(username: string): Promise { - - - let queryOptions: UserQueryOptions = {}; - if (username) { - queryOptions = { + + const queryOptions: UserQueryOptions = { username }; - } return this.findUserForPublicProfile(queryOptions); } @@ -116,7 +112,7 @@ export class UserRepository { * @Body updateUserProfile * @returns Update user profile data */ - async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { + async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { try { const userdetails = await this.prisma.user.update({ diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 0fa931ccb..9d94f00e0 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -1,4 +1,4 @@ -import { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; +import { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, UserI, userInfo } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { Controller } from '@nestjs/common'; @@ -42,7 +42,7 @@ export class UserController { } @MessagePattern({ cmd: 'get-user-public-profile' }) - async getPublicProfile(payload: { username }): Promise { + async getPublicProfile(payload: { username }): Promise { return this.userService.getPublicProfile(payload); } @MessagePattern({ cmd: 'update-user-profile' }) diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 5535cc342..96753ee0c 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -27,7 +27,7 @@ import { sendEmail } from '@credebl/common/send-grid-helper-file'; import { user } from '@prisma/client'; import { Inject } from '@nestjs/common'; import { HttpException } from '@nestjs/common'; -import { AddPasskeyDetails, InvitationsI, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; +import { AddPasskeyDetails, InvitationsI, UpdateUserProfile, UserEmailVerificationDto, UserI, userInfo } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; @@ -341,7 +341,7 @@ export class UserService { } } - async getPublicProfile(payload: {username }): Promise { + async getPublicProfile(payload: { username }): Promise { try { const userProfile = await this.userRepository.getUserPublicProfile(payload.username); @@ -356,7 +356,7 @@ export class UserService { } } - async updateUserProfile(updateUserProfileDto: UpdateUserProfile): Promise { + async updateUserProfile(updateUserProfileDto: UpdateUserProfile): Promise { try { return this.userRepository.updateUserProfile(updateUserProfileDto); } catch (error) { From 55d5ec7015f7ed570d4220f64a44af373d832708 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 16:03:29 +0530 Subject: [PATCH 037/162] chnaged regular expression for orgSLug Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/organization/src/organization.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 5f1daee51..802d9473f 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -75,8 +75,7 @@ export class OrganizationService { .toLowerCase() // Convert the input to lowercase .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens - .replace(/--+/g, '-') // Replace multiple consecutive hyphens with a single hyphen - .replace(/[^-+|-+$]/g, ''); // Trim hyphens from the beginning and end of the string + .replace(/--+/g, '-'); // Replace multiple consecutive hyphens with a single hyphen } /** From 59ccb4ce563dee03e5ccda5d74ab539db732a2d7 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Fri, 15 Sep 2023 14:24:09 +0530 Subject: [PATCH 038/162] feat: implemented username & orgslug feature for public profiles (#85) * worked on the username & orgslug feature for public profiles Signed-off-by: @nishad.shirsat * refractored the GET API endoints for public profiles Signed-off-by: @nishad.shirsat * refactored the organization GET API public profiles Signed-off-by: @nishad.shirsat * solved the regix expression grouping issue. Signed-off-by: @nishad.shirsat * resolved the comments on the PR Signed-off-by: @nishad.shirsat --------- Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/organization/src/organization.service.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 802d9473f..b9a386263 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -71,12 +71,12 @@ export class OrganizationService { * @returns OrgSlug */ createOrgSlug(orgName: string): string { - return orgName - .toLowerCase() // Convert the input to lowercase - .replace(/\s+/g, '-') // Replace spaces with hyphens - .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens - .replace(/--+/g, '-'); // Replace multiple consecutive hyphens with a single hyphen -} + return orgName + .toLowerCase() // Convert the input to lowercase + .replace(/\s+/g, '-') // Replace spaces with hyphens + .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens + .replace(/--+/g, '-'); // Replace multiple consecutive hyphens with a single hyphen + } /** * @@ -175,7 +175,7 @@ export class OrganizationService { } async getPublicProfile(payload: { orgSlug: string }): Promise { - const {orgSlug} = payload; + const { orgSlug } = payload; try { const query = { From 593d6313b629e394d3ba6fed4755f6271d24b2f8 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Fri, 15 Sep 2023 19:25:28 +0530 Subject: [PATCH 039/162] implemented org and user to bydefault public (#87) Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/organization/repositories/organization.repository.ts | 4 +++- apps/organization/src/organization.service.ts | 4 ++-- apps/user/repositories/user.repository.ts | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index f20c85219..0ebb10fb3 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -54,7 +54,9 @@ export class OrganizationRepository { logoUrl: createOrgDto.logo, description: createOrgDto.description, website: createOrgDto.website, - orgSlug: createOrgDto.orgSlug + orgSlug: createOrgDto.orgSlug, + publicProfile: true + } }); } catch (error) { diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index b9a386263..e5670064f 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -89,8 +89,8 @@ export class OrganizationService { try { const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); - - if (organizationExist) { + + if (organizationExist && organizationExist.id !== Number(updateOrgDto.orgId)) { throw new ConflictException(ResponseMessages.organisation.error.exists); } diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 0743b628b..e64f5033a 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -30,7 +30,8 @@ export class UserRepository { data: { username: userEmailVerificationDto.username, email: userEmailVerificationDto.email, - verificationCode: verifyCode.toString() + verificationCode: verifyCode.toString(), + publicProfile: true } }); From 16f25b1c4b73488f23aa0673cf62f1b8d3f1135d Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:51:55 +0530 Subject: [PATCH 040/162] refactor: API refactor in user, organization, auth and agent module (#82) * docs: add CONTRIBUTING guidelines Signed-off-by: KulkarniShashank * docs: add company name in LICESE file Signed-off-by: Ajay Jadhav Signed-off-by: KulkarniShashank * merge dev branch to main (#77) * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw * feat:implemented add passke Signed-off-by: tipusinghaw * fix: login error message Signed-off-by: tipusinghaw --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank * Included credebl-master-table json file in the .gitignore Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank * fix: API refactor in user, organization, auth and agent module Signed-off-by: KulkarniShashank * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * feat:implemented add passke Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * fix: login error message Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * Set Validation for the user login Signed-off-by: KulkarniShashank * Removed unnecessarily conditions in user dto Signed-off-by: KulkarniShashank * added the role matrix in the organization, user and agent module Signed-off-by: KulkarniShashank * Added the error handling in the API-gateway Signed-off-by: KulkarniShashank * Added role guard in user and organization Signed-off-by: KulkarniShashank * Error handling in the user signin functionality Signed-off-by: KulkarniShashank * Function name changes in the singin functionality Signed-off-by: KulkarniShashank * Added activity log success in user Signed-off-by: KulkarniShashank * Solved the bug regarding in organization update functionality Signed-off-by: KulkarniShashank * added role guard in user invitation Signed-off-by: KulkarniShashank * Error handling on api-gateway dto Signed-off-by: KulkarniShashank * Added veriable for the seed in agent-service Signed-off-by: KulkarniShashank * Added veriable for the seed on globaly in agent-service Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: Ajay Jadhav Signed-off-by: tipusinghaw Signed-off-by: @nishad.shirsat Co-authored-by: Ajay Jadhav Co-authored-by: Ajay Jadhav Co-authored-by: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Co-authored-by: @nishad.shirsat Co-authored-by: tipusinghaw Signed-off-by: tipusinghaw --- .../src/agent-service.service.ts | 20 +- apps/api-gateway/common/exception-handler.ts | 48 ++- .../agent-service/agent-service.controller.ts | 83 +++-- .../agent-service/dto/agent-service.dto.ts | 19 +- .../agent-service/dto/create-tenant.dto.ts | 15 +- .../api-gateway/src/authz/authz.controller.ts | 121 ++++++- apps/api-gateway/src/authz/authz.service.ts | 25 +- .../src/authz/guards/org-roles.guard.ts | 17 +- .../src/dtos/email-validator.dto.ts | 12 + .../organization/dtos/send-invitation.dto.ts | 6 +- .../dtos/update-organization-dto.ts | 8 +- .../dtos/update-user-roles.dto.ts | 14 +- .../organization/organization.controller.ts | 265 +++++++++------ .../src/organization/organization.service.ts | 53 ++- .../user/dto/accept-reject-invitation.dto.ts | 6 +- apps/api-gateway/src/user/dto/add-user.dto.ts | 10 +- .../src/user/dto/login-user.dto.ts | 31 +- .../src/user/dto/update-user-profile.dto.ts | 10 +- apps/api-gateway/src/user/user.controller.ts | 305 +++++------------- apps/api-gateway/src/user/user.service.ts | 83 +---- .../repositories/organization.repository.ts | 24 +- .../src/organization.controller.ts | 4 +- apps/organization/src/organization.service.ts | 41 +-- apps/user/interfaces/user.interface.ts | 1 + apps/user/src/user.controller.ts | 30 +- apps/user/src/user.service.ts | 97 +++--- libs/common/src/response-messages/index.ts | 12 +- 27 files changed, 716 insertions(+), 644 deletions(-) create mode 100644 apps/api-gateway/src/dtos/email-validator.dto.ts diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index b1a818b26..893124ff0 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -122,7 +122,7 @@ export class AgentServiceService { return internalIp; } catch (error) { this.logger.error(`error in valid internal ip : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -573,7 +573,7 @@ export class AgentServiceService { }); socket.emit('error-in-wallet-creation-process', { clientId: payload.clientSocketId, error }); } - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -739,7 +739,7 @@ export class AgentServiceService { return data; } catch (error) { this.logger.error(`Error in connection Invitation in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -761,7 +761,7 @@ export class AgentServiceService { return getProofPresentationsData; } catch (error) { this.logger.error(`Error in proof presentations in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -783,7 +783,7 @@ export class AgentServiceService { return getProofPresentationById; } catch (error) { this.logger.error(`Error in proof presentation by id in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -805,7 +805,7 @@ export class AgentServiceService { return sendProofRequest; } catch (error) { this.logger.error(`Error in send proof request in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -817,7 +817,7 @@ export class AgentServiceService { return verifyPresentation; } catch (error) { this.logger.error(`Error in verify proof presentation in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -860,7 +860,7 @@ export class AgentServiceService { } catch (error) { this.logger.error(`Agent health details : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -872,7 +872,7 @@ export class AgentServiceService { return sendProofRequest; } catch (error) { this.logger.error(`Error in send out of band proof request in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -884,7 +884,7 @@ export class AgentServiceService { return getProofFormData; } catch (error) { this.logger.error(`Error in get proof form data in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } } diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index 45a87a1cb..13c102539 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -16,12 +16,48 @@ export class CustomExceptionFilter extends BaseExceptionFilter { exception.message = 'Oops! Something went wrong. Please try again'; } - const errorResponse = { - statusCode: status, - message: exception.message || 'Internal server error', - error: exception.message - }; + let errorResponse; + if (exception && exception["statusCode"] === HttpStatus.INTERNAL_SERVER_ERROR) { + errorResponse = { + statusCode: status, + message: 'Oops! Something went wrong. Please try again', + error: 'Oops! Something went wrong. Please try again' + }; + } 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' + }; + } else { + if (exception && exception["response"] && exception.message) { - response.status(status).json(errorResponse); + if (Array.isArray(exception["response"].message)) { + exception["response"].message.forEach((msg) => { + errorResponse = { + statusCode: exception["statusCode"] ? exception["statusCode"] : status, + message: msg || 'Internal server error', + error: msg || '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' + }; + + } + } + + response.status(errorResponse.statusCode).json(errorResponse); } } \ No newline at end of file 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 55a083404..d4b2890b6 100644 --- a/apps/api-gateway/src/agent-service/agent-service.controller.ts +++ b/apps/api-gateway/src/agent-service/agent-service.controller.ts @@ -11,10 +11,10 @@ import { HttpStatus, Res, Get, - Query, - UseFilters + UseFilters, + Param } from '@nestjs/common'; -import { ApiTags, ApiResponse, ApiOperation, ApiUnauthorizedResponse, ApiForbiddenResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger'; +import { ApiTags, ApiResponse, ApiOperation, ApiUnauthorizedResponse, ApiForbiddenResponse, ApiBearerAuth } from '@nestjs/swagger'; import { GetUser } from '../authz/decorators/get-user.decorator'; import { AuthGuard } from '@nestjs/passport'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; @@ -30,43 +30,74 @@ import { user } from '@prisma/client'; import { CreateTenantDto } from './dto/create-tenant.dto'; import { User } from '../authz/decorators/user.decorator'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; +import { Roles } from '../authz/decorators/roles.decorator'; +import { OrgRoles } from 'libs/org-roles/enums'; +import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; +const seedLength = 32; @UseFilters(CustomExceptionFilter) -@Controller('agent-service') +@Controller() @ApiTags('agents') -@UseGuards(AuthGuard('jwt')) @ApiBearerAuth() @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) export class AgentController { constructor(private readonly agentService: AgentService) { } - private readonly logger = new Logger(); + @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: number, @Res() res: Response): Promise { + const agentData = await this.agentService.getAgentHealth(reqUser, orgId); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.agent.success.health, + data: agentData.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } + /** * * @param agentSpinupDto * @param user * @returns */ - @Post('/spinup') + @Post('/orgs/:orgId/agents/spinup') @ApiOperation({ summary: 'Agent spinup', description: 'Create a new agent spin up.' }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) async agentSpinup( @Body() agentSpinupDto: AgentSpinupDto, + @Param('orgId') orgId: number, @GetUser() user: user, @Res() res: Response ): Promise>> { + if (seedLength !== agentSpinupDto.seed.length) { + throw new BadRequestException(`seed must be at most 32 characters.`); + } + 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.log(`**** Spin up the agent...${JSON.stringify(agentSpinupDto)}`); + + agentSpinupDto.orgId = orgId; const agentDetails = await this.agentService.agentSpinup(agentSpinupDto, user); const finalResponse: IResponseType = { @@ -78,17 +109,27 @@ export class AgentController { return res.status(HttpStatus.CREATED).json(finalResponse); } - @Post('/tenant') + @Post('/orgs/:orgId/agents/wallet') @ApiOperation({ summary: 'Shared Agent', description: 'Create a shared agent.' }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) async createTenant( + @Param('orgId') orgId: number, @Body() createTenantDto: CreateTenantDto, @GetUser() user: user, @Res() res: Response ): Promise { + + createTenantDto.orgId = orgId; + + if (seedLength !== createTenantDto.seed.length) { + throw new BadRequestException(`seed must be at most 32 characters.`); + } + const tenantDetails = await this.agentService.createTenant(createTenantDto, user); const finalResponse: IResponseType = { @@ -99,28 +140,4 @@ export class AgentController { return res.status(HttpStatus.CREATED).json(finalResponse); } - - @Get('/health') - @ApiOperation({ - summary: 'Fetch agent details', - description: 'Fetch agent health details' - }) - @ApiQuery({ - name: 'orgId', - type: Number, - required: false - }) - async getAgentHealth(@User() reqUser: user, @Query('orgId') orgId: number, @Res() res: Response): Promise { - const agentData = await this.agentService.getAgentHealth(reqUser, orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.agent.success.health, - data: agentData.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - - } - -} +} \ No newline at end of file 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 c05383e9e..d8c449095 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 @@ -1,10 +1,12 @@ import { trim } from '@credebl/common/cast.helper'; -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, Matches, MaxLength, MinLength } from 'class-validator'; const regex = /^[a-zA-Z0-9 ]*$/; export class AgentSpinupDto { + orgId: number; + @ApiProperty() @Transform(({ value }) => trim(value)) @IsNotEmpty({ message: 'walletName is required'}) @@ -31,28 +33,28 @@ export class AgentSpinupDto { @Matches(/^\S*$/, { message: 'Spaces are not allowed in seed' }) - seed: string; - - @ApiProperty() - @IsNumber() - orgId: number; + seed: string; @ApiProperty() + @ApiPropertyOptional() @IsOptional() @IsNumber() ledgerId?: number; @ApiProperty() @IsOptional() + @ApiPropertyOptional() clientSocketId?: string; @ApiProperty() @IsOptional() - @IsBoolean() + @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.' }) @@ -62,10 +64,11 @@ export class AgentSpinupDto { @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; -} +} \ 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 6ed0aa6ad..73161436e 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 @@ -1,13 +1,13 @@ import { trim } from '@credebl/common/cast.helper'; -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { IsNotEmpty, IsNumber, IsString, Matches, MaxLength, MinLength, IsOptional } from 'class-validator'; +import { MaxLength, IsString, MinLength, Matches, IsNotEmpty, IsOptional } from 'class-validator'; const labelRegex = /^[a-zA-Z0-9 ]*$/; export class CreateTenantDto { @ApiProperty() - @IsString() - @Transform(({ value }) => value.trim()) @MaxLength(25, { message: 'Maximum length for label must be 25 characters.' }) + @IsString({ message: 'label must be in string format.' }) + @Transform(({ value }) => value.trim()) @MinLength(2, { message: 'Minimum length for label must be 2 characters.' }) @Matches(labelRegex, { message: 'Label must not contain special characters.' }) @Matches(/^\S*$/, { @@ -16,20 +16,19 @@ export class CreateTenantDto { label: string; @ApiProperty() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'seed is required' }) @MaxLength(32, { message: 'seed must be at most 32 characters.' }) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'seed is required'}) @IsString({ message: 'seed must be in string format.' }) @Matches(/^\S*$/, { message: 'Spaces are not allowed in seed' }) seed: string; - @ApiProperty() - @IsNumber() orgId: number; @ApiProperty() @IsOptional() + @ApiPropertyOptional() clientSocketId?: string; } \ 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 63b26cfa5..e1b9c3585 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -1,17 +1,130 @@ import { + Body, Controller, - Logger + Get, + HttpStatus, + Logger, + Post, + Query, + Res, + UnauthorizedException, + UseFilters } from '@nestjs/common'; import { AuthzService } from './authz.service'; -// import { CommonService } from "@credebl/common"; import { CommonService } from '../../../../libs/common/src/common.service'; +import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiResponseDto } from '../dtos/apiResponse.dto'; +import { UserEmailVerificationDto } from '../user/dto/create-user.dto'; +import IResponseType from '@credebl/common/interfaces/response.interface'; +import { ResponseMessages } from '@credebl/common/response-messages'; +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 { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; -@Controller('authz') +@Controller('auth') +@ApiTags('auth') +@UseFilters(CustomExceptionFilter) export class AuthzController { private logger = new Logger('AuthzController'); constructor(private readonly authzService: AuthzService, private readonly commonService: CommonService) { } -} + /** + * + * @param query + * @param res + * @returns User email verified + */ + @Get('/verify') + @ApiResponse({ status: 200, 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); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.emaiVerified + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } + + /** + * + * @param email + * @param res + * @returns Email sent success + */ + @Post('/verification-mail') + @ApiResponse({ status: 201, description: 'Success', 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); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.sendVerificationCode + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + /** + * + * @param email + * @param userInfo + * @param res + * @returns Add new user + */ + @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 decryptedPassword = this.commonService.decryptPassword(userInfo.password); + + userInfo.password = decryptedPassword; + const userDetails = await this.authzService.addUserDetails(userInfo); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userDetails.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + /** + * + * @param loginUserDto + * @param res + * @returns User 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 }) + @ApiBody({ type: LoginUserDto }) + async login(@Body() loginUserDto: LoginUserDto, @Res() res: Response): Promise { + + if (loginUserDto.email) { + let decryptedPassword; + if (loginUserDto.password) { + decryptedPassword = this.commonService.decryptPassword(loginUserDto.password); + } + const userData = await this.authzService.login(loginUserDto.email, decryptedPassword, loginUserDto.isPasskey); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.login, + data: userData.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } else { + throw new UnauthorizedException(`Please provide valid credentials`); + } + } + +} \ No newline at end of file diff --git a/apps/api-gateway/src/authz/authz.service.ts b/apps/api-gateway/src/authz/authz.service.ts index ab5cd587c..317712669 100644 --- a/apps/api-gateway/src/authz/authz.service.ts +++ b/apps/api-gateway/src/authz/authz.service.ts @@ -6,6 +6,9 @@ import { WebSocketServer } 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'; @Injectable() @@ -25,4 +28,24 @@ 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 verifyEmail(param: EmailVerificationDto): Promise { + const payload = { param }; + return this.sendNats(this.authServiceProxy, 'user-email-verification', payload); + + } + + async login(email: string, password?: string, isPasskey = false): Promise<{ response: object }> { + const payload = { email, password, isPasskey }; + return this.sendNats(this.authServiceProxy, 'user-holder-login', payload); + } + + async addUserDetails(userInfo: AddUserDetails): Promise<{ response: string }> { + const payload = { userInfo }; + return this.sendNats(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 0c6dd349e..46c60aec7 100644 --- a/apps/api-gateway/src/authz/guards/org-roles.guard.ts +++ b/apps/api-gateway/src/authz/guards/org-roles.guard.ts @@ -9,7 +9,7 @@ import { Reflector } from '@nestjs/core'; @Injectable() export class OrgRolesGuard implements CanActivate { - constructor(private reflector: Reflector) { } // eslint-disable-next-line array-callback-return + constructor(private reflector: Reflector) { } // eslint-disable-next-line array-callback-return private logger = new Logger('Org Role Guard'); @@ -29,8 +29,8 @@ export class OrgRolesGuard implements CanActivate { const { user } = req; - if (req.query.orgId || req.body.orgId) { - const orgId = req.query.orgId || req.body.orgId; + if (req.params.orgId || req.query.orgId || req.body.orgId) { + const orgId = req.params.orgId || req.query.orgId || req.body.orgId; const specificOrg = user.userOrgRoles.find((orgDetails) => { if (!orgDetails.orgId) { @@ -44,12 +44,17 @@ export class OrgRolesGuard implements CanActivate { } user.selectedOrg = specificOrg; - user.selectedOrg.orgRoles = user.userOrgRoles.map(roleItem => roleItem.orgRole.name); - + // eslint-disable-next-line array-callback-return + user.selectedOrg.orgRoles = user.userOrgRoles.map((orgRoleItem) => { + if (orgRoleItem.orgId && orgRoleItem.orgId.toString() === orgId.toString()) { + return orgRoleItem.orgRole.name; + } + }); + } else { throw new HttpException('organization is required', HttpStatus.BAD_REQUEST); } return requiredRoles.some((role) => user.selectedOrg?.orgRoles.includes(role)); } -} +} \ No newline at end of file diff --git a/apps/api-gateway/src/dtos/email-validator.dto.ts b/apps/api-gateway/src/dtos/email-validator.dto.ts new file mode 100644 index 000000000..c41ad0ef5 --- /dev/null +++ b/apps/api-gateway/src/dtos/email-validator.dto.ts @@ -0,0 +1,12 @@ + +import { ApiProperty } from '@nestjs/swagger'; +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() + email: string; +} \ No newline at end of file diff --git a/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts b/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts index a6c1b43c8..df6b85aae 100644 --- a/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts +++ b/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts @@ -1,5 +1,5 @@ import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsEmail, IsNotEmpty, IsNumber, IsString, ValidateNested } from 'class-validator'; +import { IsArray, IsEmail, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; import { Transform, Type } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; @@ -29,9 +29,5 @@ export class BulkSendInvitationDto { @ValidateNested({ each: true }) @Type(() => SendInvitationDto) invitations: SendInvitationDto[]; - - @ApiProperty({ example: 1 }) - @IsNotEmpty({ message: 'Please provide valid orgId' }) - @IsNumber() orgId: number; } \ No newline at end of file diff --git a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts index 1229ddc58..c3fb2d6aa 100644 --- a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts +++ b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts @@ -1,5 +1,5 @@ import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; +import { IsNotEmpty, IsOptional, IsString, IsBoolean, MaxLength, MinLength } from 'class-validator'; import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; @@ -7,9 +7,7 @@ import { trim } from '@credebl/common/cast.helper'; @ApiExtraModels() export class UpdateOrganizationDto { - @ApiProperty() - @IsNotEmpty({ message: 'orgId is required.' }) - @IsNumber() + orgId: number; @ApiProperty() @@ -40,6 +38,6 @@ export class UpdateOrganizationDto { @ApiPropertyOptional({ example: true }) @IsBoolean({ message: 'isPublic should be boolean' }) @IsOptional() - isPublic? = false; + isPublic?: boolean = false; } \ No newline at end of file diff --git a/apps/api-gateway/src/organization/dtos/update-user-roles.dto.ts b/apps/api-gateway/src/organization/dtos/update-user-roles.dto.ts index 5ddb6a9f8..f1270f88b 100644 --- a/apps/api-gateway/src/organization/dtos/update-user-roles.dto.ts +++ b/apps/api-gateway/src/organization/dtos/update-user-roles.dto.ts @@ -1,21 +1,11 @@ -import { IsArray, IsNotEmpty, IsNumber } from 'class-validator'; +import { IsArray, IsNotEmpty } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { toNumber } from '@credebl/common/cast.helper'; export class UpdateUserRolesDto { - @ApiProperty({ example: '2' }) - @IsNotEmpty({ message: 'Please provide valid orgId' }) - @Transform(({ value }) => toNumber(value)) - @IsNumber() - orgId: number; - @ApiProperty({ example: '3' }) - @IsNotEmpty({ message: 'Please provide valid userId' }) - @Transform(({ value }) => toNumber(value)) - @IsNumber() + orgId: number; userId: number; @ApiProperty({ example: [2, 1, 3] }) diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 32d4cec98..667c7bb26 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -26,9 +26,11 @@ import { GetAllOrganizationsDto } from './dtos/get-all-organizations.dto'; import { GetAllSentInvitationsDto } from './dtos/get-all-sent-invitations.dto'; import { UpdateOrganizationDto } from './dtos/update-organization-dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; +import { IUserRequestInterface } from '../interfaces/IUserRequestInterface'; +import { GetAllUsersDto } from '../user/dto/get-all-users.dto'; @UseFilters(CustomExceptionFilter) -@Controller('organization') +@Controller('orgs') @ApiTags('organizations') @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @@ -39,39 +41,6 @@ export class OrganizationController { private readonly commonService: CommonService ) { } - @Post('/') - @ApiOperation({ summary: 'Create a new Organization', description: 'Create an organization' }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - 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, - message: ResponseMessages.organisation.success.create - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - - @Get('/') - @ApiOperation({ summary: 'Get all organizations', description: 'Get all organizations' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - 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 - }; - return res.status(HttpStatus.OK).json(finalResponse); - - } - /** * * @param user @@ -79,9 +48,9 @@ export class OrganizationController { * @param res * @returns Users list of organization */ - @Get('/public-profiles') + @Get('/public-profile') @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ summary: 'Get public organization list', description: 'Get users list.' }) + @ApiOperation({ summary: 'Get all public profile of organizations', description: 'Get all public profile of organizations.' }) @ApiQuery({ name: 'pageNumber', type: Number, @@ -109,6 +78,28 @@ export class OrganizationController { return res.status(HttpStatus.OK).json(finalResponse); } + @Get('/roles') + @ApiOperation({ + summary: 'Fetch org-roles details', + description: 'Fetch org-roles details' + }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async getOrgRoles(@Res() res: Response): Promise { + + const orgRoles = await this.organizationService.getOrgRoles(); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.organisation.success.fetchOrgRoles, + data: orgRoles + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } + @Get('public-profiles/:orgSlug') @ApiOperation({ summary: 'Fetch user details', @@ -133,135 +124,221 @@ export class OrganizationController { } - @Get('/roles') - @ApiOperation({ - summary: 'Fetch org-roles details', - description: 'Fetch org-roles details' - }) + + @Get('/dashboard/:orgId') + @ApiOperation({ summary: 'Get an organization', description: 'Get an organization' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async getOrgRoles(@Res() res: Response): Promise { - const orgRoles = await this.organizationService.getOrgRoles(); + async getOrganizationDashboard(@Param('orgId') orgId: number, @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.fetchOrgRoles, - data: orgRoles + message: ResponseMessages.organisation.success.getOrgDashboard, + data: getOrganization.response }; - return res.status(HttpStatus.OK).json(finalResponse); } - @Post('/invitations') - @ApiOperation({ - summary: 'Create organization invitation', - description: 'Create send invitation' - }) + @Get('/:orgId/invitations') + @ApiOperation({ summary: 'Get an invitations', description: 'Get an invitations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() - async createInvitation(@Body() bulkInvitationDto: BulkSendInvitationDto, @User() user: user, @Res() res: Response): Promise { - await this.organizationService.createInvitation(bulkInvitationDto, user.id); + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + async getInvitationsByOrgId(@Param('orgId') orgId: number, @Query() getAllInvitationsDto: GetAllSentInvitationsDto, @Res() res: Response): Promise { + + const getInvitationById = await this.organizationService.getInvitationsByOrgId(orgId, getAllInvitationsDto); const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.organisation.success.createInvitation + statusCode: HttpStatus.OK, + message: ResponseMessages.organisation.success.getInvitation, + data: getInvitationById.response }; - - return res.status(HttpStatus.CREATED).json(finalResponse); + return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/invitations/:id') - @ApiOperation({ summary: 'Get an invitations', description: 'Get an invitations' }) + @Get('/') + @ApiOperation({ summary: 'Get all organizations', description: 'Get all organizations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async getInvitationsByOrgId(@Param('id') orgId: number, @Query() getAllInvitationsDto: GetAllSentInvitationsDto, @Res() res: Response): Promise { + @ApiQuery({ + name: 'pageNumber', + example: '1', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + example: '10', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + example: '', + type: String, + required: false + }) + async getOrganizations(@Query() getAllOrgsDto: GetAllOrganizationsDto, @Res() res: Response, @User() reqUser: user): Promise { - const getInvitationById = await this.organizationService.getInvitationsByOrgId(orgId, getAllInvitationsDto); + const getOrganizations = await this.organizationService.getOrganizations(getAllOrgsDto, reqUser.id); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.organisation.success.getInvitation, - data: getInvitationById.response + message: ResponseMessages.organisation.success.getOrganizations, + data: getOrganizations.response }; return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/dashboard') + @Get('/:orgId') @ApiOperation({ summary: 'Get an organization', description: 'Get an organization' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() - @ApiQuery( - { name: 'orgId', required: true } - ) - async getOrganizationDashboard(@Query('orgId') orgId: number, @Res() res: Response, @User() reqUser: user): Promise { + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + async getOrganization(@Param('orgId') orgId: number, @Res() res: Response, @User() reqUser: user): Promise { - const getOrganization = await this.organizationService.getOrganizationDashboard(orgId, reqUser.id); + const getOrganization = await this.organizationService.getOrganization(orgId, reqUser.id); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.organisation.success.getOrgDashboard, + message: ResponseMessages.organisation.success.getOrganization, data: getOrganization.response }; return res.status(HttpStatus.OK).json(finalResponse); } - - @Put('user-roles') - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + /** + * + * @param user + * @param orgId + * @param res + * @returns Users list of organization + */ + @Get('/:orgId/users') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) @ApiBearerAuth() @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, @Res() res: Response): Promise { - - await this.organizationService.updateUserRoles(updateUserDto, updateUserDto.userId); - + @ApiOperation({ summary: 'Get organization users list', description: 'Get organization users list.' }) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Param('orgId') orgId: number, @Res() res: Response): Promise { + const users = await this.organizationService.getOrgUsers(orgId, getAllUsersDto); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.organisation.success.updateUserRoles + message: ResponseMessages.user.success.fetchUsers, + data: users?.response }; return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/:id') - @ApiOperation({ summary: 'Get an organization', description: 'Get an organization' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @Post('/') + @ApiOperation({ summary: 'Create a new Organization', description: 'Create an organization' }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async getOrganization(@Param('id') orgId: number, @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, + message: ResponseMessages.organisation.success.create + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } - const getOrganization = await this.organizationService.getOrganization(orgId, reqUser.id); + @Post('/:orgId/invitations') + @ApiOperation({ + summary: 'Create organization invitation', + description: 'Create send invitation' + }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiBearerAuth() + async createInvitation(@Body() bulkInvitationDto: BulkSendInvitationDto, @Param('orgId') orgId: number, @User() user: user, @Res() res: Response): Promise { + + bulkInvitationDto.orgId = orgId; + await this.organizationService.createInvitation(bulkInvitationDto, user.id); const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.organisation.success.getOrganization, - data: getOrganization.response + statusCode: HttpStatus.CREATED, + message: ResponseMessages.organisation.success.createInvitation }; - return res.status(HttpStatus.OK).json(finalResponse); + + return res.status(HttpStatus.CREATED).json(finalResponse); } + @Put('/:orgId/user-roles/:userId') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @ApiBearerAuth() + @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: number, @Param('userId') userId: number, @Res() res: Response): Promise { + + updateUserDto.orgId = orgId; + updateUserDto.userId = userId; + await this.organizationService.updateUserRoles(updateUserDto, updateUserDto.userId); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.organisation.success.updateUserRoles + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } - @Put('/') + @Put('/:orgId') @ApiOperation({ summary: 'Update Organization', description: 'Update an organization' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async updateOrganization(@Body() updateOrgDto: UpdateOrganizationDto, @Res() res: Response, @User() reqUser: user): Promise { + async updateOrganization(@Body() updateOrgDto: UpdateOrganizationDto, @Param('orgId') orgId: number, @Res() res: Response, @User() reqUser: user): Promise { - await this.organizationService.updateOrganization(updateOrgDto, reqUser.id); + updateOrgDto.orgId = orgId; + await this.organizationService.updateOrganization(updateOrgDto, reqUser.id, orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, @@ -269,4 +346,4 @@ export class OrganizationController { }; 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 ba0d6f96d..c7579dcf5 100644 --- a/apps/api-gateway/src/organization/organization.service.ts +++ b/apps/api-gateway/src/organization/organization.service.ts @@ -1,6 +1,6 @@ import { Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; -import { ClientProxy, RpcException } from '@nestjs/microservices'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { CreateOrganizationDto } from './dtos/create-organization-dto'; import { GetAllOrganizationsDto } from './dtos/get-all-organizations.dto'; @@ -8,6 +8,7 @@ import { GetAllSentInvitationsDto } from './dtos/get-all-sent-invitations.dto'; 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'; @Injectable() export class OrganizationService extends BaseService { @@ -21,13 +22,8 @@ export class OrganizationService extends BaseService { * @returns Organization creation Success */ async createOrganization(createOrgDto: CreateOrganizationDto, userId: number): Promise { - try { - const payload = { createOrgDto, userId }; - return this.sendNats(this.serviceProxy, 'create-organization', payload); - } catch (error) { - this.logger.error(`In service Error: ${error}`); - throw new RpcException(error.response); - } + const payload = { createOrgDto, userId }; + return this.sendNats(this.serviceProxy, 'create-organization', payload); } /** @@ -35,14 +31,9 @@ export class OrganizationService extends BaseService { * @param updateOrgDto * @returns Organization update Success */ - async updateOrganization(updateOrgDto: UpdateOrganizationDto, userId: number): Promise { - try { - const payload = { updateOrgDto, userId }; - return this.sendNats(this.serviceProxy, 'update-organization', payload); - } catch (error) { - this.logger.error(`In service Error: ${error}`); - throw new RpcException(error.response); - } + async updateOrganization(updateOrgDto: UpdateOrganizationDto, userId: number, orgId: number): Promise { + const payload = { updateOrgDto, userId, orgId }; + return this.sendNats(this.serviceProxy, 'update-organization', payload); } /** @@ -66,7 +57,7 @@ export class OrganizationService extends BaseService { } async getPublicProfile(orgSlug: string): Promise<{ response: object }> { - const payload = {orgSlug }; + const payload = { orgSlug }; try { return this.sendNats(this.serviceProxy, 'get-organization-public-profile', payload); } catch (error) { @@ -109,13 +100,8 @@ export class OrganizationService extends BaseService { * @returns get organization roles */ async getOrgRoles(): Promise { - try { - const payload = {}; - return this.sendNats(this.serviceProxy, 'get-org-roles', payload); - } catch (error) { - this.logger.error(`In service Error: ${error}`); - throw new RpcException(error.response); - } + const payload = {}; + return this.sendNats(this.serviceProxy, 'get-org-roles', payload); } /** @@ -124,13 +110,8 @@ export class OrganizationService extends BaseService { * @returns Organization invitation creation Success */ async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: number): Promise { - try { - const payload = { bulkInvitationDto, userId }; - return this.sendNats(this.serviceProxy, 'send-invitation', payload); - } catch (error) { - this.logger.error(`In service Error: ${error}`); - throw new RpcException(error.response); - } + const payload = { bulkInvitationDto, userId }; + return this.sendNats(this.serviceProxy, 'send-invitation', payload); } /** @@ -143,4 +124,14 @@ export class OrganizationService extends BaseService { const payload = { orgId: updateUserDto.orgId, roleIds: updateUserDto.orgRoleId, userId }; return this.sendNats(this.serviceProxy, 'update-user-roles', payload); } + + async getOrgUsers( + orgId: number, + getAllUsersDto: GetAllUsersDto + ): Promise<{ response: object }> { + const { pageNumber, pageSize, search } = getAllUsersDto; + const payload = { orgId, pageNumber, pageSize, search }; + + return this.sendNats(this.serviceProxy, 'fetch-organization-user', payload); + } } diff --git a/apps/api-gateway/src/user/dto/accept-reject-invitation.dto.ts b/apps/api-gateway/src/user/dto/accept-reject-invitation.dto.ts index e3d40e7ea..6d16ed0d9 100644 --- a/apps/api-gateway/src/user/dto/accept-reject-invitation.dto.ts +++ b/apps/api-gateway/src/user/dto/accept-reject-invitation.dto.ts @@ -6,10 +6,6 @@ import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; export class AcceptRejectInvitationDto { - - @ApiProperty({ example: 1 }) - @IsNotEmpty({ message: 'Please provide valid invitationId' }) - @IsNumber() invitationId: number; @ApiProperty({ example: 1 }) @@ -25,4 +21,4 @@ export class AcceptRejectInvitationDto { @IsEnum(Invitation) status: Invitation.ACCEPTED | Invitation.REJECTED; -} +} \ 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 a72eb45eb..05983a924 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,17 @@ import { trim } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { IsBoolean, IsNotEmpty, IsOptional, IsString} from 'class-validator'; +import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class AddUserDetails { + + @ApiProperty({ example: 'awqx@getnada.com' }) + @IsEmail() + @IsNotEmpty({ message: 'Please provide valid email' }) + @IsString({ message: 'email should be string' }) + @IsOptional() + email?: string; + @ApiProperty({ example: 'Alen' }) @IsString({ message: 'firstName should be string' }) @IsOptional() 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 09c5cafaf..f62980e6e 100644 --- a/apps/api-gateway/src/user/dto/login-user.dto.ts +++ b/apps/api-gateway/src/user/dto/login-user.dto.ts @@ -1,8 +1,8 @@ -import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; +import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; +import { trim } from '@credebl/common/cast.helper'; export class LoginUserDto { @ApiProperty({ example: 'awqx@getnada.com' }) @@ -21,29 +21,4 @@ export class LoginUserDto { @IsOptional() @IsBoolean({ message: 'isPasskey should be boolean' }) isPasskey: boolean; -} - -export class AddUserDetails { - @ApiProperty({ example: 'Alen' }) - @IsString({ message: 'firstName should be string' }) - @IsOptional() - firstName?: string; - - @ApiProperty({ example: 'Harvey' }) - @IsString({ message: 'lastName should be string' }) - @IsOptional() - lastName?: string; - - @ApiProperty() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'Password is required.' }) - @MinLength(8, { message: 'Password must be at least 8 characters.' }) - @MaxLength(50, { message: 'Password must be at most 50 characters.' }) - @IsOptional() - password?: string; - - @ApiProperty({ example: 'false' }) - @IsOptional() - @IsBoolean({ message: 'isPasskey should be boolean' }) - isPasskey?: boolean; -} +} \ No newline at end of file diff --git a/apps/api-gateway/src/user/dto/update-user-profile.dto.ts b/apps/api-gateway/src/user/dto/update-user-profile.dto.ts index 41484636c..e4f39fef5 100644 --- a/apps/api-gateway/src/user/dto/update-user-profile.dto.ts +++ b/apps/api-gateway/src/user/dto/update-user-profile.dto.ts @@ -1,15 +1,13 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsOptional, IsString, IsBoolean } from 'class-validator'; + export class UpdateUserProfileDto { - @ApiProperty() - @IsNotEmpty({ message: 'userId is required.' }) - @IsNumber() id: number; @ApiPropertyOptional() @IsOptional() - @IsString({message:'ProfileLogoUrl should be string'}) + @IsString({ message: 'ProfileLogoUrl should be string' }) profileImg?: string; @ApiProperty({ example: 'Alen' }) @@ -25,5 +23,5 @@ export class UpdateUserProfileDto { @ApiPropertyOptional({ example: true }) @IsBoolean({ message: 'isPublic should be boolean' }) @IsOptional() - isPublic? = false; + isPublic?: boolean = false; } \ No newline at end of file diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 953c57cf4..8c9a25640 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -1,9 +1,7 @@ import { Controller, Post, Put, Body, Param, UseFilters } from '@nestjs/common'; import { UserService } from './user.service'; -import { UserEmailVerificationDto } from './dto/create-user.dto'; import { ApiBearerAuth, - ApiBody, ApiForbiddenResponse, ApiOperation, ApiParam, @@ -21,11 +19,7 @@ import { HttpStatus } from '@nestjs/common'; import { CommonService } from '@credebl/common'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { BadRequestException } from '@nestjs/common'; -import { AuthTokenResponse } from '../authz/dtos/auth-token-res.dto'; -import { LoginUserDto } from './dto/login-user.dto'; -import { UnauthorizedException } from '@nestjs/common'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { EmailVerificationDto } from './dto/email-verify.dto'; import { Get } from '@nestjs/common'; import { Query } from '@nestjs/common'; import { user } from '@prisma/client'; @@ -34,15 +28,13 @@ import { AuthGuard } from '@nestjs/passport'; import { User } from '../authz/decorators/user.decorator'; import { AcceptRejectInvitationDto } from './dto/accept-reject-invitation.dto'; import { Invitation } from '@credebl/enum/enum'; -import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; -import { Roles } from '../authz/decorators/roles.decorator'; -import { OrgRoles } from 'libs/org-roles/enums'; import { IUserRequestInterface } from './interfaces'; import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; import { GetAllUsersDto } from './dto/get-all-users.dto'; -import { AddPasskeyDetails, AddUserDetails } from './dto/add-user.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 { EmailValidator } from '../dtos/email-validator.dto'; @UseFilters(CustomExceptionFilter) @Controller('users') @@ -52,66 +44,6 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler export class UserController { constructor(private readonly userService: UserService, private readonly commonService: CommonService) { } - /** - * - * @param email - * @param res - * @returns Email sent success - */ - @Post('/send-mail') - @ApiResponse({ status: 201, description: 'Success', 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.userService.sendVerificationMail(userEmailVerificationDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.sendVerificationCode - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - - /** - * - * @param user - * @param orgId - * @param res - * @returns Users list of organization - */ - @Get() - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ summary: 'Get organization users list', description: 'Get organization users list.' }) - @ApiQuery({ - name: 'pageNumber', - type: Number, - required: false - }) - @ApiQuery({ - name: 'pageSize', - type: Number, - required: false - }) - @ApiQuery({ - name: 'search', - type: String, - required: false - }) - async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Query('orgId') orgId: number, @Res() res: Response): Promise { - - const org = user.selectedOrg?.orgId; - const users = await this.userService.getOrgUsers(org, getAllUsersDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchUsers, - data: users.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } - - /** * * @param user @@ -149,60 +81,30 @@ export class UserController { return res.status(HttpStatus.OK).json(finalResponse); } + @Get('public-profiles/:username') + @ApiOperation({ + summary: 'Fetch user details', + description: 'Fetch user details' + }) + @ApiParam({ + name: 'username', + type: String, + required: false + }) + async getPublicProfile(@Param('username') username: string, @Res() res: Response): Promise { + const userData = await this.userService.getPublicProfile(username); - /** - * - * @param query - * @param res - * @returns User email verified - */ - @Get('/verify') - @ApiOperation({ summary: 'Verify new users email', description: 'Email verification for new users' }) - async verifyEmail(@Query() query: EmailVerificationDto, @Res() res: Response): Promise { - await this.userService.verifyEmail(query); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.emaiVerified + message: ResponseMessages.user.success.fetchProfile, + data: userData.response }; return res.status(HttpStatus.OK).json(finalResponse); } - /** - * - * @param loginUserDto - * @param res - * @returns User access token details - */ - @Post('/login') - @ApiOperation({ - summary: 'Login API for web portal', - description: 'Password should be AES encrypted.' - }) - @ApiResponse({ status: 200, description: 'Success', type: AuthTokenResponse }) - @ApiBody({ type: LoginUserDto }) - async login(@Body() loginUserDto: LoginUserDto, @Res() res: Response): Promise { - - if (loginUserDto.email) { - let decryptedPassword; - if (loginUserDto.password) { - decryptedPassword = this.commonService.decryptPassword(loginUserDto.password); - } - const userData = await this.userService.login(loginUserDto.email, decryptedPassword, loginUserDto.isPasskey); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.login, - data: userData.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } else { - throw new UnauthorizedException(`Please provide valid credentials`); - } - } - - @Get('profile') + @Get('/profile') @ApiOperation({ summary: 'Fetch login user details', description: 'Fetch login user details' @@ -223,37 +125,56 @@ export class UserController { } - @Get('public-profiles/:username') + @Get('/activity') @ApiOperation({ - summary: 'Fetch user details', - description: 'Fetch user details' - }) - @ApiParam({ - name: 'username', - type: String, - required: false + summary: 'organization invitations', + description: 'Fetch organization invitations' }) - async getPublicProfile(@Param('username') username: string, @Res() res: Response): Promise { - const userData = await this.userService.getPublicProfile(username); + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + @ApiQuery({ name: 'limit', required: true }) + async getUserActivities(@Query('limit') limit: number, @Res() res: Response, @User() reqUser: user): Promise { + + const userDetails = await this.userService.getUserActivities(reqUser.id, limit); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchProfile, - data: userData.response + message: ResponseMessages.user.success.userActivity, + data: userDetails.response }; return res.status(HttpStatus.OK).json(finalResponse); - } - @Get('invitations') + + @Get('/org-invitations') @ApiOperation({ summary: 'organization invitations', description: 'Fetch organization invitations' }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async invitations(@User() reqUser: user, @Query() getAllInvitationsDto: GetAllInvitationsDto, @Res() res: Response): Promise { + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + @ApiQuery({ + name: 'status', + type: String, + required: false + }) + async invitations(@Query() getAllInvitationsDto: GetAllInvitationsDto, @User() reqUser: user, @Res() res: Response): Promise { if (!Object.values(Invitation).includes(getAllInvitationsDto.status)) { throw new BadRequestException(ResponseMessages.user.error.invalidInvitationStatus); @@ -271,6 +192,26 @@ export class UserController { } + /** + * + * @param email + * @param res + * @returns User email check + */ + @Get('/:email') + @ApiOperation({ summary: 'Check 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 = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.checkEmail, + data: userDetails.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } /** * @@ -279,84 +220,23 @@ export class UserController { * @param res * @returns Organization invitation status */ - @Post('invitations') + @Post('/org-invitations/:invitationId') @ApiOperation({ summary: 'accept/reject organization invitation', description: 'Accept or Reject organization invitations' }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async acceptRejectInvitaion(@Body() acceptRejectInvitation: AcceptRejectInvitationDto, @User() reqUser: user, @Res() res: Response): Promise { + async acceptRejectInvitaion(@Body() acceptRejectInvitation: AcceptRejectInvitationDto, @Param('invitationId') invitationId: string, @User() reqUser: user, @Res() res: Response): Promise { + acceptRejectInvitation.invitationId = parseInt(invitationId); const invitationRes = await this.userService.acceptRejectInvitaion(acceptRejectInvitation, reqUser.id); const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, + statusCode: HttpStatus.CREATED, message: invitationRes.response }; - return res.status(HttpStatus.OK).json(finalResponse); - - } - - /** - * - * @param email - * @param res - * @returns User email check - */ - @Get('/check-user/:email') - @ApiOperation({ summary: 'Check user exist', description: 'check user existence' }) - async checkUserExist(@Param('email') email: string, @Res() res: Response): Promise { - const userDetails = await this.userService.checkUserExist(email); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.checkEmail, - data: userDetails.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - - } - - /** - * - * @param email - * @param userInfo - * @param res - * @returns Add new user - */ - @Post('/add/:email') - @ApiOperation({ summary: 'Add user information', description: 'Add user information' }) - async addUserDetailsInKeyCloak(@Body() userInfo: AddUserDetails, @Param('email') email: string, @Res() res: Response): Promise { - let finalResponse; - let userDetails; - - if (false === userInfo.isPasskey) { - - const decryptedPassword = this.commonService.decryptPassword(userInfo.password); - if (8 <= decryptedPassword.length && 50 >= decryptedPassword.length) { - this.commonService.passwordValidation(decryptedPassword); - userInfo.password = decryptedPassword; - userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); - finalResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.user.success.create, - data: userDetails.response - }; - } else { - throw new BadRequestException('Password name must be between 8 to 50 Characters'); - } - } else { - - userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); - finalResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.user.success.create, - data: userDetails.response - }; - } - return res.status(HttpStatus.OK).json(finalResponse); + return res.status(HttpStatus.CREATED).json(finalResponse); } @@ -368,8 +248,10 @@ export class UserController { @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) - async updateUserProfile(@Body() updateUserProfileDto: UpdateUserProfileDto, @Res() res: Response): Promise { + async updateUserProfile(@Body() updateUserProfileDto: UpdateUserProfileDto, @User() reqUser: user, @Res() res: Response): Promise { + const userId = reqUser.id; + updateUserProfileDto.id = userId; await this.userService.updateUserProfile(updateUserProfileDto); const finalResponse: IResponseType = { @@ -380,36 +262,15 @@ export class UserController { } - @Get('/activity') - @ApiOperation({ - summary: 'organization invitations', - description: 'Fetch organization invitations' - }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiQuery({ name: 'limit', required: true }) - async getUserActivities(@Query('limit') limit: number, @Res() res: Response, @User() reqUser: user): Promise { - - const userDetails = await this.userService.getUserActivities(reqUser.id, limit); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: 'User activities fetched successfully', - data: userDetails.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } - - @Post('/password/:email') - @ApiOperation({ summary: 'Add user information', description: 'Add user information' }) + @Put('/password/:email') + @ApiOperation({ summary: 'Store user password details', description: 'Store user password details' }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() async addPasskey(@Body() userInfo: AddPasskeyDetails, @Param('email') email: string, @Res() res: Response): Promise { const userDetails = await this.userService.addPasskey(email, userInfo); const finalResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.user.success.create, + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.update, data: userDetails.response }; diff --git a/apps/api-gateway/src/user/user.service.ts b/apps/api-gateway/src/user/user.service.ts index 9e97e50ac..63e8ecc9e 100644 --- a/apps/api-gateway/src/user/user.service.ts +++ b/apps/api-gateway/src/user/user.service.ts @@ -1,89 +1,42 @@ import { Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; -import { ClientProxy, RpcException } from '@nestjs/microservices'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { AcceptRejectInvitationDto } from './dto/accept-reject-invitation.dto'; -import { UserEmailVerificationDto } from './dto/create-user.dto'; -import { EmailVerificationDto } from './dto/email-verify.dto'; import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; -import { AddUserDetails } from './dto/login-user.dto'; import { GetAllUsersDto } from './dto/get-all-users.dto'; import { UpdateUserProfileDto } from './dto/update-user-profile.dto'; import { AddPasskeyDetails } from './dto/add-user.dto'; - @Injectable() export class UserService extends BaseService { constructor(@Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy) { super('User Service'); } - async sendVerificationMail(userEmailVerificationDto: UserEmailVerificationDto): Promise { - try { - const payload = { userEmailVerificationDto }; - return this.sendNats(this.serviceProxy, 'send-verification-mail', payload); - } catch (error) { - throw new RpcException(error.response); - } - } - - async login(email: string, password?: string, isPasskey = false): Promise<{ response: object }> { - try { - const payload = { email, password, isPasskey }; - return this.sendNats(this.serviceProxy, 'user-holder-login', payload); - } catch (error) { - throw new RpcException(error.response); - } - } - async verifyEmail(param: EmailVerificationDto): Promise { - try { - const payload = { param }; - return this.sendNats(this.serviceProxy, 'user-email-verification', payload); - } catch (error) { - throw new RpcException(error.response); - } - } - async getProfile(id: number): Promise<{ response: object }> { const payload = { id }; - try { - return this.sendNats(this.serviceProxy, 'get-user-profile', payload); - } catch (error) { - this.logger.error(`Error in get user:${JSON.stringify(error)}`); - } + return this.sendNats(this.serviceProxy, 'get-user-profile', payload); } async getPublicProfile(username: string): Promise<{ response: object }> { const payload = { username }; - try { - return this.sendNats(this.serviceProxy, 'get-user-public-profile', payload); - } catch (error) { - this.logger.error(`Error in get user:${JSON.stringify(error)}`); - } + return this.sendNats(this.serviceProxy, 'get-user-public-profile', payload); } async updateUserProfile(updateUserProfileDto: UpdateUserProfileDto): Promise<{ response: object }> { - const payload = {updateUserProfileDto }; - try { - return this.sendNats(this.serviceProxy, 'update-user-profile', payload); - } catch (error) { - throw new RpcException(error.response); - } + const payload = { updateUserProfileDto }; + return this.sendNats(this.serviceProxy, 'update-user-profile', payload); } - + async findUserinSupabase(id: string): Promise<{ response: object }> { const payload = { id }; - - try { - return this.sendNats(this.serviceProxy, 'get-user-by-supabase', payload); - } catch (error) { - this.logger.error(`Error in get user:${JSON.stringify(error)}`); - } + return this.sendNats(this.serviceProxy, 'get-user-by-supabase', payload); } async invitations(id: number, status: string, getAllInvitationsDto: GetAllInvitationsDto): Promise<{ response: object }> { - const {pageNumber, pageSize, search} = getAllInvitationsDto; + const { pageNumber, pageSize, search } = getAllInvitationsDto; const payload = { id, status, pageNumber, pageSize, search }; return this.sendNats(this.serviceProxy, 'get-org-invitations', payload); } @@ -96,39 +49,25 @@ export class UserService extends BaseService { return this.sendNats(this.serviceProxy, 'accept-reject-invitations', payload); } - async getOrgUsers( - orgId: number, - getAllUsersDto: GetAllUsersDto - ): Promise<{ response: object }> { - const {pageNumber, pageSize, search} = getAllUsersDto; - const payload = { orgId, pageNumber, pageSize, search }; - return this.sendNats(this.serviceProxy, 'fetch-organization-users', payload); - } - async get( getAllUsersDto: GetAllUsersDto ): Promise<{ response: object }> { - const {pageNumber, pageSize, search} = getAllUsersDto; + const { pageNumber, pageSize, search } = getAllUsersDto; const payload = { pageNumber, pageSize, search }; return this.sendNats(this.serviceProxy, 'fetch-users', payload); } - + async checkUserExist(userEmail: string): Promise<{ response: string }> { const payload = { userEmail }; return this.sendNats(this.serviceProxy, 'check-user-exist', payload); } - async addUserDetailsInKeyCloak(userEmail: string, userInfo:AddUserDetails): Promise<{ response: string }> { - const payload = { userEmail, userInfo }; - return this.sendNats(this.serviceProxy, 'add-user', payload); - } - async getUserActivities(userId: number, limit: number): Promise<{ response: object }> { const payload = { userId, limit }; return this.sendNats(this.serviceProxy, 'get-user-activity', payload); } - async addPasskey(userEmail: string, userInfo:AddPasskeyDetails): Promise<{ response: string }> { + async addPasskey(userEmail: string, userInfo: AddPasskeyDetails): Promise<{ response: string }> { const payload = { userEmail, userInfo }; return this.sendNats(this.serviceProxy, 'add-passkey', payload); } diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 0ebb10fb3..4ad0bd967 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -263,9 +263,9 @@ export class OrganizationRepository { } }, userOrgRoles: { - include:{ + include: { user: true, - orgRole:true + orgRole: true } } } @@ -413,4 +413,24 @@ export class OrganizationRepository { throw new InternalServerErrorException(error); } } + + /** + * + * @param name + * @returns Organization exist details + */ + + async checkOrganizationExist(name: string, orgId: number): Promise { + try { + return this.prisma.organisation.findMany({ + where: { + id: orgId, + name + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } } diff --git a/apps/organization/src/organization.controller.ts b/apps/organization/src/organization.controller.ts index 85edbe3c9..bbe5bfd8b 100644 --- a/apps/organization/src/organization.controller.ts +++ b/apps/organization/src/organization.controller.ts @@ -31,8 +31,8 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'update-organization' }) - async updateOrganization(payload: { updateOrgDto: IUpdateOrganization; userId: number }): Promise { - return this.organizationService.updateOrganization(payload.updateOrgDto, payload.userId); + async updateOrganization(payload: { updateOrgDto: IUpdateOrganization; userId: number, orgId: number }): Promise { + return this.organizationService.updateOrganization(payload.updateOrgDto, payload.userId, payload.orgId); } /** diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index e5670064f..1ba6eea59 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -60,7 +60,7 @@ export class OrganizationService { return organizationDetails; } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -85,13 +85,16 @@ export class OrganizationService { */ // eslint-disable-next-line camelcase - async updateOrganization(updateOrgDto: IUpdateOrganization, userId: number): Promise { + async updateOrganization(updateOrgDto: IUpdateOrganization, userId: number, orgId: number): Promise { try { - const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); - - if (organizationExist && organizationExist.id !== Number(updateOrgDto.orgId)) { - throw new ConflictException(ResponseMessages.organisation.error.exists); + const organizationExist = await this.organizationRepository.checkOrganizationExist(updateOrgDto.name, orgId); + + if (0 === organizationExist.length) { + const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); + if (organizationExist) { + throw new ConflictException(ResponseMessages.organisation.error.exists); + } } const orgSlug = await this.createOrgSlug(updateOrgDto.name); @@ -102,7 +105,7 @@ export class OrganizationService { return organizationDetails; } catch (error) { this.logger.error(`In update organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -138,7 +141,7 @@ export class OrganizationService { } catch (error) { this.logger.error(`In fetch getOrganizations : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -170,7 +173,7 @@ export class OrganizationService { } catch (error) { this.logger.error(`In fetch getPublicOrganizations : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -191,7 +194,7 @@ export class OrganizationService { } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -212,7 +215,7 @@ export class OrganizationService { return organizationDetails; } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -232,7 +235,7 @@ export class OrganizationService { return getOrganization; } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -248,7 +251,7 @@ export class OrganizationService { return this.orgRoleService.getOrgRoles(); } catch (error) { this.logger.error(`In getOrgRoles : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -276,7 +279,7 @@ export class OrganizationService { return false; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -316,7 +319,7 @@ export class OrganizationService { return ResponseMessages.organisation.success.createInvitation; } catch (error) { this.logger.error(`In send Invitation : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -379,7 +382,7 @@ export class OrganizationService { return this.organizationRepository.getAllOrgInvitations(email, status, pageNumber, pageSize, search); } catch (error) { this.logger.error(`In fetchUserInvitation : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -415,7 +418,7 @@ export class OrganizationService { } catch (error) { this.logger.error(`In updateOrgInvitation : ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -451,7 +454,7 @@ export class OrganizationService { } catch (error) { this.logger.error(`Error in updateUserRoles: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -460,7 +463,7 @@ export class OrganizationService { return this.organizationRepository.getOrgDashboard(orgId); } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index ed5febe00..f42a9b5a8 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -30,6 +30,7 @@ export interface UserEmailVerificationDto{ } export interface userInfo{ + email: string, password: string, firstName: string, lastName: string, diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 9d94f00e0..026da028c 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -50,7 +50,7 @@ export class UserController { return this.userService.updateUserProfile(payload.updateUserProfileDto); } - @MessagePattern({ cmd: 'get-user-by-supabase' }) + @MessagePattern({ cmd: 'get-user-by-supabase' }) async findSupabaseUser(payload: { id }): Promise { return this.userService.findSupabaseUser(payload); } @@ -58,7 +58,7 @@ export class UserController { @MessagePattern({ cmd: 'get-user-by-mail' }) async findUserByEmail(payload: { email }): Promise { - return this.userService.findUserByEmail(payload); + return this.userService.findUserByEmail(payload); } @MessagePattern({ cmd: 'get-org-invitations' }) @@ -84,29 +84,29 @@ export class UserController { * @param payload * @returns organization users list */ - @MessagePattern({ cmd: 'fetch-organization-users' }) + @MessagePattern({ cmd: 'fetch-organization-user' }) async getOrganizationUsers(payload: { orgId: number, pageNumber: number, pageSize: number, search: string }): Promise { return this.userService.getOrgUsers(payload.orgId, payload.pageNumber, payload.pageSize, payload.search); } - /** - * - * @param payload - * @returns organization users list - */ - @MessagePattern({ cmd: 'fetch-users' }) - async get(payload: { pageNumber: number, pageSize: number, search: string }): Promise { - const users = this.userService.get(payload.pageNumber, payload.pageSize, payload.search); - return users; - } + /** + * + * @param payload + * @returns organization users list + */ + @MessagePattern({ cmd: 'fetch-users' }) + async get(payload: { pageNumber: number, pageSize: number, search: string }): Promise { + const users = this.userService.get(payload.pageNumber, payload.pageSize, payload.search); + return users; + } @MessagePattern({ cmd: 'check-user-exist' }) async checkUserExist(payload: { userEmail: string }): Promise { return this.userService.checkUserExist(payload.userEmail); } @MessagePattern({ cmd: 'add-user' }) - async addUserDetailsInKeyCloak(payload: { userEmail: string, userInfo: userInfo }): Promise { - return this.userService.createUserForToken(payload.userEmail, payload.userInfo); + async addUserDetailsInKeyCloak(payload: { userInfo: userInfo }): Promise { + return this.userService.createUserForToken(payload.userInfo); } // Fetch Users recent activities diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 96753ee0c..0f5a84c9a 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -34,7 +34,6 @@ import { SupabaseService } from '@credebl/supabase'; import { UserDevicesRepository } from '../repositories/user-device.repository'; import { v4 as uuidv4 } from 'uuid'; - @Injectable() export class UserService { constructor( @@ -59,7 +58,7 @@ export class UserService { async sendVerificationMail(userEmailVerificationDto: UserEmailVerificationDto): Promise { try { const userDetails = await this.userRepository.checkUserExist(userEmailVerificationDto.email); - + if (userDetails && userDetails.isEmailVerified) { throw new ConflictException(ResponseMessages.user.error.exists); } @@ -82,7 +81,7 @@ export class UserService { return resUser; } catch (error) { this.logger.error(`In Create User : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -106,7 +105,7 @@ export class UserService { } catch (error) { this.logger.error(`Error in createUsername: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error.message); + throw new RpcException(error.response ? error.response : error); } } @@ -138,7 +137,7 @@ export class UserService { } catch (error) { this.logger.error(`Error in sendEmailForVerification: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error.message); + throw new RpcException(error.response ? error.response : error); } } @@ -175,17 +174,18 @@ export class UserService { } } catch (error) { this.logger.error(`error in verifyEmail: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } - async createUserForToken(email: string, userInfo: userInfo): Promise { + async createUserForToken(userInfo: userInfo): Promise { try { - if (!email) { + const { email } = userInfo; + if (!userInfo.email) { throw new UnauthorizedException(ResponseMessages.user.error.invalidEmail); } - const checkUserDetails = await this.userRepository.getUserDetails(email); + const checkUserDetails = await this.userRepository.getUserDetails(userInfo.email); if (!checkUserDetails) { throw new NotFoundException(ResponseMessages.user.error.invalidEmail); } @@ -195,11 +195,11 @@ export class UserService { if (false === checkUserDetails.isEmailVerified) { throw new NotFoundException(ResponseMessages.user.error.verifyEmail); } - const resUser = await this.userRepository.updateUserInfo(email, userInfo); + const resUser = await this.userRepository.updateUserInfo(userInfo.email, userInfo); if (!resUser) { throw new NotFoundException(ResponseMessages.user.error.invalidEmail); } - const userDetails = await this.userRepository.getUserDetails(email); + const userDetails = await this.userRepository.getUserDetails(userInfo.email); if (!userDetails) { throw new NotFoundException(ResponseMessages.user.error.adduser); @@ -208,7 +208,7 @@ export class UserService { let supaUser; if (userInfo.isPasskey) { - const resUser = await this.userRepository.addUserPassword(email, userInfo.password); + const resUser = await this.userRepository.addUserPassword(email, userInfo.password); const userDetails = await this.userRepository.getUserDetails(email); const decryptedPassword = await this.commonService.decryptPassword(userDetails.password); if (!resUser) { @@ -243,7 +243,7 @@ export class UserService { return 'User created successfully'; } catch (error) { this.logger.error(`Error in createUserForToken: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -270,7 +270,7 @@ export class UserService { return 'User updated successfully'; } catch (error) { this.logger.error(`Error in createUserForToken: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -304,32 +304,36 @@ export class UserService { return this.generateToken(email, decryptedPassword); } - return this.generateToken(email, password); + return this.generateToken(email, password); } catch (error) { this.logger.error(`In Login User : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } async generateToken(email: string, password: string): Promise { - const supaInstance = await this.supabaseService.getClient(); + try { + const supaInstance = await this.supabaseService.getClient(); - this.logger.error(`supaInstance::`, supaInstance); + this.logger.error(`supaInstance::`, supaInstance); - const { data, error } = await supaInstance.auth.signInWithPassword({ - email, - password - }); + const { data, error } = await supaInstance.auth.signInWithPassword({ + email, + password + }); - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); + this.logger.error(`Supa Login Error::`, JSON.stringify(error)); - if (error) { - throw new BadRequestException(error?.message); - } + if (error) { + throw new BadRequestException(error?.message); + } - const token = data?.session; + const token = data?.session; - return token; + return token; + } catch (error) { + throw new RpcException(error.response ? error.response : error); + } } async getProfile(payload: { id }): Promise { @@ -337,7 +341,7 @@ export class UserService { return this.userRepository.getUserById(payload.id); } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -352,7 +356,7 @@ export class UserService { return userProfile; } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -361,7 +365,7 @@ export class UserService { return this.userRepository.updateUserProfile(updateUserProfileDto); } catch (error) { this.logger.error(`update user profile: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -370,7 +374,7 @@ export class UserService { return this.userRepository.getUserBySupabaseId(payload.id); } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -379,7 +383,7 @@ export class UserService { return this.userRepository.getUserBySupabaseId(payload.id); } catch (error) { this.logger.error(`Error in findSupabaseUser: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -388,7 +392,7 @@ export class UserService { return this.userRepository.findUserByEmail(payload.email); } catch (error) { this.logger.error(`findUserByEmail: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -413,7 +417,7 @@ export class UserService { return invitationsData; } catch (error) { this.logger.error(`Error in get invitations: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -473,7 +477,7 @@ export class UserService { return this.fetchInvitationsStatus(acceptRejectInvitation, userId, userData.email); } catch (error) { this.logger.error(`acceptRejectInvitations: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -513,7 +517,7 @@ export class UserService { return invitationsData; } catch (error) { this.logger.error(`Error In fetchInvitationsStatus: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -524,6 +528,7 @@ export class UserService { */ async getOrgUsers(orgId: number, pageNumber: number, pageSize: number, search: string): Promise { try { + const query = { userOrgRoles: { some: { orgId } @@ -538,10 +543,11 @@ export class UserService { const filterOptions = { orgId }; + return this.userRepository.findOrgUsers(query, pageNumber, pageSize, filterOptions); } catch (error) { this.logger.error(`get Org Users: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -563,7 +569,7 @@ export class UserService { return this.userRepository.findUsers(query, pageNumber, pageSize); } catch (error) { this.logger.error(`get Users: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -576,19 +582,22 @@ export class UserService { } else if (userDetails && userDetails.supabaseUserId) { throw new ConflictException(ResponseMessages.user.error.exists); } else if (null === userDetails) { - return 'New User'; + return { + isExist: false + }; } else { const userVerificationDetails = { isEmailVerified: userDetails.isEmailVerified, isFidoVerified: userDetails.isFidoVerified, - isSupabase: null !== userDetails.supabaseUserId && undefined !== userDetails.supabaseUserId + isSupabase: null !== userDetails.supabaseUserId && undefined !== userDetails.supabaseUserId, + isExist: true }; return userVerificationDetails; } } catch (error) { this.logger.error(`In check User : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -600,7 +609,7 @@ export class UserService { } catch (error) { this.logger.error(`In getUserActivity : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } -} +} \ No newline at end of file diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 1d265cf7a..e7cba4251 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -13,14 +13,15 @@ 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 code has been sent sucessfully to the mail. Please verify', + userActivity: 'User activities fetched successfully' }, error: { exists: 'User already exists', profileNotFound: 'User public profile not found', verificationAlreadySent: 'The verification link has already been sent to your email address', emailSend: 'Unable to send email to the user', - invalidEmailUrl: 'Invalid token or EmailId!', + invalidEmailUrl: 'Invalid verification code or EmailId!', verifiedEmail: 'Email already verified', notFound: 'User not found', verifyMail: 'Please verify your email', @@ -32,7 +33,8 @@ export const ResponseMessages = { invalidEmail: 'Invalid Email Id!', adduser: 'Unable to add user details', verifyEmail: 'The verification link has already been sent to your email address. please verify', - emailNotVerified: 'The verification link has already been sent to your email address. please verify' + emailNotVerified: 'The verification link has already been sent to your email address. please verify', + userNotRegisterd: 'The user has not yet completed the registration process' } }, organisation: { @@ -115,7 +117,7 @@ export const ResponseMessages = { }, agent: { success: { - create: 'Agent spin-up up successfully', + create: 'Agent spin-up successfully', health: 'Agent health details retrieved successfully.' }, error: { @@ -172,4 +174,4 @@ export const ResponseMessages = { emailSend: 'Unable to send email to the user' } } -}; +}; \ No newline at end of file From 627f4ad087049b6fa4b8b0cfd426e89a9a290c6c Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:46:10 +0530 Subject: [PATCH 041/162] refactor: schema APIs standardized (#84) * refactor: Schema APIs standardized Signed-off-by: KulkarniShashank * Resolved sonar lint checks Signed-off-by: KulkarniShashank * API refactor in schema module Signed-off-by: KulkarniShashank * API refactor in credential-definition Signed-off-by: KulkarniShashank * Added the error handling in the platform and credential-definition module Signed-off-by: KulkarniShashank * Error handling and solved the pagination Signed-off-by: KulkarniShashank * Added logger in the global exception handling Signed-off-by: KulkarniShashank * Changes as per comment on the Schema controller Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/api-gateway/common/exception-handler.ts | 5 +- apps/api-gateway/src/app.module.ts | 2 - .../credential-definition.controller.ts | 94 +++++--- .../dto/create-cred-defs.dto.ts | 6 +- .../dto/get-all-cred-defs.dto.ts | 15 +- .../api-gateway/src/dtos/create-schema.dto.ts | 5 +- .../dtos/get-all-organizations.dto.ts | 3 +- .../dtos/get-all-sent-invitations.dto.ts | 3 +- .../src/platform/platform.controller.ts | 45 +++- .../src/platform/platform.service.ts | 59 +---- .../src/schema/dtos/get-all-schema.dto.ts | 23 +- .../src/schema/schema.controller.ts | 221 +++++++++--------- apps/api-gateway/src/schema/schema.service.ts | 13 -- .../src/user/dto/get-all-invitations.dto.ts | 3 +- .../src/user/dto/get-all-users.dto.ts | 3 +- .../credential-definition.service.ts | 6 +- apps/ledger/src/schema/schema.service.ts | 10 +- 17 files changed, 241 insertions(+), 275 deletions(-) diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index 13c102539..162712d52 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -1,8 +1,9 @@ -import { Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'; +import { Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common'; import { BaseExceptionFilter } from '@nestjs/core'; @Catch() export class CustomExceptionFilter extends BaseExceptionFilter { + private readonly logger = new Logger(); catch(exception: HttpException, host: ArgumentsHost): void { const ctx = host.switchToHttp(); const response = ctx.getResponse(); @@ -12,10 +13,12 @@ export class CustomExceptionFilter extends BaseExceptionFilter { 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 (exception && exception["statusCode"] === HttpStatus.INTERNAL_SERVER_ERROR) { errorResponse = { diff --git a/apps/api-gateway/src/app.module.ts b/apps/api-gateway/src/app.module.ts index d710b05fb..ee0657282 100644 --- a/apps/api-gateway/src/app.module.ts +++ b/apps/api-gateway/src/app.module.ts @@ -11,7 +11,6 @@ import { CredentialDefinitionModule } from './credential-definition/credential-d import { FidoModule } from './fido/fido.module'; import { IssuanceModule } from './issuance/issuance.module'; import { OrganizationModule } from './organization/organization.module'; -import { PlatformController } from './platform/platform.controller'; import { PlatformModule } from './platform/platform.module'; import { VerificationModule } from './verification/verification.module'; import { RevocationController } from './revocation/revocation.controller'; @@ -80,7 +79,6 @@ export class AppModule { ) .forRoutes( AgentController, - PlatformController, RevocationController ); } diff --git a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts index be8e25a0f..9cd7d0494 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Logger, Post, Body, UseGuards, Get, Query, HttpStatus, Res } from '@nestjs/common'; +import { Controller, Logger, Post, Body, UseGuards, Get, Query, HttpStatus, Res, Param, UseFilters } from '@nestjs/common'; import { CredentialDefinitionService } from './credential-definition.service'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiUnauthorizedResponse, ApiForbiddenResponse, ApiQuery } from '@nestjs/swagger'; import { ApiResponseDto } from 'apps/api-gateway/src/dtos/apiResponse.dto'; @@ -15,55 +15,31 @@ import { IUserRequestInterface } from './interfaces'; import { CreateCredentialDefinitionDto } from './dto/create-cred-defs.dto'; import { OrgRoles } from 'libs/org-roles/enums'; import { Roles } from '../authz/decorators/roles.decorator'; +import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @ApiBearerAuth() -@UseGuards(AuthGuard('jwt'), OrgRolesGuard) -@Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER) @ApiTags('credential-definitions') - @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) -@Controller('credential-definitions') +@Controller('orgs') +@UseFilters(CustomExceptionFilter) export class CredentialDefinitionController { constructor(private readonly credentialDefinitionService: CredentialDefinitionService) { } private readonly logger = new Logger('CredentialDefinitionController'); - @Post('/') - @ApiOperation({ - summary: 'Sends a credential definition to the ledger', - description: 'Create and sends a credential definition to the ledger.' - }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async createCredentialDefinition( - @User() user: IUserRequestInterface, - @Body() credDef: CreateCredentialDefinitionDto, - @Res() res: Response - ): Promise { - const credentialsDefinitionDetails = await this.credentialDefinitionService.createCredentialDefinition(credDef, user); - const credDefResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.credentialDefinition.success.create, - data: credentialsDefinitionDetails.response - }; - return res.status(HttpStatus.OK).json(credDefResponse); - } - @Get('/id') + @Get('/:orgId/cred-defs/:credDefId') @ApiOperation({ summary: 'Get an existing credential definition by Id', description: 'Get an existing credential definition by Id' }) - @ApiQuery( - { name: 'credentialDefinitionId', required: true } - ) - @ApiQuery( - { name: 'orgId', required: true } - ) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getCredentialDefinitionById( - @Query('credentialDefinitionId') credentialDefinitionId: string, - @Query('orgId') orgId: number, + @Param('orgId') orgId: number, + @Param('credDefId') credentialDefinitionId: string, @Res() res: Response ): Promise { const credentialsDefinitionDetails = await this.credentialDefinitionService.getCredentialDefinitionById(credentialDefinitionId, orgId); @@ -75,20 +51,39 @@ export class CredentialDefinitionController { return res.status(HttpStatus.OK).json(credDefResponse); } - @Get('/') + @Get('/:orgId/cred-defs') @ApiOperation({ summary: 'Fetch all credential definitions of provided organization id with pagination', description: 'Fetch all credential definitions from metadata saved in database of provided organization id.' }) + @ApiQuery( + { name: 'pageNumber', required: false } + ) + @ApiQuery( + { name: 'searchByText', required: false } + ) + @ApiQuery( + { name: 'pageSize', required: false } + ) + @ApiQuery( + { name: 'sorting', required: false } + ) + @ApiQuery( + { name: 'sortByValue', required: false } + ) + @ApiQuery( + { name: 'revocable', required: false } + ) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getAllCredDefs( + @Param('orgId') orgId: number, @Query() getAllCredDefs: GetAllCredDefsDto, @User() user: IUserRequestInterface, @Res() res: Response ): Promise { - const { pageSize, pageNumber, sortByValue, sorting, orgId, searchByText, revocable } = getAllCredDefs; - const credDefSearchCriteria = { pageSize, pageNumber, searchByText, sorting, sortByValue, revocable }; const credentialsDefinitionDetails = await this.credentialDefinitionService.getAllCredDefs( - credDefSearchCriteria, + getAllCredDefs, user, orgId ); @@ -99,4 +94,29 @@ export class CredentialDefinitionController { }; return res.status(HttpStatus.OK).json(credDefResponse); } + + @Post('/:orgId/cred-defs') + @ApiOperation({ + summary: 'Sends a credential definition to ledger', + description: 'Sends a credential definition to ledger' + }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + async createCredentialDefinition( + @User() user: IUserRequestInterface, + @Body() credDef: CreateCredentialDefinitionDto, + @Param('orgId') orgId: number, + @Res() res: Response + ): Promise { + + credDef.orgId = orgId; + const credentialsDefinitionDetails = await this.credentialDefinitionService.createCredentialDefinition(credDef, user); + const credDefResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.credentialDefinition.success.create, + data: credentialsDefinitionDetails.response + }; + return res.status(HttpStatus.OK).json(credDefResponse); + } } \ No newline at end of file diff --git a/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts b/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts index 0754ebfd8..3fa2024d6 100644 --- a/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts +++ b/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts @@ -1,4 +1,4 @@ -import { IsBoolean, IsDefined, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsBoolean, IsDefined, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; @@ -13,10 +13,6 @@ export class CreateCredentialDefinitionDto { @IsNotEmpty({ message: 'Please provide a schema id' }) @IsString({ message: 'Schema id should be string' }) schemaLedgerId: string; - - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'Please provide orgId' }) orgId: number; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/credential-definition/dto/get-all-cred-defs.dto.ts b/apps/api-gateway/src/credential-definition/dto/get-all-cred-defs.dto.ts index 76172f182..0de995ad7 100644 --- a/apps/api-gateway/src/credential-definition/dto/get-all-cred-defs.dto.ts +++ b/apps/api-gateway/src/credential-definition/dto/get-all-cred-defs.dto.ts @@ -2,32 +2,27 @@ /* eslint-disable camelcase */ import { ApiProperty } from '@nestjs/swagger'; import { SortValue } from '../../enum'; -import { Transform, Type } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; -import { IsNotEmpty, IsNumber, IsOptional } from 'class-validator'; +import { Type } from 'class-transformer'; +import { IsOptional } from 'class-validator'; export class GetAllCredDefsDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => Number) - @Transform(({ value }) => trim(value)) pageNumber: number = 1; @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) searchByText: string = ''; @ApiProperty({ required: false }) @IsOptional() @Type(() => Number) - @Transform(({ value }) => trim(value)) pageSize: number = 10; @ApiProperty({ required: false }) @IsOptional() - @Transform(({ value }) => trim(value)) sorting: string = 'id'; @ApiProperty({ required: false }) @@ -37,11 +32,5 @@ export class GetAllCredDefsDto { @ApiProperty({ required: false }) @IsOptional() revocable: boolean = true; - - @ApiProperty({ required: true }) - @Type(() => Number) - @IsNumber() - @IsNotEmpty() - orgId: number; } diff --git a/apps/api-gateway/src/dtos/create-schema.dto.ts b/apps/api-gateway/src/dtos/create-schema.dto.ts index 810228959..232af5a4f 100644 --- a/apps/api-gateway/src/dtos/create-schema.dto.ts +++ b/apps/api-gateway/src/dtos/create-schema.dto.ts @@ -1,4 +1,4 @@ -import { IsArray, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; @@ -22,9 +22,6 @@ export class CreateSchemaDto { @IsNotEmpty({ message: 'please provide valid attributes' }) attributes: IAttributeValue[]; - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; @ApiProperty() diff --git a/apps/api-gateway/src/organization/dtos/get-all-organizations.dto.ts b/apps/api-gateway/src/organization/dtos/get-all-organizations.dto.ts index 53d0a082f..6a3cedf44 100644 --- a/apps/api-gateway/src/organization/dtos/get-all-organizations.dto.ts +++ b/apps/api-gateway/src/organization/dtos/get-all-organizations.dto.ts @@ -1,6 +1,6 @@ import { Transform, Type } from 'class-transformer'; // import { SortValue } from '../../enum'; -import { toNumber, trim } from '@credebl/common/cast.helper'; +import { toNumber } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { IsOptional } from 'class-validator'; @@ -15,7 +15,6 @@ export class GetAllOrganizationsDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) search = ''; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/organization/dtos/get-all-sent-invitations.dto.ts b/apps/api-gateway/src/organization/dtos/get-all-sent-invitations.dto.ts index edc9eaf1c..eaa15380d 100644 --- a/apps/api-gateway/src/organization/dtos/get-all-sent-invitations.dto.ts +++ b/apps/api-gateway/src/organization/dtos/get-all-sent-invitations.dto.ts @@ -1,5 +1,5 @@ import { Transform, Type } from 'class-transformer'; -import { toNumber, trim } from '@credebl/common/cast.helper'; +import { toNumber } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { IsOptional } from 'class-validator'; @@ -14,7 +14,6 @@ export class GetAllSentInvitationsDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) search = ''; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/platform/platform.controller.ts b/apps/api-gateway/src/platform/platform.controller.ts index 9cfc655c4..26c43bbfd 100644 --- a/apps/api-gateway/src/platform/platform.controller.ts +++ b/apps/api-gateway/src/platform/platform.controller.ts @@ -1,13 +1,52 @@ -import { Controller, Logger } from '@nestjs/common'; +import { Controller, Get, HttpStatus, Logger, Query, Res, UseFilters } from '@nestjs/common'; import { PlatformService } from './platform.service'; -import { ApiBearerAuth } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiResponseDto } from '../dtos/apiResponse.dto'; +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 IResponseType from '@credebl/common/interfaces/response.interface'; +import { ResponseMessages } from '@credebl/common/response-messages'; +import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @ApiBearerAuth() -@Controller('connections') +@Controller() +@UseFilters(CustomExceptionFilter) export class PlatformController { constructor(private readonly platformService: PlatformService) { } private readonly logger = new Logger('PlatformController'); + @Get('/platform/schemas') + @ApiTags('schemas') + @ApiOperation({ + summary: 'Get all schemas from platform.', + description: 'Get all schemas from platform.' + }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + async getAllSchema( + @Query() getAllSchemaDto: GetAllSchemaByPlatformDto, + @Res() res: Response, + @User() user: IUserRequestInterface + ): Promise { + const { pageSize, searchByText, pageNumber, sorting, sortByValue } = getAllSchemaDto; + const schemaSearchCriteria: ISchemaSearchInterface = { + pageNumber, + searchByText, + pageSize, + sorting, + sortByValue + }; + const schemasResponse = await this.platformService.getAllSchema(schemaSearchCriteria, user); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.schema.success.fetch, + data: schemasResponse.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } } diff --git a/apps/api-gateway/src/platform/platform.service.ts b/apps/api-gateway/src/platform/platform.service.ts index 54a3bb353..6443fc11a 100644 --- a/apps/api-gateway/src/platform/platform.service.ts +++ b/apps/api-gateway/src/platform/platform.service.ts @@ -1,10 +1,8 @@ -import { Injectable, Inject, Logger, HttpException } from '@nestjs/common'; +import { Injectable, Inject } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from '../../../../libs/service/base.service'; -import { map } from 'rxjs/operators'; -import { CredentialListPayload, GetCredentialListByConnectionId, IConnectedHolderList, SortValue } from './platform.interface'; -import { ConnectionDto } from '../dtos/connection.dto'; -import { credentialSortBy } from '../enum'; +import { ISchemaSearchInterface } from '../interfaces/ISchemaSearch.interface'; +import { IUserRequestInterface } from '../interfaces/IUserRequestInterface'; @Injectable() export class PlatformService extends BaseService { @@ -14,52 +12,11 @@ export class PlatformService extends BaseService { super('PlatformService'); } + async getAllSchema(schemaSearchCriteria: ISchemaSearchInterface, user: IUserRequestInterface): Promise<{ + response: object; + }> { + const schemaSearch = { schemaSearchCriteria, user }; + return this.sendNats(this.platformServiceProxy, 'get-all-schemas', schemaSearch); - /** - * Description: Calling platform service for connection-invitation - * @param alias - * @param auto_accept - * @param _public - * @param multi_use - */ - createConnectionInvitation(alias: string, auto_accept: boolean, _public: boolean, multi_use: boolean) { - this.logger.log('**** createConnectionInvitation called...'); - const payload = { alias, auto_accept, _public, multi_use }; - return this.sendNats(this.platformServiceProxy, 'default-connection-invitation', payload); - } - - /** - * Description: Calling platform service for connection-list - * @param alias - * @param initiator - * @param invitation_key - * @param my_did - * @param state - * @param their_did - * @param their_role - */ - getConnections(alias: string, initiator: string, invitation_key: string, my_did: string, state: string, their_did: string, their_role: string, user: any) { - this.logger.log('**** getConnections called...'); - const payload = { alias, initiator, invitation_key, my_did, state, their_did, their_role, user }; - return this.sendNats(this.platformServiceProxy, 'connection-list', payload); - } - - pingServicePlatform() { - this.logger.log('**** pingServicePlatform called...'); - const payload = {}; - return this.sendNats(this.platformServiceProxy, 'ping-platform', payload); - } - - - connectedHolderList(itemsPerPage: number, page: number, searchText: string, orgId: number, connectionSortBy: string, sortValue: string) { - this.logger.log('**** connectedHolderList called...'); - const payload: IConnectedHolderList = { itemsPerPage, page, searchText, orgId, connectionSortBy, sortValue }; - return this.sendNats(this.platformServiceProxy, 'connected-holder-list', payload); - } - - getCredentialListByConnectionId(connectionId: string, items_per_page: number, page: number, search_text: string, sortValue: SortValue, sortBy: credentialSortBy) { - this.logger.log('**** getCredentialListByConnectionId called...'); - const payload:GetCredentialListByConnectionId = { connectionId, items_per_page, page, search_text, sortValue, sortBy }; - return this.sendNats(this.platformServiceProxy, 'get-credential-by-connection-id', payload); } } 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 af4a49838..0004e716d 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,9 +2,8 @@ /* eslint-disable camelcase */ import { ApiProperty } from '@nestjs/swagger'; import { SortValue } from '../../enum'; -import { Transform, Type } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; -import { IsNotEmpty, IsNumber, IsOptional } from 'class-validator'; +import { Type } from 'class-transformer'; +import { IsOptional } from 'class-validator'; export class GetAllSchemaDto { @ApiProperty({ required: false }) @@ -14,7 +13,6 @@ export class GetAllSchemaDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) searchByText: string = ''; @ApiProperty({ required: false }) @@ -23,19 +21,11 @@ export class GetAllSchemaDto { @ApiProperty({ required: false }) @IsOptional() - @Transform(({ value }) => trim(value)) sorting: string = 'id'; @ApiProperty({ required: false }) @IsOptional() sortByValue: string = SortValue.DESC; - - @ApiProperty({ required: true }) - @Type(() => Number) - @IsNumber() - @IsNotEmpty() - @IsOptional() - orgId?: number; } export class GetCredentialDefinitionBySchemaIdDto { @@ -56,13 +46,6 @@ export class GetCredentialDefinitionBySchemaIdDto { @ApiProperty({ required: false }) @IsOptional() sortByValue: string = SortValue.DESC; - - @ApiProperty({ required: true }) - @Type(() => Number) - @IsNumber() - @IsNotEmpty() - @IsOptional() - orgId?: number; } export class GetAllSchemaByPlatformDto { @@ -73,7 +56,6 @@ export class GetAllSchemaByPlatformDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) searchByText: string = ''; @ApiProperty({ required: false }) @@ -82,7 +64,6 @@ export class GetAllSchemaByPlatformDto { @ApiProperty({ required: false }) @IsOptional() - @Transform(({ value }) => trim(value)) sorting: string = 'id'; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/schema/schema.controller.ts b/apps/api-gateway/src/schema/schema.controller.ts index 00f4ecacc..98e829e4b 100644 --- a/apps/api-gateway/src/schema/schema.controller.ts +++ b/apps/api-gateway/src/schema/schema.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Logger, Post, Body, HttpStatus, UseGuards, Get, Query, BadRequestException, Res, UseFilters } from '@nestjs/common'; +import { Controller, Logger, Post, Body, HttpStatus, UseGuards, Get, Query, BadRequestException, Res, UseFilters, Param } from '@nestjs/common'; /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable camelcase */ import { ApiOperation, ApiResponse, ApiTags, ApiBearerAuth, ApiForbiddenResponse, ApiUnauthorizedResponse, ApiQuery } from '@nestjs/swagger'; @@ -12,17 +12,16 @@ import { Response } from 'express'; import { User } from '../authz/decorators/user.decorator'; import { ICredDeffSchemaSearchInterface, ISchemaSearchInterface } from '../interfaces/ISchemaSearch.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { GetAllSchemaByPlatformDto, GetAllSchemaDto, GetCredentialDefinitionBySchemaIdDto } from './dtos/get-all-schema.dto'; +import { GetAllSchemaDto, GetCredentialDefinitionBySchemaIdDto } from './dtos/get-all-schema.dto'; import { OrgRoles } from 'libs/org-roles/enums'; import { Roles } from '../authz/decorators/roles.decorator'; import { IUserRequestInterface } from './interfaces'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { CreateSchemaDto } from '../dtos/create-schema.dto'; -import { TransformStreamDefaultController } from 'node:stream/web'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @UseFilters(CustomExceptionFilter) -@Controller('schemas') +@Controller('orgs') @ApiTags('schemas') @ApiBearerAuth() @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @@ -32,94 +31,19 @@ export class SchemaController { ) { } private readonly logger = new Logger('SchemaController'); - @Post('/') - @ApiOperation({ - summary: 'Sends a schema to the ledger', - description: 'Create and sends a schema to the ledger.' - }) - @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER) + @Get('/:orgId/schemas/:schemaId') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async createSchema(@Res() res: Response, @Body() schema: CreateSchemaDto, @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'); - } - }); - const schemaDetails = await this.appService.createSchema(schema, user, schema.orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: 'Schema created successfully', - data: schemaDetails.response - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - - @Get('/') @ApiOperation({ - summary: 'Get all schemas by org id.', - description: 'Get all schemas by org id.' + summary: 'Get schema information from the ledger using its schema ID.', + description: 'Get schema information from the ledger using its schema ID.' }) - @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - async getSchemas( - @Query() getAllSchemaDto: GetAllSchemaDto, - @Res() res: Response, - @User() user: IUserRequestInterface - ): Promise { - - const { orgId, pageSize, searchByText, pageNumber, sorting, sortByValue } = getAllSchemaDto; - const schemaSearchCriteria: ISchemaSearchInterface = { - pageNumber, - searchByText, - pageSize, - sorting, - sortByValue - }; - const schemasResponse = await this.appService.getSchemas(schemaSearchCriteria, user, orgId); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.schema.success.fetch, - data: schemasResponse.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - - @Get('/id') - @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiOperation({ - summary: 'Retrieve an existing schema from the ledger using its schemaId', - description: 'Retrieve an existing schema from the ledger using its schemaId' - }) - @ApiQuery( - { name: 'schemaId', required: true } - ) - - @ApiQuery( - { name: 'orgId', required: true } - ) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) async getSchemaById( @Res() res: Response, - @Query('schemaId') schemaId: string, - @Query('orgId') orgId: number + @Param('orgId') orgId: number, + @Param('schemaId') schemaId: string ): Promise { if (!schemaId) { @@ -134,20 +58,37 @@ export class SchemaController { return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/credential-definitions') + @Get('/:orgId/schemas/:schemaId/cred-defs') @ApiOperation({ - summary: 'Get an existing credential definition list by schemaId', - description: 'Get an existing credential definition list by schemaId' + summary: 'Get credential definition list by schema Id', + description: 'Get credential definition list by schema Id' }) - @ApiQuery( - { name: 'schemaId', required: true } - ) - @ApiQuery( - { name: 'orgId', required: false } - ) @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 + }) + @ApiQuery({ + name: 'sortByValue', + type: String, + required: false + }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getcredDeffListBySchemaId( - @Query('schemaId') schemaId: string, + @Param('orgId') orgId: number, + @Param('schemaId') schemaId: string, @Query() GetCredentialDefinitionBySchemaIdDto: GetCredentialDefinitionBySchemaIdDto, @Res() res: Response, @User() user: IUserRequestInterface): Promise { @@ -155,14 +96,8 @@ export class SchemaController { if (!schemaId) { throw new BadRequestException(ResponseMessages.schema.error.invalidSchemaId); } - const { orgId, pageSize, pageNumber, sorting, sortByValue } = GetCredentialDefinitionBySchemaIdDto; - const schemaSearchCriteria: ICredDeffSchemaSearchInterface = { - pageNumber, - pageSize, - sorting, - sortByValue - }; - const credentialDefinitionList = await this.appService.getcredDeffListBySchemaId(schemaId, schemaSearchCriteria, user, orgId); + + const credentialDefinitionList = await this.appService.getcredDeffListBySchemaId(schemaId, GetCredentialDefinitionBySchemaIdDto, user, orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.schema.success.fetch, @@ -171,17 +106,46 @@ export class SchemaController { return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/platform') + @Get('/:orgId/schemas') @ApiOperation({ - summary: 'Get all schemas from platform.', - description: 'Get all schemas from platform.' + summary: 'Get all 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, + required: false + }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - async getAllSchema( - @Query() getAllSchemaDto: GetAllSchemaByPlatformDto, + async getSchemas( + @Query() getAllSchemaDto: GetAllSchemaDto, + @Param('orgId') orgId: number, @Res() res: Response, @User() user: IUserRequestInterface ): Promise { + const { pageSize, searchByText, pageNumber, sorting, sortByValue } = getAllSchemaDto; const schemaSearchCriteria: ISchemaSearchInterface = { pageNumber, @@ -190,7 +154,7 @@ export class SchemaController { sorting, sortByValue }; - const schemasResponse = await this.appService.getAllSchema(schemaSearchCriteria, user); + const schemasResponse = await this.appService.getSchemas(schemaSearchCriteria, user, orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, @@ -199,4 +163,43 @@ export class SchemaController { }; return res.status(HttpStatus.OK).json(finalResponse); } + + @Post('/:orgId/schemas') + @ApiOperation({ + summary: 'Create and sends a schema to the ledger.', + description: 'Create and sends a schema to the ledger.' + }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + async createSchema(@Res() res: Response, @Body() schema: CreateSchemaDto, @Param('orgId') orgId: number, @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 + }; + 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 3a260f706..ed038952e 100644 --- a/apps/api-gateway/src/schema/schema.service.ts +++ b/apps/api-gateway/src/schema/schema.service.ts @@ -59,17 +59,4 @@ export class SchemaService extends BaseService { } } - - getAllSchema(schemaSearchCriteria: ISchemaSearchInterface, user: IUserRequestInterface): Promise<{ - response: object; - }> { - try { - const schemaSearch = { schemaSearchCriteria, user }; - return this.sendNats(this.schemaServiceProxy, 'get-all-schemas', schemaSearch); - } catch (error) { - throw new RpcException(error.response); - - } - } - } \ No newline at end of file diff --git a/apps/api-gateway/src/user/dto/get-all-invitations.dto.ts b/apps/api-gateway/src/user/dto/get-all-invitations.dto.ts index 42e50d3d7..89f079f19 100644 --- a/apps/api-gateway/src/user/dto/get-all-invitations.dto.ts +++ b/apps/api-gateway/src/user/dto/get-all-invitations.dto.ts @@ -1,6 +1,6 @@ import { IsOptional, IsString } from 'class-validator'; import { Transform, Type } from 'class-transformer'; -import { toNumber, trim } from '@credebl/common/cast.helper'; +import { toNumber } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { Invitation } from '@credebl/enum/enum'; @@ -15,7 +15,6 @@ export class GetAllInvitationsDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) search = ''; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/user/dto/get-all-users.dto.ts b/apps/api-gateway/src/user/dto/get-all-users.dto.ts index 47b98aeb8..8a9bbd8b2 100644 --- a/apps/api-gateway/src/user/dto/get-all-users.dto.ts +++ b/apps/api-gateway/src/user/dto/get-all-users.dto.ts @@ -1,5 +1,5 @@ import { Transform, Type } from 'class-transformer'; -import { toNumber, trim } from '@credebl/common/cast.helper'; +import { toNumber } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { IsOptional } from 'class-validator'; @@ -15,7 +15,6 @@ export class GetAllUsersDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) search = ''; @ApiProperty({ required: false }) diff --git a/apps/ledger/src/credential-definition/credential-definition.service.ts b/apps/ledger/src/credential-definition/credential-definition.service.ts index eb8831fcf..66341747f 100644 --- a/apps/ledger/src/credential-definition/credential-definition.service.ts +++ b/apps/ledger/src/credential-definition/credential-definition.service.ts @@ -114,7 +114,7 @@ export class CredentialDefinitionService extends BaseService { this.logger.error( `Error in creating credential definition: ${JSON.stringify(error)}` ); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -180,7 +180,7 @@ export class CredentialDefinitionService extends BaseService { return credDefResponse; } catch (error) { this.logger.error(`Error retrieving credential definition with id ${payload.credentialDefinitionId}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -252,7 +252,7 @@ export class CredentialDefinitionService extends BaseService { } catch (error) { this.logger.error(`Error in retrieving credential definitions: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } diff --git a/apps/ledger/src/schema/schema.service.ts b/apps/ledger/src/schema/schema.service.ts index ae76b2129..c1816f35d 100644 --- a/apps/ledger/src/schema/schema.service.ts +++ b/apps/ledger/src/schema/schema.service.ts @@ -180,7 +180,7 @@ export class SchemaService extends BaseService { this.logger.error( `[createSchema] - outer Error: ${JSON.stringify(error)}` ); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -244,7 +244,7 @@ export class SchemaService extends BaseService { } catch (error) { this.logger.error(`Error in getting schema by id: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -322,7 +322,7 @@ export class SchemaService extends BaseService { } catch (error) { this.logger.error(`Error in retrieving schemas by org id: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -361,7 +361,7 @@ export class SchemaService extends BaseService { } catch (error) { this.logger.error(`Error in retrieving credential definition: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -410,7 +410,7 @@ export class SchemaService extends BaseService { } catch (error) { this.logger.error(`Error in retrieving all schemas: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } } From 283fa4d321d3bf01b10711c8fb1090a53111203f Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Thu, 21 Sep 2023 19:58:41 +0530 Subject: [PATCH 042/162] refactor: Standardize API in connection, issuance and verification (#100) * refactor: connection, issuance, veriication API refactor Signed-off-by: KulkarniShashank * Added error handling and role guard in issuance and verification Signed-off-by: KulkarniShashank * Remove the duplication code in verification Signed-off-by: KulkarniShashank * Remove the duplication code in verification controller Signed-off-by: KulkarniShashank * Changes as per comment in PR Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/api-gateway/common/exception-handler.ts | 7 +- .../src/connection/connection.controller.ts | 156 ++++++-------- .../src/connection/dtos/connection.dto.ts | 7 +- .../credential-definition.controller.ts | 2 +- .../credential-definition.service.ts | 29 +-- .../api-gateway/src/dtos/create-schema.dto.ts | 23 +- .../src/issuance/dtos/issuance.dto.ts | 7 +- .../src/issuance/issuance.controller.ts | 180 ++++++++-------- apps/api-gateway/src/schema/schema.service.ts | 38 +--- .../src/verification/dto/request-proof.dto.ts | 31 ++- .../verification/verification.controller.ts | 197 ++++++++++-------- apps/connection/src/connection.service.ts | 12 +- apps/issuance/src/issuance.service.ts | 11 +- apps/verification/src/verification.service.ts | 139 +++--------- libs/common/src/response-messages/index.ts | 2 +- 15 files changed, 362 insertions(+), 479 deletions(-) diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index 162712d52..5df61a442 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -18,7 +18,6 @@ export class CustomExceptionFilter extends BaseExceptionFilter { exception.message = 'Oops! Something went wrong. Please try again'; } - let errorResponse; if (exception && exception["statusCode"] === HttpStatus.INTERNAL_SERVER_ERROR) { errorResponse = { @@ -26,6 +25,12 @@ export class CustomExceptionFilter extends BaseExceptionFilter { 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) { + errorResponse = { + statusCode: exception["error"].statusCode ? exception["error"].statusCode : status, + 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, diff --git a/apps/api-gateway/src/connection/connection.controller.ts b/apps/api-gateway/src/connection/connection.controller.ts index d9ad6bf9a..d05e52845 100644 --- a/apps/api-gateway/src/connection/connection.controller.ts +++ b/apps/api-gateway/src/connection/connection.controller.ts @@ -14,41 +14,53 @@ import { Response } from 'express'; import { Connections } from './enums/connections.enum'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; +import { OrgRoles } from 'libs/org-roles/enums'; +import { Roles } from '../authz/decorators/roles.decorator'; +import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; @UseFilters(CustomExceptionFilter) @Controller() @ApiTags('connections') +@ApiBearerAuth() @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) export class ConnectionController { private readonly logger = new Logger('Connection'); constructor(private readonly connectionService: ConnectionService - ) { - /** - * Create out-of-band connection legacy invitation - * @param connectionDto - * @param res - * @returns Created out-of-band connection invitation url - */ - } - @Post('/connections') - @ApiOperation({ summary: 'Create outbound out-of-band connection (Legacy Invitation)', description: 'Create outbound out-of-band connection (Legacy Invitation)' }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() + ) { } + + /** + * Description: Get connection by connectionId + * @param user + * @param connectionId + * @param orgId + * + */ + @Get('orgs/:orgId/connections/:connectionId') + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @ApiOperation({ + summary: `Get connections by connection Id`, + description: `Get connections by connection Id` + }) @ApiResponse({ status: 200, description: 'Success', type: AuthTokenResponse }) - async createLegacyConnectionInvitation(@Body() connectionDto: CreateConnectionDto, @User() reqUser: IUserRequestInterface, @Res() res: Response): Promise { - const connectionData = await this.connectionService.createLegacyConnectionInvitation(connectionDto, reqUser); + async getConnectionsById( + @User() user: IUserRequest, + @Param('connectionId') connectionId: string, + @Param('orgId') orgId: number, + @Res() res: Response + ): Promise { + const connectionsDetails = await this.connectionService.getConnectionsById(user, connectionId, orgId); + const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.connection.success.create, - data: connectionData.response + message: ResponseMessages.connection.success.fetch, + data: connectionsDetails.response }; return res.status(HttpStatus.OK).json(finalResponse); - } - /** * Description: Get all connections * @param user @@ -58,14 +70,14 @@ export class ConnectionController { * @param orgId * */ - @Get('/connections') - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() + @Get('/orgs/:orgId/connections') + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @ApiOperation({ - summary: `Fetch all connections details`, - description: `Fetch all connections details` + summary: `Fetch all connection details`, + description: `Fetch all connection details` }) - @ApiResponse({ status: 201, description: 'Success', type: AuthTokenResponse }) + @ApiResponse({ status: 200, description: 'Success', type: AuthTokenResponse }) @ApiQuery( { name: 'outOfBandId', required: false } ) @@ -84,10 +96,6 @@ export class ConnectionController { @ApiQuery( { name: 'theirLabel', required: false } ) - @ApiQuery( - { name: 'orgId', required: true } - ) - async getConnections( @User() user: IUserRequest, @Query('outOfBandId') outOfBandId: string, @@ -96,7 +104,7 @@ export class ConnectionController { @Query('myDid') myDid: string, @Query('theirDid') theirDid: string, @Query('theirLabel') theirLabel: string, - @Query('orgId') orgId: number, + @Param('orgId') orgId: number, @Res() res: Response ): Promise { @@ -112,6 +120,34 @@ export class ConnectionController { return res.status(HttpStatus.OK).json(finalResponse); } + /** + * Create out-of-band connection legacy invitation + * @param connectionDto + * @param res + * @returns Created out-of-band connection invitation url + */ + @Post('/orgs/:orgId/connections') + @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) + @ApiResponse({ status: 201, description: 'Success', type: AuthTokenResponse }) + async createLegacyConnectionInvitation( + @Param('orgId') orgId: number, + @Body() connectionDto: CreateConnectionDto, + @User() reqUser: IUserRequestInterface, + @Res() res: Response + ): Promise { + + connectionDto.orgId = orgId; + const connectionData = await this.connectionService.createLegacyConnectionInvitation(connectionDto, reqUser); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.connection.success.create, + data: connectionData.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + + } /** * Catch connection webhook responses. @@ -134,68 +170,10 @@ export class ConnectionController { ): Promise { const connectionData = await this.connectionService.getConnectionWebhook(connectionDto, id); const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, + statusCode: HttpStatus.CREATED, message: ResponseMessages.connection.success.create, data: connectionData }; - return res.status(HttpStatus.OK).json(finalResponse); - } - - /** - * Shortening url based on reference Id. - * @param referenceId The referenceId is set as a request parameter. - * @param res The current url is set as a header in the response parameter. - */ - @Get('connections/url/:referenceId') - @ApiOperation({ - summary: 'Shortening url based on reference Id', - description: 'Shortening url based on reference Id' - }) - async getPresentproofRequestUrl( - @Param('referenceId') referenceId: string, - @Res() res: Response - ): Promise { - const originalUrlData = await this.connectionService.getUrl(referenceId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.connection.success.create, - data: originalUrlData.response - }; - return res.status(HttpStatus.OK).json(finalResponse.data); - } - - /** -* Description: Get all connections by connectionId -* @param user -* @param connectionId -* @param orgId -* -*/ - @Get('connections/:connectionId') - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiOperation({ - summary: `Fetch all connections details by connectionId`, - description: `Fetch all connections details by connectionId` - }) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiResponse({ status: 201, description: 'Success', type: AuthTokenResponse }) - async getConnectionsById( - @User() user: IUserRequest, - @Param('connectionId') connectionId: string, - @Query('orgId') orgId: number, - @Res() res: Response - ): Promise { - const connectionsDetails = await this.connectionService.getConnectionsById(user, connectionId, orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.connection.success.fetch, - data: connectionsDetails.response - }; - return res.status(HttpStatus.OK).json(finalResponse); + return res.status(HttpStatus.CREATED).json(finalResponse); } } diff --git a/apps/api-gateway/src/connection/dtos/connection.dto.ts b/apps/api-gateway/src/connection/dtos/connection.dto.ts index e8bb7c55a..14d5d8f6e 100644 --- a/apps/api-gateway/src/connection/dtos/connection.dto.ts +++ b/apps/api-gateway/src/connection/dtos/connection.dto.ts @@ -1,4 +1,4 @@ -import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; @@ -27,12 +27,9 @@ export class CreateConnectionDto { @ApiProperty() @IsBoolean() @IsOptional() - @IsNotEmpty({ message: 'please provide valid autoAcceptConnection' }) + @IsNotEmpty({ message: 'autoAcceptConnection should boolean' }) autoAcceptConnection: boolean; - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; } diff --git a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts index 9cd7d0494..6ce6c3d57 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts @@ -117,6 +117,6 @@ export class CredentialDefinitionController { message: ResponseMessages.credentialDefinition.success.create, data: credentialsDefinitionDetails.response }; - return res.status(HttpStatus.OK).json(credDefResponse); + return res.status(HttpStatus.CREATED).json(credDefResponse); } } \ No newline at end of file diff --git a/apps/api-gateway/src/credential-definition/credential-definition.service.ts b/apps/api-gateway/src/credential-definition/credential-definition.service.ts index 36f1dbf91..b66b607f0 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.service.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.service.ts @@ -1,5 +1,5 @@ import { Injectable, Inject } from '@nestjs/common'; -import { ClientProxy, RpcException } from '@nestjs/microservices'; +import { ClientProxy } from '@nestjs/microservices'; import { CreateCredentialDefinitionDto } from './dto/create-cred-defs.dto'; import { BaseService } from '../../../../libs/service/base.service'; import { IUserRequestInterface } from '../interfaces/IUserRequestInterface'; @@ -15,32 +15,17 @@ export class CredentialDefinitionService extends BaseService { } createCredentialDefinition(credDef: CreateCredentialDefinitionDto, user: IUserRequestInterface): Promise<{ response: object }> { - try { - const payload = { credDef, user }; - return this.sendNats(this.credDefServiceProxy, 'create-credential-definition', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { credDef, user }; + return this.sendNats(this.credDefServiceProxy, 'create-credential-definition', payload); } getCredentialDefinitionById(credentialDefinitionId: string, orgId: number): Promise<{ response: object }> { - try { - const payload = { credentialDefinitionId, orgId }; - return this.sendNats(this.credDefServiceProxy, 'get-credential-definition-by-id', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { credentialDefinitionId, orgId }; + return this.sendNats(this.credDefServiceProxy, 'get-credential-definition-by-id', payload); } getAllCredDefs(credDefSearchCriteria: GetAllCredDefsDto, user: IUserRequestInterface, orgId: number): Promise<{ response: object }> { - try { - const payload = { credDefSearchCriteria, user, orgId }; - return this.sendNats(this.credDefServiceProxy, 'get-all-credential-definitions', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { credDefSearchCriteria, user, orgId }; + return this.sendNats(this.credDefServiceProxy, 'get-all-credential-definitions', payload); } } diff --git a/apps/api-gateway/src/dtos/create-schema.dto.ts b/apps/api-gateway/src/dtos/create-schema.dto.ts index 232af5a4f..bedcf23a3 100644 --- a/apps/api-gateway/src/dtos/create-schema.dto.ts +++ b/apps/api-gateway/src/dtos/create-schema.dto.ts @@ -2,9 +2,18 @@ import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -interface IAttributeValue{ +class AttributeValue { + + @IsString() + @IsNotEmpty({ message: 'attributeName is required.' }) attributeName: string; + + @IsString() + @IsNotEmpty({ message: 'schemaDataType is required.' }) schemaDataType: string; + + @IsString() + @IsNotEmpty({ message: 'displayName is required.' }) displayName: string; } @@ -17,10 +26,18 @@ export class CreateSchemaDto { @IsString({ message: 'schema name must be a string' }) @IsNotEmpty({ message: 'please provide valid schema name' }) schemaName: string; - @ApiProperty() + @ApiProperty({ + 'example': [ + { + attributeName: 'name', + schemaDataType: 'string', + displayName: 'Name' + } + ] + }) @IsArray({ message: 'attributes must be an array' }) @IsNotEmpty({ message: 'please provide valid attributes' }) - attributes: IAttributeValue[]; + attributes: AttributeValue[]; orgId: number; diff --git a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts index 5deff2558..e2f6a4c37 100644 --- a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts +++ b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts @@ -1,4 +1,4 @@ -import { IsArray, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; interface attribute { @@ -29,15 +29,10 @@ export class IssueCredentialDto { @IsString({ message: 'connectionId should be string' }) connectionId: string; - @ApiProperty({ example: 'v1' }) @IsOptional() @IsNotEmpty({ message: 'Please provide valid protocol-version' }) @IsString({ message: 'protocol-version should be string' }) protocolVersion?: string; - - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; } diff --git a/apps/api-gateway/src/issuance/issuance.controller.ts b/apps/api-gateway/src/issuance/issuance.controller.ts index f0f4d9817..3bbf6b559 100644 --- a/apps/api-gateway/src/issuance/issuance.controller.ts +++ b/apps/api-gateway/src/issuance/issuance.controller.ts @@ -45,7 +45,7 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler @Controller() @UseFilters(CustomExceptionFilter) -@ApiTags('issuances') +@ApiTags('credentials') @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @@ -57,25 +57,110 @@ export class IssuanceController { ) { } private readonly logger = new Logger('IssuanceController'); + /** + * Description: Get all issued credentials + * @param user + * @param threadId + * @param connectionId + * @param state + * @param orgId + * + */ + @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: 200, description: 'Success', type: ApiResponseDto }) + @ApiQuery( + { name: 'threadId', required: false } + ) + @ApiQuery( + { name: 'connectionId', required: false } + ) + @ApiQuery( + { name: 'state', enum: IssueCredential, required: false } + ) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) + async getIssueCredentials( + @User() user: IUserRequest, + @Query('threadId') threadId: string, + @Query('connectionId') connectionId: string, + @Query('state') state: string, + @Param('orgId') orgId: number, + @Res() res: Response + ): Promise { + + const getCredentialDetails = await this.issueCredentialService.getIssueCredentials(user, threadId, connectionId, state, orgId); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.issuance.success.fetch, + data: getCredentialDetails.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** + * Description: Get all issued credentials + * @param user + * @param credentialRecordId + * @param orgId + * + */ + @Get('/orgs/:orgId/credentials/:credentialRecordId') + @ApiBearerAuth() + @ApiOperation({ + summary: `Get credential by credentialRecordId`, + description: `Get credential credentialRecordId` + }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) + async getIssueCredentialsbyCredentialRecordId( + @User() user: IUserRequest, + @Param('credentialRecordId') credentialRecordId: string, + @Param('orgId') orgId: number, + + @Res() res: Response + ): Promise { + + const getCredentialDetails = await this.issueCredentialService.getIssueCredentialsbyCredentialRecordId(user, credentialRecordId, orgId); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.issuance.success.fetch, + data: getCredentialDetails.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + /** * Description: Issuer send credential to create offer * @param user * @param issueCredentialDto */ - @Post('issue-credentials/create-offer') - @UseGuards(AuthGuard('jwt')) + @Post('/orgs/:orgId/credentials/offer') @ApiBearerAuth() @ApiOperation({ summary: `Send credential details to create-offer`, description: `Send credential details to create-offer` }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) async sendCredential( @User() user: IUserRequest, + @Param('orgId') orgId: number, @Body() issueCredentialDto: IssueCredentialDto, @Res() res: Response ): Promise { + issueCredentialDto.orgId = orgId; const attrData = issueCredentialDto.attributes; attrData.forEach((data) => { @@ -124,93 +209,4 @@ export class IssuanceController { } - /** - * Description: Get all issued credentials - * @param user - * @param threadId - * @param connectionId - * @param state - * @param orgId - * - */ - @Get('/issue-credentials') - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiOperation({ - summary: `Fetch all issued credentials`, - description: `Fetch all issued credentials` - }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @ApiQuery( - { name: 'threadId', required: false } - ) - @ApiQuery( - { name: 'connectionId', required: false } - ) - @ApiQuery( - { name: 'state', enum: IssueCredential, required: false } - ) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiBearerAuth() - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async getIssueCredentials( - @User() user: IUserRequest, - @Query('threadId') threadId: string, - @Query('connectionId') connectionId: string, - @Query('state') state: string, - @Query('orgId') orgId: number, - @Res() res: Response - ): Promise { - - state = state || undefined; - const getCredentialDetails = await this.issueCredentialService.getIssueCredentials(user, threadId, connectionId, state, orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.issuance.success.fetch, - data: getCredentialDetails.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - - - /** - * Description: Get all issued credentials - * @param user - * @param credentialRecordId - * @param orgId - * - */ - @Get('issue-credentials/:credentialRecordId') - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiOperation({ - summary: `Fetch all issued credentials by credentialRecordId`, - description: `Fetch all issued credentials by credentialRecordId` - }) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async getIssueCredentialsbyCredentialRecordId( - @User() user: IUserRequest, - @Param('credentialRecordId') credentialRecordId: string, - @Query('orgId') orgId: number, - - @Res() res: Response - ): Promise { - - const getCredentialDetails = await this.issueCredentialService.getIssueCredentialsbyCredentialRecordId(user, credentialRecordId, orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.issuance.success.fetch, - data: getCredentialDetails.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - } diff --git a/apps/api-gateway/src/schema/schema.service.ts b/apps/api-gateway/src/schema/schema.service.ts index ed038952e..333f4b5ff 100644 --- a/apps/api-gateway/src/schema/schema.service.ts +++ b/apps/api-gateway/src/schema/schema.service.ts @@ -1,5 +1,5 @@ import { Injectable, Inject } from '@nestjs/common'; -import { ClientProxy, RpcException } from '@nestjs/microservices'; +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'; @@ -15,48 +15,28 @@ export class SchemaService extends BaseService { createSchema(schema: CreateSchemaDto, user: IUserRequestInterface, orgId: number): Promise<{ response: object; }> { - try { - const payload = { schema, user, orgId }; - return this.sendNats(this.schemaServiceProxy, 'create-schema', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { schema, user, orgId }; + return this.sendNats(this.schemaServiceProxy, 'create-schema', payload); } getSchemaById(schemaId: string, orgId: number): Promise<{ response: object; }> { - try { - const payload = { schemaId, orgId }; - return this.sendNats(this.schemaServiceProxy, 'get-schema-by-id', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { schemaId, orgId }; + return this.sendNats(this.schemaServiceProxy, 'get-schema-by-id', payload); } getSchemas(schemaSearchCriteria: ISchemaSearchInterface, user: IUserRequestInterface, orgId: number): Promise<{ response: object; }> { - try { - const schemaSearch = { schemaSearchCriteria, user, orgId }; - return this.sendNats(this.schemaServiceProxy, 'get-schemas', schemaSearch); - } catch (error) { - throw new RpcException(error.response); - - } + const schemaSearch = { schemaSearchCriteria, user, orgId }; + return this.sendNats(this.schemaServiceProxy, 'get-schemas', schemaSearch); } getcredDeffListBySchemaId(schemaId: string, schemaSearchCriteria: ICredDeffSchemaSearchInterface, user: IUserRequestInterface, orgId: number): Promise<{ response: object; }> { - try { - const payload = { schemaId, schemaSearchCriteria, user, orgId }; - return this.sendNats(this.schemaServiceProxy, 'get-cred-deff-list-by-schemas-id', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { schemaId, schemaSearchCriteria, user, orgId }; + return this.sendNats(this.schemaServiceProxy, 'get-cred-deff-list-by-schemas-id', payload); } } \ No newline at end of file diff --git a/apps/api-gateway/src/verification/dto/request-proof.dto.ts b/apps/api-gateway/src/verification/dto/request-proof.dto.ts index 4a5ee350e..7081a3f58 100644 --- a/apps/api-gateway/src/verification/dto/request-proof.dto.ts +++ b/apps/api-gateway/src/verification/dto/request-proof.dto.ts @@ -1,24 +1,31 @@ -import { IsArray, IsEmail, IsNotEmpty, IsNumber, IsObject, IsOptional, IsString, MaxLength } from 'class-validator'; +import { IsArray, IsEmail, IsNotEmpty, IsObject, IsOptional, IsString, MaxLength } from 'class-validator'; import { toLowerCase, trim } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -// import { IProofRequestAttribute } from '../interfaces/verification.interface'; -class IProofRequestAttribute { +export class ProofRequestAttribute { @IsString() + @IsNotEmpty({ message: 'attributeName is required.' }) attributeName: string; @IsString() + @IsNotEmpty({ message: 'schemaId is required.' }) + schemaId: string; + + @IsString() + @IsOptional() + @IsNotEmpty({ message: 'condition is required.' }) condition?: string; @IsString() + @IsOptional() + @IsNotEmpty({ message: 'value is required.' }) value?: string; @IsString() + @IsOptional() + @IsNotEmpty({ message: 'credDefId is required.' }) credDefId?: string; - - @IsString() - schemaId: string; } export class RequestProof { @@ -43,15 +50,11 @@ export class RequestProof { @IsArray({ message: 'attributes must be in array' }) @IsObject({ each: true }) @IsNotEmpty({ message: 'please provide valid attributes' }) - attributes: IProofRequestAttribute[]; + attributes: ProofRequestAttribute[]; @ApiProperty() @IsOptional() comment: string; - - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; @IsString({ message: 'auto accept proof must be in string' }) @@ -80,7 +83,7 @@ export class OutOfBandRequestProof { @IsArray({ message: 'attributes must be in array' }) @IsObject({ each: true }) @IsNotEmpty({ message: 'please provide valid attributes' }) - attributes: IProofRequestAttribute[]; + attributes: ProofRequestAttribute[]; @ApiProperty({ example: 'string' }) @IsNotEmpty({ message: 'Please provide valid emailId' }) @@ -94,10 +97,6 @@ export class OutOfBandRequestProof { @ApiProperty() @IsOptional() comment: string; - - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; @IsString({ message: 'autoAcceptProof must be in string' }) diff --git a/apps/api-gateway/src/verification/verification.controller.ts b/apps/api-gateway/src/verification/verification.controller.ts index 328bd2540..672e8bbba 100644 --- a/apps/api-gateway/src/verification/verification.controller.ts +++ b/apps/api-gateway/src/verification/verification.controller.ts @@ -12,7 +12,7 @@ import { ApiQuery, ApiExcludeEndpoint } from '@nestjs/swagger'; -import { Controller, Logger, Post, Body, Get, Query, HttpStatus, Res, UseGuards, Param, UseFilters } from '@nestjs/common'; +import { Controller, Logger, Post, Body, Get, Query, HttpStatus, Res, UseGuards, Param, UseFilters, BadRequestException } from '@nestjs/common'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; @@ -33,33 +33,27 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler @UseFilters(CustomExceptionFilter) @ApiBearerAuth() @Controller() +@ApiTags('verifications') export class VerificationController { constructor(private readonly verificationService: VerificationService) { } private readonly logger = new Logger('VerificationController'); - @Get('/proofs/form-data') - @ApiTags('verifications') + @Get('/orgs/:orgId/proofs/:proofId/form') @ApiOperation({ summary: `Get a proof form data`, description: `Get a proof form data` }) - @ApiQuery( - { name: 'id', required: true } - ) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getProofFormData( @Res() res: Response, @GetUser() user: IUserRequest, - @Query('id') id: string, - @Query('orgId') orgId: number + @Param('proofId') id: string, + @Param('orgId') orgId: number ): Promise { const sendProofRequest = await this.verificationService.getProofFormData(id, orgId, user); const finalResponse: IResponseType = { @@ -71,75 +65,67 @@ export class VerificationController { } /** - * Get all proof presentations + * Get proof presentation by id * @param user + * @param id * @param orgId - * @returns Get all proof presentation + * @returns Get proof presentation details */ - @Get('/proofs') - @ApiTags('verifications') + @Get('/orgs/:orgId/proofs/:proofId') @ApiOperation({ - summary: `Get all proof-presentation`, - description: `Get all proof-presentation` + summary: `Get all proof presentation by proof Id`, + description: `Get all proof presentation by proof Id` }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiQuery( - { name: 'threadId', required: false } - ) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async getProofPresentations( + async getProofPresentationById( @Res() res: Response, @GetUser() user: IUserRequest, - @Query('orgId') orgId: number, - @Query('threadId') threadId: string + @Param('proofId') id: string, + @Param('orgId') orgId: number ): Promise { - const proofPresentationDetails = await this.verificationService.getProofPresentations(orgId, threadId, user); + const getProofPresentationById = await this.verificationService.getProofPresentationById(id, orgId, user); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.verification.success.fetch, - data: proofPresentationDetails.response + data: getProofPresentationById.response }; return res.status(HttpStatus.OK).json(finalResponse); } /** - * Get proof presentation by id - * @param user - * @param id - * @param orgId - * @returns Get proof presentation details - */ - @Get('/proofs/:id') - @ApiTags('verifications') + * Get all proof presentations + * @param user + * @param orgId + * @returns Get all proof presentation + */ + @Get('/orgs/:orgId/proofs') @ApiOperation({ - summary: `Get proof-presentation by Id`, - description: `Get proof-presentation by Id` + summary: `Get all proof presentations`, + description: `Get all proof presentations` }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiQuery( - { name: 'orgId', required: true } + { name: 'threadId', required: false } ) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async getProofPresentationById( + async getProofPresentations( @Res() res: Response, @GetUser() user: IUserRequest, - @Param('id') id: string, - @Query('orgId') orgId: number + @Param('orgId') orgId: number, + @Query('threadId') threadId: string ): Promise { - const getProofPresentationById = await this.verificationService.getProofPresentationById(id, orgId, user); + const proofPresentationDetails = await this.verificationService.getProofPresentations(orgId, threadId, user); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.verification.success.fetch, - data: getProofPresentationById.response + data: proofPresentationDetails.response }; return res.status(HttpStatus.OK).json(finalResponse); } @@ -150,8 +136,7 @@ export class VerificationController { * @param requestProof * @returns Get requested proof presentation details */ - @Post('/proofs/request-proof') - @ApiTags('verifications') + @Post('/orgs/:orgId/proofs') @ApiOperation({ summary: `Sends a proof request`, description: `Sends a proof request` @@ -160,13 +145,20 @@ export class VerificationController { @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiBody({ type: RequestProof }) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) async sendPresentationRequest( @Res() res: Response, @GetUser() user: IUserRequest, + @Param('orgId') orgId: number, @Body() requestProof: RequestProof ): Promise { + + for (const attrData of requestProof.attributes) { + await this.validateAttribute(attrData); + } + + requestProof.orgId = orgId; const sendProofRequest = await this.verificationService.sendProofRequest(requestProof, user); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -183,8 +175,7 @@ export class VerificationController { * @param orgId * @returns Get verified proof presentation details */ - @Post('proofs/verify-presentation') - @ApiTags('verifications') + @Post('/orgs/:orgId/proofs/:proofId/verify') @ApiOperation({ summary: `Verify presentation`, description: `Verify presentation` @@ -192,19 +183,13 @@ export class VerificationController { @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiQuery( - { name: 'id', required: true } - ) - @ApiQuery( - { name: 'orgId', required: true } - ) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async verifyPresentation( @Res() res: Response, @GetUser() user: IUserRequest, - @Query('id') id: string, - @Query('orgId') orgId: number + @Param('proofId') id: string, + @Param('orgId') orgId: number ): Promise { const verifyPresentation = await this.verificationService.verifyPresentation(id, orgId, user); const finalResponse: IResponseType = { @@ -215,39 +200,13 @@ export class VerificationController { return res.status(HttpStatus.CREATED).json(finalResponse); } - @Post('wh/:id/proofs') - @ApiTags('verifications') - @ApiOperation({ - summary: `Webhook proof presentation`, - description: `Webhook proof presentation` - }) - @ApiExcludeEndpoint() - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) - async webhookProofPresentation( - @Param('id') id: string, - @Body() proofPresentationPayload: WebhookPresentationProof, - @Res() res: Response - ): Promise { - - const webhookProofPresentation = await this.verificationService.webhookProofPresentation(id, proofPresentationPayload); - const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.verification.success.fetch, - data: webhookProofPresentation.response - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - /** * Out-Of-Band Proof Presentation * @param user * @param outOfBandRequestProof * @returns Get out-of-band requested proof presentation details */ - @Post('/proofs/create-request-oob') - @ApiTags('verifications') + @Post('/orgs/:orgId/proofs/oob') @ApiOperation({ summary: `Sends a out-of-band proof request`, description: `Sends a out-of-band proof request` @@ -261,8 +220,15 @@ export class VerificationController { async sendOutOfBandPresentationRequest( @Res() res: Response, @GetUser() user: IUserRequest, - @Body() outOfBandRequestProof: OutOfBandRequestProof + @Body() outOfBandRequestProof: OutOfBandRequestProof, + @Param('orgId') orgId: number ): Promise { + + for (const attrData of outOfBandRequestProof.attributes) { + await this.validateAttribute(attrData); + } + + outOfBandRequestProof.orgId = orgId; const sendProofRequest = await this.verificationService.sendOutOfBandPresentationRequest(outOfBandRequestProof, user); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -271,5 +237,52 @@ export class VerificationController { }; return res.status(HttpStatus.CREATED).json(finalResponse); } + + @Post('wh/:id/proofs') + @ApiOperation({ + summary: `Webhook proof presentation`, + description: `Webhook proof presentation` + }) + @ApiExcludeEndpoint() + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) + async webhookProofPresentation( + @Param('id') id: string, + @Body() proofPresentationPayload: WebhookPresentationProof, + @Res() res: Response + ): Promise { + + const webhookProofPresentation = await this.verificationService.webhookProofPresentation(id, proofPresentationPayload); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.verification.success.fetch, + data: webhookProofPresentation.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + async validateAttribute( + attrData: object + ): Promise { + + if (!attrData['attributeName']) { + throw new BadRequestException('attributeName must be required'); + } else if (!attrData['schemaId']) { + throw new BadRequestException('schemaId must be required'); + } + + if (undefined !== attrData['credDefId'] && '' === attrData['credDefId'].trim()) { + throw new BadRequestException('credDefId cannot be empty'); + } + + if (undefined !== attrData['condition'] && '' === attrData['condition'].trim()) { + throw new BadRequestException('condition cannot be empty'); + } + + if (undefined !== attrData['value'] && '' === attrData['value'].trim()) { + throw new BadRequestException('value cannot be empty'); + } + } } diff --git a/apps/connection/src/connection.service.ts b/apps/connection/src/connection.service.ts index 51756a352..78b149be1 100644 --- a/apps/connection/src/connection.service.ts +++ b/apps/connection/src/connection.service.ts @@ -78,7 +78,7 @@ export class ConnectionService { return saveConnectionDetails; } catch (error) { this.logger.error(`[createLegacyConnectionInvitation] - error in connection invitation: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -97,7 +97,7 @@ export class ConnectionService { return saveConnectionDetails; } catch (error) { this.logger.error(`[getConnectionWebhook] - error in fetch connection webhook: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -148,8 +148,7 @@ export class ConnectionService { return urlDetails.url; } catch (error) { this.logger.error(`Error in get url in connection service: ${JSON.stringify(error)}`); - throw error; - + throw new RpcException(error.response ? error.response : error); } } @@ -209,8 +208,7 @@ export class ConnectionService { return connectionsDetails?.response; } catch (error) { this.logger.error(`Error in get url in connection service: ${JSON.stringify(error)}`); - throw error; - + throw new RpcException(error.response ? error.response : error); } } @@ -271,7 +269,7 @@ export class ConnectionService { return createConnectionInvitation?.response; } catch (error) { this.logger.error(`[getConnectionsById] - error in get connections : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index f6caddcd5..1760c97fc 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -55,7 +55,7 @@ export class IssuanceService { return credentialCreateOfferDetails?.response; } catch (error) { this.logger.error(`[sendCredentialCreateOffer] - error in create credentials : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } @@ -90,7 +90,7 @@ export class IssuanceService { return credentialCreateOfferDetails?.response; } catch (error) { this.logger.error(`[sendCredentialCreateOffer] - error in create credentials : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } @@ -152,7 +152,7 @@ export class IssuanceService { return issueCredentialsDetails?.response; } catch (error) { this.logger.error(`[sendCredentialCreateOffer] - error in create credentials : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -203,7 +203,7 @@ export class IssuanceService { return createConnectionInvitation?.response; } catch (error) { this.logger.error(`[getIssueCredentialsbyCredentialRecordId] - error in get credentials : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } @@ -213,7 +213,7 @@ export class IssuanceService { return agentDetails; } catch (error) { this.logger.error(`[getIssueCredentialsbyCredentialRecordId] - error in get credentials : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } @@ -311,7 +311,6 @@ export class IssuanceService { } catch (error) { this.logger.error(`Error in get agent url: ${JSON.stringify(error)}`); throw error; - } } } diff --git a/apps/verification/src/verification.service.ts b/apps/verification/src/verification.service.ts index c0b57c04f..b7fbad1e0 100644 --- a/apps/verification/src/verification.service.ts +++ b/apps/verification/src/verification.service.ts @@ -49,7 +49,7 @@ export class VerificationService { } catch (error) { this.logger.error(`[getProofPresentations] - error in get proof presentation : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -109,7 +109,7 @@ export class VerificationService { return getProofPresentationById?.response; } catch (error) { this.logger.error(`[getProofPresentationById] - error in get proof presentation by id : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -157,8 +157,6 @@ export class VerificationService { */ async sendProofRequest(requestProof: IRequestProof): Promise { try { - let requestedAttributes = {}; - const requestedPredicates = {}; const comment = requestProof.comment ? requestProof.comment : ''; let proofRequestPayload: ISendProofRequestPayload = { @@ -176,73 +174,7 @@ export class VerificationService { autoAcceptProof: '' }; - const attributeWithSchemaIdExists = requestProof.attributes.some(attribute => attribute.schemaId); - if (attributeWithSchemaIdExists) { - requestedAttributes = Object.fromEntries(requestProof.attributes.map((attribute, index) => { - - const attributeElement = attribute.attributeName; - const attributeReferent = `additionalProp${index + 1}`; - - if (!attribute.condition && !attribute.value) { - const keys = Object.keys(requestedAttributes); - - if (0 < keys.length) { - let attributeFound = false; - - for (const attr of keys) { - if ( - requestedAttributes[attr].restrictions.some(res => res.schema_id) === - requestProof.attributes[index].schemaId - ) { - requestedAttributes[attr].name.push(attributeElement); - attributeFound = true; - } - - if (attr === keys[keys.length - 1] && !attributeFound) { - requestedAttributes[attributeReferent] = { - name: attributeElement, - restrictions: [ - { - cred_def_id: requestProof.attributes[index].credDefId ? requestProof.attributes[index].credDefId : undefined, - schema_id: requestProof.attributes[index].schemaId - } - ] - }; - } - } - } else { - return [ - attributeReferent, - { - name: attributeElement, - restrictions: [ - { - cred_def_id: requestProof.attributes[index].credDefId ? requestProof.attributes[index].credDefId : undefined, - schema_id: requestProof.attributes[index].schemaId - } - ] - } - ]; - } - } else { - requestedPredicates[attributeReferent] = { - p_type: attribute.condition, - restrictions: [ - { - cred_def_id: requestProof.attributes[index].credDefId ? requestProof.attributes[index].credDefId : undefined, - schema_id: requestProof.attributes[index].schemaId - } - ], - name: attributeElement, - p_value: parseInt(attribute.value) - }; - } - - return [attributeReferent]; - })); - } else { - throw new BadRequestException(ResponseMessages.verification.error.schemaIdNotFound); - } + const { requestedAttributes, requestedPredicates } = await this._proofRequestPayload(requestProof); proofRequestPayload = { protocolVersion: requestProof.protocolVersion ? requestProof.protocolVersion : 'v1', @@ -272,7 +204,7 @@ export class VerificationService { return getProofPresentationById?.response; } catch (error) { this.logger.error(`[verifyPresentation] - error in verify presentation : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -330,7 +262,7 @@ export class VerificationService { return getProofPresentationById?.response; } catch (error) { this.logger.error(`[verifyPresentation] - error in verify presentation : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -378,7 +310,7 @@ export class VerificationService { } catch (error) { this.logger.error(`[webhookProofPresentation] - error in webhook proof presentation : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -487,7 +419,7 @@ export class VerificationService { } catch (error) { this.logger.error(`[sendOutOfBandPresentationRequest] - error in out of band proof request : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -536,9 +468,8 @@ export class VerificationService { const requestedPredicates = {}; const attributeWithSchemaIdExists = proofRequestpayload.attributes.some(attribute => attribute.schemaId); if (attributeWithSchemaIdExists) { + requestedAttributes = Object.fromEntries(proofRequestpayload.attributes.map((attribute, index) => { - requestedAttributes = {}; - for (const [index, attribute] of proofRequestpayload.attributes.entries()) { const attributeElement = attribute.attributeName; const attributeReferent = `additionalProp${index + 1}`; @@ -550,9 +481,8 @@ export class VerificationService { for (const attr of keys) { if ( - requestedAttributes[attr].restrictions.some( - res => res.schema_id === proofRequestpayload.attributes[index].schemaId - ) + requestedAttributes[attr].restrictions.some(res => res.schema_id) === + proofRequestpayload.attributes[index].schemaId ) { requestedAttributes[attr].name.push(attributeElement); attributeFound = true; @@ -563,9 +493,7 @@ export class VerificationService { name: attributeElement, restrictions: [ { - cred_def_id: proofRequestpayload.attributes[index].credDefId - ? proofRequestpayload.attributes[index].credDefId - : undefined, + cred_def_id: proofRequestpayload.attributes[index].credDefId ? proofRequestpayload.attributes[index].credDefId : undefined, schema_id: proofRequestpayload.attributes[index].schemaId } ] @@ -573,32 +501,25 @@ export class VerificationService { } } } else { - requestedAttributes[attributeReferent] = { - name: attributeElement, - restrictions: [ - { - cred_def_id: proofRequestpayload.attributes[index].credDefId - ? proofRequestpayload.attributes[index].credDefId - : undefined, - schema_id: proofRequestpayload.attributes[index].schemaId - } - ] - }; + return [ + attributeReferent, + { + name: attributeElement, + restrictions: [ + { + cred_def_id: proofRequestpayload.attributes[index].credDefId ? proofRequestpayload.attributes[index].credDefId : undefined, + schema_id: proofRequestpayload.attributes[index].schemaId + } + ] + } + ]; } } else { - if (isNaN(parseInt(attribute.value))) { - throw new BadRequestException( - ResponseMessages.verification.error.predicatesValueNotNumber - ); - } - requestedPredicates[attributeReferent] = { p_type: attribute.condition, restrictions: [ { - cred_def_id: proofRequestpayload.attributes[index].credDefId - ? proofRequestpayload.attributes[index].credDefId - : undefined, + cred_def_id: proofRequestpayload.attributes[index].credDefId ? proofRequestpayload.attributes[index].credDefId : undefined, schema_id: proofRequestpayload.attributes[index].schemaId } ], @@ -606,20 +527,20 @@ export class VerificationService { p_value: parseInt(attribute.value) }; } - } + + return [attributeReferent]; + })); return { requestedAttributes, requestedPredicates }; } else { - throw new BadRequestException( - ResponseMessages.verification.error.schemaIdNotFound - ); + throw new BadRequestException(ResponseMessages.verification.error.schemaIdNotFound); } } catch (error) { this.logger.error(`[proofRequestPayload] - error in proof request payload : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -815,7 +736,7 @@ export class VerificationService { return extractedDataArray; } catch (error) { this.logger.error(`[getProofFormData] - error in get proof form data : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index e7cba4251..4f9dd4b5e 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -133,7 +133,7 @@ export const ResponseMessages = { connection: { success: { create: 'Connection created successfully', - fetch: 'Connection Details fetched successfully' + fetch: 'Connection fetched successfully' }, error: { exists: 'Connection is already exist', From 38088bfdfd882166cd40c6135b91d0df574dd834 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Tue, 26 Sep 2023 19:46:07 +0530 Subject: [PATCH 043/162] feat: support afj-0.4.1 (#102) Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- .env.sample | 2 +- apps/agent-provisioning/AFJ/scripts/start_agent.sh | 2 +- apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh | 2 +- apps/api-gateway/src/connection/connection.controller.ts | 1 + apps/api-gateway/src/issuance/issuance.controller.ts | 1 + apps/api-gateway/src/verification/verification.controller.ts | 2 +- 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.env.sample b/.env.sample index 1739e22e2..9e9e35b11 100644 --- a/.env.sample +++ b/.env.sample @@ -37,7 +37,7 @@ WALLET_STORAGE_PASSWORD=xxxxxx CRYPTO_PRIVATE_KEY=xxxxx-xxxxx-xxxxx-xxxxx #It should be same as studio UI -AFJ_VERSION=afj-0.4.0:latest +AFJ_VERSION=afj-0.4.1:latest FIDO_API_ENDPOINT=http://localhost:8000 # Host:port of your FIDO (WebAuthn) Server diff --git a/apps/agent-provisioning/AFJ/scripts/start_agent.sh b/apps/agent-provisioning/AFJ/scripts/start_agent.sh index 881ed70d6..899229c80 100755 --- a/apps/agent-provisioning/AFJ/scripts/start_agent.sh +++ b/apps/agent-provisioning/AFJ/scripts/start_agent.sh @@ -48,7 +48,7 @@ cat <>${PWD}/apps/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINE "label": "${AGENCY}_${CONTAINER_NAME}", "walletId": "$WALLET_NAME", "walletKey": "$WALLET_PASSWORD", - "walletType": "postgres_storage", + "walletType": "postgres", "walletUrl": "$WALLET_STORAGE_HOST:$WALLET_STORAGE_PORT", "walletAccount": "$WALLET_STORAGE_USER", "walletPassword": "$WALLET_STORAGE_PASSWORD", diff --git a/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh b/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh index f757e53bb..db3de117f 100644 --- a/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh +++ b/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh @@ -38,7 +38,7 @@ cat <>/app/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINER_NAME} "label": "${AGENCY}_${CONTAINER_NAME}", "walletId": "$WALLET_NAME", "walletKey": "$WALLET_PASSWORD", - "walletType": "postgres_storage", + "walletType": "postgres", "walletUrl": "$WALLET_STORAGE_HOST:$WALLET_STORAGE_PORT", "walletAccount": "$WALLET_STORAGE_USER", "walletPassword": "$WALLET_STORAGE_PASSWORD", diff --git a/apps/api-gateway/src/connection/connection.controller.ts b/apps/api-gateway/src/connection/connection.controller.ts index d05e52845..d87f58721 100644 --- a/apps/api-gateway/src/connection/connection.controller.ts +++ b/apps/api-gateway/src/connection/connection.controller.ts @@ -168,6 +168,7 @@ export class ConnectionController { @Param('id') id: number, @Res() res: Response ): Promise { + this.logger.debug(`connectionDto ::: ${JSON.stringify(connectionDto)}`); const connectionData = await this.connectionService.getConnectionWebhook(connectionDto, id); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, diff --git a/apps/api-gateway/src/issuance/issuance.controller.ts b/apps/api-gateway/src/issuance/issuance.controller.ts index 3bbf6b559..ba130ad24 100644 --- a/apps/api-gateway/src/issuance/issuance.controller.ts +++ b/apps/api-gateway/src/issuance/issuance.controller.ts @@ -199,6 +199,7 @@ export class IssuanceController { @Param('id') id: number, @Res() res: Response ): Promise { + this.logger.debug(`issueCredentialDto ::: ${issueCredentialDto}`); const getCredentialDetails = await this.issueCredentialService.getIssueCredentialWebhook(issueCredentialDto, id); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, diff --git a/apps/api-gateway/src/verification/verification.controller.ts b/apps/api-gateway/src/verification/verification.controller.ts index 672e8bbba..f9f555c52 100644 --- a/apps/api-gateway/src/verification/verification.controller.ts +++ b/apps/api-gateway/src/verification/verification.controller.ts @@ -252,7 +252,7 @@ export class VerificationController { @Body() proofPresentationPayload: WebhookPresentationProof, @Res() res: Response ): Promise { - + this.logger.debug(`proofPresentationPayload ::: ${JSON.stringify(proofPresentationPayload)}`); const webhookProofPresentation = await this.verificationService.webhookProofPresentation(id, proofPresentationPayload); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, From 11ca4894eb5bc0670ac0bbc047afc9f705c1abbf Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Wed, 27 Sep 2023 16:09:21 +0530 Subject: [PATCH 044/162] Fix bugs platform module (#103) * feat: support afj-0.4.1 Signed-off-by: KulkarniShashank * fix: Add nats configuration in platform Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/api-gateway/src/platform/platform.module.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/api-gateway/src/platform/platform.module.ts b/apps/api-gateway/src/platform/platform.module.ts index ba0ffe2ec..692fe9918 100644 --- a/apps/api-gateway/src/platform/platform.module.ts +++ b/apps/api-gateway/src/platform/platform.module.ts @@ -3,7 +3,6 @@ import { PlatformController } from './platform.controller'; import { PlatformService } from './platform.service'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { ConfigModule } from '@nestjs/config'; -import { commonNatsOptions } from 'libs/service/nats.options'; @Module({ imports: [ @@ -11,10 +10,12 @@ import { commonNatsOptions } from 'libs/service/nats.options'; ClientsModule.register([ { name: 'NATS_CLIENT', - ...commonNatsOptions('AGENT_SERVICE:REQUESTER') + transport: Transport.NATS, + options: { + servers: [`${process.env.NATS_URL}`] + } } ]) - ], controllers: [PlatformController], providers: [PlatformService] From 086a8dcd28e77f05816a651ce17854ab4948db66 Mon Sep 17 00:00:00 2001 From: tipusinghaw <126460794+tipusinghaw@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:33:45 +0530 Subject: [PATCH 045/162] feat: ecosystem ms setup (#104) * worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat * merge dev branch to main (#77) * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw * feat:implemented add passke Signed-off-by: tipusinghaw * fix: login error message Signed-off-by: tipusinghaw --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw * Included credebl-master-table json file in the .gitignore Signed-off-by: @nishad.shirsat * Create ecosystem monorepo Signed-off-by: KulkarniShashank * feat: Implemented ecosystem microservice Signed-off-by: tipusinghaw * feat: changed controller name Signed-off-by: tipusinghaw --------- Signed-off-by: tipusinghaw Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank Co-authored-by: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Co-authored-by: @nishad.shirsat Co-authored-by: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Co-authored-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/api-gateway/src/app.module.ts | 4 +- .../ecosystem/dtos/create-organization-dto.ts | 36 ++++++++++++++++ .../src/ecosystem/ecosystem.controller.ts | 43 +++++++++++++++++++ .../src/ecosystem/ecosystem.module.ts | 29 +++++++++++++ .../src/ecosystem/ecosystem.service.ts | 23 ++++++++++ .../src/ecosystem.controller.spec.ts | 22 ++++++++++ apps/ecosystem/src/ecosystem.controller.ts | 24 +++++++++++ apps/ecosystem/src/ecosystem.module.ts | 26 +++++++++++ apps/ecosystem/src/ecosystem.service.ts | 20 +++++++++ apps/ecosystem/src/main.ts | 23 ++++++++++ apps/ecosystem/test/app.e2e-spec.ts | 24 +++++++++++ apps/ecosystem/test/jest-e2e.json | 9 ++++ apps/ecosystem/tsconfig.app.json | 9 ++++ libs/common/src/response-messages/index.ts | 11 ++++- nest-cli.json | 9 ++++ 15 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts create mode 100644 apps/api-gateway/src/ecosystem/ecosystem.controller.ts create mode 100644 apps/api-gateway/src/ecosystem/ecosystem.module.ts create mode 100644 apps/api-gateway/src/ecosystem/ecosystem.service.ts create mode 100644 apps/ecosystem/src/ecosystem.controller.spec.ts create mode 100644 apps/ecosystem/src/ecosystem.controller.ts create mode 100644 apps/ecosystem/src/ecosystem.module.ts create mode 100644 apps/ecosystem/src/ecosystem.service.ts create mode 100644 apps/ecosystem/src/main.ts create mode 100644 apps/ecosystem/test/app.e2e-spec.ts create mode 100644 apps/ecosystem/test/jest-e2e.json create mode 100644 apps/ecosystem/tsconfig.app.json diff --git a/apps/api-gateway/src/app.module.ts b/apps/api-gateway/src/app.module.ts index ee0657282..792999579 100644 --- a/apps/api-gateway/src/app.module.ts +++ b/apps/api-gateway/src/app.module.ts @@ -19,6 +19,7 @@ import { SchemaModule } from './schema/schema.module'; import { commonNatsOptions } from 'libs/service/nats.options'; import { UserModule } from './user/user.module'; import { ConnectionModule } from './connection/connection.module'; +import { EcosystemModule } from './ecosystem/ecosystem.module'; @Module({ imports: [ @@ -40,7 +41,8 @@ import { ConnectionModule } from './connection/connection.module'; OrganizationModule, UserModule, ConnectionModule, - IssuanceModule + IssuanceModule, + EcosystemModule ], controllers: [AppController], providers: [AppService] diff --git a/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts b/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts new file mode 100644 index 000000000..d584c5832 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts @@ -0,0 +1,36 @@ +import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; + +import { Transform } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; + +@ApiExtraModels() +export class CreateEcosystemDto { + + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Organization name is required.' }) + @MinLength(2, { message: 'Organization name must be at least 2 characters.' }) + @MaxLength(50, { message: 'Organization name must be at most 50 characters.' }) + @IsString({ message: 'Organization name must be in string format.' }) + name: string; + + @ApiPropertyOptional() + @Transform(({ value }) => trim(value)) + @MinLength(2, { message: 'Description must be at least 2 characters.' }) + @MaxLength(255, { message: 'Description must be at most 255 characters.' }) + @IsString({ message: 'Description must be in string format.' }) + description: string; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'logo must be in string format.' }) + logo: string; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + website?: 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 new file mode 100644 index 000000000..6cf86ee4f --- /dev/null +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -0,0 +1,43 @@ +import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; +import { Controller, UseGuards, UseFilters } from '@nestjs/common'; +import { EcosystemService } from './ecosystem.service'; +import { Post } from '@nestjs/common'; +import { Body } from '@nestjs/common'; +import { Res } from '@nestjs/common'; +import { CreateEcosystemDto } from './dtos/create-organization-dto'; +import IResponseType from '@credebl/common/interfaces/response.interface'; +import { HttpStatus } from '@nestjs/common'; +import { Response } from 'express'; +import { ApiResponseDto } from '../dtos/apiResponse.dto'; +import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; +import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; +import { AuthGuard } from '@nestjs/passport'; +import { ResponseMessages } from '@credebl/common/response-messages'; +import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; + + +@UseFilters(CustomExceptionFilter) +@Controller('ecosystem') +@ApiTags('ecosystem') +@ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) +export class EcosystemController { + constructor( + private readonly ecosystemService: EcosystemService + ) { } + + + @Post('/') + @ApiOperation({ summary: 'Create a new ecosystem', description: 'Create an ecosystem' }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async createOrganization(@Body() createOrgDto: CreateEcosystemDto, @Res() res: Response): Promise { + await this.ecosystemService.createEcosystem(createOrgDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.create + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } +} \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/ecosystem.module.ts b/apps/api-gateway/src/ecosystem/ecosystem.module.ts new file mode 100644 index 000000000..24a59ec00 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/ecosystem.module.ts @@ -0,0 +1,29 @@ +import { CommonModule, CommonService } from '@credebl/common'; + +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { ConfigModule } from '@nestjs/config'; +import { HttpModule } from '@nestjs/axios'; +import { Module } from '@nestjs/common'; +import { EcosystemController } from './ecosystem.controller'; +import { EcosystemService } from './ecosystem.service'; + +@Module({ + imports: [ + HttpModule, + ConfigModule.forRoot(), + ClientsModule.register([ + { + name: 'NATS_CLIENT', + transport: Transport.NATS, + options: { + servers: [`${process.env.NATS_URL}`] + } + }, + CommonModule + ]) + ], + controllers: [EcosystemController], + providers: [EcosystemService, CommonService] +}) +export class EcosystemModule { } + diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts new file mode 100644 index 000000000..4ce9cba9a --- /dev/null +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -0,0 +1,23 @@ +import { Inject } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; +import { BaseService } from 'libs/service/base.service'; + + +@Injectable() +export class EcosystemService extends BaseService { + constructor(@Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy) { + super('EcosystemService'); + } + + /** + * + * @param createEcosystemDto + * @returns Ecosystem creation success + */ + async createEcosystem(createEcosystemDto): Promise { + const payload = { createEcosystemDto }; + return this.sendNats(this.serviceProxy, 'create-ecosystem', payload); + } + +} diff --git a/apps/ecosystem/src/ecosystem.controller.spec.ts b/apps/ecosystem/src/ecosystem.controller.spec.ts new file mode 100644 index 000000000..653289261 --- /dev/null +++ b/apps/ecosystem/src/ecosystem.controller.spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { EcosystemController } from './ecosystem.controller'; +import { EcosystemService } from './ecosystem.service'; + +describe('EcosystemController', () => { + let ecosystemController: EcosystemController; + + beforeEach(async () => { + const app: TestingModule = await Test.createTestingModule({ + controllers: [EcosystemController], + providers: [EcosystemService] + }).compile(); + + ecosystemController = app.get(EcosystemController); + }); + + describe('root', () => { + it('should return "Hello World!"', () => { + expect(ecosystemController.getHello()).toBe('Hello World!'); + }); + }); +}); diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts new file mode 100644 index 000000000..55a91e22d --- /dev/null +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -0,0 +1,24 @@ +import { Controller, Logger } from '@nestjs/common'; + +import { MessagePattern } from '@nestjs/microservices'; +import { EcosystemService } from './ecosystem.service'; +import { Body } from '@nestjs/common'; + +@Controller() +export class EcosystemController { + constructor(private readonly ecosystemService: EcosystemService) {} + private readonly logger = new Logger('EcosystemController'); + + /** + * Description: create new ecosystem + * @param payload Registration Details + * @returns Get created ecosystem details + */ + + @MessagePattern({ cmd: 'create-ecosystem' }) + async createEcosystem(@Body() payload: { createOrgDto; userId }): Promise { + this.logger.log(`EcosystemPayload : ${payload}`); + return this.ecosystemService.createEcosystem(); + } + +} diff --git a/apps/ecosystem/src/ecosystem.module.ts b/apps/ecosystem/src/ecosystem.module.ts new file mode 100644 index 000000000..c749922c7 --- /dev/null +++ b/apps/ecosystem/src/ecosystem.module.ts @@ -0,0 +1,26 @@ +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 { ConnectionRepository } from './connection.repository'; +import { PrismaService } from '@credebl/prisma-service'; + +@Module({ + imports: [ + ClientsModule.register([ + { + name: 'NATS_CLIENT', + transport: Transport.NATS, + options: { + servers: [`${process.env.NATS_URL}`] + } + } + ]), + + CommonModule + ], + controllers: [EcosystemController], + providers: [EcosystemService, PrismaService, Logger] +}) +export class EcosystemModule { } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts new file mode 100644 index 000000000..f68e93329 --- /dev/null +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -0,0 +1,20 @@ +// eslint-disable-next-line camelcase +import { Injectable} from '@nestjs/common'; + +@Injectable() +export class EcosystemService { + constructor( + ) { } + + /** + * + * @param registerOrgDto + * @returns + */ + + // eslint-disable-next-line camelcase + async createEcosystem():Promise { + return "test"; + } + +} diff --git a/apps/ecosystem/src/main.ts b/apps/ecosystem/src/main.ts new file mode 100644 index 000000000..6bd3f0259 --- /dev/null +++ b/apps/ecosystem/src/main.ts @@ -0,0 +1,23 @@ +import { NestFactory } from '@nestjs/core'; +import { EcosystemModule } from './ecosystem.module'; +import { HttpExceptionFilter } from 'libs/http-exception.filter'; +import { Logger } from '@nestjs/common'; +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; + +const logger = new Logger(); + +async function bootstrap(): Promise { + + const app = await NestFactory.createMicroservice(EcosystemModule, { + transport: Transport.NATS, + options: { + servers: [`${process.env.NATS_URL}`] + } + }); + + app.useGlobalFilters(new HttpExceptionFilter()); + + await app.listen(); + logger.log('Ecosystem microservice is listening to NATS '); +} +bootstrap(); diff --git a/apps/ecosystem/test/app.e2e-spec.ts b/apps/ecosystem/test/app.e2e-spec.ts new file mode 100644 index 000000000..1f1f79169 --- /dev/null +++ b/apps/ecosystem/test/app.e2e-spec.ts @@ -0,0 +1,24 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { EcosystemModule } from './../src/ecosystem.module'; + +describe('EcosystemController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [EcosystemModule] + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/ (GET)', () => { + return request(app.getHttpServer()) + .get('/') + .expect(200) + .expect('Hello World!'); + }); +}); diff --git a/apps/ecosystem/test/jest-e2e.json b/apps/ecosystem/test/jest-e2e.json new file mode 100644 index 000000000..e9d912f3e --- /dev/null +++ b/apps/ecosystem/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/apps/ecosystem/tsconfig.app.json b/apps/ecosystem/tsconfig.app.json new file mode 100644 index 000000000..ac6e9e1bb --- /dev/null +++ b/apps/ecosystem/tsconfig.app.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": false, + "outDir": "../../dist/apps/ecosystem" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 4f9dd4b5e..3d16195bc 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -173,5 +173,14 @@ export const ResponseMessages = { platformConfigNotFound: 'Platform config not found', emailSend: 'Unable to send email to the user' } - } + }, + ecosystem: { + success: { + create: 'Ecosystem created successfully', + }, + error: { + + } + + }, }; \ No newline at end of file diff --git a/nest-cli.json b/nest-cli.json index 68dc6c90e..c4cae4482 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -232,6 +232,15 @@ "compilerOptions": { "tsConfigPath": "libs/supabase/tsconfig.lib.json" } + }, + "ecosystem": { + "type": "application", + "root": "apps/ecosystem", + "entryFile": "main", + "sourceRoot": "apps/ecosystem/src", + "compilerOptions": { + "tsConfigPath": "apps/ecosystem/tsconfig.app.json" + } } } } \ No newline at end of file From 3f79e32523494468d74f0e20b7d675422f1cc8c5 Mon Sep 17 00:00:00 2001 From: tipusinghaw <126460794+tipusinghaw@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:58:46 +0530 Subject: [PATCH 046/162] feat: ecosystem ms setup (#105) * worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat * merge dev branch to main (#77) * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw * feat:implemented add passke Signed-off-by: tipusinghaw * fix: login error message Signed-off-by: tipusinghaw --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw * Included credebl-master-table json file in the .gitignore Signed-off-by: @nishad.shirsat * Create ecosystem monorepo Signed-off-by: KulkarniShashank * feat: Implemented ecosystem microservice Signed-off-by: tipusinghaw * feat: changed controller name Signed-off-by: tipusinghaw * fix:changed return message Signed-off-by: tipusinghaw * setup ecosystem prisma schema Signed-off-by: Nishad --------- Signed-off-by: tipusinghaw Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank Signed-off-by: Nishad Co-authored-by: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Co-authored-by: @nishad.shirsat Co-authored-by: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Co-authored-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/ecosystem/src/ecosystem.service.ts | 2 +- .../migration.sql | 90 +++++++++++++++++++ libs/prisma-service/prisma/schema.prisma | 71 +++++++++++++++ 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 libs/prisma-service/prisma/migrations/20231002114401_ecosystem_setup/migration.sql diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index f68e93329..14795ec82 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -14,7 +14,7 @@ export class EcosystemService { // eslint-disable-next-line camelcase async createEcosystem():Promise { - return "test"; + return "test ecosystem"; } } diff --git a/libs/prisma-service/prisma/migrations/20231002114401_ecosystem_setup/migration.sql b/libs/prisma-service/prisma/migrations/20231002114401_ecosystem_setup/migration.sql new file mode 100644 index 000000000..0f1eed81d --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231002114401_ecosystem_setup/migration.sql @@ -0,0 +1,90 @@ +-- CreateTable +CREATE TABLE "ecosystem_roles" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_roles_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ecosystem" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT NOT NULL, + "tags" TEXT NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ecosystem_invitations" ( + "id" TEXT NOT NULL, + "email" TEXT NOT NULL, + "status" TEXT NOT NULL, + "ecosystemId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_invitations_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ecosystem_users" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "ecosystemId" TEXT NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_users_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ecosystem_orgs" ( + "id" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "status" TEXT NOT NULL, + "ecosystemId" TEXT NOT NULL, + "ecosystemRoleId" INTEGER NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_orgs_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "ecosystem_roles_name_key" ON "ecosystem_roles"("name"); + +-- AddForeignKey +ALTER TABLE "ecosystem_invitations" ADD CONSTRAINT "ecosystem_invitations_ecosystemId_fkey" FOREIGN KEY ("ecosystemId") REFERENCES "ecosystem"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ecosystem_users" ADD CONSTRAINT "ecosystem_users_ecosystemId_fkey" FOREIGN KEY ("ecosystemId") REFERENCES "ecosystem"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ecosystem_orgs" ADD CONSTRAINT "ecosystem_orgs_ecosystemId_fkey" FOREIGN KEY ("ecosystemId") REFERENCES "ecosystem"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ecosystem_orgs" ADD CONSTRAINT "ecosystem_orgs_ecosystemRoleId_fkey" FOREIGN KEY ("ecosystemRoleId") REFERENCES "ecosystem_roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 82c551246..9a6cf2497 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -307,3 +307,74 @@ model presentations { orgId Int organisation organisation @relation(fields: [orgId], references: [id]) } + +model ecosystem_roles { + id Int @id @default(autoincrement()) + name String @unique + description String + ecosystemOrgs ecosystem_orgs[] + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) +} + +model ecosystem { + id String @id @default(uuid()) + name String + description String + tags String + ecosystemOrgs ecosystem_orgs[] + ecosystemUsers ecosystem_users[] + ecosystemInvitations ecosystem_invitations[] + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) +} + +model ecosystem_invitations { + id String @id @default(uuid()) + email String + status String + ecosystemId String + userId String + orgId String + ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) +} + +model ecosystem_users { + id String @id @default(uuid()) // auto-increment + userId String + ecosystemId String + ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) +} + +model ecosystem_orgs { + id String @id @default(uuid()) // auto-increment + orgId String + status String + ecosystemId String + ecosystemRoleId Int + ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) + ecosystemRole ecosystem_roles @relation(fields: [ecosystemRoleId], references: [id]) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) + +} + From aa900901107d3e92459765b85bddc20730b84660 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Tue, 3 Oct 2023 18:18:54 +0530 Subject: [PATCH 047/162] Integrated ecosystem enable disable in config, master table entry for ecosystem roles (#113) Signed-off-by: Nishad Signed-off-by: tipusinghaw --- .../prisma/data/credebl-master-table.json | 15 ++++++++++++ .../migration.sql | 24 +++++++++++++++++++ .../migration.sql | 2 ++ libs/prisma-service/prisma/schema.prisma | 8 ++++--- libs/prisma-service/prisma/seed.ts | 14 +++++++++++ 5 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20231003080927_ecosystem_flag/migration.sql create mode 100644 libs/prisma-service/prisma/migrations/20231003114625_ecosystem_logo/migration.sql diff --git a/libs/prisma-service/prisma/data/credebl-master-table.json b/libs/prisma-service/prisma/data/credebl-master-table.json index 35a44c6c3..c1627506f 100644 --- a/libs/prisma-service/prisma/data/credebl-master-table.json +++ b/libs/prisma-service/prisma/data/credebl-master-table.json @@ -55,6 +55,21 @@ "description": "Joins the organization as member" } ], + "ecosystemRoleData": [ + { + "name": "Ecosystem Owner", + "description": "Ecosystem Owner" + }, + { + "name": "Ecosystem Lead", + "description": "Ecosystem Lead" + }, + { + "name": "Ecosystem Member", + "description": "Ecosystem Member" + } + ], + "agentTypeData": [ { "agent": "AFJ" diff --git a/libs/prisma-service/prisma/migrations/20231003080927_ecosystem_flag/migration.sql b/libs/prisma-service/prisma/migrations/20231003080927_ecosystem_flag/migration.sql new file mode 100644 index 000000000..418d645c7 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231003080927_ecosystem_flag/migration.sql @@ -0,0 +1,24 @@ +/* + Warnings: + + - The primary key for the `ecosystem_roles` table will be changed. If it partially fails, the table could be left without primary key constraint. + +*/ +-- DropForeignKey +ALTER TABLE "ecosystem_orgs" DROP CONSTRAINT "ecosystem_orgs_ecosystemRoleId_fkey"; + +-- AlterTable +ALTER TABLE "ecosystem_orgs" ALTER COLUMN "ecosystemRoleId" SET DATA TYPE TEXT; + +-- AlterTable +ALTER TABLE "ecosystem_roles" DROP CONSTRAINT "ecosystem_roles_pkey", +ALTER COLUMN "id" DROP DEFAULT, +ALTER COLUMN "id" SET DATA TYPE TEXT, +ADD CONSTRAINT "ecosystem_roles_pkey" PRIMARY KEY ("id"); +DROP SEQUENCE "ecosystem_roles_id_seq"; + +-- AlterTable +ALTER TABLE "platform_config" ADD COLUMN "enableEcosystem" BOOLEAN NOT NULL DEFAULT false; + +-- AddForeignKey +ALTER TABLE "ecosystem_orgs" ADD CONSTRAINT "ecosystem_orgs_ecosystemRoleId_fkey" FOREIGN KEY ("ecosystemRoleId") REFERENCES "ecosystem_roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/libs/prisma-service/prisma/migrations/20231003114625_ecosystem_logo/migration.sql b/libs/prisma-service/prisma/migrations/20231003114625_ecosystem_logo/migration.sql new file mode 100644 index 000000000..d4031a104 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231003114625_ecosystem_logo/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "ecosystem" ADD COLUMN "logoUrl" TEXT; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 9a6cf2497..4ba8e17e7 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -132,6 +132,7 @@ model platform_config { emailFrom String @db.VarChar apiEndpoint String @db.VarChar tailsFileServer String @db.VarChar + enableEcosystem Boolean @default(false) createDateTime DateTime @default(now()) @db.Timestamptz(6) createdBy Int @default(1) lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) @@ -309,7 +310,7 @@ model presentations { } model ecosystem_roles { - id Int @id @default(autoincrement()) + id String @id @default(uuid()) name String @unique description String ecosystemOrgs ecosystem_orgs[] @@ -324,6 +325,7 @@ model ecosystem { id String @id @default(uuid()) name String description String + logoUrl String? tags String ecosystemOrgs ecosystem_orgs[] ecosystemUsers ecosystem_users[] @@ -363,11 +365,11 @@ model ecosystem_users { } model ecosystem_orgs { - id String @id @default(uuid()) // auto-increment + id String @id @default(uuid()) orgId String status String ecosystemId String - ecosystemRoleId Int + ecosystemRoleId String ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) ecosystemRole ecosystem_roles @relation(fields: [ecosystemRoleId], references: [id]) createDateTime DateTime @default(now()) @db.Timestamptz(6) diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index 8b6b6fb51..cd50a00c6 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -113,6 +113,19 @@ const createLedger = async (): Promise => { } }; +const createEcosystemRoles = async (): Promise => { + try { + const { ecosystemRoleData } = JSON.parse(configData); + const ecosystemRoles = await prisma.ecosystem_roles.createMany({ + data: ecosystemRoleData + }); + + logger.log(ecosystemRoles); + } catch (e) { + logger.error('An error occurred seeding ecosystemRoles:', e); + } +}; + async function main(): Promise { await createPlatformConfig(); @@ -123,6 +136,7 @@ async function main(): Promise { await createPlatformUserOrgRoles(); await createOrgAgentTypes(); await createLedger(); + await createEcosystemRoles(); } From 3a5904b0bbb0f77b9257094a1feccb7bcea604fb Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Wed, 4 Oct 2023 13:17:24 +0530 Subject: [PATCH 048/162] feat: implemented enum Signed-off-by: tipusinghaw --- .../ecosystem/dtos/create-ecosystem-dto.ts | 46 +++++++ ...anization-dto.ts => edit-ecosystem-dto.ts} | 38 +++--- .../src/ecosystem/ecosystem.controller.ts | 40 +++++- .../src/ecosystem/ecosystem.service.ts | 21 +++- apps/api-gateway/src/enum.ts | 2 +- apps/ecosystem/enums/ecosystem.enum.ts | 4 + apps/ecosystem/src/ecosystem.controller.ts | 27 +++- apps/ecosystem/src/ecosystem.module.ts | 6 +- apps/ecosystem/src/ecosystem.repository.ts | 116 ++++++++++++++++++ apps/ecosystem/src/ecosystem.service.ts | 49 +++++++- libs/common/src/response-messages/index.ts | 5 +- 11 files changed, 315 insertions(+), 39 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts rename apps/api-gateway/src/ecosystem/dtos/{create-organization-dto.ts => edit-ecosystem-dto.ts} (56%) create mode 100644 apps/ecosystem/enums/ecosystem.enum.ts create mode 100644 apps/ecosystem/src/ecosystem.repository.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts b/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts new file mode 100644 index 000000000..2b66e7ee0 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts @@ -0,0 +1,46 @@ +import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsInt, IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; + +import { Transform } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; + +@ApiExtraModels() +export class CreateEcosystemDto { + + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'ecosystem name is required.' }) + @MinLength(2, { message: 'ecosystem name must be at least 2 characters.' }) + @MaxLength(50, { message: 'ecosystem name must be at most 50 characters.' }) + @IsString({ message: 'ecosystem name must be in string format.' }) + name: string; + + @ApiPropertyOptional() + @Transform(({ value }) => trim(value)) + @MinLength(2, { message: 'Description must be at least 2 characters.' }) + @MaxLength(255, { message: 'Description must be at most 255 characters.' }) + @IsString({ message: 'Description must be in string format.' }) + @IsOptional() + description?: string; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'tag must be in string format.' }) + tags?: string; + + @ApiPropertyOptional() + @IsInt({ message: 'orgId must be in number format.' }) + orgId: number; + + @ApiPropertyOptional() + @IsInt({ message: 'UserId must be in number format.' }) + userId: number; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'logo must be in string format.' }) + logo?: string; + } + \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts b/apps/api-gateway/src/ecosystem/dtos/edit-ecosystem-dto.ts similarity index 56% rename from apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts rename to apps/api-gateway/src/ecosystem/dtos/edit-ecosystem-dto.ts index d584c5832..96be3ba09 100644 --- a/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts +++ b/apps/api-gateway/src/ecosystem/dtos/edit-ecosystem-dto.ts @@ -1,36 +1,38 @@ -import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { ApiExtraModels, ApiPropertyOptional } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; @ApiExtraModels() -export class CreateEcosystemDto { - - @ApiProperty() +export class EditEcosystemDto { + @ApiPropertyOptional() @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'Organization name is required.' }) - @MinLength(2, { message: 'Organization name must be at least 2 characters.' }) - @MaxLength(50, { message: 'Organization name must be at most 50 characters.' }) - @IsString({ message: 'Organization name must be in string format.' }) - name: string; - + @IsOptional() + @IsNotEmpty({ message: 'Ecosystem name is required.' }) + @MinLength(2, { message: 'Ecosystem name must be at least 2 characters.' }) + @MaxLength(50, { message: 'Ecosystem name must be at most 50 characters.' }) + @IsString({ message: 'Ecosystem name must be in string format.' }) + name?: string; + @ApiPropertyOptional() @Transform(({ value }) => trim(value)) + @IsOptional() @MinLength(2, { message: 'Description must be at least 2 characters.' }) @MaxLength(255, { message: 'Description must be at most 255 characters.' }) @IsString({ message: 'Description must be in string format.' }) - description: string; - + description?: string; + @ApiPropertyOptional() @IsOptional() @Transform(({ value }) => trim(value)) - @IsString({ message: 'logo must be in string format.' }) - logo: string; - + @IsString({ message: 'tag must be in string format.' }) + tags?: string; + @ApiPropertyOptional() @IsOptional() @Transform(({ value }) => trim(value)) - website?: string; - -} \ No newline at end of file + @IsString({ message: 'logo must be in string format.' }) + logo?: 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 6cf86ee4f..88658779f 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -1,19 +1,20 @@ import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; -import { Controller, UseGuards, UseFilters } from '@nestjs/common'; +import { Controller, UseFilters, Put, Param, UseGuards } from '@nestjs/common'; import { EcosystemService } from './ecosystem.service'; -import { Post } from '@nestjs/common'; +import { Post, Get } from '@nestjs/common'; import { Body } from '@nestjs/common'; import { Res } from '@nestjs/common'; -import { CreateEcosystemDto } from './dtos/create-organization-dto'; +import { CreateEcosystemDto } from './dtos/create-ecosystem-dto'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { HttpStatus } from '@nestjs/common'; import { Response } from 'express'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; -import { AuthGuard } from '@nestjs/passport'; import { ResponseMessages } from '@credebl/common/response-messages'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; +import { EditEcosystemDto } from './dtos/edit-ecosystem-dto'; +import { AuthGuard } from '@nestjs/passport'; @UseFilters(CustomExceptionFilter) @@ -27,12 +28,27 @@ export class EcosystemController { ) { } + @Get('/') + @ApiOperation({ summary: 'Get all ecosystem', description: 'Get all existing ecosystem' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async getEcosystem(@Res() res: Response): Promise { + const ecosystemList = await this.ecosystemService.getAllEcosystem(); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.fetch, + data: ecosystemList.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + @Post('/') @ApiOperation({ summary: 'Create a new ecosystem', description: 'Create an ecosystem' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async createOrganization(@Body() createOrgDto: CreateEcosystemDto, @Res() res: Response): Promise { + async createNewEcosystem(@Body() createOrgDto: CreateEcosystemDto, @Res() res: Response): Promise { await this.ecosystemService.createEcosystem(createOrgDto); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -40,4 +56,18 @@ export class EcosystemController { }; return res.status(HttpStatus.CREATED).json(finalResponse); } + + @Put('/:ecosystemId/') + @ApiOperation({ summary: 'Edit ecosystem', description: 'Edit existing ecosystem' }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async editEcosystem(@Body() editEcosystemDto: EditEcosystemDto, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { + await this.ecosystemService.editEcosystem(editEcosystemDto, ecosystemId); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.update + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } } \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 4ce9cba9a..bd60b859e 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -20,4 +20,23 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'create-ecosystem', payload); } -} + /** + * + * @param editEcosystemDto + * @returns Ecosystem creation success + */ + async editEcosystem(editEcosystemDto, ecosystemId): Promise { + const payload = { editEcosystemDto, ecosystemId }; + return this.sendNats(this.serviceProxy, 'edit-ecosystem', payload); + } + + /** + * + * + * @returns Get all ecosystems + */ + async getAllEcosystem(): Promise<{ response: object }> { + return this.sendNats(this.serviceProxy, 'get-all-ecosystem', ''); + } + +} \ No newline at end of file diff --git a/apps/api-gateway/src/enum.ts b/apps/api-gateway/src/enum.ts index 0cfdecf91..7dd84939b 100644 --- a/apps/api-gateway/src/enum.ts +++ b/apps/api-gateway/src/enum.ts @@ -115,4 +115,4 @@ export enum ExpiredSubscriptionSortBy { startDate = 'startDate', endDate = 'endDate', id = 'id', -} \ No newline at end of file +} diff --git a/apps/ecosystem/enums/ecosystem.enum.ts b/apps/ecosystem/enums/ecosystem.enum.ts new file mode 100644 index 000000000..4ed11bcb4 --- /dev/null +++ b/apps/ecosystem/enums/ecosystem.enum.ts @@ -0,0 +1,4 @@ +export enum Ecosystem { + LEAD = 'Ecosystem Lead', + ACTIVE = 'ACTIVE' +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 55a91e22d..7c60d6ba3 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -16,9 +16,28 @@ export class EcosystemController { */ @MessagePattern({ cmd: 'create-ecosystem' }) - async createEcosystem(@Body() payload: { createOrgDto; userId }): Promise { - this.logger.log(`EcosystemPayload : ${payload}`); - return this.ecosystemService.createEcosystem(); + async createEcosystem(@Body() payload: { createEcosystemDto }): Promise { + return this.ecosystemService.createEcosystem(payload.createEcosystemDto); + } + + /** + * Description: edit ecosystem + * @param payload updation Details + * @returns Get updated ecosystem details + */ + @MessagePattern({ cmd: 'edit-ecosystem' }) + async editEcosystem(@Body() payload: { editEcosystemDto, ecosystemId }): Promise { + return this.ecosystemService.editEcosystem(payload.editEcosystemDto, payload.ecosystemId); + } + + /** + * Description: get all ecosystems + * @param payload Registration Details + * @returns Get all ecosystem details + */ + @MessagePattern({ cmd: 'get-all-ecosystem' }) + async getAllEcosystems(): Promise { + return this.ecosystemService.getAllEcosystem(); } -} +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.module.ts b/apps/ecosystem/src/ecosystem.module.ts index c749922c7..2d66da9d7 100644 --- a/apps/ecosystem/src/ecosystem.module.ts +++ b/apps/ecosystem/src/ecosystem.module.ts @@ -3,7 +3,7 @@ import { EcosystemController } from './ecosystem.controller'; import { EcosystemService } from './ecosystem.service'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { CommonModule } from '@credebl/common'; -// import { ConnectionRepository } from './connection.repository'; +import { EcosystemRepository } from './ecosystem.repository'; import { PrismaService } from '@credebl/prisma-service'; @Module({ @@ -21,6 +21,6 @@ import { PrismaService } from '@credebl/prisma-service'; CommonModule ], controllers: [EcosystemController], - providers: [EcosystemService, PrismaService, Logger] + providers: [EcosystemService, PrismaService, Logger, EcosystemRepository] }) -export class EcosystemModule { } +export class EcosystemModule { } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts new file mode 100644 index 000000000..7fa2aaafc --- /dev/null +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -0,0 +1,116 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PrismaService } from '@credebl/prisma-service'; +import { ecosystem } from '@prisma/client'; +import {Ecosystem} from '../enums/ecosystem.enum'; +// eslint-disable-next-line camelcase +@Injectable() +export class EcosystemRepository { + + constructor( + private readonly prisma: PrismaService, + private readonly logger: Logger + ) { } + + /** + * Description: Get getAgentEndPoint by orgId + * @param createEcosystemDto + * @returns Get getAgentEndPoint details + */ + // eslint-disable-next-line camelcase + async createNewEcosystem(createEcosystemDto): Promise { + try { + const transaction = await this.prisma.$transaction(async (prisma) => { + const { name, description, userId, logo, tags, orgId } = createEcosystemDto; + const createdEcosystem = await prisma.ecosystem.create({ + data: { + name, + description, + tags, + logoUrl: logo, + createdBy: orgId, + lastChangedBy: orgId + } + }); + let ecosystemUser; + if (createdEcosystem) { + ecosystemUser = await prisma.ecosystem_users.create({ + data: { + userId: String(userId), + ecosystemId: createdEcosystem.id, + createdBy: orgId, + lastChangedBy: orgId + } + }); + } + + if (ecosystemUser) { + const ecosystemRoleDetails = await this.prisma.ecosystem_roles.findFirst({ + where: { + name: Ecosystem.LEAD + } + }); + ecosystemUser = await prisma.ecosystem_orgs.create({ + data: { + orgId: String(orgId), + status: Ecosystem.ACTIVE, + ecosystemId: createdEcosystem.id, + ecosystemRoleId: ecosystemRoleDetails.id, + createdBy: orgId, + lastChangedBy: orgId + } + }); + } + return createdEcosystem; + }); + + return transaction; + } catch (error) { + this.logger.error(`Error in create ecosystem transaction: ${error.message}`); + throw error; + } + } + + /** + * Description: Edit ecosystem by Id + * @param editEcosystemDto + * @returns ecosystem details + */ + // eslint-disable-next-line camelcase + async updateEcosystemById(createEcosystemDto, ecosystemId): Promise { + try { + const { name, description, tags, logo } = createEcosystemDto; + const editEcosystem = await this.prisma.ecosystem.update({ + where: { id: ecosystemId }, + data: { + name, + description, + tags, + logoUrl: logo + } + }); + return editEcosystem; + } catch (error) { + this.logger.error(`Error in edit ecosystem transaction: ${error.message}`); + throw error; + } + } + + /** + * + * + * @returns Get all ecosystem details + */ + // eslint-disable-next-line camelcase + async getAllEcosystemDetails(): Promise { + try { + const ecosystemDetails = await this.prisma.ecosystem.findMany({ + }); + return ecosystemDetails; + } catch (error) { + this.logger.error(`Error in get all ecosystem transaction: ${error.message}`); + throw error; + } + } + + +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 14795ec82..0b7def360 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -1,20 +1,57 @@ // eslint-disable-next-line camelcase -import { Injectable} from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import { EcosystemRepository } from './ecosystem.repository'; +import { ResponseMessages } from '@credebl/common/response-messages'; @Injectable() export class EcosystemService { - constructor( + constructor( + private readonly ecosystemRepository: EcosystemRepository ) { } /** * - * @param registerOrgDto + * @param createEcosystemDto * @returns */ // eslint-disable-next-line camelcase - async createEcosystem():Promise { - return "test ecosystem"; + async createEcosystem(createEcosystemDto): Promise { + const createEcosystem = await this.ecosystemRepository.createNewEcosystem(createEcosystemDto); + if (!createEcosystem) { + throw new NotFoundException(ResponseMessages.ecosystem.error.update); + } + return createEcosystem; } -} + + /** + * + * @param editEcosystemDto + * @returns + */ + + // eslint-disable-next-line camelcase + async editEcosystem(editEcosystemDto, ecosystemId): Promise { + const editOrganization = await this.ecosystemRepository.updateEcosystemById(editEcosystemDto, ecosystemId); + if (!editOrganization) { + throw new NotFoundException(ResponseMessages.ecosystem.error.update); + } + return editOrganization; + } + + /** + * + * + * @returns all ecosystem details + */ + + // eslint-disable-next-line camelcase + async getAllEcosystem(): Promise { + const getAllEcosystemDetails = await this.ecosystemRepository.getAllEcosystemDetails(); + if (!getAllEcosystemDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.update); + } + return getAllEcosystemDetails; + } +} \ No newline at end of file diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 3d16195bc..23b4708d0 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -177,9 +177,12 @@ export const ResponseMessages = { ecosystem: { success: { create: 'Ecosystem created successfully', + update: 'Ecosystem updated successfully', + fetch: 'Ecosystem fetched successfully' }, error: { - + notCreated: 'Error while creating ecosystem', + update: 'Error while updating ecosystem' } }, From 6a806e79a9dec0a25ae2626ad2c884c21a5721c8 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Wed, 4 Oct 2023 14:01:06 +0530 Subject: [PATCH 049/162] fix: changed enum messages Signed-off-by: tipusinghaw --- apps/ecosystem/enums/ecosystem.enum.ts | 9 +++++++-- apps/ecosystem/src/ecosystem.repository.ts | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/ecosystem/enums/ecosystem.enum.ts b/apps/ecosystem/enums/ecosystem.enum.ts index 4ed11bcb4..6bf735879 100644 --- a/apps/ecosystem/enums/ecosystem.enum.ts +++ b/apps/ecosystem/enums/ecosystem.enum.ts @@ -1,4 +1,9 @@ -export enum Ecosystem { - LEAD = 'Ecosystem Lead', +export enum EcosystemRoles { + ECOSYSTEM_LEAD = 'Ecosystem Lead', + ECOSYSTEM_MEMBER = 'Ecosystem Member', + ECOSYSTEM_OWNER = 'Ecosystem Owner' +} + +export enum EcosystemOrgStatus { ACTIVE = 'ACTIVE' } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 7fa2aaafc..f7e7187b0 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; import { ecosystem } from '@prisma/client'; -import {Ecosystem} from '../enums/ecosystem.enum'; +import {EcosystemOrgStatus, EcosystemRoles} from '../enums/ecosystem.enum'; // eslint-disable-next-line camelcase @Injectable() export class EcosystemRepository { @@ -46,13 +46,13 @@ export class EcosystemRepository { if (ecosystemUser) { const ecosystemRoleDetails = await this.prisma.ecosystem_roles.findFirst({ where: { - name: Ecosystem.LEAD + name: EcosystemRoles.ECOSYSTEM_LEAD } }); ecosystemUser = await prisma.ecosystem_orgs.create({ data: { orgId: String(orgId), - status: Ecosystem.ACTIVE, + status: EcosystemOrgStatus.ACTIVE, ecosystemId: createdEcosystem.id, ecosystemRoleId: ecosystemRoleDetails.id, createdBy: orgId, From e32c3c608451c32f6edb6fc83b10232e90623eb2 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Thu, 5 Oct 2023 11:24:42 +0530 Subject: [PATCH 050/162] worked on the eslint issues (#117) Signed-off-by: Nishad --- .../src/ecosystem/dtos/send-invitation.dto.ts | 28 +++++ .../src/ecosystem/ecosystem.controller.ts | 35 ++++++ .../src/ecosystem/ecosystem.service.ts | 14 +++ apps/ecosystem/dtos/send-invitation.dto.ts | 12 ++ apps/ecosystem/enums/ecosystem.enum.ts | 6 + apps/ecosystem/src/ecosystem.controller.ts | 14 +++ apps/ecosystem/src/ecosystem.repository.ts | 79 ++++++++++++- apps/ecosystem/src/ecosystem.service.ts | 105 +++++++++++++++++- .../templates/EcosystemInviteTemplate.ts | 66 +++++++++++ libs/common/src/response-messages/index.ts | 4 +- 10 files changed, 357 insertions(+), 6 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/send-invitation.dto.ts create mode 100644 apps/ecosystem/dtos/send-invitation.dto.ts create mode 100644 apps/ecosystem/templates/EcosystemInviteTemplate.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/send-invitation.dto.ts b/apps/api-gateway/src/ecosystem/dtos/send-invitation.dto.ts new file mode 100644 index 000000000..a9246e7bc --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/send-invitation.dto.ts @@ -0,0 +1,28 @@ +import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsEmail, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; +import { Transform, Type } from 'class-transformer'; + +import { trim } from '@credebl/common/cast.helper'; + +@ApiExtraModels() +export class EcosystemInvitationDto { + + @ApiProperty({ example: 'acqx@getnada.com' }) + @IsEmail() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Please provide valid email' }) + @IsString({ message: 'email should be string' }) + email: string; + +} + +@ApiExtraModels() +export class BulkEcosystemInvitationDto { + + @ApiProperty({ type: [EcosystemInvitationDto] }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => EcosystemInvitationDto) + invitations: EcosystemInvitationDto[]; + ecosystemId: 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 88658779f..b05b607c1 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -15,6 +15,8 @@ import { ResponseMessages } from '@credebl/common/response-messages'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; import { EditEcosystemDto } from './dtos/edit-ecosystem-dto'; import { AuthGuard } from '@nestjs/passport'; +import { User } from '../authz/decorators/user.decorator'; +import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; @UseFilters(CustomExceptionFilter) @@ -57,6 +59,38 @@ export class EcosystemController { return res.status(HttpStatus.CREATED).json(finalResponse); } + + /** + * + * @param bulkInvitationDto + * @param ecosystemId + * @param user + * @param res + * @returns Ecosystem invitation send details + */ + @Post('/:ecosystemId/invitations') + @ApiOperation({ + summary: 'Send ecosystem invitation', + description: 'Send ecosystem invitation' + }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async createInvitation(@Body() bulkInvitationDto: BulkEcosystemInvitationDto, @Param('ecosystemId') ecosystemId: string, @User() user: user, @Res() res: Response): Promise { + + bulkInvitationDto.ecosystemId = ecosystemId; + await this.ecosystemService.createInvitation(bulkInvitationDto, String(user.id)); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.createInvitation + }; + + return res.status(HttpStatus.CREATED).json(finalResponse); + + } + + @Put('/:ecosystemId/') @ApiOperation({ summary: 'Edit ecosystem', description: 'Edit existing ecosystem' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @@ -70,4 +104,5 @@ export class EcosystemController { }; return res.status(HttpStatus.CREATED).json(finalResponse); } + } \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 760bc98b8..ff7b3702f 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -2,6 +2,7 @@ import { Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; +import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; @Injectable() @@ -38,5 +39,18 @@ export class EcosystemService extends BaseService { async getAllEcosystem(): Promise<{ response: object }> { return this.sendNats(this.serviceProxy, 'get-all-ecosystem', ''); } + + + /** + * + * @param bulkInvitationDto + * @param userId + * @returns + */ + async createInvitation(bulkInvitationDto: BulkEcosystemInvitationDto, userId: string): Promise { + const payload = { bulkInvitationDto, userId }; + return this.sendNats(this.serviceProxy, 'send-ecosystem-invitation', payload); + } + } diff --git a/apps/ecosystem/dtos/send-invitation.dto.ts b/apps/ecosystem/dtos/send-invitation.dto.ts new file mode 100644 index 000000000..476415ecd --- /dev/null +++ b/apps/ecosystem/dtos/send-invitation.dto.ts @@ -0,0 +1,12 @@ +import { ApiExtraModels } from '@nestjs/swagger'; + +@ApiExtraModels() +export class SendInvitationDto { + email: string; +} + +@ApiExtraModels() +export class BulkSendInvitationDto { + invitations: SendInvitationDto[]; + ecosystemId: string; +} \ No newline at end of file diff --git a/apps/ecosystem/enums/ecosystem.enum.ts b/apps/ecosystem/enums/ecosystem.enum.ts index 6bf735879..13d0022fb 100644 --- a/apps/ecosystem/enums/ecosystem.enum.ts +++ b/apps/ecosystem/enums/ecosystem.enum.ts @@ -6,4 +6,10 @@ export enum EcosystemRoles { export enum EcosystemOrgStatus { ACTIVE = 'ACTIVE' +} + +export enum EcosystemInvitationStatus { + ACCEPTED = 'accepted', + REJECTED = 'rejected', + PENDING = 'pending' } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index ddcb718c0..abbdd3149 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -3,6 +3,7 @@ import { Controller, Logger } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; import { EcosystemService } from './ecosystem.service'; import { Body } from '@nestjs/common'; +import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; @Controller() export class EcosystemController { @@ -39,5 +40,18 @@ export class EcosystemController { async getAllEcosystems(): Promise { return this.ecosystemService.getAllEcosystem(); } + + + /** + * + * @param payload + * @returns Sent ecosystem invitations status + */ + @MessagePattern({ cmd: 'send-ecosystem-invitation' }) + async createInvitation( + @Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: string } + ): Promise { + return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId); + } } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index f7e7187b0..db6c50e0a 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -1,7 +1,8 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; -import { ecosystem } from '@prisma/client'; -import {EcosystemOrgStatus, EcosystemRoles} from '../enums/ecosystem.enum'; +// eslint-disable-next-line camelcase +import { ecosystem, ecosystem_invitations } from '@prisma/client'; +import {EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles} from '../enums/ecosystem.enum'; // eslint-disable-next-line camelcase @Injectable() export class EcosystemRepository { @@ -112,5 +113,77 @@ export class EcosystemRepository { } } + /** + * + * @param ecosystemId + * @returns Get specific ecosystem details + */ + async getEcosystemDetails(ecosystemId: string): Promise { + try { + return this.prisma.ecosystem.findFirst({ + where: { + id: ecosystemId + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + + /** + * + * @param queryObject + * @returns Get all ecosystem invitations + */ + async getEcosystemInvitations( + queryObject: object + // eslint-disable-next-line camelcase + ): Promise { + try { + return this.prisma.ecosystem_invitations.findMany({ + where: { + ...queryObject + }, + include: { + ecosystem: true + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + + + /** + * + * @param email + * @param ecosystemId + * @param userId + * @returns + */ + async createSendInvitation( + email: string, + ecosystemId: string, + userId: string + // eslint-disable-next-line camelcase + ): Promise { + try { + return this.prisma.ecosystem_invitations.create({ + data: { + email, + userId, + ecosystem: {connect: {id: ecosystemId}}, + status: EcosystemInvitationStatus.PENDING, + orgId: '' + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index c10e1ada1..0b24abc4d 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -1,12 +1,21 @@ // eslint-disable-next-line camelcase -import { Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; import { EcosystemRepository } from './ecosystem.repository'; import { ResponseMessages } from '@credebl/common/response-messages'; +import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; +import { RpcException } from '@nestjs/microservices'; +import { PrismaService } from '@credebl/prisma-service'; +import { EcosystemInviteTemplate } from '../templates/EcosystemInviteTemplate'; +import { EmailDto } from '@credebl/common/dtos/email.dto'; +import { sendEmail } from '@credebl/common/send-grid-helper-file'; @Injectable() export class EcosystemService { constructor( - private readonly ecosystemRepository: EcosystemRepository + private readonly ecosystemRepository: EcosystemRepository, + private readonly logger: Logger, + private readonly prisma: PrismaService + ) { } /** @@ -54,4 +63,96 @@ export class EcosystemService { } return getAllEcosystemDetails; } + + + /** + * + * @param bulkInvitationDto + * @param userId + * @returns + */ + async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: string): Promise { + const { invitations, ecosystemId } = bulkInvitationDto; + + try { + const ecosystemDetails = await this.ecosystemRepository.getEcosystemDetails(ecosystemId); + + for (const invitation of invitations) { + const { email } = invitation; + + const isInvitationExist = await this.checkInvitationExist(email, ecosystemId); + + if (!isInvitationExist) { + await this.ecosystemRepository.createSendInvitation(email, ecosystemId, userId); + + try { + await this.sendInviteEmailTemplate(email, ecosystemDetails.name); + } catch (error) { + throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); + } + } + } + return ResponseMessages.ecosystem.success.createInvitation; + } catch (error) { + this.logger.error(`In send Invitation : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + + /** + * + * @param email + * @param ecosystemId + * @returns Returns boolean status for invitation + */ + async checkInvitationExist( + email: string, + ecosystemId: string + ): Promise { + try { + + const query = { + email, + ecosystemId + }; + + const invitations = await this.ecosystemRepository.getEcosystemInvitations(query); + + if (0 < invitations.length) { + return true; + } + return false; + } catch (error) { + throw new RpcException(error.response ? error.response : error); + } + } + + /** + * + * @param email + * @param ecosystemName + * @returns Send invitation mail + */ + async sendInviteEmailTemplate( + email: string, + ecosystemName: string + ): Promise { + const platformConfigData = await this.prisma.platform_config.findMany(); + + const urlEmailTemplate = new EcosystemInviteTemplate(); + const emailData = new EmailDto(); + emailData.emailFrom = platformConfigData[0].emailFrom; + emailData.emailTo = email; + emailData.emailSubject = `${process.env.PLATFORM_NAME} Platform: Invitation`; + + emailData.emailHtml = await urlEmailTemplate.sendInviteEmailTemplate(email, ecosystemName); + + //Email is sent to user for the verification through emailData + const isEmailSent = await sendEmail(emailData); + + return isEmailSent; + } + + } diff --git a/apps/ecosystem/templates/EcosystemInviteTemplate.ts b/apps/ecosystem/templates/EcosystemInviteTemplate.ts new file mode 100644 index 000000000..2f872654a --- /dev/null +++ b/apps/ecosystem/templates/EcosystemInviteTemplate.ts @@ -0,0 +1,66 @@ +export class EcosystemInviteTemplate { + + public sendInviteEmailTemplate( + email: string, + ecosystemName: string + ): string { + + const validUrl = `${process.env.FRONT_END_URL}/authentication/sign-in`; + + const message = `You have been invited to join the ecosystem so please log in and accept the ecosystem “INVITATION” and participate in the ecosystem`; + const year: number = new Date().getFullYear(); + + return ` + + + + + + + + + +
+ +
+

+ Hello ${email}, +

+

+ Congratulations! + Your have been successfully invited to join. +

    +
  • Ecosystem: ${ecosystemName}
  • +
+ ${message} + + +

In case you need any assistance to access your account, please contact CREDEBL Platform +

+
+
+

+ ® CREDEBL ${year}, Powered by Blockster Labs. All Rights Reserved. +

+
+
+
+ + + `; + + } + + +} \ No newline at end of file diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 23b4708d0..963338135 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -178,7 +178,9 @@ export const ResponseMessages = { success: { create: 'Ecosystem created successfully', update: 'Ecosystem updated successfully', - fetch: 'Ecosystem fetched successfully' + fetch: 'Ecosystem fetched successfully', + createInvitation: 'Ecosystem invitations sent successfully', + }, error: { notCreated: 'Error while creating ecosystem', From 53bddd2d49027d8c3cbedec93fbbf721d273e82f Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Wed, 30 Aug 2023 12:08:34 +0530 Subject: [PATCH 051/162] feat: Get Proof Presentation from Data with Credentials and Attributes (#66) * fix: Added the global exception error handling for api-gateway Signed-off-by: KulkarniShashank * Added the server for call the API locally Signed-off-by: KulkarniShashank * Removed qr-code in main.js Signed-off-by: KulkarniShashank * feat: Get proof presentation form data Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- .../src/agent-service.controller.ts | 5 + .../src/agent-service.service.ts | 12 ++ .../verification/verification.controller.ts | 34 +++- .../src/verification/verification.service.ts | 7 + .../src/interfaces/verification.interface.ts | 12 ++ .../src/verification.controller.ts | 8 +- apps/verification/src/verification.service.ts | 149 +++++++++++++++++- libs/common/src/common.constant.ts | 11 +- libs/common/src/response-messages/index.ts | 5 +- 9 files changed, 232 insertions(+), 11 deletions(-) diff --git a/apps/agent-service/src/agent-service.controller.ts b/apps/agent-service/src/agent-service.controller.ts index 42c140202..e85fcd6ba 100644 --- a/apps/agent-service/src/agent-service.controller.ts +++ b/apps/agent-service/src/agent-service.controller.ts @@ -101,4 +101,9 @@ export class AgentServiceController { async sendOutOfBandProofRequest(payload: { proofRequestPayload: ISendProofRequestPayload, url: string, apiKey: string }): Promise { return this.agentServiceService.sendOutOfBandProofRequest(payload.proofRequestPayload, payload.url, payload.apiKey); } + + @MessagePattern({ cmd: 'agent-proof-form-data' }) + async getProofFormData(payload: { url: string, apiKey: string }): Promise { + return this.agentServiceService.getProofFormData(payload.url, payload.apiKey); + } } diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 0af735840..b1a818b26 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -875,5 +875,17 @@ export class AgentServiceService { throw new RpcException(error); } } + + async getProofFormData(url: string, apiKey: string): Promise { + try { + const getProofFormData = await this.commonService + .httpGet(url, { headers: { 'x-api-key': apiKey } }) + .then(async response => response); + return getProofFormData; + } catch (error) { + this.logger.error(`Error in get proof form data in agent service : ${JSON.stringify(error)}`); + throw new RpcException(error); + } + } } diff --git a/apps/api-gateway/src/verification/verification.controller.ts b/apps/api-gateway/src/verification/verification.controller.ts index 503223bb3..328bd2540 100644 --- a/apps/api-gateway/src/verification/verification.controller.ts +++ b/apps/api-gateway/src/verification/verification.controller.ts @@ -38,6 +38,38 @@ export class VerificationController { private readonly logger = new Logger('VerificationController'); + @Get('/proofs/form-data') + @ApiTags('verifications') + @ApiOperation({ + summary: `Get a proof form data`, + description: `Get a proof form data` + }) + @ApiQuery( + { name: 'id', required: true } + ) + @ApiQuery( + { name: 'orgId', required: true } + ) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + async getProofFormData( + @Res() res: Response, + @GetUser() user: IUserRequest, + @Query('id') id: string, + @Query('orgId') orgId: number + ): Promise { + const sendProofRequest = await this.verificationService.getProofFormData(id, orgId, user); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.verification.success.proofFormData, + data: sendProofRequest.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + /** * Get all proof presentations * @param user @@ -138,7 +170,7 @@ export class VerificationController { const sendProofRequest = await this.verificationService.sendProofRequest(requestProof, user); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, - message: ResponseMessages.verification.success.fetch, + message: ResponseMessages.verification.success.send, data: sendProofRequest.response }; 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 c8b6ed973..6a72f78a7 100644 --- a/apps/api-gateway/src/verification/verification.service.ts +++ b/apps/api-gateway/src/verification/verification.service.ts @@ -75,4 +75,11 @@ export class VerificationService extends BaseService { const payload = { outOfBandRequestProof, user }; return this.sendNats(this.verificationServiceProxy, 'send-out-of-band-proof-request', payload); } + + + getProofFormData(id: string, orgId: number, user: IUserRequest): Promise<{ response: object }> { + const payload = { id, orgId, user }; + return this.sendNats(this.verificationServiceProxy, 'proof-form-data', payload); + } + } diff --git a/apps/verification/src/interfaces/verification.interface.ts b/apps/verification/src/interfaces/verification.interface.ts index 45b7dad1b..4f25db0f4 100644 --- a/apps/verification/src/interfaces/verification.interface.ts +++ b/apps/verification/src/interfaces/verification.interface.ts @@ -1,3 +1,4 @@ +import { IUserRequest } from "@credebl/user-request/user-request.interface"; interface IProofRequestAttribute { attributeName: string; @@ -33,6 +34,17 @@ export interface IVerifyPresentation { apiKey: string; } +export interface ProofFormDataPayload { + url: string; + apiKey: string; +} + +export interface ProofFormData { + id: string; + orgId: number; + user: IUserRequest; +} + interface IProofFormats { indy: IndyProof } diff --git a/apps/verification/src/verification.controller.ts b/apps/verification/src/verification.controller.ts index 65dd1d852..2ad7f1d62 100644 --- a/apps/verification/src/verification.controller.ts +++ b/apps/verification/src/verification.controller.ts @@ -1,7 +1,7 @@ import { Controller } from '@nestjs/common'; import { VerificationService } from './verification.service'; import { MessagePattern } from '@nestjs/microservices'; -import { IRequestProof, IWebhookProofPresentation } from './interfaces/verification.interface'; +import { IRequestProof, IWebhookProofPresentation, ProofFormData } from './interfaces/verification.interface'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { presentations } from '@prisma/client'; @@ -58,4 +58,10 @@ export class VerificationController { async sendOutOfBandPresentationRequest(payload: { outOfBandRequestProof: IRequestProof, user: IUserRequest }): Promise { return this.verificationService.sendOutOfBandPresentationRequest(payload.outOfBandRequestProof); } + + @MessagePattern({ cmd: 'proof-form-data' }) + async getProofFormData(payload: ProofFormData): Promise { + const { id, orgId } = payload; + return this.verificationService.getProofFormData(id, orgId); + } } diff --git a/apps/verification/src/verification.service.ts b/apps/verification/src/verification.service.ts index 36a0e38d3..c0b57c04f 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, IWebhookProofPresentation } from './interfaces/verification.interface'; +import { IGetAllProofPresentations, IGetProofPresentationById, IProofRequestPayload, IRequestProof, ISendProofRequestPayload, IVerifyPresentation, IWebhookProofPresentation, ProofFormDataPayload } from './interfaces/verification.interface'; import { VerificationRepository } from './repositories/verification.repository'; import { CommonConstants } from '@credebl/common/common.constant'; import { presentations } from '@prisma/client'; @@ -238,7 +238,7 @@ export class VerificationService { }; } - return [attributeReferent, null]; + return [attributeReferent]; })); } else { throw new BadRequestException(ResponseMessages.verification.error.schemaIdNotFound); @@ -534,7 +534,6 @@ export class VerificationService { try { let requestedAttributes = {}; const requestedPredicates = {}; - const attributeWithSchemaIdExists = proofRequestpayload.attributes.some(attribute => attribute.schemaId); if (attributeWithSchemaIdExists) { @@ -690,6 +689,15 @@ export class VerificationService { break; } + case 'proof-form-data': { + url = orgAgentTypeId === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_PROOF_FORM_DATA}` + : orgAgentTypeId === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_PROOF_FORM_DATA}`.replace('@', proofPresentationId).replace('#', tenantId) + : null; + break; + } + default: { break; } @@ -706,4 +714,139 @@ export class VerificationService { } } + + async getProofFormData(id: string, orgId: number): Promise { + try { + const getAgentDetails = await this.verificationRepository.getAgentEndPoint(orgId); + const verificationMethodLabel = 'proof-form-data'; + + const url = await this.getAgentUrl(verificationMethodLabel, getAgentDetails?.orgAgentTypeId, getAgentDetails?.agentEndPoint, getAgentDetails?.tenantId, '', id); + + const payload = { apiKey: '', url }; + const getProofPresentationById = await this._getProofFormData(payload); + + if (!getProofPresentationById?.response?.presentation) { + throw new NotFoundException("Proof presentation not found!"); + } + + const requestedAttributes = getProofPresentationById?.response?.request?.indy?.requested_attributes; + const requestedPredicates = getProofPresentationById?.response?.request?.indy?.requested_predicates; + const revealedAttrs = getProofPresentationById?.response?.presentation?.indy?.requested_proof?.revealed_attrs; + + const extractedDataArray = []; + + if (requestedAttributes && requestedPredicates) { + + 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; + + if (revealedAttrs.hasOwnProperty(key)) { + const extractedData = { + [attributeName]: revealedAttrs[key]?.raw, + "credDefId": credDefId ? credDefId : null, + "schemaId": schemaId ? schemaId : null + }; + extractedDataArray.push(extractedData); + } + } + } + + for (const key in requestedPredicates) { + if (requestedPredicates.hasOwnProperty(key)) { + const attribute = requestedPredicates[key]; + const attributeName = attribute?.name; + const credDefId = attribute?.restrictions[0]?.cred_def_id; + const schemaId = attribute?.restrictions[0]?.schema_id; + + const extractedData = { + [attributeName]: `${attribute?.p_type}${attribute?.p_value}`, + "credDefId": credDefId ? credDefId : null, + "schemaId": schemaId ? schemaId : null + }; + extractedDataArray.push(extractedData); + } + } + + } else if (requestedAttributes) { + 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; + + if (revealedAttrs.hasOwnProperty(key)) { + const extractedData = { + [attributeName]: revealedAttrs[key]?.raw, + "credDefId": credDefId ? credDefId : null, + "schemaId": schemaId ? schemaId : null + }; + extractedDataArray.push(extractedData); + } + } + } + } else if (requestedPredicates) { + for (const key in requestedPredicates) { + + if (requestedPredicates.hasOwnProperty(key)) { + const attribute = requestedPredicates[key]; + const attributeName = attribute?.name; + const credDefId = attribute?.restrictions[0]?.cred_def_id; + const schemaId = attribute?.restrictions[0]?.schema_id; + + const extractedData = { + [attributeName]: `${requestedPredicates?.p_type}${requestedPredicates?.p_value}`, + "credDefId": credDefId ? credDefId : null, + "schemaId": schemaId ? schemaId : null + }; + extractedDataArray.push(extractedData); + } + } + } else { + throw new InternalServerErrorException('Something went wrong!'); + } + + return extractedDataArray; + } catch (error) { + this.logger.error(`[getProofFormData] - error in get proof form data : ${JSON.stringify(error)}`); + throw new RpcException(error); + } + } + + async _getProofFormData(payload: ProofFormDataPayload): Promise<{ + response; + }> { + try { + + const pattern = { + cmd: 'agent-proof-form-data' + }; + + 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); + }); + } catch (error) { + this.logger.error(`[_getProofFormData] - error in proof form data : ${JSON.stringify(error)}`); + throw error; + } + } } diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 20a626de5..01b366bd6 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -67,10 +67,10 @@ export enum CommonConstants { URL_PUBLISH_REVOCATION = '/issue-credential/publish-revocations', URL_CREATE_ISSUE_CREDENTIAL_OUT_OF_BAND = '/issue-credential/create', URL_CREATE_OUT_OF_BAND_INVITATION = '/out-of-band/create-invitation', - URL_ISSUE_CREATE_CRED_OFFER_AFJ= '/credentials/create-offer', + URL_ISSUE_CREATE_CRED_OFFER_AFJ = '/credentials/create-offer', // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values - URL_ISSUE_GET_CREDS_AFJ= '/credentials', - URL_ISSUE_GET_CREDS_AFJ_BY_CRED_REC_ID= '/credentials', + URL_ISSUE_GET_CREDS_AFJ = '/credentials', + URL_ISSUE_GET_CREDS_AFJ_BY_CRED_REC_ID = '/credentials', // SCHEMA & CRED DEF SERVICES URL_SCHM_CREATE_SCHEMA = '/schemas', @@ -95,13 +95,16 @@ export enum CommonConstants { URL_SHAGENT_REQUEST_PROOF = '/multi-tenancy/proofs/request-proof/#', URL_SHAGENT_ACCEPT_PRESENTATION = '/multi-tenancy/proofs/@/accept-presentation/#', URL_SHAGENT_OUT_OF_BAND_CREATE_REQUEST = '/multi-tenancy/proofs/create-request-oob/#', + URL_SHAGENT_PROOF_FORM_DATA = '/multi-tenancy/form-data/#/@', // PROOF SERVICES URL_SEND_PROOF_REQUEST = '/proofs/request-proof', URL_GET_PROOF_PRESENTATIONS = '/proofs', URL_GET_PROOF_PRESENTATION_BY_ID = '/proofs/#', URL_VERIFY_PRESENTATION = '/proofs/#/accept-presentation', - URL_SEND_OUT_OF_BAND_CREATE_REQUEST='/proofs/create-request-oob', + URL_SEND_OUT_OF_BAND_CREATE_REQUEST = '/proofs/create-request-oob', + URL_PROOF_FORM_DATA = '/proofs/#/form-data', + // server or agent URL_SERVER_STATUS = '/status', diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index f39474126..2d3a95f64 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -106,7 +106,7 @@ export const ResponseMessages = { error: { NotFound: 'No credential definitions found.', NotSaved: 'Error in saving credential definition.', - Conflict: 'Tag already exists', + Conflict: 'Credential definition already exists', schemaIdNotFound: 'SchemaLedgerId not found', OrgDidNotFound: 'OrgDid not found', credDefIdNotFound: 'Credential Definition Id not found' @@ -156,7 +156,8 @@ export const ResponseMessages = { verification: { success: { fetch: 'Proof presentation received successfully.', - create: 'Proof request created successfully.', + proofFormData: 'Proof presentation form data received successfully.', + send: 'Proof request send successfully.', verified: 'Proof presentation verified successfully.' }, error: { From 0e4ae4c32854081c9fbabd15f973ebed44ca1e2a Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Fri, 1 Sep 2023 19:17:30 +0530 Subject: [PATCH 052/162] fix: Changed the passkey approch Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/api-gateway/src/user/user.controller.ts | 175 ++++++++++-------- .../repositories/user-device.repository.ts | 24 +++ apps/user/src/user.module.ts | 4 +- apps/user/src/user.service.ts | 47 +++-- .../migration.sql | 2 + libs/prisma-service/prisma/schema.prisma | 1 + 6 files changed, 160 insertions(+), 93 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20230831142029_user_device_password/migration.sql diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 345922989..bc79004a6 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -76,77 +76,77 @@ export class UserController { * @param res * @returns Users list of organization */ -@Get() -@Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) -@ApiBearerAuth() -@UseGuards(AuthGuard('jwt'), OrgRolesGuard) -@ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) -@ApiOperation({ summary: 'Get organization users list', description: 'Get organization users list.' }) -@ApiQuery({ - name: 'pageNumber', - type: Number, - required: false -}) -@ApiQuery({ - name: 'pageSize', - type: Number, - required: false -}) -@ApiQuery({ - name: 'search', - type: String, - required: false -}) -async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Query('orgId') orgId: number, @Res() res: Response): Promise { - - const org = user.selectedOrg?.orgId; - const users = await this.userService.getOrgUsers(org, getAllUsersDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchUsers, - data: users.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); -} - - -/** - * - * @param user - * @param orgId - * @param res - * @returns Users list of organization - */ -@Get('/public') -@ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) -@ApiOperation({ summary: 'Get users list', description: 'Get users list.' }) -@ApiQuery({ - name: 'pageNumber', - type: Number, - required: false -}) -@ApiQuery({ - name: 'pageSize', - type: Number, - required: false -}) -@ApiQuery({ - name: 'search', - type: String, - required: false -}) -async get(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Res() res: Response): Promise { - - const users = await this.userService.get(getAllUsersDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchUsers, - data: users.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); -} + @Get() + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Get organization users list', description: 'Get organization users list.' }) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Query('orgId') orgId: number, @Res() res: Response): Promise { + + const org = user.selectedOrg?.orgId; + const users = await this.userService.getOrgUsers(org, getAllUsersDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.fetchUsers, + data: users.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } + + + /** + * + * @param user + * @param orgId + * @param res + * @returns Users list of organization + */ + @Get('/public') + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Get users list', description: 'Get users list.' }) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async get(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Res() res: Response): Promise { + + const users = await this.userService.get(getAllUsersDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.fetchUsers, + data: users.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } /** @@ -328,21 +328,34 @@ async get(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUs @Post('/add/:email') @ApiOperation({ summary: 'Add user information', description: 'Add user information' }) async addUserDetailsInKeyCloak(@Body() userInfo: AddUserDetails, @Param('email') email: string, @Res() res: Response): Promise { - const decryptedPassword = this.commonService.decryptPassword(userInfo.password); - if (8 <= decryptedPassword.length && 50 >= decryptedPassword.length) { - this.commonService.passwordValidation(decryptedPassword); - userInfo.password = decryptedPassword; - const userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); - const finalResponse: IResponseType = { + let finalResponse; + let userDetails; + + if (false === userInfo.isPasskey) { + + const decryptedPassword = this.commonService.decryptPassword(userInfo.password); + if (8 <= decryptedPassword.length && 50 >= decryptedPassword.length) { + this.commonService.passwordValidation(decryptedPassword); + userInfo.password = decryptedPassword; + userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); + finalResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userDetails.response + }; + } else { + throw new BadRequestException('Password name must be between 8 to 50 Characters'); + } + } else { + + userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); + finalResponse = { statusCode: HttpStatus.CREATED, message: ResponseMessages.user.success.create, data: userDetails.response }; - return res.status(HttpStatus.OK).json(finalResponse); - - } else { - throw new BadRequestException('Password name must be between 8 to 50 Characters'); } + return res.status(HttpStatus.OK).json(finalResponse); } diff --git a/apps/user/repositories/user-device.repository.ts b/apps/user/repositories/user-device.repository.ts index 5e4422905..9d289f5a5 100644 --- a/apps/user/repositories/user-device.repository.ts +++ b/apps/user/repositories/user-device.repository.ts @@ -286,5 +286,29 @@ async addCredentialIdAndNameById(id:number, credentialId:string, deviceFriendlyN throw new InternalServerErrorException(error); } } + +/** + * + * @param password + * @param userId + * @returns Update password + */ +async updateUserDeviceDetails(password: string, userId: number): Promise { + try { + return await this.prisma.user_devices.updateMany({ + where: { + userId + }, + data: { + password + } + }); + + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } +} + } diff --git a/apps/user/src/user.module.ts b/apps/user/src/user.module.ts index 42362c279..865dc6e04 100644 --- a/apps/user/src/user.module.ts +++ b/apps/user/src/user.module.ts @@ -16,6 +16,7 @@ import { UserOrgRolesRepository } from 'libs/user-org-roles/repositories'; import { UserOrgRolesService } from '@credebl/user-org-roles'; import { UserRepository } from '../repositories/user.repository'; import { UserService } from './user.service'; +import { UserDevicesRepository } from '../repositories/user-device.repository'; @Module({ imports: [ @@ -46,7 +47,8 @@ import { UserService } from './user.service'; OrgRolesRepository, UserOrgRolesRepository, UserActivityService, - UserActivityRepository + UserActivityRepository, + UserDevicesRepository ] }) export class UserModule {} diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index b9e42fcd4..5d1358549 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -31,7 +31,8 @@ import { InvitationsI, UpdateUserProfile, UserEmailVerificationDto, userInfo } f import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; - +import { UserDevicesRepository } from '../repositories/user-device.repository'; +import { v4 as uuidv4 } from 'uuid'; @Injectable() export class UserService { @@ -44,6 +45,7 @@ export class UserService { private readonly userOrgRoleService: UserOrgRolesService, private readonly userActivityService: UserActivityService, private readonly userRepository: UserRepository, + private readonly userDevicesRepository: UserDevicesRepository, private readonly logger: Logger, @Inject('NATS_CLIENT') private readonly userServiceProxy: ClientProxy ) { } @@ -174,10 +176,35 @@ export class UserService { if (!userDetails) { throw new NotFoundException(ResponseMessages.user.error.adduser); } - const supaUser = await this.supabaseService.getClient().auth.signUp({ - email, - password: userInfo.password - }); + + let supaUser; + + if (userInfo.isPasskey) { + const password: string = uuidv4(); + + supaUser = await this.supabaseService.getClient().auth.signUp({ + email, + password + }); + + if (supaUser.error) { + throw new InternalServerErrorException(supaUser.error?.message); + } + + const getUserDetails = await this.userRepository.getUserDetails(userDetails.email); + await this.userDevicesRepository.updateUserDeviceDetails( + password, + getUserDetails.id + ); + + } else { + + + supaUser = await this.supabaseService.getClient().auth.signUp({ + email, + password: userInfo.password + }); + } if (supaUser.error) { throw new InternalServerErrorException(supaUser.error?.message); @@ -225,9 +252,9 @@ export class UserService { } if (true === isPasskey && userData?.username && true === userData?.isFidoVerified) { - - return this.generateToken(email, password); - + const getUserDetails = await this.userRepository.getUserDetails(userData.email); + const getFidoUserPassword = await this.userDevicesRepository.checkUserDevice(getUserDetails.id); + return this.generateToken(email, getFidoUserPassword.password); } return this.generateToken(email, password); @@ -238,6 +265,7 @@ export class UserService { } async generateToken(email: string, password: string): Promise { + const supaInstance = await this.supabaseService.getClient(); this.logger.error(`supaInstance::`, supaInstance); @@ -246,7 +274,6 @@ export class UserService { email, password }); - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); if (error) { @@ -254,11 +281,9 @@ export class UserService { } const token = data?.session; - return token; } - async getProfile(payload: { id }): Promise { try { return this.userRepository.getUserById(payload.id); diff --git a/libs/prisma-service/prisma/migrations/20230831142029_user_device_password/migration.sql b/libs/prisma-service/prisma/migrations/20230831142029_user_device_password/migration.sql new file mode 100644 index 000000000..de04d62e7 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20230831142029_user_device_password/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "user_devices" ADD COLUMN "password" VARCHAR; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 23fb8d161..3fe696cb3 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -119,6 +119,7 @@ model user_devices { userId Int? deletedAt DateTime? @db.Timestamp(6) authCounter Int @default(0) + password String? @db.VarChar user user? @relation(fields: [userId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_e12ac4f8016243ac71fd2e415af") } From 42414358a858f1a35f3771f3d3fb276ff8477bf9 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Wed, 6 Sep 2023 23:27:52 +0530 Subject: [PATCH 053/162] feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw --- apps/api-gateway/src/user/dto/add-user.dto.ts | 8 ++ apps/api-gateway/src/user/user.controller.ts | 18 +++- apps/api-gateway/src/user/user.service.ts | 7 ++ apps/user/interfaces/user.interface.ts | 4 + .../repositories/user-device.repository.ts | 23 ----- apps/user/repositories/user.repository.ts | 23 +++++ apps/user/src/user.controller.ts | 7 +- apps/user/src/user.service.ts | 87 ++++++++++++------- libs/common/src/response-messages/index.ts | 3 +- 9 files changed, 122 insertions(+), 58 deletions(-) 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 51a78d2b6..a72eb45eb 100644 --- a/apps/api-gateway/src/user/dto/add-user.dto.ts +++ b/apps/api-gateway/src/user/dto/add-user.dto.ts @@ -25,3 +25,11 @@ export class AddUserDetails { @IsBoolean({ message: 'isPasskey should be boolean' }) isPasskey?: boolean; } + +export class AddPasskeyDetails { + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Password is required.' }) + password: string; + +} diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index bc79004a6..0677ad8eb 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -39,7 +39,7 @@ import { OrgRoles } from 'libs/org-roles/enums'; import { IUserRequestInterface } from './interfaces'; import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; import { GetAllUsersDto } from './dto/get-all-users.dto'; -import { AddUserDetails } from './dto/add-user.dto'; +import { AddPasskeyDetails, AddUserDetails } from './dto/add-user.dto'; import { UpdateUserProfileDto } from './dto/update-user-profile.dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @@ -399,4 +399,20 @@ export class UserController { return res.status(HttpStatus.OK).json(finalResponse); } + + @Post('/password/:email') + @ApiOperation({ summary: 'Add user information', description: 'Add user information' }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async addPasskey(@Body() userInfo: AddPasskeyDetails, @Param('email') email: string, @Res() res: Response): Promise { + const userDetails = await this.userService.addPasskey(email, userInfo); + const finalResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userDetails.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } } \ No newline at end of file diff --git a/apps/api-gateway/src/user/user.service.ts b/apps/api-gateway/src/user/user.service.ts index fbc39fd23..6dfa2b0f5 100644 --- a/apps/api-gateway/src/user/user.service.ts +++ b/apps/api-gateway/src/user/user.service.ts @@ -9,6 +9,8 @@ import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; import { AddUserDetails } from './dto/login-user.dto'; import { GetAllUsersDto } from './dto/get-all-users.dto'; import { UpdateUserProfileDto } from './dto/update-user-profile.dto'; +import { AddPasskeyDetails } from './dto/add-user.dto'; + @Injectable() export class UserService extends BaseService { @@ -125,4 +127,9 @@ export class UserService extends BaseService { const payload = { userId, limit }; return this.sendNats(this.serviceProxy, 'get-user-activity', payload); } + + async addPasskey(userEmail: string, userInfo:AddPasskeyDetails): Promise<{ response: string }> { + const payload = { userEmail, userInfo }; + return this.sendNats(this.serviceProxy, 'add-passkey', payload); + } } diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index b93da4280..051ba3bfb 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -35,6 +35,10 @@ export interface userInfo{ isPasskey: boolean } +export interface AddPasskeyDetails{ + password: string, +} + export interface UserWhereUniqueInput{ id?: number } diff --git a/apps/user/repositories/user-device.repository.ts b/apps/user/repositories/user-device.repository.ts index 9d289f5a5..9e3c559d2 100644 --- a/apps/user/repositories/user-device.repository.ts +++ b/apps/user/repositories/user-device.repository.ts @@ -287,28 +287,5 @@ async addCredentialIdAndNameById(id:number, credentialId:string, deviceFriendlyN } } -/** - * - * @param password - * @param userId - * @returns Update password - */ -async updateUserDeviceDetails(password: string, userId: number): Promise { - try { - return await this.prisma.user_devices.updateMany({ - where: { - userId - }, - data: { - password - } - }); - - } catch (error) { - this.logger.error(`Not Found: ${JSON.stringify(error)}`); - throw new NotFoundException(error); - } -} - } diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 860e4a784..cfdfc92e7 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -452,4 +452,27 @@ export class UserRepository { } } + /** + * + * @param userInfo + * @returns Updates user credentials + */ + // eslint-disable-next-line camelcase + async addUserPassword(email: string, userInfo: string): Promise { + try { + const updateUserDetails = await this.prisma.user.update({ + where: { + email + }, + data: { + password: userInfo + } + }); + return updateUserDetails; + } catch (error) { + this.logger.error(`Error in update isEmailVerified: ${error.message} `); + throw error; + } + } + } diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 952e84dcb..629bb9dcf 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -5,7 +5,7 @@ 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 { UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; +import { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; @Controller() @@ -116,4 +116,9 @@ export class UserController { return this.userService.getUserActivity(payload.userId, payload.limit); } + @MessagePattern({ cmd: 'add-passkey' }) + async addPasskey(payload: { userEmail: string, userInfo: AddPasskeyDetails }): Promise { + return this.userService.addPasskey(payload.userEmail, payload.userInfo); + } + } diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 5d1358549..596b3bd31 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -27,12 +27,12 @@ import { sendEmail } from '@credebl/common/send-grid-helper-file'; import { user } from '@prisma/client'; import { Inject } from '@nestjs/common'; import { HttpException } from '@nestjs/common'; -import { InvitationsI, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; +import { AddPasskeyDetails, InvitationsI, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; import { UserDevicesRepository } from '../repositories/user-device.repository'; -import { v4 as uuidv4 } from 'uuid'; + @Injectable() export class UserService { @@ -58,7 +58,7 @@ export class UserService { async sendVerificationMail(userEmailVerificationDto: UserEmailVerificationDto): Promise { try { const userDetails = await this.userRepository.checkUserExist(userEmailVerificationDto.email); - + if (userDetails && userDetails.isEmailVerified) { throw new ConflictException(ResponseMessages.user.error.exists); } @@ -180,26 +180,18 @@ export class UserService { let supaUser; if (userInfo.isPasskey) { - const password: string = uuidv4(); - + const resUser = await this.userRepository.addUserPassword(email, userInfo.password); + const userDetails = await this.userRepository.getUserDetails(email); + const decryptedPassword = await this.commonService.decryptPassword(userDetails.password); + if (!resUser) { + throw new NotFoundException(ResponseMessages.user.error.invalidEmail); + } supaUser = await this.supabaseService.getClient().auth.signUp({ email, - password + password: decryptedPassword }); - if (supaUser.error) { - throw new InternalServerErrorException(supaUser.error?.message); - } - - const getUserDetails = await this.userRepository.getUserDetails(userDetails.email); - await this.userDevicesRepository.updateUserDeviceDetails( - password, - getUserDetails.id - ); - } else { - - supaUser = await this.supabaseService.getClient().auth.signUp({ email, password: userInfo.password @@ -227,6 +219,33 @@ export class UserService { } } + async addPasskey(email: string, userInfo: AddPasskeyDetails): Promise { + try { + if (!email) { + throw new UnauthorizedException(ResponseMessages.user.error.invalidEmail); + } + const checkUserDetails = await this.userRepository.getUserDetails(email); + if (!checkUserDetails) { + throw new NotFoundException(ResponseMessages.user.error.invalidEmail); + } + if (!checkUserDetails.supabaseUserId) { + throw new ConflictException(ResponseMessages.user.error.notFound); + } + if (false === checkUserDetails.isEmailVerified) { + throw new NotFoundException(ResponseMessages.user.error.emailNotVerified); + } + const resUser = await this.userRepository.addUserPassword(email, userInfo.password); + if (!resUser) { + throw new NotFoundException(ResponseMessages.user.error.invalidEmail); + } + + return 'User updated successfully'; + } catch (error) { + this.logger.error(`Error in createUserForToken: ${JSON.stringify(error)}`); + throw new RpcException(error.response); + } + } + /** * @@ -253,8 +272,8 @@ export class UserService { if (true === isPasskey && userData?.username && true === userData?.isFidoVerified) { const getUserDetails = await this.userRepository.getUserDetails(userData.email); - const getFidoUserPassword = await this.userDevicesRepository.checkUserDevice(getUserDetails.id); - return this.generateToken(email, getFidoUserPassword.password); + const decryptedPassword = await this.commonService.decryptPassword(getUserDetails.password); + return this.generateToken(email, decryptedPassword); } return this.generateToken(email, password); @@ -265,23 +284,27 @@ export class UserService { } async generateToken(email: string, password: string): Promise { + try { + const supaInstance = await this.supabaseService.getClient(); - const supaInstance = await this.supabaseService.getClient(); + this.logger.error(`supaInstance::`, supaInstance); - this.logger.error(`supaInstance::`, supaInstance); + const { data, error } = await supaInstance.auth.signInWithPassword({ + email, + password + }); - const { data, error } = await supaInstance.auth.signInWithPassword({ - email, - password - }); - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); + if (error) { + this.logger.error(`Supa Login Error::`, JSON.stringify(error)); + throw new BadRequestException(error?.message); + } - if (error) { - throw new BadRequestException(error?.message); + const token = data?.session; + return token; + } catch (error) { + this.logger.error(`An unexpected error occurred::`, error.message); + throw new InternalServerErrorException('An unexpected error occurred.'); } - - const token = data?.session; - return token; } async getProfile(payload: { id }): Promise { diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 2d3a95f64..1d265cf7a 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -31,7 +31,8 @@ export const ResponseMessages = { invalidKeycloakId: 'keycloakId is invalid', invalidEmail: 'Invalid Email Id!', adduser: 'Unable to add user details', - verifyEmail: 'The verification link has already been sent to your email address. please verify' + verifyEmail: 'The verification link has already been sent to your email address. please verify', + emailNotVerified: 'The verification link has already been sent to your email address. please verify' } }, organisation: { From bff4fa054a3de4d73d63632a1908901839ae96a9 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Thu, 7 Sep 2023 16:08:44 +0530 Subject: [PATCH 054/162] feat:implemented add passke Signed-off-by: tipusinghaw --- .../migration.sql | 11 ++ libs/prisma-service/prisma/schema.prisma | 118 +++++++++--------- 2 files changed, 70 insertions(+), 59 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20230905103154_password_user/migration.sql diff --git a/libs/prisma-service/prisma/migrations/20230905103154_password_user/migration.sql b/libs/prisma-service/prisma/migrations/20230905103154_password_user/migration.sql new file mode 100644 index 000000000..f63b1a510 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20230905103154_password_user/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the column `password` on the `user_devices` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "user" ADD COLUMN "password" VARCHAR; + +-- AlterTable +ALTER TABLE "user_devices" DROP COLUMN "password"; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 3fe696cb3..82c551246 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -22,41 +22,42 @@ model user { supabaseUserId String? @db.VarChar(500) clientId String? @db.VarChar(500) clientSecret String? @db.VarChar(500) - profileImg String? + profileImg String? fidoUserId String? @db.VarChar(1000) isFidoVerified Boolean @default(false) - userOrgRoles user_org_roles[] - userDevices user_devices[] + publicProfile Boolean @default(false) + password String? @db.VarChar orgInvitations org_invitations[] user_activities user_activity[] - publicProfile Boolean @default(false) + userDevices user_devices[] + userOrgRoles user_org_roles[] } model user_activity { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) userId Int - user user @relation(fields: [userId], references: [id]) orgId Int - organisation organisation? @relation(fields: [orgId], references: [id]) action String details String - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - deletedAt DateTime? @db.Timestamp(6) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) + organisation organisation @relation(fields: [orgId], references: [id]) + user user @relation(fields: [userId], references: [id]) } model org_roles { id Int @id @default(autoincrement()) name String @unique description String - userOrgRoles user_org_roles[] createDateTime DateTime @default(now()) @db.Timestamptz(6) createdBy Int @default(1) lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) lastChangedBy Int @default(1) deletedAt DateTime? @db.Timestamp(6) + userOrgRoles user_org_roles[] } model user_org_roles { @@ -80,15 +81,15 @@ model organisation { orgSlug String? @unique logoUrl String? website String? @db.VarChar - userOrgRoles user_org_roles[] - orgInvitations org_invitations[] - org_agents org_agents[] + publicProfile Boolean @default(false) connections connections[] credentials credentials[] + org_agents org_agents[] + orgInvitations org_invitations[] presentations presentations[] schema schema[] user_activities user_activity[] - publicProfile Boolean @default(false) + userOrgRoles user_org_roles[] } model org_invitations { @@ -101,10 +102,10 @@ model org_invitations { userId Int orgId Int status String - user user @relation(fields: [userId], references: [id]) - organisation organisation @relation(fields: [orgId], references: [id]) orgRoles Int[] email String? + organisation organisation @relation(fields: [orgId], references: [id]) + user user @relation(fields: [userId], references: [id]) } model user_devices { @@ -119,7 +120,6 @@ model user_devices { userId Int? deletedAt DateTime? @db.Timestamp(6) authCounter Int @default(0) - password String? @db.VarChar user user? @relation(fields: [userId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_e12ac4f8016243ac71fd2e415af") } @@ -159,12 +159,12 @@ model org_agents { orgId Int orgAgentTypeId Int ledgerId Int? - ledgers ledgers? @relation(fields: [ledgerId], references: [id]) - agents_type agents_type? @relation(fields: [agentsTypeId], references: [id]) - org_agent_type org_agents_type? @relation(fields: [orgAgentTypeId], references: [id]) - organisation organisation? @relation(fields: [orgId], references: [id]) - agents agents? @relation(fields: [agentId], references: [id]) agent_invitations agent_invitations[] + agents agents? @relation(fields: [agentId], references: [id]) + agents_type agents_type @relation(fields: [agentsTypeId], references: [id]) + ledgers ledgers? @relation(fields: [ledgerId], references: [id]) + org_agent_type org_agents_type @relation(fields: [orgAgentTypeId], references: [id]) + organisation organisation @relation(fields: [orgId], references: [id]) } model org_agents_type { @@ -214,20 +214,20 @@ model agents { } model schema { - id Int @id(map: "PK_c9e7e648903a9e537347aba4372") @default(autoincrement()) - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - name String @db.VarChar - version String @db.VarChar + id Int @id(map: "PK_c9e7e648903a9e537347aba4372") @default(autoincrement()) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + name String @db.VarChar + version String @db.VarChar attributes String - schemaLedgerId String @db.VarChar - publisherDid String @db.VarChar - ledgerId Int @default(1) - issuerId String @db.VarChar + schemaLedgerId String @db.VarChar + publisherDid String @db.VarChar + ledgerId Int @default(1) + issuerId String @db.VarChar orgId Int - organisation organisation? @relation(fields: [orgId], references: [id]) + organisation organisation @relation(fields: [orgId], references: [id]) } model credential_definition { @@ -252,46 +252,46 @@ model shortening_url { } model agent_invitations { - id Int @unique @default(autoincrement()) - orgId Int @default(1) - agentId Int @default(1) + id Int @unique @default(autoincrement()) + orgId Int @default(1) + agentId Int @default(1) connectionInvitation String multiUse Boolean - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - org_agents org_agents? @relation(fields: [agentId], references: [id]) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + org_agents org_agents @relation(fields: [agentId], references: [id]) } model connections { - id Int @id @default(autoincrement()) - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - connectionId String @unique + id Int @id @default(autoincrement()) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + connectionId String @unique state String orgDid String theirLabel String autoAcceptConnection Boolean outOfBandId String orgId Int - organisation organisation? @relation(fields: [orgId], references: [id]) + organisation organisation @relation(fields: [orgId], references: [id]) } model credentials { - id Int @id @default(autoincrement()) - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - connectionId String @unique + id Int @id @default(autoincrement()) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + connectionId String @unique threadId String protocolVersion String credentialAttributes Json[] orgId Int - organisation organisation? @relation(fields: [orgId], references: [id]) + organisation organisation @relation(fields: [orgId], references: [id]) } model presentations { From 62b24ed88d0d07b343316190f33033b54d862edb Mon Sep 17 00:00:00 2001 From: Ajay Jadhav Date: Sat, 2 Sep 2023 01:09:37 +0530 Subject: [PATCH 055/162] docs: update README Signed-off-by: tipusinghaw --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc05e7872..987d7cad2 100644 --- a/README.md +++ b/README.md @@ -95,5 +95,15 @@ nest start agent-service [--watch] http://localhost:5000/api ``` +## Credit + +The CREDEBL platform is built by Blockster Labs (Product division of AyanWorks) team. +For the core SSI capabilities, it leverages the great work from multiple open-source projects such as Hyperledger Aries, Bifold, Asker, Indy, etc. + +## Contributing + +Pull requests are welcome! Please read our [contributions guide](https://github.com/credebl/platform/blob/main/CONTRIBUTING.md) and submit your PRs. We enforce [developer certificate of origin](https://developercertificate.org/) (DCO) commit signing — [guidance](https://github.com/apps/dco) on this is available. We also welcome issues submitted about problems you encounter in using CREDEBL. + ## License - Apache 2.0 + +[Apache License Version 2.0](https://github.com/credebl/platform/blob/main/LICENSE) \ No newline at end of file From 8dc71269aa5c635786186e317c3e179ff9f51de6 Mon Sep 17 00:00:00 2001 From: Ajay Jadhav Date: Sat, 2 Sep 2023 01:10:55 +0530 Subject: [PATCH 056/162] docs: add CONTRIBUTING guidelines Signed-off-by: tipusinghaw --- CONTRIBUTING.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..38e4fde7e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,14 @@ +## How to contribute + +You are encouraged to contribute to the repository by **forking and submitting a pull request**. + +For significant changes, please open an issue first to discuss the proposed changes to avoid re-work. + +(If you are new to GitHub, you might start with a [basic tutorial](https://help.github.com/articles/set-up-git) and check out a more detailed guide to [pull requests](https://help.github.com/articles/using-pull-requests/).) + +Pull requests will be evaluated by the repository guardians on a schedule and if deemed beneficial will be committed to the `main` branch. Pull requests should have a descriptive name, include an summary of all changes made in the pull request description, and include unit tests that provide good coverage of the feature or fix. A Continuous Integration (CI) pipeline is executed on all PRs before review and contributors are expected to address all CI issues identified. Where appropriate, PRs that impact the +end-user and developer demos (if any) in the repo should include updates or extensions to those demos to cover the new capabilities. + +If you would like to propose a significant change, please open an issue first to discuss the work with the community. + +Contributions are made pursuant to the Developer's Certificate of Origin, available at [https://developercertificate.org](https://developercertificate.org), and licensed under the Apache License, version 2.0 (Apache-2.0). \ No newline at end of file From 10ff454e6dc85c46b0c66ce7d350f263af0cc602 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Mon, 11 Sep 2023 13:07:46 +0530 Subject: [PATCH 057/162] fix: login error message Signed-off-by: tipusinghaw --- apps/api-gateway/src/fido/fido.controller.ts | 2 +- apps/user/src/user.service.ts | 31 +++++++++----------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/apps/api-gateway/src/fido/fido.controller.ts b/apps/api-gateway/src/fido/fido.controller.ts index d41adc608..88b4d0fdd 100644 --- a/apps/api-gateway/src/fido/fido.controller.ts +++ b/apps/api-gateway/src/fido/fido.controller.ts @@ -157,7 +157,7 @@ export class FidoController { const fidoUserDetails = await this.fidoService.fetchFidoUserDetails(req.params.userName); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.login, + message: ResponseMessages.user.success.fetchUsers, data: fidoUserDetails.response }; return res.status(HttpStatus.OK).json(finalResponse); diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 596b3bd31..f18f55843 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -276,7 +276,7 @@ export class UserService { return this.generateToken(email, decryptedPassword); } - return this.generateToken(email, password); + return this.generateToken(email, password); } catch (error) { this.logger.error(`In Login User : ${JSON.stringify(error)}`); throw new RpcException(error.response); @@ -284,27 +284,24 @@ export class UserService { } async generateToken(email: string, password: string): Promise { - try { - const supaInstance = await this.supabaseService.getClient(); + const supaInstance = await this.supabaseService.getClient(); - this.logger.error(`supaInstance::`, supaInstance); + this.logger.error(`supaInstance::`, supaInstance); - const { data, error } = await supaInstance.auth.signInWithPassword({ - email, - password - }); + const { data, error } = await supaInstance.auth.signInWithPassword({ + email, + password + }); - if (error) { - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); - throw new BadRequestException(error?.message); - } + this.logger.error(`Supa Login Error::`, JSON.stringify(error)); - const token = data?.session; - return token; - } catch (error) { - this.logger.error(`An unexpected error occurred::`, error.message); - throw new InternalServerErrorException('An unexpected error occurred.'); + if (error) { + throw new BadRequestException(error?.message); } + + const token = data?.session; + + return token; } async getProfile(payload: { id }): Promise { From 71409e413de66afac78edf947e39c4622b41c351 Mon Sep 17 00:00:00 2001 From: Ajay Jadhav Date: Fri, 8 Sep 2023 21:10:08 +0530 Subject: [PATCH 058/162] docs: add company name in LICESE file Signed-off-by: Ajay Jadhav Signed-off-by: tipusinghaw --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 261eeb9e9..d98215d8f 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright [2023] [Blockster Labs Private Limited] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 57f5fd96f5986f95bb0b7cd08dca69e8e92af2d5 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Wed, 13 Sep 2023 12:20:17 +0530 Subject: [PATCH 059/162] worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- .env.sample | 58 ++----------- .gitignore | 1 - .../prisma/data/credebl-master-table.json | 85 +++++++++++++++++++ 3 files changed, 92 insertions(+), 52 deletions(-) create mode 100644 libs/prisma-service/prisma/data/credebl-master-table.json diff --git a/.env.sample b/.env.sample index e86573a16..1739e22e2 100644 --- a/.env.sample +++ b/.env.sample @@ -1,5 +1,10 @@ MODE=DEV + +SUPABASE_URL= // Please specify your Supabase Url +SUPABASE_KEY= // Please specify your Supabase Anon key +SUPABASE_JWT_SECRET= // Please specify your Supabase jwt secret + API_GATEWAY_PROTOCOL=http API_GATEWAY_HOST='0.0.0.0' API_GATEWAY_PORT=5000 @@ -10,7 +15,6 @@ AGENT_HOST=username@0.0.0.0 // Please specify your agent host VM and IP addres AWS_ACCOUNT_ID=xxxxx // Please provide your AWS account Id S3_BUCKET_ARN=arn:aws:s3:::xxxxx // Please provide your AWS bucket arn -TENANT_EMAIL_LOGO=credebl.jpg API_ENDPOINT=localhost:5000 #Use your local machine IP Address & PORT API_ENDPOINT_PORT=5000 @@ -20,68 +24,20 @@ NATS_HOST='0.0.0.0' NATS_PORT=4222 NATS_URL=nats://0.0.0.0:4222 -REDIS_HOST='localhost' # Use IP Address -REDIS_PORT=6379 - -POSTGRES_HOST=localhost # Use IP Address -POSTGRES_PORT=5432 -POSTGRES_USER=postgres -POSTGRES_PASSWORD=xxxxxxxx -POSTGRES_DATABASE=postgres - -POSTGRES_MEDIATOR_DATABASE='mediator_agent' -POSTGRES_MEDIATOR_PORT=5431 - -MEDIATOR_AGENT_LABEL=MediatorAgent -MEDIATOR_AGENT_ENDPOINT='' - SENDGRID_API_KEY=xxxxxxxxxxxxxx // Please provide your sendgrid API key FRONT_END_URL=http://localhost:3000 -FILE_SERVER=credebl-dev-mediator-indypool -FILE_SERVER_PORT=8081 -FILE_SERVER_USER=credebl -FILE_SERVER_HOST=0.0.0.0 - -REMOTE_FILE_DIR='/opt/cb-tails-file-server/tails/tails-files/' -ACCESSIBLE_FILE_LOCATION='tails-files' - -LOCAL_FILE_SERVER=/opt/credebl-platform/tails-files/ -GCLOUD_ENGINE_PATH=/home/credebl/.ssh/google_compute_engine - AFJ_AGENT_SPIN_UP=/apps/agent-provisioning/AFJ/scripts/start_agent.sh -AGENT_SPIN_UP_FILE=/agent-spinup/scripts/start_agent.sh -LIBINDY_KEY=CE7709D068DB5E88 - -AGENT_RESTART_SCRIPT=/agent-spinup/scripts/manage_agent.sh -AGENT_STATUS_SCRIPT=/agent-spinup/scripts/status_agent.sh - -WALLET_PROVISION_SCRIPT=/agent-spinup/scripts/wallet_provision.sh WALLET_STORAGE_HOST=localhost # Use IP Address WALLET_STORAGE_PORT=5432 WALLET_STORAGE_USER=postgres WALLET_STORAGE_PASSWORD=xxxxxx -KEYCLOAK_DOMAIN=http://localhost:8089/auth/ -KEYCLOAK_ADMIN_URL=http://localhost:8089 -KEYCLOAK_MASTER_REALM=master -KEYCLOAK_CREDEBL_REALM=credebl-platform -KEYCLOAK_MANAGEMENT_CLIENT_ID=adminClient -KEYCLOAK_MANAGEMENT_CLIENT_SECRET=xxxxxx-xxxx-xxxx-xxxx-xxxxxx #Refer from ADMIN CONSOLE of your Keycloak -KEYCLOAK_MANAGEMENT_ADEYA_CLIENT_ID=adeyaClient -KEYCLOAK_MANAGEMENT_ADEYA_CLIENT_SECRET=xxxxxx-xxxx-xxxx-xxxx-xxxxxx #Refer from ADMIN CONSOLE of your Keycloak - -FILE_UPLOAD_PATH_TENANT= /uploadedFiles/tenant-logo/ - -CRYPTO_PRIVATE_KEY=xxxxx-xxxxx-xxxxx-xxxxx -PLATFORM_URL=https://dev.credebl.com -KEYCLOAK_URL=http://localhost:8089 -PLATFORM_PROFILE_MODE=DEV +CRYPTO_PRIVATE_KEY=xxxxx-xxxxx-xxxxx-xxxxx #It should be same as studio UI AFJ_VERSION=afj-0.4.0:latest -INVOICE_PDF_URL=./invoice-pdf FIDO_API_ENDPOINT=http://localhost:8000 # Host:port of your FIDO (WebAuthn) Server @@ -91,6 +47,7 @@ PLATFORM_SEED= // The seed should consist of 32 characters. PLATFORM_ID= AFJ_AGENT_ENDPOINT_PATH=/apps/agent-provisioning/AFJ/endpoints/ +DATABASE_URL="postgresql://postgres:xxxxxx@localhost:5432/postgres?schema=public" #Provide supabase postgres url and Use the correct user/pwd, IP Address # This was inserted by prisma init: # Environment variables declared in this file are automatically made available to Prisma. @@ -99,7 +56,6 @@ AFJ_AGENT_ENDPOINT_PATH=/apps/agent-provisioning/AFJ/endpoints/ # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB (Preview). # See the documentation for all the connection string options: https://pris.ly/d/connection-strings -DATABASE_URL="postgresql://postgres:xxxxxx@localhost:5432/postgres?schema=public" #Use the correct user/pwd, IP Address # enable only prisma:engine-level debugging output export DEBUG="prisma:engine" diff --git a/.gitignore b/.gitignore index 4281aedfc..642765a46 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,3 @@ uploadedFiles sonar-project.properties .scannerwork/* coverage -libs/prisma-service/prisma/data/credebl-master-table.json diff --git a/libs/prisma-service/prisma/data/credebl-master-table.json b/libs/prisma-service/prisma/data/credebl-master-table.json new file mode 100644 index 000000000..35a44c6c3 --- /dev/null +++ b/libs/prisma-service/prisma/data/credebl-master-table.json @@ -0,0 +1,85 @@ +{ + "platformConfigData": { + "externalIp": "##Machine Ip Address for agent setup##", + "lastInternalId": "##Docker Network Ip Address##", + "username": "credebl", + "sgApiKey": "###Sendgrid Key###", + "emailFrom": "##Senders Mail ID##", + "apiEndpoint": "## Platform API Ip Address##", + "tailsFileServer": "##Machine Ip Address for agent setup##" + }, + "platformAdminData": { + "firstName": "CREDEBL", + "lastName": "CREDEBL", + "email": "platform.admin@yopmail.com", + "username": "platform.admin@yopmail.com", + "password": "####Please provide encrypted password using crypto-js###", + "verificationCode": "", + "isEmailVerified": true, + "supabaseUserId": "96cef763-e106-46c1-ac78-fadf2803b11f" + }, + "platformAdminOrganizationData": { + "name": "Platform-admin", + "description": "Platform-admin", + "logoUrl": "", + "website": "" + }, + "userOrgRoleData": { + "userId": 1, + "orgRoleId": 1, + "orgId": 1 + }, + "orgRoleData": [ + { + "name": "owner", + "description": "Organization Owner" + }, + { + "name": "admin", + "description": "Organization Admin" + }, + { + "name": "issuer", + "description": "Organization Credential Issuer" + }, + { + "name": "verifier", + "description": "Organization Credential Verifier" + }, + { + "name": "holder", + "description": "Receives credentials issued by organization" + }, + { + "name": "member", + "description": "Joins the organization as member" + } + ], + "agentTypeData": [ + { + "agent": "AFJ" + }, + { + "agent": "ACAPY" + } + ], + "orgAgentTypeData": [ + { + "agent": "DEDICATED" + }, + { + "agent": "SHARED" + } + ], + "ledgerData": [ + { + "name": "Bcovrin Testnet", + "networkType": "testnet", + "poolConfig": "http://test.bcovrin.vonx.io/genesis", + "isActive": true, + "networkString": "testnet", + "registerDIDEndpoint": "http://test.bcovrin.vonx.io/register", + "registerDIDPayload": "" + } + ] +} \ No newline at end of file From cd4ed9d51621050260652ec3ee401465bf284d00 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Wed, 13 Sep 2023 15:47:56 +0530 Subject: [PATCH 060/162] Included credebl-master-table json file in the .gitignore Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 642765a46..77d23f742 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ uploadedFiles sonar-project.properties .scannerwork/* coverage +libs/prisma-service/prisma/data/credebl-master-table.json \ No newline at end of file From 501b1f0fef02f35cac1ba2c606833780024aa460 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Thu, 14 Sep 2023 20:14:22 +0530 Subject: [PATCH 061/162] worked on the username & orgslug feature for public profiles Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- .../dtos/update-organization-dto.ts | 7 +++- .../organization/organization.controller.ts | 15 +++---- .../src/organization/organization.service.ts | 4 +- .../src/user/dto/update-user-profile.dto.ts | 8 +++- apps/api-gateway/src/user/user.controller.ts | 13 +++--- apps/api-gateway/src/user/user.service.ts | 4 +- .../dtos/create-organization.dto.ts | 1 + .../interfaces/organization.interface.ts | 2 + .../repositories/organization.repository.ts | 14 +++++-- .../src/organization.controller.ts | 2 +- apps/organization/src/organization.service.ts | 42 ++++++++++++++++--- apps/user/interfaces/user.interface.ts | 2 + apps/user/repositories/user.repository.ts | 38 +++++++++++------ apps/user/src/user.controller.ts | 5 +-- apps/user/src/user.service.ts | 34 +++++++++++++-- 15 files changed, 143 insertions(+), 48 deletions(-) diff --git a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts index 44569b38d..1229ddc58 100644 --- a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts +++ b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts @@ -1,5 +1,5 @@ import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumber, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; @@ -37,4 +37,9 @@ export class UpdateOrganizationDto { @IsOptional() website: string; + @ApiPropertyOptional({ example: true }) + @IsBoolean({ message: 'isPublic should be boolean' }) + @IsOptional() + isPublic? = false; + } \ No newline at end of file diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 05fafc1da..25934cfe1 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -1,4 +1,4 @@ -import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { CommonService } from '@credebl/common'; import { Controller, Get, Put, Param, UseGuards, UseFilters } from '@nestjs/common'; import { OrganizationService } from './organization.service'; @@ -109,18 +109,19 @@ export class OrganizationController { return res.status(HttpStatus.OK).json(finalResponse); } - @Get('public-profile') + @Get('public-profiles/:orgSlug') @ApiOperation({ summary: 'Fetch user details', description: 'Fetch user details' }) - @ApiQuery({ - name: 'id', - type: Number, + + @ApiParam({ + name: 'orgSlug', + type: String, required: false }) - async getPublicProfile(@User() reqUser: user, @Query('id') id: number, @Res() res: Response): Promise { - const userData = await this.organizationService.getPublicProfile(id); + async getPublicProfile(@Param('orgSlug') orgSlug: string, @Res() res: Response): Promise { + const userData = await this.organizationService.getPublicProfile(orgSlug); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, diff --git a/apps/api-gateway/src/organization/organization.service.ts b/apps/api-gateway/src/organization/organization.service.ts index eb626e6d1..ba0d6f96d 100644 --- a/apps/api-gateway/src/organization/organization.service.ts +++ b/apps/api-gateway/src/organization/organization.service.ts @@ -65,8 +65,8 @@ export class OrganizationService extends BaseService { return this.sendNats(this.serviceProxy, 'get-public-organizations', payload); } - async getPublicProfile(id: number): Promise<{ response: object }> { - const payload = { id }; + async getPublicProfile(orgSlug: string): Promise<{ response: object }> { + const payload = {orgSlug }; try { return this.sendNats(this.serviceProxy, 'get-organization-public-profile', payload); } catch (error) { diff --git a/apps/api-gateway/src/user/dto/update-user-profile.dto.ts b/apps/api-gateway/src/user/dto/update-user-profile.dto.ts index 88d1c28b5..41484636c 100644 --- a/apps/api-gateway/src/user/dto/update-user-profile.dto.ts +++ b/apps/api-gateway/src/user/dto/update-user-profile.dto.ts @@ -1,6 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumber, IsOptional, IsString} from 'class-validator'; - +import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; export class UpdateUserProfileDto { @ApiProperty() @@ -22,4 +21,9 @@ export class UpdateUserProfileDto { @IsString({ message: 'lastName should be string' }) @IsOptional() lastName?: string; + + @ApiPropertyOptional({ example: true }) + @IsBoolean({ message: 'isPublic should be boolean' }) + @IsOptional() + isPublic? = false; } \ No newline at end of file diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 0677ad8eb..6c18391e8 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -6,6 +6,7 @@ import { ApiBody, ApiForbiddenResponse, ApiOperation, + ApiParam, ApiQuery, ApiResponse, ApiTags, @@ -222,18 +223,18 @@ export class UserController { } - @Get('public-profile') + @Get('public-profiles/:username') @ApiOperation({ summary: 'Fetch user details', description: 'Fetch user details' }) - @ApiQuery({ - name: 'id', - type: Number, + @ApiParam({ + name: 'username', + type: String, required: false }) - async getPublicProfile(@User() reqUser: user, @Query('id') id: number, @Res() res: Response): Promise { - const userData = await this.userService.getPublicProfile(id); + async getPublicProfile(@Param('username') username: string, @Res() res: Response): Promise { + const userData = await this.userService.getPublicProfile(username); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, diff --git a/apps/api-gateway/src/user/user.service.ts b/apps/api-gateway/src/user/user.service.ts index 6dfa2b0f5..9e97e50ac 100644 --- a/apps/api-gateway/src/user/user.service.ts +++ b/apps/api-gateway/src/user/user.service.ts @@ -53,8 +53,8 @@ export class UserService extends BaseService { } } - async getPublicProfile(id: number): Promise<{ response: object }> { - const payload = { id }; + async getPublicProfile(username: string): Promise<{ response: object }> { + const payload = { username }; try { return this.sendNats(this.serviceProxy, 'get-user-public-profile', payload); } catch (error) { diff --git a/apps/organization/dtos/create-organization.dto.ts b/apps/organization/dtos/create-organization.dto.ts index c2fd5249e..2d54b1ef7 100644 --- a/apps/organization/dtos/create-organization.dto.ts +++ b/apps/organization/dtos/create-organization.dto.ts @@ -6,6 +6,7 @@ export class CreateOrganizationDto { description?: string; logo?: string; website?: string; + orgSlug?:string; } export class CreateUserRoleOrgDto { diff --git a/apps/organization/interfaces/organization.interface.ts b/apps/organization/interfaces/organization.interface.ts index 5a1d9667f..f8cbb9e9c 100644 --- a/apps/organization/interfaces/organization.interface.ts +++ b/apps/organization/interfaces/organization.interface.ts @@ -18,4 +18,6 @@ export interface IUpdateOrganization { orgId: string; logo?: string; website?: string; + orgSlug?: string; + isPublic?:boolean } \ No newline at end of file diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 1f44ebcf9..8339b220a 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -53,7 +53,8 @@ export class OrganizationRepository { name: createOrgDto.name, logoUrl: createOrgDto.logo, description: createOrgDto.description, - website: createOrgDto.website + website: createOrgDto.website, + orgSlug: createOrgDto.orgSlug } }); } catch (error) { @@ -78,10 +79,11 @@ export class OrganizationRepository { name: updateOrgDto.name, logoUrl: updateOrgDto.logo, description: updateOrgDto.description, - website: updateOrgDto.website + website: updateOrgDto.website, + orgSlug: updateOrgDto.orgSlug, + publicProfile: updateOrgDto.isPublic } }); - } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); @@ -257,6 +259,12 @@ export class OrganizationRepository { org_agent_type: true, ledgers: true } + }, + userOrgRoles: { + include:{ + user: true, + orgRole:true + } } } }); diff --git a/apps/organization/src/organization.controller.ts b/apps/organization/src/organization.controller.ts index 6302aa0cb..85edbe3c9 100644 --- a/apps/organization/src/organization.controller.ts +++ b/apps/organization/src/organization.controller.ts @@ -72,7 +72,7 @@ export class OrganizationController { } @MessagePattern({ cmd: 'get-organization-public-profile' }) - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: { orgSlug }): Promise { return this.organizationService.getPublicProfile(payload); } diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index ec5ddfa96..ddafd90d8 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -48,6 +48,9 @@ export class OrganizationService { throw new ConflictException(ResponseMessages.organisation.error.exists); } + const orgSlug = await this.createOrgSlug(createOrgDto.name); + createOrgDto.orgSlug = orgSlug; + const organizationDetails = await this.organizationRepository.createOrganization(createOrgDto); const ownerRoleData = await this.orgRoleService.getRole(OrgRoles.OWNER); @@ -61,6 +64,21 @@ export class OrganizationService { } } + + /** + * + * @param orgName + * @returns OrgSlug + */ + createOrgSlug(orgName: string): string { + return orgName + .toLowerCase() // Convert the input to lowercase + .replace(/\s+/g, '-') // Replace spaces with hyphens + .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens + .replace(/--+/g, '-') // Replace multiple consecutive hyphens with a single hyphen + .replace(/^-+|-+$/g, ''); // Trim hyphens from the beginning and end of the string +} + /** * * @param registerOrgDto @@ -70,6 +88,16 @@ export class OrganizationService { // eslint-disable-next-line camelcase async updateOrganization(updateOrgDto: IUpdateOrganization, userId: number): Promise { try { + + const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); + + if (organizationExist) { + throw new ConflictException(ResponseMessages.organisation.error.exists); + } + + const orgSlug = await this.createOrgSlug(updateOrgDto.name); + updateOrgDto.orgSlug = orgSlug; + const organizationDetails = await this.organizationRepository.updateOrganization(updateOrgDto); await this.userActivityService.createActivity(userId, organizationDetails.id, `${organizationDetails.name} organization updated`, 'Organization details updated successfully'); return organizationDetails; @@ -147,13 +175,17 @@ export class OrganizationService { } } - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: { orgSlug }): Promise { try { - const query = { - id: payload.id, - publicProfile: true - }; + let query = {}; + + if (payload.orgSlug) { + query = { + orgSlug: payload.orgSlug, + publicProfile: true + }; + } const organizationDetails = await this.organizationRepository.getOrganization(query); if (!organizationDetails) { diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index 051ba3bfb..ed5febe00 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -26,6 +26,7 @@ export interface InvitationsI { export interface UserEmailVerificationDto{ email:string + username?:string } export interface userInfo{ @@ -51,4 +52,5 @@ export interface UpdateUserProfile { profileImg?: string; firstName: string, lastName: string, + isPublic: boolean, } \ No newline at end of file diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index cfdfc92e7..c0e070b36 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -7,11 +7,11 @@ import { InternalServerErrorException } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase import { user } from '@prisma/client'; -import { v4 as uuidv4 } from 'uuid'; interface UserQueryOptions { id?: number; // Use the appropriate type based on your data model email?: string; // Use the appropriate type based on your data model + username?: string // Add more properties if needed for other unique identifier fields }; @@ -24,12 +24,11 @@ export class UserRepository { * @param userEmailVerificationDto * @returns user email */ - async createUser(userEmailVerificationDto: UserEmailVerificationDto): Promise { + async createUser(userEmailVerificationDto: UserEmailVerificationDto, verifyCode: string): Promise { try { - const verifyCode = uuidv4(); const saveResponse = await this.prisma.user.create({ data: { - username: userEmailVerificationDto.email, + username: userEmailVerificationDto.username, email: userEmailVerificationDto.email, verificationCode: verifyCode.toString() } @@ -99,19 +98,26 @@ export class UserRepository { * @param id * @returns User profile data */ - async getUserPublicProfile(id: number): Promise { - const queryOptions: UserQueryOptions = { - id - }; - return this.findUserForPublicProfile(queryOptions); - } + async getUserPublicProfile(username: string): Promise { + + + let queryOptions: UserQueryOptions = {}; + if (username) { + queryOptions = { + username + }; + + } + return this.findUserForPublicProfile(queryOptions); + } /** * * @Body updateUserProfile * @returns Update user profile data */ - async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { + async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { + try { const userdetails = await this.prisma.user.update({ where: { @@ -120,7 +126,8 @@ export class UserRepository { data: { profileImg: updateUserProfile.profileImg, firstName: updateUserProfile.firstName, - lastName: updateUserProfile.lastName + lastName: updateUserProfile.lastName, + publicProfile: updateUserProfile?.isPublic } }); return userdetails; @@ -233,6 +240,9 @@ export class UserRepository { }, { email: queryOptions.email + }, + { + username: queryOptions.username } ] }, @@ -253,7 +263,8 @@ export class UserRepository { name: true, description: true, logoUrl: true, - website: true + website: true, + orgSlug: true }, where:{ publicProfile: true @@ -397,6 +408,7 @@ export class UserRepository { email: true, firstName: true, lastName: true, + profileImg: true, isEmailVerified: true, clientId: false, clientSecret: false, diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 629bb9dcf..0fa931ccb 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -1,3 +1,4 @@ +import { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { Controller } from '@nestjs/common'; @@ -5,8 +6,6 @@ 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 { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; - @Controller() export class UserController { @@ -43,7 +42,7 @@ export class UserController { } @MessagePattern({ cmd: 'get-user-public-profile' }) - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: { username }): Promise { return this.userService.getPublicProfile(payload); } @MessagePattern({ cmd: 'update-user-profile' }) diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index f18f55843..5535cc342 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -32,6 +32,7 @@ import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto' import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; import { UserDevicesRepository } from '../repositories/user-device.repository'; +import { v4 as uuidv4 } from 'uuid'; @Injectable() @@ -67,7 +68,10 @@ export class UserService { throw new ConflictException(ResponseMessages.user.error.verificationAlreadySent); } - const resUser = await this.userRepository.createUser(userEmailVerificationDto); + const verifyCode = uuidv4(); + const uniqueUsername = await this.createUsername(userEmailVerificationDto.email, verifyCode); + userEmailVerificationDto.username = uniqueUsername; + const resUser = await this.userRepository.createUser(userEmailVerificationDto, verifyCode); try { await this.sendEmailForVerification(userEmailVerificationDto.email, resUser.verificationCode); @@ -82,6 +86,30 @@ export class UserService { } } + async createUsername(email: string, verifyCode: string): Promise { + + try { + // eslint-disable-next-line prefer-destructuring + const emailTrim = email.split('@')[0]; + + // Replace special characters with hyphens + const cleanedUsername = emailTrim.toLowerCase().replace(/[^a-zA-Z0-9_]/g, '-'); + + // Generate a 5-digit UUID + // eslint-disable-next-line prefer-destructuring + const uuid = verifyCode.split('-')[0]; + + // Combine cleaned username and UUID + const uniqueUsername = `${cleanedUsername}-${uuid}`; + + return uniqueUsername; + + } catch (error) { + this.logger.error(`Error in createUsername: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error.message); + } + } + /** * * @param email @@ -313,9 +341,9 @@ export class UserService { } } - async getPublicProfile(payload: { id }): Promise { + async getPublicProfile(payload: {username }): Promise { try { - const userProfile = await this.userRepository.getUserPublicProfile(payload.id); + const userProfile = await this.userRepository.getUserPublicProfile(payload.username); if (!userProfile) { throw new NotFoundException(ResponseMessages.user.error.profileNotFound); From 47cbb6d80c742ee36133745f4f4febf2adf9ac8a Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 12:04:32 +0530 Subject: [PATCH 062/162] refractored the GET API endoints for public profiles Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/api-gateway/src/organization/organization.controller.ts | 2 +- apps/api-gateway/src/user/user.controller.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 25934cfe1..e463cb3c0 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -79,7 +79,7 @@ export class OrganizationController { * @param res * @returns Users list of organization */ - @Get('/public') + @Get() @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Get public organization list', description: 'Get users list.' }) @ApiQuery({ diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 6c18391e8..953c57cf4 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -119,7 +119,7 @@ export class UserController { * @param res * @returns Users list of organization */ - @Get('/public') + @Get('/public-profiles') @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Get users list', description: 'Get users list.' }) @ApiQuery({ From d013271b3c7e42f8bda21fe468eed51332da52c5 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 12:11:13 +0530 Subject: [PATCH 063/162] refactored the organization GET API public profiles Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/api-gateway/src/organization/organization.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index e463cb3c0..32d4cec98 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -79,7 +79,7 @@ export class OrganizationController { * @param res * @returns Users list of organization */ - @Get() + @Get('/public-profiles') @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Get public organization list', description: 'Get users list.' }) @ApiQuery({ From c6fb954da225b721cae1e87b360fc4af2c94588a Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 12:28:28 +0530 Subject: [PATCH 064/162] solved the regix expression grouping issue. Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/organization/src/organization.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index ddafd90d8..10ed3e6cc 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -48,7 +48,7 @@ export class OrganizationService { throw new ConflictException(ResponseMessages.organisation.error.exists); } - const orgSlug = await this.createOrgSlug(createOrgDto.name); + const orgSlug = this.createOrgSlug(createOrgDto.name); createOrgDto.orgSlug = orgSlug; const organizationDetails = await this.organizationRepository.createOrganization(createOrgDto); @@ -76,7 +76,7 @@ export class OrganizationService { .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens .replace(/--+/g, '-') // Replace multiple consecutive hyphens with a single hyphen - .replace(/^-+|-+$/g, ''); // Trim hyphens from the beginning and end of the string + .replace(/[^-+|-+$]/g, ''); // Trim hyphens from the beginning and end of the string } /** From 9e90a80d2f6331653c5265569e209209b8113145 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 13:55:32 +0530 Subject: [PATCH 065/162] resolved the comments on the PR Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- .../repositories/organization.repository.ts | 2 +- apps/organization/src/organization.service.ts | 15 ++++++--------- apps/user/repositories/user.repository.ts | 10 +++------- apps/user/src/user.controller.ts | 4 ++-- apps/user/src/user.service.ts | 6 +++--- 5 files changed, 15 insertions(+), 22 deletions(-) diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 8339b220a..f20c85219 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -245,7 +245,7 @@ export class OrganizationRepository { } } - async getOrganization(queryObject: object): Promise { + async getOrganization(queryObject: object): Promise { try { return this.prisma.organisation.findFirst({ where: { diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 10ed3e6cc..5f1daee51 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -175,17 +175,14 @@ export class OrganizationService { } } - async getPublicProfile(payload: { orgSlug }): Promise { + async getPublicProfile(payload: { orgSlug: string }): Promise { + const {orgSlug} = payload; try { - let query = {}; - - if (payload.orgSlug) { - query = { - orgSlug: payload.orgSlug, - publicProfile: true - }; - } + const query = { + orgSlug, + publicProfile: true + }; const organizationDetails = await this.organizationRepository.getOrganization(query); if (!organizationDetails) { diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index c0e070b36..0743b628b 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -99,15 +99,11 @@ export class UserRepository { * @returns User profile data */ async getUserPublicProfile(username: string): Promise { - - - let queryOptions: UserQueryOptions = {}; - if (username) { - queryOptions = { + + const queryOptions: UserQueryOptions = { username }; - } return this.findUserForPublicProfile(queryOptions); } @@ -116,7 +112,7 @@ export class UserRepository { * @Body updateUserProfile * @returns Update user profile data */ - async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { + async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { try { const userdetails = await this.prisma.user.update({ diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 0fa931ccb..9d94f00e0 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -1,4 +1,4 @@ -import { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; +import { AddPasskeyDetails, UpdateUserProfile, UserEmailVerificationDto, UserI, userInfo } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { Controller } from '@nestjs/common'; @@ -42,7 +42,7 @@ export class UserController { } @MessagePattern({ cmd: 'get-user-public-profile' }) - async getPublicProfile(payload: { username }): Promise { + async getPublicProfile(payload: { username }): Promise { return this.userService.getPublicProfile(payload); } @MessagePattern({ cmd: 'update-user-profile' }) diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 5535cc342..96753ee0c 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -27,7 +27,7 @@ import { sendEmail } from '@credebl/common/send-grid-helper-file'; import { user } from '@prisma/client'; import { Inject } from '@nestjs/common'; import { HttpException } from '@nestjs/common'; -import { AddPasskeyDetails, InvitationsI, UpdateUserProfile, UserEmailVerificationDto, userInfo } from '../interfaces/user.interface'; +import { AddPasskeyDetails, InvitationsI, UpdateUserProfile, UserEmailVerificationDto, UserI, userInfo } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; @@ -341,7 +341,7 @@ export class UserService { } } - async getPublicProfile(payload: {username }): Promise { + async getPublicProfile(payload: { username }): Promise { try { const userProfile = await this.userRepository.getUserPublicProfile(payload.username); @@ -356,7 +356,7 @@ export class UserService { } } - async updateUserProfile(updateUserProfileDto: UpdateUserProfile): Promise { + async updateUserProfile(updateUserProfileDto: UpdateUserProfile): Promise { try { return this.userRepository.updateUserProfile(updateUserProfileDto); } catch (error) { From d18b1c624497d840f6c0db77dd5d75ff00d8d7b1 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 16:03:29 +0530 Subject: [PATCH 066/162] chnaged regular expression for orgSLug Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/organization/src/organization.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 5f1daee51..802d9473f 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -75,8 +75,7 @@ export class OrganizationService { .toLowerCase() // Convert the input to lowercase .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens - .replace(/--+/g, '-') // Replace multiple consecutive hyphens with a single hyphen - .replace(/[^-+|-+$]/g, ''); // Trim hyphens from the beginning and end of the string + .replace(/--+/g, '-'); // Replace multiple consecutive hyphens with a single hyphen } /** From 1e10b297b40c0efe2d67ddaceb4ce38802aaa0cb Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Fri, 15 Sep 2023 14:24:09 +0530 Subject: [PATCH 067/162] feat: implemented username & orgslug feature for public profiles (#85) * worked on the username & orgslug feature for public profiles Signed-off-by: @nishad.shirsat * refractored the GET API endoints for public profiles Signed-off-by: @nishad.shirsat * refactored the organization GET API public profiles Signed-off-by: @nishad.shirsat * solved the regix expression grouping issue. Signed-off-by: @nishad.shirsat * resolved the comments on the PR Signed-off-by: @nishad.shirsat --------- Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/organization/src/organization.service.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 802d9473f..b9a386263 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -71,12 +71,12 @@ export class OrganizationService { * @returns OrgSlug */ createOrgSlug(orgName: string): string { - return orgName - .toLowerCase() // Convert the input to lowercase - .replace(/\s+/g, '-') // Replace spaces with hyphens - .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens - .replace(/--+/g, '-'); // Replace multiple consecutive hyphens with a single hyphen -} + return orgName + .toLowerCase() // Convert the input to lowercase + .replace(/\s+/g, '-') // Replace spaces with hyphens + .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric characters except hyphens + .replace(/--+/g, '-'); // Replace multiple consecutive hyphens with a single hyphen + } /** * @@ -175,7 +175,7 @@ export class OrganizationService { } async getPublicProfile(payload: { orgSlug: string }): Promise { - const {orgSlug} = payload; + const { orgSlug } = payload; try { const query = { From 969f81385250a3e2135642ce99a7ecb677dceb59 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Fri, 15 Sep 2023 19:25:28 +0530 Subject: [PATCH 068/162] implemented org and user to bydefault public (#87) Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/organization/repositories/organization.repository.ts | 4 +++- apps/organization/src/organization.service.ts | 4 ++-- apps/user/repositories/user.repository.ts | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index f20c85219..0ebb10fb3 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -54,7 +54,9 @@ export class OrganizationRepository { logoUrl: createOrgDto.logo, description: createOrgDto.description, website: createOrgDto.website, - orgSlug: createOrgDto.orgSlug + orgSlug: createOrgDto.orgSlug, + publicProfile: true + } }); } catch (error) { diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index b9a386263..e5670064f 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -89,8 +89,8 @@ export class OrganizationService { try { const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); - - if (organizationExist) { + + if (organizationExist && organizationExist.id !== Number(updateOrgDto.orgId)) { throw new ConflictException(ResponseMessages.organisation.error.exists); } diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 0743b628b..e64f5033a 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -30,7 +30,8 @@ export class UserRepository { data: { username: userEmailVerificationDto.username, email: userEmailVerificationDto.email, - verificationCode: verifyCode.toString() + verificationCode: verifyCode.toString(), + publicProfile: true } }); From bcf6be29785a7e7398b87225702383d219c711c5 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:51:55 +0530 Subject: [PATCH 069/162] refactor: API refactor in user, organization, auth and agent module (#82) * docs: add CONTRIBUTING guidelines Signed-off-by: KulkarniShashank * docs: add company name in LICESE file Signed-off-by: Ajay Jadhav Signed-off-by: KulkarniShashank * merge dev branch to main (#77) * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw * feat:implemented add passke Signed-off-by: tipusinghaw * fix: login error message Signed-off-by: tipusinghaw --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank * Included credebl-master-table json file in the .gitignore Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank * fix: API refactor in user, organization, auth and agent module Signed-off-by: KulkarniShashank * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * feat:implemented add passke Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * fix: login error message Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * Set Validation for the user login Signed-off-by: KulkarniShashank * Removed unnecessarily conditions in user dto Signed-off-by: KulkarniShashank * added the role matrix in the organization, user and agent module Signed-off-by: KulkarniShashank * Added the error handling in the API-gateway Signed-off-by: KulkarniShashank * Added role guard in user and organization Signed-off-by: KulkarniShashank * Error handling in the user signin functionality Signed-off-by: KulkarniShashank * Function name changes in the singin functionality Signed-off-by: KulkarniShashank * Added activity log success in user Signed-off-by: KulkarniShashank * Solved the bug regarding in organization update functionality Signed-off-by: KulkarniShashank * added role guard in user invitation Signed-off-by: KulkarniShashank * Error handling on api-gateway dto Signed-off-by: KulkarniShashank * Added veriable for the seed in agent-service Signed-off-by: KulkarniShashank * Added veriable for the seed on globaly in agent-service Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: Ajay Jadhav Signed-off-by: tipusinghaw Signed-off-by: @nishad.shirsat Co-authored-by: Ajay Jadhav Co-authored-by: Ajay Jadhav Co-authored-by: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Co-authored-by: @nishad.shirsat Co-authored-by: tipusinghaw Signed-off-by: tipusinghaw --- .../src/agent-service.service.ts | 20 +- apps/api-gateway/common/exception-handler.ts | 48 ++- .../agent-service/agent-service.controller.ts | 83 +++-- .../agent-service/dto/agent-service.dto.ts | 19 +- .../agent-service/dto/create-tenant.dto.ts | 15 +- .../api-gateway/src/authz/authz.controller.ts | 121 ++++++- apps/api-gateway/src/authz/authz.service.ts | 25 +- .../src/authz/guards/org-roles.guard.ts | 17 +- .../src/dtos/email-validator.dto.ts | 12 + .../organization/dtos/send-invitation.dto.ts | 6 +- .../dtos/update-organization-dto.ts | 8 +- .../dtos/update-user-roles.dto.ts | 14 +- .../organization/organization.controller.ts | 265 +++++++++------ .../src/organization/organization.service.ts | 53 ++- .../user/dto/accept-reject-invitation.dto.ts | 6 +- apps/api-gateway/src/user/dto/add-user.dto.ts | 10 +- .../src/user/dto/login-user.dto.ts | 31 +- .../src/user/dto/update-user-profile.dto.ts | 10 +- apps/api-gateway/src/user/user.controller.ts | 305 +++++------------- apps/api-gateway/src/user/user.service.ts | 83 +---- .../repositories/organization.repository.ts | 24 +- .../src/organization.controller.ts | 4 +- apps/organization/src/organization.service.ts | 41 +-- apps/user/interfaces/user.interface.ts | 1 + apps/user/src/user.controller.ts | 30 +- apps/user/src/user.service.ts | 97 +++--- libs/common/src/response-messages/index.ts | 12 +- 27 files changed, 716 insertions(+), 644 deletions(-) create mode 100644 apps/api-gateway/src/dtos/email-validator.dto.ts diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index b1a818b26..893124ff0 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -122,7 +122,7 @@ export class AgentServiceService { return internalIp; } catch (error) { this.logger.error(`error in valid internal ip : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -573,7 +573,7 @@ export class AgentServiceService { }); socket.emit('error-in-wallet-creation-process', { clientId: payload.clientSocketId, error }); } - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -739,7 +739,7 @@ export class AgentServiceService { return data; } catch (error) { this.logger.error(`Error in connection Invitation in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -761,7 +761,7 @@ export class AgentServiceService { return getProofPresentationsData; } catch (error) { this.logger.error(`Error in proof presentations in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -783,7 +783,7 @@ export class AgentServiceService { return getProofPresentationById; } catch (error) { this.logger.error(`Error in proof presentation by id in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -805,7 +805,7 @@ export class AgentServiceService { return sendProofRequest; } catch (error) { this.logger.error(`Error in send proof request in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -817,7 +817,7 @@ export class AgentServiceService { return verifyPresentation; } catch (error) { this.logger.error(`Error in verify proof presentation in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -860,7 +860,7 @@ export class AgentServiceService { } catch (error) { this.logger.error(`Agent health details : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -872,7 +872,7 @@ export class AgentServiceService { return sendProofRequest; } catch (error) { this.logger.error(`Error in send out of band proof request in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -884,7 +884,7 @@ export class AgentServiceService { return getProofFormData; } catch (error) { this.logger.error(`Error in get proof form data in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } } diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index 45a87a1cb..13c102539 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -16,12 +16,48 @@ export class CustomExceptionFilter extends BaseExceptionFilter { exception.message = 'Oops! Something went wrong. Please try again'; } - const errorResponse = { - statusCode: status, - message: exception.message || 'Internal server error', - error: exception.message - }; + let errorResponse; + if (exception && exception["statusCode"] === HttpStatus.INTERNAL_SERVER_ERROR) { + errorResponse = { + statusCode: status, + message: 'Oops! Something went wrong. Please try again', + error: 'Oops! Something went wrong. Please try again' + }; + } 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' + }; + } else { + if (exception && exception["response"] && exception.message) { - response.status(status).json(errorResponse); + if (Array.isArray(exception["response"].message)) { + exception["response"].message.forEach((msg) => { + errorResponse = { + statusCode: exception["statusCode"] ? exception["statusCode"] : status, + message: msg || 'Internal server error', + error: msg || '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' + }; + + } + } + + response.status(errorResponse.statusCode).json(errorResponse); } } \ No newline at end of file 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 55a083404..d4b2890b6 100644 --- a/apps/api-gateway/src/agent-service/agent-service.controller.ts +++ b/apps/api-gateway/src/agent-service/agent-service.controller.ts @@ -11,10 +11,10 @@ import { HttpStatus, Res, Get, - Query, - UseFilters + UseFilters, + Param } from '@nestjs/common'; -import { ApiTags, ApiResponse, ApiOperation, ApiUnauthorizedResponse, ApiForbiddenResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger'; +import { ApiTags, ApiResponse, ApiOperation, ApiUnauthorizedResponse, ApiForbiddenResponse, ApiBearerAuth } from '@nestjs/swagger'; import { GetUser } from '../authz/decorators/get-user.decorator'; import { AuthGuard } from '@nestjs/passport'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; @@ -30,43 +30,74 @@ import { user } from '@prisma/client'; import { CreateTenantDto } from './dto/create-tenant.dto'; import { User } from '../authz/decorators/user.decorator'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; +import { Roles } from '../authz/decorators/roles.decorator'; +import { OrgRoles } from 'libs/org-roles/enums'; +import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; +const seedLength = 32; @UseFilters(CustomExceptionFilter) -@Controller('agent-service') +@Controller() @ApiTags('agents') -@UseGuards(AuthGuard('jwt')) @ApiBearerAuth() @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) export class AgentController { constructor(private readonly agentService: AgentService) { } - private readonly logger = new Logger(); + @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: number, @Res() res: Response): Promise { + const agentData = await this.agentService.getAgentHealth(reqUser, orgId); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.agent.success.health, + data: agentData.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } + /** * * @param agentSpinupDto * @param user * @returns */ - @Post('/spinup') + @Post('/orgs/:orgId/agents/spinup') @ApiOperation({ summary: 'Agent spinup', description: 'Create a new agent spin up.' }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) async agentSpinup( @Body() agentSpinupDto: AgentSpinupDto, + @Param('orgId') orgId: number, @GetUser() user: user, @Res() res: Response ): Promise>> { + if (seedLength !== agentSpinupDto.seed.length) { + throw new BadRequestException(`seed must be at most 32 characters.`); + } + 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.log(`**** Spin up the agent...${JSON.stringify(agentSpinupDto)}`); + + agentSpinupDto.orgId = orgId; const agentDetails = await this.agentService.agentSpinup(agentSpinupDto, user); const finalResponse: IResponseType = { @@ -78,17 +109,27 @@ export class AgentController { return res.status(HttpStatus.CREATED).json(finalResponse); } - @Post('/tenant') + @Post('/orgs/:orgId/agents/wallet') @ApiOperation({ summary: 'Shared Agent', description: 'Create a shared agent.' }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) async createTenant( + @Param('orgId') orgId: number, @Body() createTenantDto: CreateTenantDto, @GetUser() user: user, @Res() res: Response ): Promise { + + createTenantDto.orgId = orgId; + + if (seedLength !== createTenantDto.seed.length) { + throw new BadRequestException(`seed must be at most 32 characters.`); + } + const tenantDetails = await this.agentService.createTenant(createTenantDto, user); const finalResponse: IResponseType = { @@ -99,28 +140,4 @@ export class AgentController { return res.status(HttpStatus.CREATED).json(finalResponse); } - - @Get('/health') - @ApiOperation({ - summary: 'Fetch agent details', - description: 'Fetch agent health details' - }) - @ApiQuery({ - name: 'orgId', - type: Number, - required: false - }) - async getAgentHealth(@User() reqUser: user, @Query('orgId') orgId: number, @Res() res: Response): Promise { - const agentData = await this.agentService.getAgentHealth(reqUser, orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.agent.success.health, - data: agentData.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - - } - -} +} \ No newline at end of file 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 c05383e9e..d8c449095 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 @@ -1,10 +1,12 @@ import { trim } from '@credebl/common/cast.helper'; -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, Matches, MaxLength, MinLength } from 'class-validator'; const regex = /^[a-zA-Z0-9 ]*$/; export class AgentSpinupDto { + orgId: number; + @ApiProperty() @Transform(({ value }) => trim(value)) @IsNotEmpty({ message: 'walletName is required'}) @@ -31,28 +33,28 @@ export class AgentSpinupDto { @Matches(/^\S*$/, { message: 'Spaces are not allowed in seed' }) - seed: string; - - @ApiProperty() - @IsNumber() - orgId: number; + seed: string; @ApiProperty() + @ApiPropertyOptional() @IsOptional() @IsNumber() ledgerId?: number; @ApiProperty() @IsOptional() + @ApiPropertyOptional() clientSocketId?: string; @ApiProperty() @IsOptional() - @IsBoolean() + @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.' }) @@ -62,10 +64,11 @@ export class AgentSpinupDto { @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; -} +} \ 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 6ed0aa6ad..73161436e 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 @@ -1,13 +1,13 @@ import { trim } from '@credebl/common/cast.helper'; -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { IsNotEmpty, IsNumber, IsString, Matches, MaxLength, MinLength, IsOptional } from 'class-validator'; +import { MaxLength, IsString, MinLength, Matches, IsNotEmpty, IsOptional } from 'class-validator'; const labelRegex = /^[a-zA-Z0-9 ]*$/; export class CreateTenantDto { @ApiProperty() - @IsString() - @Transform(({ value }) => value.trim()) @MaxLength(25, { message: 'Maximum length for label must be 25 characters.' }) + @IsString({ message: 'label must be in string format.' }) + @Transform(({ value }) => value.trim()) @MinLength(2, { message: 'Minimum length for label must be 2 characters.' }) @Matches(labelRegex, { message: 'Label must not contain special characters.' }) @Matches(/^\S*$/, { @@ -16,20 +16,19 @@ export class CreateTenantDto { label: string; @ApiProperty() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'seed is required' }) @MaxLength(32, { message: 'seed must be at most 32 characters.' }) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'seed is required'}) @IsString({ message: 'seed must be in string format.' }) @Matches(/^\S*$/, { message: 'Spaces are not allowed in seed' }) seed: string; - @ApiProperty() - @IsNumber() orgId: number; @ApiProperty() @IsOptional() + @ApiPropertyOptional() clientSocketId?: string; } \ 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 63b26cfa5..e1b9c3585 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -1,17 +1,130 @@ import { + Body, Controller, - Logger + Get, + HttpStatus, + Logger, + Post, + Query, + Res, + UnauthorizedException, + UseFilters } from '@nestjs/common'; import { AuthzService } from './authz.service'; -// import { CommonService } from "@credebl/common"; import { CommonService } from '../../../../libs/common/src/common.service'; +import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiResponseDto } from '../dtos/apiResponse.dto'; +import { UserEmailVerificationDto } from '../user/dto/create-user.dto'; +import IResponseType from '@credebl/common/interfaces/response.interface'; +import { ResponseMessages } from '@credebl/common/response-messages'; +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 { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; -@Controller('authz') +@Controller('auth') +@ApiTags('auth') +@UseFilters(CustomExceptionFilter) export class AuthzController { private logger = new Logger('AuthzController'); constructor(private readonly authzService: AuthzService, private readonly commonService: CommonService) { } -} + /** + * + * @param query + * @param res + * @returns User email verified + */ + @Get('/verify') + @ApiResponse({ status: 200, 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); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.emaiVerified + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } + + /** + * + * @param email + * @param res + * @returns Email sent success + */ + @Post('/verification-mail') + @ApiResponse({ status: 201, description: 'Success', 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); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.sendVerificationCode + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + /** + * + * @param email + * @param userInfo + * @param res + * @returns Add new user + */ + @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 decryptedPassword = this.commonService.decryptPassword(userInfo.password); + + userInfo.password = decryptedPassword; + const userDetails = await this.authzService.addUserDetails(userInfo); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userDetails.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + /** + * + * @param loginUserDto + * @param res + * @returns User 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 }) + @ApiBody({ type: LoginUserDto }) + async login(@Body() loginUserDto: LoginUserDto, @Res() res: Response): Promise { + + if (loginUserDto.email) { + let decryptedPassword; + if (loginUserDto.password) { + decryptedPassword = this.commonService.decryptPassword(loginUserDto.password); + } + const userData = await this.authzService.login(loginUserDto.email, decryptedPassword, loginUserDto.isPasskey); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.login, + data: userData.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } else { + throw new UnauthorizedException(`Please provide valid credentials`); + } + } + +} \ No newline at end of file diff --git a/apps/api-gateway/src/authz/authz.service.ts b/apps/api-gateway/src/authz/authz.service.ts index ab5cd587c..317712669 100644 --- a/apps/api-gateway/src/authz/authz.service.ts +++ b/apps/api-gateway/src/authz/authz.service.ts @@ -6,6 +6,9 @@ import { WebSocketServer } 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'; @Injectable() @@ -25,4 +28,24 @@ 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 verifyEmail(param: EmailVerificationDto): Promise { + const payload = { param }; + return this.sendNats(this.authServiceProxy, 'user-email-verification', payload); + + } + + async login(email: string, password?: string, isPasskey = false): Promise<{ response: object }> { + const payload = { email, password, isPasskey }; + return this.sendNats(this.authServiceProxy, 'user-holder-login', payload); + } + + async addUserDetails(userInfo: AddUserDetails): Promise<{ response: string }> { + const payload = { userInfo }; + return this.sendNats(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 0c6dd349e..46c60aec7 100644 --- a/apps/api-gateway/src/authz/guards/org-roles.guard.ts +++ b/apps/api-gateway/src/authz/guards/org-roles.guard.ts @@ -9,7 +9,7 @@ import { Reflector } from '@nestjs/core'; @Injectable() export class OrgRolesGuard implements CanActivate { - constructor(private reflector: Reflector) { } // eslint-disable-next-line array-callback-return + constructor(private reflector: Reflector) { } // eslint-disable-next-line array-callback-return private logger = new Logger('Org Role Guard'); @@ -29,8 +29,8 @@ export class OrgRolesGuard implements CanActivate { const { user } = req; - if (req.query.orgId || req.body.orgId) { - const orgId = req.query.orgId || req.body.orgId; + if (req.params.orgId || req.query.orgId || req.body.orgId) { + const orgId = req.params.orgId || req.query.orgId || req.body.orgId; const specificOrg = user.userOrgRoles.find((orgDetails) => { if (!orgDetails.orgId) { @@ -44,12 +44,17 @@ export class OrgRolesGuard implements CanActivate { } user.selectedOrg = specificOrg; - user.selectedOrg.orgRoles = user.userOrgRoles.map(roleItem => roleItem.orgRole.name); - + // eslint-disable-next-line array-callback-return + user.selectedOrg.orgRoles = user.userOrgRoles.map((orgRoleItem) => { + if (orgRoleItem.orgId && orgRoleItem.orgId.toString() === orgId.toString()) { + return orgRoleItem.orgRole.name; + } + }); + } else { throw new HttpException('organization is required', HttpStatus.BAD_REQUEST); } return requiredRoles.some((role) => user.selectedOrg?.orgRoles.includes(role)); } -} +} \ No newline at end of file diff --git a/apps/api-gateway/src/dtos/email-validator.dto.ts b/apps/api-gateway/src/dtos/email-validator.dto.ts new file mode 100644 index 000000000..c41ad0ef5 --- /dev/null +++ b/apps/api-gateway/src/dtos/email-validator.dto.ts @@ -0,0 +1,12 @@ + +import { ApiProperty } from '@nestjs/swagger'; +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() + email: string; +} \ No newline at end of file diff --git a/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts b/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts index a6c1b43c8..df6b85aae 100644 --- a/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts +++ b/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts @@ -1,5 +1,5 @@ import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsEmail, IsNotEmpty, IsNumber, IsString, ValidateNested } from 'class-validator'; +import { IsArray, IsEmail, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; import { Transform, Type } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; @@ -29,9 +29,5 @@ export class BulkSendInvitationDto { @ValidateNested({ each: true }) @Type(() => SendInvitationDto) invitations: SendInvitationDto[]; - - @ApiProperty({ example: 1 }) - @IsNotEmpty({ message: 'Please provide valid orgId' }) - @IsNumber() orgId: number; } \ No newline at end of file diff --git a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts index 1229ddc58..c3fb2d6aa 100644 --- a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts +++ b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts @@ -1,5 +1,5 @@ import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; +import { IsNotEmpty, IsOptional, IsString, IsBoolean, MaxLength, MinLength } from 'class-validator'; import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; @@ -7,9 +7,7 @@ import { trim } from '@credebl/common/cast.helper'; @ApiExtraModels() export class UpdateOrganizationDto { - @ApiProperty() - @IsNotEmpty({ message: 'orgId is required.' }) - @IsNumber() + orgId: number; @ApiProperty() @@ -40,6 +38,6 @@ export class UpdateOrganizationDto { @ApiPropertyOptional({ example: true }) @IsBoolean({ message: 'isPublic should be boolean' }) @IsOptional() - isPublic? = false; + isPublic?: boolean = false; } \ No newline at end of file diff --git a/apps/api-gateway/src/organization/dtos/update-user-roles.dto.ts b/apps/api-gateway/src/organization/dtos/update-user-roles.dto.ts index 5ddb6a9f8..f1270f88b 100644 --- a/apps/api-gateway/src/organization/dtos/update-user-roles.dto.ts +++ b/apps/api-gateway/src/organization/dtos/update-user-roles.dto.ts @@ -1,21 +1,11 @@ -import { IsArray, IsNotEmpty, IsNumber } from 'class-validator'; +import { IsArray, IsNotEmpty } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { toNumber } from '@credebl/common/cast.helper'; export class UpdateUserRolesDto { - @ApiProperty({ example: '2' }) - @IsNotEmpty({ message: 'Please provide valid orgId' }) - @Transform(({ value }) => toNumber(value)) - @IsNumber() - orgId: number; - @ApiProperty({ example: '3' }) - @IsNotEmpty({ message: 'Please provide valid userId' }) - @Transform(({ value }) => toNumber(value)) - @IsNumber() + orgId: number; userId: number; @ApiProperty({ example: [2, 1, 3] }) diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 32d4cec98..667c7bb26 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -26,9 +26,11 @@ import { GetAllOrganizationsDto } from './dtos/get-all-organizations.dto'; import { GetAllSentInvitationsDto } from './dtos/get-all-sent-invitations.dto'; import { UpdateOrganizationDto } from './dtos/update-organization-dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; +import { IUserRequestInterface } from '../interfaces/IUserRequestInterface'; +import { GetAllUsersDto } from '../user/dto/get-all-users.dto'; @UseFilters(CustomExceptionFilter) -@Controller('organization') +@Controller('orgs') @ApiTags('organizations') @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @@ -39,39 +41,6 @@ export class OrganizationController { private readonly commonService: CommonService ) { } - @Post('/') - @ApiOperation({ summary: 'Create a new Organization', description: 'Create an organization' }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - 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, - message: ResponseMessages.organisation.success.create - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - - @Get('/') - @ApiOperation({ summary: 'Get all organizations', description: 'Get all organizations' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - 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 - }; - return res.status(HttpStatus.OK).json(finalResponse); - - } - /** * * @param user @@ -79,9 +48,9 @@ export class OrganizationController { * @param res * @returns Users list of organization */ - @Get('/public-profiles') + @Get('/public-profile') @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ summary: 'Get public organization list', description: 'Get users list.' }) + @ApiOperation({ summary: 'Get all public profile of organizations', description: 'Get all public profile of organizations.' }) @ApiQuery({ name: 'pageNumber', type: Number, @@ -109,6 +78,28 @@ export class OrganizationController { return res.status(HttpStatus.OK).json(finalResponse); } + @Get('/roles') + @ApiOperation({ + summary: 'Fetch org-roles details', + description: 'Fetch org-roles details' + }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async getOrgRoles(@Res() res: Response): Promise { + + const orgRoles = await this.organizationService.getOrgRoles(); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.organisation.success.fetchOrgRoles, + data: orgRoles + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } + @Get('public-profiles/:orgSlug') @ApiOperation({ summary: 'Fetch user details', @@ -133,135 +124,221 @@ export class OrganizationController { } - @Get('/roles') - @ApiOperation({ - summary: 'Fetch org-roles details', - description: 'Fetch org-roles details' - }) + + @Get('/dashboard/:orgId') + @ApiOperation({ summary: 'Get an organization', description: 'Get an organization' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async getOrgRoles(@Res() res: Response): Promise { - const orgRoles = await this.organizationService.getOrgRoles(); + async getOrganizationDashboard(@Param('orgId') orgId: number, @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.fetchOrgRoles, - data: orgRoles + message: ResponseMessages.organisation.success.getOrgDashboard, + data: getOrganization.response }; - return res.status(HttpStatus.OK).json(finalResponse); } - @Post('/invitations') - @ApiOperation({ - summary: 'Create organization invitation', - description: 'Create send invitation' - }) + @Get('/:orgId/invitations') + @ApiOperation({ summary: 'Get an invitations', description: 'Get an invitations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() - async createInvitation(@Body() bulkInvitationDto: BulkSendInvitationDto, @User() user: user, @Res() res: Response): Promise { - await this.organizationService.createInvitation(bulkInvitationDto, user.id); + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + async getInvitationsByOrgId(@Param('orgId') orgId: number, @Query() getAllInvitationsDto: GetAllSentInvitationsDto, @Res() res: Response): Promise { + + const getInvitationById = await this.organizationService.getInvitationsByOrgId(orgId, getAllInvitationsDto); const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.organisation.success.createInvitation + statusCode: HttpStatus.OK, + message: ResponseMessages.organisation.success.getInvitation, + data: getInvitationById.response }; - - return res.status(HttpStatus.CREATED).json(finalResponse); + return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/invitations/:id') - @ApiOperation({ summary: 'Get an invitations', description: 'Get an invitations' }) + @Get('/') + @ApiOperation({ summary: 'Get all organizations', description: 'Get all organizations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async getInvitationsByOrgId(@Param('id') orgId: number, @Query() getAllInvitationsDto: GetAllSentInvitationsDto, @Res() res: Response): Promise { + @ApiQuery({ + name: 'pageNumber', + example: '1', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + example: '10', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + example: '', + type: String, + required: false + }) + async getOrganizations(@Query() getAllOrgsDto: GetAllOrganizationsDto, @Res() res: Response, @User() reqUser: user): Promise { - const getInvitationById = await this.organizationService.getInvitationsByOrgId(orgId, getAllInvitationsDto); + const getOrganizations = await this.organizationService.getOrganizations(getAllOrgsDto, reqUser.id); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.organisation.success.getInvitation, - data: getInvitationById.response + message: ResponseMessages.organisation.success.getOrganizations, + data: getOrganizations.response }; return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/dashboard') + @Get('/:orgId') @ApiOperation({ summary: 'Get an organization', description: 'Get an organization' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() - @ApiQuery( - { name: 'orgId', required: true } - ) - async getOrganizationDashboard(@Query('orgId') orgId: number, @Res() res: Response, @User() reqUser: user): Promise { + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + async getOrganization(@Param('orgId') orgId: number, @Res() res: Response, @User() reqUser: user): Promise { - const getOrganization = await this.organizationService.getOrganizationDashboard(orgId, reqUser.id); + const getOrganization = await this.organizationService.getOrganization(orgId, reqUser.id); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.organisation.success.getOrgDashboard, + message: ResponseMessages.organisation.success.getOrganization, data: getOrganization.response }; return res.status(HttpStatus.OK).json(finalResponse); } - - @Put('user-roles') - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + /** + * + * @param user + * @param orgId + * @param res + * @returns Users list of organization + */ + @Get('/:orgId/users') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) @ApiBearerAuth() @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, @Res() res: Response): Promise { - - await this.organizationService.updateUserRoles(updateUserDto, updateUserDto.userId); - + @ApiOperation({ summary: 'Get organization users list', description: 'Get organization users list.' }) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Param('orgId') orgId: number, @Res() res: Response): Promise { + const users = await this.organizationService.getOrgUsers(orgId, getAllUsersDto); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.organisation.success.updateUserRoles + message: ResponseMessages.user.success.fetchUsers, + data: users?.response }; return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/:id') - @ApiOperation({ summary: 'Get an organization', description: 'Get an organization' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @Post('/') + @ApiOperation({ summary: 'Create a new Organization', description: 'Create an organization' }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async getOrganization(@Param('id') orgId: number, @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, + message: ResponseMessages.organisation.success.create + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } - const getOrganization = await this.organizationService.getOrganization(orgId, reqUser.id); + @Post('/:orgId/invitations') + @ApiOperation({ + summary: 'Create organization invitation', + description: 'Create send invitation' + }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiBearerAuth() + async createInvitation(@Body() bulkInvitationDto: BulkSendInvitationDto, @Param('orgId') orgId: number, @User() user: user, @Res() res: Response): Promise { + + bulkInvitationDto.orgId = orgId; + await this.organizationService.createInvitation(bulkInvitationDto, user.id); const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.organisation.success.getOrganization, - data: getOrganization.response + statusCode: HttpStatus.CREATED, + message: ResponseMessages.organisation.success.createInvitation }; - return res.status(HttpStatus.OK).json(finalResponse); + + return res.status(HttpStatus.CREATED).json(finalResponse); } + @Put('/:orgId/user-roles/:userId') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @ApiBearerAuth() + @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: number, @Param('userId') userId: number, @Res() res: Response): Promise { + + updateUserDto.orgId = orgId; + updateUserDto.userId = userId; + await this.organizationService.updateUserRoles(updateUserDto, updateUserDto.userId); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.organisation.success.updateUserRoles + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } - @Put('/') + @Put('/:orgId') @ApiOperation({ summary: 'Update Organization', description: 'Update an organization' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async updateOrganization(@Body() updateOrgDto: UpdateOrganizationDto, @Res() res: Response, @User() reqUser: user): Promise { + async updateOrganization(@Body() updateOrgDto: UpdateOrganizationDto, @Param('orgId') orgId: number, @Res() res: Response, @User() reqUser: user): Promise { - await this.organizationService.updateOrganization(updateOrgDto, reqUser.id); + updateOrgDto.orgId = orgId; + await this.organizationService.updateOrganization(updateOrgDto, reqUser.id, orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, @@ -269,4 +346,4 @@ export class OrganizationController { }; 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 ba0d6f96d..c7579dcf5 100644 --- a/apps/api-gateway/src/organization/organization.service.ts +++ b/apps/api-gateway/src/organization/organization.service.ts @@ -1,6 +1,6 @@ import { Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; -import { ClientProxy, RpcException } from '@nestjs/microservices'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { CreateOrganizationDto } from './dtos/create-organization-dto'; import { GetAllOrganizationsDto } from './dtos/get-all-organizations.dto'; @@ -8,6 +8,7 @@ import { GetAllSentInvitationsDto } from './dtos/get-all-sent-invitations.dto'; 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'; @Injectable() export class OrganizationService extends BaseService { @@ -21,13 +22,8 @@ export class OrganizationService extends BaseService { * @returns Organization creation Success */ async createOrganization(createOrgDto: CreateOrganizationDto, userId: number): Promise { - try { - const payload = { createOrgDto, userId }; - return this.sendNats(this.serviceProxy, 'create-organization', payload); - } catch (error) { - this.logger.error(`In service Error: ${error}`); - throw new RpcException(error.response); - } + const payload = { createOrgDto, userId }; + return this.sendNats(this.serviceProxy, 'create-organization', payload); } /** @@ -35,14 +31,9 @@ export class OrganizationService extends BaseService { * @param updateOrgDto * @returns Organization update Success */ - async updateOrganization(updateOrgDto: UpdateOrganizationDto, userId: number): Promise { - try { - const payload = { updateOrgDto, userId }; - return this.sendNats(this.serviceProxy, 'update-organization', payload); - } catch (error) { - this.logger.error(`In service Error: ${error}`); - throw new RpcException(error.response); - } + async updateOrganization(updateOrgDto: UpdateOrganizationDto, userId: number, orgId: number): Promise { + const payload = { updateOrgDto, userId, orgId }; + return this.sendNats(this.serviceProxy, 'update-organization', payload); } /** @@ -66,7 +57,7 @@ export class OrganizationService extends BaseService { } async getPublicProfile(orgSlug: string): Promise<{ response: object }> { - const payload = {orgSlug }; + const payload = { orgSlug }; try { return this.sendNats(this.serviceProxy, 'get-organization-public-profile', payload); } catch (error) { @@ -109,13 +100,8 @@ export class OrganizationService extends BaseService { * @returns get organization roles */ async getOrgRoles(): Promise { - try { - const payload = {}; - return this.sendNats(this.serviceProxy, 'get-org-roles', payload); - } catch (error) { - this.logger.error(`In service Error: ${error}`); - throw new RpcException(error.response); - } + const payload = {}; + return this.sendNats(this.serviceProxy, 'get-org-roles', payload); } /** @@ -124,13 +110,8 @@ export class OrganizationService extends BaseService { * @returns Organization invitation creation Success */ async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: number): Promise { - try { - const payload = { bulkInvitationDto, userId }; - return this.sendNats(this.serviceProxy, 'send-invitation', payload); - } catch (error) { - this.logger.error(`In service Error: ${error}`); - throw new RpcException(error.response); - } + const payload = { bulkInvitationDto, userId }; + return this.sendNats(this.serviceProxy, 'send-invitation', payload); } /** @@ -143,4 +124,14 @@ export class OrganizationService extends BaseService { const payload = { orgId: updateUserDto.orgId, roleIds: updateUserDto.orgRoleId, userId }; return this.sendNats(this.serviceProxy, 'update-user-roles', payload); } + + async getOrgUsers( + orgId: number, + getAllUsersDto: GetAllUsersDto + ): Promise<{ response: object }> { + const { pageNumber, pageSize, search } = getAllUsersDto; + const payload = { orgId, pageNumber, pageSize, search }; + + return this.sendNats(this.serviceProxy, 'fetch-organization-user', payload); + } } diff --git a/apps/api-gateway/src/user/dto/accept-reject-invitation.dto.ts b/apps/api-gateway/src/user/dto/accept-reject-invitation.dto.ts index e3d40e7ea..6d16ed0d9 100644 --- a/apps/api-gateway/src/user/dto/accept-reject-invitation.dto.ts +++ b/apps/api-gateway/src/user/dto/accept-reject-invitation.dto.ts @@ -6,10 +6,6 @@ import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; export class AcceptRejectInvitationDto { - - @ApiProperty({ example: 1 }) - @IsNotEmpty({ message: 'Please provide valid invitationId' }) - @IsNumber() invitationId: number; @ApiProperty({ example: 1 }) @@ -25,4 +21,4 @@ export class AcceptRejectInvitationDto { @IsEnum(Invitation) status: Invitation.ACCEPTED | Invitation.REJECTED; -} +} \ 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 a72eb45eb..05983a924 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,17 @@ import { trim } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { IsBoolean, IsNotEmpty, IsOptional, IsString} from 'class-validator'; +import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class AddUserDetails { + + @ApiProperty({ example: 'awqx@getnada.com' }) + @IsEmail() + @IsNotEmpty({ message: 'Please provide valid email' }) + @IsString({ message: 'email should be string' }) + @IsOptional() + email?: string; + @ApiProperty({ example: 'Alen' }) @IsString({ message: 'firstName should be string' }) @IsOptional() 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 09c5cafaf..f62980e6e 100644 --- a/apps/api-gateway/src/user/dto/login-user.dto.ts +++ b/apps/api-gateway/src/user/dto/login-user.dto.ts @@ -1,8 +1,8 @@ -import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; +import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; +import { trim } from '@credebl/common/cast.helper'; export class LoginUserDto { @ApiProperty({ example: 'awqx@getnada.com' }) @@ -21,29 +21,4 @@ export class LoginUserDto { @IsOptional() @IsBoolean({ message: 'isPasskey should be boolean' }) isPasskey: boolean; -} - -export class AddUserDetails { - @ApiProperty({ example: 'Alen' }) - @IsString({ message: 'firstName should be string' }) - @IsOptional() - firstName?: string; - - @ApiProperty({ example: 'Harvey' }) - @IsString({ message: 'lastName should be string' }) - @IsOptional() - lastName?: string; - - @ApiProperty() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'Password is required.' }) - @MinLength(8, { message: 'Password must be at least 8 characters.' }) - @MaxLength(50, { message: 'Password must be at most 50 characters.' }) - @IsOptional() - password?: string; - - @ApiProperty({ example: 'false' }) - @IsOptional() - @IsBoolean({ message: 'isPasskey should be boolean' }) - isPasskey?: boolean; -} +} \ No newline at end of file diff --git a/apps/api-gateway/src/user/dto/update-user-profile.dto.ts b/apps/api-gateway/src/user/dto/update-user-profile.dto.ts index 41484636c..e4f39fef5 100644 --- a/apps/api-gateway/src/user/dto/update-user-profile.dto.ts +++ b/apps/api-gateway/src/user/dto/update-user-profile.dto.ts @@ -1,15 +1,13 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsOptional, IsString, IsBoolean } from 'class-validator'; + export class UpdateUserProfileDto { - @ApiProperty() - @IsNotEmpty({ message: 'userId is required.' }) - @IsNumber() id: number; @ApiPropertyOptional() @IsOptional() - @IsString({message:'ProfileLogoUrl should be string'}) + @IsString({ message: 'ProfileLogoUrl should be string' }) profileImg?: string; @ApiProperty({ example: 'Alen' }) @@ -25,5 +23,5 @@ export class UpdateUserProfileDto { @ApiPropertyOptional({ example: true }) @IsBoolean({ message: 'isPublic should be boolean' }) @IsOptional() - isPublic? = false; + isPublic?: boolean = false; } \ No newline at end of file diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 953c57cf4..8c9a25640 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -1,9 +1,7 @@ import { Controller, Post, Put, Body, Param, UseFilters } from '@nestjs/common'; import { UserService } from './user.service'; -import { UserEmailVerificationDto } from './dto/create-user.dto'; import { ApiBearerAuth, - ApiBody, ApiForbiddenResponse, ApiOperation, ApiParam, @@ -21,11 +19,7 @@ import { HttpStatus } from '@nestjs/common'; import { CommonService } from '@credebl/common'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { BadRequestException } from '@nestjs/common'; -import { AuthTokenResponse } from '../authz/dtos/auth-token-res.dto'; -import { LoginUserDto } from './dto/login-user.dto'; -import { UnauthorizedException } from '@nestjs/common'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { EmailVerificationDto } from './dto/email-verify.dto'; import { Get } from '@nestjs/common'; import { Query } from '@nestjs/common'; import { user } from '@prisma/client'; @@ -34,15 +28,13 @@ import { AuthGuard } from '@nestjs/passport'; import { User } from '../authz/decorators/user.decorator'; import { AcceptRejectInvitationDto } from './dto/accept-reject-invitation.dto'; import { Invitation } from '@credebl/enum/enum'; -import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; -import { Roles } from '../authz/decorators/roles.decorator'; -import { OrgRoles } from 'libs/org-roles/enums'; import { IUserRequestInterface } from './interfaces'; import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; import { GetAllUsersDto } from './dto/get-all-users.dto'; -import { AddPasskeyDetails, AddUserDetails } from './dto/add-user.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 { EmailValidator } from '../dtos/email-validator.dto'; @UseFilters(CustomExceptionFilter) @Controller('users') @@ -52,66 +44,6 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler export class UserController { constructor(private readonly userService: UserService, private readonly commonService: CommonService) { } - /** - * - * @param email - * @param res - * @returns Email sent success - */ - @Post('/send-mail') - @ApiResponse({ status: 201, description: 'Success', 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.userService.sendVerificationMail(userEmailVerificationDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.sendVerificationCode - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - - /** - * - * @param user - * @param orgId - * @param res - * @returns Users list of organization - */ - @Get() - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ summary: 'Get organization users list', description: 'Get organization users list.' }) - @ApiQuery({ - name: 'pageNumber', - type: Number, - required: false - }) - @ApiQuery({ - name: 'pageSize', - type: Number, - required: false - }) - @ApiQuery({ - name: 'search', - type: String, - required: false - }) - async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() getAllUsersDto: GetAllUsersDto, @Query('orgId') orgId: number, @Res() res: Response): Promise { - - const org = user.selectedOrg?.orgId; - const users = await this.userService.getOrgUsers(org, getAllUsersDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchUsers, - data: users.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } - - /** * * @param user @@ -149,60 +81,30 @@ export class UserController { return res.status(HttpStatus.OK).json(finalResponse); } + @Get('public-profiles/:username') + @ApiOperation({ + summary: 'Fetch user details', + description: 'Fetch user details' + }) + @ApiParam({ + name: 'username', + type: String, + required: false + }) + async getPublicProfile(@Param('username') username: string, @Res() res: Response): Promise { + const userData = await this.userService.getPublicProfile(username); - /** - * - * @param query - * @param res - * @returns User email verified - */ - @Get('/verify') - @ApiOperation({ summary: 'Verify new users email', description: 'Email verification for new users' }) - async verifyEmail(@Query() query: EmailVerificationDto, @Res() res: Response): Promise { - await this.userService.verifyEmail(query); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.emaiVerified + message: ResponseMessages.user.success.fetchProfile, + data: userData.response }; return res.status(HttpStatus.OK).json(finalResponse); } - /** - * - * @param loginUserDto - * @param res - * @returns User access token details - */ - @Post('/login') - @ApiOperation({ - summary: 'Login API for web portal', - description: 'Password should be AES encrypted.' - }) - @ApiResponse({ status: 200, description: 'Success', type: AuthTokenResponse }) - @ApiBody({ type: LoginUserDto }) - async login(@Body() loginUserDto: LoginUserDto, @Res() res: Response): Promise { - - if (loginUserDto.email) { - let decryptedPassword; - if (loginUserDto.password) { - decryptedPassword = this.commonService.decryptPassword(loginUserDto.password); - } - const userData = await this.userService.login(loginUserDto.email, decryptedPassword, loginUserDto.isPasskey); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.login, - data: userData.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } else { - throw new UnauthorizedException(`Please provide valid credentials`); - } - } - - @Get('profile') + @Get('/profile') @ApiOperation({ summary: 'Fetch login user details', description: 'Fetch login user details' @@ -223,37 +125,56 @@ export class UserController { } - @Get('public-profiles/:username') + @Get('/activity') @ApiOperation({ - summary: 'Fetch user details', - description: 'Fetch user details' - }) - @ApiParam({ - name: 'username', - type: String, - required: false + summary: 'organization invitations', + description: 'Fetch organization invitations' }) - async getPublicProfile(@Param('username') username: string, @Res() res: Response): Promise { - const userData = await this.userService.getPublicProfile(username); + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + @ApiQuery({ name: 'limit', required: true }) + async getUserActivities(@Query('limit') limit: number, @Res() res: Response, @User() reqUser: user): Promise { + + const userDetails = await this.userService.getUserActivities(reqUser.id, limit); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchProfile, - data: userData.response + message: ResponseMessages.user.success.userActivity, + data: userDetails.response }; return res.status(HttpStatus.OK).json(finalResponse); - } - @Get('invitations') + + @Get('/org-invitations') @ApiOperation({ summary: 'organization invitations', description: 'Fetch organization invitations' }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async invitations(@User() reqUser: user, @Query() getAllInvitationsDto: GetAllInvitationsDto, @Res() res: Response): Promise { + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + @ApiQuery({ + name: 'status', + type: String, + required: false + }) + async invitations(@Query() getAllInvitationsDto: GetAllInvitationsDto, @User() reqUser: user, @Res() res: Response): Promise { if (!Object.values(Invitation).includes(getAllInvitationsDto.status)) { throw new BadRequestException(ResponseMessages.user.error.invalidInvitationStatus); @@ -271,6 +192,26 @@ export class UserController { } + /** + * + * @param email + * @param res + * @returns User email check + */ + @Get('/:email') + @ApiOperation({ summary: 'Check 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 = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.checkEmail, + data: userDetails.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + + } /** * @@ -279,84 +220,23 @@ export class UserController { * @param res * @returns Organization invitation status */ - @Post('invitations') + @Post('/org-invitations/:invitationId') @ApiOperation({ summary: 'accept/reject organization invitation', description: 'Accept or Reject organization invitations' }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async acceptRejectInvitaion(@Body() acceptRejectInvitation: AcceptRejectInvitationDto, @User() reqUser: user, @Res() res: Response): Promise { + async acceptRejectInvitaion(@Body() acceptRejectInvitation: AcceptRejectInvitationDto, @Param('invitationId') invitationId: string, @User() reqUser: user, @Res() res: Response): Promise { + acceptRejectInvitation.invitationId = parseInt(invitationId); const invitationRes = await this.userService.acceptRejectInvitaion(acceptRejectInvitation, reqUser.id); const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, + statusCode: HttpStatus.CREATED, message: invitationRes.response }; - return res.status(HttpStatus.OK).json(finalResponse); - - } - - /** - * - * @param email - * @param res - * @returns User email check - */ - @Get('/check-user/:email') - @ApiOperation({ summary: 'Check user exist', description: 'check user existence' }) - async checkUserExist(@Param('email') email: string, @Res() res: Response): Promise { - const userDetails = await this.userService.checkUserExist(email); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.checkEmail, - data: userDetails.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - - } - - /** - * - * @param email - * @param userInfo - * @param res - * @returns Add new user - */ - @Post('/add/:email') - @ApiOperation({ summary: 'Add user information', description: 'Add user information' }) - async addUserDetailsInKeyCloak(@Body() userInfo: AddUserDetails, @Param('email') email: string, @Res() res: Response): Promise { - let finalResponse; - let userDetails; - - if (false === userInfo.isPasskey) { - - const decryptedPassword = this.commonService.decryptPassword(userInfo.password); - if (8 <= decryptedPassword.length && 50 >= decryptedPassword.length) { - this.commonService.passwordValidation(decryptedPassword); - userInfo.password = decryptedPassword; - userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); - finalResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.user.success.create, - data: userDetails.response - }; - } else { - throw new BadRequestException('Password name must be between 8 to 50 Characters'); - } - } else { - - userDetails = await this.userService.addUserDetailsInKeyCloak(email, userInfo); - finalResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.user.success.create, - data: userDetails.response - }; - } - return res.status(HttpStatus.OK).json(finalResponse); + return res.status(HttpStatus.CREATED).json(finalResponse); } @@ -368,8 +248,10 @@ export class UserController { @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) - async updateUserProfile(@Body() updateUserProfileDto: UpdateUserProfileDto, @Res() res: Response): Promise { + async updateUserProfile(@Body() updateUserProfileDto: UpdateUserProfileDto, @User() reqUser: user, @Res() res: Response): Promise { + const userId = reqUser.id; + updateUserProfileDto.id = userId; await this.userService.updateUserProfile(updateUserProfileDto); const finalResponse: IResponseType = { @@ -380,36 +262,15 @@ export class UserController { } - @Get('/activity') - @ApiOperation({ - summary: 'organization invitations', - description: 'Fetch organization invitations' - }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiQuery({ name: 'limit', required: true }) - async getUserActivities(@Query('limit') limit: number, @Res() res: Response, @User() reqUser: user): Promise { - - const userDetails = await this.userService.getUserActivities(reqUser.id, limit); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: 'User activities fetched successfully', - data: userDetails.response - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } - - @Post('/password/:email') - @ApiOperation({ summary: 'Add user information', description: 'Add user information' }) + @Put('/password/:email') + @ApiOperation({ summary: 'Store user password details', description: 'Store user password details' }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() async addPasskey(@Body() userInfo: AddPasskeyDetails, @Param('email') email: string, @Res() res: Response): Promise { const userDetails = await this.userService.addPasskey(email, userInfo); const finalResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.user.success.create, + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.update, data: userDetails.response }; diff --git a/apps/api-gateway/src/user/user.service.ts b/apps/api-gateway/src/user/user.service.ts index 9e97e50ac..63e8ecc9e 100644 --- a/apps/api-gateway/src/user/user.service.ts +++ b/apps/api-gateway/src/user/user.service.ts @@ -1,89 +1,42 @@ import { Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; -import { ClientProxy, RpcException } from '@nestjs/microservices'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { AcceptRejectInvitationDto } from './dto/accept-reject-invitation.dto'; -import { UserEmailVerificationDto } from './dto/create-user.dto'; -import { EmailVerificationDto } from './dto/email-verify.dto'; import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; -import { AddUserDetails } from './dto/login-user.dto'; import { GetAllUsersDto } from './dto/get-all-users.dto'; import { UpdateUserProfileDto } from './dto/update-user-profile.dto'; import { AddPasskeyDetails } from './dto/add-user.dto'; - @Injectable() export class UserService extends BaseService { constructor(@Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy) { super('User Service'); } - async sendVerificationMail(userEmailVerificationDto: UserEmailVerificationDto): Promise { - try { - const payload = { userEmailVerificationDto }; - return this.sendNats(this.serviceProxy, 'send-verification-mail', payload); - } catch (error) { - throw new RpcException(error.response); - } - } - - async login(email: string, password?: string, isPasskey = false): Promise<{ response: object }> { - try { - const payload = { email, password, isPasskey }; - return this.sendNats(this.serviceProxy, 'user-holder-login', payload); - } catch (error) { - throw new RpcException(error.response); - } - } - async verifyEmail(param: EmailVerificationDto): Promise { - try { - const payload = { param }; - return this.sendNats(this.serviceProxy, 'user-email-verification', payload); - } catch (error) { - throw new RpcException(error.response); - } - } - async getProfile(id: number): Promise<{ response: object }> { const payload = { id }; - try { - return this.sendNats(this.serviceProxy, 'get-user-profile', payload); - } catch (error) { - this.logger.error(`Error in get user:${JSON.stringify(error)}`); - } + return this.sendNats(this.serviceProxy, 'get-user-profile', payload); } async getPublicProfile(username: string): Promise<{ response: object }> { const payload = { username }; - try { - return this.sendNats(this.serviceProxy, 'get-user-public-profile', payload); - } catch (error) { - this.logger.error(`Error in get user:${JSON.stringify(error)}`); - } + return this.sendNats(this.serviceProxy, 'get-user-public-profile', payload); } async updateUserProfile(updateUserProfileDto: UpdateUserProfileDto): Promise<{ response: object }> { - const payload = {updateUserProfileDto }; - try { - return this.sendNats(this.serviceProxy, 'update-user-profile', payload); - } catch (error) { - throw new RpcException(error.response); - } + const payload = { updateUserProfileDto }; + return this.sendNats(this.serviceProxy, 'update-user-profile', payload); } - + async findUserinSupabase(id: string): Promise<{ response: object }> { const payload = { id }; - - try { - return this.sendNats(this.serviceProxy, 'get-user-by-supabase', payload); - } catch (error) { - this.logger.error(`Error in get user:${JSON.stringify(error)}`); - } + return this.sendNats(this.serviceProxy, 'get-user-by-supabase', payload); } async invitations(id: number, status: string, getAllInvitationsDto: GetAllInvitationsDto): Promise<{ response: object }> { - const {pageNumber, pageSize, search} = getAllInvitationsDto; + const { pageNumber, pageSize, search } = getAllInvitationsDto; const payload = { id, status, pageNumber, pageSize, search }; return this.sendNats(this.serviceProxy, 'get-org-invitations', payload); } @@ -96,39 +49,25 @@ export class UserService extends BaseService { return this.sendNats(this.serviceProxy, 'accept-reject-invitations', payload); } - async getOrgUsers( - orgId: number, - getAllUsersDto: GetAllUsersDto - ): Promise<{ response: object }> { - const {pageNumber, pageSize, search} = getAllUsersDto; - const payload = { orgId, pageNumber, pageSize, search }; - return this.sendNats(this.serviceProxy, 'fetch-organization-users', payload); - } - async get( getAllUsersDto: GetAllUsersDto ): Promise<{ response: object }> { - const {pageNumber, pageSize, search} = getAllUsersDto; + const { pageNumber, pageSize, search } = getAllUsersDto; const payload = { pageNumber, pageSize, search }; return this.sendNats(this.serviceProxy, 'fetch-users', payload); } - + async checkUserExist(userEmail: string): Promise<{ response: string }> { const payload = { userEmail }; return this.sendNats(this.serviceProxy, 'check-user-exist', payload); } - async addUserDetailsInKeyCloak(userEmail: string, userInfo:AddUserDetails): Promise<{ response: string }> { - const payload = { userEmail, userInfo }; - return this.sendNats(this.serviceProxy, 'add-user', payload); - } - async getUserActivities(userId: number, limit: number): Promise<{ response: object }> { const payload = { userId, limit }; return this.sendNats(this.serviceProxy, 'get-user-activity', payload); } - async addPasskey(userEmail: string, userInfo:AddPasskeyDetails): Promise<{ response: string }> { + async addPasskey(userEmail: string, userInfo: AddPasskeyDetails): Promise<{ response: string }> { const payload = { userEmail, userInfo }; return this.sendNats(this.serviceProxy, 'add-passkey', payload); } diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 0ebb10fb3..4ad0bd967 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -263,9 +263,9 @@ export class OrganizationRepository { } }, userOrgRoles: { - include:{ + include: { user: true, - orgRole:true + orgRole: true } } } @@ -413,4 +413,24 @@ export class OrganizationRepository { throw new InternalServerErrorException(error); } } + + /** + * + * @param name + * @returns Organization exist details + */ + + async checkOrganizationExist(name: string, orgId: number): Promise { + try { + return this.prisma.organisation.findMany({ + where: { + id: orgId, + name + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } } diff --git a/apps/organization/src/organization.controller.ts b/apps/organization/src/organization.controller.ts index 85edbe3c9..bbe5bfd8b 100644 --- a/apps/organization/src/organization.controller.ts +++ b/apps/organization/src/organization.controller.ts @@ -31,8 +31,8 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'update-organization' }) - async updateOrganization(payload: { updateOrgDto: IUpdateOrganization; userId: number }): Promise { - return this.organizationService.updateOrganization(payload.updateOrgDto, payload.userId); + async updateOrganization(payload: { updateOrgDto: IUpdateOrganization; userId: number, orgId: number }): Promise { + return this.organizationService.updateOrganization(payload.updateOrgDto, payload.userId, payload.orgId); } /** diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index e5670064f..1ba6eea59 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -60,7 +60,7 @@ export class OrganizationService { return organizationDetails; } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -85,13 +85,16 @@ export class OrganizationService { */ // eslint-disable-next-line camelcase - async updateOrganization(updateOrgDto: IUpdateOrganization, userId: number): Promise { + async updateOrganization(updateOrgDto: IUpdateOrganization, userId: number, orgId: number): Promise { try { - const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); - - if (organizationExist && organizationExist.id !== Number(updateOrgDto.orgId)) { - throw new ConflictException(ResponseMessages.organisation.error.exists); + const organizationExist = await this.organizationRepository.checkOrganizationExist(updateOrgDto.name, orgId); + + if (0 === organizationExist.length) { + const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); + if (organizationExist) { + throw new ConflictException(ResponseMessages.organisation.error.exists); + } } const orgSlug = await this.createOrgSlug(updateOrgDto.name); @@ -102,7 +105,7 @@ export class OrganizationService { return organizationDetails; } catch (error) { this.logger.error(`In update organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -138,7 +141,7 @@ export class OrganizationService { } catch (error) { this.logger.error(`In fetch getOrganizations : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -170,7 +173,7 @@ export class OrganizationService { } catch (error) { this.logger.error(`In fetch getPublicOrganizations : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -191,7 +194,7 @@ export class OrganizationService { } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -212,7 +215,7 @@ export class OrganizationService { return organizationDetails; } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -232,7 +235,7 @@ export class OrganizationService { return getOrganization; } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -248,7 +251,7 @@ export class OrganizationService { return this.orgRoleService.getOrgRoles(); } catch (error) { this.logger.error(`In getOrgRoles : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -276,7 +279,7 @@ export class OrganizationService { return false; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -316,7 +319,7 @@ export class OrganizationService { return ResponseMessages.organisation.success.createInvitation; } catch (error) { this.logger.error(`In send Invitation : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -379,7 +382,7 @@ export class OrganizationService { return this.organizationRepository.getAllOrgInvitations(email, status, pageNumber, pageSize, search); } catch (error) { this.logger.error(`In fetchUserInvitation : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -415,7 +418,7 @@ export class OrganizationService { } catch (error) { this.logger.error(`In updateOrgInvitation : ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -451,7 +454,7 @@ export class OrganizationService { } catch (error) { this.logger.error(`Error in updateUserRoles: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -460,7 +463,7 @@ export class OrganizationService { return this.organizationRepository.getOrgDashboard(orgId); } catch (error) { this.logger.error(`In create organization : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index ed5febe00..f42a9b5a8 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -30,6 +30,7 @@ export interface UserEmailVerificationDto{ } export interface userInfo{ + email: string, password: string, firstName: string, lastName: string, diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 9d94f00e0..026da028c 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -50,7 +50,7 @@ export class UserController { return this.userService.updateUserProfile(payload.updateUserProfileDto); } - @MessagePattern({ cmd: 'get-user-by-supabase' }) + @MessagePattern({ cmd: 'get-user-by-supabase' }) async findSupabaseUser(payload: { id }): Promise { return this.userService.findSupabaseUser(payload); } @@ -58,7 +58,7 @@ export class UserController { @MessagePattern({ cmd: 'get-user-by-mail' }) async findUserByEmail(payload: { email }): Promise { - return this.userService.findUserByEmail(payload); + return this.userService.findUserByEmail(payload); } @MessagePattern({ cmd: 'get-org-invitations' }) @@ -84,29 +84,29 @@ export class UserController { * @param payload * @returns organization users list */ - @MessagePattern({ cmd: 'fetch-organization-users' }) + @MessagePattern({ cmd: 'fetch-organization-user' }) async getOrganizationUsers(payload: { orgId: number, pageNumber: number, pageSize: number, search: string }): Promise { return this.userService.getOrgUsers(payload.orgId, payload.pageNumber, payload.pageSize, payload.search); } - /** - * - * @param payload - * @returns organization users list - */ - @MessagePattern({ cmd: 'fetch-users' }) - async get(payload: { pageNumber: number, pageSize: number, search: string }): Promise { - const users = this.userService.get(payload.pageNumber, payload.pageSize, payload.search); - return users; - } + /** + * + * @param payload + * @returns organization users list + */ + @MessagePattern({ cmd: 'fetch-users' }) + async get(payload: { pageNumber: number, pageSize: number, search: string }): Promise { + const users = this.userService.get(payload.pageNumber, payload.pageSize, payload.search); + return users; + } @MessagePattern({ cmd: 'check-user-exist' }) async checkUserExist(payload: { userEmail: string }): Promise { return this.userService.checkUserExist(payload.userEmail); } @MessagePattern({ cmd: 'add-user' }) - async addUserDetailsInKeyCloak(payload: { userEmail: string, userInfo: userInfo }): Promise { - return this.userService.createUserForToken(payload.userEmail, payload.userInfo); + async addUserDetailsInKeyCloak(payload: { userInfo: userInfo }): Promise { + return this.userService.createUserForToken(payload.userInfo); } // Fetch Users recent activities diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 96753ee0c..0f5a84c9a 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -34,7 +34,6 @@ import { SupabaseService } from '@credebl/supabase'; import { UserDevicesRepository } from '../repositories/user-device.repository'; import { v4 as uuidv4 } from 'uuid'; - @Injectable() export class UserService { constructor( @@ -59,7 +58,7 @@ export class UserService { async sendVerificationMail(userEmailVerificationDto: UserEmailVerificationDto): Promise { try { const userDetails = await this.userRepository.checkUserExist(userEmailVerificationDto.email); - + if (userDetails && userDetails.isEmailVerified) { throw new ConflictException(ResponseMessages.user.error.exists); } @@ -82,7 +81,7 @@ export class UserService { return resUser; } catch (error) { this.logger.error(`In Create User : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -106,7 +105,7 @@ export class UserService { } catch (error) { this.logger.error(`Error in createUsername: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error.message); + throw new RpcException(error.response ? error.response : error); } } @@ -138,7 +137,7 @@ export class UserService { } catch (error) { this.logger.error(`Error in sendEmailForVerification: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error.message); + throw new RpcException(error.response ? error.response : error); } } @@ -175,17 +174,18 @@ export class UserService { } } catch (error) { this.logger.error(`error in verifyEmail: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } - async createUserForToken(email: string, userInfo: userInfo): Promise { + async createUserForToken(userInfo: userInfo): Promise { try { - if (!email) { + const { email } = userInfo; + if (!userInfo.email) { throw new UnauthorizedException(ResponseMessages.user.error.invalidEmail); } - const checkUserDetails = await this.userRepository.getUserDetails(email); + const checkUserDetails = await this.userRepository.getUserDetails(userInfo.email); if (!checkUserDetails) { throw new NotFoundException(ResponseMessages.user.error.invalidEmail); } @@ -195,11 +195,11 @@ export class UserService { if (false === checkUserDetails.isEmailVerified) { throw new NotFoundException(ResponseMessages.user.error.verifyEmail); } - const resUser = await this.userRepository.updateUserInfo(email, userInfo); + const resUser = await this.userRepository.updateUserInfo(userInfo.email, userInfo); if (!resUser) { throw new NotFoundException(ResponseMessages.user.error.invalidEmail); } - const userDetails = await this.userRepository.getUserDetails(email); + const userDetails = await this.userRepository.getUserDetails(userInfo.email); if (!userDetails) { throw new NotFoundException(ResponseMessages.user.error.adduser); @@ -208,7 +208,7 @@ export class UserService { let supaUser; if (userInfo.isPasskey) { - const resUser = await this.userRepository.addUserPassword(email, userInfo.password); + const resUser = await this.userRepository.addUserPassword(email, userInfo.password); const userDetails = await this.userRepository.getUserDetails(email); const decryptedPassword = await this.commonService.decryptPassword(userDetails.password); if (!resUser) { @@ -243,7 +243,7 @@ export class UserService { return 'User created successfully'; } catch (error) { this.logger.error(`Error in createUserForToken: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -270,7 +270,7 @@ export class UserService { return 'User updated successfully'; } catch (error) { this.logger.error(`Error in createUserForToken: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -304,32 +304,36 @@ export class UserService { return this.generateToken(email, decryptedPassword); } - return this.generateToken(email, password); + return this.generateToken(email, password); } catch (error) { this.logger.error(`In Login User : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } async generateToken(email: string, password: string): Promise { - const supaInstance = await this.supabaseService.getClient(); + try { + const supaInstance = await this.supabaseService.getClient(); - this.logger.error(`supaInstance::`, supaInstance); + this.logger.error(`supaInstance::`, supaInstance); - const { data, error } = await supaInstance.auth.signInWithPassword({ - email, - password - }); + const { data, error } = await supaInstance.auth.signInWithPassword({ + email, + password + }); - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); + this.logger.error(`Supa Login Error::`, JSON.stringify(error)); - if (error) { - throw new BadRequestException(error?.message); - } + if (error) { + throw new BadRequestException(error?.message); + } - const token = data?.session; + const token = data?.session; - return token; + return token; + } catch (error) { + throw new RpcException(error.response ? error.response : error); + } } async getProfile(payload: { id }): Promise { @@ -337,7 +341,7 @@ export class UserService { return this.userRepository.getUserById(payload.id); } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -352,7 +356,7 @@ export class UserService { return userProfile; } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -361,7 +365,7 @@ export class UserService { return this.userRepository.updateUserProfile(updateUserProfileDto); } catch (error) { this.logger.error(`update user profile: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -370,7 +374,7 @@ export class UserService { return this.userRepository.getUserBySupabaseId(payload.id); } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -379,7 +383,7 @@ export class UserService { return this.userRepository.getUserBySupabaseId(payload.id); } catch (error) { this.logger.error(`Error in findSupabaseUser: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -388,7 +392,7 @@ export class UserService { return this.userRepository.findUserByEmail(payload.email); } catch (error) { this.logger.error(`findUserByEmail: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -413,7 +417,7 @@ export class UserService { return invitationsData; } catch (error) { this.logger.error(`Error in get invitations: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -473,7 +477,7 @@ export class UserService { return this.fetchInvitationsStatus(acceptRejectInvitation, userId, userData.email); } catch (error) { this.logger.error(`acceptRejectInvitations: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -513,7 +517,7 @@ export class UserService { return invitationsData; } catch (error) { this.logger.error(`Error In fetchInvitationsStatus: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -524,6 +528,7 @@ export class UserService { */ async getOrgUsers(orgId: number, pageNumber: number, pageSize: number, search: string): Promise { try { + const query = { userOrgRoles: { some: { orgId } @@ -538,10 +543,11 @@ export class UserService { const filterOptions = { orgId }; + return this.userRepository.findOrgUsers(query, pageNumber, pageSize, filterOptions); } catch (error) { this.logger.error(`get Org Users: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -563,7 +569,7 @@ export class UserService { return this.userRepository.findUsers(query, pageNumber, pageSize); } catch (error) { this.logger.error(`get Users: ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -576,19 +582,22 @@ export class UserService { } else if (userDetails && userDetails.supabaseUserId) { throw new ConflictException(ResponseMessages.user.error.exists); } else if (null === userDetails) { - return 'New User'; + return { + isExist: false + }; } else { const userVerificationDetails = { isEmailVerified: userDetails.isEmailVerified, isFidoVerified: userDetails.isFidoVerified, - isSupabase: null !== userDetails.supabaseUserId && undefined !== userDetails.supabaseUserId + isSupabase: null !== userDetails.supabaseUserId && undefined !== userDetails.supabaseUserId, + isExist: true }; return userVerificationDetails; } } catch (error) { this.logger.error(`In check User : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -600,7 +609,7 @@ export class UserService { } catch (error) { this.logger.error(`In getUserActivity : ${JSON.stringify(error)}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } -} +} \ No newline at end of file diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 1d265cf7a..e7cba4251 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -13,14 +13,15 @@ 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 code has been sent sucessfully to the mail. Please verify', + userActivity: 'User activities fetched successfully' }, error: { exists: 'User already exists', profileNotFound: 'User public profile not found', verificationAlreadySent: 'The verification link has already been sent to your email address', emailSend: 'Unable to send email to the user', - invalidEmailUrl: 'Invalid token or EmailId!', + invalidEmailUrl: 'Invalid verification code or EmailId!', verifiedEmail: 'Email already verified', notFound: 'User not found', verifyMail: 'Please verify your email', @@ -32,7 +33,8 @@ export const ResponseMessages = { invalidEmail: 'Invalid Email Id!', adduser: 'Unable to add user details', verifyEmail: 'The verification link has already been sent to your email address. please verify', - emailNotVerified: 'The verification link has already been sent to your email address. please verify' + emailNotVerified: 'The verification link has already been sent to your email address. please verify', + userNotRegisterd: 'The user has not yet completed the registration process' } }, organisation: { @@ -115,7 +117,7 @@ export const ResponseMessages = { }, agent: { success: { - create: 'Agent spin-up up successfully', + create: 'Agent spin-up successfully', health: 'Agent health details retrieved successfully.' }, error: { @@ -172,4 +174,4 @@ export const ResponseMessages = { emailSend: 'Unable to send email to the user' } } -}; +}; \ No newline at end of file From ef04604809da34b7f408f1240597b0d22e5174b3 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:46:10 +0530 Subject: [PATCH 070/162] refactor: schema APIs standardized (#84) * refactor: Schema APIs standardized Signed-off-by: KulkarniShashank * Resolved sonar lint checks Signed-off-by: KulkarniShashank * API refactor in schema module Signed-off-by: KulkarniShashank * API refactor in credential-definition Signed-off-by: KulkarniShashank * Added the error handling in the platform and credential-definition module Signed-off-by: KulkarniShashank * Error handling and solved the pagination Signed-off-by: KulkarniShashank * Added logger in the global exception handling Signed-off-by: KulkarniShashank * Changes as per comment on the Schema controller Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/api-gateway/common/exception-handler.ts | 5 +- apps/api-gateway/src/app.module.ts | 2 - .../credential-definition.controller.ts | 94 +++++--- .../dto/create-cred-defs.dto.ts | 6 +- .../dto/get-all-cred-defs.dto.ts | 15 +- .../api-gateway/src/dtos/create-schema.dto.ts | 5 +- .../dtos/get-all-organizations.dto.ts | 3 +- .../dtos/get-all-sent-invitations.dto.ts | 3 +- .../src/platform/platform.controller.ts | 45 +++- .../src/platform/platform.service.ts | 59 +---- .../src/schema/dtos/get-all-schema.dto.ts | 23 +- .../src/schema/schema.controller.ts | 221 +++++++++--------- apps/api-gateway/src/schema/schema.service.ts | 13 -- .../src/user/dto/get-all-invitations.dto.ts | 3 +- .../src/user/dto/get-all-users.dto.ts | 3 +- .../credential-definition.service.ts | 6 +- apps/ledger/src/schema/schema.service.ts | 10 +- 17 files changed, 241 insertions(+), 275 deletions(-) diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index 13c102539..162712d52 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -1,8 +1,9 @@ -import { Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'; +import { Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common'; import { BaseExceptionFilter } from '@nestjs/core'; @Catch() export class CustomExceptionFilter extends BaseExceptionFilter { + private readonly logger = new Logger(); catch(exception: HttpException, host: ArgumentsHost): void { const ctx = host.switchToHttp(); const response = ctx.getResponse(); @@ -12,10 +13,12 @@ export class CustomExceptionFilter extends BaseExceptionFilter { 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 (exception && exception["statusCode"] === HttpStatus.INTERNAL_SERVER_ERROR) { errorResponse = { diff --git a/apps/api-gateway/src/app.module.ts b/apps/api-gateway/src/app.module.ts index d710b05fb..ee0657282 100644 --- a/apps/api-gateway/src/app.module.ts +++ b/apps/api-gateway/src/app.module.ts @@ -11,7 +11,6 @@ import { CredentialDefinitionModule } from './credential-definition/credential-d import { FidoModule } from './fido/fido.module'; import { IssuanceModule } from './issuance/issuance.module'; import { OrganizationModule } from './organization/organization.module'; -import { PlatformController } from './platform/platform.controller'; import { PlatformModule } from './platform/platform.module'; import { VerificationModule } from './verification/verification.module'; import { RevocationController } from './revocation/revocation.controller'; @@ -80,7 +79,6 @@ export class AppModule { ) .forRoutes( AgentController, - PlatformController, RevocationController ); } diff --git a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts index be8e25a0f..9cd7d0494 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Logger, Post, Body, UseGuards, Get, Query, HttpStatus, Res } from '@nestjs/common'; +import { Controller, Logger, Post, Body, UseGuards, Get, Query, HttpStatus, Res, Param, UseFilters } from '@nestjs/common'; import { CredentialDefinitionService } from './credential-definition.service'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiUnauthorizedResponse, ApiForbiddenResponse, ApiQuery } from '@nestjs/swagger'; import { ApiResponseDto } from 'apps/api-gateway/src/dtos/apiResponse.dto'; @@ -15,55 +15,31 @@ import { IUserRequestInterface } from './interfaces'; import { CreateCredentialDefinitionDto } from './dto/create-cred-defs.dto'; import { OrgRoles } from 'libs/org-roles/enums'; import { Roles } from '../authz/decorators/roles.decorator'; +import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @ApiBearerAuth() -@UseGuards(AuthGuard('jwt'), OrgRolesGuard) -@Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER) @ApiTags('credential-definitions') - @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) -@Controller('credential-definitions') +@Controller('orgs') +@UseFilters(CustomExceptionFilter) export class CredentialDefinitionController { constructor(private readonly credentialDefinitionService: CredentialDefinitionService) { } private readonly logger = new Logger('CredentialDefinitionController'); - @Post('/') - @ApiOperation({ - summary: 'Sends a credential definition to the ledger', - description: 'Create and sends a credential definition to the ledger.' - }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async createCredentialDefinition( - @User() user: IUserRequestInterface, - @Body() credDef: CreateCredentialDefinitionDto, - @Res() res: Response - ): Promise { - const credentialsDefinitionDetails = await this.credentialDefinitionService.createCredentialDefinition(credDef, user); - const credDefResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.credentialDefinition.success.create, - data: credentialsDefinitionDetails.response - }; - return res.status(HttpStatus.OK).json(credDefResponse); - } - @Get('/id') + @Get('/:orgId/cred-defs/:credDefId') @ApiOperation({ summary: 'Get an existing credential definition by Id', description: 'Get an existing credential definition by Id' }) - @ApiQuery( - { name: 'credentialDefinitionId', required: true } - ) - @ApiQuery( - { name: 'orgId', required: true } - ) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getCredentialDefinitionById( - @Query('credentialDefinitionId') credentialDefinitionId: string, - @Query('orgId') orgId: number, + @Param('orgId') orgId: number, + @Param('credDefId') credentialDefinitionId: string, @Res() res: Response ): Promise { const credentialsDefinitionDetails = await this.credentialDefinitionService.getCredentialDefinitionById(credentialDefinitionId, orgId); @@ -75,20 +51,39 @@ export class CredentialDefinitionController { return res.status(HttpStatus.OK).json(credDefResponse); } - @Get('/') + @Get('/:orgId/cred-defs') @ApiOperation({ summary: 'Fetch all credential definitions of provided organization id with pagination', description: 'Fetch all credential definitions from metadata saved in database of provided organization id.' }) + @ApiQuery( + { name: 'pageNumber', required: false } + ) + @ApiQuery( + { name: 'searchByText', required: false } + ) + @ApiQuery( + { name: 'pageSize', required: false } + ) + @ApiQuery( + { name: 'sorting', required: false } + ) + @ApiQuery( + { name: 'sortByValue', required: false } + ) + @ApiQuery( + { name: 'revocable', required: false } + ) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getAllCredDefs( + @Param('orgId') orgId: number, @Query() getAllCredDefs: GetAllCredDefsDto, @User() user: IUserRequestInterface, @Res() res: Response ): Promise { - const { pageSize, pageNumber, sortByValue, sorting, orgId, searchByText, revocable } = getAllCredDefs; - const credDefSearchCriteria = { pageSize, pageNumber, searchByText, sorting, sortByValue, revocable }; const credentialsDefinitionDetails = await this.credentialDefinitionService.getAllCredDefs( - credDefSearchCriteria, + getAllCredDefs, user, orgId ); @@ -99,4 +94,29 @@ export class CredentialDefinitionController { }; return res.status(HttpStatus.OK).json(credDefResponse); } + + @Post('/:orgId/cred-defs') + @ApiOperation({ + summary: 'Sends a credential definition to ledger', + description: 'Sends a credential definition to ledger' + }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + async createCredentialDefinition( + @User() user: IUserRequestInterface, + @Body() credDef: CreateCredentialDefinitionDto, + @Param('orgId') orgId: number, + @Res() res: Response + ): Promise { + + credDef.orgId = orgId; + const credentialsDefinitionDetails = await this.credentialDefinitionService.createCredentialDefinition(credDef, user); + const credDefResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.credentialDefinition.success.create, + data: credentialsDefinitionDetails.response + }; + return res.status(HttpStatus.OK).json(credDefResponse); + } } \ No newline at end of file diff --git a/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts b/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts index 0754ebfd8..3fa2024d6 100644 --- a/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts +++ b/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts @@ -1,4 +1,4 @@ -import { IsBoolean, IsDefined, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsBoolean, IsDefined, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; @@ -13,10 +13,6 @@ export class CreateCredentialDefinitionDto { @IsNotEmpty({ message: 'Please provide a schema id' }) @IsString({ message: 'Schema id should be string' }) schemaLedgerId: string; - - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'Please provide orgId' }) orgId: number; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/credential-definition/dto/get-all-cred-defs.dto.ts b/apps/api-gateway/src/credential-definition/dto/get-all-cred-defs.dto.ts index 76172f182..0de995ad7 100644 --- a/apps/api-gateway/src/credential-definition/dto/get-all-cred-defs.dto.ts +++ b/apps/api-gateway/src/credential-definition/dto/get-all-cred-defs.dto.ts @@ -2,32 +2,27 @@ /* eslint-disable camelcase */ import { ApiProperty } from '@nestjs/swagger'; import { SortValue } from '../../enum'; -import { Transform, Type } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; -import { IsNotEmpty, IsNumber, IsOptional } from 'class-validator'; +import { Type } from 'class-transformer'; +import { IsOptional } from 'class-validator'; export class GetAllCredDefsDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => Number) - @Transform(({ value }) => trim(value)) pageNumber: number = 1; @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) searchByText: string = ''; @ApiProperty({ required: false }) @IsOptional() @Type(() => Number) - @Transform(({ value }) => trim(value)) pageSize: number = 10; @ApiProperty({ required: false }) @IsOptional() - @Transform(({ value }) => trim(value)) sorting: string = 'id'; @ApiProperty({ required: false }) @@ -37,11 +32,5 @@ export class GetAllCredDefsDto { @ApiProperty({ required: false }) @IsOptional() revocable: boolean = true; - - @ApiProperty({ required: true }) - @Type(() => Number) - @IsNumber() - @IsNotEmpty() - orgId: number; } diff --git a/apps/api-gateway/src/dtos/create-schema.dto.ts b/apps/api-gateway/src/dtos/create-schema.dto.ts index 810228959..232af5a4f 100644 --- a/apps/api-gateway/src/dtos/create-schema.dto.ts +++ b/apps/api-gateway/src/dtos/create-schema.dto.ts @@ -1,4 +1,4 @@ -import { IsArray, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; @@ -22,9 +22,6 @@ export class CreateSchemaDto { @IsNotEmpty({ message: 'please provide valid attributes' }) attributes: IAttributeValue[]; - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; @ApiProperty() diff --git a/apps/api-gateway/src/organization/dtos/get-all-organizations.dto.ts b/apps/api-gateway/src/organization/dtos/get-all-organizations.dto.ts index 53d0a082f..6a3cedf44 100644 --- a/apps/api-gateway/src/organization/dtos/get-all-organizations.dto.ts +++ b/apps/api-gateway/src/organization/dtos/get-all-organizations.dto.ts @@ -1,6 +1,6 @@ import { Transform, Type } from 'class-transformer'; // import { SortValue } from '../../enum'; -import { toNumber, trim } from '@credebl/common/cast.helper'; +import { toNumber } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { IsOptional } from 'class-validator'; @@ -15,7 +15,6 @@ export class GetAllOrganizationsDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) search = ''; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/organization/dtos/get-all-sent-invitations.dto.ts b/apps/api-gateway/src/organization/dtos/get-all-sent-invitations.dto.ts index edc9eaf1c..eaa15380d 100644 --- a/apps/api-gateway/src/organization/dtos/get-all-sent-invitations.dto.ts +++ b/apps/api-gateway/src/organization/dtos/get-all-sent-invitations.dto.ts @@ -1,5 +1,5 @@ import { Transform, Type } from 'class-transformer'; -import { toNumber, trim } from '@credebl/common/cast.helper'; +import { toNumber } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { IsOptional } from 'class-validator'; @@ -14,7 +14,6 @@ export class GetAllSentInvitationsDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) search = ''; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/platform/platform.controller.ts b/apps/api-gateway/src/platform/platform.controller.ts index 9cfc655c4..26c43bbfd 100644 --- a/apps/api-gateway/src/platform/platform.controller.ts +++ b/apps/api-gateway/src/platform/platform.controller.ts @@ -1,13 +1,52 @@ -import { Controller, Logger } from '@nestjs/common'; +import { Controller, Get, HttpStatus, Logger, Query, Res, UseFilters } from '@nestjs/common'; import { PlatformService } from './platform.service'; -import { ApiBearerAuth } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiResponseDto } from '../dtos/apiResponse.dto'; +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 IResponseType from '@credebl/common/interfaces/response.interface'; +import { ResponseMessages } from '@credebl/common/response-messages'; +import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @ApiBearerAuth() -@Controller('connections') +@Controller() +@UseFilters(CustomExceptionFilter) export class PlatformController { constructor(private readonly platformService: PlatformService) { } private readonly logger = new Logger('PlatformController'); + @Get('/platform/schemas') + @ApiTags('schemas') + @ApiOperation({ + summary: 'Get all schemas from platform.', + description: 'Get all schemas from platform.' + }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + async getAllSchema( + @Query() getAllSchemaDto: GetAllSchemaByPlatformDto, + @Res() res: Response, + @User() user: IUserRequestInterface + ): Promise { + const { pageSize, searchByText, pageNumber, sorting, sortByValue } = getAllSchemaDto; + const schemaSearchCriteria: ISchemaSearchInterface = { + pageNumber, + searchByText, + pageSize, + sorting, + sortByValue + }; + const schemasResponse = await this.platformService.getAllSchema(schemaSearchCriteria, user); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.schema.success.fetch, + data: schemasResponse.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } } diff --git a/apps/api-gateway/src/platform/platform.service.ts b/apps/api-gateway/src/platform/platform.service.ts index 54a3bb353..6443fc11a 100644 --- a/apps/api-gateway/src/platform/platform.service.ts +++ b/apps/api-gateway/src/platform/platform.service.ts @@ -1,10 +1,8 @@ -import { Injectable, Inject, Logger, HttpException } from '@nestjs/common'; +import { Injectable, Inject } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from '../../../../libs/service/base.service'; -import { map } from 'rxjs/operators'; -import { CredentialListPayload, GetCredentialListByConnectionId, IConnectedHolderList, SortValue } from './platform.interface'; -import { ConnectionDto } from '../dtos/connection.dto'; -import { credentialSortBy } from '../enum'; +import { ISchemaSearchInterface } from '../interfaces/ISchemaSearch.interface'; +import { IUserRequestInterface } from '../interfaces/IUserRequestInterface'; @Injectable() export class PlatformService extends BaseService { @@ -14,52 +12,11 @@ export class PlatformService extends BaseService { super('PlatformService'); } + async getAllSchema(schemaSearchCriteria: ISchemaSearchInterface, user: IUserRequestInterface): Promise<{ + response: object; + }> { + const schemaSearch = { schemaSearchCriteria, user }; + return this.sendNats(this.platformServiceProxy, 'get-all-schemas', schemaSearch); - /** - * Description: Calling platform service for connection-invitation - * @param alias - * @param auto_accept - * @param _public - * @param multi_use - */ - createConnectionInvitation(alias: string, auto_accept: boolean, _public: boolean, multi_use: boolean) { - this.logger.log('**** createConnectionInvitation called...'); - const payload = { alias, auto_accept, _public, multi_use }; - return this.sendNats(this.platformServiceProxy, 'default-connection-invitation', payload); - } - - /** - * Description: Calling platform service for connection-list - * @param alias - * @param initiator - * @param invitation_key - * @param my_did - * @param state - * @param their_did - * @param their_role - */ - getConnections(alias: string, initiator: string, invitation_key: string, my_did: string, state: string, their_did: string, their_role: string, user: any) { - this.logger.log('**** getConnections called...'); - const payload = { alias, initiator, invitation_key, my_did, state, their_did, their_role, user }; - return this.sendNats(this.platformServiceProxy, 'connection-list', payload); - } - - pingServicePlatform() { - this.logger.log('**** pingServicePlatform called...'); - const payload = {}; - return this.sendNats(this.platformServiceProxy, 'ping-platform', payload); - } - - - connectedHolderList(itemsPerPage: number, page: number, searchText: string, orgId: number, connectionSortBy: string, sortValue: string) { - this.logger.log('**** connectedHolderList called...'); - const payload: IConnectedHolderList = { itemsPerPage, page, searchText, orgId, connectionSortBy, sortValue }; - return this.sendNats(this.platformServiceProxy, 'connected-holder-list', payload); - } - - getCredentialListByConnectionId(connectionId: string, items_per_page: number, page: number, search_text: string, sortValue: SortValue, sortBy: credentialSortBy) { - this.logger.log('**** getCredentialListByConnectionId called...'); - const payload:GetCredentialListByConnectionId = { connectionId, items_per_page, page, search_text, sortValue, sortBy }; - return this.sendNats(this.platformServiceProxy, 'get-credential-by-connection-id', payload); } } 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 af4a49838..0004e716d 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,9 +2,8 @@ /* eslint-disable camelcase */ import { ApiProperty } from '@nestjs/swagger'; import { SortValue } from '../../enum'; -import { Transform, Type } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; -import { IsNotEmpty, IsNumber, IsOptional } from 'class-validator'; +import { Type } from 'class-transformer'; +import { IsOptional } from 'class-validator'; export class GetAllSchemaDto { @ApiProperty({ required: false }) @@ -14,7 +13,6 @@ export class GetAllSchemaDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) searchByText: string = ''; @ApiProperty({ required: false }) @@ -23,19 +21,11 @@ export class GetAllSchemaDto { @ApiProperty({ required: false }) @IsOptional() - @Transform(({ value }) => trim(value)) sorting: string = 'id'; @ApiProperty({ required: false }) @IsOptional() sortByValue: string = SortValue.DESC; - - @ApiProperty({ required: true }) - @Type(() => Number) - @IsNumber() - @IsNotEmpty() - @IsOptional() - orgId?: number; } export class GetCredentialDefinitionBySchemaIdDto { @@ -56,13 +46,6 @@ export class GetCredentialDefinitionBySchemaIdDto { @ApiProperty({ required: false }) @IsOptional() sortByValue: string = SortValue.DESC; - - @ApiProperty({ required: true }) - @Type(() => Number) - @IsNumber() - @IsNotEmpty() - @IsOptional() - orgId?: number; } export class GetAllSchemaByPlatformDto { @@ -73,7 +56,6 @@ export class GetAllSchemaByPlatformDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) searchByText: string = ''; @ApiProperty({ required: false }) @@ -82,7 +64,6 @@ export class GetAllSchemaByPlatformDto { @ApiProperty({ required: false }) @IsOptional() - @Transform(({ value }) => trim(value)) sorting: string = 'id'; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/schema/schema.controller.ts b/apps/api-gateway/src/schema/schema.controller.ts index 00f4ecacc..98e829e4b 100644 --- a/apps/api-gateway/src/schema/schema.controller.ts +++ b/apps/api-gateway/src/schema/schema.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Logger, Post, Body, HttpStatus, UseGuards, Get, Query, BadRequestException, Res, UseFilters } from '@nestjs/common'; +import { Controller, Logger, Post, Body, HttpStatus, UseGuards, Get, Query, BadRequestException, Res, UseFilters, Param } from '@nestjs/common'; /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable camelcase */ import { ApiOperation, ApiResponse, ApiTags, ApiBearerAuth, ApiForbiddenResponse, ApiUnauthorizedResponse, ApiQuery } from '@nestjs/swagger'; @@ -12,17 +12,16 @@ import { Response } from 'express'; import { User } from '../authz/decorators/user.decorator'; import { ICredDeffSchemaSearchInterface, ISchemaSearchInterface } from '../interfaces/ISchemaSearch.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { GetAllSchemaByPlatformDto, GetAllSchemaDto, GetCredentialDefinitionBySchemaIdDto } from './dtos/get-all-schema.dto'; +import { GetAllSchemaDto, GetCredentialDefinitionBySchemaIdDto } from './dtos/get-all-schema.dto'; import { OrgRoles } from 'libs/org-roles/enums'; import { Roles } from '../authz/decorators/roles.decorator'; import { IUserRequestInterface } from './interfaces'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { CreateSchemaDto } from '../dtos/create-schema.dto'; -import { TransformStreamDefaultController } from 'node:stream/web'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @UseFilters(CustomExceptionFilter) -@Controller('schemas') +@Controller('orgs') @ApiTags('schemas') @ApiBearerAuth() @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @@ -32,94 +31,19 @@ export class SchemaController { ) { } private readonly logger = new Logger('SchemaController'); - @Post('/') - @ApiOperation({ - summary: 'Sends a schema to the ledger', - description: 'Create and sends a schema to the ledger.' - }) - @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER) + @Get('/:orgId/schemas/:schemaId') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async createSchema(@Res() res: Response, @Body() schema: CreateSchemaDto, @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'); - } - }); - const schemaDetails = await this.appService.createSchema(schema, user, schema.orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: 'Schema created successfully', - data: schemaDetails.response - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - - @Get('/') @ApiOperation({ - summary: 'Get all schemas by org id.', - description: 'Get all schemas by org id.' + summary: 'Get schema information from the ledger using its schema ID.', + description: 'Get schema information from the ledger using its schema ID.' }) - @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - async getSchemas( - @Query() getAllSchemaDto: GetAllSchemaDto, - @Res() res: Response, - @User() user: IUserRequestInterface - ): Promise { - - const { orgId, pageSize, searchByText, pageNumber, sorting, sortByValue } = getAllSchemaDto; - const schemaSearchCriteria: ISchemaSearchInterface = { - pageNumber, - searchByText, - pageSize, - sorting, - sortByValue - }; - const schemasResponse = await this.appService.getSchemas(schemaSearchCriteria, user, orgId); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.schema.success.fetch, - data: schemasResponse.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - - @Get('/id') - @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiOperation({ - summary: 'Retrieve an existing schema from the ledger using its schemaId', - description: 'Retrieve an existing schema from the ledger using its schemaId' - }) - @ApiQuery( - { name: 'schemaId', required: true } - ) - - @ApiQuery( - { name: 'orgId', required: true } - ) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) async getSchemaById( @Res() res: Response, - @Query('schemaId') schemaId: string, - @Query('orgId') orgId: number + @Param('orgId') orgId: number, + @Param('schemaId') schemaId: string ): Promise { if (!schemaId) { @@ -134,20 +58,37 @@ export class SchemaController { return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/credential-definitions') + @Get('/:orgId/schemas/:schemaId/cred-defs') @ApiOperation({ - summary: 'Get an existing credential definition list by schemaId', - description: 'Get an existing credential definition list by schemaId' + summary: 'Get credential definition list by schema Id', + description: 'Get credential definition list by schema Id' }) - @ApiQuery( - { name: 'schemaId', required: true } - ) - @ApiQuery( - { name: 'orgId', required: false } - ) @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 + }) + @ApiQuery({ + name: 'sortByValue', + type: String, + required: false + }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getcredDeffListBySchemaId( - @Query('schemaId') schemaId: string, + @Param('orgId') orgId: number, + @Param('schemaId') schemaId: string, @Query() GetCredentialDefinitionBySchemaIdDto: GetCredentialDefinitionBySchemaIdDto, @Res() res: Response, @User() user: IUserRequestInterface): Promise { @@ -155,14 +96,8 @@ export class SchemaController { if (!schemaId) { throw new BadRequestException(ResponseMessages.schema.error.invalidSchemaId); } - const { orgId, pageSize, pageNumber, sorting, sortByValue } = GetCredentialDefinitionBySchemaIdDto; - const schemaSearchCriteria: ICredDeffSchemaSearchInterface = { - pageNumber, - pageSize, - sorting, - sortByValue - }; - const credentialDefinitionList = await this.appService.getcredDeffListBySchemaId(schemaId, schemaSearchCriteria, user, orgId); + + const credentialDefinitionList = await this.appService.getcredDeffListBySchemaId(schemaId, GetCredentialDefinitionBySchemaIdDto, user, orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.schema.success.fetch, @@ -171,17 +106,46 @@ export class SchemaController { return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/platform') + @Get('/:orgId/schemas') @ApiOperation({ - summary: 'Get all schemas from platform.', - description: 'Get all schemas from platform.' + summary: 'Get all 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, + required: false + }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - async getAllSchema( - @Query() getAllSchemaDto: GetAllSchemaByPlatformDto, + async getSchemas( + @Query() getAllSchemaDto: GetAllSchemaDto, + @Param('orgId') orgId: number, @Res() res: Response, @User() user: IUserRequestInterface ): Promise { + const { pageSize, searchByText, pageNumber, sorting, sortByValue } = getAllSchemaDto; const schemaSearchCriteria: ISchemaSearchInterface = { pageNumber, @@ -190,7 +154,7 @@ export class SchemaController { sorting, sortByValue }; - const schemasResponse = await this.appService.getAllSchema(schemaSearchCriteria, user); + const schemasResponse = await this.appService.getSchemas(schemaSearchCriteria, user, orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, @@ -199,4 +163,43 @@ export class SchemaController { }; return res.status(HttpStatus.OK).json(finalResponse); } + + @Post('/:orgId/schemas') + @ApiOperation({ + summary: 'Create and sends a schema to the ledger.', + description: 'Create and sends a schema to the ledger.' + }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + async createSchema(@Res() res: Response, @Body() schema: CreateSchemaDto, @Param('orgId') orgId: number, @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 + }; + 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 3a260f706..ed038952e 100644 --- a/apps/api-gateway/src/schema/schema.service.ts +++ b/apps/api-gateway/src/schema/schema.service.ts @@ -59,17 +59,4 @@ export class SchemaService extends BaseService { } } - - getAllSchema(schemaSearchCriteria: ISchemaSearchInterface, user: IUserRequestInterface): Promise<{ - response: object; - }> { - try { - const schemaSearch = { schemaSearchCriteria, user }; - return this.sendNats(this.schemaServiceProxy, 'get-all-schemas', schemaSearch); - } catch (error) { - throw new RpcException(error.response); - - } - } - } \ No newline at end of file diff --git a/apps/api-gateway/src/user/dto/get-all-invitations.dto.ts b/apps/api-gateway/src/user/dto/get-all-invitations.dto.ts index 42e50d3d7..89f079f19 100644 --- a/apps/api-gateway/src/user/dto/get-all-invitations.dto.ts +++ b/apps/api-gateway/src/user/dto/get-all-invitations.dto.ts @@ -1,6 +1,6 @@ import { IsOptional, IsString } from 'class-validator'; import { Transform, Type } from 'class-transformer'; -import { toNumber, trim } from '@credebl/common/cast.helper'; +import { toNumber } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { Invitation } from '@credebl/enum/enum'; @@ -15,7 +15,6 @@ export class GetAllInvitationsDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) search = ''; @ApiProperty({ required: false }) diff --git a/apps/api-gateway/src/user/dto/get-all-users.dto.ts b/apps/api-gateway/src/user/dto/get-all-users.dto.ts index 47b98aeb8..8a9bbd8b2 100644 --- a/apps/api-gateway/src/user/dto/get-all-users.dto.ts +++ b/apps/api-gateway/src/user/dto/get-all-users.dto.ts @@ -1,5 +1,5 @@ import { Transform, Type } from 'class-transformer'; -import { toNumber, trim } from '@credebl/common/cast.helper'; +import { toNumber } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { IsOptional } from 'class-validator'; @@ -15,7 +15,6 @@ export class GetAllUsersDto { @ApiProperty({ required: false }) @IsOptional() @Type(() => String) - @Transform(({ value }) => trim(value)) search = ''; @ApiProperty({ required: false }) diff --git a/apps/ledger/src/credential-definition/credential-definition.service.ts b/apps/ledger/src/credential-definition/credential-definition.service.ts index eb8831fcf..66341747f 100644 --- a/apps/ledger/src/credential-definition/credential-definition.service.ts +++ b/apps/ledger/src/credential-definition/credential-definition.service.ts @@ -114,7 +114,7 @@ export class CredentialDefinitionService extends BaseService { this.logger.error( `Error in creating credential definition: ${JSON.stringify(error)}` ); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -180,7 +180,7 @@ export class CredentialDefinitionService extends BaseService { return credDefResponse; } catch (error) { this.logger.error(`Error retrieving credential definition with id ${payload.credentialDefinitionId}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -252,7 +252,7 @@ export class CredentialDefinitionService extends BaseService { } catch (error) { this.logger.error(`Error in retrieving credential definitions: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } diff --git a/apps/ledger/src/schema/schema.service.ts b/apps/ledger/src/schema/schema.service.ts index ae76b2129..c1816f35d 100644 --- a/apps/ledger/src/schema/schema.service.ts +++ b/apps/ledger/src/schema/schema.service.ts @@ -180,7 +180,7 @@ export class SchemaService extends BaseService { this.logger.error( `[createSchema] - outer Error: ${JSON.stringify(error)}` ); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -244,7 +244,7 @@ export class SchemaService extends BaseService { } catch (error) { this.logger.error(`Error in getting schema by id: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -322,7 +322,7 @@ export class SchemaService extends BaseService { } catch (error) { this.logger.error(`Error in retrieving schemas by org id: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -361,7 +361,7 @@ export class SchemaService extends BaseService { } catch (error) { this.logger.error(`Error in retrieving credential definition: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -410,7 +410,7 @@ export class SchemaService extends BaseService { } catch (error) { this.logger.error(`Error in retrieving all schemas: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } } From 61513190151f89ca15447d6168edde302b1a6df2 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Thu, 21 Sep 2023 19:58:41 +0530 Subject: [PATCH 071/162] refactor: Standardize API in connection, issuance and verification (#100) * refactor: connection, issuance, veriication API refactor Signed-off-by: KulkarniShashank * Added error handling and role guard in issuance and verification Signed-off-by: KulkarniShashank * Remove the duplication code in verification Signed-off-by: KulkarniShashank * Remove the duplication code in verification controller Signed-off-by: KulkarniShashank * Changes as per comment in PR Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/api-gateway/common/exception-handler.ts | 7 +- .../src/connection/connection.controller.ts | 156 ++++++-------- .../src/connection/dtos/connection.dto.ts | 7 +- .../credential-definition.controller.ts | 2 +- .../credential-definition.service.ts | 29 +-- .../api-gateway/src/dtos/create-schema.dto.ts | 23 +- .../src/issuance/dtos/issuance.dto.ts | 7 +- .../src/issuance/issuance.controller.ts | 180 ++++++++-------- apps/api-gateway/src/schema/schema.service.ts | 38 +--- .../src/verification/dto/request-proof.dto.ts | 31 ++- .../verification/verification.controller.ts | 197 ++++++++++-------- apps/connection/src/connection.service.ts | 12 +- apps/issuance/src/issuance.service.ts | 11 +- apps/verification/src/verification.service.ts | 139 +++--------- libs/common/src/response-messages/index.ts | 2 +- 15 files changed, 362 insertions(+), 479 deletions(-) diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index 162712d52..5df61a442 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -18,7 +18,6 @@ export class CustomExceptionFilter extends BaseExceptionFilter { exception.message = 'Oops! Something went wrong. Please try again'; } - let errorResponse; if (exception && exception["statusCode"] === HttpStatus.INTERNAL_SERVER_ERROR) { errorResponse = { @@ -26,6 +25,12 @@ export class CustomExceptionFilter extends BaseExceptionFilter { 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) { + errorResponse = { + statusCode: exception["error"].statusCode ? exception["error"].statusCode : status, + 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, diff --git a/apps/api-gateway/src/connection/connection.controller.ts b/apps/api-gateway/src/connection/connection.controller.ts index d9ad6bf9a..d05e52845 100644 --- a/apps/api-gateway/src/connection/connection.controller.ts +++ b/apps/api-gateway/src/connection/connection.controller.ts @@ -14,41 +14,53 @@ import { Response } from 'express'; import { Connections } from './enums/connections.enum'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; +import { OrgRoles } from 'libs/org-roles/enums'; +import { Roles } from '../authz/decorators/roles.decorator'; +import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; @UseFilters(CustomExceptionFilter) @Controller() @ApiTags('connections') +@ApiBearerAuth() @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) export class ConnectionController { private readonly logger = new Logger('Connection'); constructor(private readonly connectionService: ConnectionService - ) { - /** - * Create out-of-band connection legacy invitation - * @param connectionDto - * @param res - * @returns Created out-of-band connection invitation url - */ - } - @Post('/connections') - @ApiOperation({ summary: 'Create outbound out-of-band connection (Legacy Invitation)', description: 'Create outbound out-of-band connection (Legacy Invitation)' }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() + ) { } + + /** + * Description: Get connection by connectionId + * @param user + * @param connectionId + * @param orgId + * + */ + @Get('orgs/:orgId/connections/:connectionId') + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @ApiOperation({ + summary: `Get connections by connection Id`, + description: `Get connections by connection Id` + }) @ApiResponse({ status: 200, description: 'Success', type: AuthTokenResponse }) - async createLegacyConnectionInvitation(@Body() connectionDto: CreateConnectionDto, @User() reqUser: IUserRequestInterface, @Res() res: Response): Promise { - const connectionData = await this.connectionService.createLegacyConnectionInvitation(connectionDto, reqUser); + async getConnectionsById( + @User() user: IUserRequest, + @Param('connectionId') connectionId: string, + @Param('orgId') orgId: number, + @Res() res: Response + ): Promise { + const connectionsDetails = await this.connectionService.getConnectionsById(user, connectionId, orgId); + const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.connection.success.create, - data: connectionData.response + message: ResponseMessages.connection.success.fetch, + data: connectionsDetails.response }; return res.status(HttpStatus.OK).json(finalResponse); - } - /** * Description: Get all connections * @param user @@ -58,14 +70,14 @@ export class ConnectionController { * @param orgId * */ - @Get('/connections') - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() + @Get('/orgs/:orgId/connections') + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @ApiOperation({ - summary: `Fetch all connections details`, - description: `Fetch all connections details` + summary: `Fetch all connection details`, + description: `Fetch all connection details` }) - @ApiResponse({ status: 201, description: 'Success', type: AuthTokenResponse }) + @ApiResponse({ status: 200, description: 'Success', type: AuthTokenResponse }) @ApiQuery( { name: 'outOfBandId', required: false } ) @@ -84,10 +96,6 @@ export class ConnectionController { @ApiQuery( { name: 'theirLabel', required: false } ) - @ApiQuery( - { name: 'orgId', required: true } - ) - async getConnections( @User() user: IUserRequest, @Query('outOfBandId') outOfBandId: string, @@ -96,7 +104,7 @@ export class ConnectionController { @Query('myDid') myDid: string, @Query('theirDid') theirDid: string, @Query('theirLabel') theirLabel: string, - @Query('orgId') orgId: number, + @Param('orgId') orgId: number, @Res() res: Response ): Promise { @@ -112,6 +120,34 @@ export class ConnectionController { return res.status(HttpStatus.OK).json(finalResponse); } + /** + * Create out-of-band connection legacy invitation + * @param connectionDto + * @param res + * @returns Created out-of-band connection invitation url + */ + @Post('/orgs/:orgId/connections') + @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) + @ApiResponse({ status: 201, description: 'Success', type: AuthTokenResponse }) + async createLegacyConnectionInvitation( + @Param('orgId') orgId: number, + @Body() connectionDto: CreateConnectionDto, + @User() reqUser: IUserRequestInterface, + @Res() res: Response + ): Promise { + + connectionDto.orgId = orgId; + const connectionData = await this.connectionService.createLegacyConnectionInvitation(connectionDto, reqUser); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.connection.success.create, + data: connectionData.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + + } /** * Catch connection webhook responses. @@ -134,68 +170,10 @@ export class ConnectionController { ): Promise { const connectionData = await this.connectionService.getConnectionWebhook(connectionDto, id); const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, + statusCode: HttpStatus.CREATED, message: ResponseMessages.connection.success.create, data: connectionData }; - return res.status(HttpStatus.OK).json(finalResponse); - } - - /** - * Shortening url based on reference Id. - * @param referenceId The referenceId is set as a request parameter. - * @param res The current url is set as a header in the response parameter. - */ - @Get('connections/url/:referenceId') - @ApiOperation({ - summary: 'Shortening url based on reference Id', - description: 'Shortening url based on reference Id' - }) - async getPresentproofRequestUrl( - @Param('referenceId') referenceId: string, - @Res() res: Response - ): Promise { - const originalUrlData = await this.connectionService.getUrl(referenceId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.connection.success.create, - data: originalUrlData.response - }; - return res.status(HttpStatus.OK).json(finalResponse.data); - } - - /** -* Description: Get all connections by connectionId -* @param user -* @param connectionId -* @param orgId -* -*/ - @Get('connections/:connectionId') - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiOperation({ - summary: `Fetch all connections details by connectionId`, - description: `Fetch all connections details by connectionId` - }) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiResponse({ status: 201, description: 'Success', type: AuthTokenResponse }) - async getConnectionsById( - @User() user: IUserRequest, - @Param('connectionId') connectionId: string, - @Query('orgId') orgId: number, - @Res() res: Response - ): Promise { - const connectionsDetails = await this.connectionService.getConnectionsById(user, connectionId, orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.connection.success.fetch, - data: connectionsDetails.response - }; - return res.status(HttpStatus.OK).json(finalResponse); + return res.status(HttpStatus.CREATED).json(finalResponse); } } diff --git a/apps/api-gateway/src/connection/dtos/connection.dto.ts b/apps/api-gateway/src/connection/dtos/connection.dto.ts index e8bb7c55a..14d5d8f6e 100644 --- a/apps/api-gateway/src/connection/dtos/connection.dto.ts +++ b/apps/api-gateway/src/connection/dtos/connection.dto.ts @@ -1,4 +1,4 @@ -import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; @@ -27,12 +27,9 @@ export class CreateConnectionDto { @ApiProperty() @IsBoolean() @IsOptional() - @IsNotEmpty({ message: 'please provide valid autoAcceptConnection' }) + @IsNotEmpty({ message: 'autoAcceptConnection should boolean' }) autoAcceptConnection: boolean; - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; } diff --git a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts index 9cd7d0494..6ce6c3d57 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts @@ -117,6 +117,6 @@ export class CredentialDefinitionController { message: ResponseMessages.credentialDefinition.success.create, data: credentialsDefinitionDetails.response }; - return res.status(HttpStatus.OK).json(credDefResponse); + return res.status(HttpStatus.CREATED).json(credDefResponse); } } \ No newline at end of file diff --git a/apps/api-gateway/src/credential-definition/credential-definition.service.ts b/apps/api-gateway/src/credential-definition/credential-definition.service.ts index 36f1dbf91..b66b607f0 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.service.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.service.ts @@ -1,5 +1,5 @@ import { Injectable, Inject } from '@nestjs/common'; -import { ClientProxy, RpcException } from '@nestjs/microservices'; +import { ClientProxy } from '@nestjs/microservices'; import { CreateCredentialDefinitionDto } from './dto/create-cred-defs.dto'; import { BaseService } from '../../../../libs/service/base.service'; import { IUserRequestInterface } from '../interfaces/IUserRequestInterface'; @@ -15,32 +15,17 @@ export class CredentialDefinitionService extends BaseService { } createCredentialDefinition(credDef: CreateCredentialDefinitionDto, user: IUserRequestInterface): Promise<{ response: object }> { - try { - const payload = { credDef, user }; - return this.sendNats(this.credDefServiceProxy, 'create-credential-definition', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { credDef, user }; + return this.sendNats(this.credDefServiceProxy, 'create-credential-definition', payload); } getCredentialDefinitionById(credentialDefinitionId: string, orgId: number): Promise<{ response: object }> { - try { - const payload = { credentialDefinitionId, orgId }; - return this.sendNats(this.credDefServiceProxy, 'get-credential-definition-by-id', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { credentialDefinitionId, orgId }; + return this.sendNats(this.credDefServiceProxy, 'get-credential-definition-by-id', payload); } getAllCredDefs(credDefSearchCriteria: GetAllCredDefsDto, user: IUserRequestInterface, orgId: number): Promise<{ response: object }> { - try { - const payload = { credDefSearchCriteria, user, orgId }; - return this.sendNats(this.credDefServiceProxy, 'get-all-credential-definitions', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { credDefSearchCriteria, user, orgId }; + return this.sendNats(this.credDefServiceProxy, 'get-all-credential-definitions', payload); } } diff --git a/apps/api-gateway/src/dtos/create-schema.dto.ts b/apps/api-gateway/src/dtos/create-schema.dto.ts index 232af5a4f..bedcf23a3 100644 --- a/apps/api-gateway/src/dtos/create-schema.dto.ts +++ b/apps/api-gateway/src/dtos/create-schema.dto.ts @@ -2,9 +2,18 @@ import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -interface IAttributeValue{ +class AttributeValue { + + @IsString() + @IsNotEmpty({ message: 'attributeName is required.' }) attributeName: string; + + @IsString() + @IsNotEmpty({ message: 'schemaDataType is required.' }) schemaDataType: string; + + @IsString() + @IsNotEmpty({ message: 'displayName is required.' }) displayName: string; } @@ -17,10 +26,18 @@ export class CreateSchemaDto { @IsString({ message: 'schema name must be a string' }) @IsNotEmpty({ message: 'please provide valid schema name' }) schemaName: string; - @ApiProperty() + @ApiProperty({ + 'example': [ + { + attributeName: 'name', + schemaDataType: 'string', + displayName: 'Name' + } + ] + }) @IsArray({ message: 'attributes must be an array' }) @IsNotEmpty({ message: 'please provide valid attributes' }) - attributes: IAttributeValue[]; + attributes: AttributeValue[]; orgId: number; diff --git a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts index 5deff2558..e2f6a4c37 100644 --- a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts +++ b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts @@ -1,4 +1,4 @@ -import { IsArray, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; interface attribute { @@ -29,15 +29,10 @@ export class IssueCredentialDto { @IsString({ message: 'connectionId should be string' }) connectionId: string; - @ApiProperty({ example: 'v1' }) @IsOptional() @IsNotEmpty({ message: 'Please provide valid protocol-version' }) @IsString({ message: 'protocol-version should be string' }) protocolVersion?: string; - - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; } diff --git a/apps/api-gateway/src/issuance/issuance.controller.ts b/apps/api-gateway/src/issuance/issuance.controller.ts index f0f4d9817..3bbf6b559 100644 --- a/apps/api-gateway/src/issuance/issuance.controller.ts +++ b/apps/api-gateway/src/issuance/issuance.controller.ts @@ -45,7 +45,7 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler @Controller() @UseFilters(CustomExceptionFilter) -@ApiTags('issuances') +@ApiTags('credentials') @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @@ -57,25 +57,110 @@ export class IssuanceController { ) { } private readonly logger = new Logger('IssuanceController'); + /** + * Description: Get all issued credentials + * @param user + * @param threadId + * @param connectionId + * @param state + * @param orgId + * + */ + @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: 200, description: 'Success', type: ApiResponseDto }) + @ApiQuery( + { name: 'threadId', required: false } + ) + @ApiQuery( + { name: 'connectionId', required: false } + ) + @ApiQuery( + { name: 'state', enum: IssueCredential, required: false } + ) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) + async getIssueCredentials( + @User() user: IUserRequest, + @Query('threadId') threadId: string, + @Query('connectionId') connectionId: string, + @Query('state') state: string, + @Param('orgId') orgId: number, + @Res() res: Response + ): Promise { + + const getCredentialDetails = await this.issueCredentialService.getIssueCredentials(user, threadId, connectionId, state, orgId); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.issuance.success.fetch, + data: getCredentialDetails.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** + * Description: Get all issued credentials + * @param user + * @param credentialRecordId + * @param orgId + * + */ + @Get('/orgs/:orgId/credentials/:credentialRecordId') + @ApiBearerAuth() + @ApiOperation({ + summary: `Get credential by credentialRecordId`, + description: `Get credential credentialRecordId` + }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) + async getIssueCredentialsbyCredentialRecordId( + @User() user: IUserRequest, + @Param('credentialRecordId') credentialRecordId: string, + @Param('orgId') orgId: number, + + @Res() res: Response + ): Promise { + + const getCredentialDetails = await this.issueCredentialService.getIssueCredentialsbyCredentialRecordId(user, credentialRecordId, orgId); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.issuance.success.fetch, + data: getCredentialDetails.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + /** * Description: Issuer send credential to create offer * @param user * @param issueCredentialDto */ - @Post('issue-credentials/create-offer') - @UseGuards(AuthGuard('jwt')) + @Post('/orgs/:orgId/credentials/offer') @ApiBearerAuth() @ApiOperation({ summary: `Send credential details to create-offer`, description: `Send credential details to create-offer` }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) async sendCredential( @User() user: IUserRequest, + @Param('orgId') orgId: number, @Body() issueCredentialDto: IssueCredentialDto, @Res() res: Response ): Promise { + issueCredentialDto.orgId = orgId; const attrData = issueCredentialDto.attributes; attrData.forEach((data) => { @@ -124,93 +209,4 @@ export class IssuanceController { } - /** - * Description: Get all issued credentials - * @param user - * @param threadId - * @param connectionId - * @param state - * @param orgId - * - */ - @Get('/issue-credentials') - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiOperation({ - summary: `Fetch all issued credentials`, - description: `Fetch all issued credentials` - }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @ApiQuery( - { name: 'threadId', required: false } - ) - @ApiQuery( - { name: 'connectionId', required: false } - ) - @ApiQuery( - { name: 'state', enum: IssueCredential, required: false } - ) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiBearerAuth() - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async getIssueCredentials( - @User() user: IUserRequest, - @Query('threadId') threadId: string, - @Query('connectionId') connectionId: string, - @Query('state') state: string, - @Query('orgId') orgId: number, - @Res() res: Response - ): Promise { - - state = state || undefined; - const getCredentialDetails = await this.issueCredentialService.getIssueCredentials(user, threadId, connectionId, state, orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.issuance.success.fetch, - data: getCredentialDetails.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - - - /** - * Description: Get all issued credentials - * @param user - * @param credentialRecordId - * @param orgId - * - */ - @Get('issue-credentials/:credentialRecordId') - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiOperation({ - summary: `Fetch all issued credentials by credentialRecordId`, - description: `Fetch all issued credentials by credentialRecordId` - }) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async getIssueCredentialsbyCredentialRecordId( - @User() user: IUserRequest, - @Param('credentialRecordId') credentialRecordId: string, - @Query('orgId') orgId: number, - - @Res() res: Response - ): Promise { - - const getCredentialDetails = await this.issueCredentialService.getIssueCredentialsbyCredentialRecordId(user, credentialRecordId, orgId); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.issuance.success.fetch, - data: getCredentialDetails.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - } diff --git a/apps/api-gateway/src/schema/schema.service.ts b/apps/api-gateway/src/schema/schema.service.ts index ed038952e..333f4b5ff 100644 --- a/apps/api-gateway/src/schema/schema.service.ts +++ b/apps/api-gateway/src/schema/schema.service.ts @@ -1,5 +1,5 @@ import { Injectable, Inject } from '@nestjs/common'; -import { ClientProxy, RpcException } from '@nestjs/microservices'; +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'; @@ -15,48 +15,28 @@ export class SchemaService extends BaseService { createSchema(schema: CreateSchemaDto, user: IUserRequestInterface, orgId: number): Promise<{ response: object; }> { - try { - const payload = { schema, user, orgId }; - return this.sendNats(this.schemaServiceProxy, 'create-schema', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { schema, user, orgId }; + return this.sendNats(this.schemaServiceProxy, 'create-schema', payload); } getSchemaById(schemaId: string, orgId: number): Promise<{ response: object; }> { - try { - const payload = { schemaId, orgId }; - return this.sendNats(this.schemaServiceProxy, 'get-schema-by-id', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { schemaId, orgId }; + return this.sendNats(this.schemaServiceProxy, 'get-schema-by-id', payload); } getSchemas(schemaSearchCriteria: ISchemaSearchInterface, user: IUserRequestInterface, orgId: number): Promise<{ response: object; }> { - try { - const schemaSearch = { schemaSearchCriteria, user, orgId }; - return this.sendNats(this.schemaServiceProxy, 'get-schemas', schemaSearch); - } catch (error) { - throw new RpcException(error.response); - - } + const schemaSearch = { schemaSearchCriteria, user, orgId }; + return this.sendNats(this.schemaServiceProxy, 'get-schemas', schemaSearch); } getcredDeffListBySchemaId(schemaId: string, schemaSearchCriteria: ICredDeffSchemaSearchInterface, user: IUserRequestInterface, orgId: number): Promise<{ response: object; }> { - try { - const payload = { schemaId, schemaSearchCriteria, user, orgId }; - return this.sendNats(this.schemaServiceProxy, 'get-cred-deff-list-by-schemas-id', payload); - } catch (error) { - throw new RpcException(error.response); - - } + const payload = { schemaId, schemaSearchCriteria, user, orgId }; + return this.sendNats(this.schemaServiceProxy, 'get-cred-deff-list-by-schemas-id', payload); } } \ No newline at end of file diff --git a/apps/api-gateway/src/verification/dto/request-proof.dto.ts b/apps/api-gateway/src/verification/dto/request-proof.dto.ts index 4a5ee350e..7081a3f58 100644 --- a/apps/api-gateway/src/verification/dto/request-proof.dto.ts +++ b/apps/api-gateway/src/verification/dto/request-proof.dto.ts @@ -1,24 +1,31 @@ -import { IsArray, IsEmail, IsNotEmpty, IsNumber, IsObject, IsOptional, IsString, MaxLength } from 'class-validator'; +import { IsArray, IsEmail, IsNotEmpty, IsObject, IsOptional, IsString, MaxLength } from 'class-validator'; import { toLowerCase, trim } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -// import { IProofRequestAttribute } from '../interfaces/verification.interface'; -class IProofRequestAttribute { +export class ProofRequestAttribute { @IsString() + @IsNotEmpty({ message: 'attributeName is required.' }) attributeName: string; @IsString() + @IsNotEmpty({ message: 'schemaId is required.' }) + schemaId: string; + + @IsString() + @IsOptional() + @IsNotEmpty({ message: 'condition is required.' }) condition?: string; @IsString() + @IsOptional() + @IsNotEmpty({ message: 'value is required.' }) value?: string; @IsString() + @IsOptional() + @IsNotEmpty({ message: 'credDefId is required.' }) credDefId?: string; - - @IsString() - schemaId: string; } export class RequestProof { @@ -43,15 +50,11 @@ export class RequestProof { @IsArray({ message: 'attributes must be in array' }) @IsObject({ each: true }) @IsNotEmpty({ message: 'please provide valid attributes' }) - attributes: IProofRequestAttribute[]; + attributes: ProofRequestAttribute[]; @ApiProperty() @IsOptional() comment: string; - - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; @IsString({ message: 'auto accept proof must be in string' }) @@ -80,7 +83,7 @@ export class OutOfBandRequestProof { @IsArray({ message: 'attributes must be in array' }) @IsObject({ each: true }) @IsNotEmpty({ message: 'please provide valid attributes' }) - attributes: IProofRequestAttribute[]; + attributes: ProofRequestAttribute[]; @ApiProperty({ example: 'string' }) @IsNotEmpty({ message: 'Please provide valid emailId' }) @@ -94,10 +97,6 @@ export class OutOfBandRequestProof { @ApiProperty() @IsOptional() comment: string; - - @ApiProperty() - @IsNumber() - @IsNotEmpty({ message: 'please provide orgId' }) orgId: number; @IsString({ message: 'autoAcceptProof must be in string' }) diff --git a/apps/api-gateway/src/verification/verification.controller.ts b/apps/api-gateway/src/verification/verification.controller.ts index 328bd2540..672e8bbba 100644 --- a/apps/api-gateway/src/verification/verification.controller.ts +++ b/apps/api-gateway/src/verification/verification.controller.ts @@ -12,7 +12,7 @@ import { ApiQuery, ApiExcludeEndpoint } from '@nestjs/swagger'; -import { Controller, Logger, Post, Body, Get, Query, HttpStatus, Res, UseGuards, Param, UseFilters } from '@nestjs/common'; +import { Controller, Logger, Post, Body, Get, Query, HttpStatus, Res, UseGuards, Param, UseFilters, BadRequestException } from '@nestjs/common'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; @@ -33,33 +33,27 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler @UseFilters(CustomExceptionFilter) @ApiBearerAuth() @Controller() +@ApiTags('verifications') export class VerificationController { constructor(private readonly verificationService: VerificationService) { } private readonly logger = new Logger('VerificationController'); - @Get('/proofs/form-data') - @ApiTags('verifications') + @Get('/orgs/:orgId/proofs/:proofId/form') @ApiOperation({ summary: `Get a proof form data`, description: `Get a proof form data` }) - @ApiQuery( - { name: 'id', required: true } - ) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getProofFormData( @Res() res: Response, @GetUser() user: IUserRequest, - @Query('id') id: string, - @Query('orgId') orgId: number + @Param('proofId') id: string, + @Param('orgId') orgId: number ): Promise { const sendProofRequest = await this.verificationService.getProofFormData(id, orgId, user); const finalResponse: IResponseType = { @@ -71,75 +65,67 @@ export class VerificationController { } /** - * Get all proof presentations + * Get proof presentation by id * @param user + * @param id * @param orgId - * @returns Get all proof presentation + * @returns Get proof presentation details */ - @Get('/proofs') - @ApiTags('verifications') + @Get('/orgs/:orgId/proofs/:proofId') @ApiOperation({ - summary: `Get all proof-presentation`, - description: `Get all proof-presentation` + summary: `Get all proof presentation by proof Id`, + description: `Get all proof presentation by proof Id` }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiQuery( - { name: 'orgId', required: true } - ) - @ApiQuery( - { name: 'threadId', required: false } - ) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async getProofPresentations( + async getProofPresentationById( @Res() res: Response, @GetUser() user: IUserRequest, - @Query('orgId') orgId: number, - @Query('threadId') threadId: string + @Param('proofId') id: string, + @Param('orgId') orgId: number ): Promise { - const proofPresentationDetails = await this.verificationService.getProofPresentations(orgId, threadId, user); + const getProofPresentationById = await this.verificationService.getProofPresentationById(id, orgId, user); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.verification.success.fetch, - data: proofPresentationDetails.response + data: getProofPresentationById.response }; return res.status(HttpStatus.OK).json(finalResponse); } /** - * Get proof presentation by id - * @param user - * @param id - * @param orgId - * @returns Get proof presentation details - */ - @Get('/proofs/:id') - @ApiTags('verifications') + * Get all proof presentations + * @param user + * @param orgId + * @returns Get all proof presentation + */ + @Get('/orgs/:orgId/proofs') @ApiOperation({ - summary: `Get proof-presentation by Id`, - description: `Get proof-presentation by Id` + summary: `Get all proof presentations`, + description: `Get all proof presentations` }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiQuery( - { name: 'orgId', required: true } + { name: 'threadId', required: false } ) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async getProofPresentationById( + async getProofPresentations( @Res() res: Response, @GetUser() user: IUserRequest, - @Param('id') id: string, - @Query('orgId') orgId: number + @Param('orgId') orgId: number, + @Query('threadId') threadId: string ): Promise { - const getProofPresentationById = await this.verificationService.getProofPresentationById(id, orgId, user); + const proofPresentationDetails = await this.verificationService.getProofPresentations(orgId, threadId, user); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.verification.success.fetch, - data: getProofPresentationById.response + data: proofPresentationDetails.response }; return res.status(HttpStatus.OK).json(finalResponse); } @@ -150,8 +136,7 @@ export class VerificationController { * @param requestProof * @returns Get requested proof presentation details */ - @Post('/proofs/request-proof') - @ApiTags('verifications') + @Post('/orgs/:orgId/proofs') @ApiOperation({ summary: `Sends a proof request`, description: `Sends a proof request` @@ -160,13 +145,20 @@ export class VerificationController { @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiBody({ type: RequestProof }) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) async sendPresentationRequest( @Res() res: Response, @GetUser() user: IUserRequest, + @Param('orgId') orgId: number, @Body() requestProof: RequestProof ): Promise { + + for (const attrData of requestProof.attributes) { + await this.validateAttribute(attrData); + } + + requestProof.orgId = orgId; const sendProofRequest = await this.verificationService.sendProofRequest(requestProof, user); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -183,8 +175,7 @@ export class VerificationController { * @param orgId * @returns Get verified proof presentation details */ - @Post('proofs/verify-presentation') - @ApiTags('verifications') + @Post('/orgs/:orgId/proofs/:proofId/verify') @ApiOperation({ summary: `Verify presentation`, description: `Verify presentation` @@ -192,19 +183,13 @@ export class VerificationController { @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiQuery( - { name: 'id', required: true } - ) - @ApiQuery( - { name: 'orgId', required: true } - ) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async verifyPresentation( @Res() res: Response, @GetUser() user: IUserRequest, - @Query('id') id: string, - @Query('orgId') orgId: number + @Param('proofId') id: string, + @Param('orgId') orgId: number ): Promise { const verifyPresentation = await this.verificationService.verifyPresentation(id, orgId, user); const finalResponse: IResponseType = { @@ -215,39 +200,13 @@ export class VerificationController { return res.status(HttpStatus.CREATED).json(finalResponse); } - @Post('wh/:id/proofs') - @ApiTags('verifications') - @ApiOperation({ - summary: `Webhook proof presentation`, - description: `Webhook proof presentation` - }) - @ApiExcludeEndpoint() - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) - async webhookProofPresentation( - @Param('id') id: string, - @Body() proofPresentationPayload: WebhookPresentationProof, - @Res() res: Response - ): Promise { - - const webhookProofPresentation = await this.verificationService.webhookProofPresentation(id, proofPresentationPayload); - const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.verification.success.fetch, - data: webhookProofPresentation.response - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - /** * Out-Of-Band Proof Presentation * @param user * @param outOfBandRequestProof * @returns Get out-of-band requested proof presentation details */ - @Post('/proofs/create-request-oob') - @ApiTags('verifications') + @Post('/orgs/:orgId/proofs/oob') @ApiOperation({ summary: `Sends a out-of-band proof request`, description: `Sends a out-of-band proof request` @@ -261,8 +220,15 @@ export class VerificationController { async sendOutOfBandPresentationRequest( @Res() res: Response, @GetUser() user: IUserRequest, - @Body() outOfBandRequestProof: OutOfBandRequestProof + @Body() outOfBandRequestProof: OutOfBandRequestProof, + @Param('orgId') orgId: number ): Promise { + + for (const attrData of outOfBandRequestProof.attributes) { + await this.validateAttribute(attrData); + } + + outOfBandRequestProof.orgId = orgId; const sendProofRequest = await this.verificationService.sendOutOfBandPresentationRequest(outOfBandRequestProof, user); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -271,5 +237,52 @@ export class VerificationController { }; return res.status(HttpStatus.CREATED).json(finalResponse); } + + @Post('wh/:id/proofs') + @ApiOperation({ + summary: `Webhook proof presentation`, + description: `Webhook proof presentation` + }) + @ApiExcludeEndpoint() + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) + async webhookProofPresentation( + @Param('id') id: string, + @Body() proofPresentationPayload: WebhookPresentationProof, + @Res() res: Response + ): Promise { + + const webhookProofPresentation = await this.verificationService.webhookProofPresentation(id, proofPresentationPayload); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.verification.success.fetch, + data: webhookProofPresentation.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + async validateAttribute( + attrData: object + ): Promise { + + if (!attrData['attributeName']) { + throw new BadRequestException('attributeName must be required'); + } else if (!attrData['schemaId']) { + throw new BadRequestException('schemaId must be required'); + } + + if (undefined !== attrData['credDefId'] && '' === attrData['credDefId'].trim()) { + throw new BadRequestException('credDefId cannot be empty'); + } + + if (undefined !== attrData['condition'] && '' === attrData['condition'].trim()) { + throw new BadRequestException('condition cannot be empty'); + } + + if (undefined !== attrData['value'] && '' === attrData['value'].trim()) { + throw new BadRequestException('value cannot be empty'); + } + } } diff --git a/apps/connection/src/connection.service.ts b/apps/connection/src/connection.service.ts index 51756a352..78b149be1 100644 --- a/apps/connection/src/connection.service.ts +++ b/apps/connection/src/connection.service.ts @@ -78,7 +78,7 @@ export class ConnectionService { return saveConnectionDetails; } catch (error) { this.logger.error(`[createLegacyConnectionInvitation] - error in connection invitation: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -97,7 +97,7 @@ export class ConnectionService { return saveConnectionDetails; } catch (error) { this.logger.error(`[getConnectionWebhook] - error in fetch connection webhook: ${error}`); - throw new RpcException(error.response); + throw new RpcException(error.response ? error.response : error); } } @@ -148,8 +148,7 @@ export class ConnectionService { return urlDetails.url; } catch (error) { this.logger.error(`Error in get url in connection service: ${JSON.stringify(error)}`); - throw error; - + throw new RpcException(error.response ? error.response : error); } } @@ -209,8 +208,7 @@ export class ConnectionService { return connectionsDetails?.response; } catch (error) { this.logger.error(`Error in get url in connection service: ${JSON.stringify(error)}`); - throw error; - + throw new RpcException(error.response ? error.response : error); } } @@ -271,7 +269,7 @@ export class ConnectionService { return createConnectionInvitation?.response; } catch (error) { this.logger.error(`[getConnectionsById] - error in get connections : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index f6caddcd5..1760c97fc 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -55,7 +55,7 @@ export class IssuanceService { return credentialCreateOfferDetails?.response; } catch (error) { this.logger.error(`[sendCredentialCreateOffer] - error in create credentials : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } @@ -90,7 +90,7 @@ export class IssuanceService { return credentialCreateOfferDetails?.response; } catch (error) { this.logger.error(`[sendCredentialCreateOffer] - error in create credentials : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } @@ -152,7 +152,7 @@ export class IssuanceService { return issueCredentialsDetails?.response; } catch (error) { this.logger.error(`[sendCredentialCreateOffer] - error in create credentials : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -203,7 +203,7 @@ export class IssuanceService { return createConnectionInvitation?.response; } catch (error) { this.logger.error(`[getIssueCredentialsbyCredentialRecordId] - error in get credentials : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } @@ -213,7 +213,7 @@ export class IssuanceService { return agentDetails; } catch (error) { this.logger.error(`[getIssueCredentialsbyCredentialRecordId] - error in get credentials : ${JSON.stringify(error)}`); - throw error; + throw new RpcException(error.response ? error.response : error); } } @@ -311,7 +311,6 @@ export class IssuanceService { } catch (error) { this.logger.error(`Error in get agent url: ${JSON.stringify(error)}`); throw error; - } } } diff --git a/apps/verification/src/verification.service.ts b/apps/verification/src/verification.service.ts index c0b57c04f..b7fbad1e0 100644 --- a/apps/verification/src/verification.service.ts +++ b/apps/verification/src/verification.service.ts @@ -49,7 +49,7 @@ export class VerificationService { } catch (error) { this.logger.error(`[getProofPresentations] - error in get proof presentation : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -109,7 +109,7 @@ export class VerificationService { return getProofPresentationById?.response; } catch (error) { this.logger.error(`[getProofPresentationById] - error in get proof presentation by id : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -157,8 +157,6 @@ export class VerificationService { */ async sendProofRequest(requestProof: IRequestProof): Promise { try { - let requestedAttributes = {}; - const requestedPredicates = {}; const comment = requestProof.comment ? requestProof.comment : ''; let proofRequestPayload: ISendProofRequestPayload = { @@ -176,73 +174,7 @@ export class VerificationService { autoAcceptProof: '' }; - const attributeWithSchemaIdExists = requestProof.attributes.some(attribute => attribute.schemaId); - if (attributeWithSchemaIdExists) { - requestedAttributes = Object.fromEntries(requestProof.attributes.map((attribute, index) => { - - const attributeElement = attribute.attributeName; - const attributeReferent = `additionalProp${index + 1}`; - - if (!attribute.condition && !attribute.value) { - const keys = Object.keys(requestedAttributes); - - if (0 < keys.length) { - let attributeFound = false; - - for (const attr of keys) { - if ( - requestedAttributes[attr].restrictions.some(res => res.schema_id) === - requestProof.attributes[index].schemaId - ) { - requestedAttributes[attr].name.push(attributeElement); - attributeFound = true; - } - - if (attr === keys[keys.length - 1] && !attributeFound) { - requestedAttributes[attributeReferent] = { - name: attributeElement, - restrictions: [ - { - cred_def_id: requestProof.attributes[index].credDefId ? requestProof.attributes[index].credDefId : undefined, - schema_id: requestProof.attributes[index].schemaId - } - ] - }; - } - } - } else { - return [ - attributeReferent, - { - name: attributeElement, - restrictions: [ - { - cred_def_id: requestProof.attributes[index].credDefId ? requestProof.attributes[index].credDefId : undefined, - schema_id: requestProof.attributes[index].schemaId - } - ] - } - ]; - } - } else { - requestedPredicates[attributeReferent] = { - p_type: attribute.condition, - restrictions: [ - { - cred_def_id: requestProof.attributes[index].credDefId ? requestProof.attributes[index].credDefId : undefined, - schema_id: requestProof.attributes[index].schemaId - } - ], - name: attributeElement, - p_value: parseInt(attribute.value) - }; - } - - return [attributeReferent]; - })); - } else { - throw new BadRequestException(ResponseMessages.verification.error.schemaIdNotFound); - } + const { requestedAttributes, requestedPredicates } = await this._proofRequestPayload(requestProof); proofRequestPayload = { protocolVersion: requestProof.protocolVersion ? requestProof.protocolVersion : 'v1', @@ -272,7 +204,7 @@ export class VerificationService { return getProofPresentationById?.response; } catch (error) { this.logger.error(`[verifyPresentation] - error in verify presentation : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -330,7 +262,7 @@ export class VerificationService { return getProofPresentationById?.response; } catch (error) { this.logger.error(`[verifyPresentation] - error in verify presentation : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -378,7 +310,7 @@ export class VerificationService { } catch (error) { this.logger.error(`[webhookProofPresentation] - error in webhook proof presentation : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -487,7 +419,7 @@ export class VerificationService { } catch (error) { this.logger.error(`[sendOutOfBandPresentationRequest] - error in out of band proof request : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -536,9 +468,8 @@ export class VerificationService { const requestedPredicates = {}; const attributeWithSchemaIdExists = proofRequestpayload.attributes.some(attribute => attribute.schemaId); if (attributeWithSchemaIdExists) { + requestedAttributes = Object.fromEntries(proofRequestpayload.attributes.map((attribute, index) => { - requestedAttributes = {}; - for (const [index, attribute] of proofRequestpayload.attributes.entries()) { const attributeElement = attribute.attributeName; const attributeReferent = `additionalProp${index + 1}`; @@ -550,9 +481,8 @@ export class VerificationService { for (const attr of keys) { if ( - requestedAttributes[attr].restrictions.some( - res => res.schema_id === proofRequestpayload.attributes[index].schemaId - ) + requestedAttributes[attr].restrictions.some(res => res.schema_id) === + proofRequestpayload.attributes[index].schemaId ) { requestedAttributes[attr].name.push(attributeElement); attributeFound = true; @@ -563,9 +493,7 @@ export class VerificationService { name: attributeElement, restrictions: [ { - cred_def_id: proofRequestpayload.attributes[index].credDefId - ? proofRequestpayload.attributes[index].credDefId - : undefined, + cred_def_id: proofRequestpayload.attributes[index].credDefId ? proofRequestpayload.attributes[index].credDefId : undefined, schema_id: proofRequestpayload.attributes[index].schemaId } ] @@ -573,32 +501,25 @@ export class VerificationService { } } } else { - requestedAttributes[attributeReferent] = { - name: attributeElement, - restrictions: [ - { - cred_def_id: proofRequestpayload.attributes[index].credDefId - ? proofRequestpayload.attributes[index].credDefId - : undefined, - schema_id: proofRequestpayload.attributes[index].schemaId - } - ] - }; + return [ + attributeReferent, + { + name: attributeElement, + restrictions: [ + { + cred_def_id: proofRequestpayload.attributes[index].credDefId ? proofRequestpayload.attributes[index].credDefId : undefined, + schema_id: proofRequestpayload.attributes[index].schemaId + } + ] + } + ]; } } else { - if (isNaN(parseInt(attribute.value))) { - throw new BadRequestException( - ResponseMessages.verification.error.predicatesValueNotNumber - ); - } - requestedPredicates[attributeReferent] = { p_type: attribute.condition, restrictions: [ { - cred_def_id: proofRequestpayload.attributes[index].credDefId - ? proofRequestpayload.attributes[index].credDefId - : undefined, + cred_def_id: proofRequestpayload.attributes[index].credDefId ? proofRequestpayload.attributes[index].credDefId : undefined, schema_id: proofRequestpayload.attributes[index].schemaId } ], @@ -606,20 +527,20 @@ export class VerificationService { p_value: parseInt(attribute.value) }; } - } + + return [attributeReferent]; + })); return { requestedAttributes, requestedPredicates }; } else { - throw new BadRequestException( - ResponseMessages.verification.error.schemaIdNotFound - ); + throw new BadRequestException(ResponseMessages.verification.error.schemaIdNotFound); } } catch (error) { this.logger.error(`[proofRequestPayload] - error in proof request payload : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } @@ -815,7 +736,7 @@ export class VerificationService { return extractedDataArray; } catch (error) { this.logger.error(`[getProofFormData] - error in get proof form data : ${JSON.stringify(error)}`); - throw new RpcException(error); + throw new RpcException(error.response ? error.response : error); } } diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index e7cba4251..4f9dd4b5e 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -133,7 +133,7 @@ export const ResponseMessages = { connection: { success: { create: 'Connection created successfully', - fetch: 'Connection Details fetched successfully' + fetch: 'Connection fetched successfully' }, error: { exists: 'Connection is already exist', From 8dd7dcaf9db1497d75763ca5123224443f7dc4df Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Tue, 26 Sep 2023 19:46:07 +0530 Subject: [PATCH 072/162] feat: support afj-0.4.1 (#102) Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- .env.sample | 2 +- apps/agent-provisioning/AFJ/scripts/start_agent.sh | 2 +- apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh | 2 +- apps/api-gateway/src/connection/connection.controller.ts | 1 + apps/api-gateway/src/issuance/issuance.controller.ts | 1 + apps/api-gateway/src/verification/verification.controller.ts | 2 +- 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.env.sample b/.env.sample index 1739e22e2..9e9e35b11 100644 --- a/.env.sample +++ b/.env.sample @@ -37,7 +37,7 @@ WALLET_STORAGE_PASSWORD=xxxxxx CRYPTO_PRIVATE_KEY=xxxxx-xxxxx-xxxxx-xxxxx #It should be same as studio UI -AFJ_VERSION=afj-0.4.0:latest +AFJ_VERSION=afj-0.4.1:latest FIDO_API_ENDPOINT=http://localhost:8000 # Host:port of your FIDO (WebAuthn) Server diff --git a/apps/agent-provisioning/AFJ/scripts/start_agent.sh b/apps/agent-provisioning/AFJ/scripts/start_agent.sh index 881ed70d6..899229c80 100755 --- a/apps/agent-provisioning/AFJ/scripts/start_agent.sh +++ b/apps/agent-provisioning/AFJ/scripts/start_agent.sh @@ -48,7 +48,7 @@ cat <>${PWD}/apps/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINE "label": "${AGENCY}_${CONTAINER_NAME}", "walletId": "$WALLET_NAME", "walletKey": "$WALLET_PASSWORD", - "walletType": "postgres_storage", + "walletType": "postgres", "walletUrl": "$WALLET_STORAGE_HOST:$WALLET_STORAGE_PORT", "walletAccount": "$WALLET_STORAGE_USER", "walletPassword": "$WALLET_STORAGE_PASSWORD", diff --git a/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh b/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh index f757e53bb..db3de117f 100644 --- a/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh +++ b/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh @@ -38,7 +38,7 @@ cat <>/app/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINER_NAME} "label": "${AGENCY}_${CONTAINER_NAME}", "walletId": "$WALLET_NAME", "walletKey": "$WALLET_PASSWORD", - "walletType": "postgres_storage", + "walletType": "postgres", "walletUrl": "$WALLET_STORAGE_HOST:$WALLET_STORAGE_PORT", "walletAccount": "$WALLET_STORAGE_USER", "walletPassword": "$WALLET_STORAGE_PASSWORD", diff --git a/apps/api-gateway/src/connection/connection.controller.ts b/apps/api-gateway/src/connection/connection.controller.ts index d05e52845..d87f58721 100644 --- a/apps/api-gateway/src/connection/connection.controller.ts +++ b/apps/api-gateway/src/connection/connection.controller.ts @@ -168,6 +168,7 @@ export class ConnectionController { @Param('id') id: number, @Res() res: Response ): Promise { + this.logger.debug(`connectionDto ::: ${JSON.stringify(connectionDto)}`); const connectionData = await this.connectionService.getConnectionWebhook(connectionDto, id); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, diff --git a/apps/api-gateway/src/issuance/issuance.controller.ts b/apps/api-gateway/src/issuance/issuance.controller.ts index 3bbf6b559..ba130ad24 100644 --- a/apps/api-gateway/src/issuance/issuance.controller.ts +++ b/apps/api-gateway/src/issuance/issuance.controller.ts @@ -199,6 +199,7 @@ export class IssuanceController { @Param('id') id: number, @Res() res: Response ): Promise { + this.logger.debug(`issueCredentialDto ::: ${issueCredentialDto}`); const getCredentialDetails = await this.issueCredentialService.getIssueCredentialWebhook(issueCredentialDto, id); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, diff --git a/apps/api-gateway/src/verification/verification.controller.ts b/apps/api-gateway/src/verification/verification.controller.ts index 672e8bbba..f9f555c52 100644 --- a/apps/api-gateway/src/verification/verification.controller.ts +++ b/apps/api-gateway/src/verification/verification.controller.ts @@ -252,7 +252,7 @@ export class VerificationController { @Body() proofPresentationPayload: WebhookPresentationProof, @Res() res: Response ): Promise { - + this.logger.debug(`proofPresentationPayload ::: ${JSON.stringify(proofPresentationPayload)}`); const webhookProofPresentation = await this.verificationService.webhookProofPresentation(id, proofPresentationPayload); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, From d49acd2461bf835ab0f33fe9554a354ef4a41e37 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Wed, 27 Sep 2023 16:09:21 +0530 Subject: [PATCH 073/162] Fix bugs platform module (#103) * feat: support afj-0.4.1 Signed-off-by: KulkarniShashank * fix: Add nats configuration in platform Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/api-gateway/src/platform/platform.module.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/api-gateway/src/platform/platform.module.ts b/apps/api-gateway/src/platform/platform.module.ts index ba0ffe2ec..692fe9918 100644 --- a/apps/api-gateway/src/platform/platform.module.ts +++ b/apps/api-gateway/src/platform/platform.module.ts @@ -3,7 +3,6 @@ import { PlatformController } from './platform.controller'; import { PlatformService } from './platform.service'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { ConfigModule } from '@nestjs/config'; -import { commonNatsOptions } from 'libs/service/nats.options'; @Module({ imports: [ @@ -11,10 +10,12 @@ import { commonNatsOptions } from 'libs/service/nats.options'; ClientsModule.register([ { name: 'NATS_CLIENT', - ...commonNatsOptions('AGENT_SERVICE:REQUESTER') + transport: Transport.NATS, + options: { + servers: [`${process.env.NATS_URL}`] + } } ]) - ], controllers: [PlatformController], providers: [PlatformService] From cc4b82c63389180def448910c00c779ac20e7dc7 Mon Sep 17 00:00:00 2001 From: tipusinghaw <126460794+tipusinghaw@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:33:45 +0530 Subject: [PATCH 074/162] feat: ecosystem ms setup (#104) * worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat * merge dev branch to main (#77) * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw * feat:implemented add passke Signed-off-by: tipusinghaw * fix: login error message Signed-off-by: tipusinghaw --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw * Included credebl-master-table json file in the .gitignore Signed-off-by: @nishad.shirsat * Create ecosystem monorepo Signed-off-by: KulkarniShashank * feat: Implemented ecosystem microservice Signed-off-by: tipusinghaw * feat: changed controller name Signed-off-by: tipusinghaw --------- Signed-off-by: tipusinghaw Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank Co-authored-by: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Co-authored-by: @nishad.shirsat Co-authored-by: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Co-authored-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/api-gateway/src/app.module.ts | 4 +- .../ecosystem/dtos/create-organization-dto.ts | 36 ++++++++++++++++ .../src/ecosystem/ecosystem.controller.ts | 43 +++++++++++++++++++ .../src/ecosystem/ecosystem.module.ts | 29 +++++++++++++ .../src/ecosystem/ecosystem.service.ts | 23 ++++++++++ .../src/ecosystem.controller.spec.ts | 22 ++++++++++ apps/ecosystem/src/ecosystem.controller.ts | 24 +++++++++++ apps/ecosystem/src/ecosystem.module.ts | 26 +++++++++++ apps/ecosystem/src/ecosystem.service.ts | 20 +++++++++ apps/ecosystem/src/main.ts | 23 ++++++++++ apps/ecosystem/test/app.e2e-spec.ts | 24 +++++++++++ apps/ecosystem/test/jest-e2e.json | 9 ++++ apps/ecosystem/tsconfig.app.json | 9 ++++ libs/common/src/response-messages/index.ts | 11 ++++- nest-cli.json | 9 ++++ 15 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts create mode 100644 apps/api-gateway/src/ecosystem/ecosystem.controller.ts create mode 100644 apps/api-gateway/src/ecosystem/ecosystem.module.ts create mode 100644 apps/api-gateway/src/ecosystem/ecosystem.service.ts create mode 100644 apps/ecosystem/src/ecosystem.controller.spec.ts create mode 100644 apps/ecosystem/src/ecosystem.controller.ts create mode 100644 apps/ecosystem/src/ecosystem.module.ts create mode 100644 apps/ecosystem/src/ecosystem.service.ts create mode 100644 apps/ecosystem/src/main.ts create mode 100644 apps/ecosystem/test/app.e2e-spec.ts create mode 100644 apps/ecosystem/test/jest-e2e.json create mode 100644 apps/ecosystem/tsconfig.app.json diff --git a/apps/api-gateway/src/app.module.ts b/apps/api-gateway/src/app.module.ts index ee0657282..792999579 100644 --- a/apps/api-gateway/src/app.module.ts +++ b/apps/api-gateway/src/app.module.ts @@ -19,6 +19,7 @@ import { SchemaModule } from './schema/schema.module'; import { commonNatsOptions } from 'libs/service/nats.options'; import { UserModule } from './user/user.module'; import { ConnectionModule } from './connection/connection.module'; +import { EcosystemModule } from './ecosystem/ecosystem.module'; @Module({ imports: [ @@ -40,7 +41,8 @@ import { ConnectionModule } from './connection/connection.module'; OrganizationModule, UserModule, ConnectionModule, - IssuanceModule + IssuanceModule, + EcosystemModule ], controllers: [AppController], providers: [AppService] diff --git a/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts b/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts new file mode 100644 index 000000000..d584c5832 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts @@ -0,0 +1,36 @@ +import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; + +import { Transform } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; + +@ApiExtraModels() +export class CreateEcosystemDto { + + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Organization name is required.' }) + @MinLength(2, { message: 'Organization name must be at least 2 characters.' }) + @MaxLength(50, { message: 'Organization name must be at most 50 characters.' }) + @IsString({ message: 'Organization name must be in string format.' }) + name: string; + + @ApiPropertyOptional() + @Transform(({ value }) => trim(value)) + @MinLength(2, { message: 'Description must be at least 2 characters.' }) + @MaxLength(255, { message: 'Description must be at most 255 characters.' }) + @IsString({ message: 'Description must be in string format.' }) + description: string; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'logo must be in string format.' }) + logo: string; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + website?: 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 new file mode 100644 index 000000000..6cf86ee4f --- /dev/null +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -0,0 +1,43 @@ +import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; +import { Controller, UseGuards, UseFilters } from '@nestjs/common'; +import { EcosystemService } from './ecosystem.service'; +import { Post } from '@nestjs/common'; +import { Body } from '@nestjs/common'; +import { Res } from '@nestjs/common'; +import { CreateEcosystemDto } from './dtos/create-organization-dto'; +import IResponseType from '@credebl/common/interfaces/response.interface'; +import { HttpStatus } from '@nestjs/common'; +import { Response } from 'express'; +import { ApiResponseDto } from '../dtos/apiResponse.dto'; +import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; +import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; +import { AuthGuard } from '@nestjs/passport'; +import { ResponseMessages } from '@credebl/common/response-messages'; +import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; + + +@UseFilters(CustomExceptionFilter) +@Controller('ecosystem') +@ApiTags('ecosystem') +@ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) +export class EcosystemController { + constructor( + private readonly ecosystemService: EcosystemService + ) { } + + + @Post('/') + @ApiOperation({ summary: 'Create a new ecosystem', description: 'Create an ecosystem' }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async createOrganization(@Body() createOrgDto: CreateEcosystemDto, @Res() res: Response): Promise { + await this.ecosystemService.createEcosystem(createOrgDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.create + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } +} \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/ecosystem.module.ts b/apps/api-gateway/src/ecosystem/ecosystem.module.ts new file mode 100644 index 000000000..24a59ec00 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/ecosystem.module.ts @@ -0,0 +1,29 @@ +import { CommonModule, CommonService } from '@credebl/common'; + +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { ConfigModule } from '@nestjs/config'; +import { HttpModule } from '@nestjs/axios'; +import { Module } from '@nestjs/common'; +import { EcosystemController } from './ecosystem.controller'; +import { EcosystemService } from './ecosystem.service'; + +@Module({ + imports: [ + HttpModule, + ConfigModule.forRoot(), + ClientsModule.register([ + { + name: 'NATS_CLIENT', + transport: Transport.NATS, + options: { + servers: [`${process.env.NATS_URL}`] + } + }, + CommonModule + ]) + ], + controllers: [EcosystemController], + providers: [EcosystemService, CommonService] +}) +export class EcosystemModule { } + diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts new file mode 100644 index 000000000..4ce9cba9a --- /dev/null +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -0,0 +1,23 @@ +import { Inject } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; +import { BaseService } from 'libs/service/base.service'; + + +@Injectable() +export class EcosystemService extends BaseService { + constructor(@Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy) { + super('EcosystemService'); + } + + /** + * + * @param createEcosystemDto + * @returns Ecosystem creation success + */ + async createEcosystem(createEcosystemDto): Promise { + const payload = { createEcosystemDto }; + return this.sendNats(this.serviceProxy, 'create-ecosystem', payload); + } + +} diff --git a/apps/ecosystem/src/ecosystem.controller.spec.ts b/apps/ecosystem/src/ecosystem.controller.spec.ts new file mode 100644 index 000000000..653289261 --- /dev/null +++ b/apps/ecosystem/src/ecosystem.controller.spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { EcosystemController } from './ecosystem.controller'; +import { EcosystemService } from './ecosystem.service'; + +describe('EcosystemController', () => { + let ecosystemController: EcosystemController; + + beforeEach(async () => { + const app: TestingModule = await Test.createTestingModule({ + controllers: [EcosystemController], + providers: [EcosystemService] + }).compile(); + + ecosystemController = app.get(EcosystemController); + }); + + describe('root', () => { + it('should return "Hello World!"', () => { + expect(ecosystemController.getHello()).toBe('Hello World!'); + }); + }); +}); diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts new file mode 100644 index 000000000..55a91e22d --- /dev/null +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -0,0 +1,24 @@ +import { Controller, Logger } from '@nestjs/common'; + +import { MessagePattern } from '@nestjs/microservices'; +import { EcosystemService } from './ecosystem.service'; +import { Body } from '@nestjs/common'; + +@Controller() +export class EcosystemController { + constructor(private readonly ecosystemService: EcosystemService) {} + private readonly logger = new Logger('EcosystemController'); + + /** + * Description: create new ecosystem + * @param payload Registration Details + * @returns Get created ecosystem details + */ + + @MessagePattern({ cmd: 'create-ecosystem' }) + async createEcosystem(@Body() payload: { createOrgDto; userId }): Promise { + this.logger.log(`EcosystemPayload : ${payload}`); + return this.ecosystemService.createEcosystem(); + } + +} diff --git a/apps/ecosystem/src/ecosystem.module.ts b/apps/ecosystem/src/ecosystem.module.ts new file mode 100644 index 000000000..c749922c7 --- /dev/null +++ b/apps/ecosystem/src/ecosystem.module.ts @@ -0,0 +1,26 @@ +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 { ConnectionRepository } from './connection.repository'; +import { PrismaService } from '@credebl/prisma-service'; + +@Module({ + imports: [ + ClientsModule.register([ + { + name: 'NATS_CLIENT', + transport: Transport.NATS, + options: { + servers: [`${process.env.NATS_URL}`] + } + } + ]), + + CommonModule + ], + controllers: [EcosystemController], + providers: [EcosystemService, PrismaService, Logger] +}) +export class EcosystemModule { } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts new file mode 100644 index 000000000..f68e93329 --- /dev/null +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -0,0 +1,20 @@ +// eslint-disable-next-line camelcase +import { Injectable} from '@nestjs/common'; + +@Injectable() +export class EcosystemService { + constructor( + ) { } + + /** + * + * @param registerOrgDto + * @returns + */ + + // eslint-disable-next-line camelcase + async createEcosystem():Promise { + return "test"; + } + +} diff --git a/apps/ecosystem/src/main.ts b/apps/ecosystem/src/main.ts new file mode 100644 index 000000000..6bd3f0259 --- /dev/null +++ b/apps/ecosystem/src/main.ts @@ -0,0 +1,23 @@ +import { NestFactory } from '@nestjs/core'; +import { EcosystemModule } from './ecosystem.module'; +import { HttpExceptionFilter } from 'libs/http-exception.filter'; +import { Logger } from '@nestjs/common'; +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; + +const logger = new Logger(); + +async function bootstrap(): Promise { + + const app = await NestFactory.createMicroservice(EcosystemModule, { + transport: Transport.NATS, + options: { + servers: [`${process.env.NATS_URL}`] + } + }); + + app.useGlobalFilters(new HttpExceptionFilter()); + + await app.listen(); + logger.log('Ecosystem microservice is listening to NATS '); +} +bootstrap(); diff --git a/apps/ecosystem/test/app.e2e-spec.ts b/apps/ecosystem/test/app.e2e-spec.ts new file mode 100644 index 000000000..1f1f79169 --- /dev/null +++ b/apps/ecosystem/test/app.e2e-spec.ts @@ -0,0 +1,24 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { EcosystemModule } from './../src/ecosystem.module'; + +describe('EcosystemController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [EcosystemModule] + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/ (GET)', () => { + return request(app.getHttpServer()) + .get('/') + .expect(200) + .expect('Hello World!'); + }); +}); diff --git a/apps/ecosystem/test/jest-e2e.json b/apps/ecosystem/test/jest-e2e.json new file mode 100644 index 000000000..e9d912f3e --- /dev/null +++ b/apps/ecosystem/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/apps/ecosystem/tsconfig.app.json b/apps/ecosystem/tsconfig.app.json new file mode 100644 index 000000000..ac6e9e1bb --- /dev/null +++ b/apps/ecosystem/tsconfig.app.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": false, + "outDir": "../../dist/apps/ecosystem" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 4f9dd4b5e..3d16195bc 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -173,5 +173,14 @@ export const ResponseMessages = { platformConfigNotFound: 'Platform config not found', emailSend: 'Unable to send email to the user' } - } + }, + ecosystem: { + success: { + create: 'Ecosystem created successfully', + }, + error: { + + } + + }, }; \ No newline at end of file diff --git a/nest-cli.json b/nest-cli.json index 68dc6c90e..c4cae4482 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -232,6 +232,15 @@ "compilerOptions": { "tsConfigPath": "libs/supabase/tsconfig.lib.json" } + }, + "ecosystem": { + "type": "application", + "root": "apps/ecosystem", + "entryFile": "main", + "sourceRoot": "apps/ecosystem/src", + "compilerOptions": { + "tsConfigPath": "apps/ecosystem/tsconfig.app.json" + } } } } \ No newline at end of file From 0b5282b680442e6c5983d31b74f9954098cb38dc Mon Sep 17 00:00:00 2001 From: tipusinghaw <126460794+tipusinghaw@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:58:46 +0530 Subject: [PATCH 075/162] feat: ecosystem ms setup (#105) * worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat * merge dev branch to main (#77) * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw * feat:implemented add passke Signed-off-by: tipusinghaw * fix: login error message Signed-off-by: tipusinghaw --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw * Included credebl-master-table json file in the .gitignore Signed-off-by: @nishad.shirsat * Create ecosystem monorepo Signed-off-by: KulkarniShashank * feat: Implemented ecosystem microservice Signed-off-by: tipusinghaw * feat: changed controller name Signed-off-by: tipusinghaw * fix:changed return message Signed-off-by: tipusinghaw * setup ecosystem prisma schema Signed-off-by: Nishad --------- Signed-off-by: tipusinghaw Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank Signed-off-by: Nishad Co-authored-by: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Co-authored-by: @nishad.shirsat Co-authored-by: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Co-authored-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/ecosystem/src/ecosystem.service.ts | 2 +- .../migration.sql | 90 +++++++++++++++++++ libs/prisma-service/prisma/schema.prisma | 71 +++++++++++++++ 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 libs/prisma-service/prisma/migrations/20231002114401_ecosystem_setup/migration.sql diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index f68e93329..14795ec82 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -14,7 +14,7 @@ export class EcosystemService { // eslint-disable-next-line camelcase async createEcosystem():Promise { - return "test"; + return "test ecosystem"; } } diff --git a/libs/prisma-service/prisma/migrations/20231002114401_ecosystem_setup/migration.sql b/libs/prisma-service/prisma/migrations/20231002114401_ecosystem_setup/migration.sql new file mode 100644 index 000000000..0f1eed81d --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231002114401_ecosystem_setup/migration.sql @@ -0,0 +1,90 @@ +-- CreateTable +CREATE TABLE "ecosystem_roles" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_roles_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ecosystem" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT NOT NULL, + "tags" TEXT NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ecosystem_invitations" ( + "id" TEXT NOT NULL, + "email" TEXT NOT NULL, + "status" TEXT NOT NULL, + "ecosystemId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_invitations_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ecosystem_users" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "ecosystemId" TEXT NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_users_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ecosystem_orgs" ( + "id" TEXT NOT NULL, + "orgId" TEXT NOT NULL, + "status" TEXT NOT NULL, + "ecosystemId" TEXT NOT NULL, + "ecosystemRoleId" INTEGER NOT NULL, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_orgs_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "ecosystem_roles_name_key" ON "ecosystem_roles"("name"); + +-- AddForeignKey +ALTER TABLE "ecosystem_invitations" ADD CONSTRAINT "ecosystem_invitations_ecosystemId_fkey" FOREIGN KEY ("ecosystemId") REFERENCES "ecosystem"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ecosystem_users" ADD CONSTRAINT "ecosystem_users_ecosystemId_fkey" FOREIGN KEY ("ecosystemId") REFERENCES "ecosystem"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ecosystem_orgs" ADD CONSTRAINT "ecosystem_orgs_ecosystemId_fkey" FOREIGN KEY ("ecosystemId") REFERENCES "ecosystem"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ecosystem_orgs" ADD CONSTRAINT "ecosystem_orgs_ecosystemRoleId_fkey" FOREIGN KEY ("ecosystemRoleId") REFERENCES "ecosystem_roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 82c551246..9a6cf2497 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -307,3 +307,74 @@ model presentations { orgId Int organisation organisation @relation(fields: [orgId], references: [id]) } + +model ecosystem_roles { + id Int @id @default(autoincrement()) + name String @unique + description String + ecosystemOrgs ecosystem_orgs[] + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) +} + +model ecosystem { + id String @id @default(uuid()) + name String + description String + tags String + ecosystemOrgs ecosystem_orgs[] + ecosystemUsers ecosystem_users[] + ecosystemInvitations ecosystem_invitations[] + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) +} + +model ecosystem_invitations { + id String @id @default(uuid()) + email String + status String + ecosystemId String + userId String + orgId String + ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) +} + +model ecosystem_users { + id String @id @default(uuid()) // auto-increment + userId String + ecosystemId String + ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) +} + +model ecosystem_orgs { + id String @id @default(uuid()) // auto-increment + orgId String + status String + ecosystemId String + ecosystemRoleId Int + ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) + ecosystemRole ecosystem_roles @relation(fields: [ecosystemRoleId], references: [id]) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) + +} + From 87cbe330b50b0c9116e021ec9b7a944cbabd37c6 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Tue, 3 Oct 2023 18:18:54 +0530 Subject: [PATCH 076/162] Integrated ecosystem enable disable in config, master table entry for ecosystem roles (#113) Signed-off-by: Nishad Signed-off-by: tipusinghaw --- .../prisma/data/credebl-master-table.json | 15 ++++++++++++ .../migration.sql | 24 +++++++++++++++++++ .../migration.sql | 2 ++ libs/prisma-service/prisma/schema.prisma | 8 ++++--- libs/prisma-service/prisma/seed.ts | 14 +++++++++++ 5 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20231003080927_ecosystem_flag/migration.sql create mode 100644 libs/prisma-service/prisma/migrations/20231003114625_ecosystem_logo/migration.sql diff --git a/libs/prisma-service/prisma/data/credebl-master-table.json b/libs/prisma-service/prisma/data/credebl-master-table.json index 35a44c6c3..c1627506f 100644 --- a/libs/prisma-service/prisma/data/credebl-master-table.json +++ b/libs/prisma-service/prisma/data/credebl-master-table.json @@ -55,6 +55,21 @@ "description": "Joins the organization as member" } ], + "ecosystemRoleData": [ + { + "name": "Ecosystem Owner", + "description": "Ecosystem Owner" + }, + { + "name": "Ecosystem Lead", + "description": "Ecosystem Lead" + }, + { + "name": "Ecosystem Member", + "description": "Ecosystem Member" + } + ], + "agentTypeData": [ { "agent": "AFJ" diff --git a/libs/prisma-service/prisma/migrations/20231003080927_ecosystem_flag/migration.sql b/libs/prisma-service/prisma/migrations/20231003080927_ecosystem_flag/migration.sql new file mode 100644 index 000000000..418d645c7 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231003080927_ecosystem_flag/migration.sql @@ -0,0 +1,24 @@ +/* + Warnings: + + - The primary key for the `ecosystem_roles` table will be changed. If it partially fails, the table could be left without primary key constraint. + +*/ +-- DropForeignKey +ALTER TABLE "ecosystem_orgs" DROP CONSTRAINT "ecosystem_orgs_ecosystemRoleId_fkey"; + +-- AlterTable +ALTER TABLE "ecosystem_orgs" ALTER COLUMN "ecosystemRoleId" SET DATA TYPE TEXT; + +-- AlterTable +ALTER TABLE "ecosystem_roles" DROP CONSTRAINT "ecosystem_roles_pkey", +ALTER COLUMN "id" DROP DEFAULT, +ALTER COLUMN "id" SET DATA TYPE TEXT, +ADD CONSTRAINT "ecosystem_roles_pkey" PRIMARY KEY ("id"); +DROP SEQUENCE "ecosystem_roles_id_seq"; + +-- AlterTable +ALTER TABLE "platform_config" ADD COLUMN "enableEcosystem" BOOLEAN NOT NULL DEFAULT false; + +-- AddForeignKey +ALTER TABLE "ecosystem_orgs" ADD CONSTRAINT "ecosystem_orgs_ecosystemRoleId_fkey" FOREIGN KEY ("ecosystemRoleId") REFERENCES "ecosystem_roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/libs/prisma-service/prisma/migrations/20231003114625_ecosystem_logo/migration.sql b/libs/prisma-service/prisma/migrations/20231003114625_ecosystem_logo/migration.sql new file mode 100644 index 000000000..d4031a104 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231003114625_ecosystem_logo/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "ecosystem" ADD COLUMN "logoUrl" TEXT; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 9a6cf2497..4ba8e17e7 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -132,6 +132,7 @@ model platform_config { emailFrom String @db.VarChar apiEndpoint String @db.VarChar tailsFileServer String @db.VarChar + enableEcosystem Boolean @default(false) createDateTime DateTime @default(now()) @db.Timestamptz(6) createdBy Int @default(1) lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) @@ -309,7 +310,7 @@ model presentations { } model ecosystem_roles { - id Int @id @default(autoincrement()) + id String @id @default(uuid()) name String @unique description String ecosystemOrgs ecosystem_orgs[] @@ -324,6 +325,7 @@ model ecosystem { id String @id @default(uuid()) name String description String + logoUrl String? tags String ecosystemOrgs ecosystem_orgs[] ecosystemUsers ecosystem_users[] @@ -363,11 +365,11 @@ model ecosystem_users { } model ecosystem_orgs { - id String @id @default(uuid()) // auto-increment + id String @id @default(uuid()) orgId String status String ecosystemId String - ecosystemRoleId Int + ecosystemRoleId String ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) ecosystemRole ecosystem_roles @relation(fields: [ecosystemRoleId], references: [id]) createDateTime DateTime @default(now()) @db.Timestamptz(6) diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index 8b6b6fb51..cd50a00c6 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -113,6 +113,19 @@ const createLedger = async (): Promise => { } }; +const createEcosystemRoles = async (): Promise => { + try { + const { ecosystemRoleData } = JSON.parse(configData); + const ecosystemRoles = await prisma.ecosystem_roles.createMany({ + data: ecosystemRoleData + }); + + logger.log(ecosystemRoles); + } catch (e) { + logger.error('An error occurred seeding ecosystemRoles:', e); + } +}; + async function main(): Promise { await createPlatformConfig(); @@ -123,6 +136,7 @@ async function main(): Promise { await createPlatformUserOrgRoles(); await createOrgAgentTypes(); await createLedger(); + await createEcosystemRoles(); } From 66eaac4e0f4873ae504d273c76c4febbe493be4e Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Wed, 30 Aug 2023 12:08:34 +0530 Subject: [PATCH 077/162] feat: Get Proof Presentation from Data with Credentials and Attributes (#66) * fix: Added the global exception error handling for api-gateway Signed-off-by: KulkarniShashank * Added the server for call the API locally Signed-off-by: KulkarniShashank * Removed qr-code in main.js Signed-off-by: KulkarniShashank * feat: Get proof presentation form data Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/agent-service/src/agent-service.service.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 893124ff0..bd013bcc5 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -887,5 +887,17 @@ export class AgentServiceService { throw new RpcException(error.response ? error.response : error); } } + + async getProofFormData(url: string, apiKey: string): Promise { + try { + const getProofFormData = await this.commonService + .httpGet(url, { headers: { 'x-api-key': apiKey } }) + .then(async response => response); + return getProofFormData; + } catch (error) { + this.logger.error(`Error in get proof form data in agent service : ${JSON.stringify(error)}`); + throw new RpcException(error); + } + } } From 169449b83941c6c3ea5789831c406f736b2f8991 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Thu, 5 Oct 2023 12:28:28 +0530 Subject: [PATCH 078/162] refactor: Implemented and configured the Indicio testnet. (#106) * feat: support afj-0.4.1 Signed-off-by: KulkarniShashank * fix: Add nats configuration in platform Signed-off-by: KulkarniShashank * feat: Added the indicio testnet Signed-off-by: KulkarniShashank * Added get all ledgers API in platform module Signed-off-by: KulkarniShashank * Refactore and changed API schema and credential-def Signed-off-by: KulkarniShashank * Added ledgerId as a optional in the wallet spinup Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank --- .../AFJ/scripts/start_agent.sh | 2 + .../AFJ/scripts/start_agent_ecs.sh | 8 +- .../src/agent-provisioning.service.ts | 4 +- .../agent-provisioning.interfaces.ts | 2 +- .../src/agent-service.service.ts | 151 +++++++++--------- .../src/interface/agent-service.interface.ts | 8 +- apps/agent-service/src/main.ts | 3 +- .../repositories/agent-service.repository.ts | 20 +-- .../agent-service/dto/agent-service.dto.ts | 28 ++-- .../agent-service/dto/create-tenant.dto.ts | 10 +- .../src/platform/platform.controller.ts | 29 +++- .../src/platform/platform.service.ts | 7 + apps/ledger/src/ledger.controller.ts | 8 +- apps/ledger/src/ledger.module.ts | 12 +- apps/ledger/src/ledger.service.ts | 30 +++- .../src/repositories/ledger.repository.ts | 22 +++ libs/common/src/common.constant.ts | 4 + libs/common/src/response-messages/index.ts | 8 + .../prisma/data/credebl-master-table.json | 13 +- .../migration.sql | 2 + libs/prisma-service/prisma/schema.prisma | 1 + 21 files changed, 252 insertions(+), 120 deletions(-) create mode 100644 apps/ledger/src/repositories/ledger.repository.ts create mode 100644 libs/prisma-service/prisma/migrations/20230928140558_ledgers_indy_namespace/migration.sql diff --git a/apps/agent-provisioning/AFJ/scripts/start_agent.sh b/apps/agent-provisioning/AFJ/scripts/start_agent.sh index 899229c80..cbe95d936 100755 --- a/apps/agent-provisioning/AFJ/scripts/start_agent.sh +++ b/apps/agent-provisioning/AFJ/scripts/start_agent.sh @@ -14,6 +14,7 @@ CONTAINER_NAME=${11} PROTOCOL=${12} TENANT=${13} AFJ_VERSION=${14} +INDY_LEDGER=${15} ADMIN_PORT=$((8000 + $AGENCY)) INBOUND_PORT=$((9000 + $AGENCY)) CONTROLLER_PORT=$((3000 + $AGENCY)) @@ -55,6 +56,7 @@ cat <>${PWD}/apps/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINE "walletAdminAccount": "$WALLET_STORAGE_USER", "walletAdminPassword": "$WALLET_STORAGE_PASSWORD", "walletScheme": "DatabasePerWallet", + "indyLedger": $INDY_LEDGER, "endpoint": [ "$AGENT_ENDPOINT" ], diff --git a/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh b/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh index db3de117f..1233a5de2 100644 --- a/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh +++ b/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh @@ -16,9 +16,10 @@ CONTAINER_NAME=${11} PROTOCOL=${12} TENANT=${13} AFJ_VERSION=${14} -AGENT_HOST=${15} -AWS_ACCOUNT_ID=${16} -S3_BUCKET_ARN=${17} +INDY_LEDGER=${15} +AGENT_HOST=${16} +AWS_ACCOUNT_ID=${17} +S3_BUCKET_ARN=${18} ADMIN_PORT=$((8000 + AGENCY)) INBOUND_PORT=$((9000 + AGENCY)) CONTROLLER_PORT=$((3000 + AGENCY)) @@ -45,6 +46,7 @@ cat <>/app/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINER_NAME} "walletAdminAccount": "$WALLET_STORAGE_USER", "walletAdminPassword": "$WALLET_STORAGE_PASSWORD", "walletScheme": "DatabasePerWallet", + "indyLedger": $INDY_LEDGER, "endpoint": [ "$AGENT_ENDPOINT" ], diff --git a/apps/agent-provisioning/src/agent-provisioning.service.ts b/apps/agent-provisioning/src/agent-provisioning.service.ts index 596f3ae93..3b85d3e2e 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.ts @@ -22,12 +22,12 @@ export class AgentProvisioningService { async walletProvision(payload: IWalletProvision): Promise { try { - const { containerName, externalIp, orgId, seed, walletName, walletPassword, walletStorageHost, walletStoragePassword, walletStoragePort, walletStorageUser, webhookEndpoint, agentType, protocol, afjVersion, tenant } = payload; + 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} ${process.env.AGENT_HOST} ${process.env.AWS_ACCOUNT_ID} ${process.env.S3_BUCKET_ARN}`; + } ${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}`; const spinUpResponse: object = new Promise(async (resolve) => { diff --git a/apps/agent-provisioning/src/interface/agent-provisioning.interfaces.ts b/apps/agent-provisioning/src/interface/agent-provisioning.interfaces.ts index d74d14090..0d5042eda 100644 --- a/apps/agent-provisioning/src/interface/agent-provisioning.interfaces.ts +++ b/apps/agent-provisioning/src/interface/agent-provisioning.interfaces.ts @@ -15,7 +15,7 @@ export interface IWalletProvision { containerName: string; agentType: AgentType; orgName: string; - genesisUrl: string; + indyLedger: string; protocol: string; afjVersion: string; tenant: boolean; diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 893124ff0..46eb595e3 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -133,9 +133,11 @@ export class AgentServiceService { agentSpinupDto.agentType = agentSpinupDto.agentType ? agentSpinupDto.agentType : 1; agentSpinupDto.tenant = agentSpinupDto.tenant ? agentSpinupDto.tenant : false; + agentSpinupDto.ledgerId = !agentSpinupDto.ledgerId || 0 === agentSpinupDto.ledgerId.length ? [1] : agentSpinupDto.ledgerId; + const platformConfig: platform_config = await this.agentServiceRepository.getPlatformConfigDetails(); - const ledgerDetails: ledgers = await this.agentServiceRepository.getGenesisUrl(agentSpinupDto.ledgerId); + const ledgerDetails: ledgers[] = await this.agentServiceRepository.getGenesisUrl(agentSpinupDto.ledgerId); const orgData: organisation = await this.agentServiceRepository.getOrgDetails(agentSpinupDto.orgId); if (!orgData) { @@ -177,6 +179,18 @@ export class AgentServiceService { controllerIp ); + const ledgerArray = []; + for (const iterator of ledgerDetails) { + const ledgerJson = {}; + + ledgerJson["genesisTransactions"] = iterator.poolConfig; + ledgerJson["indyNamespace"] = iterator.indyNamespace; + + ledgerArray.push(ledgerJson); + } + + const ledgerString = JSON.stringify(ledgerArray); + const escapedJsonString = ledgerString.replace(/"/g, '\\"'); if (agentSpinupDto.agentType === AgentType.ACAPY) { // TODO: ACA-PY Agent Spin-Up @@ -197,7 +211,7 @@ export class AgentServiceService { containerName, agentType: AgentType.AFJ, orgName: orgData.name, - genesisUrl: ledgerDetails?.poolConfig, + indyLedger: escapedJsonString, afjVersion: process.env.AFJ_VERSION, protocol: process.env.API_GATEWAY_PROTOCOL, tenant: agentSpinupDto.tenant @@ -470,14 +484,10 @@ export class AgentServiceService { async _createTenant(payload: ITenantDto, user: IUserRequestInterface): Promise { try { + payload.ledgerId = !payload.ledgerId || 0 === payload.ledgerId.length ? [1] : payload.ledgerId; + + const ledgerDetails: ledgers[] = await this.agentServiceRepository.getGenesisUrl(payload.ledgerId); const sharedAgentSpinUpResponse = new Promise(async (resolve, _reject) => { - const { label, seed } = payload; - const createTenantOptions = { - config: { - label - }, - seed - }; const socket = await io(`${process.env.SOCKET_HOST}`, { reconnection: true, @@ -504,45 +514,57 @@ export class AgentServiceService { throw new NotFoundException('Platform-admin agent is not spun-up'); } - const apiKey = ''; + let tenantDetails; const url = `${platformAdminSpinnedUp.org_agents[0].agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_TENANT}`; - const tenantDetails = await this.commonService - .httpPost(url, createTenantOptions, { headers: { 'x-api-key': apiKey } }) - .then(async (tenant) => { - this.logger.debug(`API Response Data: ${JSON.stringify(tenant)}`); - return tenant; - }); + for (const iterator of ledgerDetails) { + const { label, seed } = payload; + const createTenantOptions = { + config: { + label + }, + seed, + method: iterator.indyNamespace + }; + const apiKey = ''; + tenantDetails = await this.commonService + .httpPost(url, createTenantOptions, { headers: { 'x-api-key': apiKey } }) + .then(async (tenant) => { + this.logger.debug(`API Response Data: ${JSON.stringify(tenant)}`); + return tenant; + }); + + const storeOrgAgentData: IStoreOrgAgentDetails = { + did: tenantDetails.did, + verkey: tenantDetails.verkey, + isDidPublic: true, + agentSpinUpStatus: 2, + agentsTypeId: AgentType.AFJ, + orgId: payload.orgId, + agentEndPoint: platformAdminSpinnedUp.org_agents[0].agentEndPoint, + orgAgentTypeId: OrgAgentType.SHARED, + tenantId: tenantDetails.tenantRecord.id, + walletName: label + }; + + if (payload.clientSocketId) { + socket.emit('agent-spinup-process-completed', { clientId: payload.clientSocketId }); + } - const storeOrgAgentData: IStoreOrgAgentDetails = { - did: tenantDetails.did, - verkey: tenantDetails.verkey, - isDidPublic: true, - agentSpinUpStatus: 2, - agentsTypeId: AgentType.AFJ, - orgId: payload.orgId, - agentEndPoint: platformAdminSpinnedUp.org_agents[0].agentEndPoint, - orgAgentTypeId: OrgAgentType.SHARED, - tenantId: tenantDetails.tenantRecord.id, - walletName: label - }; + const saveTenant = await this.agentServiceRepository.storeOrgAgentDetails(storeOrgAgentData); - if (payload.clientSocketId) { - socket.emit('agent-spinup-process-completed', { clientId: payload.clientSocketId }); - } - - const saveTenant = await this.agentServiceRepository.storeOrgAgentDetails(storeOrgAgentData); + if (payload.clientSocketId) { + socket.emit('invitation-url-creation-started', { clientId: payload.clientSocketId }); + } - if (payload.clientSocketId) { - socket.emit('invitation-url-creation-started', { clientId: payload.clientSocketId }); - } + await this._createLegacyConnectionInvitation(payload.orgId, user, storeOrgAgentData.walletName); - await this._createLegacyConnectionInvitation(payload.orgId, user, storeOrgAgentData.walletName); + if (payload.clientSocketId) { + socket.emit('invitation-url-creation-success', { clientId: payload.clientSocketId }); + } - if (payload.clientSocketId) { - socket.emit('invitation-url-creation-success', { clientId: payload.clientSocketId }); + resolve(saveTenant); } - resolve(saveTenant); } else { throw new InternalServerErrorException('Agent not able to spin-up'); } @@ -599,16 +621,12 @@ export class AgentServiceService { } else if (2 === payload.agentType) { - const url = `${payload.agentEndPoint}${CommonConstants.URL_SHAGENT_WITH_TENANT_AGENT}`; + const url = `${payload.agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_SCHEMA}`.replace('#', `${payload.tenantId}`); const schemaPayload = { - tenantId: payload.tenantId, - method: 'registerSchema', - payload: { - attributes: payload.payload.attributes, - version: payload.payload.version, - name: payload.payload.name, - issuerId: payload.payload.issuerId - } + attributes: payload.payload.attributes, + version: payload.payload.version, + name: payload.payload.name, + issuerId: payload.payload.issuerId }; schemaResponse = await this.commonService.httpPost(url, schemaPayload, { headers: { 'x-api-key': payload.apiKey } }) .then(async (schema) => { @@ -636,15 +654,9 @@ export class AgentServiceService { }); } else if (2 === payload.agentType) { - const url = `${payload.agentEndPoint}${CommonConstants.URL_SHAGENT_WITH_TENANT_AGENT}`; - const schemaPayload = { - tenantId: payload.tenantId, - method: payload.method, - payload: { - 'schemaId': `${payload.payload.schemaId}` - } - }; - schemaResponse = await this.commonService.httpPost(url, schemaPayload, { headers: { 'x-api-key': payload.apiKey } }) + 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 } }) .then(async (schema) => { this.logger.debug(`API Response Data: ${JSON.stringify(schema)}`); return schema; @@ -674,15 +686,11 @@ export class AgentServiceService { }); } else if (2 === payload.agentType) { - const url = `${payload.agentEndPoint}${CommonConstants.URL_SHAGENT_WITH_TENANT_AGENT}`; + const url = `${payload.agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_CRED_DEF}`.replace('#', `${payload.tenantId}`); const credDefPayload = { - tenantId: payload.tenantId, - method: 'registerCredentialDefinition', - payload: { - tag: payload.payload.tag, - schemaId: payload.payload.schemaId, - issuerId: payload.payload.issuerId - } + tag: payload.payload.tag, + schemaId: payload.payload.schemaId, + issuerId: payload.payload.issuerId }; credDefResponse = await this.commonService.httpPost(url, credDefPayload, { headers: { 'x-api-key': payload.apiKey } }) .then(async (credDef) => { @@ -710,15 +718,8 @@ export class AgentServiceService { }); } else if (2 === payload.agentType) { - const url = `${payload.agentEndPoint}${CommonConstants.URL_SHAGENT_WITH_TENANT_AGENT}`; - const credDefPayload = { - tenantId: payload.tenantId, - method: payload.method, - payload: { - 'credentialDefinitionId': `${payload.payload.credentialDefinitionId}` - } - }; - credDefResponse = await this.commonService.httpPost(url, credDefPayload, { headers: { 'x-api-key': payload.apiKey } }) + 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 } }) .then(async (credDef) => { this.logger.debug(`API Response Data: ${JSON.stringify(credDef)}`); return credDef; diff --git a/apps/agent-service/src/interface/agent-service.interface.ts b/apps/agent-service/src/interface/agent-service.interface.ts index 81ed67264..d6a7d4b99 100644 --- a/apps/agent-service/src/interface/agent-service.interface.ts +++ b/apps/agent-service/src/interface/agent-service.interface.ts @@ -7,8 +7,8 @@ export interface IAgentSpinupDto { walletPassword: string; seed: string; orgId: number; + ledgerId?: number[]; agentType?: AgentType; - ledgerId?: number; transactionApproval?: boolean; clientSocketId?: string tenant?: boolean; @@ -17,8 +17,10 @@ export interface IAgentSpinupDto { export interface ITenantDto { label: string; seed: string; - tenantId?: string; + ledgerId?: number[]; + method: string; orgId: number; + tenantId?: string; clientSocketId?: string; } @@ -103,7 +105,7 @@ export interface IWalletProvision { containerName: string; agentType: AgentType; orgName: string; - genesisUrl: string; + indyLedger: string; afjVersion: string; protocol: string; tenant: boolean; diff --git a/apps/agent-service/src/main.ts b/apps/agent-service/src/main.ts index d936711a9..53d5fbba0 100644 --- a/apps/agent-service/src/main.ts +++ b/apps/agent-service/src/main.ts @@ -27,7 +27,8 @@ async function bootstrap(): Promise { walletPassword: process.env.PLATFORM_WALLET_PASSWORD, seed: process.env.PLATFORM_SEED, orgId: parseInt(process.env.PLATFORM_ID), - tenant: true + tenant: true, + ledgerId: [1, 2] }; const agentService = app.get(AgentServiceService); diff --git a/apps/agent-service/src/repositories/agent-service.repository.ts b/apps/agent-service/src/repositories/agent-service.repository.ts index eda85e851..15d333e92 100644 --- a/apps/agent-service/src/repositories/agent-service.repository.ts +++ b/apps/agent-service/src/repositories/agent-service.repository.ts @@ -29,12 +29,14 @@ export class AgentServiceRepository { * @param id * @returns */ - async getGenesisUrl(id: number): Promise { + async getGenesisUrl(ledgerId: number[]): Promise { try { - const genesisData = await this.prisma.ledgers.findFirst({ + const genesisData = await this.prisma.ledgers.findMany({ where: { - id + id: { + in: ledgerId + } } }); return genesisData; @@ -135,13 +137,13 @@ export class AgentServiceRepository { } } - /** - * Get agent details - * @param orgId - * @returns Agent health details - */ + /** + * Get agent details + * @param orgId + * @returns Agent health details + */ // eslint-disable-next-line camelcase - async getOrgAgentDetails(orgId: number): Promise { + async getOrgAgentDetails(orgId: number): Promise { try { const oranizationAgentDetails = await this.prisma.org_agents.findFirst({ where: { 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 d8c449095..a4227b193 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 @@ -1,7 +1,7 @@ import { trim } from '@credebl/common/cast.helper'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, Matches, MaxLength, MinLength } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsOptional, IsString, Matches, MaxLength, MinLength, IsArray } from 'class-validator'; const regex = /^[a-zA-Z0-9 ]*$/; export class AgentSpinupDto { @@ -9,7 +9,7 @@ export class AgentSpinupDto { @ApiProperty() @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'walletName is required'}) + @IsNotEmpty({ message: 'walletName is required' }) @MinLength(2, { message: 'walletName must be at least 2 characters.' }) @MaxLength(50, { message: 'walletName must be at most 50 characters.' }) @IsString({ message: 'walletName must be in string format.' }) @@ -24,39 +24,39 @@ export class AgentSpinupDto { @IsNotEmpty({ message: 'Password is required.' }) walletPassword: string; - + @ApiProperty() @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'seed is required'}) + @IsNotEmpty({ message: 'seed is required' }) @MaxLength(32, { message: 'seed must be at most 32 characters.' }) @IsString({ message: 'seed must be in string format.' }) @Matches(/^\S*$/, { message: 'Spaces are not allowed in seed' }) - seed: string; + seed: string; - @ApiProperty() - @ApiPropertyOptional() + @ApiProperty({ example: [1] }) @IsOptional() - @IsNumber() - ledgerId?: number; + @IsArray({ message: 'ledgerId must be an array' }) + @IsNotEmpty({ message: 'please provide valid ledgerId' }) + ledgerId?: number[]; @ApiProperty() - @IsOptional() + @IsOptional() @ApiPropertyOptional() clientSocketId?: string; @ApiProperty() @IsOptional() - @IsBoolean() + @IsBoolean() @ApiPropertyOptional() tenant?: boolean; - + @ApiProperty() @IsOptional() @ApiPropertyOptional() @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'agentType is required'}) + @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.' }) @@ -66,7 +66,7 @@ export class AgentSpinupDto { @IsOptional() @ApiPropertyOptional() @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'transactionApproval is required'}) + @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.' }) 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 73161436e..f0757532a 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 @@ -1,7 +1,7 @@ import { trim } from '@credebl/common/cast.helper'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { MaxLength, IsString, MinLength, Matches, IsNotEmpty, IsOptional } from 'class-validator'; +import { MaxLength, IsString, MinLength, Matches, IsNotEmpty, IsOptional, IsArray } from 'class-validator'; const labelRegex = /^[a-zA-Z0-9 ]*$/; export class CreateTenantDto { @ApiProperty() @@ -18,13 +18,19 @@ export class CreateTenantDto { @ApiProperty() @MaxLength(32, { message: 'seed must be at most 32 characters.' }) @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'seed is required'}) + @IsNotEmpty({ message: 'seed is required' }) @IsString({ message: 'seed must be in string format.' }) @Matches(/^\S*$/, { message: 'Spaces are not allowed in seed' }) seed: string; + @ApiProperty({ example: [1] }) + @IsOptional() + @IsArray({ message: 'ledgerId must be an array' }) + @IsNotEmpty({ message: 'please provide valid ledgerId' }) + ledgerId?: number[]; + orgId: number; @ApiProperty() diff --git a/apps/api-gateway/src/platform/platform.controller.ts b/apps/api-gateway/src/platform/platform.controller.ts index 26c43bbfd..2905c9e49 100644 --- a/apps/api-gateway/src/platform/platform.controller.ts +++ b/apps/api-gateway/src/platform/platform.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, HttpStatus, Logger, Query, Res, UseFilters } from '@nestjs/common'; +import { Controller, Get, HttpStatus, Logger, Query, Res, UseFilters, UseGuards } from '@nestjs/common'; import { PlatformService } from './platform.service'; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; @@ -10,21 +10,23 @@ import { ISchemaSearchInterface } 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'; +import { AuthGuard } from '@nestjs/passport'; @ApiBearerAuth() -@Controller() +@Controller('platform') @UseFilters(CustomExceptionFilter) export class PlatformController { constructor(private readonly platformService: PlatformService) { } private readonly logger = new Logger('PlatformController'); - @Get('/platform/schemas') + @Get('/schemas') @ApiTags('schemas') @ApiOperation({ summary: 'Get all schemas from platform.', description: 'Get all schemas from platform.' }) + @UseGuards(AuthGuard('jwt')) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) async getAllSchema( @Query() getAllSchemaDto: GetAllSchemaByPlatformDto, @@ -48,5 +50,26 @@ export class PlatformController { }; return res.status(HttpStatus.OK).json(finalResponse); } + + @Get('/ledgers') + @ApiTags('ledgers') + @ApiOperation({ + summary: 'Get all ledgers from platform.', + description: 'Get all ledgers from platform.' + }) + @UseGuards(AuthGuard('jwt')) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + async getAllLedgers( + @Res() res: Response + ): Promise { + const networksResponse = await this.platformService.getAllLedgers(); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ledger.success.fetch, + data: networksResponse.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } } diff --git a/apps/api-gateway/src/platform/platform.service.ts b/apps/api-gateway/src/platform/platform.service.ts index 6443fc11a..e15742fd6 100644 --- a/apps/api-gateway/src/platform/platform.service.ts +++ b/apps/api-gateway/src/platform/platform.service.ts @@ -19,4 +19,11 @@ export class PlatformService extends BaseService { return this.sendNats(this.platformServiceProxy, 'get-all-schemas', schemaSearch); } + + async getAllLedgers(): Promise<{ + response: object; + }> { + const payload = {}; + return this.sendNats(this.platformServiceProxy, 'get-all-ledgers', payload); + } } diff --git a/apps/ledger/src/ledger.controller.ts b/apps/ledger/src/ledger.controller.ts index 10ec63cd2..03b57fc2d 100644 --- a/apps/ledger/src/ledger.controller.ts +++ b/apps/ledger/src/ledger.controller.ts @@ -1,8 +1,14 @@ import { Controller } from '@nestjs/common'; import { LedgerService } from './ledger.service'; +import { MessagePattern } from '@nestjs/microservices'; +import { ledgers } from '@prisma/client'; @Controller() export class LedgerController { - constructor(private readonly ledgerService: LedgerService) {} + constructor(private readonly ledgerService: LedgerService) { } + @MessagePattern({ cmd: 'get-all-ledgers' }) + async getAllLedgers(): Promise { + return this.ledgerService.getAllLedgers(); + } } diff --git a/apps/ledger/src/ledger.module.ts b/apps/ledger/src/ledger.module.ts index 3728649a8..7d65d6fc6 100644 --- a/apps/ledger/src/ledger.module.ts +++ b/apps/ledger/src/ledger.module.ts @@ -1,10 +1,11 @@ -import { Module } from '@nestjs/common'; +import { Logger, Module } from '@nestjs/common'; import { LedgerController } from './ledger.controller'; import { LedgerService } from './ledger.service'; import { SchemaModule } from './schema/schema.module'; import { PrismaService } from '@credebl/prisma-service'; import { CredentialDefinitionModule } from './credential-definition/credential-definition.module'; import { ClientsModule, Transport } from '@nestjs/microservices'; +import { LedgerRepository } from './repositories/ledger.repository'; @Module({ imports: [ @@ -18,8 +19,13 @@ import { ClientsModule, Transport } from '@nestjs/microservices'; } ]), SchemaModule, CredentialDefinitionModule -], + ], controllers: [LedgerController], - providers: [LedgerService, PrismaService] + providers: [ + LedgerService, + PrismaService, + LedgerRepository, + Logger + ] }) export class LedgerModule { } diff --git a/apps/ledger/src/ledger.service.ts b/apps/ledger/src/ledger.service.ts index 60f32c21a..b8254d3f4 100644 --- a/apps/ledger/src/ledger.service.ts +++ b/apps/ledger/src/ledger.service.ts @@ -1,5 +1,31 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import { BaseService } from 'libs/service/base.service'; +import { LedgerRepository } from './repositories/ledger.repository'; +import { RpcException } from '@nestjs/microservices'; +import { ledgers } from '@prisma/client'; +import { ResponseMessages } from '@credebl/common/response-messages'; @Injectable() -export class LedgerService { +export class LedgerService extends BaseService { + + constructor( + private readonly ledgerRepository: LedgerRepository + ) { + super('LedgerService'); + } + + async getAllLedgers(): Promise { + try { + const getAllLedgerDetails = await this.ledgerRepository.getAllLedgers(); + + if (!getAllLedgerDetails) { + throw new NotFoundException(ResponseMessages.ledger.error.NotFound); + } + + return getAllLedgerDetails; + } catch (error) { + this.logger.error(`Error in retrieving all ledgers: ${error}`); + throw new RpcException(error.response ? error.response : error); + } + } } diff --git a/apps/ledger/src/repositories/ledger.repository.ts b/apps/ledger/src/repositories/ledger.repository.ts new file mode 100644 index 000000000..c1f39ef33 --- /dev/null +++ b/apps/ledger/src/repositories/ledger.repository.ts @@ -0,0 +1,22 @@ +import { PrismaService } from "@credebl/prisma-service"; +import { Injectable, Logger } from "@nestjs/common"; +import { ledgers } from "@prisma/client"; + + +@Injectable() +export class LedgerRepository { + private readonly logger = new Logger('LedgerRepository'); + + constructor( + private prisma: PrismaService + ) { } + + async getAllLedgers(): Promise { + try { + return this.prisma.ledgers.findMany(); + } catch (error) { + this.logger.error(`Error in getAllLedgers: ${error}`); + throw error; + } + } +} \ No newline at end of file diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 01b366bd6..8492ee009 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -83,6 +83,10 @@ export enum CommonConstants { // SHARED AGENT URL_SHAGENT_CREATE_TENANT = '/multi-tenancy/create-tenant', URL_SHAGENT_WITH_TENANT_AGENT = '/multi-tenancy/with-tenant-agent', + URL_SHAGENT_CREATE_SCHEMA = '/multi-tenancy/schema/#', + URL_SHAGENT_GET_SCHEMA = '/multi-tenancy/schema/@/#', + URL_SHAGENT_CREATE_CRED_DEF = '/multi-tenancy/credential-definition/#', + URL_SHAGENT_GET_CRED_DEF = '/multi-tenancy/credential-definition/@/#', URL_SHAGENT_CREATE_INVITATION = '/multi-tenancy/create-legacy-invitation/#', URL_SHAGENT_GET_CREATEED_INVITATIONS = '/multi-tenancy/connections/#', URL_SHAGENT_GET_CREATEED_INVITATION_BY_CONNECTIONID = '/multi-tenancy/connections/#/@', diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 963338135..7d8c43635 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -115,6 +115,14 @@ export const ResponseMessages = { credDefIdNotFound: 'Credential Definition Id not found' } }, + ledger: { + success: { + fetch: 'Ledgers retrieved successfully.' + }, + error: { + NotFound: 'No ledgers found.' + } + }, agent: { success: { create: 'Agent spin-up successfully', diff --git a/libs/prisma-service/prisma/data/credebl-master-table.json b/libs/prisma-service/prisma/data/credebl-master-table.json index c1627506f..a3966a6ca 100644 --- a/libs/prisma-service/prisma/data/credebl-master-table.json +++ b/libs/prisma-service/prisma/data/credebl-master-table.json @@ -94,7 +94,18 @@ "isActive": true, "networkString": "testnet", "registerDIDEndpoint": "http://test.bcovrin.vonx.io/register", - "registerDIDPayload": "" + "registerDIDPayload": "", + "indyNamespace": "bcovrin:testnet" + }, + { + "name": "Indicio Testnet", + "networkType": "testnet", + "poolConfig": "https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_testnet_genesis", + "isActive": true, + "networkString": "testnet", + "registerDIDEndpoint": "https://selfserve.indiciotech.io/nym", + "registerDIDPayload": "", + "indyNamespace": "indicio:testnet" } ] } \ No newline at end of file diff --git a/libs/prisma-service/prisma/migrations/20230928140558_ledgers_indy_namespace/migration.sql b/libs/prisma-service/prisma/migrations/20230928140558_ledgers_indy_namespace/migration.sql new file mode 100644 index 000000000..caa16612f --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20230928140558_ledgers_indy_namespace/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "ledgers" ADD COLUMN "indyNamespace" VARCHAR; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 4ba8e17e7..a353456cd 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -200,6 +200,7 @@ model ledgers { isActive Boolean networkString String @db.VarChar registerDIDEndpoint String @db.VarChar + indyNamespace String? @db.VarChar registerDIDPayload Json? org_agents org_agents[] } From 0f2f783a83f72e1d76de18eb0e4f733e9cf16992 Mon Sep 17 00:00:00 2001 From: KulkarniShashank Date: Fri, 1 Sep 2023 19:17:30 +0530 Subject: [PATCH 079/162] fix: Changed the passkey approch Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/user/repositories/user-device.repository.ts | 1 - apps/user/src/user.service.ts | 5 ++++- libs/prisma-service/prisma/schema.prisma | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/user/repositories/user-device.repository.ts b/apps/user/repositories/user-device.repository.ts index 9e3c559d2..6913aa0e3 100644 --- a/apps/user/repositories/user-device.repository.ts +++ b/apps/user/repositories/user-device.repository.ts @@ -288,4 +288,3 @@ async addCredentialIdAndNameById(id:number, credentialId:string, deviceFriendlyN } } - diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 0f5a84c9a..19b057c14 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -338,7 +338,10 @@ export class UserService { async getProfile(payload: { id }): Promise { try { - return this.userRepository.getUserById(payload.id); + const userData = await this.userRepository.getUserById(payload.id); + const platformConfigData = await this.prisma.platform_config.findMany(); + userData['enableEcosystem'] = platformConfigData[0].enableEcosystem; + return userData; } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 4ba8e17e7..d402fe88e 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -120,6 +120,7 @@ model user_devices { userId Int? deletedAt DateTime? @db.Timestamp(6) authCounter Int @default(0) + password String? @db.VarChar user user? @relation(fields: [userId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_e12ac4f8016243ac71fd2e415af") } From b534c05506534a9c2dbd56b09c13738deb6f3d33 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Wed, 6 Sep 2023 23:27:52 +0530 Subject: [PATCH 080/162] feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw --- apps/api-gateway/src/user/user.service.ts | 2 +- apps/user/src/user.controller.ts | 2 +- libs/common/src/response-messages/index.ts | 15 ++++++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/api-gateway/src/user/user.service.ts b/apps/api-gateway/src/user/user.service.ts index 63e8ecc9e..2f91377ee 100644 --- a/apps/api-gateway/src/user/user.service.ts +++ b/apps/api-gateway/src/user/user.service.ts @@ -71,4 +71,4 @@ export class UserService extends BaseService { const payload = { userEmail, userInfo }; return this.sendNats(this.serviceProxy, 'add-passkey', payload); } -} +} \ No newline at end of file diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 026da028c..26b0c4237 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -120,4 +120,4 @@ export class UserController { return this.userService.addPasskey(payload.userEmail, payload.userInfo); } -} +} \ No newline at end of file diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 3d16195bc..7d8c43635 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -115,6 +115,14 @@ export const ResponseMessages = { credDefIdNotFound: 'Credential Definition Id not found' } }, + ledger: { + success: { + fetch: 'Ledgers retrieved successfully.' + }, + error: { + NotFound: 'No ledgers found.' + } + }, agent: { success: { create: 'Agent spin-up successfully', @@ -177,9 +185,14 @@ export const ResponseMessages = { ecosystem: { success: { create: 'Ecosystem created successfully', + update: 'Ecosystem updated successfully', + fetch: 'Ecosystem fetched successfully', + createInvitation: 'Ecosystem invitations sent successfully', + }, error: { - + notCreated: 'Error while creating ecosystem', + update: 'Error while updating ecosystem' } }, From af1f88792714a3255d5963c05557ce793baec5ee Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Thu, 7 Sep 2023 16:08:44 +0530 Subject: [PATCH 081/162] feat:implemented add passke Signed-off-by: tipusinghaw --- libs/prisma-service/prisma/schema.prisma | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index d402fe88e..4ba8e17e7 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -120,7 +120,6 @@ model user_devices { userId Int? deletedAt DateTime? @db.Timestamp(6) authCounter Int @default(0) - password String? @db.VarChar user user? @relation(fields: [userId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_e12ac4f8016243ac71fd2e415af") } From 13adad0b109b0cc3b3009b1ee05837e26da7cda0 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Mon, 11 Sep 2023 13:07:46 +0530 Subject: [PATCH 082/162] fix: login error message Signed-off-by: tipusinghaw --- apps/user/src/user.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 19b057c14..fb418cb2d 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -615,4 +615,5 @@ export class UserService { throw new RpcException(error.response ? error.response : error); } } + } \ No newline at end of file From 19e580e88f6b17b95d5860e7791ca61b63fc6b86 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Wed, 13 Sep 2023 12:20:17 +0530 Subject: [PATCH 083/162] worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- .../prisma/data/credebl-master-table.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/libs/prisma-service/prisma/data/credebl-master-table.json b/libs/prisma-service/prisma/data/credebl-master-table.json index c1627506f..a3966a6ca 100644 --- a/libs/prisma-service/prisma/data/credebl-master-table.json +++ b/libs/prisma-service/prisma/data/credebl-master-table.json @@ -94,7 +94,18 @@ "isActive": true, "networkString": "testnet", "registerDIDEndpoint": "http://test.bcovrin.vonx.io/register", - "registerDIDPayload": "" + "registerDIDPayload": "", + "indyNamespace": "bcovrin:testnet" + }, + { + "name": "Indicio Testnet", + "networkType": "testnet", + "poolConfig": "https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_testnet_genesis", + "isActive": true, + "networkString": "testnet", + "registerDIDEndpoint": "https://selfserve.indiciotech.io/nym", + "registerDIDPayload": "", + "indyNamespace": "indicio:testnet" } ] } \ No newline at end of file From d54a5ce9c7473f7fe7011162a4cd2e69e3d694f1 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Thu, 14 Sep 2023 20:14:22 +0530 Subject: [PATCH 084/162] worked on the username & orgslug feature for public profiles Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/organization/repositories/organization.repository.ts | 2 +- apps/organization/src/organization.service.ts | 2 +- apps/user/repositories/user.repository.ts | 2 +- apps/user/src/user.service.ts | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 4ad0bd967..6677b0e80 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -433,4 +433,4 @@ export class OrganizationRepository { throw new InternalServerErrorException(error); } } -} +} \ No newline at end of file diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 1ba6eea59..604276162 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -467,4 +467,4 @@ export class OrganizationService { } } -} +} \ No newline at end of file diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index e64f5033a..6c4372542 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -484,4 +484,4 @@ export class UserRepository { } } -} +} \ No newline at end of file diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index fb418cb2d..19b057c14 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -615,5 +615,4 @@ export class UserService { throw new RpcException(error.response ? error.response : error); } } - } \ No newline at end of file From f15aa908ac1074f9790d8d8b8ae1856b7ab90469 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 12:04:32 +0530 Subject: [PATCH 085/162] refractored the GET API endoints for public profiles Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/api-gateway/src/user/user.controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 8c9a25640..e88752f18 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -277,4 +277,5 @@ export class UserController { return res.status(HttpStatus.OK).json(finalResponse); } + } \ No newline at end of file From 97a51cfee1a358f7dbff63b1b1e2702ebc289414 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 12:11:13 +0530 Subject: [PATCH 086/162] refactored the organization GET API public profiles Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/api-gateway/src/organization/organization.controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 667c7bb26..689db270b 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -346,4 +346,5 @@ export class OrganizationController { }; return res.status(HttpStatus.OK).json(finalResponse); } + } \ No newline at end of file From 913c2cec7e405f3ba8c007c417e68a8949545fa1 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 12:28:28 +0530 Subject: [PATCH 087/162] solved the regix expression grouping issue. Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/organization/src/organization.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 604276162..fb42088c3 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -71,6 +71,7 @@ export class OrganizationService { * @returns OrgSlug */ createOrgSlug(orgName: string): string { + return orgName .toLowerCase() // Convert the input to lowercase .replace(/\s+/g, '-') // Replace spaces with hyphens From 44b3a80c711e67a4803fdbe110bff6acc69e2a86 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 13:55:32 +0530 Subject: [PATCH 088/162] resolved the comments on the PR Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/organization/src/organization.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index fb42088c3..3916bc28f 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -71,7 +71,7 @@ export class OrganizationService { * @returns OrgSlug */ createOrgSlug(orgName: string): string { - + return orgName .toLowerCase() // Convert the input to lowercase .replace(/\s+/g, '-') // Replace spaces with hyphens From db7c43a03ab009bf407de40e58b50d4c37f36066 Mon Sep 17 00:00:00 2001 From: "@nishad.shirsat" Date: Fri, 15 Sep 2023 16:03:29 +0530 Subject: [PATCH 089/162] chnaged regular expression for orgSLug Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/organization/src/organization.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 3916bc28f..604276162 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -71,7 +71,6 @@ export class OrganizationService { * @returns OrgSlug */ createOrgSlug(orgName: string): string { - return orgName .toLowerCase() // Convert the input to lowercase .replace(/\s+/g, '-') // Replace spaces with hyphens From 2b894027f7a2454a3f5bf14cd54b1a640ad12ce9 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Fri, 15 Sep 2023 19:25:28 +0530 Subject: [PATCH 090/162] implemented org and user to bydefault public (#87) Signed-off-by: @nishad.shirsat Signed-off-by: tipusinghaw --- apps/organization/src/organization.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 604276162..3dae32045 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -466,5 +466,4 @@ export class OrganizationService { throw new RpcException(error.response ? error.response : error); } } - } \ No newline at end of file From 645a48aef9501ec554c886d06f63961169de66e4 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:51:55 +0530 Subject: [PATCH 091/162] refactor: API refactor in user, organization, auth and agent module (#82) * docs: add CONTRIBUTING guidelines Signed-off-by: KulkarniShashank * docs: add company name in LICESE file Signed-off-by: Ajay Jadhav Signed-off-by: KulkarniShashank * merge dev branch to main (#77) * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw * feat:implemented add passke Signed-off-by: tipusinghaw * fix: login error message Signed-off-by: tipusinghaw --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank * Included credebl-master-table json file in the .gitignore Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank * fix: API refactor in user, organization, auth and agent module Signed-off-by: KulkarniShashank * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * feat:implemented add passke Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * fix: login error message Signed-off-by: tipusinghaw Signed-off-by: KulkarniShashank * Set Validation for the user login Signed-off-by: KulkarniShashank * Removed unnecessarily conditions in user dto Signed-off-by: KulkarniShashank * added the role matrix in the organization, user and agent module Signed-off-by: KulkarniShashank * Added the error handling in the API-gateway Signed-off-by: KulkarniShashank * Added role guard in user and organization Signed-off-by: KulkarniShashank * Error handling in the user signin functionality Signed-off-by: KulkarniShashank * Function name changes in the singin functionality Signed-off-by: KulkarniShashank * Added activity log success in user Signed-off-by: KulkarniShashank * Solved the bug regarding in organization update functionality Signed-off-by: KulkarniShashank * added role guard in user invitation Signed-off-by: KulkarniShashank * Error handling on api-gateway dto Signed-off-by: KulkarniShashank * Added veriable for the seed in agent-service Signed-off-by: KulkarniShashank * Added veriable for the seed on globaly in agent-service Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: Ajay Jadhav Signed-off-by: tipusinghaw Signed-off-by: @nishad.shirsat Co-authored-by: Ajay Jadhav Co-authored-by: Ajay Jadhav Co-authored-by: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Co-authored-by: @nishad.shirsat Co-authored-by: tipusinghaw Signed-off-by: tipusinghaw --- apps/agent-service/src/agent-service.service.ts | 11 ----------- .../repositories/organization.repository.ts | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index bd013bcc5..0edc673bd 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -888,16 +888,5 @@ export class AgentServiceService { } } - async getProofFormData(url: string, apiKey: string): Promise { - try { - const getProofFormData = await this.commonService - .httpGet(url, { headers: { 'x-api-key': apiKey } }) - .then(async response => response); - return getProofFormData; - } catch (error) { - this.logger.error(`Error in get proof form data in agent service : ${JSON.stringify(error)}`); - throw new RpcException(error); - } - } } diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 6677b0e80..4ad0bd967 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -433,4 +433,4 @@ export class OrganizationRepository { throw new InternalServerErrorException(error); } } -} \ No newline at end of file +} From 5e8c146d865b2ef46282a0d2a62d82aa5e02c0d1 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:46:10 +0530 Subject: [PATCH 092/162] refactor: schema APIs standardized (#84) * refactor: Schema APIs standardized Signed-off-by: KulkarniShashank * Resolved sonar lint checks Signed-off-by: KulkarniShashank * API refactor in schema module Signed-off-by: KulkarniShashank * API refactor in credential-definition Signed-off-by: KulkarniShashank * Added the error handling in the platform and credential-definition module Signed-off-by: KulkarniShashank * Error handling and solved the pagination Signed-off-by: KulkarniShashank * Added logger in the global exception handling Signed-off-by: KulkarniShashank * Changes as per comment on the Schema controller Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/api-gateway/common/exception-handler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index 5df61a442..1036cb02e 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -18,6 +18,7 @@ export class CustomExceptionFilter extends BaseExceptionFilter { exception.message = 'Oops! Something went wrong. Please try again'; } + let errorResponse; if (exception && exception["statusCode"] === HttpStatus.INTERNAL_SERVER_ERROR) { errorResponse = { From 6305b84d283cff3cd94de34eef864ed9bcdbc7cd Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Thu, 21 Sep 2023 19:58:41 +0530 Subject: [PATCH 093/162] refactor: Standardize API in connection, issuance and verification (#100) * refactor: connection, issuance, veriication API refactor Signed-off-by: KulkarniShashank * Added error handling and role guard in issuance and verification Signed-off-by: KulkarniShashank * Remove the duplication code in verification Signed-off-by: KulkarniShashank * Remove the duplication code in verification controller Signed-off-by: KulkarniShashank * Changes as per comment in PR Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw --- apps/api-gateway/common/exception-handler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index 1036cb02e..5df61a442 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -18,7 +18,6 @@ export class CustomExceptionFilter extends BaseExceptionFilter { exception.message = 'Oops! Something went wrong. Please try again'; } - let errorResponse; if (exception && exception["statusCode"] === HttpStatus.INTERNAL_SERVER_ERROR) { errorResponse = { From 5b99245a6fdde65623eae4a8b3386d7717469bac Mon Sep 17 00:00:00 2001 From: tipusinghaw <126460794+tipusinghaw@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:33:45 +0530 Subject: [PATCH 094/162] feat: ecosystem ms setup (#104) * worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat * merge dev branch to main (#77) * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw * feat:implemented add passke Signed-off-by: tipusinghaw * fix: login error message Signed-off-by: tipusinghaw --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw * Included credebl-master-table json file in the .gitignore Signed-off-by: @nishad.shirsat * Create ecosystem monorepo Signed-off-by: KulkarniShashank * feat: Implemented ecosystem microservice Signed-off-by: tipusinghaw * feat: changed controller name Signed-off-by: tipusinghaw --------- Signed-off-by: tipusinghaw Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank Co-authored-by: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Co-authored-by: @nishad.shirsat Co-authored-by: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Co-authored-by: KulkarniShashank Signed-off-by: tipusinghaw --- libs/common/src/response-messages/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 7d8c43635..5d61ab672 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -194,6 +194,5 @@ export const ResponseMessages = { notCreated: 'Error while creating ecosystem', update: 'Error while updating ecosystem' } - }, }; \ No newline at end of file From 4c0b1afa071a06c1adce50657bc463f759eac270 Mon Sep 17 00:00:00 2001 From: tipusinghaw <126460794+tipusinghaw@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:58:46 +0530 Subject: [PATCH 095/162] feat: ecosystem ms setup (#105) * worked on the master table json file and .env sample refractoring Signed-off-by: @nishad.shirsat * merge dev branch to main (#77) * fix: Changed the passkey approch Signed-off-by: KulkarniShashank * feat/fix: Implemented Add passkey for existing users Signed-off-by: tipusinghaw * feat:implemented add passke Signed-off-by: tipusinghaw * fix: login error message Signed-off-by: tipusinghaw --------- Signed-off-by: KulkarniShashank Signed-off-by: tipusinghaw * Included credebl-master-table json file in the .gitignore Signed-off-by: @nishad.shirsat * Create ecosystem monorepo Signed-off-by: KulkarniShashank * feat: Implemented ecosystem microservice Signed-off-by: tipusinghaw * feat: changed controller name Signed-off-by: tipusinghaw * fix:changed return message Signed-off-by: tipusinghaw * setup ecosystem prisma schema Signed-off-by: Nishad --------- Signed-off-by: tipusinghaw Signed-off-by: @nishad.shirsat Signed-off-by: KulkarniShashank Signed-off-by: Nishad Co-authored-by: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Co-authored-by: @nishad.shirsat Co-authored-by: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Co-authored-by: KulkarniShashank Signed-off-by: tipusinghaw --- libs/prisma-service/prisma/schema.prisma | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 4ba8e17e7..7bb41b390 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -377,6 +377,5 @@ model ecosystem_orgs { lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) lastChangedBy Int @default(1) deletedAt DateTime? @db.Timestamp(6) - } From b5a655e680f30059edc517522539c93ae71bdafa Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Wed, 4 Oct 2023 13:17:24 +0530 Subject: [PATCH 096/162] feat: implemented enum Signed-off-by: tipusinghaw --- .../ecosystem/dtos/create-ecosystem-dto.ts | 46 +++++++ ...anization-dto.ts => edit-ecosystem-dto.ts} | 38 +++--- .../src/ecosystem/ecosystem.controller.ts | 40 +++++- .../src/ecosystem/ecosystem.service.ts | 21 +++- apps/api-gateway/src/enum.ts | 2 +- apps/ecosystem/enums/ecosystem.enum.ts | 4 + apps/ecosystem/src/ecosystem.controller.ts | 27 +++- apps/ecosystem/src/ecosystem.module.ts | 6 +- apps/ecosystem/src/ecosystem.repository.ts | 116 ++++++++++++++++++ apps/ecosystem/src/ecosystem.service.ts | 49 +++++++- 10 files changed, 311 insertions(+), 38 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts rename apps/api-gateway/src/ecosystem/dtos/{create-organization-dto.ts => edit-ecosystem-dto.ts} (56%) create mode 100644 apps/ecosystem/enums/ecosystem.enum.ts create mode 100644 apps/ecosystem/src/ecosystem.repository.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts b/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts new file mode 100644 index 000000000..2b66e7ee0 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts @@ -0,0 +1,46 @@ +import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsInt, IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; + +import { Transform } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; + +@ApiExtraModels() +export class CreateEcosystemDto { + + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'ecosystem name is required.' }) + @MinLength(2, { message: 'ecosystem name must be at least 2 characters.' }) + @MaxLength(50, { message: 'ecosystem name must be at most 50 characters.' }) + @IsString({ message: 'ecosystem name must be in string format.' }) + name: string; + + @ApiPropertyOptional() + @Transform(({ value }) => trim(value)) + @MinLength(2, { message: 'Description must be at least 2 characters.' }) + @MaxLength(255, { message: 'Description must be at most 255 characters.' }) + @IsString({ message: 'Description must be in string format.' }) + @IsOptional() + description?: string; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'tag must be in string format.' }) + tags?: string; + + @ApiPropertyOptional() + @IsInt({ message: 'orgId must be in number format.' }) + orgId: number; + + @ApiPropertyOptional() + @IsInt({ message: 'UserId must be in number format.' }) + userId: number; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'logo must be in string format.' }) + logo?: string; + } + \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts b/apps/api-gateway/src/ecosystem/dtos/edit-ecosystem-dto.ts similarity index 56% rename from apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts rename to apps/api-gateway/src/ecosystem/dtos/edit-ecosystem-dto.ts index d584c5832..96be3ba09 100644 --- a/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts +++ b/apps/api-gateway/src/ecosystem/dtos/edit-ecosystem-dto.ts @@ -1,36 +1,38 @@ -import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { ApiExtraModels, ApiPropertyOptional } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; @ApiExtraModels() -export class CreateEcosystemDto { - - @ApiProperty() +export class EditEcosystemDto { + @ApiPropertyOptional() @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'Organization name is required.' }) - @MinLength(2, { message: 'Organization name must be at least 2 characters.' }) - @MaxLength(50, { message: 'Organization name must be at most 50 characters.' }) - @IsString({ message: 'Organization name must be in string format.' }) - name: string; - + @IsOptional() + @IsNotEmpty({ message: 'Ecosystem name is required.' }) + @MinLength(2, { message: 'Ecosystem name must be at least 2 characters.' }) + @MaxLength(50, { message: 'Ecosystem name must be at most 50 characters.' }) + @IsString({ message: 'Ecosystem name must be in string format.' }) + name?: string; + @ApiPropertyOptional() @Transform(({ value }) => trim(value)) + @IsOptional() @MinLength(2, { message: 'Description must be at least 2 characters.' }) @MaxLength(255, { message: 'Description must be at most 255 characters.' }) @IsString({ message: 'Description must be in string format.' }) - description: string; - + description?: string; + @ApiPropertyOptional() @IsOptional() @Transform(({ value }) => trim(value)) - @IsString({ message: 'logo must be in string format.' }) - logo: string; - + @IsString({ message: 'tag must be in string format.' }) + tags?: string; + @ApiPropertyOptional() @IsOptional() @Transform(({ value }) => trim(value)) - website?: string; - -} \ No newline at end of file + @IsString({ message: 'logo must be in string format.' }) + logo?: 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 6cf86ee4f..88658779f 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -1,19 +1,20 @@ import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; -import { Controller, UseGuards, UseFilters } from '@nestjs/common'; +import { Controller, UseFilters, Put, Param, UseGuards } from '@nestjs/common'; import { EcosystemService } from './ecosystem.service'; -import { Post } from '@nestjs/common'; +import { Post, Get } from '@nestjs/common'; import { Body } from '@nestjs/common'; import { Res } from '@nestjs/common'; -import { CreateEcosystemDto } from './dtos/create-organization-dto'; +import { CreateEcosystemDto } from './dtos/create-ecosystem-dto'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { HttpStatus } from '@nestjs/common'; import { Response } from 'express'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; -import { AuthGuard } from '@nestjs/passport'; import { ResponseMessages } from '@credebl/common/response-messages'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; +import { EditEcosystemDto } from './dtos/edit-ecosystem-dto'; +import { AuthGuard } from '@nestjs/passport'; @UseFilters(CustomExceptionFilter) @@ -27,12 +28,27 @@ export class EcosystemController { ) { } + @Get('/') + @ApiOperation({ summary: 'Get all ecosystem', description: 'Get all existing ecosystem' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async getEcosystem(@Res() res: Response): Promise { + const ecosystemList = await this.ecosystemService.getAllEcosystem(); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.fetch, + data: ecosystemList.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + @Post('/') @ApiOperation({ summary: 'Create a new ecosystem', description: 'Create an ecosystem' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async createOrganization(@Body() createOrgDto: CreateEcosystemDto, @Res() res: Response): Promise { + async createNewEcosystem(@Body() createOrgDto: CreateEcosystemDto, @Res() res: Response): Promise { await this.ecosystemService.createEcosystem(createOrgDto); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -40,4 +56,18 @@ export class EcosystemController { }; return res.status(HttpStatus.CREATED).json(finalResponse); } + + @Put('/:ecosystemId/') + @ApiOperation({ summary: 'Edit ecosystem', description: 'Edit existing ecosystem' }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async editEcosystem(@Body() editEcosystemDto: EditEcosystemDto, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { + await this.ecosystemService.editEcosystem(editEcosystemDto, ecosystemId); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.update + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } } \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 4ce9cba9a..bd60b859e 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -20,4 +20,23 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'create-ecosystem', payload); } -} + /** + * + * @param editEcosystemDto + * @returns Ecosystem creation success + */ + async editEcosystem(editEcosystemDto, ecosystemId): Promise { + const payload = { editEcosystemDto, ecosystemId }; + return this.sendNats(this.serviceProxy, 'edit-ecosystem', payload); + } + + /** + * + * + * @returns Get all ecosystems + */ + async getAllEcosystem(): Promise<{ response: object }> { + return this.sendNats(this.serviceProxy, 'get-all-ecosystem', ''); + } + +} \ No newline at end of file diff --git a/apps/api-gateway/src/enum.ts b/apps/api-gateway/src/enum.ts index 0cfdecf91..7dd84939b 100644 --- a/apps/api-gateway/src/enum.ts +++ b/apps/api-gateway/src/enum.ts @@ -115,4 +115,4 @@ export enum ExpiredSubscriptionSortBy { startDate = 'startDate', endDate = 'endDate', id = 'id', -} \ No newline at end of file +} diff --git a/apps/ecosystem/enums/ecosystem.enum.ts b/apps/ecosystem/enums/ecosystem.enum.ts new file mode 100644 index 000000000..4ed11bcb4 --- /dev/null +++ b/apps/ecosystem/enums/ecosystem.enum.ts @@ -0,0 +1,4 @@ +export enum Ecosystem { + LEAD = 'Ecosystem Lead', + ACTIVE = 'ACTIVE' +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 55a91e22d..7c60d6ba3 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -16,9 +16,28 @@ export class EcosystemController { */ @MessagePattern({ cmd: 'create-ecosystem' }) - async createEcosystem(@Body() payload: { createOrgDto; userId }): Promise { - this.logger.log(`EcosystemPayload : ${payload}`); - return this.ecosystemService.createEcosystem(); + async createEcosystem(@Body() payload: { createEcosystemDto }): Promise { + return this.ecosystemService.createEcosystem(payload.createEcosystemDto); + } + + /** + * Description: edit ecosystem + * @param payload updation Details + * @returns Get updated ecosystem details + */ + @MessagePattern({ cmd: 'edit-ecosystem' }) + async editEcosystem(@Body() payload: { editEcosystemDto, ecosystemId }): Promise { + return this.ecosystemService.editEcosystem(payload.editEcosystemDto, payload.ecosystemId); + } + + /** + * Description: get all ecosystems + * @param payload Registration Details + * @returns Get all ecosystem details + */ + @MessagePattern({ cmd: 'get-all-ecosystem' }) + async getAllEcosystems(): Promise { + return this.ecosystemService.getAllEcosystem(); } -} +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.module.ts b/apps/ecosystem/src/ecosystem.module.ts index c749922c7..2d66da9d7 100644 --- a/apps/ecosystem/src/ecosystem.module.ts +++ b/apps/ecosystem/src/ecosystem.module.ts @@ -3,7 +3,7 @@ import { EcosystemController } from './ecosystem.controller'; import { EcosystemService } from './ecosystem.service'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { CommonModule } from '@credebl/common'; -// import { ConnectionRepository } from './connection.repository'; +import { EcosystemRepository } from './ecosystem.repository'; import { PrismaService } from '@credebl/prisma-service'; @Module({ @@ -21,6 +21,6 @@ import { PrismaService } from '@credebl/prisma-service'; CommonModule ], controllers: [EcosystemController], - providers: [EcosystemService, PrismaService, Logger] + providers: [EcosystemService, PrismaService, Logger, EcosystemRepository] }) -export class EcosystemModule { } +export class EcosystemModule { } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts new file mode 100644 index 000000000..7fa2aaafc --- /dev/null +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -0,0 +1,116 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PrismaService } from '@credebl/prisma-service'; +import { ecosystem } from '@prisma/client'; +import {Ecosystem} from '../enums/ecosystem.enum'; +// eslint-disable-next-line camelcase +@Injectable() +export class EcosystemRepository { + + constructor( + private readonly prisma: PrismaService, + private readonly logger: Logger + ) { } + + /** + * Description: Get getAgentEndPoint by orgId + * @param createEcosystemDto + * @returns Get getAgentEndPoint details + */ + // eslint-disable-next-line camelcase + async createNewEcosystem(createEcosystemDto): Promise { + try { + const transaction = await this.prisma.$transaction(async (prisma) => { + const { name, description, userId, logo, tags, orgId } = createEcosystemDto; + const createdEcosystem = await prisma.ecosystem.create({ + data: { + name, + description, + tags, + logoUrl: logo, + createdBy: orgId, + lastChangedBy: orgId + } + }); + let ecosystemUser; + if (createdEcosystem) { + ecosystemUser = await prisma.ecosystem_users.create({ + data: { + userId: String(userId), + ecosystemId: createdEcosystem.id, + createdBy: orgId, + lastChangedBy: orgId + } + }); + } + + if (ecosystemUser) { + const ecosystemRoleDetails = await this.prisma.ecosystem_roles.findFirst({ + where: { + name: Ecosystem.LEAD + } + }); + ecosystemUser = await prisma.ecosystem_orgs.create({ + data: { + orgId: String(orgId), + status: Ecosystem.ACTIVE, + ecosystemId: createdEcosystem.id, + ecosystemRoleId: ecosystemRoleDetails.id, + createdBy: orgId, + lastChangedBy: orgId + } + }); + } + return createdEcosystem; + }); + + return transaction; + } catch (error) { + this.logger.error(`Error in create ecosystem transaction: ${error.message}`); + throw error; + } + } + + /** + * Description: Edit ecosystem by Id + * @param editEcosystemDto + * @returns ecosystem details + */ + // eslint-disable-next-line camelcase + async updateEcosystemById(createEcosystemDto, ecosystemId): Promise { + try { + const { name, description, tags, logo } = createEcosystemDto; + const editEcosystem = await this.prisma.ecosystem.update({ + where: { id: ecosystemId }, + data: { + name, + description, + tags, + logoUrl: logo + } + }); + return editEcosystem; + } catch (error) { + this.logger.error(`Error in edit ecosystem transaction: ${error.message}`); + throw error; + } + } + + /** + * + * + * @returns Get all ecosystem details + */ + // eslint-disable-next-line camelcase + async getAllEcosystemDetails(): Promise { + try { + const ecosystemDetails = await this.prisma.ecosystem.findMany({ + }); + return ecosystemDetails; + } catch (error) { + this.logger.error(`Error in get all ecosystem transaction: ${error.message}`); + throw error; + } + } + + +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 14795ec82..0b7def360 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -1,20 +1,57 @@ // eslint-disable-next-line camelcase -import { Injectable} from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import { EcosystemRepository } from './ecosystem.repository'; +import { ResponseMessages } from '@credebl/common/response-messages'; @Injectable() export class EcosystemService { - constructor( + constructor( + private readonly ecosystemRepository: EcosystemRepository ) { } /** * - * @param registerOrgDto + * @param createEcosystemDto * @returns */ // eslint-disable-next-line camelcase - async createEcosystem():Promise { - return "test ecosystem"; + async createEcosystem(createEcosystemDto): Promise { + const createEcosystem = await this.ecosystemRepository.createNewEcosystem(createEcosystemDto); + if (!createEcosystem) { + throw new NotFoundException(ResponseMessages.ecosystem.error.update); + } + return createEcosystem; } -} + + /** + * + * @param editEcosystemDto + * @returns + */ + + // eslint-disable-next-line camelcase + async editEcosystem(editEcosystemDto, ecosystemId): Promise { + const editOrganization = await this.ecosystemRepository.updateEcosystemById(editEcosystemDto, ecosystemId); + if (!editOrganization) { + throw new NotFoundException(ResponseMessages.ecosystem.error.update); + } + return editOrganization; + } + + /** + * + * + * @returns all ecosystem details + */ + + // eslint-disable-next-line camelcase + async getAllEcosystem(): Promise { + const getAllEcosystemDetails = await this.ecosystemRepository.getAllEcosystemDetails(); + if (!getAllEcosystemDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.update); + } + return getAllEcosystemDetails; + } +} \ No newline at end of file From f4de22d5c680b95c337ccf8e91cebd47367a30b6 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Wed, 4 Oct 2023 14:01:06 +0530 Subject: [PATCH 097/162] fix: changed enum messages Signed-off-by: tipusinghaw --- apps/ecosystem/enums/ecosystem.enum.ts | 9 +++++++-- apps/ecosystem/src/ecosystem.repository.ts | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/ecosystem/enums/ecosystem.enum.ts b/apps/ecosystem/enums/ecosystem.enum.ts index 4ed11bcb4..6bf735879 100644 --- a/apps/ecosystem/enums/ecosystem.enum.ts +++ b/apps/ecosystem/enums/ecosystem.enum.ts @@ -1,4 +1,9 @@ -export enum Ecosystem { - LEAD = 'Ecosystem Lead', +export enum EcosystemRoles { + ECOSYSTEM_LEAD = 'Ecosystem Lead', + ECOSYSTEM_MEMBER = 'Ecosystem Member', + ECOSYSTEM_OWNER = 'Ecosystem Owner' +} + +export enum EcosystemOrgStatus { ACTIVE = 'ACTIVE' } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 7fa2aaafc..f7e7187b0 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; import { ecosystem } from '@prisma/client'; -import {Ecosystem} from '../enums/ecosystem.enum'; +import {EcosystemOrgStatus, EcosystemRoles} from '../enums/ecosystem.enum'; // eslint-disable-next-line camelcase @Injectable() export class EcosystemRepository { @@ -46,13 +46,13 @@ export class EcosystemRepository { if (ecosystemUser) { const ecosystemRoleDetails = await this.prisma.ecosystem_roles.findFirst({ where: { - name: Ecosystem.LEAD + name: EcosystemRoles.ECOSYSTEM_LEAD } }); ecosystemUser = await prisma.ecosystem_orgs.create({ data: { orgId: String(orgId), - status: Ecosystem.ACTIVE, + status: EcosystemOrgStatus.ACTIVE, ecosystemId: createdEcosystem.id, ecosystemRoleId: ecosystemRoleDetails.id, createdBy: orgId, From 61e6f30ceef18af46f8952de06b98a6aa1ea8f78 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Thu, 5 Oct 2023 12:02:36 +0530 Subject: [PATCH 098/162] refactor: global exception handler and user DTO Signed-off-by: tipusinghaw --- apps/api-gateway/common/exception-handler.ts | 13 ++++++------- apps/api-gateway/src/user/dto/add-user.dto.ts | 11 +++++------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index 5df61a442..38259e86e 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -41,13 +41,12 @@ export class CustomExceptionFilter extends BaseExceptionFilter { if (exception && exception["response"] && exception.message) { if (Array.isArray(exception["response"].message)) { - exception["response"].message.forEach((msg) => { - errorResponse = { - statusCode: exception["statusCode"] ? exception["statusCode"] : status, - message: msg || 'Internal server error', - error: msg || 'Internal server error' - }; - }); + + 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, 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 05983a924..e0dea93df 100644 --- a/apps/api-gateway/src/user/dto/add-user.dto.ts +++ b/apps/api-gateway/src/user/dto/add-user.dto.ts @@ -9,18 +9,17 @@ export class AddUserDetails { @IsEmail() @IsNotEmpty({ message: 'Please provide valid email' }) @IsString({ message: 'email should be string' }) - @IsOptional() - email?: string; + email: string; @ApiProperty({ example: 'Alen' }) + @IsNotEmpty({ message: 'Please provide valid email' }) @IsString({ message: 'firstName should be string' }) - @IsOptional() - firstName?: string; + firstName: string; @ApiProperty({ example: 'Harvey' }) + @IsNotEmpty({ message: 'Please provide valid email' }) @IsString({ message: 'lastName should be string' }) - @IsOptional() - lastName?: string; + lastName: string; @ApiProperty() @Transform(({ value }) => trim(value)) From 3e6191662524da70d89e15600820b008820128bf Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Thu, 5 Oct 2023 15:15:28 +0530 Subject: [PATCH 099/162] feat: get ecosystem invitations Signed-off-by: bhavanakarwade --- .../get-all-sent-ecosystemInvitations-dto.ts | 29 +++++++++++ .../src/ecosystem/ecosystem.controller.ts | 49 ++++++++++++++++-- .../src/ecosystem/ecosystem.service.ts | 15 ++++++ apps/ecosystem/src/ecosystem.controller.ts | 18 ++++++- apps/ecosystem/src/ecosystem.repository.ts | 51 ++++++++++++++++++- apps/ecosystem/src/ecosystem.service.ts | 23 ++++++++- libs/common/src/response-messages/index.ts | 8 +-- 7 files changed, 183 insertions(+), 10 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/get-all-sent-ecosystemInvitations-dto.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/get-all-sent-ecosystemInvitations-dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-all-sent-ecosystemInvitations-dto.ts new file mode 100644 index 000000000..4f2da4f80 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/get-all-sent-ecosystemInvitations-dto.ts @@ -0,0 +1,29 @@ +import { Transform, Type } from 'class-transformer'; +import { toNumber } from '@credebl/common/cast.helper'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; +import { Invitation } from '@credebl/enum/enum'; + +export class GetAllSentEcosystemInvitationsDto { + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => Number) + @Transform(({ value }) => toNumber(value)) + pageNumber = 1; + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => String) + search = ''; + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => Number) + @Transform(({ value }) => toNumber(value)) + pageSize = 10; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + status = Invitation.PENDING; +} diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 88658779f..ca98a2385 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -1,5 +1,5 @@ -import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; -import { Controller, UseFilters, Put, Param, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; +import { Controller, UseFilters, Put, Param, UseGuards, Query, BadRequestException } from '@nestjs/common'; import { EcosystemService } from './ecosystem.service'; import { Post, Get } from '@nestjs/common'; import { Body } from '@nestjs/common'; @@ -15,7 +15,11 @@ import { ResponseMessages } from '@credebl/common/response-messages'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; import { EditEcosystemDto } from './dtos/edit-ecosystem-dto'; import { AuthGuard } from '@nestjs/passport'; - +import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; +import { User } from '../authz/decorators/user.decorator'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { user } from '@prisma/client'; +import { Invitation } from '@credebl/enum/enum'; @UseFilters(CustomExceptionFilter) @Controller('ecosystem') @@ -43,6 +47,45 @@ export class EcosystemController { return res.status(HttpStatus.OK).json(finalResponse); } + @Get('/users/invitations') + @ApiOperation({ summary: 'Get an ecosystem invitations', description: 'Get an ecosystem invitations' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + @ApiQuery({ + name: 'status', + type: String, + required: false + }) + async getEcosystemInvitations(@Query() getAllInvitationsDto: GetAllSentEcosystemInvitationsDto, @User() user: user, @Res() res: Response): Promise { + if (!Object.values(Invitation).includes(getAllInvitationsDto.status)) { + throw new BadRequestException(ResponseMessages.ecosystem.error.invalidInvitationStatus); + } + const getEcosystemInvitation = await this.ecosystemService.getEcosystemInvitations(getAllInvitationsDto, user.email, getAllInvitationsDto.status); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.getInvitation, + data: getEcosystemInvitation.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + + } + @Post('/') @ApiOperation({ summary: 'Create a new ecosystem', description: 'Create an ecosystem' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 760bc98b8..3f1dcf87b 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -2,6 +2,7 @@ import { Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; +import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; @Injectable() @@ -39,4 +40,18 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'get-all-ecosystem', ''); } + /** + * + * @returns Ecosystem Invitations details + */ + async getEcosystemInvitations( + getAllInvitationsDto: GetAllSentEcosystemInvitationsDto, + userEmail: string, + status: string + ): Promise<{ response: object }> { + const { pageNumber, pageSize, search } = getAllInvitationsDto; + const payload = { userEmail, status, pageNumber, pageSize, search }; + return this.sendNats(this.serviceProxy, 'get-ecosystem-invitations', payload); + } + } diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index ddcb718c0..ba847f6e0 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -39,5 +39,21 @@ export class EcosystemController { async getAllEcosystems(): Promise { return this.ecosystemService.getAllEcosystem(); } - + + /** + * 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 } + ): Promise { + return this.ecosystemService.getEcosystemInvitations( + payload.userEmail, + payload.status, + payload.pageNumber, + payload.pageSize, + payload.search + ); + } } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index f7e7187b0..f356a7d76 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; import { ecosystem } from '@prisma/client'; import {EcosystemOrgStatus, EcosystemRoles} from '../enums/ecosystem.enum'; @@ -112,5 +112,54 @@ export class EcosystemRepository { } } + async getEcosystemInvitationsPagination(queryObject: object, status: string, pageNumber: number, pageSize: number): Promise { + try { + const result = await this.prisma.$transaction([ + this.prisma.ecosystem_invitations.findMany({ + where: { + ...queryObject, + status + }, + include: { + ecosystem: true + }, + take: pageSize, + skip: (pageNumber - 1) * pageSize, + orderBy: { + createDateTime: 'desc' + } + }), + this.prisma.ecosystem_invitations.count({ + where: { + ...queryObject + } + }) + ]); + + const [invitations, totalCount] = result; + const totalPages = Math.ceil(totalCount / pageSize); + + return { totalPages, invitations }; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + async getEcosystemInvitations(userEmail: string, status: string, pageNumber: number, pageSize: number, search = ''): Promise { + try { + const query = { + AND: [ + { email: userEmail }, + { status: { contains: search, mode: 'insensitive' } } + ] + }; + + return this.getEcosystemInvitationsPagination(query, status, pageNumber, pageSize); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index c10e1ada1..06630ec7f 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -1,12 +1,15 @@ // eslint-disable-next-line camelcase -import { Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { EcosystemRepository } from './ecosystem.repository'; import { ResponseMessages } from '@credebl/common/response-messages'; +import { RpcException } from '@nestjs/microservices'; @Injectable() export class EcosystemService { constructor( - private readonly ecosystemRepository: EcosystemRepository + private readonly ecosystemRepository: EcosystemRepository, + private readonly logger: Logger + ) { } /** @@ -54,4 +57,20 @@ export class EcosystemService { } return getAllEcosystemDetails; } + + /** + * 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 { + try { + const getEcosystem = await this.ecosystemRepository.getEcosystemInvitations(userEmail, status, pageNumber, pageSize, search); + return getEcosystem; + } catch (error) { + this.logger.error(`In get invitation : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + } diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 23b4708d0..9a9f8e7f0 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -178,12 +178,14 @@ export const ResponseMessages = { success: { create: 'Ecosystem created successfully', update: 'Ecosystem updated successfully', - fetch: 'Ecosystem fetched successfully' + fetch: 'Ecosystem fetched successfully', + getInvitation: 'Ecosystem invitations fetched successfully' }, error: { notCreated: 'Error while creating ecosystem', - update: 'Error while updating ecosystem' + update: 'Error while updating ecosystem', + invalidInvitationStatus: 'Invalid invitation status' } - }, + } }; \ No newline at end of file From c4fdf14ca54d915ca51cf3aaf2fd4f72f09adca0 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Thu, 5 Oct 2023 15:59:57 +0530 Subject: [PATCH 100/162] resolved sonar cloud checks Signed-off-by: bhavanakarwade --- .../dtos/get-all-sent-ecosystemInvitations-dto.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/dtos/get-all-sent-ecosystemInvitations-dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-all-sent-ecosystemInvitations-dto.ts index 4f2da4f80..ea3fe3176 100644 --- a/apps/api-gateway/src/ecosystem/dtos/get-all-sent-ecosystemInvitations-dto.ts +++ b/apps/api-gateway/src/ecosystem/dtos/get-all-sent-ecosystemInvitations-dto.ts @@ -5,16 +5,18 @@ import { IsOptional, IsString } from 'class-validator'; import { Invitation } from '@credebl/enum/enum'; export class GetAllSentEcosystemInvitationsDto { - @ApiProperty({ required: false }) - @IsOptional() - @Type(() => Number) - @Transform(({ value }) => toNumber(value)) - pageNumber = 1; + @ApiProperty({ required: false }) @IsOptional() @Type(() => String) search = ''; + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => Number) + @Transform(({ value }) => toNumber(value)) + pageNumber = 1; @ApiProperty({ required: false }) @IsOptional() From e652298d9d52346debb5becda26ed3cda91ae569 Mon Sep 17 00:00:00 2001 From: Sheetal-ayanworks Date: Thu, 5 Oct 2023 16:00:48 +0530 Subject: [PATCH 101/162] Added dockerfile for ecosystem microservice Signed-off-by: Sheetal-ayanworks --- Dockerfiles/Dockerfile.ecosystem | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 Dockerfiles/Dockerfile.ecosystem diff --git a/Dockerfiles/Dockerfile.ecosystem b/Dockerfiles/Dockerfile.ecosystem new file mode 100644 index 000000000..3f258801a --- /dev/null +++ b/Dockerfiles/Dockerfile.ecosystem @@ -0,0 +1,41 @@ +# Stage 1: Build the application +FROM node:18-alpine as build +RUN npm install -g pnpm +# Set the working directory +WORKDIR /app + +# Copy package.json and package-lock.json +COPY package.json ./ +#COPY package-lock.json ./ + +# Install dependencies +RUN pnpm i + +# Copy the rest of the application code +COPY . . +RUN cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate + +# Build the issuance service +RUN pnpm run build ecosystem + +# Stage 2: Create the final image +FROM node:18-alpine +RUN npm install -g pnpm +# Set the working directory +WORKDIR /app + +# Copy the compiled code from the build stage +COPY --from=build /app/dist/apps/ecosystem/ ./dist/apps/ecosystem/ + +# Copy the libs folder from the build stage +COPY --from=build /app/libs/ ./libs/ +#COPY --from=build /app/package.json ./ +COPY --from=build /app/node_modules ./node_modules + + +# Set the command to run the microservice +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/ecosystem/main.js"] + +# docker build -t ecosystem -f Dockerfiles/Dockerfile.ecosystem . +# docker run -d --env-file .env --name ecosystem docker.io/library/ecosystem +# docker logs -f ecosystem From 24d1c398110959a5a1c3fb29704d7f28b438b8e2 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Thu, 5 Oct 2023 16:08:02 +0530 Subject: [PATCH 102/162] fix: changed platform route Signed-off-by: tipusinghaw --- apps/api-gateway/src/platform/platform.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api-gateway/src/platform/platform.controller.ts b/apps/api-gateway/src/platform/platform.controller.ts index fd13f18ff..2905c9e49 100644 --- a/apps/api-gateway/src/platform/platform.controller.ts +++ b/apps/api-gateway/src/platform/platform.controller.ts @@ -20,7 +20,7 @@ export class PlatformController { private readonly logger = new Logger('PlatformController'); - @Get('/platform/schemas') + @Get('/schemas') @ApiTags('schemas') @ApiOperation({ summary: 'Get all schemas from platform.', From 196c2e2a68ebc294aeb9f8174dc81a54730a4b88 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Thu, 5 Oct 2023 16:12:10 +0530 Subject: [PATCH 103/162] fix: resolved sonar lint checks Signed-off-by: bhavanakarwade --- apps/ecosystem/src/ecosystem.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 6ea8ae550..2e31b5437 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -65,9 +65,10 @@ export class EcosystemService { } /** - * Description: get an ecosystem invitation + * 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 { @@ -79,7 +80,7 @@ export class EcosystemService { ] }; - return this.ecosystemRepository.getEcosystemInvitationsPagination(query, status, pageNumber, pageSize); + return await this.ecosystemRepository.getEcosystemInvitationsPagination(query, status, pageNumber, pageSize); } catch (error) { this.logger.error(`In error getEcosystemInvitations: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); From 2c534e714fa2be60643a24fe01b1c81ccd344a9d Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Thu, 5 Oct 2023 16:49:53 +0530 Subject: [PATCH 104/162] fix: resolved build issue for prisma client Signed-off-by: bhavanakarwade --- apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 61b12507c..30e932120 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -16,8 +16,6 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler import { EditEcosystemDto } from './dtos/edit-ecosystem-dto'; import { AuthGuard } from '@nestjs/passport'; import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import { user } from '@prisma/client'; import { Invitation } from '@credebl/enum/enum'; import { User } from '../authz/decorators/user.decorator'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; From 612b1cb626a3daa69b453145946b47949da99db2 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Thu, 5 Oct 2023 19:09:24 +0530 Subject: [PATCH 105/162] feat: fetch ecosystem sent invitations (#123) * worked on the eslint issues Signed-off-by: Nishad * worked on GET API sent invitation list to join ecosystem Signed-off-by: Nishad * Solved promise issue in ecosystem repository Signed-off-by: Nishad * Created interface for send invitation payload Signed-off-by: Nishad --------- Signed-off-by: Nishad --- .../dtos/get-all-sent-invitations.dto.ts | 25 +++++ .../src/ecosystem/ecosystem.controller.ts | 39 ++++++++ .../src/ecosystem/ecosystem.service.ts | 13 ++- .../interfaces/invitations.interface.ts | 9 ++ apps/ecosystem/src/ecosystem.controller.ts | 11 +++ apps/ecosystem/src/ecosystem.repository.ts | 91 +++++++++++-------- apps/ecosystem/src/ecosystem.service.ts | 19 +++- 7 files changed, 168 insertions(+), 39 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/get-all-sent-invitations.dto.ts create mode 100644 apps/ecosystem/interfaces/invitations.interface.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/get-all-sent-invitations.dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-all-sent-invitations.dto.ts new file mode 100644 index 000000000..23c7fecd6 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/get-all-sent-invitations.dto.ts @@ -0,0 +1,25 @@ +import { Transform, Type } from 'class-transformer'; +import { toNumber } from '@credebl/common/cast.helper'; + +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional } from 'class-validator'; + +export class GetAllEcosystemInvitationsDto { + @ApiProperty({ required: false, default: 1 }) + @IsOptional() + @Type(() => Number) + @Transform(({ value }) => toNumber(value)) + pageNumber = 1; + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => String) + search = ''; + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => Number) + @Transform(({ value }) => toNumber(value)) + pageSize = 10; + +} diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 30e932120..b6c132007 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -21,6 +21,7 @@ import { User } from '../authz/decorators/user.decorator'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { user } from '@prisma/client'; +import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.dto'; @UseFilters(CustomExceptionFilter) @@ -133,6 +134,43 @@ export class EcosystemController { } + @Get('/:ecosystemId/invitations') + @ApiOperation({ summary: 'Get all sent invitations', description: 'Get all sent invitations' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getInvitationsByEcosystemId( + @Param('ecosystemId') ecosystemId: string, + @Query() getAllInvitationsDto: GetAllEcosystemInvitationsDto, + @User() user: user, + @Res() res: Response): Promise { + + const getInvitationById = await this.ecosystemService.getInvitationsByEcosystemId(ecosystemId, getAllInvitationsDto, String(user.id)); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.organisation.success.getInvitation, + data: getInvitationById.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + + } + @Put('/:ecosystemId/') @ApiOperation({ summary: 'Edit ecosystem', description: 'Edit existing ecosystem' }) @@ -147,4 +185,5 @@ export class EcosystemController { }; return res.status(HttpStatus.CREATED).json(finalResponse); } + } \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index cfb0177d4..616a287d5 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -2,8 +2,9 @@ import { Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; -import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; +import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.dto'; +import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; @Injectable() @@ -52,6 +53,16 @@ export class EcosystemService extends BaseService { const payload = { bulkInvitationDto, userId }; return this.sendNats(this.serviceProxy, 'send-ecosystem-invitation', payload); } + + async getInvitationsByEcosystemId( + ecosystemId: string, + getAllInvitationsDto: GetAllEcosystemInvitationsDto, + userId: string + ): Promise<{ response: object }> { + const { pageNumber, pageSize, search } = getAllInvitationsDto; + const payload = { ecosystemId, pageNumber, pageSize, search, userId }; + return this.sendNats(this.serviceProxy, 'get-sent-invitations-ecosystemId', payload); + } /** diff --git a/apps/ecosystem/interfaces/invitations.interface.ts b/apps/ecosystem/interfaces/invitations.interface.ts new file mode 100644 index 000000000..932e490ea --- /dev/null +++ b/apps/ecosystem/interfaces/invitations.interface.ts @@ -0,0 +1,9 @@ + + +export interface FetchInvitationsPayload { + ecosystemId: string; + userId: string, + pageNumber: number; + pageSize: number; + search: string +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index ae4cf9e79..85943a4ec 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -4,6 +4,7 @@ import { MessagePattern } from '@nestjs/microservices'; import { EcosystemService } from './ecosystem.service'; import { Body } from '@nestjs/common'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; +import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; @Controller() export class EcosystemController { @@ -69,5 +70,15 @@ export class EcosystemController { ): Promise { return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId); } + + + @MessagePattern({ cmd: 'get-sent-invitations-ecosystemId' }) + async getInvitationsByOrgId( + @Body() payload: FetchInvitationsPayload + ): Promise { + return this.ecosystemService.getInvitationsByEcosystemId( + payload + ); + } } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 4044e8dc8..2f0888013 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -113,41 +113,6 @@ export class EcosystemRepository { } } - async getEcosystemInvitationsPagination(queryObject: object, status: string, pageNumber: number, pageSize: number): Promise { - try { - const result = await this.prisma.$transaction([ - this.prisma.ecosystem_invitations.findMany({ - where: { - ...queryObject, - status - }, - include: { - ecosystem: true - }, - take: pageSize, - skip: (pageNumber - 1) * pageSize, - orderBy: { - createDateTime: 'desc' - } - }), - this.prisma.ecosystem_invitations.count({ - where: { - ...queryObject - } - }) - ]); - - const [invitations, totalCount] = result; - const totalPages = Math.ceil(totalCount / pageSize); - - return { totalPages, invitations }; - - } catch (error) { - this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); - } - } - /** * * @param ecosystemId @@ -190,7 +155,6 @@ export class EcosystemRepository { } } - /** * * @param email @@ -219,5 +183,60 @@ export class EcosystemRepository { throw new InternalServerErrorException(error); } } + + async getInvitationsByEcosystemId(ecosystemId: string, pageNumber: number, pageSize: number, userId: string, search = ''): Promise { + try { + const query = { + ecosystemId, + userId, + OR: [ + { email: { contains: search, mode: 'insensitive' } }, + { status: { contains: search, mode: 'insensitive' } } + ] + }; + + return await this.getEcosystemInvitationsPagination(query, pageNumber, pageSize); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + + + async getEcosystemInvitationsPagination(queryObject: object, pageNumber: number, pageSize: number): Promise { + try { + const result = await this.prisma.$transaction([ + this.prisma.ecosystem_invitations.findMany({ + where: { + ...queryObject + }, + include: { + ecosystem: true + }, + take: pageSize, + skip: (pageNumber - 1) * pageSize, + orderBy: { + createDateTime: 'desc' + } + }), + this.prisma.ecosystem_invitations.count({ + where: { + ...queryObject + } + }) + ]); + + // eslint-disable-next-line prefer-destructuring + const invitations = result[0]; + // eslint-disable-next-line prefer-destructuring + const totalCount = result[1]; + const totalPages = Math.ceil(totalCount / pageSize); + + return { totalPages, invitations }; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 2e31b5437..98c19f675 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -1,13 +1,14 @@ // eslint-disable-next-line camelcase +import { Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; import { EcosystemRepository } from './ecosystem.repository'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; import { RpcException } from '@nestjs/microservices'; import { PrismaService } from '@credebl/prisma-service'; import { EcosystemInviteTemplate } from '../templates/EcosystemInviteTemplate'; import { EmailDto } from '@credebl/common/dtos/email.dto'; import { sendEmail } from '@credebl/common/send-grid-helper-file'; +import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; @Injectable() export class EcosystemService { @@ -64,6 +65,7 @@ export class EcosystemService { return getAllEcosystemDetails; } + /** * Description: get an ecosystem invitation * @returns Get sent ecosystem invitation details @@ -80,7 +82,7 @@ export class EcosystemService { ] }; - return await this.ecosystemRepository.getEcosystemInvitationsPagination(query, status, pageNumber, pageSize); + return await this.ecosystemRepository.getEcosystemInvitationsPagination(query, pageNumber, pageSize); } catch (error) { this.logger.error(`In error getEcosystemInvitations: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); @@ -176,4 +178,17 @@ export class EcosystemService { return isEmailSent; } + async getInvitationsByEcosystemId( + payload: FetchInvitationsPayload + ): Promise { + try { + + const { ecosystemId, userId, pageNumber, pageSize, search} = payload; + const ecosystemInvitations = await this.ecosystemRepository.getInvitationsByEcosystemId(ecosystemId, pageNumber, pageSize, userId, search); + return ecosystemInvitations; + } catch (error) { + this.logger.error(`In getInvitationsByEcosystemId : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } } From b5006a62d4b1fdef9ae4c786c8c095085d2724f1 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Fri, 6 Oct 2023 17:13:31 +0530 Subject: [PATCH 106/162] feat: accept or reject ecosystem invitations Signed-off-by: bhavanakarwade --- .../accept-reject-ecosysteminvitation-dto.ts | 22 +++++ .../src/ecosystem/ecosystem.controller.ts | 32 ++++++- .../src/ecosystem/ecosystem.service.ts | 9 ++ .../accept-reject-ecosysteminvitation.dto.ts | 7 ++ .../dtos/update-ecosystem-invitation.dto.ts | 8 ++ .../dtos/update-ecosystemOrgs.dto.ts | 6 ++ apps/ecosystem/src/ecosystem.controller.ts | 15 +++- apps/ecosystem/src/ecosystem.repository.ts | 86 ++++++++++++++++++- apps/ecosystem/src/ecosystem.service.ts | 75 ++++++++++++++++ libs/common/src/response-messages/index.ts | 9 +- 10 files changed, 262 insertions(+), 7 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/accept-reject-ecosysteminvitation-dto.ts create mode 100644 apps/ecosystem/dtos/accept-reject-ecosysteminvitation.dto.ts create mode 100644 apps/ecosystem/dtos/update-ecosystem-invitation.dto.ts create mode 100644 apps/ecosystem/dtos/update-ecosystemOrgs.dto.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/accept-reject-ecosysteminvitation-dto.ts b/apps/api-gateway/src/ecosystem/dtos/accept-reject-ecosysteminvitation-dto.ts new file mode 100644 index 000000000..63cdfae41 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/accept-reject-ecosysteminvitation-dto.ts @@ -0,0 +1,22 @@ +import { IsEnum, IsNotEmpty} from 'class-validator'; + +import { ApiProperty } from '@nestjs/swagger'; +import { Invitation } from '@credebl/enum/enum'; +import { Transform } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; + +export class AcceptRejectEcosystemInvitationDto { + + ecosystemId: string; + invitationId: string; + orgId: string; + + @ApiProperty({ + enum: [Invitation.ACCEPTED, Invitation.REJECTED] + }) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Please provide valid status' }) + @IsEnum(Invitation) + status: Invitation.ACCEPTED | Invitation.REJECTED; + +} \ 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 30e932120..b3aa3a842 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -21,6 +21,7 @@ import { User } from '../authz/decorators/user.decorator'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { user } from '@prisma/client'; +import { AcceptRejectEcosystemInvitationDto } from './dtos/accept-reject-ecosysteminvitation-dto'; @UseFilters(CustomExceptionFilter) @@ -50,7 +51,7 @@ export class EcosystemController { } @Get('/users/invitations') - @ApiOperation({ summary: 'Get an ecosystem invitations', description: 'Get an ecosystem invitations' }) + @ApiOperation({ summary: 'Get received ecosystem invitations', description: 'Get received ecosystem invitations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() @@ -134,6 +135,35 @@ export class EcosystemController { } + /** + * + * @param acceptRejectEcosystemInvitation + * @param reqUser + * @param res + * @returns Ecosystem invitation status + */ + @Post('/:ecosytemId/:orgId/invitations/:invitationId') + @ApiOperation({ + summary: 'Accept or reject ecosystem invitation', + description: 'Accept or Reject ecosystem invitations' + }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async acceptRejectEcosystemInvitaion(@Body() acceptRejectEcosystemInvitation: AcceptRejectEcosystemInvitationDto, @Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: string, @Param('invitationId') invitationId: string, @User() user: user, @Res() res: Response): Promise { + acceptRejectEcosystemInvitation.ecosystemId = ecosystemId; + acceptRejectEcosystemInvitation.orgId = orgId; + acceptRejectEcosystemInvitation.invitationId = invitationId; + + const invitationRes = await this.ecosystemService.acceptRejectEcosystemInvitaion(acceptRejectEcosystemInvitation, user.email); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: invitationRes.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + @Put('/:ecosystemId/') @ApiOperation({ summary: 'Edit ecosystem', description: 'Edit existing ecosystem' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index cfb0177d4..59cf48d32 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -4,6 +4,7 @@ import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; +import { AcceptRejectEcosystemInvitationDto } from './dtos/accept-reject-ecosysteminvitation-dto'; @Injectable() @@ -67,5 +68,13 @@ export class EcosystemService extends BaseService { const payload = { userEmail, status, pageNumber, pageSize, search }; return this.sendNats(this.serviceProxy, 'get-ecosystem-invitations', payload); } + + async acceptRejectEcosystemInvitaion( + acceptRejectInvitation: AcceptRejectEcosystemInvitationDto, + userEmail: string + ): Promise<{ response: string }> { + const payload = { acceptRejectInvitation, userEmail }; + return this.sendNats(this.serviceProxy, 'accept-reject-ecosystem-invitations', payload); + } } diff --git a/apps/ecosystem/dtos/accept-reject-ecosysteminvitation.dto.ts b/apps/ecosystem/dtos/accept-reject-ecosysteminvitation.dto.ts new file mode 100644 index 000000000..0fbfcdaa7 --- /dev/null +++ b/apps/ecosystem/dtos/accept-reject-ecosysteminvitation.dto.ts @@ -0,0 +1,7 @@ +import { Invitation } from '@credebl/enum/enum'; + +export class AcceptRejectEcosystemInvitationDto { + orgId: string; + invitationId: string; + status: Invitation; +} diff --git a/apps/ecosystem/dtos/update-ecosystem-invitation.dto.ts b/apps/ecosystem/dtos/update-ecosystem-invitation.dto.ts new file mode 100644 index 000000000..0badcf828 --- /dev/null +++ b/apps/ecosystem/dtos/update-ecosystem-invitation.dto.ts @@ -0,0 +1,8 @@ +export class updateEcosystemInvitationDto { + invitationId: string; + orgId: string; + status: string; + userId: string; + email: string; + roleId: string; +} \ No newline at end of file diff --git a/apps/ecosystem/dtos/update-ecosystemOrgs.dto.ts b/apps/ecosystem/dtos/update-ecosystemOrgs.dto.ts new file mode 100644 index 000000000..7f3e27a8b --- /dev/null +++ b/apps/ecosystem/dtos/update-ecosystemOrgs.dto.ts @@ -0,0 +1,6 @@ +export class updateEcosystemOrgsDto { + orgId: string; + status: string; + ecosystemId: string; + ecosystemRoleId: string; +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index ae4cf9e79..1202951ca 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -4,6 +4,7 @@ import { MessagePattern } from '@nestjs/microservices'; import { EcosystemService } from './ecosystem.service'; import { Body } from '@nestjs/common'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; +import { AcceptRejectEcosystemInvitationDto } from '../dtos/accept-reject-ecosysteminvitation.dto'; @Controller() export class EcosystemController { @@ -69,5 +70,17 @@ export class EcosystemController { ): Promise { return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId); } - + + /** + * + * @param payload + * @returns Ecosystem invitation status fetch-ecosystem-users + */ + @MessagePattern({ cmd: 'accept-reject-ecosystem-invitations' }) + async acceptRejectInvitations(payload: { + acceptRejectInvitation: AcceptRejectEcosystemInvitationDto; + }): Promise { + return this.ecosystemService.acceptRejectEcosystemInvitations(payload.acceptRejectInvitation); + } + } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 4044e8dc8..6d67acf6e 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -1,9 +1,11 @@ import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase -import { ecosystem, ecosystem_invitations } from '@prisma/client'; +import { ecosystem, ecosystem_invitations, ecosystem_orgs, ecosystem_roles } from '@prisma/client'; import {EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles} from '../enums/ecosystem.enum'; +import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; // eslint-disable-next-line camelcase + @Injectable() export class EcosystemRepository { @@ -71,7 +73,7 @@ export class EcosystemRepository { } } - /** + /** * Description: Edit ecosystem by Id * @param editEcosystemDto * @returns ecosystem details @@ -96,7 +98,7 @@ export class EcosystemRepository { } } - /** + /** * * * @returns Get all ecosystem details @@ -191,6 +193,84 @@ export class EcosystemRepository { } + /** + * + * @param id + * @returns Invitation details + */ + // eslint-disable-next-line camelcase + async getEcosystemInvitationById(id: string): Promise { + try { + return this.prisma.ecosystem_invitations.findUnique({ + where: { + id + }, + include: { + ecosystem: true + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + + /** + * + * @param queryObject + * @param data + * @returns Updated ecosystem invitation response + */ + async updateEcosystemInvitation(id: string, data: object): Promise { + try { + return this.prisma.ecosystem_invitations.update({ + where: { + id: String(id) + }, + data: { + ...data + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException('Unable to update ecosystem invitation'); + } + } + + // eslint-disable-next-line camelcase + async getEcosystemRole(name: string): Promise { + try { + return this.prisma.ecosystem_roles.findFirst({ + where: { + name + } + }); + } catch (error) { + this.logger.error(`getEcosystemRole: ${JSON.stringify(error)}`); + throw error; + } + } + + // eslint-disable-next-line camelcase + async updateEcosystemOrgs(createEcosystemOrgsDto: updateEcosystemOrgsDto): Promise { + try { + const { orgId, status, ecosystemRoleId, ecosystemId } = createEcosystemOrgsDto; + + return this.prisma.ecosystem_orgs.create({ + data: { + orgId: String(orgId), + ecosystemId, + status, + ecosystemRoleId + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException('Unable to update ecosystem orgs'); + } + } + + /** * * @param email diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 2e31b5437..2e1cedb27 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -8,6 +8,9 @@ import { PrismaService } from '@credebl/prisma-service'; import { EcosystemInviteTemplate } from '../templates/EcosystemInviteTemplate'; 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 { Invitation } from '@credebl/enum/enum'; +import { EcosystemOrgStatus, EcosystemRoles } from '../enums/ecosystem.enum'; @Injectable() export class EcosystemService { @@ -121,6 +124,78 @@ export class EcosystemService { } } + /** + * + * @param acceptRejectEcosystemInvitation + * @param userId + * @returns Ecosystem invitation status + */ + async acceptRejectEcosystemInvitations(acceptRejectInvitation: AcceptRejectEcosystemInvitationDto): Promise { + try { + const { orgId, status, invitationId } = acceptRejectInvitation; + const invitation = await this.ecosystemRepository.getEcosystemInvitationById(invitationId); + + if (!invitation) { + throw new NotFoundException(ResponseMessages.ecosystem.error.invitationNotFound); + } + + const updatedInvitation = await this.updateEcosystemInvitation(invitationId, orgId, status); + if (!updatedInvitation) { + throw new NotFoundException(ResponseMessages.ecosystem.error.invitationNotUpdate); + } + + if (status === Invitation.REJECTED) { + return ResponseMessages.ecosystem.success.invitationReject; + } + + const ecosystemRole = await this.ecosystemRepository.getEcosystemRole(EcosystemRoles.ECOSYSTEM_MEMBER); + const updateEcosystemOrgs = await this.updatedEcosystemOrgs(orgId, invitation.ecosystemId, ecosystemRole.id); + + if (!updateEcosystemOrgs) { + throw new NotFoundException(ResponseMessages.ecosystem.error.orgsNotUpdate); + } + return ResponseMessages.ecosystem.success.invitationAccept; + + } catch (error) { + this.logger.error(`acceptRejectInvitations: ${error}`); + throw new RpcException(error.response ? error.response : error); + } + } + + async updatedEcosystemOrgs(orgId: string, ecosystemId: string, ecosystemRoleId: string): Promise { + try { + const data = { + orgId, + status: EcosystemOrgStatus.ACTIVE, + ecosystemId, + ecosystemRoleId + }; + return this.ecosystemRepository.updateEcosystemOrgs(data); + } catch (error) { + this.logger.error(`In newEcosystemMneber : ${error}`); + throw new RpcException(error.response ? error.response : error); + } + } + + /** + * + * @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); + } + } /** * diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 7af055646..d14721093 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -188,12 +188,17 @@ export const ResponseMessages = { update: 'Ecosystem updated successfully', fetch: 'Ecosystem fetched successfully', getInvitation: 'Ecosystem invitations fetched successfully', - createInvitation: 'Ecosystem invitations sent successfully' + createInvitation: 'Ecosystem invitations sent successfully', + invitationReject: 'Ecosystem invitation rejected', + invitationAccept: 'Ecosystem invitation accepted successfully' }, error: { notCreated: 'Error while creating ecosystem', update: 'Error while updating ecosystem', - invalidInvitationStatus: 'Invalid invitation status' + invalidInvitationStatus: 'Invalid invitation status', + invitationNotFound: 'Ecosystem Invitation not found', + invitationNotUpdate: 'Ecosystem Invitation not updated', + orgsNotUpdate: 'Ecosystem Orgs not updated' } } }; \ No newline at end of file From 0737d197492b33e2bc438a15e0af611766546739 Mon Sep 17 00:00:00 2001 From: Nishad Date: Fri, 6 Oct 2023 18:52:11 +0530 Subject: [PATCH 107/162] worked on the ecosystem role guard and used in invitation sent APIs Signed-off-by: Nishad --- apps/api-gateway/common/exception-handler.ts | 7 +- apps/api-gateway/src/authz/authz.module.ts | 4 +- .../src/authz/decorators/roles.decorator.ts | 3 + .../src/authz/guards/ecosystem-roles.guard.ts | 64 ++++++++ .../ecosystem/dtos/create-ecosystem-dto.ts | 6 +- .../src/ecosystem/ecosystem.controller.ts | 137 +++++++++++------- .../src/ecosystem/ecosystem.service.ts | 14 +- apps/ecosystem/src/ecosystem.controller.ts | 17 ++- apps/ecosystem/src/ecosystem.repository.ts | 41 ++++-- apps/ecosystem/src/ecosystem.service.ts | 42 +++++- libs/common/src/response-messages/index.ts | 3 +- libs/enum/src/enum.ts | 6 + 12 files changed, 262 insertions(+), 82 deletions(-) create mode 100644 apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index 38259e86e..27a78f391 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -14,6 +14,7 @@ export class CustomExceptionFilter extends BaseExceptionFilter { } 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'; } @@ -25,9 +26,11 @@ export class CustomExceptionFilter extends BaseExceptionFilter { 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) { + } 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: exception["error"].statusCode ? exception["error"].statusCode : status, + statusCode, message: exception["error"].message || 'Internal server error', error: exception["error"].message || 'Internal server error' }; diff --git a/apps/api-gateway/src/authz/authz.module.ts b/apps/api-gateway/src/authz/authz.module.ts index 1e6b80cd2..3c9448584 100644 --- a/apps/api-gateway/src/authz/authz.module.ts +++ b/apps/api-gateway/src/authz/authz.module.ts @@ -16,6 +16,7 @@ import { SupabaseService } from '@credebl/supabase'; import { UserModule } from '../user/user.module'; import { UserService } from '../user/user.service'; import { VerificationService } from '../verification/verification.service'; +import { EcosystemService } from '../ecosystem/ecosystem.service'; //import { WebhookService } from "../../../platform-service/src/webhook/webhook.service"; @@ -48,7 +49,8 @@ import { VerificationService } from '../verification/verification.service'; AgentService, CommonService, UserService, - SupabaseService + SupabaseService, + EcosystemService ], exports: [ PassportModule, diff --git a/apps/api-gateway/src/authz/decorators/roles.decorator.ts b/apps/api-gateway/src/authz/decorators/roles.decorator.ts index a172bdaee..5fd3237ab 100644 --- a/apps/api-gateway/src/authz/decorators/roles.decorator.ts +++ b/apps/api-gateway/src/authz/decorators/roles.decorator.ts @@ -1,9 +1,12 @@ import { CustomDecorator } from '@nestjs/common'; import { OrgRoles } from 'libs/org-roles/enums'; import { SetMetadata } from '@nestjs/common'; +import { EcosystemRoles } from '@credebl/enum/enum'; export const ROLES_KEY = 'roles'; +export const ECOSYSTEM_ROLES_KEY = 'ecosystem_roles'; export const Roles = (...roles: OrgRoles[]): CustomDecorator => SetMetadata(ROLES_KEY, roles); +export const EcosystemsRoles = (...roles: EcosystemRoles[]): CustomDecorator => SetMetadata(ECOSYSTEM_ROLES_KEY, roles); export const Permissions = (...permissions: string[]): CustomDecorator => SetMetadata('permissions', permissions); export const Subscriptions = (...subscriptions: string[]): CustomDecorator => SetMetadata('subscriptions', subscriptions); diff --git a/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts b/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts new file mode 100644 index 000000000..325c9893b --- /dev/null +++ b/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts @@ -0,0 +1,64 @@ +import { CanActivate, ExecutionContext, Logger } from '@nestjs/common'; + +import { HttpException } from '@nestjs/common'; +import { HttpStatus } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; +import { ECOSYSTEM_ROLES_KEY } from '../decorators/roles.decorator'; +import { Reflector } from '@nestjs/core'; +import { EcosystemService } from '../../ecosystem/ecosystem.service'; +import { EcosystemRoles } from '@credebl/enum/enum'; + +@Injectable() +export class EcosystemRolesGuard implements CanActivate { + constructor( + private reflector: Reflector, + private readonly ecosystemService: EcosystemService // Inject the service + ) { } + + + private logger = new Logger('Ecosystem Role Guard'); + async canActivate(context: ExecutionContext): Promise { + const requiredRoles = this.reflector.getAllAndOverride(ECOSYSTEM_ROLES_KEY, [ + context.getHandler(), + context.getClass() + ]); + const requiredRolesNames = Object.values(requiredRoles) as string[]; + + if (!requiredRolesNames) { + return true; + } + + // Request requires org check, proceed with it + const req = context.switchToHttp().getRequest(); + + const { user } = req; + + if ((req.params.orgId || req.query.orgId || req.body.orgId) + && (req.params.ecosystemId || req.query.ecosystemId || req.body.ecosystemId)) { + + const orgId = req.params.orgId || req.query.orgId || req.body.orgId; + const ecosystemId = req.params.ecosystemId || req.query.ecosystemId || req.body.ecosystemId; + + + const ecosystemOrgData = await this.ecosystemService.fetchEcosystemOrg(ecosystemId, orgId); + + if (!ecosystemOrgData) { + throw new HttpException('Organization does not match', HttpStatus.FORBIDDEN); + } + + const {response} = ecosystemOrgData; + + user.ecosystemOrgRole = response['ecosystemRole']['name']; + + if (!user.ecosystemOrgRole) { + throw new HttpException('Ecosystem role not match', HttpStatus.FORBIDDEN); + } + + } else { + throw new HttpException('organization & ecosystem is required', HttpStatus.BAD_REQUEST); + } + + return requiredRoles.some((role) => user.ecosystemOrgRole === role); + + } +} \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts b/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts index 2b66e7ee0..b8915ca81 100644 --- a/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts +++ b/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts @@ -29,10 +29,6 @@ export class CreateEcosystemDto { @IsString({ message: 'tag must be in string format.' }) tags?: string; - @ApiPropertyOptional() - @IsInt({ message: 'orgId must be in number format.' }) - orgId: number; - @ApiPropertyOptional() @IsInt({ message: 'UserId must be in number format.' }) userId: number; @@ -42,5 +38,7 @@ export class CreateEcosystemDto { @Transform(({ value }) => trim(value)) @IsString({ message: 'logo must be in string format.' }) logo?: string; + + orgId?: 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 b6c132007..940dd24ed 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -16,12 +16,16 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler import { EditEcosystemDto } from './dtos/edit-ecosystem-dto'; import { AuthGuard } from '@nestjs/passport'; import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; -import { Invitation } from '@credebl/enum/enum'; +import { EcosystemRoles, Invitation } from '@credebl/enum/enum'; import { User } from '../authz/decorators/user.decorator'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { user } from '@prisma/client'; import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.dto'; +import { EcosystemRolesGuard } from '../authz/guards/ecosystem-roles.guard'; +import { EcosystemsRoles, Roles } from '../authz/decorators/roles.decorator'; +import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; +import { OrgRoles } from 'libs/org-roles/enums'; @UseFilters(CustomExceptionFilter) @@ -35,13 +39,17 @@ export class EcosystemController { ) { } - @Get('/') + @Get('/:orgId') @ApiOperation({ summary: 'Get all ecosystem', description: 'Get all existing ecosystem' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @ApiBearerAuth() - async getEcosystem(@Res() res: Response): Promise { - const ecosystemList = await this.ecosystemService.getAllEcosystem(); + async getEcosystem( + @Param('orgId') orgId: string, + @Res() res: Response + ): Promise { + const ecosystemList = await this.ecosystemService.getAllEcosystem(orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.ecosystem.success.fetch, @@ -53,7 +61,8 @@ export class EcosystemController { @Get('/users/invitations') @ApiOperation({ summary: 'Get an ecosystem invitations', description: 'Get an ecosystem invitations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @ApiBearerAuth() @ApiQuery({ name: 'pageNumber', @@ -89,12 +98,63 @@ export class EcosystemController { } - @Post('/') + @Get('/:ecosystemId/:orgId/invitations') + @ApiOperation({ summary: 'Get all sent invitations', description: 'Get all sent invitations' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getInvitationsByEcosystemId( + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @Query() getAllInvitationsDto: GetAllEcosystemInvitationsDto, + @User() user: user, + @Res() res: Response): Promise { + + const getInvitationById = await this.ecosystemService.getInvitationsByEcosystemId(ecosystemId, getAllInvitationsDto, String(user.id)); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.organisation.success.getInvitation, + data: getInvitationById.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + + } + + /** + * + * @param createOrgDto + * @param res + * @returns Ecosystem create response + */ + @Post('/:orgId') @ApiOperation({ summary: 'Create a new ecosystem', description: 'Create an ecosystem' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() - async createNewEcosystem(@Body() createOrgDto: CreateEcosystemDto, @Res() res: Response): Promise { + @Roles(OrgRoles.OWNER) + async createNewEcosystem( + @Body() createOrgDto: CreateEcosystemDto, + @Param('orgId') orgId: string, + @Res() res: Response): Promise { + createOrgDto.orgId = orgId; await this.ecosystemService.createEcosystem(createOrgDto); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -112,15 +172,20 @@ export class EcosystemController { * @param res * @returns Ecosystem invitation send details */ - @Post('/:ecosystemId/invitations') + @Post('/:ecosystemId/:orgId/invitations') @ApiOperation({ summary: 'Send ecosystem invitation', description: 'Send ecosystem invitation' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) @ApiBearerAuth() - async createInvitation(@Body() bulkInvitationDto: BulkEcosystemInvitationDto, @Param('ecosystemId') ecosystemId: string, @User() user: user, @Res() res: Response): Promise { + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + async createInvitation(@Body() bulkInvitationDto: BulkEcosystemInvitationDto, + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @User() user: user, @Res() res: Response): Promise { bulkInvitationDto.ecosystemId = ecosystemId; await this.ecosystemService.createInvitation(bulkInvitationDto, String(user.id)); @@ -134,50 +199,18 @@ export class EcosystemController { } - @Get('/:ecosystemId/invitations') - @ApiOperation({ summary: 'Get all sent invitations', description: 'Get all sent invitations' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiQuery({ - name: 'pageNumber', - type: Number, - required: false - }) - @ApiQuery({ - name: 'pageSize', - type: Number, - required: false - }) - @ApiQuery({ - name: 'search', - type: String, - required: false - }) - async getInvitationsByEcosystemId( - @Param('ecosystemId') ecosystemId: string, - @Query() getAllInvitationsDto: GetAllEcosystemInvitationsDto, - @User() user: user, - @Res() res: Response): Promise { - - const getInvitationById = await this.ecosystemService.getInvitationsByEcosystemId(ecosystemId, getAllInvitationsDto, String(user.id)); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.organisation.success.getInvitation, - data: getInvitationById.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - - } - - - @Put('/:ecosystemId/') + @Put('/:ecosystemId/:orgId') @ApiOperation({ summary: 'Edit ecosystem', description: 'Edit existing ecosystem' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async editEcosystem(@Body() editEcosystemDto: EditEcosystemDto, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER) + async editEcosystem( + @Body() editEcosystemDto: EditEcosystemDto, + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @Res() res: Response): Promise { await this.ecosystemService.editEcosystem(editEcosystemDto, ecosystemId); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 616a287d5..a87694943 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -38,8 +38,9 @@ export class EcosystemService extends BaseService { * * @returns Get all ecosystems */ - async getAllEcosystem(): Promise<{ response: object }> { - return this.sendNats(this.serviceProxy, 'get-all-ecosystem', ''); + async getAllEcosystem(orgId: string): Promise<{ response: object }> { + const payload = { orgId }; + return this.sendNats(this.serviceProxy, 'get-all-ecosystem', payload); } @@ -78,5 +79,14 @@ export class EcosystemService extends BaseService { const payload = { userEmail, status, pageNumber, pageSize, search }; return this.sendNats(this.serviceProxy, 'get-ecosystem-invitations', payload); } + + async fetchEcosystemOrg( + ecosystemId: string, + orgId: string + ): Promise<{ response: object }> { + const payload = { ecosystemId, orgId }; + return this.sendNats(this.serviceProxy, 'fetch-ecosystem-org-data', payload); + } + } diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 85943a4ec..cdb461cec 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -38,8 +38,10 @@ export class EcosystemController { * @returns Get all ecosystem details */ @MessagePattern({ cmd: 'get-all-ecosystem' }) - async getAllEcosystems(): Promise { - return this.ecosystemService.getAllEcosystem(); + async getAllEcosystems( + @Body() payload: {orgId: string} + ): Promise { + return this.ecosystemService.getAllEcosystem(payload); } /** @@ -71,7 +73,7 @@ export class EcosystemController { return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId); } - + @MessagePattern({ cmd: 'get-sent-invitations-ecosystemId' }) async getInvitationsByOrgId( @Body() payload: FetchInvitationsPayload @@ -80,5 +82,14 @@ export class EcosystemController { payload ); } + + @MessagePattern({ cmd: 'fetch-ecosystem-org-data' }) + async fetchEcosystemOrg( + @Body() payload: { ecosystemId: string, orgId: string} + ): Promise { + return this.ecosystemService.fetchEcosystemOrg( + payload + ); + } } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 2f0888013..4bd16d4e4 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -27,9 +27,7 @@ export class EcosystemRepository { name, description, tags, - logoUrl: logo, - createdBy: orgId, - lastChangedBy: orgId + logoUrl: logo } }); let ecosystemUser; @@ -37,9 +35,7 @@ export class EcosystemRepository { ecosystemUser = await prisma.ecosystem_users.create({ data: { userId: String(userId), - ecosystemId: createdEcosystem.id, - createdBy: orgId, - lastChangedBy: orgId + ecosystemId: createdEcosystem.id } }); } @@ -55,9 +51,7 @@ export class EcosystemRepository { orgId: String(orgId), status: EcosystemOrgStatus.ACTIVE, ecosystemId: createdEcosystem.id, - ecosystemRoleId: ecosystemRoleDetails.id, - createdBy: orgId, - lastChangedBy: orgId + ecosystemRoleId: ecosystemRoleDetails.id } }); } @@ -102,9 +96,16 @@ export class EcosystemRepository { * @returns Get all ecosystem details */ // eslint-disable-next-line camelcase - async getAllEcosystemDetails(): Promise { + async getAllEcosystemDetails(orgId: string): Promise { try { const ecosystemDetails = await this.prisma.ecosystem.findMany({ + where: { + ecosystemOrgs:{ + some: { + orgId + } + } + } }); return ecosystemDetails; } catch (error) { @@ -184,11 +185,10 @@ export class EcosystemRepository { } } - async getInvitationsByEcosystemId(ecosystemId: string, pageNumber: number, pageSize: number, userId: string, search = ''): Promise { + async getInvitationsByEcosystemId(ecosystemId: string, pageNumber: number, pageSize: number, search = ''): Promise { try { const query = { ecosystemId, - userId, OR: [ { email: { contains: search, mode: 'insensitive' } }, { status: { contains: search, mode: 'insensitive' } } @@ -238,5 +238,22 @@ export class EcosystemRepository { throw new InternalServerErrorException(error); } } + + + async fetchEcosystemOrg( + payload: { ecosystemId: string, orgId: string } + ): Promise { + + return this.prisma.ecosystem_orgs.findFirst({ + where: { + ...payload + }, + select:{ + ecosystemRole: true + } + }); + + } + } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 98c19f675..d97d9ec9e 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -1,5 +1,5 @@ // eslint-disable-next-line camelcase -import { Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; +import { ForbiddenException, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; import { EcosystemRepository } from './ecosystem.repository'; import { ResponseMessages } from '@credebl/common/response-messages'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; @@ -57,8 +57,8 @@ export class EcosystemService { */ // eslint-disable-next-line camelcase - async getAllEcosystem(): Promise { - const getAllEcosystemDetails = await this.ecosystemRepository.getAllEcosystemDetails(); + async getAllEcosystem(payload: {orgId: string}): Promise { + const getAllEcosystemDetails = await this.ecosystemRepository.getAllEcosystemDetails(payload.orgId); if (!getAllEcosystemDetails) { throw new NotFoundException(ResponseMessages.ecosystem.error.update); } @@ -183,12 +183,44 @@ export class EcosystemService { ): Promise { try { - const { ecosystemId, userId, pageNumber, pageSize, search} = payload; - const ecosystemInvitations = await this.ecosystemRepository.getInvitationsByEcosystemId(ecosystemId, pageNumber, pageSize, userId, search); + const { ecosystemId, pageNumber, pageSize, search} = payload; + const ecosystemInvitations = await this.ecosystemRepository.getInvitationsByEcosystemId(ecosystemId, pageNumber, pageSize, search); return ecosystemInvitations; } catch (error) { this.logger.error(`In getInvitationsByEcosystemId : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } } + + /** + * + * @param payload + * @returns + */ + 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 + ); + } + + /** + * + * @returns Returns ecosystem flag from settings + */ + async checkEcosystemEnableFlag( + ): Promise { + const platformConfigData = await this.prisma.platform_config.findMany(); + return platformConfigData[0].enableEcosystem; + } + + } diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 7af055646..26f813a5c 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -193,7 +193,8 @@ export const ResponseMessages = { error: { notCreated: 'Error while creating ecosystem', update: 'Error while updating ecosystem', - invalidInvitationStatus: 'Invalid invitation status' + invalidInvitationStatus: 'Invalid invitation status', + ecosystemNotEnabled: 'Ecosystem service is not enabled' } } }; \ No newline at end of file diff --git a/libs/enum/src/enum.ts b/libs/enum/src/enum.ts index a59e0c81e..94ac465ea 100644 --- a/libs/enum/src/enum.ts +++ b/libs/enum/src/enum.ts @@ -13,6 +13,12 @@ export enum Invitation { PENDING = 'pending' } +export enum EcosystemRoles { + ECOSYSTEM_LEAD = 'Ecosystem Lead', + ECOSYSTEM_MEMBER = 'Ecosystem Member', + ECOSYSTEM_OWNER = 'Ecosystem Owner' +} + export enum OrgAgentType { DEDICATED = 1, SHARED = 2 From 6e573b18979a92f0c62234c047c4683138db6cc5 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Fri, 6 Oct 2023 19:10:57 +0530 Subject: [PATCH 108/162] feat: delete ecosystem invitations Signed-off-by: bhavanakarwade --- .../dtos/delete-ecosystemInvitations-dto.ts | 19 +++++++++++++ .../src/ecosystem/ecosystem.controller.ts | 28 +++++++++++++++---- .../src/ecosystem/ecosystem.service.ts | 7 +++++ apps/ecosystem/src/ecosystem.controller.ts | 7 +++++ libs/common/src/response-messages/index.ts | 1 + 5 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/delete-ecosystemInvitations-dto.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/delete-ecosystemInvitations-dto.ts b/apps/api-gateway/src/ecosystem/dtos/delete-ecosystemInvitations-dto.ts new file mode 100644 index 000000000..2e849232d --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/delete-ecosystemInvitations-dto.ts @@ -0,0 +1,19 @@ +import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; +import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; +import { Transform } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; + +@ApiExtraModels() +export class deleteEcosystemInvitationsDto { + @ApiProperty({ example: 'acqx@getnada.com' }) + @IsEmail() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Please provide valid email' }) + @IsString({ message: 'email should be string' }) + email: string; + + ecosystemId: string; + invitationId: string; + orgId: 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 b6c132007..fca061242 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -1,5 +1,5 @@ import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; -import { Controller, UseFilters, Put, Param, UseGuards, Query, BadRequestException } from '@nestjs/common'; +import { Controller, UseFilters, Put, Param, UseGuards, Query, BadRequestException, Delete } from '@nestjs/common'; import { EcosystemService } from './ecosystem.service'; import { Post, Get } from '@nestjs/common'; import { Body } from '@nestjs/common'; @@ -22,6 +22,7 @@ import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { user } from '@prisma/client'; import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.dto'; +import { deleteEcosystemInvitationsDto } from './dtos/delete-ecosystemInvitations-dto'; @UseFilters(CustomExceptionFilter) @@ -171,19 +172,36 @@ export class EcosystemController { } - @Put('/:ecosystemId/') @ApiOperation({ summary: 'Edit ecosystem', description: 'Edit existing ecosystem' }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() async editEcosystem(@Body() editEcosystemDto: EditEcosystemDto, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { await this.ecosystemService.editEcosystem(editEcosystemDto, ecosystemId); const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, + statusCode: HttpStatus.OK, message: ResponseMessages.ecosystem.success.update }; - return res.status(HttpStatus.CREATED).json(finalResponse); + return res.status(HttpStatus.OK).json(finalResponse); + } + + @Delete('/:ecosystemId/invitatons/:orgId') + @ApiOperation({ summary: 'Delete ecosystem pending invitations', description: 'Delete ecosystem pending invitations' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async deleteEcosystemInvitations(@Body() deleteInvitationDto: deleteEcosystemInvitationsDto, @Param('ecosystemId') invitationId: string, @Param('orgId') orgId: string, ecosystemId: string, @User() user: user, @Res() res: Response): Promise { + deleteInvitationDto.ecosystemId = ecosystemId; + deleteInvitationDto.orgId = orgId; + deleteInvitationDto.invitationId = invitationId; + + await this.ecosystemService.deleteEcosystemInvitations(deleteInvitationDto, user.email); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.delete + }; + return res.status(HttpStatus.OK).json(finalResponse); } } \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 616a287d5..a7a29bf28 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -5,6 +5,7 @@ import { BaseService } from 'libs/service/base.service'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.dto'; import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; +import { deleteEcosystemInvitationsDto } from './dtos/delete-ecosystemInvitations-dto'; @Injectable() @@ -78,5 +79,11 @@ export class EcosystemService extends BaseService { const payload = { userEmail, status, pageNumber, pageSize, search }; return this.sendNats(this.serviceProxy, 'get-ecosystem-invitations', payload); } + + + async deleteEcosystemInvitations(deleteInvitationDto: deleteEcosystemInvitationsDto, userEmail: string): Promise { + const payload = { deleteInvitationDto, userEmail }; + return this.sendNats(this.serviceProxy, 'delete-ecosystem-invitations', payload); +} } diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 85943a4ec..260a64e5a 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -80,5 +80,12 @@ export class EcosystemController { payload ); } + + @MessagePattern({ cmd: 'send-ecosystem-invitation' }) + async deleteInvitation( + @Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: string } + ): Promise { + return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId); + } } diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 7af055646..45627d20a 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -186,6 +186,7 @@ export const ResponseMessages = { success: { create: 'Ecosystem created successfully', update: 'Ecosystem updated successfully', + delete: 'Ecosystem invitations deleted successfully', fetch: 'Ecosystem fetched successfully', getInvitation: 'Ecosystem invitations fetched successfully', createInvitation: 'Ecosystem invitations sent successfully' From 34cb2c2d8caae971795068abf030d90de4f64f86 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Fri, 6 Oct 2023 19:54:05 +0530 Subject: [PATCH 109/162] fix: sonarlint checks Signed-off-by: bhavanakarwade --- apps/ecosystem/src/ecosystem.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index bcbfb1086..00e2293e1 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -172,7 +172,7 @@ export class EcosystemService { ecosystemId, ecosystemRoleId }; - return this.ecosystemRepository.updateEcosystemOrgs(data); + return await this.ecosystemRepository.updateEcosystemOrgs(data); } catch (error) { this.logger.error(`In newEcosystemMneber : ${error}`); throw new RpcException(error.response ? error.response : error); From a7e20622939594cc1d3a4b1576a622ea1257b0a2 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Sat, 7 Oct 2023 14:07:15 +0530 Subject: [PATCH 110/162] feat: delete pending invitations Signed-off-by: bhavanakarwade --- .../src/ecosystem/ecosystem.controller.ts | 17 ++++++++--------- .../src/ecosystem/ecosystem.service.ts | 10 +++++----- apps/ecosystem/src/ecosystem.controller.ts | 13 +++++++------ apps/ecosystem/src/ecosystem.repository.ts | 17 ++++++++++++++++- apps/ecosystem/src/ecosystem.service.ts | 10 ++++++++++ 5 files changed, 46 insertions(+), 21 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index fca061242..3363af3b5 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -22,8 +22,6 @@ import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { user } from '@prisma/client'; import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.dto'; -import { deleteEcosystemInvitationsDto } from './dtos/delete-ecosystemInvitations-dto'; - @UseFilters(CustomExceptionFilter) @Controller('ecosystem') @@ -186,17 +184,18 @@ export class EcosystemController { return res.status(HttpStatus.OK).json(finalResponse); } - @Delete('/:ecosystemId/invitatons/:orgId') + @Delete('/:ecosystemId/:orgId/invitations/:invitationId') @ApiOperation({ summary: 'Delete ecosystem pending invitations', description: 'Delete ecosystem pending invitations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async deleteEcosystemInvitations(@Body() deleteInvitationDto: deleteEcosystemInvitationsDto, @Param('ecosystemId') invitationId: string, @Param('orgId') orgId: string, ecosystemId: string, @User() user: user, @Res() res: Response): Promise { - deleteInvitationDto.ecosystemId = ecosystemId; - deleteInvitationDto.orgId = orgId; - deleteInvitationDto.invitationId = invitationId; - - await this.ecosystemService.deleteEcosystemInvitations(deleteInvitationDto, user.email); + async deleteEcosystemInvitations( + @Param('ecosystemId') ecosystemId: string, + @Param('invitationId') invitationId: string, + @Param('orgId') orgId: string, + @Res() res: Response): Promise { + + await this.ecosystemService.deleteEcosystemInvitations(invitationId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.ecosystem.success.delete diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index a7a29bf28..61dc08150 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -5,8 +5,6 @@ import { BaseService } from 'libs/service/base.service'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.dto'; import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; -import { deleteEcosystemInvitationsDto } from './dtos/delete-ecosystemInvitations-dto'; - @Injectable() export class EcosystemService extends BaseService { @@ -81,9 +79,11 @@ export class EcosystemService extends BaseService { } - async deleteEcosystemInvitations(deleteInvitationDto: deleteEcosystemInvitationsDto, userEmail: string): Promise { - const payload = { deleteInvitationDto, userEmail }; + async deleteEcosystemInvitations( + invitationId: string + ): Promise { + const payload = { invitationId }; return this.sendNats(this.serviceProxy, 'delete-ecosystem-invitations', payload); -} + } } diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 260a64e5a..4089be90b 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -81,11 +81,12 @@ export class EcosystemController { ); } - @MessagePattern({ cmd: 'send-ecosystem-invitation' }) + @MessagePattern({ cmd: 'delete-ecosystem-invitations' }) async deleteInvitation( - @Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: string } - ): Promise { - return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId); - } - + @Body() payload: {invitationId: string} + ): Promise { + return this.ecosystemService.deleteEcosystemInvitations( + payload.invitationId + ); + } } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 2f0888013..acca8ae07 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -1,4 +1,4 @@ -import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; +import { Injectable, InternalServerErrorException, Logger} from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase import { ecosystem, ecosystem_invitations } from '@prisma/client'; @@ -239,4 +239,19 @@ export class EcosystemRepository { } } + // eslint-disable-next-line camelcase + async deleteInvitations (invitationId: string): Promise { + try { + const deletedInvitation = await this.prisma.ecosystem_invitations.delete({ + where: { + id: invitationId, + status: EcosystemInvitationStatus.PENDING + } + }); + return deletedInvitation; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 98c19f675..fe0acd35a 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -191,4 +191,14 @@ export class EcosystemService { throw new RpcException(error.response ? error.response : error); } } + + 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); + } + } } From df9a6651cedfaffb656f1cedda6318028c0ba633 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Sat, 7 Oct 2023 14:31:38 +0530 Subject: [PATCH 111/162] feat: request schema and sign transaction Signed-off-by: tipusinghaw --- .../src/agent-service.controller.ts | 10 + .../src/agent-service.service.ts | 26 + .../ecosystem/dtos/create-organization-dto.ts | 36 -- .../src/ecosystem/dtos/request-schema-dto.ts | 47 ++ .../src/ecosystem/ecosystem.controller.ts | 30 +- .../src/ecosystem/ecosystem.service.ts | 11 + apps/ecosystem/enums/ecosystem.enum.ts | 7 + .../interfaces/ecosystem.interfaces.ts | 51 ++ apps/ecosystem/src/ecosystem.controller.ts | 24 + apps/ecosystem/src/ecosystem.repository.ts | 522 +++++++++++------- apps/ecosystem/src/ecosystem.service.ts | 188 ++++++- libs/common/src/response-messages/index.ts | 4 +- .../migration.sql | 15 + libs/prisma-service/prisma/schema.prisma | 95 ++-- 14 files changed, 781 insertions(+), 285 deletions(-) delete mode 100644 apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts create mode 100644 apps/api-gateway/src/ecosystem/dtos/request-schema-dto.ts create mode 100644 apps/ecosystem/interfaces/ecosystem.interfaces.ts create mode 100644 libs/prisma-service/prisma/migrations/20231006120455_endorsement_transaction/migration.sql diff --git a/apps/agent-service/src/agent-service.controller.ts b/apps/agent-service/src/agent-service.controller.ts index e85fcd6ba..38c658264 100644 --- a/apps/agent-service/src/agent-service.controller.ts +++ b/apps/agent-service/src/agent-service.controller.ts @@ -106,4 +106,14 @@ export class AgentServiceController { 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 { + return this.agentServiceService.schemaEndorsementRequest(payload.url, payload.apiKey, payload.requestSchemaPayload); + } + + @MessagePattern({ cmd: 'agent-sign-transaction' }) + async signTransaction(payload: { url: string, apiKey: string, signEndorsementPayload:string }): Promise { + return this.agentServiceService.signTransaction(payload.url, payload.apiKey, payload.signEndorsementPayload); + } } diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 5604dce5e..ebf066249 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -889,5 +889,31 @@ 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 } }) + .then(async response => response); + return schemaRequest; + } catch (error) { + this.logger.error(`Error in schema endorsement request in agent service : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + async signTransaction(url: string, apiKey: string, signEndorsementPayload: string): Promise { + try { + + const signEndorsementTransaction = await this.commonService + .httpPost(url, signEndorsementPayload, { headers: { 'x-api-key': apiKey } }) + .then(async response => response); + + return signEndorsementTransaction; + } catch (error) { + this.logger.error(`Error in sign transaction in agent service : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + } diff --git a/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts b/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts deleted file mode 100644 index d584c5832..000000000 --- a/apps/api-gateway/src/ecosystem/dtos/create-organization-dto.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; - -import { Transform } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; - -@ApiExtraModels() -export class CreateEcosystemDto { - - @ApiProperty() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'Organization name is required.' }) - @MinLength(2, { message: 'Organization name must be at least 2 characters.' }) - @MaxLength(50, { message: 'Organization name must be at most 50 characters.' }) - @IsString({ message: 'Organization name must be in string format.' }) - name: string; - - @ApiPropertyOptional() - @Transform(({ value }) => trim(value)) - @MinLength(2, { message: 'Description must be at least 2 characters.' }) - @MaxLength(255, { message: 'Description must be at most 255 characters.' }) - @IsString({ message: 'Description must be in string format.' }) - description: string; - - @ApiPropertyOptional() - @IsOptional() - @Transform(({ value }) => trim(value)) - @IsString({ message: 'logo must be in string format.' }) - logo: string; - - @ApiPropertyOptional() - @IsOptional() - @Transform(({ value }) => trim(value)) - website?: string; - -} \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/dtos/request-schema-dto.ts b/apps/api-gateway/src/ecosystem/dtos/request-schema-dto.ts new file mode 100644 index 000000000..c2742fca2 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/request-schema-dto.ts @@ -0,0 +1,47 @@ +import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +@ApiExtraModels() + +class AttributeValue { + + @IsString() + @IsNotEmpty({ message: 'attributeName is required.' }) + attributeName: string; + + @IsString() + @IsNotEmpty({ message: 'schemaDataType is required.' }) + schemaDataType: string; + + @IsString() + @IsNotEmpty({ message: 'displayName is required.' }) + displayName: string; +} + +export class RequestSchemaDto { + @ApiProperty() + @IsString({ message: 'name must be in string format.' }) + name: string; + + @ApiProperty() + @IsInt({ message: 'version must be in number format.' }) + version: number; + + @ApiProperty({ + 'example': [ + { + attributeName: 'name', + schemaDataType: 'string', + displayName: 'Name' + } + ] + }) + @IsArray({ message: 'attributes must be an array' }) + @IsNotEmpty({ message: 'please provide valid attributes' }) + attributes: AttributeValue[]; + + @ApiProperty() + @IsBoolean({ message: 'endorse must be a boolean.' }) + @IsOptional() + endorse?: boolean; + +} \ 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 30e932120..2e9450445 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -4,7 +4,7 @@ import { EcosystemService } from './ecosystem.service'; import { Post, Get } from '@nestjs/common'; import { Body } from '@nestjs/common'; import { Res } from '@nestjs/common'; -import { CreateEcosystemDto } from './dtos/create-ecosystem-dto'; +import { RequestSchemaDto } from './dtos/request-schema-dto'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { HttpStatus } from '@nestjs/common'; import { Response } from 'express'; @@ -21,6 +21,7 @@ import { User } from '../authz/decorators/user.decorator'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { user } from '@prisma/client'; +import { CreateEcosystemDto } from './dtos/create-ecosystem-dto'; @UseFilters(CustomExceptionFilter) @@ -102,6 +103,33 @@ export class EcosystemController { return res.status(HttpStatus.CREATED).json(finalResponse); } + @Post('/:orgId/transaction/schema') + @ApiOperation({ summary: 'Request new schema', description: 'Request new schema' }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async requestSchemaTransaction(@Body() requestSchemaPayload: RequestSchemaDto, @Param('orgId') orgId: number, @Res() res: Response): Promise { + await this.ecosystemService.schemaEndorsementRequest(requestSchemaPayload, orgId); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.schemaRequest + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + @Post('transaction/sign/:endorsementId') + @ApiOperation({ summary: 'Sign transaction', description: 'Sign transaction' }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async SignEndorsementRequests(@Param('endorsementId') endorsementId: string, @Res() res: Response): Promise { + await this.ecosystemService.signTransaction(endorsementId); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.sign + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } /** * diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index cfb0177d4..39ca0e0bc 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -4,6 +4,7 @@ import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; +import { RequestSchemaDto } from './dtos/request-schema-dto'; @Injectable() @@ -68,4 +69,14 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'get-ecosystem-invitations', payload); } + async schemaEndorsementRequest(requestSchemaPayload: RequestSchemaDto, orgId: number): Promise { + const payload = { requestSchemaPayload, orgId}; + return this.sendNats(this.serviceProxy, 'schema-endorsement-request', payload); + } + + + async signTransaction(endorsementId:string): Promise { + const payload = { endorsementId }; + return this.sendNats(this.serviceProxy, 'sign-endorsement-transaction', payload); + } } diff --git a/apps/ecosystem/enums/ecosystem.enum.ts b/apps/ecosystem/enums/ecosystem.enum.ts index 13d0022fb..ed3fe5199 100644 --- a/apps/ecosystem/enums/ecosystem.enum.ts +++ b/apps/ecosystem/enums/ecosystem.enum.ts @@ -12,4 +12,11 @@ export enum EcosystemInvitationStatus { ACCEPTED = 'accepted', REJECTED = 'rejected', PENDING = 'pending' +} + +export enum endorsementTransactionStatus { + REQUESTED = 'Requested', + SIGNED = 'Signed', + DECLINED = 'Declined', + SUBMITED = 'Submited' } \ No newline at end of file diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts new file mode 100644 index 000000000..fb0e27946 --- /dev/null +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -0,0 +1,51 @@ +export interface RequestSchemaEndorsement { + orgId: number + name: string; + version: number; + attributes: IAttributeValue[]; + endorse?: boolean; +} + +export interface IAttributeValue { + attributeName: string; + schemaDataType: string; + displayName: string +} + +export interface SchemaTransactionPayload { + endorserDid: string; + endorse: boolean; + attributes: string[]; + version: string; + name: string; + issuerId: string; +} + +export interface SchemaMessage { + message?: { + jobId: string; + schemaState: { + state: string; + action: string; + schemaId: string; + schema: Record; + schemaRequest: string; + }; + registrationMetadata: Record; + schemaMetadata: Record; + }; +} + +export interface SchemaTransactionResponse { + endorserDid: string; + authorDid: string; + requestPayload: string; + status: string; + ecosystemOrgId: string; +} + +export interface SignedTransactionMessage { + message?: { + signedTransaction: string; + }; +} diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index ae4cf9e79..e257dac50 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -4,6 +4,7 @@ import { MessagePattern } from '@nestjs/microservices'; import { EcosystemService } from './ecosystem.service'; import { Body } from '@nestjs/common'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; +import { RequestSchemaEndorsement } from '../interfaces/ecosystem.interfaces'; @Controller() export class EcosystemController { @@ -70,4 +71,27 @@ export class EcosystemController { return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId); } + /** + * + * @param payload + * @returns Schema endorsement request + */ + @MessagePattern({ cmd: 'schema-endorsement-request' }) + async schemaEndorsementRequest( + @Body() payload: { requestSchemaPayload: RequestSchemaEndorsement; orgId: number } + ): Promise { + return this.ecosystemService.requestSchemaEndorsement(payload.requestSchemaPayload, payload.orgId); + } + + /** + * + * @param payload + * @returns sign endorsement request + */ + @MessagePattern({ cmd: 'sign-endorsement-transaction' }) + async signTransaction( + @Body() payload: { endorsementId: string } + ): Promise { + return this.ecosystemService.signTransaction(payload.endorsementId); + } } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 4044e8dc8..545d5f098 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -1,223 +1,369 @@ import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase -import { ecosystem, ecosystem_invitations } from '@prisma/client'; -import {EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles} from '../enums/ecosystem.enum'; +import { ecosystem, ecosystem_invitations, ecosystem_orgs, endorsement_transaction, org_agents, platform_config } from '@prisma/client'; +import { EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus } from '../enums/ecosystem.enum'; +import { SchemaTransactionResponse } from '../interfaces/ecosystem.interfaces'; // eslint-disable-next-line camelcase @Injectable() export class EcosystemRepository { - constructor( - private readonly prisma: PrismaService, - private readonly logger: Logger - ) { } + constructor( + private readonly prisma: PrismaService, + private readonly logger: Logger + ) { } - /** - * Description: Get getAgentEndPoint by orgId - * @param createEcosystemDto - * @returns Get getAgentEndPoint details - */ - // eslint-disable-next-line camelcase - async createNewEcosystem(createEcosystemDto): Promise { - try { - const transaction = await this.prisma.$transaction(async (prisma) => { - const { name, description, userId, logo, tags, orgId } = createEcosystemDto; - const createdEcosystem = await prisma.ecosystem.create({ - data: { - name, - description, - tags, - logoUrl: logo, - createdBy: orgId, - lastChangedBy: orgId - } - }); - let ecosystemUser; - if (createdEcosystem) { - ecosystemUser = await prisma.ecosystem_users.create({ - data: { - userId: String(userId), - ecosystemId: createdEcosystem.id, - createdBy: orgId, - lastChangedBy: orgId - } - }); - } - - if (ecosystemUser) { - const ecosystemRoleDetails = await this.prisma.ecosystem_roles.findFirst({ - where: { - name: EcosystemRoles.ECOSYSTEM_LEAD - } - }); - ecosystemUser = await prisma.ecosystem_orgs.create({ - data: { - orgId: String(orgId), - status: EcosystemOrgStatus.ACTIVE, - ecosystemId: createdEcosystem.id, - ecosystemRoleId: ecosystemRoleDetails.id, - createdBy: orgId, - lastChangedBy: orgId - } - }); - } - return createdEcosystem; - }); - - return transaction; - } catch (error) { - this.logger.error(`Error in create ecosystem transaction: ${error.message}`); - throw error; + /** + * Description: Get getAgentEndPoint by orgId + * @param createEcosystemDto + * @returns Get getAgentEndPoint details + */ + // eslint-disable-next-line camelcase + async createNewEcosystem(createEcosystemDto): Promise { + try { + const transaction = await this.prisma.$transaction(async (prisma) => { + const { name, description, userId, logo, tags, orgId } = createEcosystemDto; + const createdEcosystem = await prisma.ecosystem.create({ + data: { + name, + description, + tags, + logoUrl: logo, + createdBy: orgId, + lastChangedBy: orgId + } + }); + let ecosystemUser; + if (createdEcosystem) { + ecosystemUser = await prisma.ecosystem_users.create({ + data: { + userId: String(userId), + ecosystemId: createdEcosystem.id, + createdBy: orgId, + lastChangedBy: orgId + } + }); + } + + if (ecosystemUser) { + const ecosystemRoleDetails = await this.prisma.ecosystem_roles.findFirst({ + where: { + name: EcosystemRoles.ECOSYSTEM_LEAD + } + }); + ecosystemUser = await prisma.ecosystem_orgs.create({ + data: { + orgId: String(orgId), + status: EcosystemOrgStatus.ACTIVE, + ecosystemId: createdEcosystem.id, + ecosystemRoleId: ecosystemRoleDetails.id, + createdBy: orgId, + lastChangedBy: orgId + } + }); + } + return createdEcosystem; + }); + + return transaction; + } catch (error) { + this.logger.error(`Error in create ecosystem transaction: ${error.message}`); + throw error; + } + } + + /** + * Description: Edit ecosystem by Id + * @param editEcosystemDto + * @returns ecosystem details + */ + // eslint-disable-next-line camelcase + async updateEcosystemById(createEcosystemDto, ecosystemId): Promise { + try { + const { name, description, tags, logo } = createEcosystemDto; + const editEcosystem = await this.prisma.ecosystem.update({ + where: { id: ecosystemId }, + data: { + name, + description, + tags, + logoUrl: logo + } + }); + return editEcosystem; + } catch (error) { + this.logger.error(`Error in edit ecosystem transaction: ${error.message}`); + throw error; + } + } + + /** + * + * + * @returns Get all ecosystem details + */ + // eslint-disable-next-line camelcase + async getAllEcosystemDetails(): Promise { + try { + const ecosystemDetails = await this.prisma.ecosystem.findMany({ + }); + return ecosystemDetails; + } catch (error) { + this.logger.error(`Error in get all ecosystem transaction: ${error.message}`); + throw error; + } + } + + async getEcosystemInvitationsPagination(queryObject: object, status: string, pageNumber: number, pageSize: number): Promise { + try { + const result = await this.prisma.$transaction([ + this.prisma.ecosystem_invitations.findMany({ + where: { + ...queryObject, + status + }, + include: { + ecosystem: true + }, + take: pageSize, + skip: (pageNumber - 1) * pageSize, + orderBy: { + createDateTime: 'desc' + } + }), + this.prisma.ecosystem_invitations.count({ + where: { + ...queryObject + } + }) + ]); + + const [invitations, totalCount] = result; + const totalPages = Math.ceil(totalCount / pageSize); + + return { totalPages, invitations }; + + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + + /** + * + * @param ecosystemId + * @returns Get specific ecosystem details + */ + async getEcosystemDetails(ecosystemId: string): Promise { + try { + return this.prisma.ecosystem.findFirst({ + where: { + id: ecosystemId } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); } + } - /** - * Description: Edit ecosystem by Id - * @param editEcosystemDto - * @returns ecosystem details + /** + * + * @param queryObject + * @returns Get all ecosystem invitations */ + async getEcosystemInvitations( + queryObject: object // eslint-disable-next-line camelcase - async updateEcosystemById(createEcosystemDto, ecosystemId): Promise { - try { - const { name, description, tags, logo } = createEcosystemDto; - const editEcosystem = await this.prisma.ecosystem.update({ - where: { id: ecosystemId }, - data: { - name, - description, - tags, - logoUrl: logo - } - }); - return editEcosystem; - } catch (error) { - this.logger.error(`Error in edit ecosystem transaction: ${error.message}`); - throw error; + ): Promise { + try { + return this.prisma.ecosystem_invitations.findMany({ + where: { + ...queryObject + }, + include: { + ecosystem: true } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); } + } - /** + + /** * - * - * @returns Get all ecosystem details + * @param email + * @param ecosystemId + * @param userId + * @returns */ + async createSendInvitation( + email: string, + ecosystemId: string, + userId: string // eslint-disable-next-line camelcase - async getAllEcosystemDetails(): Promise { - try { - const ecosystemDetails = await this.prisma.ecosystem.findMany({ - }); - return ecosystemDetails; - } catch (error) { - this.logger.error(`Error in get all ecosystem transaction: ${error.message}`); - throw error; + ): Promise { + try { + return this.prisma.ecosystem_invitations.create({ + data: { + email, + userId, + ecosystem: { connect: { id: ecosystemId } }, + status: EcosystemInvitationStatus.PENDING, + orgId: '' } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); } + } - async getEcosystemInvitationsPagination(queryObject: object, status: string, pageNumber: number, pageSize: number): Promise { - try { - const result = await this.prisma.$transaction([ - this.prisma.ecosystem_invitations.findMany({ - where: { - ...queryObject, - status - }, - include: { - ecosystem: true - }, - take: pageSize, - skip: (pageNumber - 1) * pageSize, - orderBy: { - createDateTime: 'desc' - } - }), - this.prisma.ecosystem_invitations.count({ - where: { - ...queryObject - } - }) - ]); - - const [invitations, totalCount] = result; - const totalPages = Math.ceil(totalCount / pageSize); - - return { totalPages, invitations }; - - } catch (error) { - this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); - } + /** + * Description: Get getAgentEndPoint by orgId + * @param orgId + * @returns Get getAgentEndPoint details + */ + // eslint-disable-next-line camelcase + async getAgentDetails(orgId: number): Promise { + try { + const agentDetails = await this.prisma.org_agents.findFirst({ + where: { + orgId + } + }); + return agentDetails; + + } catch (error) { + this.logger.error(`Error in getting getAgentEndPoint for the ecosystem: ${error.message} `); + throw error; } + } - /** - * - * @param ecosystemId - * @returns Get specific ecosystem details + /** + * Description: Get getAgentEndPoint by orgId + * @param orgId + * @returns Get getAgentEndPoint details */ - async getEcosystemDetails(ecosystemId: string): Promise { - try { - return this.prisma.ecosystem.findFirst({ - where: { - id: ecosystemId - } - }); - } catch (error) { - this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + // eslint-disable-next-line camelcase + async getEcosystemLeadDetails(): Promise { + try { + const ecosystemRoleDetails = await this.prisma.ecosystem_roles.findFirst({ + where: { + name: EcosystemRoles.ECOSYSTEM_LEAD + } + }); + const ecosystemLeadDetails = await this.prisma.ecosystem_orgs.findFirst({ + where: { + ecosystemRoleId: ecosystemRoleDetails.id } + }); + return ecosystemLeadDetails; + + } catch (error) { + this.logger.error(`Error in getting ecosystem lead details for the ecosystem: ${error.message} `); + throw error; } + } - /** - * - * @param queryObject - * @returns Get all ecosystem invitations - */ - async getEcosystemInvitations( - queryObject: object - // eslint-disable-next-line camelcase - ): Promise { - try { - return this.prisma.ecosystem_invitations.findMany({ - where: { - ...queryObject - }, - include: { - ecosystem: true + /** + * Get platform config details + * @returns + */ + // eslint-disable-next-line camelcase + async getPlatformConfigDetails(): Promise { + try { + + return this.prisma.platform_config.findFirst(); + + } catch (error) { + this.logger.error(`Error in getting getPlatformConfigDetails for the ecosystem - error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + + async storeTransactionRequest( + schemaTransactionResponse: SchemaTransactionResponse + ): Promise { + try { + const { endorserDid, authorDid, requestPayload, status, ecosystemOrgId } = schemaTransactionResponse; + return await this.prisma.endorsement_transaction.create({ + data: { + endorserDid, + authorDid, + requestPayload, + status, + ecosystemOrgId, + responsePayload: '' + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + // eslint-disable-next-line camelcase + async getEcosystemOrgDetailsbyId(orgId: string): Promise { + try { + const ecosystemLeadDetails = await this.prisma.ecosystem_orgs.findFirst({ + where: { + orgId + } + }); + return ecosystemLeadDetails; + + } catch (error) { + this.logger.error(`Error in getting ecosystem lead details for the ecosystem: ${error.message} `); + throw error; + } + } + // eslint-disable-next-line camelcase + async getEndorsementTransactionById(endorsementId: string): Promise { + try { + const ecosystemLeadDetails = await this.prisma.endorsement_transaction.findFirst({ + where: { + id: endorsementId, + status: endorsementTransactionStatus.REQUESTED + }, + include: { + ecosystemOrgs: { + select: { + orgId: true } - }); - } catch (error) { - this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + } } + }); + + return ecosystemLeadDetails; + + } catch (error) { + this.logger.error(`Error in getting ecosystem lead details for the ecosystem: ${error.message} `); + throw error; } + } + async updateTransactionDetails( + endorsementId: string, + schemaTransactionRequest: string - /** - * - * @param email - * @param ecosystemId - * @param userId - * @returns - */ - async createSendInvitation( - email: string, - ecosystemId: string, - userId: string - // eslint-disable-next-line camelcase - ): Promise { - try { - return this.prisma.ecosystem_invitations.create({ - data: { - email, - userId, - ecosystem: {connect: {id: ecosystemId}}, - status: EcosystemInvitationStatus.PENDING, - orgId: '' - } - }); - } catch (error) { - this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + // eslint-disable-next-line camelcase, + ): Promise { + try { + const updatedTransaction = await this.prisma.endorsement_transaction.update({ + where: { id: endorsementId }, + data: { + responsePayload: schemaTransactionRequest } - } - + }); + + return updatedTransaction; + + } catch (error) { + this.logger.error(`Error in updating endorsement transaction: ${error.message}`); + throw error; + } + } + } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 2e31b5437..5f0dc424b 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -1,17 +1,23 @@ // eslint-disable-next-line camelcase import { EcosystemRepository } from './ecosystem.repository'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; +import { HttpException, Inject, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; -import { RpcException } from '@nestjs/microservices'; +import { ClientProxy, RpcException } from '@nestjs/microservices'; import { PrismaService } from '@credebl/prisma-service'; import { EcosystemInviteTemplate } from '../templates/EcosystemInviteTemplate'; import { EmailDto } from '@credebl/common/dtos/email.dto'; import { sendEmail } from '@credebl/common/send-grid-helper-file'; +import { RequestSchemaEndorsement, SchemaMessage, SchemaTransactionPayload, SchemaTransactionResponse, SignedTransactionMessage } from '../interfaces/ecosystem.interfaces'; +import { OrgAgentType } from '@credebl/enum/enum'; +import { CommonConstants } from '@credebl/common/common.constant'; + // eslint-disable-next-line camelcase +import { platform_config } from '@prisma/client'; @Injectable() export class EcosystemService { constructor( + @Inject('NATS_CLIENT') private readonly ecosystemServiceProxy: ClientProxy, private readonly ecosystemRepository: EcosystemRepository, private readonly logger: Logger, private readonly prisma: PrismaService @@ -26,11 +32,11 @@ export class EcosystemService { // eslint-disable-next-line camelcase async createEcosystem(createEcosystemDto): Promise { - const createEcosystem = await this.ecosystemRepository.createNewEcosystem(createEcosystemDto); - if (!createEcosystem) { - throw new NotFoundException(ResponseMessages.ecosystem.error.update); - } - return createEcosystem; + const createEcosystem = await this.ecosystemRepository.createNewEcosystem(createEcosystemDto); + if (!createEcosystem) { + throw new NotFoundException(ResponseMessages.ecosystem.error.update); + } + return createEcosystem; } @@ -57,21 +63,21 @@ export class EcosystemService { // eslint-disable-next-line camelcase async getAllEcosystem(): Promise { - const getAllEcosystemDetails = await this.ecosystemRepository.getAllEcosystemDetails(); - if (!getAllEcosystemDetails) { - throw new NotFoundException(ResponseMessages.ecosystem.error.update); - } - return getAllEcosystemDetails; - } + const getAllEcosystemDetails = await this.ecosystemRepository.getAllEcosystemDetails(); + if (!getAllEcosystemDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.update); + } + return getAllEcosystemDetails; + } /** * 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 { - + try { const query = { AND: [ @@ -86,7 +92,7 @@ export class EcosystemService { throw new InternalServerErrorException(error); } } - + /** * * @param bulkInvitationDto @@ -119,7 +125,7 @@ export class EcosystemService { this.logger.error(`In send Invitation : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } - } + } /** @@ -176,4 +182,152 @@ export class EcosystemService { return isEmailSent; } + /** + * + * @param RequestSchemaEndorsement + * @returns + */ + async requestSchemaEndorsement(requestSchemaPayload: RequestSchemaEndorsement, orgId: number): Promise { + try { + const agentDetails = await this.ecosystemRepository.getAgentDetails(orgId); + // eslint-disable-next-line camelcase + const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); + + const url = await this.getAgentUrl(agentDetails?.orgAgentTypeId, agentDetails.agentEndPoint); + + const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); + + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(); + + const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); + + const getEcosystemOrgDetailsByOrgId = await this.ecosystemRepository.getEcosystemOrgDetailsbyId(String(orgId)); + + const schemaTransactionPayload: SchemaTransactionPayload = { + endorserDid: ecosystemLeadAgentDetails.orgDid, + endorse: requestSchemaPayload.endorse, + attributes: attributeArray, + version: String(requestSchemaPayload.version), + name: requestSchemaPayload.name, + issuerId: agentDetails.orgDid + }; + + const schemaTransactionRequest: SchemaMessage = await this._requestSchemaEndorsement(schemaTransactionPayload, url, platformConfig?.sgApiKey); + const schemaTransactionResponse: SchemaTransactionResponse = { + endorserDid: ecosystemLeadAgentDetails.orgDid, + authorDid: agentDetails.orgDid, + requestPayload: schemaTransactionRequest.message.schemaState.schemaRequest, + status: "Requested", + ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id + }; + return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse); + } catch (error) { + this.logger.error(`In request schema endorsement : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + /** + * Description: Store shortening URL + * @param referenceId + * @param url + * @returns connection invitation URL + */ + async _requestSchemaEndorsement(requestSchemaPayload: object, url: string, apiKey: string): Promise { + const pattern = { cmd: 'agent-schema-endorsement-request' }; + const payload = { requestSchemaPayload, url, apiKey }; + + 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); + } + } + + async signTransaction(endorsementId: string): Promise { + try { + const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId); + + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(); + // eslint-disable-next-line camelcase + const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); + + const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); + const url = `${ecosystemLeadAgentDetails.agentEndPoint}/transactions/endorse`; + + + const parsedRequestPayload = JSON.parse(endorsementTransactionPayload.requestPayload); + + + const payload = { + transaction: JSON.stringify(parsedRequestPayload), + endorserDid: endorsementTransactionPayload.endorserDid + }; + + const schemaTransactionRequest: SignedTransactionMessage = await this._signTransaction(payload, url, platformConfig.sgApiKey); + return this.ecosystemRepository.updateTransactionDetails(endorsementId, schemaTransactionRequest.message.signedTransaction); + + } catch (error) { + this.logger.error(`In sign transaction : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + /** + * 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 }; + + 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); + } + } + + + async getAgentUrl( + orgAgentTypeId: number, + agentEndPoint: string + ): Promise { + try { + + let url; + if (orgAgentTypeId === OrgAgentType.DEDICATED) { + + url = `${agentEndPoint}${CommonConstants.URL_SCHM_CREATE_SCHEMA}`; + } else if (orgAgentTypeId === OrgAgentType.SHARED) { + + // TODO + // url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_INVITATION}`.replace('#', tenantId); + } else { + + throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); + } + return url; + + } catch (error) { + this.logger.error(`Error in get agent url: ${JSON.stringify(error)}`); + throw error; + + } + } + } diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 7af055646..1b5ad24c5 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -188,7 +188,9 @@ export const ResponseMessages = { update: 'Ecosystem updated successfully', fetch: 'Ecosystem fetched successfully', getInvitation: 'Ecosystem invitations fetched successfully', - createInvitation: 'Ecosystem invitations sent successfully' + createInvitation: 'Ecosystem invitations sent successfully', + schemaRequest: 'Schema transaction request created successfully', + sign: 'Transaction request signed successfully' }, error: { notCreated: 'Error while creating ecosystem', diff --git a/libs/prisma-service/prisma/migrations/20231006120455_endorsement_transaction/migration.sql b/libs/prisma-service/prisma/migrations/20231006120455_endorsement_transaction/migration.sql new file mode 100644 index 000000000..083feb655 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231006120455_endorsement_transaction/migration.sql @@ -0,0 +1,15 @@ +-- CreateTable +CREATE TABLE "endorsement_transaction" ( + "id" TEXT NOT NULL, + "endorserDid" TEXT NOT NULL, + "authorDid" TEXT NOT NULL, + "requestPayload" TEXT NOT NULL, + "responsePayload" TEXT NOT NULL, + "status" TEXT NOT NULL, + "ecosystemOrgId" TEXT NOT NULL, + + CONSTRAINT "endorsement_transaction_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "endorsement_transaction" ADD CONSTRAINT "endorsement_transaction_ecosystemOrgId_fkey" FOREIGN KEY ("ecosystemOrgId") REFERENCES "ecosystem_orgs"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index ca36b915b..5637e59d0 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -132,12 +132,12 @@ model platform_config { emailFrom String @db.VarChar apiEndpoint String @db.VarChar tailsFileServer String @db.VarChar - enableEcosystem Boolean @default(false) createDateTime DateTime @default(now()) @db.Timestamptz(6) createdBy Int @default(1) lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) lastChangedBy Int @default(1) deletedAt DateTime? @db.Timestamp(6) + enableEcosystem Boolean @default(false) } model org_agents { @@ -200,8 +200,8 @@ model ledgers { isActive Boolean networkString String @db.VarChar registerDIDEndpoint String @db.VarChar - indyNamespace String? @db.VarChar registerDIDPayload Json? + indyNamespace String? @db.VarChar org_agents org_agents[] } @@ -314,69 +314,80 @@ model ecosystem_roles { id String @id @default(uuid()) name String @unique description String - ecosystemOrgs ecosystem_orgs[] createDateTime DateTime @default(now()) @db.Timestamptz(6) createdBy Int @default(1) lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) lastChangedBy Int @default(1) deletedAt DateTime? @db.Timestamp(6) + ecosystemOrgs ecosystem_orgs[] } model ecosystem { - id String @id @default(uuid()) - name String - description String - logoUrl String? - tags String - ecosystemOrgs ecosystem_orgs[] - ecosystemUsers ecosystem_users[] - ecosystemInvitations ecosystem_invitations[] - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - deletedAt DateTime? @db.Timestamp(6) + id String @id @default(uuid()) + name String + description String + tags String + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) + logoUrl String? + ecosystemInvitations ecosystem_invitations[] + ecosystemOrgs ecosystem_orgs[] + ecosystemUsers ecosystem_users[] } model ecosystem_invitations { - id String @id @default(uuid()) + id String @id @default(uuid()) email String status String ecosystemId String userId String orgId String - ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - deletedAt DateTime? @db.Timestamp(6) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) + ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) } model ecosystem_users { - id String @id @default(uuid()) // auto-increment + id String @id @default(uuid()) userId String ecosystemId String - ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - deletedAt DateTime? @db.Timestamp(6) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) + ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) } model ecosystem_orgs { - id String @id @default(uuid()) - orgId String - status String - ecosystemId String - ecosystemRoleId String - ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) - ecosystemRole ecosystem_roles @relation(fields: [ecosystemRoleId], references: [id]) - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - deletedAt DateTime? @db.Timestamp(6) + id String @id @default(uuid()) + orgId String + status String + ecosystemId String + ecosystemRoleId String + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) + ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) + ecosystemRole ecosystem_roles @relation(fields: [ecosystemRoleId], references: [id]) + endorsementTransaction endorsement_transaction[] } +model endorsement_transaction { + id String @id @default(uuid()) + endorserDid String + authorDid String + requestPayload String + responsePayload String + status String + ecosystemOrgId String + ecosystemOrgs ecosystem_orgs @relation(fields: [ecosystemOrgId], references: [id]) +} From 80b20e6db45e5e59232d39090e6ac79f5e47d78b Mon Sep 17 00:00:00 2001 From: Nishad Date: Sat, 7 Oct 2023 17:48:45 +0530 Subject: [PATCH 112/162] worked on the GET API for endorsement transactions Signed-off-by: Nishad --- .../dtos/get-all-endorsements.dto.ts | 31 +++++++++++++ .../src/ecosystem/ecosystem.controller.ts | 37 +++++++++++++++ .../src/ecosystem/ecosystem.service.ts | 11 +++++ .../interfaces/endorsements.interface.ts | 8 ++++ apps/ecosystem/src/ecosystem.controller.ts | 12 ++--- apps/ecosystem/src/ecosystem.repository.ts | 45 +++++++++++++++++++ apps/ecosystem/src/ecosystem.service.ts | 24 ++++++++++ .../prisma/data/credebl-master-table.json | 38 ++++++++++++++++ .../migration.sql | 15 +++++++ libs/prisma-service/prisma/schema.prisma | 12 +++++ libs/prisma-service/prisma/seed.ts | 32 +++++++++---- 11 files changed, 251 insertions(+), 14 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts create mode 100644 apps/ecosystem/interfaces/endorsements.interface.ts create mode 100644 libs/prisma-service/prisma/migrations/20231007072334_endorsement_transaction/migration.sql diff --git a/apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts new file mode 100644 index 000000000..96f59a857 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts @@ -0,0 +1,31 @@ + +import { Transform, Type } from 'class-transformer'; +import { toNumber } from '@credebl/common/cast.helper'; + +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class GetAllEndorsementsDto { + @ApiProperty({ required: false, default: 1 }) + @IsOptional() + @Type(() => Number) + @Transform(({ value }) => toNumber(value)) + pageNumber = 1; + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => String) + search = ''; + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => Number) + @Transform(({ value }) => toNumber(value)) + pageSize = 10; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + status; + +} diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 01a245c20..ef3620d5d 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -27,6 +27,7 @@ import { EcosystemRolesGuard } from '../authz/guards/ecosystem-roles.guard'; import { EcosystemsRoles, Roles } from '../authz/decorators/roles.decorator'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { OrgRoles } from 'libs/org-roles/enums'; +import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; @UseFilters(CustomExceptionFilter) @@ -103,6 +104,42 @@ export class EcosystemController { } + @Get('/:ecosystemId/:orgId/endorsement-transactions') + @ApiOperation({ summary: 'Get all endorsement transactions', description: 'Get all endorsement transactions' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getEndorsementTranasactions( + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @Query() getAllEndorsementsDto: GetAllEndorsementsDto, + @Res() res: Response + ): Promise { + const ecosystemList = await this.ecosystemService.getEndorsementTranasactions(ecosystemId, orgId, getAllEndorsementsDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: `Endorser transactions fetched successfully`, + data: ecosystemList.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + @Get('/:ecosystemId/:orgId/invitations') @ApiOperation({ summary: 'Get all sent invitations', description: 'Get all sent invitations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 49652d550..d425ac610 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -6,6 +6,7 @@ import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; import { AcceptRejectEcosystemInvitationDto } from './dtos/accept-reject-ecosysteminvitation-dto'; import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.dto'; import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; +import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; @Injectable() @@ -98,5 +99,15 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'fetch-ecosystem-org-data', payload); } + async getEndorsementTranasactions( + ecosystemId: string, + orgId: string, + getAllEndorsements: GetAllEndorsementsDto + ): Promise<{ response: object }> { + const { pageNumber, pageSize, search, status } = getAllEndorsements; + const payload = { ecosystemId, orgId, pageNumber, pageSize, search, status }; + return this.sendNats(this.serviceProxy, 'get-endorsement-transactions', payload); + } + } diff --git a/apps/ecosystem/interfaces/endorsements.interface.ts b/apps/ecosystem/interfaces/endorsements.interface.ts new file mode 100644 index 000000000..b16259cee --- /dev/null +++ b/apps/ecosystem/interfaces/endorsements.interface.ts @@ -0,0 +1,8 @@ +export interface GetEndorsementsPayload { + ecosystemId: string; + orgId: string; + status: string; + pageNumber: number; + pageSize: number; + search: string; + } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 470e2b3b8..3888a8f30 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -6,6 +6,7 @@ import { Body } from '@nestjs/common'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; import { AcceptRejectEcosystemInvitationDto } from '../dtos/accept-reject-ecosysteminvitation.dto'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; +import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; @Controller() export class EcosystemController { @@ -96,13 +97,14 @@ export class EcosystemController { ); } - @MessagePattern({ cmd: 'fetch-ecosystem-org-data' }) - async fetchEcosystemOrg( - @Body() payload: { ecosystemId: string, orgId: string} + @MessagePattern({ cmd: 'get-endorsement-transactions' }) + async getEndorsementTransactions( + @Body() payload: GetEndorsementsPayload ): Promise { - return this.ecosystemService.fetchEcosystemOrg( + return this.ecosystemService.getEndorsementTransactions( payload ); - } + } + } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 9494dda2a..70582688d 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -334,6 +334,51 @@ export class EcosystemRepository { }); } + + async getEndorsementsWithPagination(queryObject: object, filterOptions: object, pageNumber: number, pageSize: number): Promise { + try { + const result = await this.prisma.$transaction([ + this.prisma.endorsement_transaction.findMany({ + where: { + ...queryObject + }, + select:{ + id:true, + endorserDid: true, + authorDid: true, + status: true, + ecosystemOrgs: { + where: { + ...filterOptions + // Additional filtering conditions if needed + } + } + }, + take: pageSize, + skip: (pageNumber - 1) * pageSize + // orderBy: { + // createDateTime: 'desc' + // } + }), + this.prisma.endorsement_transaction.count({ + where: { + ...queryObject + } + }) + ]); + + // eslint-disable-next-line prefer-destructuring + const transactions = result[0]; + // eslint-disable-next-line prefer-destructuring + const totalCount = result[1]; + const totalPages = Math.ceil(totalCount / pageSize); + + return { totalPages, transactions }; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 00e2293e1..375667d2f 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -12,6 +12,7 @@ import { AcceptRejectEcosystemInvitationDto } from '../dtos/accept-reject-ecosys import { Invitation } from '@credebl/enum/enum'; import { EcosystemOrgStatus, EcosystemRoles } from '../enums/ecosystem.enum'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; +import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; @Injectable() export class EcosystemService { @@ -297,5 +298,28 @@ export class EcosystemService { return platformConfigData[0].enableEcosystem; } + async getEndorsementTransactions(payload: GetEndorsementsPayload): Promise { + const {ecosystemId, orgId, pageNumber, pageSize, search} = payload; + try { + + const query = { + ecosystemOrgs: { + ecosystemId, + orgId + }, + OR: [ + { status: { contains: search, mode: 'insensitive' } }, + { authorDid: { contains: search, mode: 'insensitive' } } + ] + }; + + const filterOptions = {}; + + return await this.ecosystemRepository.getEndorsementsWithPagination(query, filterOptions, pageNumber, pageSize); + } catch (error) { + this.logger.error(`In error getEndorsementTransactions: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } } diff --git a/libs/prisma-service/prisma/data/credebl-master-table.json b/libs/prisma-service/prisma/data/credebl-master-table.json index a3966a6ca..5053c4d65 100644 --- a/libs/prisma-service/prisma/data/credebl-master-table.json +++ b/libs/prisma-service/prisma/data/credebl-master-table.json @@ -107,5 +107,43 @@ "registerDIDPayload": "", "indyNamespace": "indicio:testnet" } + ], + "endorseData": [ + { + "id": "0f8fad5b-d9cb-469f-a165-70867728950f", + "endorserDid": "endorser123", + "authorDid": "author456", + "requestPayload": "{\"type\": \"dummy_request_1\"}", + "responsePayload": "{\"type\": \"dummy_response_1\"}", + "status": "Requested", + "ecosystemOrgId": "1c247b4a-e2f6-48c0-8aa2-65ea47474294" + }, + { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "endorserDid": "endorser789", + "authorDid": "author101", + "requestPayload": "{\"type\": \"dummy_request_2\"}", + "responsePayload": "{\"type\": \"dummy_response_2\"}", + "status": "Signed", + "ecosystemOrgId": "1c247b4a-e2f6-48c0-8aa2-65ea47474294" + }, + { + "id": "a89b6e81-a1ff-4d13-a9e2-17176e707aac", + "endorserDid": "endorser321", + "authorDid": "author654", + "requestPayload": "{\"type\": \"dummy_request_3\"}", + "responsePayload": "{\"type\": \"dummy_response_3\"}", + "status": "Declined", + "ecosystemOrgId": "a2443e09-45be-4739-b8b3-0d4ffaecea94" + }, + { + "id": "f47ac10b-58cc-4372-a567-0e02b2c3d47c", + "endorserDid": "endorser999", + "authorDid": "author777", + "requestPayload": "{\"type\": \"dummy_request_4\"}", + "responsePayload": "{\"type\": \"dummy_response_4\"}", + "status": "Submitted", + "ecosystemOrgId": "ca6ee687-a3a9-42ce-9e49-02bf62f5c93a" + } ] } \ No newline at end of file diff --git a/libs/prisma-service/prisma/migrations/20231007072334_endorsement_transaction/migration.sql b/libs/prisma-service/prisma/migrations/20231007072334_endorsement_transaction/migration.sql new file mode 100644 index 000000000..1d10effd7 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231007072334_endorsement_transaction/migration.sql @@ -0,0 +1,15 @@ +-- CreateTable +CREATE TABLE "endorsement_transaction" ( + "id" TEXT NOT NULL, + "endorserDid" VARCHAR, + "authorDid" VARCHAR, + "requestPayload" VARCHAR, + "responsePayload" VARCHAR, + "status" VARCHAR, + "ecosystemOrgId" VARCHAR, + + CONSTRAINT "endorsement_transaction_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "endorsement_transaction" ADD CONSTRAINT "endorsement_transaction_ecosystemOrgId_fkey" FOREIGN KEY ("ecosystemOrgId") REFERENCES "ecosystem_orgs"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index ca36b915b..660400637 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -378,5 +378,17 @@ model ecosystem_orgs { lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) lastChangedBy Int @default(1) deletedAt DateTime? @db.Timestamp(6) + endorseTransaction endorsement_transaction[] +} + +model endorsement_transaction { + id String @id @default(uuid()) + endorserDid String? @db.VarChar + authorDid String? @db.VarChar + requestPayload String? @db.VarChar + responsePayload String? @db.VarChar + status String? @db.VarChar + ecosystemOrgId String? @db.VarChar + ecosystemOrgs ecosystem_orgs? @relation(fields: [ecosystemOrgId], references: [id]) } diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index cd50a00c6..61bdb0c76 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -126,17 +126,31 @@ const createEcosystemRoles = async (): Promise => { } }; +const createEndoresementTransactions = async (): Promise => { + try { + const { endorseData } = JSON.parse(configData); + const endoresments = await prisma.endorsement_transaction.createMany({ + data: endorseData + }); + + logger.log(endoresments); + } catch (e) { + logger.error('An error occurred seeding ecosystemRoles:', e); + } +}; + async function main(): Promise { - await createPlatformConfig(); - await createOrgRoles(); - await createAgentTypes(); - await createPlatformOrganization(); - await createPlatformUser(); - await createPlatformUserOrgRoles(); - await createOrgAgentTypes(); - await createLedger(); - await createEcosystemRoles(); + // await createPlatformConfig(); + // await createOrgRoles(); + // await createAgentTypes(); + // await createPlatformOrganization(); + // await createPlatformUser(); + // await createPlatformUserOrgRoles(); + // await createOrgAgentTypes(); + // await createLedger(); + // await createEcosystemRoles(); + await createEndoresementTransactions(); } From 057a0ead3b0d69b3043a2b007c77fad400e8f980 Mon Sep 17 00:00:00 2001 From: Nishad Date: Sat, 7 Oct 2023 18:46:16 +0530 Subject: [PATCH 113/162] Implemented type parameter in the GET API of endorser transactions Signed-off-by: Nishad --- .../dtos/get-all-endorsements.dto.ts | 12 ++++--- .../src/ecosystem/ecosystem.controller.ts | 4 ++- .../src/ecosystem/ecosystem.service.ts | 4 +-- .../interfaces/endorsements.interface.ts | 1 + apps/ecosystem/src/ecosystem.repository.ts | 18 ++++------- apps/ecosystem/src/ecosystem.service.ts | 10 +++--- libs/enum/src/enum.ts | 5 +++ .../migration.sql | 15 --------- .../migration.sql | 7 ++++ libs/prisma-service/prisma/schema.prisma | 22 ++++++++----- libs/prisma-service/prisma/seed.ts | 32 ++++++------------- 11 files changed, 62 insertions(+), 68 deletions(-) delete mode 100644 libs/prisma-service/prisma/migrations/20231007072334_endorsement_transaction/migration.sql create mode 100644 libs/prisma-service/prisma/migrations/20231007125030_endorse_transaction_type_date/migration.sql diff --git a/apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts index 96f59a857..a0878bf15 100644 --- a/apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts +++ b/apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts @@ -3,7 +3,8 @@ import { Transform, Type } from 'class-transformer'; import { toNumber } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import { IsEnum, IsOptional, IsString } from 'class-validator'; +import { EndorserTransactionType } from '@credebl/enum/enum'; export class GetAllEndorsementsDto { @ApiProperty({ required: false, default: 1 }) @@ -14,6 +15,7 @@ export class GetAllEndorsementsDto { @ApiProperty({ required: false }) @IsOptional() + @IsString() @Type(() => String) search = ''; @@ -23,9 +25,11 @@ export class GetAllEndorsementsDto { @Transform(({ value }) => toNumber(value)) pageSize = 10; - @ApiProperty({ required: false }) + @ApiProperty({ + enum: [EndorserTransactionType.SCHEMA, EndorserTransactionType.CREDENTIAL_DEFINITION] + }) @IsOptional() - @IsString() - status; + @IsEnum(EndorserTransactionType) + type: EndorserTransactionType.SCHEMA | EndorserTransactionType.CREDENTIAL_DEFINITION; } diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 2959a4a19..c62b87ce6 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -108,8 +108,10 @@ export class EcosystemController { @Get('/:ecosystemId/:orgId/endorsement-transactions') @ApiOperation({ summary: 'Get all endorsement transactions', description: 'Get all endorsement transactions' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @ApiQuery({ name: 'pageNumber', type: Number, diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 716337353..d8416fca0 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -105,8 +105,8 @@ export class EcosystemService extends BaseService { orgId: string, getAllEndorsements: GetAllEndorsementsDto ): Promise<{ response: object }> { - const { pageNumber, pageSize, search, status } = getAllEndorsements; - const payload = { ecosystemId, orgId, pageNumber, pageSize, search, status }; + const { pageNumber, pageSize, search, type } = getAllEndorsements; + const payload = { ecosystemId, orgId, pageNumber, pageSize, search, type }; return this.sendNats(this.serviceProxy, 'get-endorsement-transactions', payload); } diff --git a/apps/ecosystem/interfaces/endorsements.interface.ts b/apps/ecosystem/interfaces/endorsements.interface.ts index b16259cee..d4c837c06 100644 --- a/apps/ecosystem/interfaces/endorsements.interface.ts +++ b/apps/ecosystem/interfaces/endorsements.interface.ts @@ -5,4 +5,5 @@ export interface GetEndorsementsPayload { pageNumber: number; pageSize: number; search: string; + type: string; } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index b941b5f54..0de7ad1f0 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -336,7 +336,7 @@ export class EcosystemRepository { } - async getEndorsementsWithPagination(queryObject: object, filterOptions: object, pageNumber: number, pageSize: number): Promise { + async getEndorsementsWithPagination(queryObject: object, pageNumber: number, pageSize: number): Promise { try { const result = await this.prisma.$transaction([ this.prisma.endorsement_transaction.findMany({ @@ -348,18 +348,14 @@ export class EcosystemRepository { endorserDid: true, authorDid: true, status: true, - ecosystemOrgs: { - where: { - ...filterOptions - // Additional filtering conditions if needed - } - } + type: true, + ecosystemOrgs: true }, take: pageSize, - skip: (pageNumber - 1) * pageSize - // orderBy: { - // createDateTime: 'desc' - // } + skip: (pageNumber - 1) * pageSize, + orderBy: { + createDateTime: 'desc' + } }), this.prisma.endorsement_transaction.count({ where: { diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index c797c90b9..f511d1283 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -294,7 +294,7 @@ export class EcosystemService { endorserDid: ecosystemLeadAgentDetails.orgDid, authorDid: agentDetails.orgDid, requestPayload: schemaTransactionRequest.message.schemaState.schemaRequest, - status: "Requested", + status: 'Requested', ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id }; return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse); @@ -448,7 +448,7 @@ export class EcosystemService { } async getEndorsementTransactions(payload: GetEndorsementsPayload): Promise { - const {ecosystemId, orgId, pageNumber, pageSize, search} = payload; + const {ecosystemId, orgId, pageNumber, pageSize, search, type } = payload; try { const query = { @@ -462,9 +462,11 @@ export class EcosystemService { ] }; - const filterOptions = {}; + if (type) { + query['type'] = type; + } - return await this.ecosystemRepository.getEndorsementsWithPagination(query, filterOptions, pageNumber, pageSize); + return await this.ecosystemRepository.getEndorsementsWithPagination(query, pageNumber, pageSize); } catch (error) { this.logger.error(`In error getEndorsementTransactions: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); diff --git a/libs/enum/src/enum.ts b/libs/enum/src/enum.ts index 94ac465ea..e47471927 100644 --- a/libs/enum/src/enum.ts +++ b/libs/enum/src/enum.ts @@ -19,6 +19,11 @@ export enum EcosystemRoles { ECOSYSTEM_OWNER = 'Ecosystem Owner' } +export enum EndorserTransactionType{ + SCHEMA = 'schema', + CREDENTIAL_DEFINITION = 'credential-definition', +} + export enum OrgAgentType { DEDICATED = 1, SHARED = 2 diff --git a/libs/prisma-service/prisma/migrations/20231007072334_endorsement_transaction/migration.sql b/libs/prisma-service/prisma/migrations/20231007072334_endorsement_transaction/migration.sql deleted file mode 100644 index 1d10effd7..000000000 --- a/libs/prisma-service/prisma/migrations/20231007072334_endorsement_transaction/migration.sql +++ /dev/null @@ -1,15 +0,0 @@ --- CreateTable -CREATE TABLE "endorsement_transaction" ( - "id" TEXT NOT NULL, - "endorserDid" VARCHAR, - "authorDid" VARCHAR, - "requestPayload" VARCHAR, - "responsePayload" VARCHAR, - "status" VARCHAR, - "ecosystemOrgId" VARCHAR, - - CONSTRAINT "endorsement_transaction_pkey" PRIMARY KEY ("id") -); - --- AddForeignKey -ALTER TABLE "endorsement_transaction" ADD CONSTRAINT "endorsement_transaction_ecosystemOrgId_fkey" FOREIGN KEY ("ecosystemOrgId") REFERENCES "ecosystem_orgs"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/libs/prisma-service/prisma/migrations/20231007125030_endorse_transaction_type_date/migration.sql b/libs/prisma-service/prisma/migrations/20231007125030_endorse_transaction_type_date/migration.sql new file mode 100644 index 000000000..472616297 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231007125030_endorse_transaction_type_date/migration.sql @@ -0,0 +1,7 @@ +-- AlterTable +ALTER TABLE "endorsement_transaction" ADD COLUMN "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "createdBy" INTEGER NOT NULL DEFAULT 1, +ADD COLUMN "deletedAt" TIMESTAMP(6), +ADD COLUMN "lastChangedBy" INTEGER NOT NULL DEFAULT 1, +ADD COLUMN "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "type" TEXT; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 5637e59d0..45aa68dd9 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -382,12 +382,18 @@ model ecosystem_orgs { } model endorsement_transaction { - id String @id @default(uuid()) - endorserDid String - authorDid String - requestPayload String - responsePayload String - status String - ecosystemOrgId String - ecosystemOrgs ecosystem_orgs @relation(fields: [ecosystemOrgId], references: [id]) + id String @id @default(uuid()) + endorserDid String + authorDid String + requestPayload String + responsePayload String + type String? + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) + status String + ecosystemOrgId String + ecosystemOrgs ecosystem_orgs @relation(fields: [ecosystemOrgId], references: [id]) } diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index 61bdb0c76..cd50a00c6 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -126,31 +126,17 @@ const createEcosystemRoles = async (): Promise => { } }; -const createEndoresementTransactions = async (): Promise => { - try { - const { endorseData } = JSON.parse(configData); - const endoresments = await prisma.endorsement_transaction.createMany({ - data: endorseData - }); - - logger.log(endoresments); - } catch (e) { - logger.error('An error occurred seeding ecosystemRoles:', e); - } -}; - async function main(): Promise { - // await createPlatformConfig(); - // await createOrgRoles(); - // await createAgentTypes(); - // await createPlatformOrganization(); - // await createPlatformUser(); - // await createPlatformUserOrgRoles(); - // await createOrgAgentTypes(); - // await createLedger(); - // await createEcosystemRoles(); - await createEndoresementTransactions(); + await createPlatformConfig(); + await createOrgRoles(); + await createAgentTypes(); + await createPlatformOrganization(); + await createPlatformUser(); + await createPlatformUserOrgRoles(); + await createOrgAgentTypes(); + await createLedger(); + await createEcosystemRoles(); } From 014c218d57537e8a55dd9692671f6e5862a75e35 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Sat, 7 Oct 2023 19:25:46 +0530 Subject: [PATCH 114/162] feat: get endorsement transactions (#131) * worked on the GET API for endorsement transactions Signed-off-by: Nishad * Implemented type parameter in the GET API of endorser transactions Signed-off-by: Nishad --------- Signed-off-by: Nishad --- .../dtos/get-all-endorsements.dto.ts | 35 +++++++++++++++ .../src/ecosystem/ecosystem.controller.ts | 39 ++++++++++++++++ .../src/ecosystem/ecosystem.service.ts | 11 +++++ .../interfaces/endorsements.interface.ts | 9 ++++ apps/ecosystem/src/ecosystem.controller.ts | 12 ++--- apps/ecosystem/src/ecosystem.repository.ts | 45 ++++++++++++++++++- apps/ecosystem/src/ecosystem.service.ts | 28 +++++++++++- libs/enum/src/enum.ts | 5 +++ .../prisma/data/credebl-master-table.json | 38 ++++++++++++++++ .../migration.sql | 7 +++ libs/prisma-service/prisma/schema.prisma | 22 +++++---- 11 files changed, 235 insertions(+), 16 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts create mode 100644 apps/ecosystem/interfaces/endorsements.interface.ts create mode 100644 libs/prisma-service/prisma/migrations/20231007125030_endorse_transaction_type_date/migration.sql diff --git a/apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts new file mode 100644 index 000000000..a0878bf15 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts @@ -0,0 +1,35 @@ + +import { Transform, Type } from 'class-transformer'; +import { toNumber } from '@credebl/common/cast.helper'; + +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsOptional, IsString } from 'class-validator'; +import { EndorserTransactionType } from '@credebl/enum/enum'; + +export class GetAllEndorsementsDto { + @ApiProperty({ required: false, default: 1 }) + @IsOptional() + @Type(() => Number) + @Transform(({ value }) => toNumber(value)) + pageNumber = 1; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + @Type(() => String) + search = ''; + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => Number) + @Transform(({ value }) => toNumber(value)) + pageSize = 10; + + @ApiProperty({ + enum: [EndorserTransactionType.SCHEMA, EndorserTransactionType.CREDENTIAL_DEFINITION] + }) + @IsOptional() + @IsEnum(EndorserTransactionType) + type: EndorserTransactionType.SCHEMA | EndorserTransactionType.CREDENTIAL_DEFINITION; + +} diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index cca833bf4..c62b87ce6 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -27,6 +27,7 @@ import { EcosystemRolesGuard } from '../authz/guards/ecosystem-roles.guard'; import { EcosystemsRoles, Roles } from '../authz/decorators/roles.decorator'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { OrgRoles } from 'libs/org-roles/enums'; +import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; import { CreateEcosystemDto } from './dtos/create-ecosystem-dto'; @@ -104,6 +105,44 @@ export class EcosystemController { } + @Get('/:ecosystemId/:orgId/endorsement-transactions') + @ApiOperation({ summary: 'Get all endorsement transactions', description: 'Get all endorsement transactions' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getEndorsementTranasactions( + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @Query() getAllEndorsementsDto: GetAllEndorsementsDto, + @Res() res: Response + ): Promise { + const ecosystemList = await this.ecosystemService.getEndorsementTranasactions(ecosystemId, orgId, getAllEndorsementsDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: `Endorser transactions fetched successfully`, + data: ecosystemList.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + @Get('/:ecosystemId/:orgId/invitations') @ApiOperation({ summary: 'Get all sent invitations', description: 'Get all sent invitations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index b3bf387fa..d8416fca0 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -6,6 +6,7 @@ import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; import { AcceptRejectEcosystemInvitationDto } from './dtos/accept-reject-ecosysteminvitation-dto'; import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.dto'; import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; +import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; import { RequestSchemaDto } from './dtos/request-schema-dto'; @@ -99,6 +100,16 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'fetch-ecosystem-org-data', payload); } + async getEndorsementTranasactions( + ecosystemId: string, + orgId: string, + getAllEndorsements: GetAllEndorsementsDto + ): Promise<{ response: object }> { + const { pageNumber, pageSize, search, type } = getAllEndorsements; + const payload = { ecosystemId, orgId, pageNumber, pageSize, search, type }; + return this.sendNats(this.serviceProxy, 'get-endorsement-transactions', payload); + } + async schemaEndorsementRequest(requestSchemaPayload: RequestSchemaDto, orgId: number): Promise { const payload = { requestSchemaPayload, orgId}; diff --git a/apps/ecosystem/interfaces/endorsements.interface.ts b/apps/ecosystem/interfaces/endorsements.interface.ts new file mode 100644 index 000000000..d4c837c06 --- /dev/null +++ b/apps/ecosystem/interfaces/endorsements.interface.ts @@ -0,0 +1,9 @@ +export interface GetEndorsementsPayload { + ecosystemId: string; + orgId: string; + status: string; + pageNumber: number; + pageSize: number; + search: string; + type: string; + } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 17960c4a1..f12ea629f 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -6,6 +6,7 @@ import { Body } from '@nestjs/common'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; import { AcceptRejectEcosystemInvitationDto } from '../dtos/accept-reject-ecosysteminvitation.dto'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; +import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; import { RequestSchemaEndorsement } from '../interfaces/ecosystem.interfaces'; @Controller() @@ -97,14 +98,15 @@ export class EcosystemController { ); } - @MessagePattern({ cmd: 'fetch-ecosystem-org-data' }) - async fetchEcosystemOrg( - @Body() payload: { ecosystemId: string, orgId: string} + @MessagePattern({ cmd: 'get-endorsement-transactions' }) + async getEndorsementTransactions( + @Body() payload: GetEndorsementsPayload ): Promise { - return this.ecosystemService.fetchEcosystemOrg( + return this.ecosystemService.getEndorsementTransactions( payload ); - } + } + /** * diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index d5dad6814..0de7ad1f0 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -336,7 +336,48 @@ export class EcosystemRepository { } - /** + async getEndorsementsWithPagination(queryObject: object, pageNumber: number, pageSize: number): Promise { + try { + const result = await this.prisma.$transaction([ + this.prisma.endorsement_transaction.findMany({ + where: { + ...queryObject + }, + select:{ + id:true, + endorserDid: true, + authorDid: true, + status: true, + type: true, + ecosystemOrgs: true + }, + take: pageSize, + skip: (pageNumber - 1) * pageSize, + orderBy: { + createDateTime: 'desc' + } + }), + this.prisma.endorsement_transaction.count({ + where: { + ...queryObject + } + }) + ]); + + // eslint-disable-next-line prefer-destructuring + const transactions = result[0]; + // eslint-disable-next-line prefer-destructuring + const totalCount = result[1]; + const totalPages = Math.ceil(totalCount / pageSize); + + return { totalPages, transactions }; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + +/** * Description: Get getAgentEndPoint by orgId * @param orgId * @returns Get getAgentEndPoint details @@ -419,6 +460,7 @@ export class EcosystemRepository { throw new InternalServerErrorException(error); } } + // eslint-disable-next-line camelcase async getEcosystemOrgDetailsbyId(orgId: string): Promise { try { @@ -481,5 +523,4 @@ export class EcosystemRepository { } } - } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 2f2e358d7..f511d1283 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -12,6 +12,7 @@ import { AcceptRejectEcosystemInvitationDto } from '../dtos/accept-reject-ecosys import { Invitation, OrgAgentType } from '@credebl/enum/enum'; import { EcosystemOrgStatus, EcosystemRoles } from '../enums/ecosystem.enum'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; +import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; import { RequestSchemaEndorsement, SchemaMessage, SchemaTransactionPayload, SchemaTransactionResponse, SignedTransactionMessage } from '../interfaces/ecosystem.interfaces'; // eslint-disable-next-line camelcase import { platform_config } from '@prisma/client'; @@ -293,7 +294,7 @@ export class EcosystemService { endorserDid: ecosystemLeadAgentDetails.orgDid, authorDid: agentDetails.orgDid, requestPayload: schemaTransactionRequest.message.schemaState.schemaRequest, - status: "Requested", + status: 'Requested', ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id }; return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse); @@ -446,5 +447,30 @@ export class EcosystemService { return platformConfigData[0].enableEcosystem; } + async getEndorsementTransactions(payload: GetEndorsementsPayload): Promise { + const {ecosystemId, orgId, pageNumber, pageSize, search, type } = payload; + try { + + const query = { + ecosystemOrgs: { + ecosystemId, + orgId + }, + OR: [ + { status: { contains: search, mode: 'insensitive' } }, + { authorDid: { contains: search, mode: 'insensitive' } } + ] + }; + + if (type) { + query['type'] = type; + } + + return await this.ecosystemRepository.getEndorsementsWithPagination(query, pageNumber, pageSize); + } catch (error) { + this.logger.error(`In error getEndorsementTransactions: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } } diff --git a/libs/enum/src/enum.ts b/libs/enum/src/enum.ts index 94ac465ea..e47471927 100644 --- a/libs/enum/src/enum.ts +++ b/libs/enum/src/enum.ts @@ -19,6 +19,11 @@ export enum EcosystemRoles { ECOSYSTEM_OWNER = 'Ecosystem Owner' } +export enum EndorserTransactionType{ + SCHEMA = 'schema', + CREDENTIAL_DEFINITION = 'credential-definition', +} + export enum OrgAgentType { DEDICATED = 1, SHARED = 2 diff --git a/libs/prisma-service/prisma/data/credebl-master-table.json b/libs/prisma-service/prisma/data/credebl-master-table.json index a3966a6ca..5053c4d65 100644 --- a/libs/prisma-service/prisma/data/credebl-master-table.json +++ b/libs/prisma-service/prisma/data/credebl-master-table.json @@ -107,5 +107,43 @@ "registerDIDPayload": "", "indyNamespace": "indicio:testnet" } + ], + "endorseData": [ + { + "id": "0f8fad5b-d9cb-469f-a165-70867728950f", + "endorserDid": "endorser123", + "authorDid": "author456", + "requestPayload": "{\"type\": \"dummy_request_1\"}", + "responsePayload": "{\"type\": \"dummy_response_1\"}", + "status": "Requested", + "ecosystemOrgId": "1c247b4a-e2f6-48c0-8aa2-65ea47474294" + }, + { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "endorserDid": "endorser789", + "authorDid": "author101", + "requestPayload": "{\"type\": \"dummy_request_2\"}", + "responsePayload": "{\"type\": \"dummy_response_2\"}", + "status": "Signed", + "ecosystemOrgId": "1c247b4a-e2f6-48c0-8aa2-65ea47474294" + }, + { + "id": "a89b6e81-a1ff-4d13-a9e2-17176e707aac", + "endorserDid": "endorser321", + "authorDid": "author654", + "requestPayload": "{\"type\": \"dummy_request_3\"}", + "responsePayload": "{\"type\": \"dummy_response_3\"}", + "status": "Declined", + "ecosystemOrgId": "a2443e09-45be-4739-b8b3-0d4ffaecea94" + }, + { + "id": "f47ac10b-58cc-4372-a567-0e02b2c3d47c", + "endorserDid": "endorser999", + "authorDid": "author777", + "requestPayload": "{\"type\": \"dummy_request_4\"}", + "responsePayload": "{\"type\": \"dummy_response_4\"}", + "status": "Submitted", + "ecosystemOrgId": "ca6ee687-a3a9-42ce-9e49-02bf62f5c93a" + } ] } \ No newline at end of file diff --git a/libs/prisma-service/prisma/migrations/20231007125030_endorse_transaction_type_date/migration.sql b/libs/prisma-service/prisma/migrations/20231007125030_endorse_transaction_type_date/migration.sql new file mode 100644 index 000000000..472616297 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231007125030_endorse_transaction_type_date/migration.sql @@ -0,0 +1,7 @@ +-- AlterTable +ALTER TABLE "endorsement_transaction" ADD COLUMN "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "createdBy" INTEGER NOT NULL DEFAULT 1, +ADD COLUMN "deletedAt" TIMESTAMP(6), +ADD COLUMN "lastChangedBy" INTEGER NOT NULL DEFAULT 1, +ADD COLUMN "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "type" TEXT; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 5637e59d0..45aa68dd9 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -382,12 +382,18 @@ model ecosystem_orgs { } model endorsement_transaction { - id String @id @default(uuid()) - endorserDid String - authorDid String - requestPayload String - responsePayload String - status String - ecosystemOrgId String - ecosystemOrgs ecosystem_orgs @relation(fields: [ecosystemOrgId], references: [id]) + id String @id @default(uuid()) + endorserDid String + authorDid String + requestPayload String + responsePayload String + type String? + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) + status String + ecosystemOrgId String + ecosystemOrgs ecosystem_orgs @relation(fields: [ecosystemOrgId], references: [id]) } From 8d78016f2d814f351eaec635cda16ce530802062 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Sun, 8 Oct 2023 22:04:25 +0530 Subject: [PATCH 115/162] feat:endorser sumbit transaction Signed-off-by: tipusinghaw --- .../src/agent-service.controller.ts | 4 + .../src/agent-service.service.ts | 14 ++ .../src/ecosystem/ecosystem.controller.ts | 17 ++- .../src/ecosystem/ecosystem.service.ts | 5 + .../interfaces/ecosystem.interfaces.ts | 13 ++ apps/ecosystem/src/ecosystem.controller.ts | 12 ++ apps/ecosystem/src/ecosystem.repository.ts | 29 +++- apps/ecosystem/src/ecosystem.service.ts | 134 +++++++++++++----- 8 files changed, 189 insertions(+), 39 deletions(-) diff --git a/apps/agent-service/src/agent-service.controller.ts b/apps/agent-service/src/agent-service.controller.ts index 38c658264..8b1b4dfce 100644 --- a/apps/agent-service/src/agent-service.controller.ts +++ b/apps/agent-service/src/agent-service.controller.ts @@ -116,4 +116,8 @@ export class AgentServiceController { async signTransaction(payload: { url: string, apiKey: string, signEndorsementPayload:string }): Promise { return this.agentServiceService.signTransaction(payload.url, payload.apiKey, payload.signEndorsementPayload); } + @MessagePattern({ cmd: 'agent-submit-transaction' }) + async submitTransaction(payload: { url: string, apiKey: string, submitEndorsementPayload:string }): Promise { + return this.agentServiceService.sumbitTransaction(payload.url, payload.apiKey, payload.submitEndorsementPayload); + } } diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index ebf066249..26d2c25ed 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -915,5 +915,19 @@ export class AgentServiceService { } } + async sumbitTransaction(url: string, apiKey: string, submitEndorsementPayload: string): Promise { + try { + + const signEndorsementTransaction = await this.commonService + .httpPost(url, submitEndorsementPayload, { headers: { 'x-api-key': apiKey } }) + .then(async response => response); + + return signEndorsementTransaction; + } catch (error) { + this.logger.error(`Error in sumbit transaction in agent service : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + } diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index cca833bf4..c82a1799c 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -172,7 +172,7 @@ export class EcosystemController { @Post('/:orgId/transaction/schema') @ApiOperation({ summary: 'Request new schema', description: 'Request new schema' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) + // @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() async requestSchemaTransaction(@Body() requestSchemaPayload: RequestSchemaDto, @Param('orgId') orgId: number, @Res() res: Response): Promise { await this.ecosystemService.schemaEndorsementRequest(requestSchemaPayload, orgId); @@ -186,7 +186,7 @@ export class EcosystemController { @Post('transaction/sign/:endorsementId') @ApiOperation({ summary: 'Sign transaction', description: 'Sign transaction' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) + // @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() async SignEndorsementRequests(@Param('endorsementId') endorsementId: string, @Res() res: Response): Promise { await this.ecosystemService.signTransaction(endorsementId); @@ -197,6 +197,19 @@ export class EcosystemController { return res.status(HttpStatus.CREATED).json(finalResponse); } + @Post('transaction/sumbit/:endorsementId') + @ApiOperation({ summary: 'Sumbit transaction', description: 'Sumbit transaction' }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + // @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + async SumbitEndorsementRequests(@Param('endorsementId') endorsementId: string, @Res() res: Response): Promise { + await this.ecosystemService.submitTransaction(endorsementId); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.sign + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } /** * * @param bulkInvitationDto diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index b3bf387fa..e63ed0bd4 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -110,4 +110,9 @@ export class EcosystemService extends BaseService { const payload = { endorsementId }; return this.sendNats(this.serviceProxy, 'sign-endorsement-transaction', payload); } + + async submitTransaction(endorsementId:string): Promise { + const payload = { endorsementId }; + return this.sendNats(this.serviceProxy, 'sumbit-endorsement-transaction', payload); + } } diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts index fb0e27946..7657ef28c 100644 --- a/apps/ecosystem/interfaces/ecosystem.interfaces.ts +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -49,3 +49,16 @@ export interface SignedTransactionMessage { signedTransaction: string; }; } + +export interface EndorsementTransactionPayload { + id: string; + endorserDid: string; + authorDid: string; + requestPayload: string; + responsePayload: string; + status: string; + ecosystemOrgId: string; + ecosystemOrgs?: { + orgId: string; + }; +} diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 17960c4a1..6b0cda69d 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -129,4 +129,16 @@ export class EcosystemController { ): Promise { return this.ecosystemService.signTransaction(payload.endorsementId); } + + /** + * + * @param payload + * @returns submit endorsement request + */ + @MessagePattern({ cmd: 'sumbit-endorsement-transaction' }) + async submitTransaction( + @Body() payload: { endorsementId: string } + ): Promise { + return this.ecosystemService.submitTransaction(payload.endorsementId); + } } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index d5dad6814..b49e09216 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -422,6 +422,7 @@ export class EcosystemRepository { // eslint-disable-next-line camelcase async getEcosystemOrgDetailsbyId(orgId: string): Promise { try { + //need to change const ecosystemLeadDetails = await this.prisma.ecosystem_orgs.findFirst({ where: { orgId @@ -435,12 +436,12 @@ export class EcosystemRepository { } } // eslint-disable-next-line camelcase - async getEndorsementTransactionById(endorsementId: string): Promise { + async getEndorsementTransactionById(endorsementId: string, status:endorsementTransactionStatus): Promise { try { const ecosystemLeadDetails = await this.prisma.endorsement_transaction.findFirst({ where: { id: endorsementId, - status: endorsementTransactionStatus.REQUESTED + status }, include: { ecosystemOrgs: { @@ -469,7 +470,29 @@ export class EcosystemRepository { const updatedTransaction = await this.prisma.endorsement_transaction.update({ where: { id: endorsementId }, data: { - responsePayload: schemaTransactionRequest + responsePayload: schemaTransactionRequest, + status: endorsementTransactionStatus.SIGNED + } + }); + + return updatedTransaction; + + } catch (error) { + this.logger.error(`Error in updating endorsement transaction: ${error.message}`); + throw error; + } + } + + async updateTransactionStatus( + endorsementId: string, + status:endorsementTransactionStatus + // eslint-disable-next-line camelcase, + ): Promise { + try { + const updatedTransaction = await this.prisma.endorsement_transaction.update({ + where: { id: endorsementId }, + data: { + status } }); diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 2f2e358d7..5013237b8 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -10,9 +10,9 @@ 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 { Invitation, OrgAgentType } from '@credebl/enum/enum'; -import { EcosystemOrgStatus, EcosystemRoles } from '../enums/ecosystem.enum'; +import { EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus } from '../enums/ecosystem.enum'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; -import { RequestSchemaEndorsement, SchemaMessage, SchemaTransactionPayload, SchemaTransactionResponse, SignedTransactionMessage } from '../interfaces/ecosystem.interfaces'; +import { EndorsementTransactionPayload, RequestSchemaEndorsement, SchemaMessage, SchemaTransactionPayload, SchemaTransactionResponse, SignedTransactionMessage } from '../interfaces/ecosystem.interfaces'; // eslint-disable-next-line camelcase import { platform_config } from '@prisma/client'; import { CommonConstants } from '@credebl/common/common.constant'; @@ -65,13 +65,13 @@ export class EcosystemService { */ // eslint-disable-next-line camelcase - async getAllEcosystem(payload: {orgId: string}): Promise { - const getAllEcosystemDetails = await this.ecosystemRepository.getAllEcosystemDetails(payload.orgId); - if (!getAllEcosystemDetails) { - throw new NotFoundException(ResponseMessages.ecosystem.error.update); - } - return getAllEcosystemDetails; - } + async getAllEcosystem(payload: { orgId: string }): Promise { + const getAllEcosystemDetails = await this.ecosystemRepository.getAllEcosystemDetails(payload.orgId); + if (!getAllEcosystemDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.update); + } + return getAllEcosystemDetails; + } /** @@ -141,14 +141,14 @@ export class EcosystemService { try { const { orgId, status, invitationId } = acceptRejectInvitation; const invitation = await this.ecosystemRepository.getEcosystemInvitationById(invitationId); - + if (!invitation) { throw new NotFoundException(ResponseMessages.ecosystem.error.invitationNotFound); } const updatedInvitation = await this.updateEcosystemInvitation(invitationId, orgId, status); if (!updatedInvitation) { - throw new NotFoundException(ResponseMessages.ecosystem.error.invitationNotUpdate); + throw new NotFoundException(ResponseMessages.ecosystem.error.invitationNotUpdate); } if (status === Invitation.REJECTED) { @@ -162,13 +162,13 @@ export class EcosystemService { throw new NotFoundException(ResponseMessages.ecosystem.error.orgsNotUpdate); } return ResponseMessages.ecosystem.success.invitationAccept; - + } catch (error) { this.logger.error(`acceptRejectInvitations: ${error}`); throw new RpcException(error.response ? error.response : error); } } - + async updatedEcosystemOrgs(orgId: string, ecosystemId: string, ecosystemRoleId: string): Promise { try { const data = { @@ -191,7 +191,7 @@ export class EcosystemService { */ async updateEcosystemInvitation(invitationId: string, orgId: string, status: string): Promise { try { - + const data = { status, orgId: String(orgId) @@ -280,12 +280,12 @@ export class EcosystemService { const getEcosystemOrgDetailsByOrgId = await this.ecosystemRepository.getEcosystemOrgDetailsbyId(String(orgId)); const schemaTransactionPayload: SchemaTransactionPayload = { - endorserDid: ecosystemLeadAgentDetails.orgDid, + endorserDid: 'did:indy:indicio:testnet:23FTzrmb5QUxTiZUmueJbY', endorse: requestSchemaPayload.endorse, attributes: attributeArray, version: String(requestSchemaPayload.version), name: requestSchemaPayload.name, - issuerId: agentDetails.orgDid + issuerId: 'did:indy:indicio:testnet:G8jvtxj59GJRznz4Kk426u' }; const schemaTransactionRequest: SchemaMessage = await this._requestSchemaEndorsement(schemaTransactionPayload, url, platformConfig?.sgApiKey); @@ -293,30 +293,30 @@ export class EcosystemService { endorserDid: ecosystemLeadAgentDetails.orgDid, authorDid: agentDetails.orgDid, requestPayload: schemaTransactionRequest.message.schemaState.schemaRequest, - status: "Requested", + status: endorsementTransactionStatus.REQUESTED, ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id }; return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse); } catch (error) { this.logger.error(`In request schema endorsement : ${JSON.stringify(error)}`); - + throw new RpcException(error.response ? error.response : error); } } async getInvitationsByEcosystemId( payload: FetchInvitationsPayload - ): Promise { - try { - - const { ecosystemId, pageNumber, pageSize, search} = payload; - const ecosystemInvitations = await this.ecosystemRepository.getInvitationsByEcosystemId(ecosystemId, pageNumber, pageSize, search); - return ecosystemInvitations; - } catch (error) { - this.logger.error(`In getInvitationsByEcosystemId : ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); - } - } + ): Promise { + try { + + const { ecosystemId, pageNumber, pageSize, search } = payload; + const ecosystemInvitations = await this.ecosystemRepository.getInvitationsByEcosystemId(ecosystemId, pageNumber, pageSize, search); + return ecosystemInvitations; + } catch (error) { + this.logger.error(`In getInvitationsByEcosystemId : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } /** * Description: Store shortening URL * @param referenceId @@ -342,7 +342,7 @@ export class EcosystemService { async signTransaction(endorsementId: string): Promise { try { - const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId); + const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.REQUESTED); const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(); // eslint-disable-next-line camelcase @@ -357,7 +357,7 @@ export class EcosystemService { const payload = { transaction: JSON.stringify(parsedRequestPayload), - endorserDid: endorsementTransactionPayload.endorserDid + endorserDid: 'did:indy:indicio:testnet:23FTzrmb5QUxTiZUmueJbY' }; const schemaTransactionRequest: SignedTransactionMessage = await this._signTransaction(payload, url, platformConfig.sgApiKey); @@ -392,6 +392,72 @@ export class EcosystemService { } } + async submitTransaction(endorsementId: string): Promise { + try { + const endorsementTransactionPayload: EndorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.SIGNED); + + const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(Number(endorsementTransactionPayload.ecosystemOrgs.orgId)); + + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(); + // eslint-disable-next-line camelcase + const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); + + const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); + + const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint); + + this.logger.log(url); + + const apiUrl = `http://localhost:6002/transactions/write`; + + const parsedRequestPayload = JSON.parse(endorsementTransactionPayload.requestPayload); + + const payload = { + transaction: JSON.stringify(parsedRequestPayload), + endorserDid: ecosystemLeadAgentDetails.orgDid, + schema: { + attributes: parsedRequestPayload.operation.data.attr_names, + version: parsedRequestPayload.operation.data.version, + name: parsedRequestPayload.operation.data.name, + issuerId: parsedRequestPayload.identifier + } + }; + + const submitTransactionRequest = await this._submitTransaction(payload, apiUrl, platformConfig.sgApiKey); + if (submitTransactionRequest) { + return this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SUBMITED); + + } + + } catch (error) { + this.logger.error(`In sumit transaction : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + /** + * Description: Store shortening URL + * @param signEndorsementPayload + * @param url + * @returns sign message + */ + async _submitTransaction(signEndorsementPayload: object, url: string, apiKey: string): Promise { + const pattern = { cmd: 'agent-submit-transaction' }; + const payload = { signEndorsementPayload, url, apiKey }; + + 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); + } + } + async getAgentUrl( orgAgentTypeId: number, @@ -420,12 +486,12 @@ export class EcosystemService { } } - + async fetchEcosystemOrg( - payload: { ecosystemId: string, orgId: string} + payload: { ecosystemId: string, orgId: string } ): Promise { - const isEcosystemEnabled = await this.checkEcosystemEnableFlag(); + const isEcosystemEnabled = await this.checkEcosystemEnableFlag(); if (!isEcosystemEnabled) { throw new ForbiddenException(ResponseMessages.ecosystem.error.ecosystemNotEnabled); From f9a2ec79d268310647dfc0f061b797d50a006ad4 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Mon, 9 Oct 2023 01:03:47 +0530 Subject: [PATCH 116/162] feat:endorser request cred-def transaction Signed-off-by: tipusinghaw --- .../src/agent-service.controller.ts | 4 + .../src/agent-service.service.ts | 12 ++ .../src/ecosystem/dtos/request-schema-dto.ts | 21 ++- .../src/ecosystem/ecosystem.controller.ts | 32 +++- .../src/ecosystem/ecosystem.service.ts | 8 +- apps/ecosystem/enums/ecosystem.enum.ts | 4 + .../interfaces/ecosystem.interfaces.ts | 37 ++++- apps/ecosystem/src/ecosystem.controller.ts | 14 +- apps/ecosystem/src/ecosystem.repository.ts | 12 +- apps/ecosystem/src/ecosystem.service.ts | 150 ++++++++++++++---- libs/common/src/common.constant.ts | 4 + libs/common/src/response-messages/index.ts | 9 +- 12 files changed, 263 insertions(+), 44 deletions(-) diff --git a/apps/agent-service/src/agent-service.controller.ts b/apps/agent-service/src/agent-service.controller.ts index 8b1b4dfce..cf97fdc36 100644 --- a/apps/agent-service/src/agent-service.controller.ts +++ b/apps/agent-service/src/agent-service.controller.ts @@ -111,6 +111,10 @@ export class AgentServiceController { 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); + } @MessagePattern({ cmd: 'agent-sign-transaction' }) async signTransaction(payload: { url: string, apiKey: string, signEndorsementPayload:string }): Promise { diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 26d2c25ed..56da91997 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -901,6 +901,18 @@ 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 } }) + .then(async response => response); + return credDefRequest; + } catch (error) { + this.logger.error(`Error in credential-definition endorsement request in agent service : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + async signTransaction(url: string, apiKey: string, signEndorsementPayload: string): Promise { try { diff --git a/apps/api-gateway/src/ecosystem/dtos/request-schema-dto.ts b/apps/api-gateway/src/ecosystem/dtos/request-schema-dto.ts index c2742fca2..0d0598f1f 100644 --- a/apps/api-gateway/src/ecosystem/dtos/request-schema-dto.ts +++ b/apps/api-gateway/src/ecosystem/dtos/request-schema-dto.ts @@ -1,5 +1,5 @@ import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsBoolean, IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; @ApiExtraModels() class AttributeValue { @@ -23,8 +23,8 @@ export class RequestSchemaDto { name: string; @ApiProperty() - @IsInt({ message: 'version must be in number format.' }) - version: number; + @IsString({ message: 'version must be in string format.' }) + version: string; @ApiProperty({ 'example': [ @@ -44,4 +44,19 @@ export class RequestSchemaDto { @IsOptional() endorse?: boolean; +} + +export class RequestCredDefDto { + @ApiProperty() + @IsBoolean({ message: 'endorse must be a boolean.' }) + @IsOptional() + endorse?: boolean; + + @ApiProperty() + @IsString({ message: 'tag must be in string format.' }) + tag: string; + + @ApiProperty() + @IsString({ message: 'schemaId must be in string format.' }) + schemaId: 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 b94577ac3..9a0573739 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -4,7 +4,7 @@ import { EcosystemService } from './ecosystem.service'; import { Post, Get } from '@nestjs/common'; import { Body } from '@nestjs/common'; import { Res } from '@nestjs/common'; -import { RequestSchemaDto } from './dtos/request-schema-dto'; +import { RequestCredDefDto, RequestSchemaDto } from './dtos/request-schema-dto'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { HttpStatus } from '@nestjs/common'; import { Response } from 'express'; @@ -211,8 +211,10 @@ export class EcosystemController { @Post('/:orgId/transaction/schema') @ApiOperation({ summary: 'Request new schema', description: 'Request new schema' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - // @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() + // @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + // @ApiBearerAuth() + // @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) async requestSchemaTransaction(@Body() requestSchemaPayload: RequestSchemaDto, @Param('orgId') orgId: number, @Res() res: Response): Promise { await this.ecosystemService.schemaEndorsementRequest(requestSchemaPayload, orgId); const finalResponse: IResponseType = { @@ -222,11 +224,29 @@ export class EcosystemController { return res.status(HttpStatus.CREATED).json(finalResponse); } + + @Post('/:orgId/transaction/cred-def') + @ApiOperation({ summary: 'Request new credential-definition', description: 'Request new credential-definition' }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + async requestCredDefTransaction(@Body() requestCredDefPayload: RequestCredDefDto, @Param('orgId') orgId: number, @Res() res: Response): Promise { + await this.ecosystemService.credDefEndorsementRequest(requestCredDefPayload, orgId); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.schemaRequest + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } @Post('transaction/sign/:endorsementId') @ApiOperation({ summary: 'Sign transaction', description: 'Sign transaction' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - // @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) async SignEndorsementRequests(@Param('endorsementId') endorsementId: string, @Res() res: Response): Promise { await this.ecosystemService.signTransaction(endorsementId); const finalResponse: IResponseType = { @@ -239,8 +259,10 @@ export class EcosystemController { @Post('transaction/sumbit/:endorsementId') @ApiOperation({ summary: 'Sumbit transaction', description: 'Sumbit transaction' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - // @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) async SumbitEndorsementRequests(@Param('endorsementId') endorsementId: string, @Res() res: Response): Promise { await this.ecosystemService.submitTransaction(endorsementId); const finalResponse: IResponseType = { diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 878e1ee59..6b5792444 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -7,7 +7,7 @@ import { AcceptRejectEcosystemInvitationDto } from './dtos/accept-reject-ecosyst import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.dto'; import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; -import { RequestSchemaDto } from './dtos/request-schema-dto'; +import { RequestCredDefDto, RequestSchemaDto } from './dtos/request-schema-dto'; @Injectable() @@ -116,7 +116,11 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'schema-endorsement-request', payload); } - + async credDefEndorsementRequest(requestCredDefPayload: RequestCredDefDto, orgId: number): Promise { + const payload = { requestCredDefPayload, orgId}; + return this.sendNats(this.serviceProxy, 'credDef-endorsement-request', payload); + } + async signTransaction(endorsementId:string): Promise { const payload = { endorsementId }; return this.sendNats(this.serviceProxy, 'sign-endorsement-transaction', payload); diff --git a/apps/ecosystem/enums/ecosystem.enum.ts b/apps/ecosystem/enums/ecosystem.enum.ts index ed3fe5199..9f4b9bee2 100644 --- a/apps/ecosystem/enums/ecosystem.enum.ts +++ b/apps/ecosystem/enums/ecosystem.enum.ts @@ -19,4 +19,8 @@ export enum endorsementTransactionStatus { SIGNED = 'Signed', DECLINED = 'Declined', SUBMITED = 'Submited' +} +export enum endorsementTransactionType { + SCHEMA = 'Schema', + CREDENTIAL_DEFINITION = 'Credential-definition' } \ No newline at end of file diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts index 7657ef28c..e6eec7e2b 100644 --- a/apps/ecosystem/interfaces/ecosystem.interfaces.ts +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -1,11 +1,17 @@ export interface RequestSchemaEndorsement { orgId: number name: string; - version: number; + version: string; attributes: IAttributeValue[]; endorse?: boolean; } +export interface RequestCredDeffEndorsement { + schemaId: string + tag: string; + endorse?: boolean; +} + export interface IAttributeValue { attributeName: string; schemaDataType: string; @@ -21,6 +27,14 @@ export interface SchemaTransactionPayload { issuerId: string; } +export interface CredDefTransactionPayload { + endorserDid: string; + endorse: boolean; + tag: string; + schemaId: string; + issuerId: string; +} + export interface SchemaMessage { message?: { jobId: string; @@ -58,7 +72,28 @@ export interface EndorsementTransactionPayload { responsePayload: string; status: string; ecosystemOrgId: string; + type: string; ecosystemOrgs?: { orgId: string; }; } + +interface SchemaPayload { + attributes: string[]; + version: string; + name: string; + issuerId: string; +} + +interface CredentialDefinitionPayload { + tag: string; + issuerId: string; + schemaId: string; +} + +export interface submitTransactionPayload { + endorsedTransaction: string; + endorserDid: string; + schema?: SchemaPayload; + credentialDefinition?: CredentialDefinitionPayload; +} diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index f9ddf4129..b5fa2ea7e 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -7,7 +7,7 @@ import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; import { AcceptRejectEcosystemInvitationDto } from '../dtos/accept-reject-ecosysteminvitation.dto'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; -import { RequestSchemaEndorsement } from '../interfaces/ecosystem.interfaces'; +import { RequestCredDeffEndorsement, RequestSchemaEndorsement } from '../interfaces/ecosystem.interfaces'; @Controller() export class EcosystemController { @@ -120,6 +120,18 @@ export class EcosystemController { return this.ecosystemService.requestSchemaEndorsement(payload.requestSchemaPayload, payload.orgId); } + /** + * + * @param payload + * @returns Schema endorsement request + */ + @MessagePattern({ cmd: 'credDef-endorsement-request' }) + async credDefEndorsementRequest( + @Body() payload: { requestCredDefPayload: RequestCredDeffEndorsement; orgId: number } + ): Promise { + return this.ecosystemService.requestCredDeffEndorsement(payload.requestCredDefPayload, payload.orgId); + } + /** * * @param payload diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index b63d3feb8..8c8b97e86 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -2,9 +2,10 @@ import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase import { ecosystem, ecosystem_invitations, ecosystem_orgs, ecosystem_roles, endorsement_transaction, org_agents, platform_config } from '@prisma/client'; -import { EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus } from '../enums/ecosystem.enum'; +import { EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; import { SchemaTransactionResponse } from '../interfaces/ecosystem.interfaces'; +import { ResponseMessages } from '@credebl/common/response-messages'; // eslint-disable-next-line camelcase @Injectable() @@ -385,6 +386,9 @@ export class EcosystemRepository { // eslint-disable-next-line camelcase async getAgentDetails(orgId: number): Promise { try { + if (!orgId) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidOrgId); + } const agentDetails = await this.prisma.org_agents.findFirst({ where: { orgId @@ -441,7 +445,8 @@ export class EcosystemRepository { } async storeTransactionRequest( - schemaTransactionResponse: SchemaTransactionResponse + schemaTransactionResponse: SchemaTransactionResponse, + type: endorsementTransactionType ): Promise { try { const { endorserDid, authorDid, requestPayload, status, ecosystemOrgId } = schemaTransactionResponse; @@ -452,7 +457,8 @@ export class EcosystemRepository { requestPayload, status, ecosystemOrgId, - responsePayload: '' + responsePayload: '', + type } }); } catch (error) { diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 004a4b340..b0e37d0f8 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -10,9 +10,9 @@ 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 { Invitation, OrgAgentType } from '@credebl/enum/enum'; -import { EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus } from '../enums/ecosystem.enum'; +import { EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; -import { EndorsementTransactionPayload, RequestSchemaEndorsement, SchemaMessage, SchemaTransactionPayload, SchemaTransactionResponse, SignedTransactionMessage } from '../interfaces/ecosystem.interfaces'; +import { CredDefTransactionPayload, EndorsementTransactionPayload, RequestCredDeffEndorsement, RequestSchemaEndorsement, SchemaMessage, SchemaTransactionPayload, SchemaTransactionResponse, SignedTransactionMessage, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; // eslint-disable-next-line camelcase import { platform_config } from '@prisma/client'; @@ -266,11 +266,15 @@ export class EcosystemService { */ async requestSchemaEndorsement(requestSchemaPayload: RequestSchemaEndorsement, orgId: number): Promise { try { - const agentDetails = await this.ecosystemRepository.getAgentDetails(orgId); + const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(orgId); + if (!ecosystemMemberDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.notFound); + } + // eslint-disable-next-line camelcase const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); - const url = await this.getAgentUrl(agentDetails?.orgAgentTypeId, agentDetails.agentEndPoint); + const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint); const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); @@ -278,26 +282,34 @@ export class EcosystemService { const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); + if (!ecosystemLeadAgentDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); + } + const getEcosystemOrgDetailsByOrgId = await this.ecosystemRepository.getEcosystemOrgDetailsbyId(String(orgId)); const schemaTransactionPayload: SchemaTransactionPayload = { - endorserDid: 'did:indy:indicio:testnet:23FTzrmb5QUxTiZUmueJbY', + endorserDid: ecosystemLeadAgentDetails.orgDid, endorse: requestSchemaPayload.endorse, attributes: attributeArray, version: String(requestSchemaPayload.version), name: requestSchemaPayload.name, - issuerId: 'did:indy:indicio:testnet:G8jvtxj59GJRznz4Kk426u' + issuerId: ecosystemMemberDetails.orgDid }; const schemaTransactionRequest: SchemaMessage = await this._requestSchemaEndorsement(schemaTransactionPayload, url, platformConfig?.sgApiKey); const schemaTransactionResponse: SchemaTransactionResponse = { endorserDid: ecosystemLeadAgentDetails.orgDid, - authorDid: agentDetails.orgDid, + authorDid: ecosystemMemberDetails.orgDid, requestPayload: schemaTransactionRequest.message.schemaState.schemaRequest, status: endorsementTransactionStatus.REQUESTED, ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id }; - return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse); + + if ('failed' === schemaTransactionRequest.message.schemaState.state) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestSchemaTransaction); + } + return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, endorsementTransactionType.SCHEMA); } catch (error) { this.logger.error(`In request schema endorsement : ${JSON.stringify(error)}`); @@ -305,6 +317,58 @@ export class EcosystemService { } } + async requestCredDeffEndorsement(requestCredDefPayload: RequestCredDeffEndorsement, orgId: number): Promise { + try { + const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(orgId); + if (!ecosystemMemberDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.notFound); + } + + // eslint-disable-next-line camelcase + const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); + + const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint); + + // const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); + + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(); + + const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); + + if (!ecosystemLeadAgentDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); + } + + const getEcosystemOrgDetailsByOrgId = await this.ecosystemRepository.getEcosystemOrgDetailsbyId(String(orgId)); + + const credDefTransactionPayload: CredDefTransactionPayload = { + endorserDid: ecosystemLeadAgentDetails.orgDid, + endorse: requestCredDefPayload.endorse, + tag: requestCredDefPayload.tag, + schemaId: requestCredDefPayload.schemaId, + issuerId: ecosystemMemberDetails.orgDid + }; + // Need to add login and type for credential-definition + const credDefTransactionRequest: SchemaMessage = await this._requestCredDeffEndorsement(credDefTransactionPayload, url, platformConfig?.sgApiKey); + const schemaTransactionResponse: SchemaTransactionResponse = { + endorserDid: ecosystemLeadAgentDetails.orgDid, + authorDid: ecosystemMemberDetails.orgDid, + requestPayload: credDefTransactionRequest.message.schemaState.schemaRequest, + status: endorsementTransactionStatus.REQUESTED, + ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id + }; + + if ('failed' === credDefTransactionRequest.message.schemaState.state) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); + } + return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, endorsementTransactionType.CREDENTIAL_DEFINITION); + } catch (error) { + this.logger.error(`In request cred-def endorsement : ${JSON.stringify(error)}`); + + throw new RpcException(error.response ? error.response : error); + } + } + async getInvitationsByEcosystemId( payload: FetchInvitationsPayload ): Promise { @@ -318,12 +382,7 @@ export class EcosystemService { throw new RpcException(error.response ? error.response : error); } } - /** - * Description: Store shortening URL - * @param referenceId - * @param url - * @returns connection invitation URL - */ + async _requestSchemaEndorsement(requestSchemaPayload: object, url: string, apiKey: string): Promise { const pattern = { cmd: 'agent-schema-endorsement-request' }; const payload = { requestSchemaPayload, url, apiKey }; @@ -341,24 +400,50 @@ export class EcosystemService { } } + async _requestCredDeffEndorsement(requestSchemaPayload: object, url: string, apiKey: string): Promise { + const pattern = { cmd: 'agent-credDef-endorsement-request' }; + const payload = { requestSchemaPayload, url, apiKey }; + + 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); + } + } + async signTransaction(endorsementId: string): Promise { try { const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.REQUESTED); + if (!endorsementTransactionPayload) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidTransaction); + } const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(); + + if (!getEcosystemLeadDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); + } // eslint-disable-next-line camelcase const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); - const url = `${ecosystemLeadAgentDetails.agentEndPoint}/transactions/endorse`; + const url = `${ecosystemLeadAgentDetails.agentEndPoint}${CommonConstants.SIGN_TRANSACTION}`; + if (!ecosystemLeadAgentDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); + } const parsedRequestPayload = JSON.parse(endorsementTransactionPayload.requestPayload); - const payload = { transaction: JSON.stringify(parsedRequestPayload), - endorserDid: 'did:indy:indicio:testnet:23FTzrmb5QUxTiZUmueJbY' + endorserDid: endorsementTransactionPayload.endorserDid }; const schemaTransactionRequest: SignedTransactionMessage = await this._signTransaction(payload, url, platformConfig.sgApiKey); @@ -409,26 +494,35 @@ export class EcosystemService { this.logger.log(url); - const apiUrl = `http://localhost:6002/transactions/write`; + const apiUrl = `${url}${CommonConstants.SUBMIT_TRANSACTION}`; const parsedRequestPayload = JSON.parse(endorsementTransactionPayload.requestPayload); - const payload = { - transaction: JSON.stringify(parsedRequestPayload), - endorserDid: ecosystemLeadAgentDetails.orgDid, - schema: { + const payload: submitTransactionPayload = { + endorsedTransaction: JSON.stringify(parsedRequestPayload), + endorserDid: ecosystemLeadAgentDetails.orgDid + }; + + if (endorsementTransactionPayload.type === endorsementTransactionType.SCHEMA) { + payload.schema = { attributes: parsedRequestPayload.operation.data.attr_names, version: parsedRequestPayload.operation.data.version, name: parsedRequestPayload.operation.data.name, issuerId: parsedRequestPayload.identifier - } - }; + }; + } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { + payload.credentialDefinition = { + tag: parsedRequestPayload.operation.tag, + issuerId: parsedRequestPayload.identifier, + schemaId: parsedRequestPayload.operation.schemaId + }; + } const submitTransactionRequest = await this._submitTransaction(payload, apiUrl, platformConfig.sgApiKey); - if (submitTransactionRequest) { - return this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SUBMITED); - + if (!submitTransactionRequest) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); } + return this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SUBMITED); } catch (error) { this.logger.error(`In sumit transaction : ${JSON.stringify(error)}`); @@ -514,7 +608,7 @@ export class EcosystemService { } async getEndorsementTransactions(payload: GetEndorsementsPayload): Promise { - const {ecosystemId, orgId, pageNumber, pageSize, search, type } = payload; + const { ecosystemId, orgId, pageNumber, pageSize, search, type } = payload; try { const query = { diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 8492ee009..f27f4e904 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -281,6 +281,10 @@ export enum CommonConstants { ENDORSER_DID = 8, ORGANIZATION_CREATION = 9, ADD_USER = 10, + + // Ecosystem + SIGN_TRANSACTION = '/transactions/endorse', + SUBMIT_TRANSACTION = '/transactions/endorse' } export const postgresqlErrorCodes = []; diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index dfc83dece..3dcaf9764 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -201,7 +201,14 @@ export const ResponseMessages = { invitationNotFound: 'Ecosystem Invitation not found', invitationNotUpdate: 'Ecosystem Invitation not updated', orgsNotUpdate: 'Ecosystem Orgs not updated', - ecosystemNotEnabled: 'Ecosystem service is not enabled' + ecosystemNotEnabled: 'Ecosystem service is not enabled', + sumbitTransaction: 'Error while submitting transaction', + requestSchemaTransaction: 'Error while request schema transaction', + requestCredDefTransaction: 'Error while submitting transaction', + notFound: 'Organization not found', + leadNotFound: 'Lead details not found', + invalidOrgId: 'Invalid organization Id', + invalidTransaction: 'Transaction does not exist' } } }; \ No newline at end of file From 74b6295379ea61d984b8dd5e7022b85ee3043639 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Mon, 9 Oct 2023 01:08:13 +0530 Subject: [PATCH 117/162] feat:added auth guard in request schema Signed-off-by: tipusinghaw --- apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 9a0573739..3f4a92c9e 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -211,10 +211,10 @@ export class EcosystemController { @Post('/:orgId/transaction/schema') @ApiOperation({ summary: 'Request new schema', description: 'Request new schema' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - // @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - // @ApiBearerAuth() - // @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) - // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) async requestSchemaTransaction(@Body() requestSchemaPayload: RequestSchemaDto, @Param('orgId') orgId: number, @Res() res: Response): Promise { await this.ecosystemService.schemaEndorsementRequest(requestSchemaPayload, orgId); const finalResponse: IResponseType = { From 7f8b5bb8089f7012abf3c731ed7a8c5675bfaf66 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Mon, 9 Oct 2023 15:04:53 +0530 Subject: [PATCH 118/162] feat: get ecosystem members list Signed-off-by: bhavanakarwade --- .../dtos/get-ecosystemMembers-dto.ts | 26 ++++++++ .../src/ecosystem/ecosystem.controller.ts | 46 ++++++++++++++- .../src/ecosystem/ecosystem.service.ts | 13 ++++ .../interfaces/ecosystemMembers.interface.ts | 7 +++ apps/ecosystem/src/ecosystem.controller.ts | 15 +++++ apps/ecosystem/src/ecosystem.repository.ts | 59 ++++++++++++++++++- apps/ecosystem/src/ecosystem.service.ts | 19 ++++++ libs/common/src/response-messages/index.ts | 3 +- 8 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/get-ecosystemMembers-dto.ts create mode 100644 apps/ecosystem/interfaces/ecosystemMembers.interface.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/get-ecosystemMembers-dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-ecosystemMembers-dto.ts new file mode 100644 index 000000000..789945457 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/get-ecosystemMembers-dto.ts @@ -0,0 +1,26 @@ +import { Transform, Type } from 'class-transformer'; +import { toNumber } from '@credebl/common/cast.helper'; + +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional } from 'class-validator'; + +export class GetAllEcosystemMembersDto { + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => String) + search = ''; + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => Number) + @Transform(({ value }) => toNumber(value)) + pageNumber = 1; + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => Number) + @Transform(({ value }) => toNumber(value)) + pageSize = 10; + +} diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 01a245c20..977b20461 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -27,6 +27,7 @@ import { EcosystemRolesGuard } from '../authz/guards/ecosystem-roles.guard'; import { EcosystemsRoles, Roles } from '../authz/decorators/roles.decorator'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { OrgRoles } from 'libs/org-roles/enums'; +import { GetAllEcosystemMembersDto } from './dtos/get-ecosystemMembers-dto'; @UseFilters(CustomExceptionFilter) @@ -143,6 +144,50 @@ export class EcosystemController { } + +/** + * + * @param orgId + * @param res + * @returns Ecosystem members list + */ + @Get('/:ecosystemId/:orgId/members') + // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + // @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt')) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Get ecosystem members list', description: 'Get ecosystem members list.' }) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getEcosystemMembers( + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @Query() getEcosystemMembers: GetAllEcosystemMembersDto, + @Res() res: Response): Promise { + const members = await this.ecosystemService.getEcosystemMembers(ecosystemId, getEcosystemMembers); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.fetchMembers, + data: members?.response + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } + /** * * @param createOrgDto @@ -168,7 +213,6 @@ export class EcosystemController { return res.status(HttpStatus.CREATED).json(finalResponse); } - /** * * @param bulkInvitationDto diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 49652d550..2c6ed327e 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -6,6 +6,7 @@ import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; import { AcceptRejectEcosystemInvitationDto } from './dtos/accept-reject-ecosysteminvitation-dto'; import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.dto'; import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; +import { GetAllEcosystemMembersDto } from './dtos/get-ecosystemMembers-dto'; @Injectable() @@ -66,6 +67,18 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'get-sent-invitations-ecosystemId', payload); } +/** + * + * @returns Ecosystem members + */ + async getEcosystemMembers( + ecosystemId: string, + getEcosystemMembers: GetAllEcosystemMembersDto + ): Promise<{ response: object }> { + const { pageNumber, pageSize, search } = getEcosystemMembers; + const payload = { ecosystemId, pageNumber, pageSize, search}; + return this.sendNats(this.serviceProxy, 'fetch-ecosystem-members', payload); + } /** * diff --git a/apps/ecosystem/interfaces/ecosystemMembers.interface.ts b/apps/ecosystem/interfaces/ecosystemMembers.interface.ts new file mode 100644 index 000000000..563f4228b --- /dev/null +++ b/apps/ecosystem/interfaces/ecosystemMembers.interface.ts @@ -0,0 +1,7 @@ +export interface EcosystemMembersPayload { + ecosystemId: string; + orgId: string, + pageNumber: number; + pageSize: number; + search: string +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 470e2b3b8..3d47bd83a 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -6,6 +6,7 @@ import { Body } from '@nestjs/common'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; import { AcceptRejectEcosystemInvitationDto } from '../dtos/accept-reject-ecosysteminvitation.dto'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; +import { EcosystemMembersPayload } from '../interfaces/ecosystemMembers.interface'; @Controller() export class EcosystemController { @@ -62,6 +63,20 @@ export class EcosystemController { ); } + /** + * + * @param payload + * @returns ecosystem members list + */ + @MessagePattern({ cmd: 'fetch-ecosystem-members' }) + async getEcosystemMembers( + @Body() payload: EcosystemMembersPayload + ): Promise { + return this.ecosystemService.getEcoystemMembers( + payload + ); + } + /** * * @param payload diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 9494dda2a..ca2e6af5a 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -1,7 +1,7 @@ import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase -import { ecosystem, ecosystem_invitations, ecosystem_orgs, ecosystem_roles } from '@prisma/client'; +import { ecosystem, ecosystem_invitations, ecosystem_orgs, ecosystem_roles} from '@prisma/client'; import {EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles} from '../enums/ecosystem.enum'; import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; // eslint-disable-next-line camelcase @@ -282,6 +282,63 @@ export class EcosystemRepository { } } + /** + * + * @param queryOptions + * @param filterOptions + * @returns users list + */ + // eslint-disable-next-line camelcase + async findEcosystemMembers(ecosystemId: string, pageNumber: number, pageSize: number, search = ''): Promise { + try { + const query = { + ecosystemId, + OR: + { orgId: { contains: search, mode: 'insensitive' } } + }; + return await this.getEcosystemMembersPagination(query, pageNumber, pageSize); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + + async getEcosystemMembersPagination(queryObject: object, pageNumber: number, pageSize: number): Promise { + try { + const result = await this.prisma.$transaction([ + this.prisma.ecosystem_orgs.findMany({ + where: { + ...queryObject + }, + include: { + ecosystem: true + }, + take: pageSize, + skip: (pageNumber - 1) * pageSize, + orderBy: { + createDateTime: 'desc' + } + }), + this.prisma.ecosystem_orgs.count({ + where: { + ...queryObject + } + }) + ]); + + // eslint-disable-next-line prefer-destructuring + const members = result[0]; + // eslint-disable-next-line prefer-destructuring + const totalCount = result[1]; + const totalPages = Math.ceil(totalCount / pageSize); + + return { totalPages, members }; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + async getEcosystemInvitationsPagination(queryObject: object, pageNumber: number, pageSize: number): Promise { try { diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 00e2293e1..502c5620e 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -12,6 +12,7 @@ import { AcceptRejectEcosystemInvitationDto } from '../dtos/accept-reject-ecosys import { Invitation } from '@credebl/enum/enum'; import { EcosystemOrgStatus, EcosystemRoles } from '../enums/ecosystem.enum'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; +import { EcosystemMembersPayload } from '../interfaces/ecosystemMembers.interface'; @Injectable() export class EcosystemService { @@ -91,6 +92,7 @@ export class EcosystemService { throw new InternalServerErrorException(error); } } + /** * @@ -267,6 +269,23 @@ export class EcosystemService { } } + /** + * + * @returns Ecosystem members list + */ + async getEcoystemMembers( + // ecosystemId: string, pageNumber: number, pageSize: number, search: string + payload: EcosystemMembersPayload + ): Promise { + try { + const { ecosystemId, pageNumber, pageSize, search} = payload; + return await this.ecosystemRepository.findEcosystemMembers(ecosystemId, pageNumber, pageSize, search); + } catch (error) { + this.logger.error(`In getEcosystemMembers: ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + /** * * @param payload diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 12f5e0c50..49f28acfe 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -190,7 +190,8 @@ export const ResponseMessages = { getInvitation: 'Ecosystem invitations fetched successfully', createInvitation: 'Ecosystem invitations sent successfully', invitationReject: 'Ecosystem invitation rejected', - invitationAccept: 'Ecosystem invitation accepted successfully' + invitationAccept: 'Ecosystem invitation accepted successfully', + fetchMembers: 'Ecosystem members fetched successfully' }, error: { notCreated: 'Error while creating ecosystem', From 9310fdaf05402ea935afc73719fe2809d5cfdbb4 Mon Sep 17 00:00:00 2001 From: Nishad Date: Mon, 9 Oct 2023 18:43:30 +0530 Subject: [PATCH 119/162] Solved issue of endorser transaction API Signed-off-by: Nishad --- .../src/ecosystem/ecosystem.controller.ts | 75 +++++++++---------- apps/ecosystem/src/ecosystem.controller.ts | 10 ++- apps/ecosystem/src/ecosystem.repository.ts | 5 +- libs/common/src/response-messages/index.ts | 3 +- 4 files changed, 52 insertions(+), 41 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index c62b87ce6..ecbb247d4 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -41,6 +41,43 @@ export class EcosystemController { private readonly ecosystemService: EcosystemService ) { } + @Get('/:ecosystemId/:orgId/endorsement-transactions') + @ApiOperation({ summary: 'Get all endorsement transactions', description: 'Get all endorsement transactions' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getEndorsementTranasactions( + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @Query() getAllEndorsementsDto: GetAllEndorsementsDto, + @Res() res: Response + ): Promise { + + const ecosystemList = await this.ecosystemService.getEndorsementTranasactions(ecosystemId, orgId, getAllEndorsementsDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.fetchEndorsors, + data: ecosystemList.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } @Get('/:orgId') @ApiOperation({ summary: 'Get all ecosystem', description: 'Get all existing ecosystem' }) @@ -105,44 +142,6 @@ export class EcosystemController { } - @Get('/:ecosystemId/:orgId/endorsement-transactions') - @ApiOperation({ summary: 'Get all endorsement transactions', description: 'Get all endorsement transactions' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - @ApiBearerAuth() - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) - @ApiQuery({ - name: 'pageNumber', - type: Number, - required: false - }) - @ApiQuery({ - name: 'pageSize', - type: Number, - required: false - }) - @ApiQuery({ - name: 'search', - type: String, - required: false - }) - async getEndorsementTranasactions( - @Param('ecosystemId') ecosystemId: string, - @Param('orgId') orgId: string, - @Query() getAllEndorsementsDto: GetAllEndorsementsDto, - @Res() res: Response - ): Promise { - const ecosystemList = await this.ecosystemService.getEndorsementTranasactions(ecosystemId, orgId, getAllEndorsementsDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: `Endorser transactions fetched successfully`, - data: ecosystemList.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - - @Get('/:ecosystemId/:orgId/invitations') @ApiOperation({ summary: 'Get all sent invitations', description: 'Get all sent invitations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index f12ea629f..862fe5d66 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -101,12 +101,20 @@ export class EcosystemController { @MessagePattern({ cmd: 'get-endorsement-transactions' }) async getEndorsementTransactions( @Body() payload: GetEndorsementsPayload - ): Promise { + ): Promise { return this.ecosystemService.getEndorsementTransactions( payload ); } + @MessagePattern({ cmd: 'fetch-ecosystem-org-data' }) + async fetchEcosystemOrg( + @Body() payload: { ecosystemId: string, orgId: string } + ): Promise { + return this.ecosystemService.fetchEcosystemOrg( + payload + ); + } /** * diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 0de7ad1f0..54489e7a9 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -349,7 +349,10 @@ export class EcosystemRepository { authorDid: true, status: true, type: true, - ecosystemOrgs: true + ecosystemOrgs: true, + requestPayload: true, + responsePayload: true, + createDateTime: true }, take: pageSize, skip: (pageNumber - 1) * pageSize, diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index dfc83dece..4c2226a08 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -192,7 +192,8 @@ export const ResponseMessages = { schemaRequest: 'Schema transaction request created successfully', sign: 'Transaction request signed successfully', invitationReject: 'Ecosystem invitation rejected', - invitationAccept: 'Ecosystem invitation accepted successfully' + invitationAccept: 'Ecosystem invitation accepted successfully', + fetchEndorsors: 'Endorser transactions fetched successfully' }, error: { notCreated: 'Error while creating ecosystem', From bb20aa3090ab808735e7495be88ddbcf37f28ea7 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Mon, 9 Oct 2023 18:57:03 +0530 Subject: [PATCH 120/162] feat: get ecosystem members list Signed-off-by: bhavanakarwade --- apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 258ab803d..d4a9650ab 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -189,10 +189,10 @@ export class EcosystemController { * @returns Ecosystem members list */ @Get('/:ecosystemId/:orgId/members') - // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - // @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) @ApiBearerAuth() - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Get ecosystem members list', description: 'Get ecosystem members list.' }) @ApiQuery({ From c86fc60124c038107658bcfec8f954bb08788278 Mon Sep 17 00:00:00 2001 From: Nishad Date: Mon, 9 Oct 2023 19:01:47 +0530 Subject: [PATCH 121/162] Removed redundent function of endorser list Signed-off-by: Nishad --- .../src/ecosystem/ecosystem.controller.ts | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index c7930a423..4bc2dbcde 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -141,44 +141,6 @@ export class EcosystemController { } - @Get('/:ecosystemId/:orgId/endorsement-transactions') - @ApiOperation({ summary: 'Get all endorsement transactions', description: 'Get all endorsement transactions' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - @ApiBearerAuth() - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) - @ApiQuery({ - name: 'pageNumber', - type: Number, - required: false - }) - @ApiQuery({ - name: 'pageSize', - type: Number, - required: false - }) - @ApiQuery({ - name: 'search', - type: String, - required: false - }) - async getEndorsementTranasactions( - @Param('ecosystemId') ecosystemId: string, - @Param('orgId') orgId: string, - @Query() getAllEndorsementsDto: GetAllEndorsementsDto, - @Res() res: Response - ): Promise { - const ecosystemList = await this.ecosystemService.getEndorsementTranasactions(ecosystemId, orgId, getAllEndorsementsDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: `Endorser transactions fetched successfully`, - data: ecosystemList.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - - @Get('/:ecosystemId/:orgId/invitations') @ApiOperation({ summary: 'Get all sent invitations', description: 'Get all sent invitations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) From a59c5af8fd58d35af9d58bdf37bd2ffadf1fbbd9 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Mon, 9 Oct 2023 19:21:19 +0530 Subject: [PATCH 122/162] resolved sonarcloud checks Signed-off-by: bhavanakarwade --- .../dtos/get-ecosystemMembers-dto.ts | 11 +++--- .../src/ecosystem/ecosystem.controller.ts | 38 ------------------- 2 files changed, 6 insertions(+), 43 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/dtos/get-ecosystemMembers-dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-ecosystemMembers-dto.ts index 789945457..0644d4455 100644 --- a/apps/api-gateway/src/ecosystem/dtos/get-ecosystemMembers-dto.ts +++ b/apps/api-gateway/src/ecosystem/dtos/get-ecosystemMembers-dto.ts @@ -6,11 +6,6 @@ import { IsOptional } from 'class-validator'; export class GetAllEcosystemMembersDto { - @ApiProperty({ required: false }) - @IsOptional() - @Type(() => String) - search = ''; - @ApiProperty({ required: false }) @IsOptional() @Type(() => Number) @@ -23,4 +18,10 @@ export class GetAllEcosystemMembersDto { @Transform(({ value }) => toNumber(value)) pageSize = 10; + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => String) + search = ''; + } diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 03cf948b9..c4ccc0520 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -142,44 +142,6 @@ export class EcosystemController { } - @Get('/:ecosystemId/:orgId/endorsement-transactions') - @ApiOperation({ summary: 'Get all endorsement transactions', description: 'Get all endorsement transactions' }) - @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - @ApiBearerAuth() - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) - @ApiQuery({ - name: 'pageNumber', - type: Number, - required: false - }) - @ApiQuery({ - name: 'pageSize', - type: Number, - required: false - }) - @ApiQuery({ - name: 'search', - type: String, - required: false - }) - async getEndorsementTranasactions( - @Param('ecosystemId') ecosystemId: string, - @Param('orgId') orgId: string, - @Query() getAllEndorsementsDto: GetAllEndorsementsDto, - @Res() res: Response - ): Promise { - const ecosystemList = await this.ecosystemService.getEndorsementTranasactions(ecosystemId, orgId, getAllEndorsementsDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: `Endorser transactions fetched successfully`, - data: ecosystemList.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - - @Get('/:ecosystemId/:orgId/invitations') @ApiOperation({ summary: 'Get all sent invitations', description: 'Get all sent invitations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) From 9797adc35615e0a406107150a7e342e5958926d7 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Mon, 9 Oct 2023 19:57:25 +0530 Subject: [PATCH 123/162] changed naming convention for dto Signed-off-by: bhavanakarwade --- ...Invitations-dto.ts => delete-ecosystemInvitations.dto.ts} | 0 ...tions-dto.ts => get-all-sent-ecosystemInvitations.dto.ts} | 0 ...t-ecosystemMembers-dto.ts => get-ecosystemMembers.dto.ts} | 0 apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 4 ++-- apps/api-gateway/src/ecosystem/ecosystem.service.ts | 5 ++--- 5 files changed, 4 insertions(+), 5 deletions(-) rename apps/api-gateway/src/ecosystem/dtos/{delete-ecosystemInvitations-dto.ts => delete-ecosystemInvitations.dto.ts} (100%) rename apps/api-gateway/src/ecosystem/dtos/{get-all-sent-ecosystemInvitations-dto.ts => get-all-sent-ecosystemInvitations.dto.ts} (100%) rename apps/api-gateway/src/ecosystem/dtos/{get-ecosystemMembers-dto.ts => get-ecosystemMembers.dto.ts} (100%) diff --git a/apps/api-gateway/src/ecosystem/dtos/delete-ecosystemInvitations-dto.ts b/apps/api-gateway/src/ecosystem/dtos/delete-ecosystemInvitations.dto.ts similarity index 100% rename from apps/api-gateway/src/ecosystem/dtos/delete-ecosystemInvitations-dto.ts rename to apps/api-gateway/src/ecosystem/dtos/delete-ecosystemInvitations.dto.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/get-all-sent-ecosystemInvitations-dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-all-sent-ecosystemInvitations.dto.ts similarity index 100% rename from apps/api-gateway/src/ecosystem/dtos/get-all-sent-ecosystemInvitations-dto.ts rename to apps/api-gateway/src/ecosystem/dtos/get-all-sent-ecosystemInvitations.dto.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/get-ecosystemMembers-dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-ecosystemMembers.dto.ts similarity index 100% rename from apps/api-gateway/src/ecosystem/dtos/get-ecosystemMembers-dto.ts rename to apps/api-gateway/src/ecosystem/dtos/get-ecosystemMembers.dto.ts diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index c4ccc0520..d32800acc 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -15,7 +15,7 @@ import { ResponseMessages } from '@credebl/common/response-messages'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; import { EditEcosystemDto } from './dtos/edit-ecosystem-dto'; import { AuthGuard } from '@nestjs/passport'; -import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; +import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations.dto'; import { EcosystemRoles, Invitation } from '@credebl/enum/enum'; import { User } from '../authz/decorators/user.decorator'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; @@ -27,7 +27,7 @@ import { EcosystemRolesGuard } from '../authz/guards/ecosystem-roles.guard'; import { EcosystemsRoles, Roles } from '../authz/decorators/roles.decorator'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { OrgRoles } from 'libs/org-roles/enums'; -import { GetAllEcosystemMembersDto } from './dtos/get-ecosystemMembers-dto'; +import { GetAllEcosystemMembersDto } from './dtos/get-ecosystemMembers.dto'; import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; import { CreateEcosystemDto } from './dtos/create-ecosystem-dto'; diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 0df75949b..c8f31cee7 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -5,9 +5,8 @@ import { BaseService } from 'libs/service/base.service'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; import { AcceptRejectEcosystemInvitationDto } from './dtos/accept-reject-ecosysteminvitation-dto'; import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.dto'; -import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; -import { GetAllEcosystemMembersDto } from './dtos/get-ecosystemMembers-dto'; - +import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations.dto'; +import { GetAllEcosystemMembersDto } from './dtos/get-ecosystemMembers.dto'; import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; import { RequestSchemaDto } from './dtos/request-schema-dto'; From 7f347400f4f00d72de5464f4209a810dc182c4a0 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Mon, 9 Oct 2023 20:13:36 +0530 Subject: [PATCH 124/162] changed dto naming convention Signed-off-by: bhavanakarwade --- ...invitation-dto.ts => accept-reject-invitations.dto.ts} | 0 ...systemInvitations.dto.ts => delete-invitations.dto.ts} | 0 ...tations.dto.ts => get-all-received-invitations.dto.ts} | 0 .../{get-ecosystemMembers.dto.ts => get-members.dto.ts} | 0 .../dtos/{request-schema-dto.ts => request-schema.dto.ts} | 0 apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 8 ++++---- apps/api-gateway/src/ecosystem/ecosystem.service.ts | 8 ++++---- 7 files changed, 8 insertions(+), 8 deletions(-) rename apps/api-gateway/src/ecosystem/dtos/{accept-reject-ecosysteminvitation-dto.ts => accept-reject-invitations.dto.ts} (100%) rename apps/api-gateway/src/ecosystem/dtos/{delete-ecosystemInvitations.dto.ts => delete-invitations.dto.ts} (100%) rename apps/api-gateway/src/ecosystem/dtos/{get-all-sent-ecosystemInvitations.dto.ts => get-all-received-invitations.dto.ts} (100%) rename apps/api-gateway/src/ecosystem/dtos/{get-ecosystemMembers.dto.ts => get-members.dto.ts} (100%) rename apps/api-gateway/src/ecosystem/dtos/{request-schema-dto.ts => request-schema.dto.ts} (100%) diff --git a/apps/api-gateway/src/ecosystem/dtos/accept-reject-ecosysteminvitation-dto.ts b/apps/api-gateway/src/ecosystem/dtos/accept-reject-invitations.dto.ts similarity index 100% rename from apps/api-gateway/src/ecosystem/dtos/accept-reject-ecosysteminvitation-dto.ts rename to apps/api-gateway/src/ecosystem/dtos/accept-reject-invitations.dto.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/delete-ecosystemInvitations.dto.ts b/apps/api-gateway/src/ecosystem/dtos/delete-invitations.dto.ts similarity index 100% rename from apps/api-gateway/src/ecosystem/dtos/delete-ecosystemInvitations.dto.ts rename to apps/api-gateway/src/ecosystem/dtos/delete-invitations.dto.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/get-all-sent-ecosystemInvitations.dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-all-received-invitations.dto.ts similarity index 100% rename from apps/api-gateway/src/ecosystem/dtos/get-all-sent-ecosystemInvitations.dto.ts rename to apps/api-gateway/src/ecosystem/dtos/get-all-received-invitations.dto.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/get-ecosystemMembers.dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-members.dto.ts similarity index 100% rename from apps/api-gateway/src/ecosystem/dtos/get-ecosystemMembers.dto.ts rename to apps/api-gateway/src/ecosystem/dtos/get-members.dto.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/request-schema-dto.ts b/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts similarity index 100% rename from apps/api-gateway/src/ecosystem/dtos/request-schema-dto.ts rename to apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index d32800acc..1da19f1f2 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -4,7 +4,7 @@ import { EcosystemService } from './ecosystem.service'; import { Post, Get } from '@nestjs/common'; import { Body } from '@nestjs/common'; import { Res } from '@nestjs/common'; -import { RequestSchemaDto } from './dtos/request-schema-dto'; +import { RequestSchemaDto } from './dtos/request-schema.dto'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { HttpStatus } from '@nestjs/common'; import { Response } from 'express'; @@ -15,19 +15,19 @@ import { ResponseMessages } from '@credebl/common/response-messages'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; import { EditEcosystemDto } from './dtos/edit-ecosystem-dto'; import { AuthGuard } from '@nestjs/passport'; -import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations.dto'; +import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-received-invitations.dto'; import { EcosystemRoles, Invitation } from '@credebl/enum/enum'; import { User } from '../authz/decorators/user.decorator'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { user } from '@prisma/client'; -import { AcceptRejectEcosystemInvitationDto } from './dtos/accept-reject-ecosysteminvitation-dto'; +import { AcceptRejectEcosystemInvitationDto } from './dtos/accept-reject-invitations.dto'; import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.dto'; import { EcosystemRolesGuard } from '../authz/guards/ecosystem-roles.guard'; import { EcosystemsRoles, Roles } from '../authz/decorators/roles.decorator'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { OrgRoles } from 'libs/org-roles/enums'; -import { GetAllEcosystemMembersDto } from './dtos/get-ecosystemMembers.dto'; +import { GetAllEcosystemMembersDto } from './dtos/get-members.dto'; import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; import { CreateEcosystemDto } from './dtos/create-ecosystem-dto'; diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index c8f31cee7..5918d3df4 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -3,12 +3,12 @@ import { Injectable } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; -import { AcceptRejectEcosystemInvitationDto } from './dtos/accept-reject-ecosysteminvitation-dto'; +import { AcceptRejectEcosystemInvitationDto } from './dtos/accept-reject-invitations.dto'; import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.dto'; -import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations.dto'; -import { GetAllEcosystemMembersDto } from './dtos/get-ecosystemMembers.dto'; +import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-received-invitations.dto'; +import { GetAllEcosystemMembersDto } from './dtos/get-members.dto'; import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; -import { RequestSchemaDto } from './dtos/request-schema-dto'; +import { RequestSchemaDto } from './dtos/request-schema.dto'; @Injectable() export class EcosystemService extends BaseService { From 27b9dd21496cd086e0bc901d45915bc66cd6d578 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Mon, 9 Oct 2023 22:37:36 +0530 Subject: [PATCH 125/162] fix: data type issue while sign transaction Signed-off-by: tipusinghaw --- .../src/agent-service.controller.ts | 4 +- .../src/agent-service.service.ts | 25 +++-- apps/ecosystem/enums/ecosystem.enum.ts | 17 ++-- .../interfaces/ecosystem.interfaces.ts | 15 +++ apps/ecosystem/src/ecosystem.service.ts | 94 +++++++++++-------- libs/common/src/common.constant.ts | 7 +- libs/common/src/response-messages/index.ts | 3 +- 7 files changed, 103 insertions(+), 62 deletions(-) diff --git a/apps/agent-service/src/agent-service.controller.ts b/apps/agent-service/src/agent-service.controller.ts index cf97fdc36..1d4a99192 100644 --- a/apps/agent-service/src/agent-service.controller.ts +++ b/apps/agent-service/src/agent-service.controller.ts @@ -117,11 +117,11 @@ export class AgentServiceController { } @MessagePattern({ cmd: 'agent-sign-transaction' }) - async signTransaction(payload: { url: string, apiKey: string, signEndorsementPayload:string }): Promise { + async signTransaction(payload: { url: string, apiKey: string, signEndorsementPayload:object }): Promise { return this.agentServiceService.signTransaction(payload.url, payload.apiKey, payload.signEndorsementPayload); } @MessagePattern({ cmd: 'agent-submit-transaction' }) - async submitTransaction(payload: { url: string, apiKey: string, submitEndorsementPayload:string }): Promise { + async submitTransaction(payload: { url: string, apiKey: string, submitEndorsementPayload:object }): Promise { return this.agentServiceService.sumbitTransaction(payload.url, payload.apiKey, payload.submitEndorsementPayload); } } diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 56da91997..0d3f67403 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -889,11 +889,11 @@ export class AgentServiceService { } } - async schemaEndorsementRequest(url: string, apiKey: string, requestSchemaPayload:object): Promise { + async schemaEndorsementRequest(url: string, apiKey: string, requestSchemaPayload: object): Promise { try { const schemaRequest = await this.commonService - .httpPost(url, requestSchemaPayload, { headers: { 'x-api-key': apiKey } }) - .then(async response => response); + .httpPost(url, requestSchemaPayload, { headers: { 'x-api-key': apiKey } }) + .then(async response => response); return schemaRequest; } catch (error) { this.logger.error(`Error in schema endorsement request in agent service : ${JSON.stringify(error)}`); @@ -901,11 +901,11 @@ export class AgentServiceService { } } - async credDefEndorsementRequest(url: string, apiKey: string, requestSchemaPayload:object): Promise { + async credDefEndorsementRequest(url: string, apiKey: string, requestSchemaPayload: object): Promise { try { const credDefRequest = await this.commonService - .httpPost(url, requestSchemaPayload, { headers: { 'x-api-key': apiKey } }) - .then(async response => response); + .httpPost(url, requestSchemaPayload, { headers: { 'x-api-key': apiKey } }) + .then(async response => response); return credDefRequest; } catch (error) { this.logger.error(`Error in credential-definition endorsement request in agent service : ${JSON.stringify(error)}`); @@ -913,12 +913,11 @@ export class AgentServiceService { } } - async signTransaction(url: string, apiKey: string, signEndorsementPayload: string): Promise { + async signTransaction(url: string, apiKey: string, signEndorsementPayload: object): Promise { try { - const signEndorsementTransaction = await this.commonService - .httpPost(url, signEndorsementPayload, { headers: { 'x-api-key': apiKey } }) - .then(async response => response); + .httpPost(url, signEndorsementPayload, { headers: { 'x-api-key': apiKey } }) + .then(async response => response); return signEndorsementTransaction; } catch (error) { @@ -927,12 +926,12 @@ export class AgentServiceService { } } - async sumbitTransaction(url: string, apiKey: string, submitEndorsementPayload: string): Promise { + async sumbitTransaction(url: string, apiKey: string, submitEndorsementPayload: object): Promise { try { const signEndorsementTransaction = await this.commonService - .httpPost(url, submitEndorsementPayload, { headers: { 'x-api-key': apiKey } }) - .then(async response => response); + .httpPost(url, submitEndorsementPayload, { headers: { 'x-api-key': apiKey } }) + .then(async response => response); return signEndorsementTransaction; } catch (error) { diff --git a/apps/ecosystem/enums/ecosystem.enum.ts b/apps/ecosystem/enums/ecosystem.enum.ts index 9f4b9bee2..dbb5c3dc3 100644 --- a/apps/ecosystem/enums/ecosystem.enum.ts +++ b/apps/ecosystem/enums/ecosystem.enum.ts @@ -15,12 +15,15 @@ export enum EcosystemInvitationStatus { } export enum endorsementTransactionStatus { - REQUESTED = 'Requested', - SIGNED = 'Signed', - DECLINED = 'Declined', - SUBMITED = 'Submited' + REQUESTED = 'requested', + SIGNED = 'signed', + DECLINED = 'declined', + SUBMITED = 'submited' } + export enum endorsementTransactionType { - SCHEMA = 'Schema', - CREDENTIAL_DEFINITION = 'Credential-definition' -} \ No newline at end of file + SCHEMA = 'schema', + CREDENTIAL_DEFINITION = 'credential-definition', + SIGN = 'sign', + SUBMIT = 'submit' +} diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts index e6eec7e2b..2d01cbebb 100644 --- a/apps/ecosystem/interfaces/ecosystem.interfaces.ts +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -50,6 +50,20 @@ export interface SchemaMessage { }; } +export interface CredDefMessage { + message?: { + jobId: string; + credentialDefinitionState: { + state: string; + action: string; + schemaId: string; + schema: Record; + credentialDefinitionRequest: string; + }; + registrationMetadata: Record; + schemaMetadata: Record; + }; +} export interface SchemaTransactionResponse { endorserDid: string; authorDid: string; @@ -97,3 +111,4 @@ export interface submitTransactionPayload { schema?: SchemaPayload; credentialDefinition?: CredentialDefinitionPayload; } + diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index b0e37d0f8..9a72cdc32 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -12,7 +12,7 @@ import { AcceptRejectEcosystemInvitationDto } from '../dtos/accept-reject-ecosys import { Invitation, OrgAgentType } from '@credebl/enum/enum'; import { EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; -import { CredDefTransactionPayload, EndorsementTransactionPayload, RequestCredDeffEndorsement, RequestSchemaEndorsement, SchemaMessage, SchemaTransactionPayload, SchemaTransactionResponse, SignedTransactionMessage, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; +import { CredDefMessage, CredDefTransactionPayload, EndorsementTransactionPayload, RequestCredDeffEndorsement, RequestSchemaEndorsement, SchemaMessage, SchemaTransactionPayload, SchemaTransactionResponse, SignedTransactionMessage, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; // eslint-disable-next-line camelcase import { platform_config } from '@prisma/client'; @@ -274,7 +274,7 @@ export class EcosystemService { // eslint-disable-next-line camelcase const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); - const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint); + const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.SCHEMA, ecosystemMemberDetails?.tenantId); const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); @@ -327,7 +327,7 @@ export class EcosystemService { // eslint-disable-next-line camelcase const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); - const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint); + const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.CREDENTIAL_DEFINITION, ecosystemMemberDetails?.tenantId); // const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); @@ -348,19 +348,20 @@ export class EcosystemService { schemaId: requestCredDefPayload.schemaId, issuerId: ecosystemMemberDetails.orgDid }; - // Need to add login and type for credential-definition - const credDefTransactionRequest: SchemaMessage = await this._requestCredDeffEndorsement(credDefTransactionPayload, url, platformConfig?.sgApiKey); - const schemaTransactionResponse: SchemaTransactionResponse = { + // Need to add logic and type for credential-definition + const credDefTransactionRequest:CredDefMessage = await this._requestCredDeffEndorsement(credDefTransactionPayload, url, platformConfig?.sgApiKey); + const schemaTransactionResponse = { endorserDid: ecosystemLeadAgentDetails.orgDid, authorDid: ecosystemMemberDetails.orgDid, - requestPayload: credDefTransactionRequest.message.schemaState.schemaRequest, + requestPayload: credDefTransactionRequest.message.credentialDefinitionState.credentialDefinitionRequest, status: endorsementTransactionStatus.REQUESTED, ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id }; - if ('failed' === credDefTransactionRequest.message.schemaState.state) { + if ('failed' === credDefTransactionRequest.message.credentialDefinitionState.state) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); } + return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, endorsementTransactionType.CREDENTIAL_DEFINITION); } catch (error) { this.logger.error(`In request cred-def endorsement : ${JSON.stringify(error)}`); @@ -382,7 +383,7 @@ export class EcosystemService { throw new RpcException(error.response ? error.response : error); } } - + async _requestSchemaEndorsement(requestSchemaPayload: object, url: string, apiKey: string): Promise { const pattern = { cmd: 'agent-schema-endorsement-request' }; const payload = { requestSchemaPayload, url, apiKey }; @@ -433,16 +434,14 @@ export class EcosystemService { const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); - const url = `${ecosystemLeadAgentDetails.agentEndPoint}${CommonConstants.SIGN_TRANSACTION}`; + const url = await this.getAgentUrl(ecosystemLeadAgentDetails?.orgAgentTypeId, ecosystemLeadAgentDetails.agentEndPoint, endorsementTransactionType.SIGN, ecosystemLeadAgentDetails?.tenantId); if (!ecosystemLeadAgentDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); } - - const parsedRequestPayload = JSON.parse(endorsementTransactionPayload.requestPayload); - + const jsonString = endorsementTransactionPayload.requestPayload.toString(); const payload = { - transaction: JSON.stringify(parsedRequestPayload), + transaction: jsonString, endorserDid: endorsementTransactionPayload.endorserDid }; @@ -481,7 +480,9 @@ export class EcosystemService { async submitTransaction(endorsementId: string): Promise { try { const endorsementTransactionPayload: EndorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.SIGNED); - + if (!endorsementTransactionPayload) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidTransaction); + } const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(Number(endorsementTransactionPayload.ecosystemOrgs.orgId)); const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(); @@ -490,16 +491,13 @@ export class EcosystemService { const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); - const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint); - - this.logger.log(url); + const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.SUBMIT, ecosystemMemberDetails?.tenantId); - const apiUrl = `${url}${CommonConstants.SUBMIT_TRANSACTION}`; - - const parsedRequestPayload = JSON.parse(endorsementTransactionPayload.requestPayload); + const parsedRequestPayload = JSON.parse(endorsementTransactionPayload.responsePayload); + const jsonString = endorsementTransactionPayload.responsePayload.toString(); const payload: submitTransactionPayload = { - endorsedTransaction: JSON.stringify(parsedRequestPayload), + endorsedTransaction: jsonString, endorserDid: ecosystemLeadAgentDetails.orgDid }; @@ -508,17 +506,18 @@ export class EcosystemService { attributes: parsedRequestPayload.operation.data.attr_names, version: parsedRequestPayload.operation.data.version, name: parsedRequestPayload.operation.data.name, - issuerId: parsedRequestPayload.identifier + issuerId: ecosystemMemberDetails.orgDid }; } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { payload.credentialDefinition = { tag: parsedRequestPayload.operation.tag, - issuerId: parsedRequestPayload.identifier, + issuerId: ecosystemMemberDetails.orgDid, schemaId: parsedRequestPayload.operation.schemaId }; } - - const submitTransactionRequest = await this._submitTransaction(payload, apiUrl, platformConfig.sgApiKey); + // Need to add valid schema Id + const submitTransactionRequest = await this._submitTransaction(payload, url, platformConfig.sgApiKey); + // need to implement the type and state if (!submitTransactionRequest) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); } @@ -536,9 +535,9 @@ export class EcosystemService { * @param url * @returns sign message */ - async _submitTransaction(signEndorsementPayload: object, url: string, apiKey: string): Promise { + async _submitTransaction(submitEndorsementPayload: object, url: string, apiKey: string): Promise { const pattern = { cmd: 'agent-submit-transaction' }; - const payload = { signEndorsementPayload, url, apiKey }; + const payload = { submitEndorsementPayload, url, apiKey }; try { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -556,32 +555,51 @@ export class EcosystemService { async getAgentUrl( orgAgentTypeId: number, - agentEndPoint: string + agentEndPoint: string, + type: string, + tenantId?: string ): Promise { try { - let url; - if (orgAgentTypeId === OrgAgentType.DEDICATED) { - url = `${agentEndPoint}${CommonConstants.URL_SCHM_CREATE_SCHEMA}`; + if (orgAgentTypeId === OrgAgentType.DEDICATED) { + if (type === endorsementTransactionType.SCHEMA) { + url = `${agentEndPoint}${CommonConstants.URL_SCHM_CREATE_SCHEMA}`; + } else if (type === endorsementTransactionType.CREDENTIAL_DEFINITION) { + url = `${agentEndPoint}${CommonConstants.URL_SCHM_CREATE_CRED_DEF}`; + } else if (type === endorsementTransactionType.SIGN) { + url = `${agentEndPoint}${CommonConstants.SIGN_TRANSACTION}`; + } else if (type === endorsementTransactionType.SUBMIT) { + url = `${agentEndPoint}${CommonConstants.SUBMIT_TRANSACTION}`; + } } else if (orgAgentTypeId === OrgAgentType.SHARED) { - // TODO - // url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_INVITATION}`.replace('#', tenantId); + if (tenantId !== undefined) { + if (type === endorsementTransactionType.SCHEMA) { + url = `${agentEndPoint}${CommonConstants.TRANSACTION_MULTITENANT_SCHEMA}`.replace('#', tenantId); + } else if (type === endorsementTransactionType.CREDENTIAL_DEFINITION) { + url = `${agentEndPoint}${CommonConstants.TRANSACTION_MULTITENANT_CRED_DEF}`.replace('#', tenantId); + } else if (type === endorsementTransactionType.SIGN) { + url = `${agentEndPoint}${CommonConstants.TRANSACTION_MULTITENANT_SIGN}`.replace('#', tenantId); + } else if (type === endorsementTransactionType.SUBMIT) { + url = `${agentEndPoint}${CommonConstants.TRANSACTION_MULTITENANT_SUMBIT}`.replace('#', tenantId); + } else { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidAgentUrl); + } + } else { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidAgentUrl); + } } else { - throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); } - return url; + return url; } catch (error) { this.logger.error(`Error in get agent url: ${JSON.stringify(error)}`); throw error; - } } - async fetchEcosystemOrg( payload: { ecosystemId: string, orgId: string } ): Promise { diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index f27f4e904..1bd48d76d 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -284,7 +284,12 @@ export enum CommonConstants { // Ecosystem SIGN_TRANSACTION = '/transactions/endorse', - SUBMIT_TRANSACTION = '/transactions/endorse' + SUBMIT_TRANSACTION = '/transactions/write', + TRANSACTION_MULTITENANT_SCHEMA = '/multi-tenancy/schema/#', + TRANSACTION_MULTITENANT_CRED_DEF = '/multi-tenancy/credential-definition/#', + TRANSACTION_MULTITENANT_SIGN = '/multi-tenancy/transactions/endorse/#', + TRANSACTION_MULTITENANT_SUMBIT = '/multi-tenancy/transactions/write/#' + } export const postgresqlErrorCodes = []; diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 3dcaf9764..1ac396853 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -208,7 +208,8 @@ export const ResponseMessages = { notFound: 'Organization not found', leadNotFound: 'Lead details not found', invalidOrgId: 'Invalid organization Id', - invalidTransaction: 'Transaction does not exist' + invalidTransaction: 'Transaction does not exist', + invalidAgentUrl: 'Invalid agent url' } } }; \ No newline at end of file From cefa1fe076b0251f68e73bc722332af84bf95c15 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Tue, 10 Oct 2023 15:14:40 +0530 Subject: [PATCH 126/162] feat: get ecosystem dashboard card count Signed-off-by: bhavanakarwade --- .../src/ecosystem/ecosystem.controller.ts | 18 ++++++ .../src/ecosystem/ecosystem.service.ts | 10 ++++ apps/ecosystem/src/ecosystem.controller.ts | 11 ++++ apps/ecosystem/src/ecosystem.repository.ts | 59 +++++++++++++++++++ apps/ecosystem/src/ecosystem.service.ts | 15 ++++- libs/common/src/response-messages/index.ts | 1 + 6 files changed, 113 insertions(+), 1 deletion(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 4bc2dbcde..58c684832 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -97,6 +97,24 @@ export class EcosystemController { return res.status(HttpStatus.OK).json(finalResponse); } + @Get('/:ecosystemId/:orgId') + @ApiOperation({ summary: 'Get an ecosystem dashboard', description: 'Get an ecosystem dashboard' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + + async getEcosystemDashboardDetails(@Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: string, @Res() res: Response): Promise { + + const getEcosystemDetails = await this.ecosystemService.getEcosystemDashboardDetails(ecosystemId, orgId); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.getEcosystemDashboard, + data: getEcosystemDetails.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + + } + @Get('/:orgId/users/invitations') @ApiOperation({ summary: 'Get received ecosystem invitations', description: 'Get received ecosystem invitations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 0a7d37bf7..db3dbaa70 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -45,6 +45,16 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'get-all-ecosystem', payload); } + /** + * + * + * @returns Get ecosystems dashboard card counts + */ + async getEcosystemDashboardDetails(ecosystemId: string, orgId: string): Promise<{ response: object }> { + const payload = { ecosystemId, orgId }; + return this.sendNats(this.serviceProxy, 'get-ecosystem-dashboard-details', payload); + } + /** * diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index bf9344c56..b66d8348c 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -47,6 +47,17 @@ export class EcosystemController { return this.ecosystemService.getAllEcosystem(payload); } + /** + * Description: get ecosystems dashboard details + * @returns Get ecosystem dashboard card counts + */ + @MessagePattern({ cmd: 'get-ecosystem-dashboard-details' }) + async getEcosystemDashboardDetails( + payload: { ecosystemId: string; orgId: string }): Promise { + return this.ecosystemService.getEcosystemDashboardDetails(payload.ecosystemId); + } + + /** * Description: get ecosystem invitations * @returns Get sent invitation details diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 2211ed939..216a58c53 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -135,6 +135,65 @@ export class EcosystemRepository { } } + /** + * + * @returns Get ecosystem dashboard card count + */ + + async getEcosystemDashboardDetails(ecosystemId: string): Promise { + try { + const membersCount = await this.getEcosystemMembersCount(ecosystemId); + const endorsementsCount = await this.getEcosystemEndorsementsCount(ecosystemId); + return { + membersCount, + endorsementsCount + }; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + + + async getEcosystemMembersCount (ecosystemId: string): Promise { + try { + const membersCount = await this.prisma.ecosystem_orgs.count( + { + where: { + ecosystemId + } + } + ); + + return membersCount; + + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + + } + + async getEcosystemEndorsementsCount (ecosystemId: string): Promise { + try { + const endorsementsCount = await this.prisma.endorsement_transaction.count({ + where: { + ecosystemOrgs: { + ecosystemId + + } + } + }); + + return endorsementsCount; + + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + + } + /** * * @param queryObject diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 0a775408a..943580d4f 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -74,7 +74,20 @@ export class EcosystemService { return getAllEcosystemDetails; } - + /** + * + * + * @returns ecosystem dashboard details + */ + async getEcosystemDashboardDetails(ecosystemId: string): Promise { + try { + return this.ecosystemRepository.getEcosystemDashboardDetails(ecosystemId); + } catch (error) { + this.logger.error(`In ecosystem dashboard details : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + /** * Description: get an ecosystem invitation * @returns Get sent ecosystem invitation details diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 82626baae..c7455ca49 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -188,6 +188,7 @@ export const ResponseMessages = { update: 'Ecosystem updated successfully', delete: 'Ecosystem invitations deleted successfully', fetch: 'Ecosystem fetched successfully', + getEcosystemDashboard: 'Ecosystem dashboard card counts fetched successfully', getInvitation: 'Ecosystem invitations fetched successfully', createInvitation: 'Ecosystem invitations sent successfully', schemaRequest: 'Schema transaction request created successfully', From cb824c0ba7f7dca4bdf39de095f0bca5d8dd82ea Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Tue, 10 Oct 2023 15:46:48 +0530 Subject: [PATCH 127/162] fix: added ecosystem id in endorsement APIs Signed-off-by: tipusinghaw --- .../src/ecosystem/ecosystem.controller.ts | 117 +++++++++--------- .../src/ecosystem/ecosystem.service.ts | 16 +-- .../interfaces/ecosystem.interfaces.ts | 8 +- apps/ecosystem/src/ecosystem.controller.ts | 16 +-- apps/ecosystem/src/ecosystem.repository.ts | 18 ++- apps/ecosystem/src/ecosystem.service.ts | 21 ++-- libs/common/src/response-messages/index.ts | 1 + .../migration.sql | 2 + libs/prisma-service/prisma/schema.prisma | 29 ++--- 9 files changed, 123 insertions(+), 105 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20231010055923_endorsement_transaction_schema_body/migration.sql diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 06900397d..13e09ea4f 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -67,8 +67,8 @@ export class EcosystemController { @Param('orgId') orgId: string, @Query() getAllEndorsementsDto: GetAllEndorsementsDto, @Res() res: Response - ): Promise { - + ): Promise { + const ecosystemList = await this.ecosystemService.getEndorsementTranasactions(ecosystemId, orgId, getAllEndorsementsDto); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, @@ -87,7 +87,7 @@ export class EcosystemController { async getEcosystem( @Param('orgId') orgId: string, @Res() res: Response - ): Promise { + ): Promise { const ecosystemList = await this.ecosystemService.getAllEcosystem(orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, @@ -127,7 +127,7 @@ export class EcosystemController { @Query() getAllInvitationsDto: GetAllSentEcosystemInvitationsDto, @Param('orgId') orgId: string, @User() user: user, @Res() res: Response): Promise { - + if (!Object.values(Invitation).includes(getAllInvitationsDto.status)) { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidInvitationStatus); } @@ -206,15 +206,15 @@ export class EcosystemController { return res.status(HttpStatus.CREATED).json(finalResponse); } - @Post('/:orgId/transaction/schema') + @Post('/:ecosystemId/:orgId/transaction/schema') @ApiOperation({ summary: 'Request new schema', description: 'Request new schema' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - @ApiBearerAuth() - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - async requestSchemaTransaction(@Body() requestSchemaPayload: RequestSchemaDto, @Param('orgId') orgId: number, @Res() res: Response): Promise { - await this.ecosystemService.schemaEndorsementRequest(requestSchemaPayload, orgId); + // @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + // @ApiBearerAuth() + // @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) + // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + async requestSchemaTransaction(@Body() requestSchemaPayload: RequestSchemaDto, @Param('orgId') orgId: number, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { + await this.ecosystemService.schemaEndorsementRequest(requestSchemaPayload, orgId, ecosystemId); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, message: ResponseMessages.ecosystem.success.schemaRequest @@ -223,30 +223,31 @@ export class EcosystemController { } - @Post('/:orgId/transaction/cred-def') + @Post('/:ecosystemId/:orgId/transaction/cred-def') @ApiOperation({ summary: 'Request new credential-definition', description: 'Request new credential-definition' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) @ApiBearerAuth() - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - async requestCredDefTransaction(@Body() requestCredDefPayload: RequestCredDefDto, @Param('orgId') orgId: number, @Res() res: Response): Promise { - await this.ecosystemService.credDefEndorsementRequest(requestCredDefPayload, orgId); + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + async requestCredDefTransaction(@Body() requestCredDefPayload: RequestCredDefDto, @Param('orgId') orgId: number, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { + await this.ecosystemService.credDefEndorsementRequest(requestCredDefPayload, orgId, ecosystemId); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, message: ResponseMessages.ecosystem.success.schemaRequest }; return res.status(HttpStatus.CREATED).json(finalResponse); } - @Post('transaction/sign/:endorsementId') + + @Post('/:ecosystemId/transaction/sign/:endorsementId') @ApiOperation({ summary: 'Sign transaction', description: 'Sign transaction' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) @ApiBearerAuth() - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - async SignEndorsementRequests(@Param('endorsementId') endorsementId: string, @Res() res: Response): Promise { - await this.ecosystemService.signTransaction(endorsementId); + async SignEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { + await this.ecosystemService.signTransaction(endorsementId, ecosystemId); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, message: ResponseMessages.ecosystem.success.sign @@ -254,15 +255,15 @@ export class EcosystemController { return res.status(HttpStatus.CREATED).json(finalResponse); } - @Post('transaction/sumbit/:endorsementId') + @Post('/:ecosystemId/transaction/sumbit/:endorsementId') @ApiOperation({ summary: 'Sumbit transaction', description: 'Sumbit transaction' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) @ApiBearerAuth() - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - async SumbitEndorsementRequests(@Param('endorsementId') endorsementId: string, @Res() res: Response): Promise { - await this.ecosystemService.submitTransaction(endorsementId); + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + async SumbitEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { + await this.ecosystemService.submitTransaction(endorsementId, ecosystemId); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, message: ResponseMessages.ecosystem.success.sign @@ -288,9 +289,9 @@ export class EcosystemController { @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) async createInvitation(@Body() bulkInvitationDto: BulkEcosystemInvitationDto, - @Param('ecosystemId') ecosystemId: string, - @Param('orgId') orgId: string, - @User() user: user, @Res() res: Response): Promise { + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @User() user: user, @Res() res: Response): Promise { bulkInvitationDto.ecosystemId = ecosystemId; await this.ecosystemService.createInvitation(bulkInvitationDto, String(user.id)); @@ -304,33 +305,33 @@ export class EcosystemController { } - /** - * - * @param acceptRejectEcosystemInvitation - * @param reqUser - * @param res - * @returns Ecosystem invitation status - */ - @Post('/:orgId/invitations/:invitationId') - @ApiOperation({ - summary: 'Accept or reject ecosystem invitation', - description: 'Accept or Reject ecosystem invitations' - }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiBearerAuth() - @Roles(OrgRoles.OWNER) - async acceptRejectEcosystemInvitaion(@Body() acceptRejectEcosystemInvitation: AcceptRejectEcosystemInvitationDto, @Param('orgId') orgId: string, @Param('invitationId') invitationId: string, @User() user: user, @Res() res: Response): Promise { - acceptRejectEcosystemInvitation.orgId = orgId; - acceptRejectEcosystemInvitation.invitationId = invitationId; + /** + * + * @param acceptRejectEcosystemInvitation + * @param reqUser + * @param res + * @returns Ecosystem invitation status + */ + @Post('/:orgId/invitations/:invitationId') + @ApiOperation({ + summary: 'Accept or reject ecosystem invitation', + description: 'Accept or Reject ecosystem invitations' + }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiBearerAuth() + @Roles(OrgRoles.OWNER) + async acceptRejectEcosystemInvitaion(@Body() acceptRejectEcosystemInvitation: AcceptRejectEcosystemInvitationDto, @Param('orgId') orgId: string, @Param('invitationId') invitationId: string, @User() user: user, @Res() res: Response): Promise { + acceptRejectEcosystemInvitation.orgId = orgId; + acceptRejectEcosystemInvitation.invitationId = invitationId; - const invitationRes = await this.ecosystemService.acceptRejectEcosystemInvitaion(acceptRejectEcosystemInvitation, user.email); + const invitationRes = await this.ecosystemService.acceptRejectEcosystemInvitaion(acceptRejectEcosystemInvitation, user.email); - const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: invitationRes.response - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: invitationRes.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } @Put('/:ecosystemId/:orgId') @ApiOperation({ summary: 'Edit ecosystem', description: 'Edit existing ecosystem' }) @@ -352,7 +353,7 @@ export class EcosystemController { return res.status(HttpStatus.OK).json(finalResponse); } - + @Delete('/:ecosystemId/:orgId/invitations/:invitationId') @ApiOperation({ summary: 'Delete ecosystem pending invitations', description: 'Delete ecosystem pending invitations' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @@ -361,11 +362,11 @@ export class EcosystemController { @Roles(OrgRoles.OWNER) @ApiBearerAuth() async deleteEcosystemInvitations( - @Param('ecosystemId') ecosystemId: string, - @Param('invitationId') invitationId: string, - @Param('orgId') orgId: string, + @Param('ecosystemId') ecosystemId: string, + @Param('invitationId') invitationId: string, + @Param('orgId') orgId: string, @Res() res: Response): Promise { - + await this.ecosystemService.deleteEcosystemInvitations(invitationId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 353caf962..e40dba567 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -117,23 +117,23 @@ export class EcosystemService extends BaseService { } - async schemaEndorsementRequest(requestSchemaPayload: RequestSchemaDto, orgId: number): Promise { - const payload = { requestSchemaPayload, orgId}; + async schemaEndorsementRequest(requestSchemaPayload: RequestSchemaDto, orgId: number, ecosystemId:string): Promise { + const payload = { requestSchemaPayload, orgId, ecosystemId}; return this.sendNats(this.serviceProxy, 'schema-endorsement-request', payload); } - async credDefEndorsementRequest(requestCredDefPayload: RequestCredDefDto, orgId: number): Promise { - const payload = { requestCredDefPayload, orgId}; + async credDefEndorsementRequest(requestCredDefPayload: RequestCredDefDto, orgId: number, ecosystemId:string): Promise { + const payload = { requestCredDefPayload, orgId, ecosystemId}; return this.sendNats(this.serviceProxy, 'credDef-endorsement-request', payload); } - async signTransaction(endorsementId:string): Promise { - const payload = { endorsementId }; + async signTransaction(endorsementId:string, ecosystemId:string): Promise { + const payload = { endorsementId, ecosystemId }; return this.sendNats(this.serviceProxy, 'sign-endorsement-transaction', payload); } - async submitTransaction(endorsementId:string): Promise { - const payload = { endorsementId }; + async submitTransaction(endorsementId:string, ecosystemId:string): Promise { + const payload = { endorsementId, ecosystemId }; return this.sendNats(this.serviceProxy, 'sumbit-endorsement-transaction', payload); } } diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts index 2d01cbebb..1ca0aff37 100644 --- a/apps/ecosystem/interfaces/ecosystem.interfaces.ts +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -1,8 +1,14 @@ +export interface AttributeValue { + attributeName: string; + schemaDataType: string; + displayName: string; +} + export interface RequestSchemaEndorsement { orgId: number name: string; version: string; - attributes: IAttributeValue[]; + attributes: AttributeValue[]; endorse?: boolean; } diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index c8e5317b6..d1a82bc97 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -131,9 +131,9 @@ export class EcosystemController { */ @MessagePattern({ cmd: 'schema-endorsement-request' }) async schemaEndorsementRequest( - @Body() payload: { requestSchemaPayload: RequestSchemaEndorsement; orgId: number } + @Body() payload: { requestSchemaPayload: RequestSchemaEndorsement; orgId: number, ecosystemId: string } ): Promise { - return this.ecosystemService.requestSchemaEndorsement(payload.requestSchemaPayload, payload.orgId); + return this.ecosystemService.requestSchemaEndorsement(payload.requestSchemaPayload, payload.orgId, payload.ecosystemId); } /** @@ -143,9 +143,9 @@ export class EcosystemController { */ @MessagePattern({ cmd: 'credDef-endorsement-request' }) async credDefEndorsementRequest( - @Body() payload: { requestCredDefPayload: RequestCredDeffEndorsement; orgId: number } + @Body() payload: { requestCredDefPayload: RequestCredDeffEndorsement; orgId: number; ecosystemId:string} ): Promise { - return this.ecosystemService.requestCredDeffEndorsement(payload.requestCredDefPayload, payload.orgId); + return this.ecosystemService.requestCredDeffEndorsement(payload.requestCredDefPayload, payload.orgId, payload.ecosystemId); } /** @@ -155,9 +155,9 @@ export class EcosystemController { */ @MessagePattern({ cmd: 'sign-endorsement-transaction' }) async signTransaction( - @Body() payload: { endorsementId: string } + @Body() payload: { endorsementId: string, ecosystemId:string } ): Promise { - return this.ecosystemService.signTransaction(payload.endorsementId); + return this.ecosystemService.signTransaction(payload.endorsementId, payload.ecosystemId); } /** @@ -167,8 +167,8 @@ export class EcosystemController { */ @MessagePattern({ cmd: 'sumbit-endorsement-transaction' }) async submitTransaction( - @Body() payload: { endorsementId: string } + @Body() payload: { endorsementId: string, ecosystemId:string } ): Promise { - return this.ecosystemService.submitTransaction(payload.endorsementId); + return this.ecosystemService.submitTransaction(payload.endorsementId, payload.ecosystemId); } } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 89d5faaea..69bc831a8 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -4,7 +4,7 @@ import { PrismaService } from '@credebl/prisma-service'; import { ecosystem, ecosystem_invitations, ecosystem_orgs, ecosystem_roles, endorsement_transaction, org_agents, platform_config } from '@prisma/client'; import { EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; -import { SchemaTransactionResponse } from '../interfaces/ecosystem.interfaces'; +import { SchemaTransactionResponse } from '../interfaces/ecosystem.interfaces'; import { ResponseMessages } from '@credebl/common/response-messages'; // eslint-disable-next-line camelcase @@ -406,13 +406,16 @@ export class EcosystemRepository { } /** - * Description: Get getAgentEndPoint by orgId - * @param orgId + * Description: Get getAgentEndPoint by invalidEcosystemId + * @param invalidEcosystemId * @returns Get getAgentEndPoint details */ // eslint-disable-next-line camelcase - async getEcosystemLeadDetails(): Promise { + async getEcosystemLeadDetails(ecosystemId:string): Promise { try { + if (!ecosystemId) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidEcosystemId); + } const ecosystemRoleDetails = await this.prisma.ecosystem_roles.findFirst({ where: { name: EcosystemRoles.ECOSYSTEM_LEAD @@ -420,7 +423,8 @@ export class EcosystemRepository { }); const ecosystemLeadDetails = await this.prisma.ecosystem_orgs.findFirst({ where: { - ecosystemRoleId: ecosystemRoleDetails.id + ecosystemRoleId: ecosystemRoleDetails.id, + ecosystemId } }); return ecosystemLeadDetails; @@ -449,6 +453,7 @@ export class EcosystemRepository { async storeTransactionRequest( schemaTransactionResponse: SchemaTransactionResponse, + requestBody: object, type: endorsementTransactionType ): Promise { try { @@ -461,7 +466,8 @@ export class EcosystemRepository { status, ecosystemOrgId, responsePayload: '', - type + type, + requestBody } }); } catch (error) { diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 29069fd97..b6ce87f29 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -264,8 +264,9 @@ export class EcosystemService { * @param RequestSchemaEndorsement * @returns */ - async requestSchemaEndorsement(requestSchemaPayload: RequestSchemaEndorsement, orgId: number): Promise { + async requestSchemaEndorsement(requestSchemaPayload: RequestSchemaEndorsement, orgId: number, ecosystemId:string): Promise { try { + const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(orgId); if (!ecosystemMemberDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.notFound); @@ -278,7 +279,7 @@ export class EcosystemService { const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); - const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(); + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); @@ -309,7 +310,7 @@ export class EcosystemService { if ('failed' === schemaTransactionRequest.message.schemaState.state) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestSchemaTransaction); } - return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, endorsementTransactionType.SCHEMA); + return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, requestSchemaPayload, endorsementTransactionType.SCHEMA); } catch (error) { this.logger.error(`In request schema endorsement : ${JSON.stringify(error)}`); @@ -317,7 +318,7 @@ export class EcosystemService { } } - async requestCredDeffEndorsement(requestCredDefPayload: RequestCredDeffEndorsement, orgId: number): Promise { + async requestCredDeffEndorsement(requestCredDefPayload: RequestCredDeffEndorsement, orgId: number, ecosystemId:string): Promise { try { const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(orgId); if (!ecosystemMemberDetails) { @@ -331,7 +332,7 @@ export class EcosystemService { // const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); - const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(); + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); @@ -362,7 +363,7 @@ export class EcosystemService { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); } - return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, 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)}`); @@ -418,14 +419,14 @@ export class EcosystemService { } } - async signTransaction(endorsementId: string): Promise { + async signTransaction(endorsementId: string, ecosystemId:string): Promise { try { const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.REQUESTED); if (!endorsementTransactionPayload) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidTransaction); } - const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(); + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); if (!getEcosystemLeadDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); @@ -486,7 +487,7 @@ export class EcosystemService { } } - async submitTransaction(endorsementId: string): Promise { + async submitTransaction(endorsementId: string, ecosystemId:string): Promise { try { const endorsementTransactionPayload: EndorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.SIGNED); if (!endorsementTransactionPayload) { @@ -494,7 +495,7 @@ export class EcosystemService { } const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(Number(endorsementTransactionPayload.ecosystemOrgs.orgId)); - const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(); + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); // eslint-disable-next-line camelcase const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 1cdead15d..beb6e1fb4 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -210,6 +210,7 @@ export const ResponseMessages = { notFound: 'Organization not found', leadNotFound: 'Lead details not found', invalidOrgId: 'Invalid organization Id', + invalidEcosystemId: 'Invalid ecosystem Id', invalidTransaction: 'Transaction does not exist', invalidAgentUrl: 'Invalid agent url' } diff --git a/libs/prisma-service/prisma/migrations/20231010055923_endorsement_transaction_schema_body/migration.sql b/libs/prisma-service/prisma/migrations/20231010055923_endorsement_transaction_schema_body/migration.sql new file mode 100644 index 000000000..77f492116 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231010055923_endorsement_transaction_schema_body/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "endorsement_transaction" ADD COLUMN "requestBody" JSONB; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 45aa68dd9..4f2373d54 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -382,18 +382,19 @@ model ecosystem_orgs { } model endorsement_transaction { - id String @id @default(uuid()) - endorserDid String - authorDid String - requestPayload String - responsePayload String - type String? - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) - deletedAt DateTime? @db.Timestamp(6) - status String - ecosystemOrgId String - ecosystemOrgs ecosystem_orgs @relation(fields: [ecosystemOrgId], references: [id]) + id String @id @default(uuid()) + endorserDid String + authorDid String + requestPayload String + responsePayload String + type String? + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) + status String + ecosystemOrgId String + requestBody Json? + ecosystemOrgs ecosystem_orgs @relation(fields: [ecosystemOrgId], references: [id]) } From d6c06f1c7ce2fca2ffcf98336ad482ff26dc9bf8 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Tue, 10 Oct 2023 16:02:29 +0530 Subject: [PATCH 128/162] fix: added auth guard Signed-off-by: tipusinghaw --- apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index c472e8720..51fc3262c 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -252,10 +252,10 @@ export class EcosystemController { @Post('/:ecosystemId/:orgId/transaction/schema') @ApiOperation({ summary: 'Request new schema', description: 'Request new schema' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - // @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - // @ApiBearerAuth() - // @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) - // @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) async requestSchemaTransaction(@Body() requestSchemaPayload: RequestSchemaDto, @Param('orgId') orgId: number, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { await this.ecosystemService.schemaEndorsementRequest(requestSchemaPayload, orgId, ecosystemId); const finalResponse: IResponseType = { From 47e6aafe4b2038940b396d59e841b940518f8c70 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Tue, 10 Oct 2023 17:04:03 +0530 Subject: [PATCH 129/162] fix: added ordId sign and submit transaction Signed-off-by: tipusinghaw --- apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 51fc3262c..020398a4b 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -282,14 +282,14 @@ export class EcosystemController { return res.status(HttpStatus.CREATED).json(finalResponse); } - @Post('/:ecosystemId/transaction/sign/:endorsementId') + @Post('/:ecosystemId/:orgId/transaction/sign/:endorsementId') @ApiOperation({ summary: 'Sign transaction', description: 'Sign transaction' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) @ApiBearerAuth() @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - async SignEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { + async SignEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: number, @Res() res: Response): Promise { await this.ecosystemService.signTransaction(endorsementId, ecosystemId); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -298,14 +298,14 @@ export class EcosystemController { return res.status(HttpStatus.CREATED).json(finalResponse); } - @Post('/:ecosystemId/transaction/sumbit/:endorsementId') + @Post('/:ecosystemId/:orgId/transaction/sumbit/:endorsementId') @ApiOperation({ summary: 'Sumbit transaction', description: 'Sumbit transaction' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) @ApiBearerAuth() @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) - async SumbitEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Res() res: Response): Promise { + async SumbitEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: number, @Res() res: Response): Promise { await this.ecosystemService.submitTransaction(endorsementId, ecosystemId); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, From 738f7ba5454427d55caf4de0ce99a6044835a025 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Tue, 10 Oct 2023 17:14:57 +0530 Subject: [PATCH 130/162] feat: get ecosystem dashboard card counts Signed-off-by: bhavanakarwade --- apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 2 +- apps/ecosystem/src/ecosystem.controller.ts | 2 +- apps/ecosystem/src/ecosystem.repository.ts | 10 ++-------- apps/ecosystem/src/ecosystem.service.ts | 2 +- libs/common/src/response-messages/index.ts | 2 +- 5 files changed, 6 insertions(+), 12 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index ba342792f..2f6f90706 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -98,7 +98,7 @@ export class EcosystemController { } @Get('/:ecosystemId/:orgId') - @ApiOperation({ summary: 'Get an ecosystem dashboard', description: 'Get an ecosystem dashboard' }) + @ApiOperation({ summary: 'Get an ecosystem dashboard details', description: 'Get an ecosystem dashboard details' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 00a5b3624..1c9d60494 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -49,7 +49,7 @@ export class EcosystemController { /** * Description: get ecosystems dashboard details - * @returns Get ecosystem dashboard card counts + * @returns Get ecosystem dashboard details */ @MessagePattern({ cmd: 'get-ecosystem-dashboard-details' }) async getEcosystemDashboardDetails( diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 79fde92c9..2cbd32d9d 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -165,14 +165,11 @@ export class EcosystemRepository { } } ); - - return membersCount; - + return membersCount; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); } - } async getEcosystemEndorsementsCount (ecosystemId: string): Promise { @@ -185,14 +182,11 @@ export class EcosystemRepository { } } }); - - return endorsementsCount; - + return endorsementsCount; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); } - } /** diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index d48a7ce63..00bd02e66 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -81,7 +81,7 @@ export class EcosystemService { */ async getEcosystemDashboardDetails(ecosystemId: string): Promise { try { - return this.ecosystemRepository.getEcosystemDashboardDetails(ecosystemId); + return await this.ecosystemRepository.getEcosystemDashboardDetails(ecosystemId); } catch (error) { this.logger.error(`In ecosystem dashboard details : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index faba709fd..1d7db4da1 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -188,7 +188,7 @@ export const ResponseMessages = { update: 'Ecosystem updated successfully', delete: 'Ecosystem invitations deleted successfully', fetch: 'Ecosystem fetched successfully', - getEcosystemDashboard: 'Ecosystem dashboard card counts fetched successfully', + getEcosystemDashboard: 'Ecosystem dashboard details fetched successfully', getInvitation: 'Ecosystem invitations fetched successfully', createInvitation: 'Ecosystem invitations sent successfully', schemaRequest: 'Schema transaction request created successfully', From a23cf097430861332c478c0010c8eeba060a4261 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Tue, 10 Oct 2023 17:38:23 +0530 Subject: [PATCH 131/162] worked on ecosystem config model (#139) Signed-off-by: Nishad --- apps/ecosystem/src/ecosystem.service.ts | 2 +- apps/user/src/user.service.ts | 2 +- .../prisma/data/credebl-master-table.json | 7 ++++++- .../migration.sql | 21 +++++++++++++++++++ libs/prisma-service/prisma/schema.prisma | 13 +++++++++++- libs/prisma-service/prisma/seed.ts | 15 +++++++++++++ 6 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20231010065246_ecosystem_config/migration.sql diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 4cb2160c8..618ca271d 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -649,7 +649,7 @@ export class EcosystemService { */ async checkEcosystemEnableFlag( ): Promise { - const platformConfigData = await this.prisma.platform_config.findMany(); + const platformConfigData = await this.prisma.ecosystem_config.findMany(); return platformConfigData[0].enableEcosystem; } diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 19b057c14..5102b5bf1 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -339,7 +339,7 @@ export class UserService { async getProfile(payload: { id }): Promise { try { const userData = await this.userRepository.getUserById(payload.id); - const platformConfigData = await this.prisma.platform_config.findMany(); + const platformConfigData = await this.prisma.ecosystem_config.findMany(); userData['enableEcosystem'] = platformConfigData[0].enableEcosystem; return userData; } catch (error) { diff --git a/libs/prisma-service/prisma/data/credebl-master-table.json b/libs/prisma-service/prisma/data/credebl-master-table.json index 5053c4d65..9d6bdb4d0 100644 --- a/libs/prisma-service/prisma/data/credebl-master-table.json +++ b/libs/prisma-service/prisma/data/credebl-master-table.json @@ -145,5 +145,10 @@ "status": "Submitted", "ecosystemOrgId": "ca6ee687-a3a9-42ce-9e49-02bf62f5c93a" } - ] + ], + "ecosystemConfigData": { + "url": "https://example3.com", + "enableEcosystem": false, + "autoEndorsement": false + } } \ No newline at end of file diff --git a/libs/prisma-service/prisma/migrations/20231010065246_ecosystem_config/migration.sql b/libs/prisma-service/prisma/migrations/20231010065246_ecosystem_config/migration.sql new file mode 100644 index 000000000..0882ed891 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231010065246_ecosystem_config/migration.sql @@ -0,0 +1,21 @@ +/* + Warnings: + - You are about to drop the column `enableEcosystem` on the `platform_config` table. All the data in the column will be lost. +*/ +-- AlterTable +ALTER TABLE "platform_config" DROP COLUMN "enableEcosystem"; + +-- CreateTable +CREATE TABLE "ecosystem_config" ( + "id" TEXT NOT NULL, + "url" TEXT NOT NULL, + "enableEcosystem" BOOLEAN NOT NULL DEFAULT false, + "autoEndorsement" BOOLEAN NOT NULL DEFAULT false, + "createDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdBy" INTEGER NOT NULL DEFAULT 1, + "lastChangedDateTime" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastChangedBy" INTEGER NOT NULL DEFAULT 1, + "deletedAt" TIMESTAMP(6), + + CONSTRAINT "ecosystem_config_pkey" PRIMARY KEY ("id") +); \ No newline at end of file diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 4f2373d54..b85c95c05 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -137,7 +137,6 @@ model platform_config { lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) lastChangedBy Int @default(1) deletedAt DateTime? @db.Timestamp(6) - enableEcosystem Boolean @default(false) } model org_agents { @@ -398,3 +397,15 @@ model endorsement_transaction { requestBody Json? ecosystemOrgs ecosystem_orgs @relation(fields: [ecosystemOrgId], references: [id]) } + +model ecosystem_config { + id String @id @default(uuid()) + url String + enableEcosystem Boolean @default(false) + autoEndorsement Boolean @default(false) + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy Int @default(1) + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy Int @default(1) + deletedAt DateTime? @db.Timestamp(6) +} diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index cd50a00c6..ccaa7d8c9 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -126,6 +126,19 @@ const createEcosystemRoles = async (): Promise => { } }; +const createEcosystemConfig = async (): Promise => { + try { + const { ecosystemConfigData } = JSON.parse(configData); + const configDetails = await prisma.ecosystem_config.create({ + data: ecosystemConfigData + }); + + logger.log(configDetails); + } catch (e) { + logger.error('An error occurred seeding createEcosystemConfig:', e); + } +}; + async function main(): Promise { await createPlatformConfig(); @@ -137,6 +150,8 @@ async function main(): Promise { await createOrgAgentTypes(); await createLedger(); await createEcosystemRoles(); + await createEcosystemConfig(); + } From 85e0ccaafd318fdef99b216731e81cc997069fdb Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Tue, 10 Oct 2023 17:43:11 +0530 Subject: [PATCH 132/162] changed endpoints Signed-off-by: bhavanakarwade --- apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index e2afdd97e..ae6039b6b 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -98,8 +98,8 @@ export class EcosystemController { return res.status(HttpStatus.OK).json(finalResponse); } - @Get('/:ecosystemId/:orgId') - @ApiOperation({ summary: 'Get an ecosystem dashboard details', description: 'Get an ecosystem dashboard details' }) + @Get('/:ecosystemId/:orgId/dashboard') + @ApiOperation({ summary: 'Get ecosystem dashboard details', description: 'Get ecosystem dashboard details' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() From 108b76b0291db2773db367b4c9f4ff9dd29e13f7 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Tue, 10 Oct 2023 17:49:37 +0530 Subject: [PATCH 133/162] added role guards Signed-off-by: bhavanakarwade --- apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index ae6039b6b..5f706f685 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -101,7 +101,9 @@ export class EcosystemController { @Get('/:ecosystemId/:orgId/dashboard') @ApiOperation({ summary: 'Get ecosystem dashboard details', description: 'Get ecosystem dashboard details' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) @ApiBearerAuth() async getEcosystemDashboardDetails(@Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: string, @Res() res: Response): Promise { From 9caa7a3158eda752044159d3ca7ff1ed88b77b98 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Tue, 10 Oct 2023 19:09:55 +0530 Subject: [PATCH 134/162] fixed endorser list for ecosystem lead (#143) Signed-off-by: Nishad --- apps/ecosystem/src/ecosystem.repository.ts | 1 + apps/ecosystem/src/ecosystem.service.ts | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 3fbcf4eb1..318bb6c39 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -448,6 +448,7 @@ export class EcosystemRepository { } + async getEndorsementsWithPagination(queryObject: object, pageNumber: number, pageSize: number): Promise { try { const result = await this.prisma.$transaction([ diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 7d5e0fd78..fcfc30d13 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -670,10 +670,14 @@ export class EcosystemService { const { ecosystemId, orgId, pageNumber, pageSize, search, type } = payload; try { + const queryEcoOrgs = { + ecosystemId, + orgId + }; + const query = { ecosystemOrgs: { - ecosystemId, - orgId + ecosystemId }, OR: [ { status: { contains: search, mode: 'insensitive' } }, @@ -681,6 +685,12 @@ export class EcosystemService { ] }; + const ecosystemOrgData = await this.ecosystemRepository.fetchEcosystemOrg(queryEcoOrgs); + + if (ecosystemOrgData['ecosystemRole']['name'] !== EcosystemRoles.ECOSYSTEM_LEAD) { + query.ecosystemOrgs['orgId'] = orgId; + } + if (type) { query['type'] = type; } From a75787f337e3fc0f0749298ba9a21bc2b1eef546 Mon Sep 17 00:00:00 2001 From: pallavicoder Date: Tue, 10 Oct 2023 20:12:37 +0530 Subject: [PATCH 135/162] feat:reject endorsement request by lead Signed-off-by: pallavicoder --- .../decline-endorsement-transaction-dto.ts | 21 ++++++++++++ .../src/ecosystem/ecosystem.controller.ts | 34 +++++++++++++++++++ .../src/ecosystem/ecosystem.service.ts | 7 ++++ apps/ecosystem/src/ecosystem.controller.ts | 15 ++++++++ apps/ecosystem/src/ecosystem.repository.ts | 23 ++++++++++++- apps/ecosystem/src/ecosystem.service.ts | 12 +++++++ libs/common/src/response-messages/index.ts | 3 +- 7 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction-dto.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction-dto.ts b/apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction-dto.ts new file mode 100644 index 000000000..f7658d5e2 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction-dto.ts @@ -0,0 +1,21 @@ +import { IsEnum, IsNotEmpty } from 'class-validator'; + +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; +import { endorsementTransactionStatus } from 'apps/ecosystem/enums/ecosystem.enum'; + +export class DeclienEndorsementTransactionDto { + ecosystemId: string; + orgId: string; + endorsementId: string; + + @ApiProperty({ + enum: [endorsementTransactionStatus.DECLINED] + }) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Please provide only DECLINED status' }) + @IsEnum(endorsementTransactionStatus) + status:endorsementTransactionStatus.DECLINED; + +} \ 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 06900397d..65bfcc5a0 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -29,6 +29,8 @@ import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { OrgRoles } from 'libs/org-roles/enums'; import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; import { CreateEcosystemDto } from './dtos/create-ecosystem-dto'; +import { DeclienEndorsementTransactionDto } from './dtos/decline-endorsement-transaction-dto'; + @UseFilters(CustomExceptionFilter) @Controller('ecosystem') @@ -332,6 +334,7 @@ export class EcosystemController { return res.status(HttpStatus.CREATED).json(finalResponse); } + @Put('/:ecosystemId/:orgId') @ApiOperation({ summary: 'Edit ecosystem', description: 'Edit existing ecosystem' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @@ -374,4 +377,35 @@ export class EcosystemController { return res.status(HttpStatus.OK).json(finalResponse); } + + /** + * + * @param declineEndorsementTransactionRequest + * + * @param res + * @returns endorsement transaction status + */ + + + @Put('/:ecosystemId/:orgId/transactions/:endorsementId') + @ApiOperation({ + summary: 'Declien Endorsement Request By Lead', + description: 'Declien Endorsement Request By Lead' + }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) + async declineEndorsementRequestByLead(@Body() declineEndorsementTransactionRequest: DeclienEndorsementTransactionDto, @Param('ecosystemId') ecosystemId:string, @Param('orgId') orgId: string, @Param('endorsementId') endorsementId: string, @User() user: user, @Res() res: Response): Promise { + declineEndorsementTransactionRequest.orgId = orgId; + declineEndorsementTransactionRequest.ecosystemId = ecosystemId; + declineEndorsementTransactionRequest.endorsementId = endorsementId; + + const DeclienEndorsementTransaction = await this.ecosystemService.declineEndorsementRequestByLead(declineEndorsementTransactionRequest); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: DeclienEndorsementTransaction.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } } \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 353caf962..b81f47ea5 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -8,6 +8,7 @@ import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.d import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-sent-ecosystemInvitations-dto'; import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; import { RequestCredDefDto, RequestSchemaDto } from './dtos/request-schema-dto'; +import { DeclienEndorsementTransactionDto } from './dtos/decline-endorsement-transaction-dto'; @Injectable() export class EcosystemService extends BaseService { @@ -136,4 +137,10 @@ export class EcosystemService extends BaseService { const payload = { endorsementId }; return this.sendNats(this.serviceProxy, 'sumbit-endorsement-transaction', payload); } + + async declineEndorsementRequestByLead(declineEndorsementTransactionRequest: DeclienEndorsementTransactionDto): Promise<{ response: string }> { + const payload = { declineEndorsementTransactionRequest}; + return this.sendNats(this.serviceProxy, 'decline-endorsement-transaction', payload); + } + } diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index c8e5317b6..641a61717 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -8,6 +8,7 @@ import { AcceptRejectEcosystemInvitationDto } from '../dtos/accept-reject-ecosys import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; import { RequestCredDeffEndorsement, RequestSchemaEndorsement } from '../interfaces/ecosystem.interfaces'; +import { DeclienEndorsementTransactionDto } from 'apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction-dto'; @Controller() export class EcosystemController { @@ -171,4 +172,18 @@ export class EcosystemController { ): Promise { return this.ecosystemService.submitTransaction(payload.endorsementId); } + + /** + * + * @param payload + * @returns Declien Endorsement Transaction status + */ + @MessagePattern({ cmd: 'decline-endorsement-transaction' }) + async declineEndorsementRequestByLead(payload: { + declineEndorsementTransactionRequest: DeclienEndorsementTransactionDto; + }): Promise { + return this.ecosystemService.declineEndorsementRequestByLead(payload.declineEndorsementTransactionRequest); + } + + } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 89d5faaea..cb604b375 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -572,4 +572,25 @@ export class EcosystemRepository { } } -} \ No newline at end of file + async updateEndorsementRequestStatus( + endorsementId: string, + status:endorsementTransactionStatus + // eslint-disable-next-line camelcase, + ): Promise { + try { + const updatedTransaction = await this.prisma.endorsement_transaction.update({ + where: { id: endorsementId, status:endorsementTransactionStatus.REQUESTED}, + data: { + status + } + }); + + return updatedTransaction; + + } catch (error) { + this.logger.error(`Error in updating endorsement transaction: ${error.message}`); + throw error; + } + } +} + diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 29069fd97..217a1830a 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -17,6 +17,7 @@ import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; // eslint-disable-next-line camelcase import { platform_config } from '@prisma/client'; import { CommonConstants } from '@credebl/common/common.constant'; +import { DeclienEndorsementTransactionDto } from 'apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction-dto'; @Injectable() export class EcosystemService { @@ -660,4 +661,15 @@ export class EcosystemService { } } + + async declineEndorsementRequestByLead(declineEndorsementTransactionRequest: DeclienEndorsementTransactionDto): Promise { + try { + const {endorsementId } = declineEndorsementTransactionRequest; + return this.ecosystemRepository.updateEndorsementRequestStatus(endorsementId, endorsementTransactionStatus.DECLINED); + } catch (error) { + this.logger.error(`acceptRejectInvitations: ${error}`); + throw new RpcException(error.response ? error.response : error); + } + } + } diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 1cdead15d..4dcd4d174 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -194,7 +194,8 @@ export const ResponseMessages = { sign: 'Transaction request signed successfully', invitationReject: 'Ecosystem invitation rejected', invitationAccept: 'Ecosystem invitation accepted successfully', - fetchEndorsors: 'Endorser transactions fetched successfully' + fetchEndorsors: 'Endorser transactions fetched successfully', + DeclienEndorsementTransaction:'decline Endorsement Request successfully' }, error: { notCreated: 'Error while creating ecosystem', From a4bb7c765944098037336c68760ae5a4a876c65b Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Wed, 11 Oct 2023 18:39:25 +0530 Subject: [PATCH 136/162] refactor: ecosystem org model update (#144) * refactored GET API ecosystem details Signed-off-by: Nishad * refactored ecosystem orgs model for orgname and org did Signed-off-by: Nishad --------- Signed-off-by: Nishad --- .../dtos/accept-reject-invitations.dto.ts | 16 ++++++++++- .../ecosystem/dtos/create-ecosystem-dto.ts | 14 ++++++++++ .../src/ecosystem/ecosystem.controller.ts | 4 +-- .../accept-reject-ecosysteminvitation.dto.ts | 2 ++ .../dtos/update-ecosystemOrgs.dto.ts | 2 ++ apps/ecosystem/src/ecosystem.repository.ts | 28 ++++++++++++++----- apps/ecosystem/src/ecosystem.service.ts | 14 ++++++---- .../migration.sql | 3 ++ libs/prisma-service/prisma/schema.prisma | 2 ++ 9 files changed, 70 insertions(+), 15 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20231011095332_ecosystem_orgs_did/migration.sql diff --git a/apps/api-gateway/src/ecosystem/dtos/accept-reject-invitations.dto.ts b/apps/api-gateway/src/ecosystem/dtos/accept-reject-invitations.dto.ts index 63cdfae41..ee70759c4 100644 --- a/apps/api-gateway/src/ecosystem/dtos/accept-reject-invitations.dto.ts +++ b/apps/api-gateway/src/ecosystem/dtos/accept-reject-invitations.dto.ts @@ -1,4 +1,4 @@ -import { IsEnum, IsNotEmpty} from 'class-validator'; +import { IsEnum, IsNotEmpty, IsString, MaxLength, MinLength} from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { Invitation } from '@credebl/enum/enum'; @@ -11,6 +11,20 @@ export class AcceptRejectEcosystemInvitationDto { invitationId: string; orgId: string; + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'organization name is required.' }) + @MinLength(2, { message: 'organization name must be at least 2 characters.' }) + @MaxLength(50, { message: 'organization name must be at most 50 characters.' }) + @IsString({ message: 'organization name must be in string format.' }) + orgName: string; + + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'organization did is required.' }) + @IsString({ message: 'organization did must be in string format.' }) + orgDid: string; + @ApiProperty({ enum: [Invitation.ACCEPTED, Invitation.REJECTED] }) diff --git a/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts b/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts index b8915ca81..1dc36955c 100644 --- a/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts +++ b/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts @@ -39,6 +39,20 @@ export class CreateEcosystemDto { @IsString({ message: 'logo must be in string format.' }) logo?: string; + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'organization name is required.' }) + @MinLength(2, { message: 'organization name must be at least 2 characters.' }) + @MaxLength(50, { message: 'organization name must be at most 50 characters.' }) + @IsString({ message: 'organization name must be in string format.' }) + orgName: string; + + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'organization did is required.' }) + @IsString({ message: 'organization did must be in string format.' }) + orgDid: string; + orgId?: 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 6b04d362d..cbc704356 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -80,7 +80,7 @@ export class EcosystemController { } @Get('/:orgId') - @ApiOperation({ summary: 'Get all ecosystem', description: 'Get all existing ecosystem' }) + @ApiOperation({ summary: 'Get all organization ecosystems', description: 'Get all existing ecosystems of an specific organization' }) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @@ -375,7 +375,7 @@ export class EcosystemController { * @param res * @returns Ecosystem invitation status */ - @Post('/:orgId/invitations/:invitationId') + @Put('/:orgId/invitations/:invitationId') @ApiOperation({ summary: 'Accept or reject ecosystem invitation', description: 'Accept or Reject ecosystem invitations' diff --git a/apps/ecosystem/dtos/accept-reject-ecosysteminvitation.dto.ts b/apps/ecosystem/dtos/accept-reject-ecosysteminvitation.dto.ts index 0fbfcdaa7..789408777 100644 --- a/apps/ecosystem/dtos/accept-reject-ecosysteminvitation.dto.ts +++ b/apps/ecosystem/dtos/accept-reject-ecosysteminvitation.dto.ts @@ -4,4 +4,6 @@ export class AcceptRejectEcosystemInvitationDto { orgId: string; invitationId: string; status: Invitation; + orgName: string; + orgDid: string; } diff --git a/apps/ecosystem/dtos/update-ecosystemOrgs.dto.ts b/apps/ecosystem/dtos/update-ecosystemOrgs.dto.ts index 7f3e27a8b..f87298807 100644 --- a/apps/ecosystem/dtos/update-ecosystemOrgs.dto.ts +++ b/apps/ecosystem/dtos/update-ecosystemOrgs.dto.ts @@ -1,5 +1,7 @@ export class updateEcosystemOrgsDto { orgId: string; + orgName: string; + orgDid: string; status: string; ecosystemId: string; ecosystemRoleId: string; diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 318bb6c39..def034393 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -25,7 +25,7 @@ export class EcosystemRepository { async createNewEcosystem(createEcosystemDto): Promise { try { const transaction = await this.prisma.$transaction(async (prisma) => { - const { name, description, userId, logo, tags, orgId } = createEcosystemDto; + const { name, description, userId, logo, tags, orgId, orgName, orgDid } = createEcosystemDto; const createdEcosystem = await prisma.ecosystem.create({ data: { name, @@ -55,7 +55,9 @@ export class EcosystemRepository { orgId: String(orgId), status: EcosystemOrgStatus.ACTIVE, ecosystemId: createdEcosystem.id, - ecosystemRoleId: ecosystemRoleDetails.id + ecosystemRoleId: ecosystemRoleDetails.id, + orgName, + orgDid } }); } @@ -105,9 +107,19 @@ export class EcosystemRepository { const ecosystemDetails = await this.prisma.ecosystem.findMany({ where: { ecosystemOrgs: { - some: { - orgId - } + some: { + orgId + } + } + }, + include:{ + ecosystemOrgs: { + where: { + orgId + }, + include:{ + ecosystemRole: true + } } } }); @@ -275,14 +287,16 @@ export class EcosystemRepository { // eslint-disable-next-line camelcase async updateEcosystemOrgs(createEcosystemOrgsDto: updateEcosystemOrgsDto): Promise { try { - const { orgId, status, ecosystemRoleId, ecosystemId } = createEcosystemOrgsDto; + const { orgId, status, ecosystemRoleId, ecosystemId, orgName, orgDid } = createEcosystemOrgsDto; return this.prisma.ecosystem_orgs.create({ data: { orgId: String(orgId), ecosystemId, status, - ecosystemRoleId + ecosystemRoleId, + orgName, + orgDid } }); } catch (error) { diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index fcfc30d13..0d1be624b 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -68,7 +68,8 @@ export class EcosystemService { // eslint-disable-next-line camelcase async getAllEcosystem(payload: { orgId: string }): Promise { - const getAllEcosystemDetails = await this.ecosystemRepository.getAllEcosystemDetails(payload.orgId); + const getAllEcosystemDetails = await this.ecosystemRepository.getAllEcosystemDetails(payload.orgId); + if (!getAllEcosystemDetails) { throw new NotFoundException(ResponseMessages.ecosystem.error.update); } @@ -155,7 +156,8 @@ export class EcosystemService { */ async acceptRejectEcosystemInvitations(acceptRejectInvitation: AcceptRejectEcosystemInvitationDto): Promise { try { - const { orgId, status, invitationId } = acceptRejectInvitation; + + const { orgId, status, invitationId, orgName, orgDid } = acceptRejectInvitation; const invitation = await this.ecosystemRepository.getEcosystemInvitationById(invitationId); if (!invitation) { @@ -172,7 +174,7 @@ export class EcosystemService { } const ecosystemRole = await this.ecosystemRepository.getEcosystemRole(EcosystemRoles.ECOSYSTEM_MEMBER); - const updateEcosystemOrgs = await this.updatedEcosystemOrgs(orgId, invitation.ecosystemId, ecosystemRole.id); + const updateEcosystemOrgs = await this.updatedEcosystemOrgs(orgId, orgName, orgDid, invitation.ecosystemId, ecosystemRole.id); if (!updateEcosystemOrgs) { throw new NotFoundException(ResponseMessages.ecosystem.error.orgsNotUpdate); @@ -185,13 +187,15 @@ export class EcosystemService { } } - async updatedEcosystemOrgs(orgId: string, ecosystemId: string, ecosystemRoleId: string): Promise { + async updatedEcosystemOrgs(orgId: string, orgName: string, orgDid: string, ecosystemId: string, ecosystemRoleId: string): Promise { try { const data = { orgId, status: EcosystemOrgStatus.ACTIVE, ecosystemId, - ecosystemRoleId + ecosystemRoleId, + orgName, + orgDid }; return await this.ecosystemRepository.updateEcosystemOrgs(data); } catch (error) { diff --git a/libs/prisma-service/prisma/migrations/20231011095332_ecosystem_orgs_did/migration.sql b/libs/prisma-service/prisma/migrations/20231011095332_ecosystem_orgs_did/migration.sql new file mode 100644 index 000000000..00b7d3514 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231011095332_ecosystem_orgs_did/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "ecosystem_orgs" ADD COLUMN "orgDid" VARCHAR, +ADD COLUMN "orgName" TEXT; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index b85c95c05..ee1d2edae 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -367,6 +367,8 @@ model ecosystem_users { model ecosystem_orgs { id String @id @default(uuid()) orgId String + orgName String? + orgDid String? @db.VarChar status String ecosystemId String ecosystemRoleId String From dc72094aae13ebc8a8fa1e1f5300f99b20ccab84 Mon Sep 17 00:00:00 2001 From: pallavicoder Date: Wed, 11 Oct 2023 19:34:01 +0530 Subject: [PATCH 137/162] feat:Decline endorsement request by lead Signed-off-by: pallavicoder --- .../decline-endorsement-transaction-dto.ts | 21 ------- .../decline-endorsement-transaction.dto.ts | 6 ++ .../src/ecosystem/ecosystem.controller.ts | 62 +++++++++---------- .../src/ecosystem/ecosystem.service.ts | 12 ++-- apps/ecosystem/src/ecosystem.controller.ts | 14 ++--- apps/ecosystem/src/ecosystem.repository.ts | 38 ++++++++---- apps/ecosystem/src/ecosystem.service.ts | 20 ++++-- libs/common/src/response-messages/index.ts | 6 +- 8 files changed, 96 insertions(+), 83 deletions(-) delete mode 100644 apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction-dto.ts create mode 100644 apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction.dto.ts diff --git a/apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction-dto.ts b/apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction-dto.ts deleted file mode 100644 index f7658d5e2..000000000 --- a/apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction-dto.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { IsEnum, IsNotEmpty } from 'class-validator'; - -import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; -import { endorsementTransactionStatus } from 'apps/ecosystem/enums/ecosystem.enum'; - -export class DeclienEndorsementTransactionDto { - ecosystemId: string; - orgId: string; - endorsementId: string; - - @ApiProperty({ - enum: [endorsementTransactionStatus.DECLINED] - }) - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'Please provide only DECLINED status' }) - @IsEnum(endorsementTransactionStatus) - status:endorsementTransactionStatus.DECLINED; - -} \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction.dto.ts b/apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction.dto.ts new file mode 100644 index 000000000..558736027 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction.dto.ts @@ -0,0 +1,6 @@ +export class DeclienEndorsementTransactionDto { + ecosystemId: string; + orgId: string; + endorsementId: 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 a8e9cb33a..c1a5ffb12 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -30,7 +30,6 @@ import { OrgRoles } from 'libs/org-roles/enums'; import { GetAllEcosystemMembersDto } from './dtos/get-members.dto'; import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; import { CreateEcosystemDto } from './dtos/create-ecosystem-dto'; -import { DeclienEndorsementTransactionDto } from './dtos/decline-endorsement-transaction-dto'; @UseFilters(CustomExceptionFilter) @@ -419,6 +418,37 @@ export class EcosystemController { return res.status(HttpStatus.OK).json(finalResponse); } + + /** + * + * @param declineEndorsementTransactionRequest + * + * @param res + * @returns endorsement transaction status + */ + @Put('/:ecosystemId/:orgId/transactions/:endorsementId') + @ApiOperation({ + summary: 'Decline Endorsement Request By Lead', + description: 'Decline Endorsement Request By Lead' + }) + // @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + // @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + async declineEndorsementRequestByLead( + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @Param('endorsementId') endorsementId: string, + @Res() res: Response + ): Promise { + await this.ecosystemService.declineEndorsementRequestByLead(ecosystemId, orgId, endorsementId); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.DeclineEndorsementTransaction + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + @Delete('/:ecosystemId/:orgId/invitations/:invitationId') @ApiOperation({ summary: 'Delete ecosystem pending invitations', description: 'Delete ecosystem pending invitations' }) @@ -442,34 +472,4 @@ export class EcosystemController { } - /** - * - * @param declineEndorsementTransactionRequest - * - * @param res - * @returns endorsement transaction status - */ - - - @Put('/:ecosystemId/:orgId/transactions/:endorsementId') - @ApiOperation({ - summary: 'Declien Endorsement Request By Lead', - description: 'Declien Endorsement Request By Lead' - }) - @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - @ApiBearerAuth() - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) - async declineEndorsementRequestByLead(@Body() declineEndorsementTransactionRequest: DeclienEndorsementTransactionDto, @Param('ecosystemId') ecosystemId:string, @Param('orgId') orgId: string, @Param('endorsementId') endorsementId: string, @User() user: user, @Res() res: Response): Promise { - declineEndorsementTransactionRequest.orgId = orgId; - declineEndorsementTransactionRequest.ecosystemId = ecosystemId; - declineEndorsementTransactionRequest.endorsementId = endorsementId; - - const DeclienEndorsementTransaction = await this.ecosystemService.declineEndorsementRequestByLead(declineEndorsementTransactionRequest); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: DeclienEndorsementTransaction.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } } \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index fb273fb4f..39e4c10e0 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -8,7 +8,7 @@ import { GetAllEcosystemInvitationsDto } from './dtos/get-all-sent-invitations.d import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-received-invitations.dto'; import { GetAllEcosystemMembersDto } from './dtos/get-members.dto'; import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; -import { DeclienEndorsementTransactionDto } from './dtos/decline-endorsement-transaction-dto'; + import { RequestSchemaDto, RequestCredDefDto } from './dtos/request-schema.dto'; @Injectable() @@ -161,9 +161,13 @@ export class EcosystemService extends BaseService { return this.sendNats(this.serviceProxy, 'sumbit-endorsement-transaction', payload); } - async declineEndorsementRequestByLead(declineEndorsementTransactionRequest: DeclienEndorsementTransactionDto): Promise<{ response: string }> { - const payload = { declineEndorsementTransactionRequest}; + async declineEndorsementRequestByLead( + ecosystemId: string, + endorsementId: string, + orgId: string + ): Promise<{ response: object }> { + const payload = { ecosystemId, endorsementId, orgId }; return this.sendNats(this.serviceProxy, 'decline-endorsement-transaction', payload); - } + } } diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index d72ca6866..c207ffbb1 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -9,7 +9,6 @@ import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; import { EcosystemMembersPayload } from '../interfaces/ecosystemMembers.interface'; import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; import { RequestCredDeffEndorsement, RequestSchemaEndorsement } from '../interfaces/ecosystem.interfaces'; -import { DeclienEndorsementTransactionDto } from 'apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction-dto'; @Controller() export class EcosystemController { @@ -199,17 +198,18 @@ export class EcosystemController { return this.ecosystemService.submitTransaction(payload.endorsementId, payload.ecosystemId); } + /** * * @param payload * @returns Declien Endorsement Transaction status */ - @MessagePattern({ cmd: 'decline-endorsement-transaction' }) - async declineEndorsementRequestByLead(payload: { - declineEndorsementTransactionRequest: DeclienEndorsementTransactionDto; - }): Promise { - return this.ecosystemService.declineEndorsementRequestByLead(payload.declineEndorsementTransactionRequest); - } + @MessagePattern({ cmd: 'decline-endorsement-transaction' }) + async declineEndorsementRequestByLead(payload: { + ecosystemId:string, endorsementId:string, orgId:string + }): Promise { + return this.ecosystemService.declineEndorsementRequestByLead(payload.ecosystemId, payload.orgId, payload.endorsementId); + } } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 4cb4e4e5a..effd9b169 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -6,6 +6,7 @@ import { EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles, endorsem import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; import { SchemaTransactionResponse } from '../interfaces/ecosystem.interfaces'; import { ResponseMessages } from '@credebl/common/response-messages'; +import { NotFoundException } from '@nestjs/common'; // eslint-disable-next-line camelcase @Injectable() @@ -690,23 +691,36 @@ export class EcosystemRepository { } } - async updateEndorsementRequestStatus( - endorsementId: string, - status:endorsementTransactionStatus - // eslint-disable-next-line camelcase, - ): Promise { + async updateEndorsementRequestStatus(ecosystemId: string, orgId: string, endorsementId: string): Promise { try { - const updatedTransaction = await this.prisma.endorsement_transaction.update({ - where: { id: endorsementId, status:endorsementTransactionStatus.REQUESTED}, - data: { - status - } + + const endorsementTransaction = await this.prisma.endorsement_transaction.findUnique({ + where: { id: endorsementId, status: endorsementTransactionStatus.REQUESTED } }); + + if (!endorsementTransaction) { + throw new NotFoundException(ResponseMessages.ecosystem.error.EndorsementTransactionNotFoundException); + } + const { ecosystemOrgId } = endorsementTransaction; - return updatedTransaction; + const endorsementTransactionEcosystemOrg = await this.prisma.ecosystem_orgs.findUnique({ + where: { id: ecosystemOrgId } + }); + + if (endorsementTransactionEcosystemOrg.orgId === orgId && endorsementTransactionEcosystemOrg.ecosystemId === ecosystemId) { + const updatedEndorsementTransaction = await this.prisma.endorsement_transaction.update({ + where: { id: endorsementId }, + data: { + status: endorsementTransactionStatus.DECLINED + } + }); + return updatedEndorsementTransaction; + } else { + throw new NotFoundException(ResponseMessages.ecosystem.error.OrgOrEcosystemNotFoundExceptionForEndorsementTransaction); + } } catch (error) { - this.logger.error(`Error in updating endorsement transaction: ${error.message}`); + this.logger.error(`Error in updating endorsement transaction status: ${error.message}`); throw error; } } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index e3c721312..18932a7e2 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -18,7 +18,7 @@ import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; // eslint-disable-next-line camelcase import { platform_config } from '@prisma/client'; import { CommonConstants } from '@credebl/common/common.constant'; -import { DeclienEndorsementTransactionDto } from 'apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction-dto'; + @Injectable() export class EcosystemService { @@ -704,13 +704,21 @@ export class EcosystemService { } - async declineEndorsementRequestByLead(declineEndorsementTransactionRequest: DeclienEndorsementTransactionDto): Promise { + /** + * + * @param ecosystemId + * @param endorsementId + * @param orgId + * @returns EndorsementTransactionRequest Status message + */ + + async declineEndorsementRequestByLead(ecosystemId:string, endorsementId:string, orgId:string): Promise { try { - const {endorsementId } = declineEndorsementTransactionRequest; - return this.ecosystemRepository.updateEndorsementRequestStatus(endorsementId, endorsementTransactionStatus.DECLINED); + + return this.ecosystemRepository.updateEndorsementRequestStatus(ecosystemId, orgId, endorsementId); } catch (error) { - this.logger.error(`acceptRejectInvitations: ${error}`); - throw new RpcException(error.response ? error.response : error); + this.logger.error(`error in decline endorsement request: ${error}`); + throw new InternalServerErrorException(error); } } diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 375e75604..31bc3bd58 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -196,7 +196,7 @@ export const ResponseMessages = { invitationReject: 'Ecosystem invitation rejected', invitationAccept: 'Ecosystem invitation accepted successfully', fetchEndorsors: 'Endorser transactions fetched successfully', - DeclienEndorsementTransaction:'decline Endorsement Request successfully', + DeclineEndorsementTransaction:'Decline endorsement request successfully', fetchMembers: 'Ecosystem members fetched successfully' }, error: { @@ -215,7 +215,9 @@ export const ResponseMessages = { invalidOrgId: 'Invalid organization Id', invalidEcosystemId: 'Invalid ecosystem Id', invalidTransaction: 'Transaction does not exist', - invalidAgentUrl: 'Invalid agent url' + invalidAgentUrl: 'Invalid agent url', + EndorsementTransactionNotFoundException:'Endorsement transaction with status requested not found', + OrgOrEcosystemNotFoundExceptionForEndorsementTransaction:'Cannot update endorsement transaction status as OrgId and EcosystemId is not present in ecosystemOrg' } } }; \ No newline at end of file From de4f0152f4cc4f1d62ac042954cc603eaf5ad09d Mon Sep 17 00:00:00 2001 From: pallavicoder Date: Wed, 11 Oct 2023 19:54:15 +0530 Subject: [PATCH 138/162] fix:Resolve sonar lint checks Signed-off-by: pallavicoder --- apps/ecosystem/src/ecosystem.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 1fc5c3981..f85c0d324 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -719,7 +719,7 @@ export class EcosystemService { async declineEndorsementRequestByLead(ecosystemId:string, endorsementId:string, orgId:string): Promise { try { - return this.ecosystemRepository.updateEndorsementRequestStatus(ecosystemId, orgId, endorsementId); + return await this.ecosystemRepository.updateEndorsementRequestStatus(ecosystemId, orgId, endorsementId); } catch (error) { this.logger.error(`error in decline endorsement request: ${error}`); throw new InternalServerErrorException(error); From 01769e8a9ed62ad5c3510c96cec08a14b4b64fe2 Mon Sep 17 00:00:00 2001 From: pallavicoder Date: Wed, 11 Oct 2023 21:34:49 +0530 Subject: [PATCH 139/162] fix:comment removed Signed-off-by: pallavicoder --- apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index e11a07ea5..85ce32303 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -431,8 +431,8 @@ export class EcosystemController { summary: 'Decline Endorsement Request By Lead', description: 'Decline Endorsement Request By Lead' }) - // @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) - // @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) async declineEndorsementRequestByLead( From d5b971de30f2a1cf4d0d5233699debfd7e51cb0c Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Thu, 12 Oct 2023 11:16:30 +0530 Subject: [PATCH 140/162] feat: Implemented store request details after submit transaction Signed-off-by: tipusinghaw --- .../src/ecosystem/ecosystem.controller.ts | 4 +- .../interfaces/ecosystem.interfaces.ts | 52 +++++++- apps/ecosystem/src/ecosystem.repository.ts | 94 +++++++++++--- apps/ecosystem/src/ecosystem.service.ts | 117 +++++++++++++----- libs/common/src/response-messages/index.ts | 5 + 5 files changed, 219 insertions(+), 53 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 020398a4b..73635abf2 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -277,7 +277,7 @@ export class EcosystemController { await this.ecosystemService.credDefEndorsementRequest(requestCredDefPayload, orgId, ecosystemId); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, - message: ResponseMessages.ecosystem.success.schemaRequest + message: ResponseMessages.ecosystem.success.credDefRequest }; return res.status(HttpStatus.CREATED).json(finalResponse); } @@ -309,7 +309,7 @@ export class EcosystemController { await this.ecosystemService.submitTransaction(endorsementId, ecosystemId); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, - message: ResponseMessages.ecosystem.success.sign + message: ResponseMessages.ecosystem.success.submit }; return res.status(HttpStatus.CREATED).json(finalResponse); } diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts index 1ca0aff37..50040f120 100644 --- a/apps/ecosystem/interfaces/ecosystem.interfaces.ts +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -1,3 +1,4 @@ +import { Prisma } from "@prisma/client"; export interface AttributeValue { attributeName: string; schemaDataType: string; @@ -90,9 +91,15 @@ export interface EndorsementTransactionPayload { authorDid: string; requestPayload: string; responsePayload: string; + requestBody: Prisma.JsonValue status: string; ecosystemOrgId: string; - type: string; + createDateTime: Date; + createdBy: number; + lastChangedDateTime: Date; + lastChangedBy: number; + deletedAt?: Date; + type?: string; ecosystemOrgs?: { orgId: string; }; @@ -118,3 +125,46 @@ export interface submitTransactionPayload { credentialDefinition?: CredentialDefinitionPayload; } + +export interface SaveSchema { + name: string; + version: string; + attributes: string; + schemaLedgerId: string; + issuerId: string; + createdBy: string; + lastChangedBy: string; + publisherDid: string; + orgId: string; + ledgerId: number; +} + +export interface saveCredDef { + schemaLedgerId: string; + tag: string; + credentialDefinitionId: string; + revocable: boolean; + createdBy: string; + orgId: number; + schemaId: number; +} + +export interface EndorsementTransactionPayloadDetails { + id: string; + endorserDid: string; + authorDid: string; + requestPayload: string; + responsePayload: string; + type: string; + createDateTime: Date; + createdBy: number; + lastChangedDateTime: Date; + lastChangedBy: number; + deletedAt: Date | null; + status: string; + ecosystemOrgId: string; + requestBody: unknown; + ecosystemOrgs?: { + orgId: string; + }; +} diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index f89ec9c7c..da185ffde 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -1,10 +1,10 @@ -import { Injectable, InternalServerErrorException, Logger} from '@nestjs/common'; +import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase -import { ecosystem, ecosystem_invitations, ecosystem_orgs, ecosystem_roles, endorsement_transaction, org_agents, platform_config } from '@prisma/client'; +import { credential_definition, ecosystem, ecosystem_invitations, ecosystem_orgs, ecosystem_roles, endorsement_transaction, org_agents, platform_config, schema } from '@prisma/client'; import { EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; -import { SchemaTransactionResponse } from '../interfaces/ecosystem.interfaces'; +import { SaveSchema, SchemaTransactionResponse, saveCredDef } from '../interfaces/ecosystem.interfaces'; import { ResponseMessages } from '@credebl/common/response-messages'; // eslint-disable-next-line camelcase @@ -304,7 +304,7 @@ export class EcosystemRepository { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); } - } + } async getEcosystemMembersPagination(queryObject: object, pageNumber: number, pageSize: number): Promise { try { @@ -402,8 +402,8 @@ export class EcosystemRepository { where: { ...queryObject }, - select:{ - id:true, + select: { + id: true, endorserDid: true, authorDid: true, status: true, @@ -439,11 +439,11 @@ export class EcosystemRepository { } } -/** -* Description: Get getAgentEndPoint by orgId -* @param orgId -* @returns Get getAgentEndPoint details -*/ + /** + * Description: Get getAgentEndPoint by orgId + * @param orgId + * @returns Get getAgentEndPoint details + */ // eslint-disable-next-line camelcase async getAgentDetails(orgId: number): Promise { try { @@ -469,7 +469,7 @@ export class EcosystemRepository { * @returns Get getAgentEndPoint details */ // eslint-disable-next-line camelcase - async getEcosystemLeadDetails(ecosystemId:string): Promise { + async getEcosystemLeadDetails(ecosystemId: string): Promise { try { if (!ecosystemId) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidEcosystemId); @@ -533,7 +533,7 @@ export class EcosystemRepository { throw new InternalServerErrorException(error); } } - + // eslint-disable-next-line camelcase async deleteInvitations(invitationId: string): Promise { try { @@ -550,7 +550,7 @@ export class EcosystemRepository { } } - + // eslint-disable-next-line camelcase async getEcosystemOrgDetailsbyId(orgId: string): Promise { try { @@ -568,7 +568,7 @@ export class EcosystemRepository { } } // eslint-disable-next-line camelcase - async getEndorsementTransactionById(endorsementId: string, status:endorsementTransactionStatus): Promise { + async getEndorsementTransactionById(endorsementId: string, status: endorsementTransactionStatus): Promise { try { const ecosystemLeadDetails = await this.prisma.endorsement_transaction.findFirst({ where: { @@ -617,14 +617,14 @@ export class EcosystemRepository { async updateTransactionStatus( endorsementId: string, - status:endorsementTransactionStatus + status: endorsementTransactionStatus // eslint-disable-next-line camelcase, ): Promise { try { const updatedTransaction = await this.prisma.endorsement_transaction.update({ where: { id: endorsementId }, data: { - status + status } }); @@ -636,4 +636,64 @@ export class EcosystemRepository { } } + async saveSchema(schemaResult: SaveSchema): Promise { + try { + const { name, version, attributes, schemaLedgerId, issuerId, createdBy, lastChangedBy, publisherDid, orgId, ledgerId } = schemaResult; + const saveResult = await this.prisma.schema.create({ + data: { + name, + version, + attributes, + schemaLedgerId, + issuerId, + createdBy: Number(createdBy), + lastChangedBy: Number(lastChangedBy), + publisherDid, + orgId: Number(orgId), + ledgerId + } + }); + return saveResult; + } catch (error) { + this.logger.error(`Error in storing schema for submit transaction: ${error.message} `); + throw error; + } + } + + // eslint-disable-next-line camelcase + async saveCredDef(credDefResult: saveCredDef): Promise { + try { + const { schemaLedgerId, tag, credentialDefinitionId, revocable, createdBy, orgId, schemaId } = credDefResult; + const saveResult = await this.prisma.credential_definition.create({ + data: { + schemaLedgerId, + tag, + credentialDefinitionId, + revocable, + createdBy: Number(createdBy), + orgId: Number(orgId), + schemaId + } + }); + return saveResult; + } catch (error) { + this.logger.error(`Error in saving credential-definition for submit transaction: ${error.message} `); + throw error; + } + } + + async getSchemaDetailsById(schemaLedgerId: string): Promise { + try { + const schemaDetails = await this.prisma.schema.findFirst({ + where: { + schemaLedgerId + } + }); + return schemaDetails; + } catch (error) { + this.logger.error(`Error in fetching schema details for submit transaction: ${error.message}`); + throw error; + } + } + } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 4cb2160c8..94817a967 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -1,5 +1,5 @@ // eslint-disable-next-line camelcase -import { ForbiddenException, HttpException, Inject, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; +import { ConflictException, ForbiddenException, HttpException, Inject, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; import { EcosystemRepository } from './ecosystem.repository'; import { ResponseMessages } from '@credebl/common/response-messages'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; @@ -13,7 +13,7 @@ import { Invitation, OrgAgentType } from '@credebl/enum/enum'; import { EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; import { EcosystemMembersPayload } from '../interfaces/ecosystemMembers.interface'; -import { CredDefMessage, CredDefTransactionPayload, EndorsementTransactionPayload, RequestCredDeffEndorsement, RequestSchemaEndorsement, SchemaMessage, SchemaTransactionPayload, SchemaTransactionResponse, SignedTransactionMessage, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; +import { CredDefMessage, CredDefTransactionPayload, EndorsementTransactionPayload, RequestCredDeffEndorsement, RequestSchemaEndorsement, SaveSchema, SchemaMessage, SchemaTransactionPayload, SchemaTransactionResponse, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; // eslint-disable-next-line camelcase import { platform_config } from '@prisma/client'; @@ -99,7 +99,7 @@ export class EcosystemService { } } - + /** * * @param bulkInvitationDto @@ -266,9 +266,9 @@ export class EcosystemService { * @param RequestSchemaEndorsement * @returns */ - async requestSchemaEndorsement(requestSchemaPayload: RequestSchemaEndorsement, orgId: number, ecosystemId:string): Promise { + async requestSchemaEndorsement(requestSchemaPayload: RequestSchemaEndorsement, orgId: number, ecosystemId: string): Promise { try { - + const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(orgId); if (!ecosystemMemberDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.notFound); @@ -320,7 +320,7 @@ export class EcosystemService { } } - async requestCredDeffEndorsement(requestCredDefPayload: RequestCredDeffEndorsement, orgId: number, ecosystemId:string): Promise { + async requestCredDeffEndorsement(requestCredDefPayload: RequestCredDeffEndorsement, orgId: number, ecosystemId: string): Promise { try { const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(orgId); if (!ecosystemMemberDetails) { @@ -335,7 +335,6 @@ export class EcosystemService { // const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); - const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); if (!ecosystemLeadAgentDetails) { @@ -352,7 +351,7 @@ export class EcosystemService { issuerId: ecosystemMemberDetails.orgDid }; // Need to add logic and type for credential-definition - const credDefTransactionRequest:CredDefMessage = await this._requestCredDeffEndorsement(credDefTransactionPayload, url, platformConfig?.sgApiKey); + const credDefTransactionRequest: CredDefMessage = await this._requestCredDeffEndorsement(credDefTransactionPayload, url, platformConfig?.sgApiKey); const schemaTransactionResponse = { endorserDid: ecosystemLeadAgentDetails.orgDid, authorDid: ecosystemMemberDetails.orgDid, @@ -361,6 +360,7 @@ export class EcosystemService { ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id }; + if ('failed' === credDefTransactionRequest.message.credentialDefinitionState.state) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); } @@ -421,7 +421,7 @@ export class EcosystemService { } } - async signTransaction(endorsementId: string, ecosystemId:string): Promise { + async signTransaction(endorsementId: string, ecosystemId: string): Promise { try { const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.REQUESTED); if (!endorsementTransactionPayload) { @@ -457,26 +457,26 @@ export class EcosystemService { } } - /** - * - * @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); - } catch (error) { - this.logger.error(`In getEcosystemMembers: ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); - } + /** + * + * @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); + } catch (error) { + this.logger.error(`In getEcosystemMembers: ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); } - - async deleteEcosystemInvitations (invitationId: string): Promise { - try { + } + + 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); @@ -505,12 +505,16 @@ export class EcosystemService { } } - async submitTransaction(endorsementId: string, ecosystemId:string): Promise { + async submitTransaction(endorsementId: string, ecosystemId: string): Promise { try { const endorsementTransactionPayload: EndorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.SIGNED); if (!endorsementTransactionPayload) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidTransaction); } + if ('submitted' === endorsementTransactionPayload.status) { + throw new ConflictException(ResponseMessages.ecosystem.error.transactionSubmitted); + } + const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(Number(endorsementTransactionPayload.ecosystemOrgs.orgId)); const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); @@ -540,16 +544,63 @@ export class EcosystemService { payload.credentialDefinition = { tag: parsedRequestPayload.operation.tag, issuerId: ecosystemMemberDetails.orgDid, - schemaId: parsedRequestPayload.operation.schemaId + schemaId: endorsementTransactionPayload.requestBody['schemaId'] }; } - // Need to add valid schema Id + const submitTransactionRequest = await this._submitTransaction(payload, url, platformConfig.sgApiKey); - // need to implement the type and state - if (!submitTransactionRequest) { + + if ('failed' === submitTransactionRequest['message'].state) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); } - return this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SUBMITED); + + await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SUBMITED); + if (endorsementTransactionPayload.type === endorsementTransactionType.SCHEMA) { + + const regex = /[^:]+$/; + const match = ecosystemMemberDetails.orgDid.match(regex); + let extractedDidValue; + + if (match) { + // eslint-disable-next-line prefer-destructuring + extractedDidValue = match[0]; + + } + const saveSchemaPayload: SaveSchema = { + name: endorsementTransactionPayload.requestBody['name'], + version: endorsementTransactionPayload.requestBody['version'], + attributes: JSON.stringify(endorsementTransactionPayload.requestBody['attributes']), + schemaLedgerId: submitTransactionRequest['message'].schemaId, + issuerId: ecosystemMemberDetails.orgDid, + createdBy: endorsementTransactionPayload.ecosystemOrgs.orgId, + lastChangedBy: endorsementTransactionPayload.ecosystemOrgs.orgId, + publisherDid: extractedDidValue, + orgId: endorsementTransactionPayload.ecosystemOrgs.orgId, + ledgerId: ecosystemMemberDetails.ledgerId + }; + const saveSchemaDetails = await this.ecosystemRepository.saveSchema(saveSchemaPayload); + if (!saveSchemaDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.saveSchema); + } + return saveSchemaDetails; + } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { + const schemaDetails = await this.ecosystemRepository.getSchemaDetailsById(endorsementTransactionPayload.requestBody['schemaId']); + const saveCredentialDefinition: saveCredDef = { + schemaLedgerId: endorsementTransactionPayload.requestBody['schemaId'], + tag: endorsementTransactionPayload.requestBody['tag'], + credentialDefinitionId: submitTransactionRequest['message'].credentialDefinitionId, + revocable: false, + createdBy: endorsementTransactionPayload.ecosystemOrgs.orgId, + orgId: ecosystemMemberDetails.orgId, + schemaId: schemaDetails.id + }; + const saveCredDefDetails = await this.ecosystemRepository.saveCredDef(saveCredentialDefinition); + if (!saveCredDefDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.saveCredDef); + } + return saveCredDefDetails; + } + } catch (error) { this.logger.error(`In sumit transaction : ${JSON.stringify(error)}`); diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index d0525378b..07935609c 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -191,7 +191,9 @@ export const ResponseMessages = { getInvitation: 'Ecosystem invitations fetched successfully', createInvitation: 'Ecosystem invitations sent successfully', schemaRequest: 'Schema transaction request created successfully', + credDefRequest: 'credential-definition transaction request created successfully', sign: 'Transaction request signed successfully', + submit: 'Transaction request submitted successfully', invitationReject: 'Ecosystem invitation rejected', invitationAccept: 'Ecosystem invitation accepted successfully', fetchMembers: 'Ecosystem members fetched successfully', @@ -210,9 +212,12 @@ export const ResponseMessages = { requestCredDefTransaction: 'Error while submitting transaction', notFound: 'Organization not found', leadNotFound: 'Lead details not found', + saveSchema: 'Error while storing the schema details', + saveCredDef: 'Error while storing the credential-definition details', invalidOrgId: 'Invalid organization Id', invalidEcosystemId: 'Invalid ecosystem Id', invalidTransaction: 'Transaction does not exist', + transactionSubmitted: 'Transaction already submitted', invalidAgentUrl: 'Invalid agent url' } } From c445d099da21c8ceb41bc5da46e298ca85d69190 Mon Sep 17 00:00:00 2001 From: pallavicoder Date: Thu, 12 Oct 2023 12:48:35 +0530 Subject: [PATCH 141/162] refactor:included ecosystem role information Signed-off-by: pallavicoder --- apps/ecosystem/src/ecosystem.repository.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 796d4a234..af20d143e 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -382,7 +382,8 @@ export class EcosystemRepository { ...queryObject }, include: { - ecosystem: true + ecosystem: true, + ecosystemRole:true }, take: pageSize, skip: (pageNumber - 1) * pageSize, From 85becb670c4ebce3b9688b0b1640d52521b06470 Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Thu, 12 Oct 2023 15:59:05 +0530 Subject: [PATCH 142/162] refactored ecosystem_config and ecosystem_orgs model and its references (#148) Signed-off-by: Nishad --- apps/ecosystem/enums/ecosystem.enum.ts | 6 +++++ apps/ecosystem/src/ecosystem.repository.ts | 8 +++--- apps/ecosystem/src/ecosystem.service.ts | 17 ++++++++++--- apps/user/src/user.service.ts | 17 +++++++++++-- .../prisma/data/credebl-master-table.json | 23 +++++++++++++---- .../migration.sql | 25 +++++++++++++++++++ libs/prisma-service/prisma/schema.prisma | 14 +++++------ libs/prisma-service/prisma/seed.ts | 2 +- 8 files changed, 91 insertions(+), 21 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20231012093420_ecosystem_config_key_value/migration.sql diff --git a/apps/ecosystem/enums/ecosystem.enum.ts b/apps/ecosystem/enums/ecosystem.enum.ts index dbb5c3dc3..ceae1e332 100644 --- a/apps/ecosystem/enums/ecosystem.enum.ts +++ b/apps/ecosystem/enums/ecosystem.enum.ts @@ -27,3 +27,9 @@ export enum endorsementTransactionType { SIGN = 'sign', SUBMIT = 'submit' } + +export enum DeploymentModeType { + PROVIDER_HOSTED = 'ProviderHosted', + ON_PREMISE = 'OnPremise' +} + diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index af20d143e..169c81865 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -2,7 +2,7 @@ import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase import { credential_definition, ecosystem, ecosystem_invitations, ecosystem_orgs, ecosystem_roles, endorsement_transaction, org_agents, platform_config, schema } from '@prisma/client'; -import { EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; +import { DeploymentModeType, EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; import { SaveSchema, SchemaTransactionResponse, saveCredDef } from '../interfaces/ecosystem.interfaces'; import { ResponseMessages } from '@credebl/common/response-messages'; @@ -58,7 +58,8 @@ export class EcosystemRepository { ecosystemId: createdEcosystem.id, ecosystemRoleId: ecosystemRoleDetails.id, orgName, - orgDid + orgDid, + deploymentMode: DeploymentModeType.PROVIDER_HOSTED } }); } @@ -297,7 +298,8 @@ export class EcosystemRepository { status, ecosystemRoleId, orgName, - orgDid + orgDid, + deploymentMode: DeploymentModeType.PROVIDER_HOSTED } }); } catch (error) { diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 2608ed291..e04720103 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -702,7 +702,7 @@ export class EcosystemService { ): Promise { const isEcosystemEnabled = await this.checkEcosystemEnableFlag(); - + if (!isEcosystemEnabled) { throw new ForbiddenException(ResponseMessages.ecosystem.error.ecosystemNotEnabled); } @@ -718,8 +718,19 @@ export class EcosystemService { */ async checkEcosystemEnableFlag( ): Promise { - const platformConfigData = await this.prisma.ecosystem_config.findMany(); - return platformConfigData[0].enableEcosystem; + const ecosystemDetails = await this.prisma.ecosystem_config.findFirst( + { + where:{ + key: 'enableEcosystem' + } + } + ); + + if ('true' === ecosystemDetails.value) { + return true; + } + + return false; } async getEndorsementTransactions(payload: GetEndorsementsPayload): Promise { diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 5102b5bf1..3c82595c7 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -339,9 +339,22 @@ export class UserService { async getProfile(payload: { id }): Promise { try { const userData = await this.userRepository.getUserById(payload.id); - const platformConfigData = await this.prisma.ecosystem_config.findMany(); - userData['enableEcosystem'] = platformConfigData[0].enableEcosystem; + const ecosystemDetails = await this.prisma.ecosystem_config.findFirst( + { + where:{ + key: 'enableEcosystem' + } + } + ); + + if ('true' === ecosystemDetails.value) { + userData['enableEcosystem'] = true; + return userData; + } + + userData['enableEcosystem'] = false; return userData; + } catch (error) { this.logger.error(`get user: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); diff --git a/libs/prisma-service/prisma/data/credebl-master-table.json b/libs/prisma-service/prisma/data/credebl-master-table.json index 9d6bdb4d0..23bbb73b2 100644 --- a/libs/prisma-service/prisma/data/credebl-master-table.json +++ b/libs/prisma-service/prisma/data/credebl-master-table.json @@ -146,9 +146,22 @@ "ecosystemOrgId": "ca6ee687-a3a9-42ce-9e49-02bf62f5c93a" } ], - "ecosystemConfigData": { - "url": "https://example3.com", - "enableEcosystem": false, - "autoEndorsement": false - } + "ecosystemConfigData": [ + { + "key": "url", + "value": "https://dummyurl.tk" + }, + { + "key": "enableEcosystem", + "value": "false" + }, + { + "key": "autoEndorsement", + "value": "false" + }, + { + "key": "participateInEcosystem", + "value": "false" + } + ] } \ No newline at end of file diff --git a/libs/prisma-service/prisma/migrations/20231012093420_ecosystem_config_key_value/migration.sql b/libs/prisma-service/prisma/migrations/20231012093420_ecosystem_config_key_value/migration.sql new file mode 100644 index 000000000..77cad7115 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20231012093420_ecosystem_config_key_value/migration.sql @@ -0,0 +1,25 @@ +/* + Warnings: + + - You are about to drop the column `autoEndorsement` on the `ecosystem_config` table. All the data in the column will be lost. + - You are about to drop the column `enableEcosystem` on the `ecosystem_config` table. All the data in the column will be lost. + - You are about to drop the column `url` on the `ecosystem_config` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "ecosystem_config" DROP COLUMN "autoEndorsement", +DROP COLUMN "enableEcosystem", +DROP COLUMN "url", +ADD COLUMN "key" TEXT, +ADD COLUMN "value" TEXT, +ALTER COLUMN "createdBy" SET DEFAULT '1', +ALTER COLUMN "createdBy" SET DATA TYPE TEXT, +ALTER COLUMN "lastChangedBy" SET DEFAULT '1', +ALTER COLUMN "lastChangedBy" SET DATA TYPE TEXT; + +-- AlterTable +ALTER TABLE "ecosystem_orgs" ADD COLUMN "deploymentMode" TEXT, +ALTER COLUMN "createdBy" SET DEFAULT '1', +ALTER COLUMN "createdBy" SET DATA TYPE TEXT, +ALTER COLUMN "lastChangedBy" SET DEFAULT '1', +ALTER COLUMN "lastChangedBy" SET DATA TYPE TEXT; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index ee1d2edae..1104e0fc9 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -370,12 +370,13 @@ model ecosystem_orgs { orgName String? orgDid String? @db.VarChar status String + deploymentMode String? ecosystemId String ecosystemRoleId String createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) + createdBy String @default("1") lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) + lastChangedBy String @default("1") deletedAt DateTime? @db.Timestamp(6) ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) ecosystemRole ecosystem_roles @relation(fields: [ecosystemRoleId], references: [id]) @@ -402,12 +403,11 @@ model endorsement_transaction { model ecosystem_config { id String @id @default(uuid()) - url String - enableEcosystem Boolean @default(false) - autoEndorsement Boolean @default(false) + key String? + value String? createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy Int @default(1) + createdBy String @default("1") lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy Int @default(1) + lastChangedBy String @default("1") deletedAt DateTime? @db.Timestamp(6) } diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index ccaa7d8c9..c7a1f4b23 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -129,7 +129,7 @@ const createEcosystemRoles = async (): Promise => { const createEcosystemConfig = async (): Promise => { try { const { ecosystemConfigData } = JSON.parse(configData); - const configDetails = await prisma.ecosystem_config.create({ + const configDetails = await prisma.ecosystem_config.createMany({ data: ecosystemConfigData }); From 7e3bcb2463ba96a68d393dff5b8be025d3f95b96 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Thu, 12 Oct 2023 20:49:53 +0530 Subject: [PATCH 143/162] refactor: removed org validation for reject Signed-off-by: tipusinghaw --- apps/ecosystem/src/ecosystem.repository.ts | 4 ++-- apps/ecosystem/src/ecosystem.service.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 169c81865..ac72a94fe 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -768,7 +768,7 @@ export class EcosystemRepository { } } - async updateEndorsementRequestStatus(ecosystemId: string, orgId: string, endorsementId: string): Promise { + async updateEndorsementRequestStatus(ecosystemId: string, endorsementId: string): Promise { try { const endorsementTransaction = await this.prisma.endorsement_transaction.findUnique({ @@ -784,7 +784,7 @@ export class EcosystemRepository { where: { id: ecosystemOrgId } }); - if (endorsementTransactionEcosystemOrg.orgId === orgId && endorsementTransactionEcosystemOrg.ecosystemId === ecosystemId) { + if (endorsementTransactionEcosystemOrg.ecosystemId === ecosystemId) { const updatedEndorsementTransaction = await this.prisma.endorsement_transaction.update({ where: { id: endorsementId }, data: { diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index e04720103..3d6b2a26e 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -778,10 +778,10 @@ export class EcosystemService { * @returns EndorsementTransactionRequest Status message */ - async declineEndorsementRequestByLead(ecosystemId:string, endorsementId:string, orgId:string): Promise { + async declineEndorsementRequestByLead(ecosystemId:string, endorsementId:string): Promise { try { - return await this.ecosystemRepository.updateEndorsementRequestStatus(ecosystemId, orgId, endorsementId); + return await this.ecosystemRepository.updateEndorsementRequestStatus(ecosystemId, endorsementId); } catch (error) { this.logger.error(`error in decline endorsement request: ${error}`); throw new InternalServerErrorException(error); From d5ca7e85106379d3a4f5d146cbaf791c6734ed4c Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Fri, 13 Oct 2023 11:47:47 +0530 Subject: [PATCH 144/162] refactor: added request body parameter on endorsement list Signed-off-by: tipusinghaw --- apps/ecosystem/src/ecosystem.repository.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index ac72a94fe..19ba3355a 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -483,7 +483,8 @@ export class EcosystemRepository { ecosystemOrgs: true, requestPayload: true, responsePayload: true, - createDateTime: true + createDateTime: true, + requestBody: true }, take: pageSize, skip: (pageNumber - 1) * pageSize, @@ -731,7 +732,7 @@ export class EcosystemRepository { throw error; } } - + // eslint-disable-next-line camelcase async saveCredDef(credDefResult: saveCredDef): Promise { try { From d4d124d8118a2a668cb0d6b7b1c911dec06519e4 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Fri, 13 Oct 2023 12:31:51 +0530 Subject: [PATCH 145/162] fix: Added validation for schema and cred-def and updated payload for create request cred-def (#149) * fix: Added validation for the schema and cred-def request creation Signed-off-by: KulkarniShashank * Remove commented code Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank --- .../interfaces/ecosystem.interfaces.ts | 3 + apps/ecosystem/src/ecosystem.repository.ts | 114 ++++++++++-------- apps/ecosystem/src/ecosystem.service.ts | 61 ++++++---- libs/common/src/response-messages/index.ts | 2 + 4 files changed, 111 insertions(+), 69 deletions(-) diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts index 50040f120..0fc061f0a 100644 --- a/apps/ecosystem/interfaces/ecosystem.interfaces.ts +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -66,6 +66,7 @@ export interface CredDefMessage { schemaId: string; schema: Record; credentialDefinitionRequest: string; + credentialDefinition: Record; }; registrationMetadata: Record; schemaMetadata: Record; @@ -116,6 +117,8 @@ interface CredentialDefinitionPayload { tag: string; issuerId: string; schemaId: string; + type: string; + value: Record; } export interface submitTransactionPayload { diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 19ba3355a..5269530d1 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -109,19 +109,19 @@ export class EcosystemRepository { const ecosystemDetails = await this.prisma.ecosystem.findMany({ where: { ecosystemOrgs: { - some: { - orgId - } + some: { + orgId + } } }, - include:{ + include: { ecosystemOrgs: { - where: { - orgId - }, - include:{ - ecosystemRole: true - } + where: { + orgId + }, + include: { + ecosystemRole: true + } } } }); @@ -166,11 +166,11 @@ export class EcosystemRepository { } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); - } + } } - async getEcosystemMembersCount (ecosystemId: string): Promise { + async getEcosystemMembersCount(ecosystemId: string): Promise { try { const membersCount = await this.prisma.ecosystem_orgs.count( { @@ -179,24 +179,24 @@ export class EcosystemRepository { } } ); - return membersCount; + return membersCount; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); } } - async getEcosystemEndorsementsCount (ecosystemId: string): Promise { + async getEcosystemEndorsementsCount(ecosystemId: string): Promise { try { const endorsementsCount = await this.prisma.endorsement_transaction.count({ where: { ecosystemOrgs: { ecosystemId - + } } }); - return endorsementsCount; + return endorsementsCount; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); @@ -385,7 +385,7 @@ export class EcosystemRepository { }, include: { ecosystem: true, - ecosystemRole:true + ecosystemRole: true }, take: pageSize, skip: (pageNumber - 1) * pageSize, @@ -665,6 +665,26 @@ export class EcosystemRepository { } } + // eslint-disable-next-line camelcase + async findRecordsByNameAndVersion(name: string, version: string): Promise { + try { + return this.prisma.$queryRaw`SELECT * FROM endorsement_transaction WHERE "requestBody"->>'name' = ${name} AND "requestBody"->>'version' = ${version}`; + } catch (error) { + this.logger.error(`Error in getting ecosystem schema: ${error.message} `); + throw error; + } + } + + // eslint-disable-next-line camelcase + async findRecordsByCredDefTag(tag: string): Promise { + try { + return this.prisma.$queryRaw`SELECT * FROM endorsement_transaction WHERE "requestBody"->>'tag' = ${tag}`; + } catch (error) { + this.logger.error(`Error in getting ecosystem credential-definition: ${error.message} `); + throw error; + } + } + async updateTransactionDetails( endorsementId: string, schemaTransactionRequest: string @@ -732,7 +752,7 @@ export class EcosystemRepository { throw error; } } - + // eslint-disable-next-line camelcase async saveCredDef(credDefResult: saveCredDef): Promise { try { @@ -771,35 +791,35 @@ export class EcosystemRepository { async updateEndorsementRequestStatus(ecosystemId: string, endorsementId: string): Promise { try { - - const endorsementTransaction = await this.prisma.endorsement_transaction.findUnique({ - where: { id: endorsementId, status: endorsementTransactionStatus.REQUESTED } - }); - - if (!endorsementTransaction) { - throw new NotFoundException(ResponseMessages.ecosystem.error.EndorsementTransactionNotFoundException); - } - const { ecosystemOrgId } = endorsementTransaction; - - const endorsementTransactionEcosystemOrg = await this.prisma.ecosystem_orgs.findUnique({ - where: { id: ecosystemOrgId } - }); - - if (endorsementTransactionEcosystemOrg.ecosystemId === ecosystemId) { - const updatedEndorsementTransaction = await this.prisma.endorsement_transaction.update({ - where: { id: endorsementId }, - data: { - status: endorsementTransactionStatus.DECLINED - } - }); - - return updatedEndorsementTransaction; - } else { - throw new NotFoundException(ResponseMessages.ecosystem.error.OrgOrEcosystemNotFoundExceptionForEndorsementTransaction); - } + + const endorsementTransaction = await this.prisma.endorsement_transaction.findUnique({ + where: { id: endorsementId, status: endorsementTransactionStatus.REQUESTED } + }); + + if (!endorsementTransaction) { + throw new NotFoundException(ResponseMessages.ecosystem.error.EndorsementTransactionNotFoundException); + } + const { ecosystemOrgId } = endorsementTransaction; + + const endorsementTransactionEcosystemOrg = await this.prisma.ecosystem_orgs.findUnique({ + where: { id: ecosystemOrgId } + }); + + if (endorsementTransactionEcosystemOrg.ecosystemId === ecosystemId) { + const updatedEndorsementTransaction = await this.prisma.endorsement_transaction.update({ + where: { id: endorsementId }, + data: { + status: endorsementTransactionStatus.DECLINED + } + }); + + return updatedEndorsementTransaction; + } else { + throw new NotFoundException(ResponseMessages.ecosystem.error.OrgOrEcosystemNotFoundExceptionForEndorsementTransaction); + } } catch (error) { - this.logger.error(`Error in updating endorsement transaction status: ${error.message}`); - throw error; - } + this.logger.error(`Error in updating endorsement transaction status: ${error.message}`); + throw error; } + } } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 3d6b2a26e..2a31a045f 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -69,8 +69,8 @@ export class EcosystemService { // eslint-disable-next-line camelcase async getAllEcosystem(payload: { orgId: string }): Promise { - const getAllEcosystemDetails = await this.ecosystemRepository.getAllEcosystemDetails(payload.orgId); - + const getAllEcosystemDetails = await this.ecosystemRepository.getAllEcosystemDetails(payload.orgId); + if (!getAllEcosystemDetails) { throw new NotFoundException(ResponseMessages.ecosystem.error.update); } @@ -82,15 +82,15 @@ export class EcosystemService { * * @returns ecosystem dashboard details */ - async getEcosystemDashboardDetails(ecosystemId: string): Promise { - try { - return await this.ecosystemRepository.getEcosystemDashboardDetails(ecosystemId); - } catch (error) { - this.logger.error(`In ecosystem dashboard details : ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); - } + async getEcosystemDashboardDetails(ecosystemId: string): Promise { + try { + return await this.ecosystemRepository.getEcosystemDashboardDetails(ecosystemId); + } catch (error) { + this.logger.error(`In ecosystem dashboard details : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); } - + } + /** * Description: get an ecosystem invitation * @returns Get sent ecosystem invitation details @@ -157,7 +157,7 @@ export class EcosystemService { */ async acceptRejectEcosystemInvitations(acceptRejectInvitation: AcceptRejectEcosystemInvitationDto): Promise { try { - + const { orgId, status, invitationId, orgName, orgDid } = acceptRejectInvitation; const invitation = await this.ecosystemRepository.getEcosystemInvitationById(invitationId); @@ -286,6 +286,11 @@ export class EcosystemService { */ async requestSchemaEndorsement(requestSchemaPayload: RequestSchemaEndorsement, orgId: number, ecosystemId: string): Promise { try { + const schemaRequestExist = await this.ecosystemRepository.findRecordsByNameAndVersion(requestSchemaPayload?.name, requestSchemaPayload?.version); + + if (0 !== schemaRequestExist.length) { + throw new ConflictException(ResponseMessages.ecosystem.error.schemaAlreadyExist); + } const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(orgId); if (!ecosystemMemberDetails) { @@ -340,6 +345,13 @@ export class EcosystemService { async requestCredDeffEndorsement(requestCredDefPayload: RequestCredDeffEndorsement, orgId: number, ecosystemId: string): Promise { try { + + const credDefRequestExist = await this.ecosystemRepository.findRecordsByCredDefTag(requestCredDefPayload?.tag); + + if (0 !== credDefRequestExist.length) { + throw new ConflictException(ResponseMessages.ecosystem.error.credDefAlreadyExist); + } + const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(orgId); if (!ecosystemMemberDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.notFound); @@ -383,7 +395,8 @@ export class EcosystemService { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); } - return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, requestCredDefPayload, endorsementTransactionType.CREDENTIAL_DEFINITION); + const requestBody = credDefTransactionRequest.message.credentialDefinitionState.credentialDefinition; + return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, requestBody, endorsementTransactionType.CREDENTIAL_DEFINITION); } catch (error) { this.logger.error(`In request cred-def endorsement : ${JSON.stringify(error)}`); @@ -562,7 +575,9 @@ export class EcosystemService { payload.credentialDefinition = { tag: parsedRequestPayload.operation.tag, issuerId: ecosystemMemberDetails.orgDid, - schemaId: endorsementTransactionPayload.requestBody['schemaId'] + schemaId: endorsementTransactionPayload.requestBody['schemaId'], + type: endorsementTransactionPayload.requestBody['type'], + value: endorsementTransactionPayload.requestBody['value'] }; } @@ -602,6 +617,7 @@ export class EcosystemService { } return saveSchemaDetails; } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { + const schemaDetails = await this.ecosystemRepository.getSchemaDetailsById(endorsementTransactionPayload.requestBody['schemaId']); const saveCredentialDefinition: saveCredDef = { schemaLedgerId: endorsementTransactionPayload.requestBody['schemaId'], @@ -612,6 +628,7 @@ export class EcosystemService { orgId: ecosystemMemberDetails.orgId, schemaId: schemaDetails.id }; + const saveCredDefDetails = await this.ecosystemRepository.saveCredDef(saveCredentialDefinition); if (!saveCredDefDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.saveCredDef); @@ -752,12 +769,12 @@ export class EcosystemService { ] }; - const ecosystemOrgData = await this.ecosystemRepository.fetchEcosystemOrg(queryEcoOrgs); + const ecosystemOrgData = await this.ecosystemRepository.fetchEcosystemOrg(queryEcoOrgs); if (ecosystemOrgData['ecosystemRole']['name'] !== EcosystemRoles.ECOSYSTEM_LEAD) { query.ecosystemOrgs['orgId'] = orgId; } - + if (type) { query['type'] = type; } @@ -770,13 +787,13 @@ 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 { diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 18564fbaa..001d51bdf 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -214,6 +214,8 @@ export const ResponseMessages = { requestCredDefTransaction: 'Error while submitting transaction', notFound: 'Organization not found', leadNotFound: 'Lead details not found', + schemaAlreadyExist: 'Schema name and schema version already exist', + credDefAlreadyExist: 'Credential definition already exist', saveSchema: 'Error while storing the schema details', saveCredDef: 'Error while storing the credential-definition details', invalidOrgId: 'Invalid organization Id', From 290a84c94f9eb3accfc4128699f6f7b34aba7898 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Fri, 13 Oct 2023 16:00:14 +0530 Subject: [PATCH 146/162] fix: removed unused orgId Signed-off-by: tipusinghaw --- apps/ecosystem/src/ecosystem.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index c207ffbb1..524d499c0 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -206,9 +206,9 @@ export class EcosystemController { */ @MessagePattern({ cmd: 'decline-endorsement-transaction' }) async declineEndorsementRequestByLead(payload: { - ecosystemId:string, endorsementId:string, orgId:string + ecosystemId:string, endorsementId:string }): Promise { - return this.ecosystemService.declineEndorsementRequestByLead(payload.ecosystemId, payload.orgId, payload.endorsementId); + return this.ecosystemService.declineEndorsementRequestByLead(payload.ecosystemId, payload.endorsementId); } From 7199dc76461b7bc023e8d22e4b622c158ff602d2 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Fri, 13 Oct 2023 16:03:23 +0530 Subject: [PATCH 147/162] feat: refactor api for passkey module Signed-off-by: bhavanakarwade --- .../api-gateway/src/authz/authz.controller.ts | 36 +- apps/api-gateway/src/dtos/fido-user.dto.ts | 6 +- apps/api-gateway/src/fido/fido.controller.ts | 182 ++++---- apps/api-gateway/src/fido/fido.service.ts | 22 +- .../repositories/user-device.repository.ts | 437 +++++++++--------- apps/user/src/fido/dtos/fido-user.dto.ts | 16 +- apps/user/src/fido/fido.controller.ts | 4 +- apps/user/src/fido/fido.service.ts | 51 +- apps/user/src/user.service.ts | 10 +- 9 files changed, 384 insertions(+), 380 deletions(-) diff --git a/apps/api-gateway/src/authz/authz.controller.ts b/apps/api-gateway/src/authz/authz.controller.ts index e1b9c3585..c2100717b 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -1,4 +1,5 @@ import { + BadRequestException, Body, Controller, Get, @@ -82,16 +83,35 @@ export class AuthzController { @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 decryptedPassword = this.commonService.decryptPassword(userInfo.password); + let finalResponse; + let userDetails; - userInfo.password = decryptedPassword; - const userDetails = await this.authzService.addUserDetails(userInfo); - const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.user.success.create, - data: userDetails.response - }; + if (false === userInfo.isPasskey) { + + const decryptedPassword = this.commonService.decryptPassword(userInfo.password); + if (8 <= decryptedPassword.length && 50 >= decryptedPassword.length) { + this.commonService.passwordValidation(decryptedPassword); + userInfo.password = decryptedPassword; + userDetails = await this.authzService.addUserDetails(userInfo); + finalResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userDetails.response + }; + } else { + throw new BadRequestException('Password name must be between 8 to 50 Characters'); + } + } else { + + userDetails = await this.authzService.addUserDetails(userInfo); + finalResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userDetails.response + }; + } return res.status(HttpStatus.CREATED).json(finalResponse); + } /** diff --git a/apps/api-gateway/src/dtos/fido-user.dto.ts b/apps/api-gateway/src/dtos/fido-user.dto.ts index a0864cc04..54635b27f 100644 --- a/apps/api-gateway/src/dtos/fido-user.dto.ts +++ b/apps/api-gateway/src/dtos/fido-user.dto.ts @@ -1,11 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { IsArray, IsBoolean, IsOptional, IsString, ValidateNested } from 'class-validator'; export class GenerateRegistrationDto { - @ApiProperty({ example: 'abc@vomoto.com' }) - @IsNotEmpty({ message: 'Email is required.' }) - @IsEmail() - userName: string; @IsOptional() @ApiProperty({ example: 'false' }) diff --git a/apps/api-gateway/src/fido/fido.controller.ts b/apps/api-gateway/src/fido/fido.controller.ts index 88b4d0fdd..7768e98e9 100644 --- a/apps/api-gateway/src/fido/fido.controller.ts +++ b/apps/api-gateway/src/fido/fido.controller.ts @@ -1,5 +1,5 @@ -import { Body, Controller, Delete, Get, Logger, Param, Post, Put, Query, Request, Res, UseFilters } from '@nestjs/common'; -import { ApiBadRequestResponse, ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; +import { Body, Controller, Delete, Get, HttpStatus, Logger, Param, Post, Put, Query, Request, Res, UseFilters } from '@nestjs/common'; +import { ApiBadRequestResponse, ApiBearerAuth, ApiExcludeEndpoint, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { BadRequestErrorDto } from '../dtos/bad-request-error.dto'; import { GenerateAuthenticationDto, GenerateRegistrationDto, UpdateFidoUserDetailsDto, VerifyRegistrationDto, VerifyAuthenticationDto } from '../dtos/fido-user.dto'; @@ -8,7 +8,6 @@ import { InternalServerErrorDto } from '../dtos/internal-server-error-res.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { FidoService } from './fido.service'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { HttpStatus } from '@nestjs/common'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { Response } from 'express'; import { Roles } from '../authz/decorators/roles.decorator'; @@ -16,7 +15,7 @@ import { OrgRoles } from 'libs/org-roles/enums'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; @UseFilters(CustomExceptionFilter) -@Controller('fido') +@Controller('auth') @ApiTags('fido') @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) @@ -24,48 +23,80 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler export class FidoController { private logger = new Logger('FidoController'); constructor(private readonly fidoService: FidoService) { } + /** * - * @param GenerateRegistrationDto + * @param userName * @param res - * @returns Generate registration response + * @returns User get success */ - @Post('/generate-registration-options') + @Get('/passkey/:email') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) + @ApiBearerAuth() @ApiResponse({ status: 500, description: 'Internal server error', type: InternalServerErrorDto }) - @ApiOperation({ summary: 'Generate registration option' }) + + @ApiOperation({ summary: 'Fetch fido user details' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async generateRegistrationOption(@Body() body: GenerateRegistrationDto, @Res() res: Response): Promise { + @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) + @ApiBadRequestResponse({ status: 400, description: 'Bad Request', type: BadRequestErrorDto }) + async fetchFidoUserDetails(@Request() req, @Param('email') email: string, @Res() res: Response): Promise { try { - const { userName, deviceFlag } = body; - const registrationOption = await this.fidoService.generateRegistrationOption(userName, deviceFlag); - + const fidoUserDetails = await this.fidoService.fetchFidoUserDetails(req.params.email); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.fido.success.RegistrationOption, - data: registrationOption.response + message: ResponseMessages.user.success.fetchUsers, + data: fidoUserDetails.response }; return res.status(HttpStatus.OK).json(finalResponse); + } catch (error) { this.logger.error(`Error::${error}`); throw error; } } - + /** + * + * @param GenerateRegistrationDto + * @param res + * @returns Generate registration response + */ + @Post('/passkey/generate-registration/:email') + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Generate registration option' }) + @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + async generateRegistrationOption(@Body() body: GenerateRegistrationDto, @Param('email') email: string, @Res() res: Response): Promise { + try { + const { deviceFlag } = body; + const registrationOption = await this.fidoService.generateRegistrationOption(deviceFlag, email); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.fido.success.RegistrationOption, + data: registrationOption.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } catch (error) { + this.logger.error(`Error::${error}`); + } + } + + + /** * * @param VerifyRegistrationDto * @param res * @returns User create success */ - @Post('/verify-registration/:userName') - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) + @Post('/passkey/verify-registration/:email') + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @ApiOperation({ summary: 'Verify registration' }) - async verifyRegistration(@Request() req, @Body() verifyRegistrationDto: VerifyRegistrationDto, @Param('userName') userName: string, @Res() res: Response): Promise { - const verifyRegistration = await this.fidoService.verifyRegistration(verifyRegistrationDto, req.params.userName); + async verifyRegistration(@Request() req, @Body() verifyRegistrationDto: VerifyRegistrationDto, @Param('email') email: string, @Res() res: Response): Promise { + const verifyRegistration = await this.fidoService.verifyRegistration(verifyRegistrationDto, req.params.email); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.fido.success.verifyRegistration, @@ -74,32 +105,13 @@ export class FidoController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * - * @param updateFidoUserDetailsDto - * @param res - * @returns User update success - */ - @Put('/user-update') - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ summary: 'Update fido user details' }) - async updateFidoUser(@Request() req, @Body() updateFidoUserDetailsDto: UpdateFidoUserDetailsDto, @Res() res: Response): Promise { - const verifyRegistration = await this.fidoService.updateFidoUser(updateFidoUserDetailsDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.fido.success.updateUserDetails, - data: verifyRegistration.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - /** * * @param GenerateAuthenticationDto * @param res * @returns Generate authentication object */ - @Post('/generate-authentication-options') + @Post('/passkey/authentication-options') @ApiOperation({ summary: 'Generate authentication option' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) async generateAuthenticationOption(@Body() body: GenerateAuthenticationDto, @Request() req, @Res() res: Response): Promise { @@ -119,11 +131,10 @@ export class FidoController { * @param res * @returns Verify authentication object */ - @Post('/verify-authentication/:userName') + @Post('/passkey/verify-authentication/:email') @ApiOperation({ summary: 'Verify authentication' }) - async verifyAuthentication(@Request() req, @Body() verifyAuthenticationDto: VerifyAuthenticationDto, @Param('userName') userName: string, @Res() res: Response): Promise { - const verifyAuthentication = await this.fidoService.verifyAuthentication(verifyAuthenticationDto, req.params.userName); - + async verifyAuthentication(@Request() req, @Body() verifyAuthenticationDto: VerifyAuthenticationDto, @Param('email') email: string, @Res() res: Response): Promise { + const verifyAuthentication = await this.fidoService.verifyAuthentication(verifyAuthenticationDto, req.params.email); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.fido.success.login, @@ -132,13 +143,28 @@ export class FidoController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * - * @param userName - * @param res - * @returns User get success - */ - @Get('/user-details/:userName') +/** + * + * @param updateFidoUserDetailsDto + * @param res + * @returns User update success + */ + @Put('/passkey/user-details/:credentialId') + @ApiExcludeEndpoint() + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Update fido user details' }) + async updateFidoUser(@Request() req, @Body() updateFidoUserDetailsDto: UpdateFidoUserDetailsDto, @Param('credentialId') credentialId: string, @Res() res: Response): Promise { + const verifyRegistration = await this.fidoService.updateFidoUser(updateFidoUserDetailsDto, decodeURIComponent(credentialId)); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.fido.success.updateUserDetails, + data: verifyRegistration.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + + @Put('/passkey/:credentialId') @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) @ApiBearerAuth() @ApiResponse({ @@ -146,29 +172,30 @@ export class FidoController { description: 'Internal server error', type: InternalServerErrorDto }) - - @ApiOperation({ summary: 'Fetch fido user details' }) + // @ApiQuery( + // { name: 'credentialId', required: true } + // ) + @ApiQuery( + { name: 'deviceName', required: true } + ) + @ApiOperation({ summary: 'Update fido user device name' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiBadRequestResponse({ status: 400, description: 'Bad Request', type: BadRequestErrorDto }) - async fetchFidoUserDetails(@Request() req, @Param('userName') userName: string, @Res() res: Response): Promise { + async updateFidoUserDeviceName(@Param('credentialId') credentialId: string, @Query('deviceName') deviceName: string, @Res() res: Response): Promise { try { - const fidoUserDetails = await this.fidoService.fetchFidoUserDetails(req.params.userName); + const updateDeviceName = await this.fidoService.updateFidoUserDeviceName(credentialId, deviceName); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchUsers, - data: fidoUserDetails.response + message: ResponseMessages.fido.success.updateDeviceName, + data: updateDeviceName.response }; return res.status(HttpStatus.OK).json(finalResponse); - } catch (error) { this.logger.error(`Error::${error}`); throw error; } } - @Delete('/device') + @Delete('/passkey/:credentialId') @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) @ApiBearerAuth() @ApiResponse({ @@ -181,7 +208,7 @@ export class FidoController { ) @ApiOperation({ summary: 'Delete fido user device' }) @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async deleteFidoUserDevice(@Query('credentialId') credentialId: string, @Res() res: Response): Promise { + async deleteFidoUserDevice(@Param('credentialId') credentialId: string, @Res() res: Response): Promise { try { const deleteFidoUser = await this.fidoService.deleteFidoUserDevice(credentialId); const finalResponse: IResponseType = { @@ -197,35 +224,4 @@ export class FidoController { } } - @Put('/device-name') - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) - @ApiBearerAuth() - @ApiResponse({ - status: 500, - description: 'Internal server error', - type: InternalServerErrorDto - }) - @ApiQuery( - { name: 'credentialId', required: true } - ) - @ApiQuery( - { name: 'deviceName', required: true } - ) - @ApiOperation({ summary: 'Update fido user device name' }) - @ApiResponse({ status: 201, description: 'Success', type: ApiResponseDto }) - async updateFidoUserDeviceName(@Query('credentialId') credentialId: string, @Query('deviceName') deviceName: string, @Res() res: Response): Promise { - try { - const updateDeviceName = await this.fidoService.updateFidoUserDeviceName(credentialId, deviceName); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.fido.success.updateDeviceName, - data: updateDeviceName.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } catch (error) { - this.logger.error(`Error::${error}`); - throw error; - } - } - } diff --git a/apps/api-gateway/src/fido/fido.service.ts b/apps/api-gateway/src/fido/fido.service.ts index cb9e87ad0..f98659706 100644 --- a/apps/api-gateway/src/fido/fido.service.ts +++ b/apps/api-gateway/src/fido/fido.service.ts @@ -11,18 +11,18 @@ export class FidoService extends BaseService { ) { super('FidoService'); } - async generateRegistrationOption(userName: string, deviceFlag: boolean): Promise<{response: object}> { + async generateRegistrationOption(deviceFlag: boolean, email:string): Promise<{response: object}> { try { - const payload = { userName, deviceFlag }; - return this.sendNats(this.fidoServiceProxy, 'generate-registration-options', payload); + const payload = { deviceFlag, email }; + return await this.sendNats(this.fidoServiceProxy, 'generate-registration-options', payload); } catch (error) { throw new RpcException(error.response); } } - async verifyRegistration(verifyRegistrationDto: VerifyRegistrationDto, userName: string): Promise<{response: object}> { - const payload = { verifyRegistrationDetails: verifyRegistrationDto, userName }; + async verifyRegistration(verifyRegistrationDto: VerifyRegistrationDto, email: string): Promise<{response: object}> { + const payload = { verifyRegistrationDetails: verifyRegistrationDto, email }; return this.sendNats(this.fidoServiceProxy, 'verify-registration', payload); } @@ -31,18 +31,18 @@ export class FidoService extends BaseService { return this.sendNats(this.fidoServiceProxy, 'generate-authentication-options', payload); } - async verifyAuthentication(verifyAuthenticationDto: VerifyAuthenticationDto, userName: string): Promise<{response: object}> { - const payload = { verifyAuthenticationDetails: verifyAuthenticationDto, userName }; + async verifyAuthentication(verifyAuthenticationDto: VerifyAuthenticationDto, email: string): Promise<{response: object}> { + const payload = { verifyAuthenticationDetails: verifyAuthenticationDto, email }; return this.sendNats(this.fidoServiceProxy, 'verify-authentication', payload); } - async updateFidoUser(updateFidoUserDetailsDto: UpdateFidoUserDetailsDto) : Promise<{response: object}> { - const payload = updateFidoUserDetailsDto; + async updateFidoUser(updateFidoUserDetailsDto: UpdateFidoUserDetailsDto, credentialId: string) : Promise<{response: object}> { + const payload = {updateFidoUserDetailsDto, credentialId}; return this.sendNats(this.fidoServiceProxy, 'update-user', payload); } - async fetchFidoUserDetails(userName: string): Promise<{response: string}> { - const payload = { userName }; + async fetchFidoUserDetails(email: string): Promise<{response: string}> { + const payload = { email }; return this.sendNats(this.fidoServiceProxy, 'fetch-fido-user-details', payload); } diff --git a/apps/user/repositories/user-device.repository.ts b/apps/user/repositories/user-device.repository.ts index 6913aa0e3..5b45fc0a2 100644 --- a/apps/user/repositories/user-device.repository.ts +++ b/apps/user/repositories/user-device.repository.ts @@ -38,253 +38,254 @@ export class UserDevicesRepository { } } -/** - * - * @param createFidoMultiDevice - * @returns Device details - */ -// eslint-disable-next-line camelcase -async createMultiDevice(newDevice:Prisma.JsonValue, userId:number): Promise { - try { - - const saveResponse = await this.prisma.user_devices.create({ - data: { - devices: newDevice, - userId - } - }); + /** + * + * @param createFidoMultiDevice + * @returns Device details + */ + // eslint-disable-next-line camelcase + async createMultiDevice(newDevice: Prisma.JsonValue, userId: number): Promise { + try { + + const saveResponse = await this.prisma.user_devices.create({ + data: { + devices: newDevice, + userId + } + }); - return saveResponse; + return saveResponse; - } catch (error) { - this.logger.error(`In Create User Repository: ${JSON.stringify(error)}`); - throw error; + } catch (error) { + this.logger.error(`In Create User Repository: ${JSON.stringify(error)}`); + throw error; + } } - } -/** - * - * @param userId - * @returns Device details - */ - // eslint-disable-next-line camelcase - async fidoMultiDevice(userId: number): Promise { - try { - const userDetails = await this.prisma.user_devices.findMany({ - where: { - userId, - deletedAt: null - }, - orderBy: { - createDateTime: 'desc' - } - }); + /** + * + * @param userId + * @returns Device details + */ + // eslint-disable-next-line camelcase + async fidoMultiDevice(userId: number): Promise { + try { + const userDetails = await this.prisma.user_devices.findMany({ + where: { + userId, + deletedAt: null + }, + orderBy: { + createDateTime: 'desc' + } + }); - return userDetails; - } catch (error) { - this.logger.error(`Not Found: ${JSON.stringify(error)}`); - throw new NotFoundException(error); + return userDetails; + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } } -} -/** - * - * @param userId - * @returns Get all device details - */ -// eslint-disable-next-line camelcase, @typescript-eslint/no-explicit-any -async getfidoMultiDevice(userId: number): Promise { - try { - - const fidoMultiDevice = await this.prisma.user_devices.findMany({ - where: { - userId - } - }); - return fidoMultiDevice; - } catch (error) { - this.logger.error(`Not Found: ${JSON.stringify(error)}`); - throw new NotFoundException(error); + /** + * + * @param userId + * @returns Get all device details + */ + // eslint-disable-next-line camelcase, @typescript-eslint/no-explicit-any + async getfidoMultiDevice(userId: number): Promise { + try { + + const fidoMultiDevice = await this.prisma.user_devices.findMany({ + where: { + userId + } + }); + return fidoMultiDevice; + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } } -} -/** - * - * @param userId - * @returns Get all active device details - */ -async getfidoMultiDeviceDetails(userId: number): Promise { - try { - const fidoMultiDevice = await this.prisma.user_devices.findMany({ - where: { - userId, - deletedAt: null - }, - select: { - id: true, - createDateTime: true, - createdBy: true, - lastChangedDateTime: true, - lastChangedBy: true, - devices: true, - credentialId: true, - deviceFriendlyName: true - } - }); - return fidoMultiDevice; - } catch (error) { - this.logger.error(`Not Found: ${JSON.stringify(error)}`); - throw new NotFoundException(error); + /** + * + * @param userId + * @returns Get all active device details + */ + async getfidoMultiDeviceDetails(userId: number): Promise { + try { + const fidoMultiDevice = await this.prisma.user_devices.findMany({ + where: { + userId, + deletedAt: null + }, + select: { + id: true, + createDateTime: true, + createdBy: true, + lastChangedDateTime: true, + lastChangedBy: true, + devices: true, + credentialId: true, + deviceFriendlyName: true + } + }); + return fidoMultiDevice; + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } } -} -/** - * - * @param credentialId - * @returns Find device details from credentialID - */ -async getFidoUserDeviceDetails(credentialId: string): Promise { - this.logger.log(`credentialId: ${credentialId}`); - try { - const getUserDevice = await this.prisma.$queryRaw` + /** + * + * @param credentialId + * @returns Find device details from credentialID + */ + async getFidoUserDeviceDetails(credentialId: string): Promise { + this.logger.log(`credentialId: ${credentialId}`); + try { + const getUserDevice = await this.prisma.$queryRaw` SELECT * FROM user_devices WHERE credentialId LIKE '%${credentialId}%' LIMIT 1; `; - return getUserDevice; - } catch (error) { - this.logger.error(`Not Found: ${JSON.stringify(error)}`); - throw new NotFoundException(error); + return getUserDevice; + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } } -} -/** - * - * @param credentialId - * @param loginCounter - * @returns Update Auth counter - */ -async updateFidoAuthCounter(credentialId: string, loginCounter:number): Promise { - try { - return await this.prisma.user_devices.updateMany({ - where: { - credentialId - }, - data: { - authCounter: loginCounter - } - }); - - } catch (error) { - this.logger.error(`Not Found: ${JSON.stringify(error)}`); - throw new NotFoundException(error); + /** + * + * @param credentialId + * @param loginCounter + * @returns Update Auth counter + */ + async updateFidoAuthCounter(credentialId: string, loginCounter: number): Promise { + try { + return await this.prisma.user_devices.updateMany({ + where: { + credentialId + }, + data: { + authCounter: loginCounter + } + }); + + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } } -} -/** - * - * @param credentialId - * @returns Device detail for specific credentialId - */ -// eslint-disable-next-line camelcase -async checkUserDeviceByCredentialId(credentialId: string): Promise { - this.logger.log(`checkUserDeviceByCredentialId: ${credentialId}`); - try { - return this.prisma.user_devices.findFirst({ - where: { - credentialId - } - }); - } catch (error) { - this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + /** + * + * @param credentialId + * @returns Device detail for specific credentialId + */ + // eslint-disable-next-line camelcase + async checkUserDeviceByCredentialId(credentialId: string): Promise { + this.logger.log(`checkUserDeviceByCredentialId: ${credentialId}`); + try { + return await this.prisma.user_devices.findFirst({ + where: { + credentialId + } + }); + } catch (error) { + this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } } -} -/** - * - * @param credentialId - * @returns Delete device - */ -// eslint-disable-next-line camelcase -async deleteUserDeviceByCredentialId(credentialId: string): Promise { - try { - return await this.prisma.user_devices.updateMany({ - where: { - credentialId - }, - data: { - deletedAt: new Date() - } - }); - } catch (error) { - this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + /** + * + * @param credentialId + * @returns Delete device + */ + // eslint-disable-next-line camelcase + async deleteUserDeviceByCredentialId(credentialId: string): Promise { + try { + return await this.prisma.user_devices.updateMany({ + where: { + credentialId + }, + data: { + deletedAt: new Date() + } + }); + } catch (error) { + this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } } -} -/** - * - * @param id - * @param deviceName - * @returns Update device name - */ -async updateUserDeviceByCredentialId(id: number, deviceName:string): Promise { - try { - return await this.prisma.user_devices.updateMany({ - where: { - id - }, - data: { - deviceFriendlyName: deviceName - } - }); - } catch (error) { - this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + /** + * + * @param id + * @param deviceName + * @returns Update device name + */ + async updateUserDeviceByCredentialId(id: number, deviceName: string): Promise { + try { + return await this.prisma.user_devices.updateMany({ + where: { + id + }, + data: { + deviceFriendlyName: deviceName + } + }); + } catch (error) { + this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } } -} -/** - * - * @param credentialId - * @param deviceFriendlyName - * @returns Get device details name for specific credentialId - */ -async updateDeviceByCredentialId(credentialId:string): Promise { - try { - return await this.prisma.$queryRaw` + /** + * + * @param credentialId + * @param deviceFriendlyName + * @returns Get device details name for specific credentialId + */ + async updateDeviceByCredentialId(credentialId: string): Promise { + try { + return await this.prisma.$queryRaw` SELECT * FROM user_devices WHERE devices->>'credentialID' = ${credentialId} `; - } catch (error) { - this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + } catch (error) { + this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } } -} -/** - * - * @param id - * @param credentialId - * @param deviceFriendlyName - * @returns Update device name for specific credentialId - */ -// eslint-disable-next-line camelcase -async addCredentialIdAndNameById(id:number, credentialId:string, deviceFriendlyName:string): Promise { - try { - return await this.prisma.user_devices.update({ - where: { - id - }, - data: { - credentialId, - deviceFriendlyName - } - }); - } catch (error) { - this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + /** + * + * @param id + * @param credentialId + * @param deviceFriendlyName + * @returns Update device name for specific credentialId + */ + // eslint-disable-next-line camelcase + async addCredentialIdAndNameById(id: number, updateFidoUserDetails: string): Promise { + + try { + return await this.prisma.user_devices.update({ + where: { + id + }, + data: { + credentialId: JSON.parse(updateFidoUserDetails).credentialId, + deviceFriendlyName: JSON.parse(updateFidoUserDetails).updateFidoUserDetailsDto.deviceFriendlyName + } + }); + } catch (error) { + this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } } -} } diff --git a/apps/user/src/fido/dtos/fido-user.dto.ts b/apps/user/src/fido/dtos/fido-user.dto.ts index fbf546b67..2353717ab 100644 --- a/apps/user/src/fido/dtos/fido-user.dto.ts +++ b/apps/user/src/fido/dtos/fido-user.dto.ts @@ -1,10 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { IsArray, IsBoolean, IsOptional, IsString, ValidateNested } from 'class-validator'; export class GenerateRegistrationDto { - @ApiProperty({ example: 'abc@vomoto.com' }) - @IsNotEmpty({ message: 'Email is required.' }) - @IsEmail() - userName: string; + email: string; @IsOptional() @ApiProperty({ example: 'false' }) @@ -66,7 +63,7 @@ class ResponseDto { @ApiProperty() @IsString() - userName: string; + email: string; } // @@ -119,10 +116,7 @@ class VerifyAuthenticationResponseDto { export class VerifyAuthenticationPayloadDto { @ApiProperty() verifyAuthenticationDetails: VerifyAuthenticationDto; - - @ApiProperty() - @IsString() - userName: string; + email: string; } export class UpdateFidoUserDetailsDto { @@ -142,7 +136,7 @@ class VerifyAuthenticationResponseDto { export class UserNameDto { @ApiProperty() @IsString() - userName: string; + email: string; } export class credentialDto { diff --git a/apps/user/src/fido/fido.controller.ts b/apps/user/src/fido/fido.controller.ts index 6d3f9f4dd..9d43dbf0d 100644 --- a/apps/user/src/fido/fido.controller.ts +++ b/apps/user/src/fido/fido.controller.ts @@ -34,7 +34,7 @@ export class FidoController { */ @MessagePattern({ cmd: 'generate-authentication-options' }) generateAuthenticationOption(payload: GenerateRegistrationDto): Promise { - return this.fidoService.generateAuthenticationOption(payload.userName); + return this.fidoService.generateAuthenticationOption(payload.email); } /** * Description: FIDO User Verification @@ -61,7 +61,7 @@ export class FidoController { */ @MessagePattern({ cmd: 'fetch-fido-user-details' }) fetchFidoUserDetails(payload: UserNameDto):Promise { - return this.fidoService.fetchFidoUserDetails(payload.userName); + return this.fidoService.fetchFidoUserDetails(payload.email); } /** diff --git a/apps/user/src/fido/fido.service.ts b/apps/user/src/fido/fido.service.ts index 5095826b0..203f7d404 100644 --- a/apps/user/src/fido/fido.service.ts +++ b/apps/user/src/fido/fido.service.ts @@ -18,17 +18,17 @@ export class FidoService { ) { } async generateRegistration(payload: GenerateRegistrationDto): Promise { try { - const { userName, deviceFlag } = payload; - const fidoUser = await this.fidoUserRepository.checkFidoUserExist(userName); + const { email, deviceFlag } = payload; + const fidoUser = await this.fidoUserRepository.checkFidoUserExist(email); if (!fidoUser && !fidoUser.id) { throw new NotFoundException(ResponseMessages.user.error.notFound); } if (!fidoUser || true === deviceFlag || false === deviceFlag) { - const generatedOption = await this.generateRegistrationOption(userName); + const generatedOption = await this.generateRegistrationOption(email); return generatedOption; } else if (!fidoUser.isFidoVerified) { - const generatedOption = await this.updateUserRegistrationOption(userName); + const generatedOption = await this.updateUserRegistrationOption(email); return generatedOption; } else { throw new BadRequestException(ResponseMessages.fido.error.exists); @@ -39,13 +39,13 @@ export class FidoService { } } - generateRegistrationOption(userName: string): Promise { - const url = `${process.env.FIDO_API_ENDPOINT}/generate-registration-options/?userName=${userName}`; + generateRegistrationOption(email: string): Promise { + const url = `${process.env.FIDO_API_ENDPOINT}/generate-registration-options/?userName=${email}`; return this.commonService .httpGet(url, { headers: { 'Content-Type': 'application/json' } }) .then(async (response) => { const { user } = response; - const updateUser = await this.fidoUserRepository.updateUserDetails(userName, [ + const updateUser = await this.fidoUserRepository.updateUserDetails(email, [ {fidoUserId:user.id}, {username:user.name} ]); @@ -57,14 +57,14 @@ export class FidoService { }); } - updateUserRegistrationOption(userName: string): Promise { - const url = `${process.env.FIDO_API_ENDPOINT}/generate-registration-options/?userName=${userName}`; + updateUserRegistrationOption(email: string): Promise { + const url = `${process.env.FIDO_API_ENDPOINT}/generate-registration-options/?userName=${email}`; return this.commonService .httpGet(url, { headers: { 'Content-Type': 'application/json' } }) .then(async (response) => { const { user } = response; this.logger.debug(`registration option:: already${JSON.stringify(response)}`); - await this.fidoUserRepository.updateUserDetails(userName, [ + await this.fidoUserRepository.updateUserDetails(email, [ {fidoUserId:user.id}, {isFidoVerified:false} ]); @@ -74,17 +74,17 @@ export class FidoService { async verifyRegistration(verifyRegistrationDto: VerifyRegistrationPayloadDto): Promise { try { - const { verifyRegistrationDetails, userName } = verifyRegistrationDto; + const { verifyRegistrationDetails, email } = verifyRegistrationDto; const url = `${process.env.FIDO_API_ENDPOINT}/verify-registration`; const payload = JSON.stringify(verifyRegistrationDetails); const response = await this.commonService.httpPost(url, payload, { headers: { 'Content-Type': 'application/json' } }); - if (response?.verified && userName) { - await this.fidoUserRepository.updateUserDetails(userName, [{isFidoVerified:true}]); + if (response?.verified && email) { + await this.fidoUserRepository.updateUserDetails(email, [{isFidoVerified:true}]); const credentialID = response.newDevice.credentialID.replace(/=*$/, ''); response.newDevice.credentialID = credentialID; - const getUser = await this.fidoUserRepository.checkFidoUserExist(userName); + const getUser = await this.fidoUserRepository.checkFidoUserExist(email); await this.userDevicesRepository.createMultiDevice(response?.newDevice, getUser.id); return response; } else { @@ -96,9 +96,9 @@ export class FidoService { } } - async generateAuthenticationOption(userName: string): Promise { + async generateAuthenticationOption(email: string): Promise { try { - const fidoUser = await this.fidoUserRepository.checkFidoUserExist(userName); + const fidoUser = await this.fidoUserRepository.checkFidoUserExist(email); if (fidoUser && fidoUser.id) { const fidoMultiDevice = await this.userDevicesRepository.getfidoMultiDevice(fidoUser.id); const credentialIds = []; @@ -124,8 +124,8 @@ export class FidoService { async verifyAuthentication(verifyAuthenticationDto: VerifyAuthenticationPayloadDto): Promise { try { - const { verifyAuthenticationDetails, userName } = verifyAuthenticationDto; - const fidoUser = await this.fidoUserRepository.checkFidoUserExist(userName); + const { verifyAuthenticationDetails, email } = verifyAuthenticationDto; + const fidoUser = await this.fidoUserRepository.checkFidoUserExist(email); const fidoMultiDevice = await this.userDevicesRepository.getfidoMultiDeviceDetails(fidoUser.id); const url = `${process.env.FIDO_API_ENDPOINT}/verify-authentication`; const payload = { verifyAuthenticationDetails: JSON.stringify(verifyAuthenticationDetails), devices: fidoMultiDevice }; @@ -164,11 +164,13 @@ export class FidoService { async updateUser(updateFidoUserDetailsDto: UpdateFidoUserDetailsDto): Promise { try { - const { deviceFriendlyName, credentialId } = updateFidoUserDetailsDto; - const updateFidoUser = await this.userDevicesRepository.updateDeviceByCredentialId(credentialId); - if (updateFidoUser[0].id) { - await this.userDevicesRepository.addCredentialIdAndNameById(updateFidoUser[0].id, credentialId, deviceFriendlyName); + const updateFidoUserDetails = JSON.stringify(updateFidoUserDetailsDto); + const updateFidoUser = await this.userDevicesRepository.updateDeviceByCredentialId(updateFidoUserDetailsDto.credentialId); + + if (updateFidoUser[0].id) { + await this.userDevicesRepository.addCredentialIdAndNameById(updateFidoUser[0].id, updateFidoUserDetails); + } if (updateFidoUser[0].id) { return 'User updated.'; @@ -182,9 +184,9 @@ export class FidoService { } } - async fetchFidoUserDetails(userName: string): Promise { + async fetchFidoUserDetails(email: string): Promise { try { - const fidoUser = await this.fidoUserRepository.checkFidoUserExist(userName); + const fidoUser = await this.fidoUserRepository.checkFidoUserExist(email); if (!fidoUser) { throw new NotFoundException(ResponseMessages.user.error.notFound); } @@ -219,7 +221,6 @@ export class FidoService { async updateFidoUserDeviceName(payload: updateDeviceDto): Promise { try { const { credentialId, deviceName } = payload; - const getUserDevice = await this.userDevicesRepository.checkUserDeviceByCredentialId(credentialId); const updateUserDevice = await this.userDevicesRepository.updateUserDeviceByCredentialId(getUserDevice.id, deviceName); if (1 === updateUserDevice.count) { diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 5102b5bf1..faf949a74 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -186,6 +186,7 @@ export class UserService { throw new UnauthorizedException(ResponseMessages.user.error.invalidEmail); } const checkUserDetails = await this.userRepository.getUserDetails(userInfo.email); + if (!checkUserDetails) { throw new NotFoundException(ResponseMessages.user.error.invalidEmail); } @@ -200,7 +201,6 @@ export class UserService { throw new NotFoundException(ResponseMessages.user.error.invalidEmail); } const userDetails = await this.userRepository.getUserDetails(userInfo.email); - if (!userDetails) { throw new NotFoundException(ResponseMessages.user.error.adduser); } @@ -282,10 +282,8 @@ export class UserService { */ async login(loginUserDto: LoginUserDto): Promise { const { email, password, isPasskey } = loginUserDto; - try { const userData = await this.userRepository.checkUserExist(email); - if (!userData) { throw new NotFoundException(ResponseMessages.user.error.notFound); } @@ -321,7 +319,6 @@ export class UserService { email, password }); - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); if (error) { @@ -329,7 +326,6 @@ export class UserService { } const token = data?.session; - return token; } catch (error) { throw new RpcException(error.response ? error.response : error); @@ -383,7 +379,7 @@ export class UserService { async findSupabaseUser(payload: { id }): Promise { try { - return this.userRepository.getUserBySupabaseId(payload.id); + return await this.userRepository.getUserBySupabaseId(payload.id); } catch (error) { this.logger.error(`Error in findSupabaseUser: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -392,7 +388,7 @@ export class UserService { async findUserByEmail(payload: { email }): Promise { try { - return this.userRepository.findUserByEmail(payload.email); + return await this.userRepository.findUserByEmail(payload.email); } catch (error) { this.logger.error(`findUserByEmail: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); From 64c86326bd10b008f182ed93f8f181c3d23dad26 Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Fri, 13 Oct 2023 19:51:54 +0530 Subject: [PATCH 148/162] feat: Schema and cred-def auto sign and submit endorsement transaction (#153) * Added the auto submit flag for automatic submit the transaction Signed-off-by: KulkarniShashank * Removed unnecessary params Signed-off-by: KulkarniShashank * Error handling in the schema endorsement Signed-off-by: KulkarniShashank * feat: Schema and credDef auto sign and submit transaction Signed-off-by: KulkarniShashank * Solved issue when schema sign Signed-off-by: KulkarniShashank * Added the validation for the cedDef create and write Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank --- apps/ecosystem/src/ecosystem.repository.ts | 54 ++- apps/ecosystem/src/ecosystem.service.ts | 389 +++++++++++++-------- libs/common/src/common.constant.ts | 2 + libs/common/src/response-messages/index.ts | 8 + libs/prisma-service/prisma/schema.prisma | 22 +- 5 files changed, 308 insertions(+), 167 deletions(-) diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 5269530d1..6ffa674e9 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -1,7 +1,7 @@ import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase -import { credential_definition, ecosystem, ecosystem_invitations, ecosystem_orgs, ecosystem_roles, endorsement_transaction, org_agents, platform_config, schema } from '@prisma/client'; +import { credential_definition, ecosystem, ecosystem_config, ecosystem_invitations, ecosystem_orgs, ecosystem_roles, endorsement_transaction, org_agents, platform_config, schema } from '@prisma/client'; import { DeploymentModeType, EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; import { SaveSchema, SchemaTransactionResponse, saveCredDef } from '../interfaces/ecosystem.interfaces'; @@ -146,7 +146,7 @@ export class EcosystemRepository { }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -165,7 +165,7 @@ export class EcosystemRepository { }; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -182,7 +182,7 @@ export class EcosystemRepository { return membersCount; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -199,7 +199,7 @@ export class EcosystemRepository { return endorsementsCount; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -223,7 +223,7 @@ export class EcosystemRepository { }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -246,7 +246,7 @@ export class EcosystemRepository { }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -268,7 +268,7 @@ export class EcosystemRepository { }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException('Unable to update ecosystem invitation'); + throw error; } } @@ -304,7 +304,7 @@ export class EcosystemRepository { }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException('Unable to update ecosystem orgs'); + throw error; } } @@ -333,7 +333,7 @@ export class EcosystemRepository { }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -350,7 +350,7 @@ export class EcosystemRepository { return await this.getEcosystemInvitationsPagination(query, pageNumber, pageSize); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -409,7 +409,7 @@ export class EcosystemRepository { return { totalPages, members }; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -446,7 +446,7 @@ export class EcosystemRepository { return { totalPages, invitations }; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -508,7 +508,7 @@ export class EcosystemRepository { return { totalPages, transactions }; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -578,7 +578,27 @@ export class EcosystemRepository { } catch (error) { this.logger.error(`Error in getting getPlatformConfigDetails for the ecosystem - error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; + } + } + + /** + * Get platform config details + * @returns + */ + // eslint-disable-next-line camelcase + async getEcosystemConfigDetails(key: string): Promise { + try { + + return this.prisma.ecosystem_config.findFirst({ + where: { + key + } + }); + + } catch (error) { + this.logger.error(`Error in getting getPlatformConfigDetails for the ecosystem - error: ${JSON.stringify(error)}`); + throw error; } } @@ -603,7 +623,7 @@ export class EcosystemRepository { }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } @@ -619,7 +639,7 @@ export class EcosystemRepository { return deletedInvitation; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw new InternalServerErrorException(error); + throw error; } } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 2a31a045f..4d7121ed3 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -13,11 +13,11 @@ import { Invitation, OrgAgentType } from '@credebl/enum/enum'; import { EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; import { EcosystemMembersPayload } from '../interfaces/ecosystemMembers.interface'; -import { CredDefMessage, CredDefTransactionPayload, EndorsementTransactionPayload, RequestCredDeffEndorsement, RequestSchemaEndorsement, SaveSchema, SchemaMessage, SchemaTransactionPayload, SchemaTransactionResponse, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; +import { CredDefMessage, SaveSchema, SchemaMessage, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; -// eslint-disable-next-line camelcase -import { platform_config } from '@prisma/client'; import { CommonConstants } from '@credebl/common/common.constant'; +// eslint-disable-next-line camelcase +import { credential_definition, org_agents, platform_config, schema } from '@prisma/client'; @Injectable() @@ -284,37 +284,47 @@ export class EcosystemService { * @param RequestSchemaEndorsement * @returns */ - async requestSchemaEndorsement(requestSchemaPayload: RequestSchemaEndorsement, orgId: number, ecosystemId: string): Promise { + async requestSchemaEndorsement(requestSchemaPayload, orgId, ecosystemId): Promise { try { - const schemaRequestExist = await this.ecosystemRepository.findRecordsByNameAndVersion(requestSchemaPayload?.name, requestSchemaPayload?.version); + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + + const [schemaRequestExist, ecosystemMemberDetails, platformConfig, ecosystemLeadAgentDetails, getEcosystemOrgDetailsByOrgId] = await Promise.all([ + this.ecosystemRepository.findRecordsByNameAndVersion(requestSchemaPayload?.name, requestSchemaPayload?.version), + this.ecosystemRepository.getAgentDetails(orgId), + this.ecosystemRepository.getPlatformConfigDetails(), + this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)), + this.ecosystemRepository.getEcosystemOrgDetailsbyId(String(orgId)) + ]); if (0 !== schemaRequestExist.length) { throw new ConflictException(ResponseMessages.ecosystem.error.schemaAlreadyExist); } - const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(orgId); if (!ecosystemMemberDetails) { - throw new InternalServerErrorException(ResponseMessages.ecosystem.error.notFound); + throw new NotFoundException(ResponseMessages.ecosystem.error.notFound); } - // eslint-disable-next-line camelcase - const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); - - const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.SCHEMA, ecosystemMemberDetails?.tenantId); - - const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); - - const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + if (!platformConfig) { + throw new NotFoundException(ResponseMessages.ecosystem.error.platformConfigNotFound); + } - const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); + if (!getEcosystemLeadDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemNotFound); + } if (!ecosystemLeadAgentDetails) { - throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); + throw new NotFoundException(ResponseMessages.ecosystem.error.leadNotFound); + } + + if (!getEcosystemOrgDetailsByOrgId) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemOrgNotFound); } - const getEcosystemOrgDetailsByOrgId = await this.ecosystemRepository.getEcosystemOrgDetailsbyId(String(orgId)); + const url = await this.getAgentUrl(ecosystemMemberDetails.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.SCHEMA, ecosystemMemberDetails.tenantId); - const schemaTransactionPayload: SchemaTransactionPayload = { + const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); + + const schemaTransactionPayload = { endorserDid: ecosystemLeadAgentDetails.orgDid, endorse: requestSchemaPayload.endorse, attributes: attributeArray, @@ -324,7 +334,8 @@ export class EcosystemService { }; const schemaTransactionRequest: SchemaMessage = await this._requestSchemaEndorsement(schemaTransactionPayload, url, platformConfig?.sgApiKey); - const schemaTransactionResponse: SchemaTransactionResponse = { + + const schemaTransactionResponse = { endorserDid: ecosystemLeadAgentDetails.orgDid, authorDid: ecosystemMemberDetails.orgDid, requestPayload: schemaTransactionRequest.message.schemaState.schemaRequest, @@ -335,53 +346,73 @@ export class EcosystemService { if ('failed' === schemaTransactionRequest.message.schemaState.state) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestSchemaTransaction); } + return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, requestSchemaPayload, endorsementTransactionType.SCHEMA); } catch (error) { this.logger.error(`In request schema endorsement : ${JSON.stringify(error)}`); - - throw new RpcException(error.response ? error.response : error); + throw new RpcException(error.response || error); } } - async requestCredDeffEndorsement(requestCredDefPayload: RequestCredDeffEndorsement, orgId: number, ecosystemId: string): Promise { + async requestCredDeffEndorsement(requestCredDefPayload, orgId, ecosystemId): Promise { try { - const credDefRequestExist = await this.ecosystemRepository.findRecordsByCredDefTag(requestCredDefPayload?.tag); + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + + const [credDefRequestExist, ecosystemMemberDetails, platformConfig, ecosystemLeadAgentDetails, getEcosystemOrgDetailsByOrgId] = await Promise.all([ + this.ecosystemRepository.findRecordsByCredDefTag(requestCredDefPayload?.tag), + this.ecosystemRepository.getAgentDetails(orgId), + this.ecosystemRepository.getPlatformConfigDetails(), + this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)), + this.ecosystemRepository.getEcosystemOrgDetailsbyId(String(orgId)) + ]); if (0 !== credDefRequestExist.length) { throw new ConflictException(ResponseMessages.ecosystem.error.credDefAlreadyExist); } - const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(orgId); if (!ecosystemMemberDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.notFound); } - // eslint-disable-next-line camelcase - const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); - - const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.CREDENTIAL_DEFINITION, ecosystemMemberDetails?.tenantId); - - // const attributeArray = requestSchemaPayload.attributes.map(item => item.attributeName); + if (!platformConfig) { + throw new NotFoundException(ResponseMessages.ecosystem.error.platformConfigNotFound); + } - const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); - const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); + if (!getEcosystemLeadDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemNotFound); + } if (!ecosystemLeadAgentDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); } - const getEcosystemOrgDetailsByOrgId = await this.ecosystemRepository.getEcosystemOrgDetailsbyId(String(orgId)); + if (!getEcosystemOrgDetailsByOrgId) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemOrgNotFound); + } + + const url = await this.getAgentUrl(ecosystemMemberDetails.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.CREDENTIAL_DEFINITION, ecosystemMemberDetails.tenantId); - const credDefTransactionPayload: CredDefTransactionPayload = { + const credDefTransactionPayload = { endorserDid: ecosystemLeadAgentDetails.orgDid, endorse: requestCredDefPayload.endorse, tag: requestCredDefPayload.tag, schemaId: requestCredDefPayload.schemaId, issuerId: ecosystemMemberDetails.orgDid }; - // Need to add logic and type for credential-definition + const credDefTransactionRequest: CredDefMessage = await this._requestCredDeffEndorsement(credDefTransactionPayload, url, platformConfig?.sgApiKey); + + if ('failed' === credDefTransactionRequest.message.credentialDefinitionState.state) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); + } + + const requestBody = credDefTransactionRequest.message.credentialDefinitionState.credentialDefinition; + + if (!requestBody) { + throw new NotFoundException(ResponseMessages.ecosystem.error.credentialDefinitionNotFound); + } + const schemaTransactionResponse = { endorserDid: ecosystemLeadAgentDetails.orgDid, authorDid: ecosystemMemberDetails.orgDid, @@ -390,20 +421,14 @@ export class EcosystemService { ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id }; - - if ('failed' === credDefTransactionRequest.message.credentialDefinitionState.state) { - throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); - } - - const requestBody = credDefTransactionRequest.message.credentialDefinitionState.credentialDefinition; return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, requestBody, endorsementTransactionType.CREDENTIAL_DEFINITION); } catch (error) { - this.logger.error(`In request cred-def endorsement : ${JSON.stringify(error)}`); - - throw new RpcException(error.response ? error.response : error); + this.logger.error(`In request cred-def endorsement: ${JSON.stringify(error)}`); + throw new RpcException(error.response || error); } } + async getInvitationsByEcosystemId( payload: FetchInvitationsPayload ): Promise { @@ -454,25 +479,32 @@ export class EcosystemService { async signTransaction(endorsementId: string, ecosystemId: string): Promise { try { - const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.REQUESTED); + const [endorsementTransactionPayload, ecosystemLeadDetails, platformConfig] = await Promise.all([ + this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.REQUESTED), + this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId), + this.ecosystemRepository.getPlatformConfigDetails() + ]); + if (!endorsementTransactionPayload) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidTransaction); } - const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); - - if (!getEcosystemLeadDetails) { + if (!ecosystemLeadDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); } - // eslint-disable-next-line camelcase - const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); - const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); - const url = await this.getAgentUrl(ecosystemLeadAgentDetails?.orgAgentTypeId, ecosystemLeadAgentDetails.agentEndPoint, endorsementTransactionType.SIGN, ecosystemLeadAgentDetails?.tenantId); + if (!platformConfig) { + throw new NotFoundException(ResponseMessages.ecosystem.error.platformConfigNotFound); + } + + const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(ecosystemLeadDetails.orgId)); if (!ecosystemLeadAgentDetails) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); } + + const url = await this.getAgentUrl(ecosystemLeadAgentDetails?.orgAgentTypeId, ecosystemLeadAgentDetails.agentEndPoint, endorsementTransactionType.SIGN, ecosystemLeadAgentDetails?.tenantId); + const jsonString = endorsementTransactionPayload.requestPayload.toString(); const payload = { transaction: jsonString, @@ -480,11 +512,38 @@ export class EcosystemService { }; const schemaTransactionRequest: SignedTransactionMessage = await this._signTransaction(payload, url, platformConfig.sgApiKey); - return this.ecosystemRepository.updateTransactionDetails(endorsementId, schemaTransactionRequest.message.signedTransaction); + if (!schemaTransactionRequest) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.signRequestError); + } + + const autoEndorsement = `${CommonConstants.ECOSYSTEM_AUTO_ENDOSEMENT}`; + const ecosystemConfigDetails = await this.ecosystemRepository.getEcosystemConfigDetails(autoEndorsement); + + if (!ecosystemConfigDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemConfigNotFound); + } + + const updateSignedTransaction = await this.ecosystemRepository.updateTransactionDetails(endorsementId, schemaTransactionRequest.message.signedTransaction); + + if (!updateSignedTransaction) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.updateTransactionError); + } + + if (updateSignedTransaction && 'true' === ecosystemConfigDetails.value) { + + const submitTxn = await this.submitTransaction(endorsementId, ecosystemId); + if (!submitTxn) { + await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.REQUESTED); + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); + } + return submitTxn; + } + + return updateSignedTransaction; } catch (error) { - this.logger.error(`In sign transaction : ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); + this.logger.error(`In sign transaction: ${JSON.stringify(error)}`); + throw new RpcException(error.response || error); } } @@ -536,113 +595,165 @@ export class EcosystemService { } } - async submitTransaction(endorsementId: string, ecosystemId: string): Promise { + // eslint-disable-next-line camelcase + async getEcosystemMemberDetails(endorsementTransactionPayload): Promise { + const orgId = Number(endorsementTransactionPayload.ecosystemOrgs.orgId); + return this.ecosystemRepository.getAgentDetails(orgId); + } + + // eslint-disable-next-line camelcase + async getEcosystemLeadAgentDetails(ecosystemId): Promise { + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + return this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); + } + + // eslint-disable-next-line camelcase + async getPlatformConfig(): Promise { + return this.ecosystemRepository.getPlatformConfigDetails(); + } + + async submitTransactionPayload(endorsementTransactionPayload, ecosystemMemberDetails, ecosystemLeadAgentDetails): Promise { + const parsedRequestPayload = JSON.parse(endorsementTransactionPayload.responsePayload); + const jsonString = endorsementTransactionPayload.responsePayload.toString(); + const payload: submitTransactionPayload = { + endorsedTransaction: jsonString, + endorserDid: ecosystemLeadAgentDetails.orgDid + }; + + if (endorsementTransactionPayload.type === endorsementTransactionType.SCHEMA) { + payload.schema = { + attributes: parsedRequestPayload.operation.data.attr_names, + version: parsedRequestPayload.operation.data.version, + name: parsedRequestPayload.operation.data.name, + issuerId: ecosystemMemberDetails.orgDid + }; + } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { + + payload.credentialDefinition = { + tag: parsedRequestPayload.operation.tag, + issuerId: ecosystemMemberDetails.orgDid, + schemaId: endorsementTransactionPayload.requestBody['schemaId'], + type: endorsementTransactionPayload.requestBody['type'], + value: endorsementTransactionPayload.requestBody['value'] + }; + } + + return payload; + } + + async handleSchemaSubmission(endorsementTransactionPayload, ecosystemMemberDetails, submitTransactionRequest): Promise { + const regex = /[^:]+$/; + const match = ecosystemMemberDetails.orgDid.match(regex); + let extractedDidValue; + + if (match) { + // eslint-disable-next-line prefer-destructuring + extractedDidValue = match[0]; + + } + const saveSchemaPayload: SaveSchema = { + name: endorsementTransactionPayload.requestBody['name'], + version: endorsementTransactionPayload.requestBody['version'], + attributes: JSON.stringify(endorsementTransactionPayload.requestBody['attributes']), + schemaLedgerId: submitTransactionRequest['message'].schemaId, + issuerId: ecosystemMemberDetails.orgDid, + createdBy: endorsementTransactionPayload.ecosystemOrgs.orgId, + lastChangedBy: endorsementTransactionPayload.ecosystemOrgs.orgId, + publisherDid: extractedDidValue, + orgId: endorsementTransactionPayload.ecosystemOrgs.orgId, + ledgerId: ecosystemMemberDetails.ledgerId + }; + const saveSchemaDetails = await this.ecosystemRepository.saveSchema(saveSchemaPayload); + if (!saveSchemaDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.saveSchema); + } + return saveSchemaDetails; + } + + // eslint-disable-next-line camelcase + async handleCredDefSubmission(endorsementTransactionPayload, ecosystemMemberDetails, submitTransactionRequest): Promise { + const schemaDetails = await this.ecosystemRepository.getSchemaDetailsById(endorsementTransactionPayload.requestBody['schemaId']); + + if (!schemaDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.schemaNotFound); + } + + const saveCredentialDefinition: saveCredDef = { + schemaLedgerId: endorsementTransactionPayload.requestBody['schemaId'], + tag: endorsementTransactionPayload.requestBody['tag'], + credentialDefinitionId: submitTransactionRequest['message'].credentialDefinitionId, + revocable: false, + createdBy: endorsementTransactionPayload.ecosystemOrgs.orgId, + orgId: ecosystemMemberDetails.orgId, + schemaId: schemaDetails.id + }; + + const saveCredDefDetails = await this.ecosystemRepository.saveCredDef(saveCredentialDefinition); + if (!saveCredDefDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.saveCredDef); + } + return saveCredDefDetails; + } + + async updateTransactionStatus(endorsementId): Promise { + return this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SUBMITED); + } + + async submitTransaction(endorsementId, ecosystemId): Promise { try { - const endorsementTransactionPayload: EndorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.SIGNED); + const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.SIGNED); + if (!endorsementTransactionPayload) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidTransaction); } + if ('submitted' === endorsementTransactionPayload.status) { throw new ConflictException(ResponseMessages.ecosystem.error.transactionSubmitted); } - const ecosystemMemberDetails = await this.ecosystemRepository.getAgentDetails(Number(endorsementTransactionPayload.ecosystemOrgs.orgId)); - - const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); - // eslint-disable-next-line camelcase - const platformConfig: platform_config = await this.ecosystemRepository.getPlatformConfigDetails(); - - const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(Number(getEcosystemLeadDetails.orgId)); - + const ecosystemMemberDetails = await this.getEcosystemMemberDetails(endorsementTransactionPayload); + const ecosystemLeadAgentDetails = await this.getEcosystemLeadAgentDetails(ecosystemId); + const platformConfig = await this.getPlatformConfig(); const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.SUBMIT, ecosystemMemberDetails?.tenantId); + const payload = await this.submitTransactionPayload(endorsementTransactionPayload, ecosystemMemberDetails, ecosystemLeadAgentDetails); - const parsedRequestPayload = JSON.parse(endorsementTransactionPayload.responsePayload); - const jsonString = endorsementTransactionPayload.responsePayload.toString(); + const submitTransactionRequest = await this._submitTransaction(payload, url, platformConfig.sgApiKey); - const payload: submitTransactionPayload = { - endorsedTransaction: jsonString, - endorserDid: ecosystemLeadAgentDetails.orgDid - }; + if ('failed' === submitTransactionRequest["message"].state) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); + } + + await this.updateTransactionStatus(endorsementId); if (endorsementTransactionPayload.type === endorsementTransactionType.SCHEMA) { - payload.schema = { - attributes: parsedRequestPayload.operation.data.attr_names, - version: parsedRequestPayload.operation.data.version, - name: parsedRequestPayload.operation.data.name, - issuerId: ecosystemMemberDetails.orgDid - }; + return this.handleSchemaSubmission(endorsementTransactionPayload, ecosystemMemberDetails, submitTransactionRequest); } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { - payload.credentialDefinition = { - tag: parsedRequestPayload.operation.tag, - issuerId: ecosystemMemberDetails.orgDid, - schemaId: endorsementTransactionPayload.requestBody['schemaId'], - type: endorsementTransactionPayload.requestBody['type'], - value: endorsementTransactionPayload.requestBody['value'] - }; - } - const submitTransactionRequest = await this._submitTransaction(payload, url, platformConfig.sgApiKey); + if ('undefined' === submitTransactionRequest["message"].credentialDefinitionId.split(":")[3]) { - if ('failed' === submitTransactionRequest['message'].state) { - throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); - } + const autoEndorsement = `${CommonConstants.ECOSYSTEM_AUTO_ENDOSEMENT}`; + const ecosystemConfigDetails = await this.ecosystemRepository.getEcosystemConfigDetails(autoEndorsement); - await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SUBMITED); - if (endorsementTransactionPayload.type === endorsementTransactionType.SCHEMA) { + if ('true' === ecosystemConfigDetails.value) { - const regex = /[^:]+$/; - const match = ecosystemMemberDetails.orgDid.match(regex); - let extractedDidValue; + await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.REQUESTED); + } else { - if (match) { - // eslint-disable-next-line prefer-destructuring - extractedDidValue = match[0]; + await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SIGNED); + } + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); } - const saveSchemaPayload: SaveSchema = { - name: endorsementTransactionPayload.requestBody['name'], - version: endorsementTransactionPayload.requestBody['version'], - attributes: JSON.stringify(endorsementTransactionPayload.requestBody['attributes']), - schemaLedgerId: submitTransactionRequest['message'].schemaId, - issuerId: ecosystemMemberDetails.orgDid, - createdBy: endorsementTransactionPayload.ecosystemOrgs.orgId, - lastChangedBy: endorsementTransactionPayload.ecosystemOrgs.orgId, - publisherDid: extractedDidValue, - orgId: endorsementTransactionPayload.ecosystemOrgs.orgId, - ledgerId: ecosystemMemberDetails.ledgerId - }; - const saveSchemaDetails = await this.ecosystemRepository.saveSchema(saveSchemaPayload); - if (!saveSchemaDetails) { - throw new InternalServerErrorException(ResponseMessages.ecosystem.error.saveSchema); - } - return saveSchemaDetails; - } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { - const schemaDetails = await this.ecosystemRepository.getSchemaDetailsById(endorsementTransactionPayload.requestBody['schemaId']); - const saveCredentialDefinition: saveCredDef = { - schemaLedgerId: endorsementTransactionPayload.requestBody['schemaId'], - tag: endorsementTransactionPayload.requestBody['tag'], - credentialDefinitionId: submitTransactionRequest['message'].credentialDefinitionId, - revocable: false, - createdBy: endorsementTransactionPayload.ecosystemOrgs.orgId, - orgId: ecosystemMemberDetails.orgId, - schemaId: schemaDetails.id - }; - - const saveCredDefDetails = await this.ecosystemRepository.saveCredDef(saveCredentialDefinition); - if (!saveCredDefDetails) { - throw new InternalServerErrorException(ResponseMessages.ecosystem.error.saveCredDef); - } - return saveCredDefDetails; + return this.handleCredDefSubmission(endorsementTransactionPayload, ecosystemMemberDetails, submitTransactionRequest); } - - } catch (error) { - this.logger.error(`In sumit transaction : ${JSON.stringify(error)}`); + this.logger.error(`In submit transaction: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } } + /** * Description: Store shortening URL * @param signEndorsementPayload @@ -719,7 +830,7 @@ export class EcosystemService { ): Promise { const isEcosystemEnabled = await this.checkEcosystemEnableFlag(); - + if (!isEcosystemEnabled) { throw new ForbiddenException(ResponseMessages.ecosystem.error.ecosystemNotEnabled); } @@ -737,7 +848,7 @@ export class EcosystemService { ): Promise { const ecosystemDetails = await this.prisma.ecosystem_config.findFirst( { - where:{ + where: { key: 'enableEcosystem' } } @@ -795,11 +906,11 @@ export class EcosystemService { * @returns EndorsementTransactionRequest Status message */ - async declineEndorsementRequestByLead(ecosystemId:string, endorsementId:string): Promise { + async declineEndorsementRequestByLead(ecosystemId: string, endorsementId: string): Promise { try { return await this.ecosystemRepository.updateEndorsementRequestStatus(ecosystemId, endorsementId); - } catch (error) { + } catch (error) { this.logger.error(`error in decline endorsement request: ${error}`); throw new InternalServerErrorException(error); } diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 1bd48d76d..2e9fe6e88 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -261,6 +261,8 @@ export enum CommonConstants { ONBOARDING_TYPE_EXTERNAL = 1, ONBOARDING_TYPE_INVITATION = 2, + // ecosystem config auto endorsement + ECOSYSTEM_AUTO_ENDOSEMENT = 'autoEndorsement', // Network TESTNET = 'testnet', diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 001d51bdf..679e9590c 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -213,7 +213,15 @@ export const ResponseMessages = { requestSchemaTransaction: 'Error while request schema transaction', requestCredDefTransaction: 'Error while submitting transaction', notFound: 'Organization not found', + platformConfigNotFound: 'Platform configurations not found', + schemaNotFound: 'Schema not found', + ecosystemNotFound: 'Ecosystem not found', + ecosystemOrgNotFound: 'Ecosystem org not found', + ecosystemConfigNotFound: 'Ecosystem config not found', + credentialDefinitionNotFound: 'Credential definition found', leadNotFound: 'Lead details not found', + signRequestError: 'Error while signing the transaction', + updateTransactionError: 'Error while update the transaction', schemaAlreadyExist: 'Schema name and schema version already exist', credDefAlreadyExist: 'Credential definition already exist', saveSchema: 'Error while storing the schema details', diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 1104e0fc9..d3d57fe61 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -368,15 +368,15 @@ model ecosystem_orgs { id String @id @default(uuid()) orgId String orgName String? - orgDid String? @db.VarChar + orgDid String? @db.VarChar status String deploymentMode String? ecosystemId String ecosystemRoleId String createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy String @default("1") + createdBy String @default("1") lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy String @default("1") + lastChangedBy String @default("1") deletedAt DateTime? @db.Timestamp(6) ecosystem ecosystem @relation(fields: [ecosystemId], references: [id]) ecosystemRole ecosystem_roles @relation(fields: [ecosystemRoleId], references: [id]) @@ -402,12 +402,12 @@ model endorsement_transaction { } model ecosystem_config { - id String @id @default(uuid()) - key String? - value String? - createDateTime DateTime @default(now()) @db.Timestamptz(6) - createdBy String @default("1") - lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) - lastChangedBy String @default("1") - deletedAt DateTime? @db.Timestamp(6) + id String @id @default(uuid()) + key String? + value String? + createDateTime DateTime @default(now()) @db.Timestamptz(6) + createdBy String @default("1") + lastChangedDateTime DateTime @default(now()) @db.Timestamptz(6) + lastChangedBy String @default("1") + deletedAt DateTime? @db.Timestamp(6) } From 9773e2bce3677001cb1189e8eac3bf25a0b8b35b Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Mon, 16 Oct 2023 10:23:54 +0530 Subject: [PATCH 149/162] resolved sonar lint checks Signed-off-by: bhavanakarwade --- apps/api-gateway/src/user/user.controller.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index e88752f18..b14d9e3c6 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Post, Put, Body, Param, UseFilters } from '@nestjs/common'; +import { Controller, Post, Put, Body, Param, UseFilters, Res, HttpStatus, BadRequestException, Get, Query, UseGuards } from '@nestjs/common'; import { UserService } from './user.service'; import { ApiBearerAuth, @@ -13,17 +13,11 @@ import { import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; -import { Res } from '@nestjs/common'; import { Response } from 'express'; -import { HttpStatus } from '@nestjs/common'; import { CommonService } from '@credebl/common'; import IResponseType from '@credebl/common/interfaces/response.interface'; -import { BadRequestException } from '@nestjs/common'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { Get } from '@nestjs/common'; -import { Query } from '@nestjs/common'; import { user } from '@prisma/client'; -import { UseGuards } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { User } from '../authz/decorators/user.decorator'; import { AcceptRejectInvitationDto } from './dto/accept-reject-invitation.dto'; From ef161ed44e4c1ad93ad9204ac45277cdaaadc8dc Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Mon, 16 Oct 2023 10:55:38 +0530 Subject: [PATCH 150/162] fix: decline endorsement request Signed-off-by: tipusinghaw --- apps/api-gateway/src/ecosystem/ecosystem.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 2ef578338..36fbf9b6a 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -437,11 +437,11 @@ export class EcosystemController { @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) async declineEndorsementRequestByLead( @Param('ecosystemId') ecosystemId: string, - @Param('orgId') orgId: string, @Param('endorsementId') endorsementId: string, + @Param('orgId') orgId: string, @Res() res: Response ): Promise { - await this.ecosystemService.declineEndorsementRequestByLead(ecosystemId, orgId, endorsementId); + await this.ecosystemService.declineEndorsementRequestByLead(ecosystemId, endorsementId, orgId); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, message: ResponseMessages.ecosystem.success.DeclineEndorsementTransaction From 71daf6407b418670ce786abef0b664c5c5c903ce Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Mon, 16 Oct 2023 12:21:55 +0530 Subject: [PATCH 151/162] refactor: ecosystem lead details included ecosystem dashboard (#156) * worked on the ecosystem lead details Signed-off-by: Nishad * removed the unnecessary code and comments Signed-off-by: Nishad --------- Signed-off-by: Nishad --- .../src/ecosystem/ecosystem.controller.ts | 2 +- apps/ecosystem/src/ecosystem.repository.ts | 8 +++--- apps/ecosystem/src/ecosystem.service.ts | 25 ++++++++++++++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 36fbf9b6a..79fb14575 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -104,7 +104,7 @@ export class EcosystemController { @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) @ApiBearerAuth() async getEcosystemDashboardDetails(@Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: string, @Res() res: Response): Promise { diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 6ffa674e9..c7af0371f 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -155,7 +155,7 @@ export class EcosystemRepository { * @returns Get ecosystem dashboard card count */ - async getEcosystemDashboardDetails(ecosystemId: string): Promise { + async getEcosystemDashboardDetails(ecosystemId: string): Promise<{membersCount: number; endorsementsCount: number}> { try { const membersCount = await this.getEcosystemMembersCount(ecosystemId); const endorsementsCount = await this.getEcosystemEndorsementsCount(ecosystemId); @@ -452,7 +452,7 @@ export class EcosystemRepository { async fetchEcosystemOrg( - payload: { ecosystemId: string, orgId: string } + payload: object ): Promise { return this.prisma.ecosystem_orgs.findFirst({ @@ -460,7 +460,9 @@ export class EcosystemRepository { ...payload }, select: { - ecosystemRole: true + ecosystem: true, + ecosystemRole: true, + orgName: true } }); diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 4d7121ed3..cf987de63 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -84,7 +84,30 @@ export class EcosystemService { */ async getEcosystemDashboardDetails(ecosystemId: string): Promise { try { - return await this.ecosystemRepository.getEcosystemDashboardDetails(ecosystemId); + const endorseMemberCount = await this.ecosystemRepository.getEcosystemDashboardDetails(ecosystemId); + + const query = { + ecosystemId, + ecosystemRole: { + name: EcosystemRoles.ECOSYSTEM_LEAD + } + }; + + const ecosystemDetails = await this.ecosystemRepository.fetchEcosystemOrg( + query + ); + + const dashboardDetails = { + ecosystem: ecosystemDetails['ecosystem'], + membersCount: endorseMemberCount.membersCount, + endorsementsCount: endorseMemberCount.endorsementsCount, + ecosystemLead:{ + role: ecosystemDetails['ecosystemRole']['name'], + orgName: ecosystemDetails['orgName'] + } + }; + + return dashboardDetails; } catch (error) { this.logger.error(`In ecosystem dashboard details : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); From aed6b68ff81992a1a42e239df2497bf48dfb35a3 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Mon, 16 Oct 2023 13:02:49 +0530 Subject: [PATCH 152/162] refactor: Implemented org validation for create and invite ecosystem Signed-off-by: tipusinghaw --- .../src/ecosystem/ecosystem.service.ts | 6 ++-- .../interfaces/ecosystem.interfaces.ts | 18 ++++++++++++ apps/ecosystem/src/ecosystem.repository.ts | 28 +++++++++++++++++-- apps/ecosystem/src/ecosystem.service.ts | 16 ++++++++--- libs/common/src/response-messages/index.ts | 3 +- 5 files changed, 61 insertions(+), 10 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 39e4c10e0..2fdd48a47 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -10,6 +10,8 @@ import { GetAllEcosystemMembersDto } from './dtos/get-members.dto'; import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; import { RequestSchemaDto, RequestCredDefDto } from './dtos/request-schema.dto'; +import { CreateEcosystemDto } from './dtos/create-ecosystem-dto'; +import { EditEcosystemDto } from './dtos/edit-ecosystem-dto'; @Injectable() export class EcosystemService extends BaseService { @@ -22,7 +24,7 @@ export class EcosystemService extends BaseService { * @param createEcosystemDto * @returns Ecosystem creation success */ - async createEcosystem(createEcosystemDto): Promise { + async createEcosystem(createEcosystemDto: CreateEcosystemDto): Promise { const payload = { createEcosystemDto }; return this.sendNats(this.serviceProxy, 'create-ecosystem', payload); } @@ -32,7 +34,7 @@ export class EcosystemService extends BaseService { * @param editEcosystemDto * @returns Ecosystem creation success */ - async editEcosystem(editEcosystemDto, ecosystemId): Promise { + async editEcosystem(editEcosystemDto: EditEcosystemDto, ecosystemId:string): Promise { const payload = { editEcosystemDto, ecosystemId }; return this.sendNats(this.serviceProxy, 'edit-ecosystem', payload); } diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts index 0fc061f0a..1e7aa379b 100644 --- a/apps/ecosystem/interfaces/ecosystem.interfaces.ts +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -171,3 +171,21 @@ export interface EndorsementTransactionPayloadDetails { orgId: string; }; } + +export interface CreateEcosystem { + name: string; + + description?: string; + + tags?: string; + + userId: number; + + logo?: string; + + orgName: string; + + orgDid: string; + + orgId?: string; +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 6ffa674e9..87f1a0dbd 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -1,4 +1,4 @@ -import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; +import { BadRequestException, Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase import { credential_definition, ecosystem, ecosystem_config, ecosystem_invitations, ecosystem_orgs, ecosystem_roles, endorsement_transaction, org_agents, platform_config, schema } from '@prisma/client'; @@ -18,9 +18,9 @@ export class EcosystemRepository { ) { } /** - * Description: Get getAgentEndPoint by orgId + * Description: create ecosystem * @param createEcosystemDto - * @returns Get getAgentEndPoint details + * @returns ecosystem */ // eslint-disable-next-line camelcase async createNewEcosystem(createEcosystemDto): Promise { @@ -150,6 +150,28 @@ export class EcosystemRepository { } } + /** + * + * @param orgId + * @returns Get specific organization details from ecosystem + */ + // eslint-disable-next-line camelcase + async checkEcosystemOrgs(orgId:string): Promise { + try { + if (!orgId) { + throw new BadRequestException(ResponseMessages.ecosystem.error.invalidOrgId); + } + return this.prisma.ecosystem_orgs.findFirst({ + where: { + orgId + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + /** * * @returns Get ecosystem dashboard card count diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 4d7121ed3..730fca401 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -13,7 +13,7 @@ import { Invitation, OrgAgentType } from '@credebl/enum/enum'; import { EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; import { EcosystemMembersPayload } from '../interfaces/ecosystemMembers.interface'; -import { CredDefMessage, SaveSchema, SchemaMessage, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; +import { CreateEcosystem, CredDefMessage, SaveSchema, SchemaMessage, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; import { CommonConstants } from '@credebl/common/common.constant'; // eslint-disable-next-line camelcase @@ -37,10 +37,14 @@ export class EcosystemService { */ // eslint-disable-next-line camelcase - async createEcosystem(createEcosystemDto): Promise { + async createEcosystem(createEcosystemDto: CreateEcosystem): Promise { + const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(createEcosystemDto.orgId); + if (checkOrganization) { + throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); + }; const createEcosystem = await this.ecosystemRepository.createNewEcosystem(createEcosystemDto); if (!createEcosystem) { - throw new NotFoundException(ResponseMessages.ecosystem.error.update); + throw new NotFoundException(ResponseMessages.ecosystem.error.notCreated); } return createEcosystem; } @@ -157,7 +161,11 @@ export class EcosystemService { */ async acceptRejectEcosystemInvitations(acceptRejectInvitation: AcceptRejectEcosystemInvitationDto): Promise { try { - + const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(acceptRejectInvitation.orgId); + + if (checkOrganization) { + throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); + }; const { orgId, status, invitationId, orgName, orgDid } = acceptRejectInvitation; const invitation = await this.ecosystemRepository.getEcosystemInvitationById(invitationId); diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 679e9590c..52fb01b9d 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -232,7 +232,8 @@ export const ResponseMessages = { transactionSubmitted: 'Transaction already submitted', invalidAgentUrl: 'Invalid agent url', EndorsementTransactionNotFoundException:'Endorsement transaction with status requested not found', - OrgOrEcosystemNotFoundExceptionForEndorsementTransaction:'Cannot update endorsement transaction status as OrgId and EcosystemId is not present in ecosystemOrg' + OrgOrEcosystemNotFoundExceptionForEndorsementTransaction:'Cannot update endorsement transaction status as OrgId and EcosystemId is not present in ecosystemOrg', + ecosystemOrgAlready: 'Organization is already part of the ecosystem. Please ensure that the organization is not duplicated.' } } }; \ No newline at end of file From 1734f87f04090aade5842d44cc9b96cd49ab90e7 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Mon, 16 Oct 2023 14:59:04 +0530 Subject: [PATCH 153/162] refactor: api for passkey module Signed-off-by: bhavanakarwade --- apps/api-gateway/src/fido/fido.controller.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/api-gateway/src/fido/fido.controller.ts b/apps/api-gateway/src/fido/fido.controller.ts index 7768e98e9..ee3f066d8 100644 --- a/apps/api-gateway/src/fido/fido.controller.ts +++ b/apps/api-gateway/src/fido/fido.controller.ts @@ -172,9 +172,6 @@ export class FidoController { description: 'Internal server error', type: InternalServerErrorDto }) - // @ApiQuery( - // { name: 'credentialId', required: true } - // ) @ApiQuery( { name: 'deviceName', required: true } ) From 42b186085d0161e103c7eaa105df4939fb3d0062 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Tue, 17 Oct 2023 08:25:29 +0530 Subject: [PATCH 154/162] remove unnecessary code Signed-off-by: bhavanakarwade --- apps/user/src/fido/dtos/fido-user.dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/user/src/fido/dtos/fido-user.dto.ts b/apps/user/src/fido/dtos/fido-user.dto.ts index 2353717ab..9336e4a9c 100644 --- a/apps/user/src/fido/dtos/fido-user.dto.ts +++ b/apps/user/src/fido/dtos/fido-user.dto.ts @@ -66,7 +66,7 @@ class ResponseDto { email: string; } -// + class VerifyAuthenticationResponseDto { @ApiProperty() @IsString() From 4915ef50ea80f646839dd8b19b19501fce18ee59 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Tue, 17 Oct 2023 15:28:39 +0530 Subject: [PATCH 155/162] refactor return type Signed-off-by: bhavanakarwade --- apps/user/repositories/user-device.repository.ts | 2 +- apps/user/src/fido/fido.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/user/repositories/user-device.repository.ts b/apps/user/repositories/user-device.repository.ts index 5b45fc0a2..f5a78e691 100644 --- a/apps/user/repositories/user-device.repository.ts +++ b/apps/user/repositories/user-device.repository.ts @@ -93,7 +93,7 @@ export class UserDevicesRepository { * @returns Get all device details */ // eslint-disable-next-line camelcase, @typescript-eslint/no-explicit-any - async getfidoMultiDevice(userId: number): Promise { + async getfidoMultiDevice(userId: number): Promise { try { const fidoMultiDevice = await this.prisma.user_devices.findMany({ diff --git a/apps/user/src/fido/fido.service.ts b/apps/user/src/fido/fido.service.ts index 203f7d404..f21a8a164 100644 --- a/apps/user/src/fido/fido.service.ts +++ b/apps/user/src/fido/fido.service.ts @@ -104,7 +104,7 @@ export class FidoService { const credentialIds = []; if (fidoMultiDevice) { for (const iterator of fidoMultiDevice) { - credentialIds.push(iterator.devices.credentialID); + credentialIds.push(iterator.devices['credentialID']); } } else { throw new BadRequestException(ResponseMessages.fido.error.deviceNotFound); From 1b3a59d406f679e12ee07b9add2601f4627d2bcb Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Tue, 17 Oct 2023 19:08:36 +0530 Subject: [PATCH 156/162] refactor: Implemented auto flag handling within the ecosystem and introduced validation. (#158) * Added the auto submit flag for automatic submit the transaction Signed-off-by: KulkarniShashank * Removed unnecessary params Signed-off-by: KulkarniShashank * Error handling in the schema endorsement Signed-off-by: KulkarniShashank * feat: Schema and credDef auto sign and submit transaction Signed-off-by: KulkarniShashank * Solved issue when schema sign Signed-off-by: KulkarniShashank * Added the validation for the cedDef create and write Signed-off-by: KulkarniShashank * feat: auto endorsement flag handle and refactor schema and cred-def Signed-off-by: KulkarniShashank * Solved conflicts in endorsement service Signed-off-by: KulkarniShashank * Change the API URL for auto sign and submit flag Signed-off-by: KulkarniShashank * Modify the error messages in ecosystem Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank --- .../src/agent-service.service.ts | 13 +- .../src/interface/agent-service.interface.ts | 1 + .../repositories/agent-service.repository.ts | 2 +- .../src/ecosystem/ecosystem.controller.ts | 30 ++- .../src/ecosystem/ecosystem.service.ts | 149 +++++++------- apps/ecosystem/src/ecosystem.controller.ts | 183 +++++++++--------- apps/ecosystem/src/ecosystem.repository.ts | 58 +++++- apps/ecosystem/src/ecosystem.service.ts | 24 ++- libs/common/src/response-messages/index.ts | 9 +- 9 files changed, 289 insertions(+), 180 deletions(-) diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index 0d3f67403..557960456 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -229,7 +229,7 @@ export class AgentServiceService { socket.emit('agent-spinup-process-initiated', { clientId: agentSpinupDto.clientSocketId }); } - await this._agentSpinup(walletProvisionPayload, agentSpinupDto, orgApiKey, orgData, user, socket); + await this._agentSpinup(walletProvisionPayload, agentSpinupDto, orgApiKey, orgData, user, socket, agentSpinupDto.ledgerId); const agentStatusResponse = { agentSpinupStatus: 1 }; @@ -251,7 +251,7 @@ export class AgentServiceService { } } - async _agentSpinup(walletProvisionPayload: IWalletProvision, agentSpinupDto: IAgentSpinupDto, orgApiKey: string, orgData: organisation, user: IUserRequestInterface, socket): Promise { + async _agentSpinup(walletProvisionPayload: IWalletProvision, agentSpinupDto: IAgentSpinupDto, orgApiKey: string, orgData: organisation, user: IUserRequestInterface, socket, ledgerId: number[]): Promise { try { const agentSpinUpResponse = new Promise(async (resolve, _reject) => { @@ -288,7 +288,8 @@ export class AgentServiceService { agentsTypeId: AgentType.AFJ, orgId: orgData.id, walletName: agentSpinupDto.walletName, - clientSocketId: agentSpinupDto.clientSocketId + clientSocketId: agentSpinupDto.clientSocketId, + ledgerId }; if (agentEndPoint && agentSpinupDto.clientSocketId) { @@ -358,7 +359,8 @@ export class AgentServiceService { orgId: payload.orgId, agentEndPoint: payload.agentEndPoint, agentId: payload.agentId, - orgAgentTypeId: OrgAgentType.DEDICATED + orgAgentTypeId: OrgAgentType.DEDICATED, + ledgerId: payload.ledgerId }; @@ -543,7 +545,8 @@ export class AgentServiceService { agentEndPoint: platformAdminSpinnedUp.org_agents[0].agentEndPoint, orgAgentTypeId: OrgAgentType.SHARED, tenantId: tenantDetails.tenantRecord.id, - walletName: label + walletName: label, + ledgerId: payload.ledgerId }; if (payload.clientSocketId) { diff --git a/apps/agent-service/src/interface/agent-service.interface.ts b/apps/agent-service/src/interface/agent-service.interface.ts index d6a7d4b99..3386889bb 100644 --- a/apps/agent-service/src/interface/agent-service.interface.ts +++ b/apps/agent-service/src/interface/agent-service.interface.ts @@ -137,6 +137,7 @@ export interface IStoreOrgAgentDetails { agentId?: number; orgAgentTypeId?: OrgAgentType; tenantId?: string; + ledgerId?: number[]; } diff --git a/apps/agent-service/src/repositories/agent-service.repository.ts b/apps/agent-service/src/repositories/agent-service.repository.ts index 15d333e92..90d050471 100644 --- a/apps/agent-service/src/repositories/agent-service.repository.ts +++ b/apps/agent-service/src/repositories/agent-service.repository.ts @@ -88,7 +88,7 @@ export class AgentServiceRepository { agentId: storeOrgAgentDetails.agentId ? storeOrgAgentDetails.agentId : null, orgAgentTypeId: storeOrgAgentDetails.orgAgentTypeId ? storeOrgAgentDetails.orgAgentTypeId : null, tenantId: storeOrgAgentDetails.tenantId ? storeOrgAgentDetails.tenantId : null, - ledgerId: 1 + ledgerId: storeOrgAgentDetails.ledgerId[0] } }); } catch (error) { diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 79fb14575..c6e037a0d 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -106,7 +106,6 @@ export class EcosystemController { @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) @ApiBearerAuth() - async getEcosystemDashboardDetails(@Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: string, @Res() res: Response): Promise { const getEcosystemDetails = await this.ecosystemService.getEcosystemDashboardDetails(ecosystemId, orgId); @@ -244,7 +243,7 @@ export class EcosystemController { return res.status(HttpStatus.OK).json(finalResponse); } - + /** * * @param createOrgDto @@ -310,7 +309,7 @@ export class EcosystemController { @ApiBearerAuth() @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - async SignEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: number, @Res() res: Response): Promise { + async SignEndorsementRequests(@Param('endorsementId') endorsementId: string, @Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: number, @Res() res: Response): Promise { await this.ecosystemService.signTransaction(endorsementId, ecosystemId); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -369,6 +368,31 @@ export class EcosystemController { } + /** + * + * @param res + * @returns + */ + @Put('transaction/endorsement/auto') + @ApiOperation({ + summary: 'Auto sign and submit transactions', + description: 'Auto sign and submit transactions' + }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + async autoSignAndSubmitTransaction( + @Res() res: Response + ): Promise { + await this.ecosystemService.autoSignAndSubmitTransaction(); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.AutoEndorsementTransaction + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + /** * * @param acceptRejectEcosystemInvitation diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 2fdd48a47..1ad6f07af 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -48,7 +48,7 @@ export class EcosystemService extends BaseService { const payload = { orgId }; return this.sendNats(this.serviceProxy, 'get-all-ecosystem', payload); } - + /** * * @@ -67,8 +67,8 @@ export class EcosystemService extends BaseService { * @returns */ async createInvitation(bulkInvitationDto: BulkEcosystemInvitationDto, userId: string): Promise { - const payload = { bulkInvitationDto, userId }; - return this.sendNats(this.serviceProxy, 'send-ecosystem-invitation', payload); + const payload = { bulkInvitationDto, userId }; + return this.sendNats(this.serviceProxy, 'send-ecosystem-invitation', payload); } async getInvitationsByEcosystemId( @@ -80,7 +80,7 @@ export class EcosystemService extends BaseService { const payload = { ecosystemId, pageNumber, pageSize, search, userId }; return this.sendNats(this.serviceProxy, 'get-sent-invitations-ecosystemId', payload); } - + /** * * @returns Ecosystem members @@ -92,84 +92,89 @@ export class EcosystemService extends BaseService { const { pageNumber, pageSize, search } = getEcosystemMembers; const payload = { ecosystemId, pageNumber, pageSize, search }; return this.sendNats(this.serviceProxy, 'fetch-ecosystem-members', payload); - } + } /** * * @returns Ecosystem Invitations details */ - async getEcosystemInvitations( - getAllInvitationsDto: GetAllSentEcosystemInvitationsDto, - userEmail: string, - status: string - ): Promise<{ response: object }> { - const { pageNumber, pageSize, search } = getAllInvitationsDto; - const payload = { userEmail, status, pageNumber, pageSize, search }; - return this.sendNats(this.serviceProxy, 'get-ecosystem-invitations', payload); - } + async getEcosystemInvitations( + getAllInvitationsDto: GetAllSentEcosystemInvitationsDto, + userEmail: string, + status: string + ): Promise<{ response: object }> { + const { pageNumber, pageSize, search } = getAllInvitationsDto; + const payload = { userEmail, status, pageNumber, pageSize, search }; + return this.sendNats(this.serviceProxy, 'get-ecosystem-invitations', payload); + } async deleteEcosystemInvitations( invitationId: string - ): Promise { + ): Promise { const payload = { invitationId }; return this.sendNats(this.serviceProxy, 'delete-ecosystem-invitations', payload); } - async acceptRejectEcosystemInvitaion( - acceptRejectInvitation: AcceptRejectEcosystemInvitationDto, - userEmail: string - ): Promise<{ response: string }> { - const payload = { acceptRejectInvitation, userEmail }; - return this.sendNats(this.serviceProxy, 'accept-reject-ecosystem-invitations', payload); - } - - - async fetchEcosystemOrg( - ecosystemId: string, - orgId: string - ): Promise<{ response: object }> { - const payload = { ecosystemId, orgId }; - return this.sendNats(this.serviceProxy, 'fetch-ecosystem-org-data', payload); - } - - async getEndorsementTranasactions( - ecosystemId: string, - orgId: string, - getAllEndorsements: GetAllEndorsementsDto - ): Promise<{ response: object }> { - const { pageNumber, pageSize, search, type } = getAllEndorsements; - const payload = { ecosystemId, orgId, pageNumber, pageSize, search, type }; - return this.sendNats(this.serviceProxy, 'get-endorsement-transactions', payload); - } - - - async schemaEndorsementRequest(requestSchemaPayload: RequestSchemaDto, orgId: number, ecosystemId:string): Promise { - const payload = { requestSchemaPayload, orgId, ecosystemId}; - return this.sendNats(this.serviceProxy, 'schema-endorsement-request', payload); - } - - async credDefEndorsementRequest(requestCredDefPayload: RequestCredDefDto, orgId: number, ecosystemId:string): Promise { - const payload = { requestCredDefPayload, orgId, ecosystemId}; - return this.sendNats(this.serviceProxy, 'credDef-endorsement-request', payload); - } - - async signTransaction(endorsementId:string, ecosystemId:string): Promise { - const payload = { endorsementId, ecosystemId }; - return this.sendNats(this.serviceProxy, 'sign-endorsement-transaction', payload); - } - - async submitTransaction(endorsementId:string, ecosystemId:string): Promise { - const payload = { endorsementId, ecosystemId }; - return this.sendNats(this.serviceProxy, 'sumbit-endorsement-transaction', payload); - } - - async declineEndorsementRequestByLead( - ecosystemId: string, - endorsementId: string, - orgId: string - ): Promise<{ response: object }> { - const payload = { ecosystemId, endorsementId, orgId }; - return this.sendNats(this.serviceProxy, 'decline-endorsement-transaction', payload); - } - + async acceptRejectEcosystemInvitaion( + acceptRejectInvitation: AcceptRejectEcosystemInvitationDto, + userEmail: string + ): Promise<{ response: string }> { + const payload = { acceptRejectInvitation, userEmail }; + return this.sendNats(this.serviceProxy, 'accept-reject-ecosystem-invitations', payload); + } + + + async fetchEcosystemOrg( + ecosystemId: string, + orgId: string + ): Promise<{ response: object }> { + const payload = { ecosystemId, orgId }; + return this.sendNats(this.serviceProxy, 'fetch-ecosystem-org-data', payload); + } + + async getEndorsementTranasactions( + ecosystemId: string, + orgId: string, + getAllEndorsements: GetAllEndorsementsDto + ): Promise<{ response: object }> { + const { pageNumber, pageSize, search, type } = getAllEndorsements; + const payload = { ecosystemId, orgId, pageNumber, pageSize, search, type }; + return this.sendNats(this.serviceProxy, 'get-endorsement-transactions', payload); + } + + + async schemaEndorsementRequest(requestSchemaPayload: RequestSchemaDto, orgId: number, ecosystemId: string): Promise { + const payload = { requestSchemaPayload, orgId, ecosystemId }; + return this.sendNats(this.serviceProxy, 'schema-endorsement-request', payload); + } + + async credDefEndorsementRequest(requestCredDefPayload: RequestCredDefDto, orgId: number, ecosystemId: string): Promise { + const payload = { requestCredDefPayload, orgId, ecosystemId }; + return this.sendNats(this.serviceProxy, 'credDef-endorsement-request', payload); + } + + async signTransaction(endorsementId: string, ecosystemId: string): Promise { + const payload = { endorsementId, ecosystemId }; + return this.sendNats(this.serviceProxy, 'sign-endorsement-transaction', payload); + } + + async submitTransaction(endorsementId: string, ecosystemId: string): Promise { + const payload = { endorsementId, ecosystemId }; + return this.sendNats(this.serviceProxy, 'sumbit-endorsement-transaction', payload); + } + + async autoSignAndSubmitTransaction(): Promise<{ response: object }> { + const payload = {}; + return this.sendNats(this.serviceProxy, 'auto-endorsement-transaction', payload); + } + + async declineEndorsementRequestByLead( + ecosystemId: string, + endorsementId: string, + orgId: string + ): Promise<{ response: object }> { + const payload = { ecosystemId, endorsementId, orgId }; + return this.sendNats(this.serviceProxy, 'decline-endorsement-transaction', payload); + } + } diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 524d499c0..f17c28d1b 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'); /** @@ -31,7 +31,7 @@ export class EcosystemController { * @param payload updation Details * @returns Get updated ecosystem details */ - @MessagePattern({ cmd: 'edit-ecosystem' }) + @MessagePattern({ cmd: 'edit-ecosystem' }) async editEcosystem(@Body() payload: { editEcosystemDto, ecosystemId }): Promise { return this.ecosystemService.editEcosystem(payload.editEcosystemDto, payload.ecosystemId); } @@ -43,7 +43,7 @@ export class EcosystemController { */ @MessagePattern({ cmd: 'get-all-ecosystem' }) async getAllEcosystems( - @Body() payload: {orgId: string} + @Body() payload: { orgId: string } ): Promise { return this.ecosystemService.getAllEcosystem(payload); } @@ -63,18 +63,18 @@ export class EcosystemController { * 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 } - ): Promise { - return this.ecosystemService.getEcosystemInvitations( - payload.userEmail, - payload.status, - payload.pageNumber, - payload.pageSize, - payload.search - ); - } + @MessagePattern({ cmd: 'get-ecosystem-invitations' }) + async getEcosystemInvitations( + @Body() payload: { userEmail: string, status: string; pageNumber: number; pageSize: number; search: string } + ): Promise { + return this.ecosystemService.getEcosystemInvitations( + payload.userEmail, + payload.status, + payload.pageNumber, + payload.pageSize, + payload.search + ); + } /** * @@ -98,7 +98,7 @@ export class EcosystemController { @MessagePattern({ cmd: 'send-ecosystem-invitation' }) async createInvitation( @Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: string } - ): Promise { + ): Promise { return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId); } @@ -127,89 +127,98 @@ export class EcosystemController { @MessagePattern({ cmd: 'get-endorsement-transactions' }) async getEndorsementTransactions( @Body() payload: GetEndorsementsPayload - ): Promise { + ): Promise { return this.ecosystemService.getEndorsementTransactions( payload ); - } + } @MessagePattern({ cmd: 'delete-ecosystem-invitations' }) async deleteInvitation( - @Body() payload: {invitationId: string} - ): Promise { + @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} + @Body() payload: { ecosystemId: string, orgId: string } ): Promise { - return this.ecosystemService.fetchEcosystemOrg( - payload - ); + return this.ecosystemService.fetchEcosystemOrg( + payload + ); + } + + /** + * + * @param payload + * @returns Schema endorsement request + */ + @MessagePattern({ cmd: 'schema-endorsement-request' }) + async schemaEndorsementRequest( + @Body() payload: { requestSchemaPayload: RequestSchemaEndorsement; orgId: number, ecosystemId: string } + ): Promise { + return this.ecosystemService.requestSchemaEndorsement(payload.requestSchemaPayload, payload.orgId, payload.ecosystemId); + } + + /** + * + * @param payload + * @returns Schema endorsement request + */ + @MessagePattern({ cmd: 'credDef-endorsement-request' }) + async credDefEndorsementRequest( + @Body() payload: { requestCredDefPayload: RequestCredDeffEndorsement; orgId: number; ecosystemId: string } + ): Promise { + return this.ecosystemService.requestCredDeffEndorsement(payload.requestCredDefPayload, payload.orgId, payload.ecosystemId); + } + + /** + * + * @param payload + * @returns sign endorsement request + */ + @MessagePattern({ cmd: 'sign-endorsement-transaction' }) + async signTransaction( + @Body() payload: { endorsementId: string, ecosystemId: string } + ): Promise { + return this.ecosystemService.signTransaction(payload.endorsementId, payload.ecosystemId); + } + + /** + * + * @param payload + * @returns submit endorsement request + */ + @MessagePattern({ cmd: 'sumbit-endorsement-transaction' }) + async submitTransaction( + @Body() payload: { endorsementId: string, ecosystemId: string } + ): Promise { + return this.ecosystemService.submitTransaction(payload.endorsementId, payload.ecosystemId); + } + + /** + * + * @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 + */ + @MessagePattern({ cmd: 'decline-endorsement-transaction' }) + async declineEndorsementRequestByLead(payload: { + ecosystemId: string, endorsementId: string + }): Promise { + return this.ecosystemService.declineEndorsementRequestByLead(payload.ecosystemId, payload.endorsementId); } - - /** - * - * @param payload - * @returns Schema endorsement request - */ - @MessagePattern({ cmd: 'schema-endorsement-request' }) - async schemaEndorsementRequest( - @Body() payload: { requestSchemaPayload: RequestSchemaEndorsement; orgId: number, ecosystemId: string } - ): Promise { - return this.ecosystemService.requestSchemaEndorsement(payload.requestSchemaPayload, payload.orgId, payload.ecosystemId); - } - - /** - * - * @param payload - * @returns Schema endorsement request - */ - @MessagePattern({ cmd: 'credDef-endorsement-request' }) - async credDefEndorsementRequest( - @Body() payload: { requestCredDefPayload: RequestCredDeffEndorsement; orgId: number; ecosystemId:string} - ): Promise { - return this.ecosystemService.requestCredDeffEndorsement(payload.requestCredDefPayload, payload.orgId, payload.ecosystemId); - } - - /** - * - * @param payload - * @returns sign endorsement request - */ - @MessagePattern({ cmd: 'sign-endorsement-transaction' }) - async signTransaction( - @Body() payload: { endorsementId: string, ecosystemId:string } - ): Promise { - return this.ecosystemService.signTransaction(payload.endorsementId, payload.ecosystemId); - } - - /** - * - * @param payload - * @returns submit endorsement request - */ - @MessagePattern({ cmd: 'sumbit-endorsement-transaction' }) - async submitTransaction( - @Body() payload: { endorsementId: string, ecosystemId:string } - ): Promise { - return this.ecosystemService.submitTransaction(payload.endorsementId, payload.ecosystemId); - } - - - /** - * - * @param payload - * @returns Declien Endorsement Transaction status - */ - @MessagePattern({ cmd: 'decline-endorsement-transaction' }) - async declineEndorsementRequestByLead(payload: { - ecosystemId:string, endorsementId:string - }): Promise { - return this.ecosystemService.declineEndorsementRequestByLead(payload.ecosystemId, payload.endorsementId); - } } diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts index 7c4e40a2c..96c265473 100644 --- a/apps/ecosystem/src/ecosystem.repository.ts +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -7,6 +7,7 @@ import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; import { SaveSchema, SchemaTransactionResponse, saveCredDef } from '../interfaces/ecosystem.interfaces'; import { ResponseMessages } from '@credebl/common/response-messages'; import { NotFoundException } from '@nestjs/common'; +import { CommonConstants } from '@credebl/common/common.constant'; // eslint-disable-next-line camelcase @Injectable() @@ -156,7 +157,7 @@ export class EcosystemRepository { * @returns Get specific organization details from ecosystem */ // eslint-disable-next-line camelcase - async checkEcosystemOrgs(orgId:string): Promise { + async checkEcosystemOrgs(orgId: string): Promise { try { if (!orgId) { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidOrgId); @@ -176,14 +177,16 @@ export class EcosystemRepository { * * @returns Get ecosystem dashboard card count */ - - async getEcosystemDashboardDetails(ecosystemId: string): Promise<{membersCount: number; endorsementsCount: number}> { + // eslint-disable-next-line camelcase + async getEcosystemDashboardDetails(ecosystemId: string): Promise<{ membersCount: number; endorsementsCount: number; ecosystemConfigData: ecosystem_config[] }> { try { const membersCount = await this.getEcosystemMembersCount(ecosystemId); const endorsementsCount = await this.getEcosystemEndorsementsCount(ecosystemId); + const ecosystemConfigData = await this.getEcosystemConfig(); return { membersCount, - endorsementsCount + endorsementsCount, + ecosystemConfigData }; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); @@ -191,6 +194,16 @@ export class EcosystemRepository { } } + // eslint-disable-next-line camelcase + async getEcosystemConfig(): Promise { + try { + const getEcosystemConfigDetails = await this.prisma.ecosystem_config.findMany(); + return getEcosystemConfigDetails; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } async getEcosystemMembersCount(ecosystemId: string): Promise { try { @@ -866,4 +879,41 @@ export class EcosystemRepository { throw error; } } + + async updateAutoSignAndSubmitTransaction(): Promise<{ + id: string; + key: string; + value: string; + createDateTime: Date; + createdBy: string; + lastChangedDateTime: Date; + lastChangedBy: string; + deletedAt: Date; + }> { + try { + + const { id, value } = await this.prisma.ecosystem_config.findFirst({ + where: { + key: `${CommonConstants.ECOSYSTEM_AUTO_ENDOSEMENT}` + } + }); + + const updatedValue = 'false' === value ? 'true' : 'false'; + + const updateEcosystemConfig = await this.prisma.ecosystem_config.update({ + where: { + id + }, + data: { + value: updatedValue + } + }); + + return updateEcosystemConfig; + + } catch (error) { + this.logger.error(`Error in update auto sign and submit flag: ${error.message}`); + throw error; + } + } } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 5b18da802..503685d61 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -107,7 +107,8 @@ export class EcosystemService { endorsementsCount: endorseMemberCount.endorsementsCount, ecosystemLead:{ role: ecosystemDetails['ecosystemRole']['name'], - orgName: ecosystemDetails['orgName'] + orgName: ecosystemDetails['orgName'], + config: endorseMemberCount.ecosystemConfigData } }; @@ -563,7 +564,7 @@ export class EcosystemService { if (updateSignedTransaction && 'true' === ecosystemConfigDetails.value) { - const submitTxn = await this.submitTransaction(endorsementId, ecosystemId); + const submitTxn = await this.submitTransaction(endorsementId, ecosystemId, ecosystemLeadAgentDetails.agentEndPoint); if (!submitTxn) { await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.REQUESTED); throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); @@ -730,7 +731,7 @@ export class EcosystemService { return this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SUBMITED); } - async submitTransaction(endorsementId, ecosystemId): Promise { + async submitTransaction(endorsementId, ecosystemId, ecosystemLeadAgentEndPoint?): Promise { try { const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.SIGNED); @@ -745,7 +746,9 @@ export class EcosystemService { const ecosystemMemberDetails = await this.getEcosystemMemberDetails(endorsementTransactionPayload); const ecosystemLeadAgentDetails = await this.getEcosystemLeadAgentDetails(ecosystemId); const platformConfig = await this.getPlatformConfig(); - const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, ecosystemMemberDetails.agentEndPoint, endorsementTransactionType.SUBMIT, ecosystemMemberDetails?.tenantId); + + const agentEndPoint = ecosystemLeadAgentEndPoint ? ecosystemLeadAgentEndPoint : ecosystemMemberDetails.agentEndPoint; + const url = await this.getAgentUrl(ecosystemMemberDetails?.orgAgentTypeId, agentEndPoint, endorsementTransactionType.SUBMIT, ecosystemMemberDetails?.tenantId); const payload = await this.submitTransactionPayload(endorsementTransactionPayload, ecosystemMemberDetails, ecosystemLeadAgentDetails); const submitTransactionRequest = await this._submitTransaction(payload, url, platformConfig.sgApiKey); @@ -928,6 +931,19 @@ export class EcosystemService { } } + /** + * @returns EndorsementTransaction Status message + */ + + async autoSignAndSubmitTransaction(): Promise { + try { + + return await this.ecosystemRepository.updateAutoSignAndSubmitTransaction(); + } catch (error) { + this.logger.error(`error in decline endorsement request: ${error}`); + throw new InternalServerErrorException(error); + } + } /** * diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 52fb01b9d..e713dc8cc 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -198,7 +198,8 @@ export const ResponseMessages = { invitationReject: 'Ecosystem invitation rejected', invitationAccept: 'Ecosystem invitation accepted successfully', fetchEndorsors: 'Endorser transactions fetched successfully', - DeclineEndorsementTransaction:'Decline endorsement request successfully', + DeclineEndorsementTransaction: 'Decline endorsement request successfully', + AutoEndorsementTransaction: 'The flag for transactions has been successfully set', fetchMembers: 'Ecosystem members fetched successfully' }, error: { @@ -231,9 +232,9 @@ export const ResponseMessages = { invalidTransaction: 'Transaction does not exist', transactionSubmitted: 'Transaction already submitted', invalidAgentUrl: 'Invalid agent url', - EndorsementTransactionNotFoundException:'Endorsement transaction with status requested not found', - OrgOrEcosystemNotFoundExceptionForEndorsementTransaction:'Cannot update endorsement transaction status as OrgId and EcosystemId is not present in ecosystemOrg', + EndorsementTransactionNotFoundException: 'Endorsement transaction with status requested not found', + OrgOrEcosystemNotFoundExceptionForEndorsementTransaction: 'The endorsement transaction status cant be updated', ecosystemOrgAlready: 'Organization is already part of the ecosystem. Please ensure that the organization is not duplicated.' } - } + } }; \ No newline at end of file From 6e167d5ea2a382d44ca3ad602569dd34fe4383b2 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Wed, 18 Oct 2023 15:46:32 +0530 Subject: [PATCH 157/162] refactor: send ecosystem invitations link Signed-off-by: bhavanakarwade --- .../src/ecosystem/ecosystem.controller.ts | 2 +- apps/ecosystem/src/ecosystem.service.ts | 36 ++++++++++++++++--- .../templates/EcosystemInviteTemplate.ts | 11 ++++-- libs/common/src/send-grid-helper-file.ts | 2 +- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index c6e037a0d..d9a8bf1a0 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -209,7 +209,7 @@ export class EcosystemController { */ @Get('/:ecosystemId/:orgId/members') @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 503685d61..18f75e7c3 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -17,7 +17,7 @@ import { CreateEcosystem, CredDefMessage, SaveSchema, SchemaMessage, SignedTrans import { 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 } from '@prisma/client'; +import { credential_definition, org_agents, platform_config, schema, user } from '@prisma/client'; @Injectable() @@ -158,13 +158,14 @@ export class EcosystemService { for (const invitation of invitations) { const { email } = invitation; + const isUserExist = await this.checkUserExistInPlatform(email); + const isInvitationExist = await this.checkInvitationExist(email, ecosystemId); if (!isInvitationExist) { await this.ecosystemRepository.createSendInvitation(email, ecosystemId, userId); - try { - await this.sendInviteEmailTemplate(email, ecosystemDetails.name); + await this.sendInviteEmailTemplate(email, ecosystemDetails.name, isUserExist); } catch (error) { throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); } @@ -293,7 +294,8 @@ export class EcosystemService { */ async sendInviteEmailTemplate( email: string, - ecosystemName: string + ecosystemName: string, + isUserExist: boolean ): Promise { const platformConfigData = await this.prisma.platform_config.findMany(); @@ -303,7 +305,7 @@ export class EcosystemService { emailData.emailTo = email; emailData.emailSubject = `${process.env.PLATFORM_NAME} Platform: Invitation`; - emailData.emailHtml = await urlEmailTemplate.sendInviteEmailTemplate(email, ecosystemName); + emailData.emailHtml = await urlEmailTemplate.sendInviteEmailTemplate(email, ecosystemName, isUserExist); //Email is sent to user for the verification through emailData const isEmailSent = await sendEmail(emailData); @@ -311,6 +313,30 @@ export class EcosystemService { return isEmailSent; } + async checkUserExistInPlatform(email: string): Promise { + const pattern = { cmd: 'get-user-by-mail' }; + const payload = { email }; + + const userData: user = await this.ecosystemServiceProxy + .send(pattern, payload) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); + + if (userData && userData.isEmailVerified) { + return true; + } + return false; + } + /** * * @param RequestSchemaEndorsement diff --git a/apps/ecosystem/templates/EcosystemInviteTemplate.ts b/apps/ecosystem/templates/EcosystemInviteTemplate.ts index 2f872654a..1486b1db4 100644 --- a/apps/ecosystem/templates/EcosystemInviteTemplate.ts +++ b/apps/ecosystem/templates/EcosystemInviteTemplate.ts @@ -2,12 +2,17 @@ export class EcosystemInviteTemplate { public sendInviteEmailTemplate( email: string, - ecosystemName: string + ecosystemName: string, + isUserExist = false ): string { - const validUrl = `${process.env.FRONT_END_URL}/authentication/sign-in`; + const validUrl = isUserExist ? `${process.env.FRONT_END_URL}/authentication/sign-in` : `${process.env.FRONT_END_URL}/authentication/sign-up`; + + const message = isUserExist + ? `You have already registered on platform, you can access the application. + Please log in and accept the ecosystem “INVITATION” and participate in the ecosystem` + : `You have to register on the platform and then you can access the application. Accept the ecosystem “INVITATION” and participate in the ecosystem`; - const message = `You have been invited to join the ecosystem so please log in and accept the ecosystem “INVITATION” and participate in the ecosystem`; const year: number = new Date().getFullYear(); return ` diff --git a/libs/common/src/send-grid-helper-file.ts b/libs/common/src/send-grid-helper-file.ts index a98bd518b..623d118e1 100644 --- a/libs/common/src/send-grid-helper-file.ts +++ b/libs/common/src/send-grid-helper-file.ts @@ -18,7 +18,7 @@ export const sendEmail = async (EmailDto: EmailDto): Promise => { html: EmailDto.emailHtml, attachments: EmailDto.emailAttachments }; - return await sendgrid.send(msg).then(() => true).catch(() => false) + return await sendgrid.send(msg).then(() => true).catch(() => false); } catch (error) { return false; From 6533458f460b30a1a3806147041488beff59cedc Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Wed, 18 Oct 2023 16:00:54 +0530 Subject: [PATCH 158/162] fix: implemented request body in credential definition request Signed-off-by: tipusinghaw --- .../src/ecosystem/dtos/request-schema.dto.ts | 49 +++++++++++++++++-- .../interfaces/ecosystem.interfaces.ts | 1 + apps/ecosystem/src/ecosystem.controller.ts | 12 ++--- apps/ecosystem/src/ecosystem.service.ts | 15 ++---- 4 files changed, 55 insertions(+), 22 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts b/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts index 0d0598f1f..bbd0aad6a 100644 --- a/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts +++ b/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts @@ -1,5 +1,6 @@ import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { Type } from 'class-transformer'; +import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator'; @ApiExtraModels() class AttributeValue { @@ -17,6 +18,17 @@ class AttributeValue { displayName: string; } +class CredDefSchemaDetails { + + @IsString() + @IsNotEmpty({ message: 'attributeName is required.' }) + attributeName: string; + + @IsString() + @IsNotEmpty({ message: 'schemaDataType is required.' }) + schemaDataType: string; +} + export class RequestSchemaDto { @ApiProperty() @IsString({ message: 'name must be in string format.' }) @@ -46,6 +58,32 @@ export class RequestSchemaDto { } +export class SchemaDetails { + @ApiProperty() + @IsString({ message: 'name must be a string.' }) + name: string; + + @ApiProperty() + @IsString({ message: 'version must be a string.' }) + version: string; + + @ApiProperty({ + example: [ + { + attributeName: 'name', + schemaDataType: 'string', + displayName: 'Name' + } + ] + }) + @IsArray({ message: 'attributes must be an array.' }) + @IsNotEmpty({ message: 'please provide valid attributes.' }) + @ValidateNested({ each: true }) + @Type(() => CredDefSchemaDetails) + attributes: CredDefSchemaDetails[]; + +} + export class RequestCredDefDto { @ApiProperty() @IsBoolean({ message: 'endorse must be a boolean.' }) @@ -53,10 +91,15 @@ export class RequestCredDefDto { endorse?: boolean; @ApiProperty() - @IsString({ message: 'tag must be in string format.' }) + @IsString({ message: 'tag must be a string.' }) tag: string; @ApiProperty() - @IsString({ message: 'schemaId must be in string format.' }) + @IsString({ message: 'schemaId must be a string.' }) schemaId: string; + + @ApiProperty() + @ValidateNested() + @Type(() => SchemaDetails) + schemaDetails: SchemaDetails; } \ No newline at end of file diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts index 1e7aa379b..e77392db7 100644 --- a/apps/ecosystem/interfaces/ecosystem.interfaces.ts +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -17,6 +17,7 @@ export interface RequestCredDeffEndorsement { schemaId: string tag: string; endorse?: boolean; + schemaDetails?: object; } export interface IAttributeValue { diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index f17c28d1b..77469fb10 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -156,8 +156,7 @@ export class EcosystemController { * @returns Schema endorsement request */ @MessagePattern({ cmd: 'schema-endorsement-request' }) - async schemaEndorsementRequest( - @Body() payload: { requestSchemaPayload: RequestSchemaEndorsement; orgId: number, ecosystemId: string } + async schemaEndorsementRequest(payload: { requestSchemaPayload: RequestSchemaEndorsement; orgId: number, ecosystemId: string } ): Promise { return this.ecosystemService.requestSchemaEndorsement(payload.requestSchemaPayload, payload.orgId, payload.ecosystemId); } @@ -168,8 +167,7 @@ export class EcosystemController { * @returns Schema endorsement request */ @MessagePattern({ cmd: 'credDef-endorsement-request' }) - async credDefEndorsementRequest( - @Body() payload: { requestCredDefPayload: RequestCredDeffEndorsement; orgId: number; ecosystemId: string } + async credDefEndorsementRequest(payload: { requestCredDefPayload: RequestCredDeffEndorsement; orgId: number; ecosystemId: string } ): Promise { return this.ecosystemService.requestCredDeffEndorsement(payload.requestCredDefPayload, payload.orgId, payload.ecosystemId); } @@ -180,8 +178,7 @@ export class EcosystemController { * @returns sign endorsement request */ @MessagePattern({ cmd: 'sign-endorsement-transaction' }) - async signTransaction( - @Body() payload: { endorsementId: string, ecosystemId: string } + async signTransaction(payload: { endorsementId: string, ecosystemId: string } ): Promise { return this.ecosystemService.signTransaction(payload.endorsementId, payload.ecosystemId); } @@ -192,8 +189,7 @@ export class EcosystemController { * @returns submit endorsement request */ @MessagePattern({ cmd: 'sumbit-endorsement-transaction' }) - async submitTransaction( - @Body() payload: { endorsementId: string, ecosystemId: string } + async submitTransaction(payload: { endorsementId: string, ecosystemId: string } ): Promise { return this.ecosystemService.submitTransaction(payload.endorsementId, payload.ecosystemId); } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 503685d61..dbd068b07 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -13,7 +13,7 @@ import { Invitation, OrgAgentType } from '@credebl/enum/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, SaveSchema, SchemaMessage, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; +import { CreateEcosystem, CredDefMessage, RequestCredDeffEndorsement, RequestSchemaEndorsement, SaveSchema, SchemaMessage, SignedTransactionMessage, saveCredDef, submitTransactionPayload } from '../interfaces/ecosystem.interfaces'; import { GetEndorsementsPayload } from '../interfaces/endorsements.interface'; import { CommonConstants } from '@credebl/common/common.constant'; // eslint-disable-next-line camelcase @@ -316,7 +316,7 @@ export class EcosystemService { * @param RequestSchemaEndorsement * @returns */ - async requestSchemaEndorsement(requestSchemaPayload, orgId, ecosystemId): Promise { + async requestSchemaEndorsement(requestSchemaPayload:RequestSchemaEndorsement, orgId: number, ecosystemId: string): Promise { try { const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); @@ -386,7 +386,7 @@ export class EcosystemService { } } - async requestCredDeffEndorsement(requestCredDefPayload, orgId, ecosystemId): Promise { + async requestCredDeffEndorsement(requestCredDefPayload:RequestCredDeffEndorsement, orgId:number, ecosystemId:string): Promise { try { const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); @@ -439,12 +439,6 @@ export class EcosystemService { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); } - const requestBody = credDefTransactionRequest.message.credentialDefinitionState.credentialDefinition; - - if (!requestBody) { - throw new NotFoundException(ResponseMessages.ecosystem.error.credentialDefinitionNotFound); - } - const schemaTransactionResponse = { endorserDid: ecosystemLeadAgentDetails.orgDid, authorDid: ecosystemMemberDetails.orgDid, @@ -453,7 +447,7 @@ export class EcosystemService { ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id }; - return this.ecosystemRepository.storeTransactionRequest(schemaTransactionResponse, requestBody, 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)}`); throw new RpcException(error.response || error); @@ -734,7 +728,6 @@ export class EcosystemService { async submitTransaction(endorsementId, ecosystemId, ecosystemLeadAgentEndPoint?): Promise { try { const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.SIGNED); - if (!endorsementTransactionPayload) { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidTransaction); } From 2bdac3c4950ec9978947cb2abbb810a8ab9ad5f0 Mon Sep 17 00:00:00 2001 From: tipusinghaw Date: Wed, 18 Oct 2023 17:53:12 +0530 Subject: [PATCH 159/162] fix: added attributes as array of string Signed-off-by: tipusinghaw --- .../src/ecosystem/dtos/request-schema.dto.ts | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts b/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts index bbd0aad6a..202857f57 100644 --- a/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts +++ b/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts @@ -18,16 +18,6 @@ class AttributeValue { displayName: string; } -class CredDefSchemaDetails { - - @IsString() - @IsNotEmpty({ message: 'attributeName is required.' }) - attributeName: string; - - @IsString() - @IsNotEmpty({ message: 'schemaDataType is required.' }) - schemaDataType: string; -} export class RequestSchemaDto { @ApiProperty() @@ -68,19 +58,11 @@ export class SchemaDetails { version: string; @ApiProperty({ - example: [ - { - attributeName: 'name', - schemaDataType: 'string', - displayName: 'Name' - } - ] + example: ['name', 'id'] }) @IsArray({ message: 'attributes must be an array.' }) @IsNotEmpty({ message: 'please provide valid attributes.' }) - @ValidateNested({ each: true }) - @Type(() => CredDefSchemaDetails) - attributes: CredDefSchemaDetails[]; + attributes: string[]; } @@ -100,6 +82,7 @@ export class RequestCredDefDto { @ApiProperty() @ValidateNested() + @IsOptional() @Type(() => SchemaDetails) - schemaDetails: SchemaDetails; + schemaDetails?: SchemaDetails; } \ No newline at end of file From d6bb2638a55c7c913c8750a90742f3ca9112c02e Mon Sep 17 00:00:00 2001 From: Nishad Shirsat <103021375+nishad-ayanworks@users.noreply.github.com> Date: Thu, 19 Oct 2023 12:03:33 +0530 Subject: [PATCH 160/162] fix: send invitation flow for organization & ecosystem (#179) * fix: invitation flow Signed-off-by: Nishad * resolved the comments on PR Signed-off-by: Nishad --------- Signed-off-by: Nishad --- .../src/ecosystem/ecosystem.controller.ts | 2 +- .../src/ecosystem/ecosystem.service.ts | 4 ++-- .../organization/organization.controller.ts | 2 +- .../src/organization/organization.service.ts | 4 ++-- apps/ecosystem/src/ecosystem.controller.ts | 4 ++-- apps/ecosystem/src/ecosystem.service.ts | 19 +++++++++++++--- .../src/organization.controller.ts | 4 ++-- apps/organization/src/organization.service.ts | 22 +++++++++++++++---- libs/common/src/response-messages/index.ts | 4 ++-- 9 files changed, 46 insertions(+), 19 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index d9a8bf1a0..f8212a7fe 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -357,7 +357,7 @@ export class EcosystemController { @User() user: user, @Res() res: Response): Promise { bulkInvitationDto.ecosystemId = ecosystemId; - await this.ecosystemService.createInvitation(bulkInvitationDto, String(user.id)); + await this.ecosystemService.createInvitation(bulkInvitationDto, String(user.id), user.email); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 1ad6f07af..3a7e6b2e7 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -66,8 +66,8 @@ export class EcosystemService extends BaseService { * @param userId * @returns */ - async createInvitation(bulkInvitationDto: BulkEcosystemInvitationDto, userId: string): Promise { - const payload = { bulkInvitationDto, userId }; + async createInvitation(bulkInvitationDto: BulkEcosystemInvitationDto, userId: string, userEmail: string): Promise { + const payload = { bulkInvitationDto, userId, userEmail }; return this.sendNats(this.serviceProxy, 'send-ecosystem-invitation', payload); } diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 667c7bb26..a4a796895 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -298,7 +298,7 @@ export class OrganizationController { async createInvitation(@Body() bulkInvitationDto: BulkSendInvitationDto, @Param('orgId') orgId: number, @User() user: user, @Res() res: Response): Promise { bulkInvitationDto.orgId = orgId; - await this.organizationService.createInvitation(bulkInvitationDto, user.id); + await this.organizationService.createInvitation(bulkInvitationDto, user.id, user.email); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, diff --git a/apps/api-gateway/src/organization/organization.service.ts b/apps/api-gateway/src/organization/organization.service.ts index c7579dcf5..2af36e772 100644 --- a/apps/api-gateway/src/organization/organization.service.ts +++ b/apps/api-gateway/src/organization/organization.service.ts @@ -109,8 +109,8 @@ export class OrganizationService extends BaseService { * @param sendInvitationDto * @returns Organization invitation creation Success */ - async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: number): Promise { - const payload = { bulkInvitationDto, userId }; + async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: number, userEmail: string): Promise { + const payload = { bulkInvitationDto, userId, userEmail }; return this.sendNats(this.serviceProxy, 'send-invitation', payload); } diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 77469fb10..e996dc550 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -97,9 +97,9 @@ export class EcosystemController { */ @MessagePattern({ cmd: 'send-ecosystem-invitation' }) async createInvitation( - @Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: string } + payload: { bulkInvitationDto: BulkSendInvitationDto; userId: string, userEmail: string } ): Promise { - return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId); + return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId, payload.userEmail); } /** diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index c325b28dc..4a297d902 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -149,7 +149,7 @@ export class EcosystemService { * @param userId * @returns */ - async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: string): Promise { + async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: string, userEmail: string): Promise { const { invitations, ecosystemId } = bulkInvitationDto; try { @@ -162,7 +162,7 @@ export class EcosystemService { const isInvitationExist = await this.checkInvitationExist(email, ecosystemId); - if (!isInvitationExist) { + if (!isInvitationExist && userEmail === invitation.email) { await this.ecosystemRepository.createSendInvitation(email, ecosystemId, userId); try { await this.sendInviteEmailTemplate(email, ecosystemDetails.name, isUserExist); @@ -277,9 +277,22 @@ export class EcosystemService { const invitations = await this.ecosystemRepository.getEcosystemInvitations(query); - if (0 < invitations.length) { + let isPendingInvitation = false; + let isAcceptedInvitation = false; + + for (const invitation of invitations) { + if (invitation.status === Invitation.PENDING) { + isPendingInvitation = true; + } + if (invitation.status === Invitation.ACCEPTED) { + isAcceptedInvitation = true; + } + } + + if (isPendingInvitation || isAcceptedInvitation) { return true; } + return false; } catch (error) { throw new RpcException(error.response ? error.response : error); diff --git a/apps/organization/src/organization.controller.ts b/apps/organization/src/organization.controller.ts index bbe5bfd8b..6128c1592 100644 --- a/apps/organization/src/organization.controller.ts +++ b/apps/organization/src/organization.controller.ts @@ -110,9 +110,9 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'send-invitation' }) async createInvitation( - @Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: number } + @Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: number, userEmail: string } ): Promise { - return this.organizationService.createInvitation(payload.bulkInvitationDto, payload.userId); + return this.organizationService.createInvitation(payload.bulkInvitationDto, payload.userId, payload.userEmail); } @MessagePattern({ cmd: 'fetch-user-invitations' }) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 3dae32045..55ec0e538 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -273,9 +273,22 @@ export class OrganizationService { const invitations = await this.organizationRepository.getOrgInvitations(query); - if (0 < invitations.length) { + let isPendingInvitation = false; + let isAcceptedInvitation = false; + + for (const invitation of invitations) { + if (invitation.status === Invitation.PENDING) { + isPendingInvitation = true; + } + if (invitation.status === Invitation.ACCEPTED) { + isAcceptedInvitation = true; + } + } + + if (isPendingInvitation || isAcceptedInvitation) { return true; } + return false; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); @@ -290,7 +303,7 @@ export class OrganizationService { */ // eslint-disable-next-line camelcase - async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: number): Promise { + async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: number, userEmail: string): Promise { const { invitations, orgId } = bulkInvitationDto; try { @@ -301,9 +314,10 @@ export class OrganizationService { const isUserExist = await this.checkUserExistInPlatform(email); - const isInvitationExist = await this.checkInvitationExist(email, orgId); + const isInvitationExist = await this.checkInvitationExist(email, orgId); + + if (!isInvitationExist && userEmail !== invitation.email) { - if (!isInvitationExist) { await this.organizationRepository.createSendInvitation(email, orgId, userId, orgRoleId); const orgRolesDetails = await this.orgRoleService.getOrgRolesByIds(orgRoleId); diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index e713dc8cc..6faf17c0b 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -43,7 +43,7 @@ export const ResponseMessages = { update: 'Organization updated successfully', fetchProfile: 'Organization profile fetched successfully', fetchOrgRoles: 'Organization roles fetched successfully', - createInvitation: 'Organization invitations sent successfully', + createInvitation: 'Organization invitations sent', getInvitation: 'Organization invitations fetched successfully', getOrganization: 'Organization details fetched successfully', getOrgDashboard: 'Organization dashboard details fetched', @@ -190,7 +190,7 @@ export const ResponseMessages = { fetch: 'Ecosystem fetched successfully', getEcosystemDashboard: 'Ecosystem dashboard details fetched successfully', getInvitation: 'Ecosystem invitations fetched successfully', - createInvitation: 'Ecosystem invitations sent successfully', + createInvitation: 'Ecosystem invitations sent', schemaRequest: 'Schema transaction request created successfully', credDefRequest: 'credential-definition transaction request created successfully', sign: 'Transaction request signed successfully', From 4fa1a24625aa78db8e1b8b8e3c1a103aad96146f Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Thu, 19 Oct 2023 12:26:55 +0530 Subject: [PATCH 161/162] feat: cred-def list by schemaId for verification (#180) * fix: cred-def list by schemaId for verification Signed-off-by: KulkarniShashank * Added await by returning the ced defs Signed-off-by: KulkarniShashank * Discription changes on get cred-def list by schema Id Signed-off-by: KulkarniShashank --------- Signed-off-by: KulkarniShashank --- .../credential-definition.controller.ts | 28 ++++++++++++++++--- .../credential-definition.service.ts | 5 ++++ .../credential-definition.controller.ts | 7 ++++- .../credential-definition.service.ts | 12 +++++++- .../create-credential-definition.interface.ts | 4 +++ .../credential-definition.repository.ts | 14 ++++++++++ 6 files changed, 64 insertions(+), 6 deletions(-) diff --git a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts index 6ce6c3d57..cbc1f1f4f 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts @@ -20,16 +20,16 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler @ApiBearerAuth() @ApiTags('credential-definitions') +@Controller() @ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) -@Controller('orgs') @UseFilters(CustomExceptionFilter) export class CredentialDefinitionController { constructor(private readonly credentialDefinitionService: CredentialDefinitionService) { } private readonly logger = new Logger('CredentialDefinitionController'); - @Get('/:orgId/cred-defs/:credDefId') + @Get('/orgs/:orgId/cred-defs/:credDefId') @ApiOperation({ summary: 'Get an existing credential definition by Id', description: 'Get an existing credential definition by Id' @@ -51,7 +51,27 @@ export class CredentialDefinitionController { return res.status(HttpStatus.OK).json(credDefResponse); } - @Get('/:orgId/cred-defs') + @Get('/verifiation/cred-defs/:schemaId') + @ApiOperation({ + summary: 'Get an existing credential definitions by schema Id', + description: 'Get an existing credential definitions by schema Id' + }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + async getCredentialDefinitionBySchemaId( + @Param('schemaId') schemaId: string, + @Res() res: Response + ): Promise { + const credentialsDefinitions = await this.credentialDefinitionService.getCredentialDefinitionBySchemaId(schemaId); + const credDefResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.credentialDefinition.success.fetch, + data: credentialsDefinitions.response + }; + return res.status(HttpStatus.OK).json(credDefResponse); + } + + @Get('/orgs/:orgId/cred-defs') @ApiOperation({ summary: 'Fetch all credential definitions of provided organization id with pagination', description: 'Fetch all credential definitions from metadata saved in database of provided organization id.' @@ -95,7 +115,7 @@ export class CredentialDefinitionController { return res.status(HttpStatus.OK).json(credDefResponse); } - @Post('/:orgId/cred-defs') + @Post('/orgs/:orgId/cred-defs') @ApiOperation({ summary: 'Sends a credential definition to ledger', description: 'Sends a credential definition to ledger' diff --git a/apps/api-gateway/src/credential-definition/credential-definition.service.ts b/apps/api-gateway/src/credential-definition/credential-definition.service.ts index b66b607f0..799bbed7f 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.service.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.service.ts @@ -28,4 +28,9 @@ export class CredentialDefinitionService extends BaseService { const payload = { credDefSearchCriteria, user, orgId }; return this.sendNats(this.credDefServiceProxy, 'get-all-credential-definitions', payload); } + + getCredentialDefinitionBySchemaId(schemaId: string): Promise<{ response: object }> { + const payload = { schemaId }; + return this.sendNats(this.credDefServiceProxy, 'get-all-credential-definitions-by-schema-id', payload); + } } diff --git a/apps/ledger/src/credential-definition/credential-definition.controller.ts b/apps/ledger/src/credential-definition/credential-definition.controller.ts index 7ef0d638c..1f9aab2f3 100644 --- a/apps/ledger/src/credential-definition/credential-definition.controller.ts +++ b/apps/ledger/src/credential-definition/credential-definition.controller.ts @@ -4,7 +4,7 @@ import { Controller, Logger } from '@nestjs/common'; import { CredentialDefinitionService } from './credential-definition.service'; import { MessagePattern } from '@nestjs/microservices'; -import { GetAllCredDefsPayload } from './interfaces/create-credential-definition.interface'; +import { GetAllCredDefsPayload, GetCredDefBySchemaId } from './interfaces/create-credential-definition.interface'; import { CreateCredDefPayload, GetCredDefPayload } from './interfaces/create-credential-definition.interface'; import { credential_definition } from '@prisma/client'; @@ -45,4 +45,9 @@ export class CredentialDefinitionController { }> { return this.credDefService.getAllCredDefs(payload); } + + @MessagePattern({ cmd: 'get-all-credential-definitions-by-schema-id' }) + async getCredentialDefinitionBySchemaId(payload: GetCredDefBySchemaId): Promise { + return this.credDefService.getCredentialDefinitionBySchemaId(payload); + } } \ No newline at end of file diff --git a/apps/ledger/src/credential-definition/credential-definition.service.ts b/apps/ledger/src/credential-definition/credential-definition.service.ts index 66341747f..201bda760 100644 --- a/apps/ledger/src/credential-definition/credential-definition.service.ts +++ b/apps/ledger/src/credential-definition/credential-definition.service.ts @@ -9,7 +9,7 @@ import { import { ClientProxy, RpcException } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { CredentialDefinitionRepository } from './repositories/credential-definition.repository'; -import { CreateCredDefPayload, CredDefPayload, GetAllCredDefsPayload, GetCredDefPayload } from './interfaces/create-credential-definition.interface'; +import { CreateCredDefPayload, CredDefPayload, GetAllCredDefsPayload, GetCredDefBySchemaId, GetCredDefPayload } from './interfaces/create-credential-definition.interface'; import { credential_definition } from '@prisma/client'; import { ResponseMessages } from '@credebl/common/response-messages'; import { CreateCredDefAgentRedirection, GetCredDefAgentRedirection } from './interfaces/credential-definition.interface'; @@ -256,4 +256,14 @@ export class CredentialDefinitionService extends BaseService { } } + async getCredentialDefinitionBySchemaId(payload: GetCredDefBySchemaId): Promise { + try { + const { schemaId } = payload; + const credDefListBySchemaId = await this.credentialDefinitionRepository.getCredentialDefinitionBySchemaId(schemaId); + return credDefListBySchemaId; + } catch (error) { + this.logger.error(`Error in retrieving credential definitions: ${error}`); + throw new RpcException(error.response ? error.response : error); + } + } } \ No newline at end of file diff --git a/apps/ledger/src/credential-definition/interfaces/create-credential-definition.interface.ts b/apps/ledger/src/credential-definition/interfaces/create-credential-definition.interface.ts index ccefdcf37..0f574fe19 100644 --- a/apps/ledger/src/credential-definition/interfaces/create-credential-definition.interface.ts +++ b/apps/ledger/src/credential-definition/interfaces/create-credential-definition.interface.ts @@ -50,4 +50,8 @@ export interface GetAllCredDefsPayload { credDefSearchCriteria: GetAllCredDefsDto, user: IUserRequestInterface, orgId: number +} + +export interface GetCredDefBySchemaId { + schemaId: string } \ No newline at end of file diff --git a/apps/ledger/src/credential-definition/repositories/credential-definition.repository.ts b/apps/ledger/src/credential-definition/repositories/credential-definition.repository.ts index 03690982e..12597749b 100644 --- a/apps/ledger/src/credential-definition/repositories/credential-definition.repository.ts +++ b/apps/ledger/src/credential-definition/repositories/credential-definition.repository.ts @@ -152,4 +152,18 @@ export class CredentialDefinitionRepository { throw error; } } + + async getCredentialDefinitionBySchemaId(schemaId: string): Promise { + try { + return this.prisma.credential_definition.findMany({ + where: { + schemaLedgerId: schemaId + } + + }); + } catch (error) { + this.logger.error(`Error in getting credential definitions: ${error}`); + throw error; + } + } } \ No newline at end of file From e1283cb4f07df6bc79c0ff64fad76c9658591cbe Mon Sep 17 00:00:00 2001 From: Shashank Kulkarni <44693969+KulkarniShashank@users.noreply.github.com> Date: Thu, 19 Oct 2023 13:46:28 +0530 Subject: [PATCH 162/162] Solved the credential-def bug in endorsement (#181) Signed-off-by: KulkarniShashank --- apps/ecosystem/src/ecosystem.service.ts | 47 ++++++++++++++----------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 4a297d902..874ae6f4b 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -38,10 +38,10 @@ export class EcosystemService { // eslint-disable-next-line camelcase async createEcosystem(createEcosystemDto: CreateEcosystem): Promise { - const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(createEcosystemDto.orgId); - if (checkOrganization) { - throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); - }; + const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(createEcosystemDto.orgId); + if (checkOrganization) { + throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); + }; const createEcosystem = await this.ecosystemRepository.createNewEcosystem(createEcosystemDto); if (!createEcosystem) { throw new NotFoundException(ResponseMessages.ecosystem.error.notCreated); @@ -105,11 +105,11 @@ export class EcosystemService { ecosystem: ecosystemDetails['ecosystem'], membersCount: endorseMemberCount.membersCount, endorsementsCount: endorseMemberCount.endorsementsCount, - ecosystemLead:{ - role: ecosystemDetails['ecosystemRole']['name'], - orgName: ecosystemDetails['orgName'], - config: endorseMemberCount.ecosystemConfigData - } + ecosystemLead: { + role: ecosystemDetails['ecosystemRole']['name'], + orgName: ecosystemDetails['orgName'], + config: endorseMemberCount.ecosystemConfigData + } }; return dashboardDetails; @@ -187,9 +187,9 @@ export class EcosystemService { async acceptRejectEcosystemInvitations(acceptRejectInvitation: AcceptRejectEcosystemInvitationDto): Promise { try { const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(acceptRejectInvitation.orgId); - + if (checkOrganization) { - throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); + throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); }; const { orgId, status, invitationId, orgName, orgDid } = acceptRejectInvitation; const invitation = await this.ecosystemRepository.getEcosystemInvitationById(invitationId); @@ -281,12 +281,12 @@ export class EcosystemService { let isAcceptedInvitation = false; for (const invitation of invitations) { - if (invitation.status === Invitation.PENDING) { + if (invitation.status === Invitation.PENDING) { isPendingInvitation = true; - } - if (invitation.status === Invitation.ACCEPTED) { + } + if (invitation.status === Invitation.ACCEPTED) { isAcceptedInvitation = true; - } + } } if (isPendingInvitation || isAcceptedInvitation) { @@ -355,7 +355,7 @@ export class EcosystemService { * @param RequestSchemaEndorsement * @returns */ - async requestSchemaEndorsement(requestSchemaPayload:RequestSchemaEndorsement, orgId: number, ecosystemId: string): Promise { + async requestSchemaEndorsement(requestSchemaPayload: RequestSchemaEndorsement, orgId: number, ecosystemId: string): Promise { try { const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); @@ -425,7 +425,7 @@ export class EcosystemService { } } - async requestCredDeffEndorsement(requestCredDefPayload:RequestCredDeffEndorsement, orgId:number, ecosystemId:string): Promise { + async requestCredDeffEndorsement(requestCredDefPayload: RequestCredDeffEndorsement, orgId: number, ecosystemId: string): Promise { try { const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); @@ -478,6 +478,13 @@ export class EcosystemService { throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); } + const requestBody = credDefTransactionRequest.message.credentialDefinitionState.credentialDefinition; + + if (!requestBody) { + throw new NotFoundException(ResponseMessages.ecosystem.error.credentialDefinitionNotFound); + } + + requestCredDefPayload["credentialDefinition"] = requestBody; const schemaTransactionResponse = { endorserDid: ecosystemLeadAgentDetails.orgDid, authorDid: ecosystemMemberDetails.orgDid, @@ -697,9 +704,9 @@ export class EcosystemService { payload.credentialDefinition = { tag: parsedRequestPayload.operation.tag, issuerId: ecosystemMemberDetails.orgDid, - schemaId: endorsementTransactionPayload.requestBody['schemaId'], - type: endorsementTransactionPayload.requestBody['type'], - value: endorsementTransactionPayload.requestBody['value'] + schemaId: endorsementTransactionPayload.requestBody.credentialDefinition['schemaId'], + type: endorsementTransactionPayload.requestBody.credentialDefinition['type'], + value: endorsementTransactionPayload.requestBody.credentialDefinition['value'] }; }