diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index 82641ea5f2..f182e362b1 100755 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -44,7 +44,6 @@ import { CreateEcosystemDto, PaginationGetAllEcosystem } from 'apps/ecosystem/dt import { DeleteEcosystemOrgDto } from './dtos/delete-ecosystem-users'; import { GetEcosystemInvitationsQueryDto, UpdateEcosystemOrgStatusDto } from './dtos/ecosystem'; import { IIntentTemplateList } from '@credebl/common/interfaces/intents-template.interface'; -import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { CreateIntentDto } from 'apps/ecosystem/dtos/create-intent.dto'; import { UpdateIntentDto } from 'apps/ecosystem/dtos/update-intent.dto'; import { GetAllIntentTemplatesResponseDto } from '../utilities/dtos/get-all-intent-templates-response.dto'; @@ -264,7 +263,7 @@ export class EcosystemController { TrimStringParamPipe, new ParseUUIDPipe({ exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); + throw new BadRequestException(ResponseMessages.ecosystem.error.invalidOrgId); } }) ) @@ -547,6 +546,7 @@ export class EcosystemController { async getIntentTemplatesByIntentId( @Param( 'intentId', + TrimStringParamPipe, new ParseUUIDPipe({ exceptionFactory: (): Error => { throw new BadRequestException('Invalid intent ID format'); @@ -580,7 +580,19 @@ export class EcosystemController { description: 'Retrieve all intent templates for a specific organization.' }) @ApiResponse({ status: HttpStatus.OK, description: 'Intent templates retrieved successfully', type: ApiResponseDto }) - async getIntentTemplatesByOrgId(@Param('orgId') orgId: string, @Res() res: Response): Promise { + async getIntentTemplatesByOrgId( + @Param( + 'orgId', + TrimStringParamPipe, + new ParseUUIDPipe({ + exceptionFactory: (): Error => { + throw new BadRequestException('Invalid orgId format'); + } + }) + ) + orgId: string, + @Res() res: Response + ): Promise { const intentTemplates = await this.ecosystemService.getIntentTemplatesByOrgId(orgId); const finalResponse: IResponse = { statusCode: HttpStatus.OK, @@ -605,6 +617,7 @@ export class EcosystemController { async getIntentTemplateById( @Param( 'id', + TrimStringParamPipe, new ParseUUIDPipe({ exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.oid4vpIntentToTemplate.error.invalidId); @@ -637,9 +650,18 @@ export class EcosystemController { @ApiOperation({ summary: 'Update Intent Template', description: 'Update an existing intent template mapping.' }) @ApiResponse({ status: HttpStatus.OK, description: 'Intent template updated successfully', type: ApiResponseDto }) async updateIntentTemplate( - @Param('id') id: string, + @Param( + 'id', + TrimStringParamPipe, + new ParseUUIDPipe({ + exceptionFactory: (): Error => { + throw new BadRequestException(ResponseMessages.oid4vpIntentToTemplate.error.invalidId); + } + }) + ) + id: string, @Body() updateIntentTemplateDto: UpdateIntentTemplateDto, - @User() user: IUserRequest, + @User() user: user, @Res() res: Response ): Promise { const intentTemplate = await this.ecosystemService.updateIntentTemplate(id, updateIntentTemplateDto, user); @@ -741,6 +763,7 @@ export class EcosystemController { @Res() res: Response, @Param( 'ecosystemId', + TrimStringParamPipe, new ParseUUIDPipe({ exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidFormatOfEcosystemId); @@ -749,9 +772,18 @@ export class EcosystemController { ) ecosystemId: string, @Query() pageDto: PaginationDto, - @Query('intentId') intentId?: string + @Query( + 'intentId', + new ParseUUIDPipe({ + optional: true, + exceptionFactory: (): Error => { + throw new BadRequestException(ResponseMessages.ecosystem.error.invalidFormatOfIntentId); + } + }) + ) + intentId?: string ): Promise { - const intents = await this.ecosystemService.getIntents(ecosystemId, pageDto, intentId); + const intents = await this.ecosystemService.getIntents(ecosystemId, pageDto, intentId?.trim()); return res.status(HttpStatus.OK).json({ statusCode: HttpStatus.OK, @@ -781,7 +813,16 @@ export class EcosystemController { description: 'Template details fetched successfully' }) async getTemplateByIntentId( - @Param('orgId') orgId: string, + @Param( + 'orgId', + TrimStringParamPipe, + new ParseUUIDPipe({ + exceptionFactory: (): Error => { + throw new BadRequestException(ResponseMessages.ecosystem.error.invalidOrgId); + } + }) + ) + orgId: string, @Res() res: Response, @Query() pageDto: PaginationDto ): Promise { @@ -816,6 +857,7 @@ export class EcosystemController { async updateIntent( @Param( 'ecosystemId', + TrimStringParamPipe, new ParseUUIDPipe({ exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidFormatOfEcosystemId); @@ -825,6 +867,7 @@ export class EcosystemController { ecosystemId: string, @Param( 'intentId', + TrimStringParamPipe, new ParseUUIDPipe({ exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidFormatOfIntentId); @@ -871,6 +914,7 @@ export class EcosystemController { async deleteIntent( @Param( 'ecosystemId', + TrimStringParamPipe, new ParseUUIDPipe({ exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidFormatOfEcosystemId); @@ -880,6 +924,7 @@ export class EcosystemController { ecosystemId: string, @Param( 'intentId', + TrimStringParamPipe, new ParseUUIDPipe({ exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidFormatOfIntentId); diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index afa01d7b69..57d7e5d0f3 100755 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -12,7 +12,6 @@ import { import { CreateEcosystemDto } from 'apps/ecosystem/dtos/create-ecosystem-dto'; // eslint-disable-next-line camelcase import { user } from '@prisma/client'; -import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { CreateIntentDto } from 'apps/ecosystem/dtos/create-intent.dto'; import { UpdateIntentDto } from 'apps/ecosystem/dtos/update-intent.dto'; import { CreateIntentTemplateDto, UpdateIntentTemplateDto } from '../utilities/dtos/intent-template.dto'; @@ -143,9 +142,9 @@ export class EcosystemService { async updateIntentTemplate( id: string, updateIntentTemplateDto: UpdateIntentTemplateDto, - user: IUserRequest + user: user ): Promise { - const payload = { id, ...updateIntentTemplateDto, user: { id: user.userId } }; + const payload = { id, ...updateIntentTemplateDto, user: { id: user.id } }; return this.natsClient.sendNatsMessage(this.serviceProxy, 'update-intent-template', payload); } diff --git a/apps/api-gateway/src/utilities/dtos/get-intent-template-by-intent-and-org.dto.ts b/apps/api-gateway/src/utilities/dtos/get-intent-template-by-intent-and-org.dto.ts index f4f27ddc42..81a4b59248 100644 --- a/apps/api-gateway/src/utilities/dtos/get-intent-template-by-intent-and-org.dto.ts +++ b/apps/api-gateway/src/utilities/dtos/get-intent-template-by-intent-and-org.dto.ts @@ -1,14 +1,19 @@ -import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; + export class GetIntentTemplateByIntentAndOrgDto { @ApiProperty({ example: 'WEB_CHECKIN', description: 'Intent name' }) - @IsNotEmpty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'intentName is required' }) @IsString() intentName: string; @ApiProperty({ example: '123e4567-e89b-12d3-a456-426614174000', description: 'Verifier Organization ID' }) - @IsNotEmpty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'verifierOrgId is required' }) @IsString() verifierOrgId: string; } diff --git a/apps/api-gateway/src/utilities/dtos/intent-template.dto.ts b/apps/api-gateway/src/utilities/dtos/intent-template.dto.ts index 778dcf977b..8256caa34d 100644 --- a/apps/api-gateway/src/utilities/dtos/intent-template.dto.ts +++ b/apps/api-gateway/src/utilities/dtos/intent-template.dto.ts @@ -1,6 +1,7 @@ -import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional, IsUUID } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + export class CreateIntentTemplateDto { @ApiProperty({ example: '123e4567-e89b-12d3-a456-426614174000', description: 'Organization ID' }) @IsOptional() @@ -20,7 +21,7 @@ export class CreateIntentTemplateDto { export class UpdateIntentTemplateDto { @ApiProperty({ example: '123e4567-e89b-12d3-a456-426614174000', description: 'Organization ID' }) - @IsNotEmpty() + @IsOptional() @IsUUID() orgId: string; diff --git a/apps/ecosystem/repositories/ecosystem.repository.ts b/apps/ecosystem/repositories/ecosystem.repository.ts index 84c9d0e50e..a2a8170234 100755 --- a/apps/ecosystem/repositories/ecosystem.repository.ts +++ b/apps/ecosystem/repositories/ecosystem.repository.ts @@ -640,7 +640,7 @@ export class EcosystemRepository { } // eslint-disable-next-line camelcase - async getIntentTemplateById(id: string): Promise { + async getIntentTemplateById(id: string): Promise { try { const intentTemplate = await this.prisma.intent_templates.findUnique({ where: { id }, @@ -650,10 +650,8 @@ export class EcosystemRepository { template: true } }); - if (!intentTemplate) { - throw new NotFoundException('Intent template not found'); - } - this.logger.log(`[getIntentTemplateById] - Intent template details ${id}`); + + this.logger.log(`[getIntentTemplateById] fetched id ${id}`); return intentTemplate; } catch (error) { this.logger.error(`Error in getIntentTemplateById: ${error}`); @@ -772,7 +770,9 @@ export class EcosystemRepository { async deleteIntentTemplate(id: string): Promise { try { const intentTemplate = await this.prisma.intent_templates.delete({ - where: { id } + where: { + id + } }); this.logger.log(`[deleteIntentTemplate] - Intent template deleted with id ${id}`); diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index bb67b69114..8a69104211 100755 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -213,13 +213,13 @@ export class EcosystemController { } @MessagePattern({ cmd: 'get-intent-templates-by-intent-id' }) - async getIntentTemplatesByIntentId(intentId: string): Promise { - return this.ecosystemService.getIntentTemplatesByIntentId(intentId); + async getIntentTemplatesByIntentId(payload: { intentId: string }): Promise { + return this.ecosystemService.getIntentTemplatesByIntentId(payload.intentId); } @MessagePattern({ cmd: 'get-intent-templates-by-org-id' }) - async getIntentTemplatesByOrgId(orgId: string): Promise { - return this.ecosystemService.getIntentTemplatesByOrgId(orgId); + async getIntentTemplatesByOrgId(payload: { orgId: string }): Promise { + return this.ecosystemService.getIntentTemplatesByOrgId(payload.orgId); } @MessagePattern({ cmd: 'get-all-intent-templates-by-query' }) @@ -254,10 +254,9 @@ export class EcosystemController { } @MessagePattern({ cmd: 'delete-intent-template' }) - async deleteIntentTemplate(id: string): Promise { - return this.ecosystemService.deleteIntentTemplate(id); + async deleteIntentTemplate(payload: { id: string }): Promise { + return this.ecosystemService.deleteIntentTemplate(payload.id); } - /** * Create a new intent * diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 7949f84694..4648351805 100755 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -579,6 +579,14 @@ export class EcosystemService { async getIntentTemplatesByIntentId(intentId: string): Promise { try { + const intent = await this.ecosystemRepository.findIntentById(intentId); + + if (!intent) { + throw new RpcException({ + statusCode: HttpStatus.NOT_FOUND, + message: ResponseMessages.ecosystem.error.intentNotFound + }); + } return await this.ecosystemRepository.getIntentTemplates({ intentId }); } catch (error) { const errorResponse = ErrorHandler.categorize(error, 'Failed to retrieve intent templates'); @@ -644,15 +652,50 @@ export class EcosystemService { data: { orgId: string; intentId: string; templateId: string; user: { id: string } } ): Promise { try { - const { user, ...templateData } = data; + const { user, orgId, intentId, templateId } = data; + if (!user?.id) { throw new RpcException({ statusCode: HttpStatus.BAD_REQUEST, message: 'user is required' }); } + + if (!intentId || !templateId) { + throw new RpcException({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'intentId and templateId are required' + }); + } + + const existing = await this.ecosystemRepository.getIntentTemplateById(id); + + if (!existing) { + throw new RpcException({ + statusCode: HttpStatus.NOT_FOUND, + message: `Intent template not found for id ${id}` + }); + } + + const duplicate = await this.ecosystemRepository.findIntentTemplate({ + orgId, + intentId, + templateId + }); + + if (duplicate && duplicate.id !== id) { + const scope = orgId ? `org ${orgId}` : 'globally'; + + throw new RpcException({ + statusCode: HttpStatus.CONFLICT, + message: `A template is already assigned to this intent for ${scope}.` + }); + } + return await this.ecosystemRepository.updateIntentTemplate(id, { - ...templateData, + orgId, + intentId, + templateId, lastChangedBy: user.id }); } catch (error) { @@ -685,11 +728,11 @@ export class EcosystemService { async createIntent(createIntentDto: CreateIntentDto): Promise { try { const { name, description, ecosystemId, userId } = createIntentDto; + const ecosystem = await this.ecosystemRepository.getEcosystemById(ecosystemId); - if (!name || !ecosystemId || !userId) { - throw new BadRequestException('name, ecosystemId and userId are required'); + if (!ecosystem) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemNotFound); } - return this.ecosystemRepository.createIntent({ name, description, @@ -715,6 +758,12 @@ export class EcosystemService { intentId?: string ): Promise> { try { + const ecosystem = await this.ecosystemRepository.getEcosystemById(ecosystemId); + + if (!ecosystem) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemNotFound); + } + return await this.ecosystemRepository.getIntents(ecosystemId, pageDetail, intentId); } catch (error) { const errorResponse = ErrorHandler.categorize(error, 'Failed to retrieve intents'); @@ -739,10 +788,10 @@ export class EcosystemService { */ async updateIntent(updateIntentDto: UpdateIntentDto): Promise { try { - const { intentId, ecosystemId, userId, name, description } = updateIntentDto; + const { userId, name, description } = updateIntentDto; - if (!intentId || !ecosystemId || !userId) { - throw new BadRequestException('intentId, ecosystemId and userId are required'); + if (!userId) { + throw new BadRequestException(ResponseMessages.ecosystem.error.userIdMissing); } const intent = await this.ecosystemRepository.updateIntent({ diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 0f4c6daa37..db9e5d9ca2 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -212,6 +212,7 @@ export const ResponseMessages = { failInvitationUpdate: 'Failed to update Ecosystem Invitation', ecosystemIdIsRequired: 'ecosystemId is required', notCreated: 'Error while creating ecosystem', + intentNotFound: 'Ecosystem intent not found', agentNotSpunUp: 'Agent is not spun up for this organization', userNotHaveAccess: 'You do not have access', orgAlreadyExists: 'Organization already exists in ecosystem', @@ -271,6 +272,7 @@ export const ResponseMessages = { ecosystemNotExists: 'Ecosystem does not exist', invalidFormatOfEcosystemId: 'Invalid format of ecosystemId', invalidFormatOfIntentId: 'Invalid format of intentId', + intentIdIsRequired: 'IntentId is required', emailOrPlatformAdminIdMissing: 'Email or platformAdminId missing', platformIdRequired: 'PlatformId is required', userIdMissing: 'UserId is required',