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 d1f89fe18..3a3930d8c 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') @@ -96,60 +88,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' @@ -170,37 +132,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); @@ -218,6 +199,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); + + } /** * @@ -226,84 +227,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); } @@ -315,8 +255,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 = { @@ -349,8 +291,8 @@ export class UserController { 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 967e7c89d..123adfa8b 100644 --- a/apps/api-gateway/src/user/user.service.ts +++ b/apps/api-gateway/src/user/user.service.ts @@ -1,65 +1,27 @@ 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 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 getPublicProfile(id: number): Promise<{ response: object }> { @@ -73,17 +35,12 @@ export class UserService extends BaseService { async findUserByKeycloakId(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 +53,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 6d59bc632..94820a3f2 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -49,7 +49,7 @@ export class UserController { return this.userService.getPublicProfile(payload); } - @MessagePattern({ cmd: 'get-user-by-supabase' }) + @MessagePattern({ cmd: 'get-user-by-supabase' }) async findSupabaseUser(payload: { id }): Promise { return this.userService.findSupabaseUser(payload); } @@ -57,7 +57,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' }) @@ -83,29 +83,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 55743e759..8f4a4a425 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -55,7 +55,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); } @@ -78,7 +78,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); } } @@ -102,7 +102,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); } } @@ -134,7 +134,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); } } @@ -171,17 +171,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); } @@ -191,11 +192,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); @@ -204,7 +205,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) { @@ -239,7 +240,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); } } @@ -266,7 +267,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); } } @@ -300,32 +301,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 { @@ -333,7 +338,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); } } @@ -356,7 +361,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); } } @@ -365,7 +370,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); } } @@ -374,7 +379,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); } } @@ -383,7 +388,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); } } @@ -408,7 +413,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); } } @@ -468,7 +473,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); } } @@ -508,7 +513,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); } } @@ -519,6 +524,7 @@ export class UserService { */ async getOrgUsers(orgId: number, pageNumber: number, pageSize: number, search: string): Promise { try { + const query = { userOrgRoles: { some: { orgId } @@ -533,10 +539,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); } } @@ -558,7 +565,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); } } @@ -571,19 +578,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); } } @@ -595,7 +605,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