From 5cdc27d5a84a004dd67ac07eda9f6155ddc936bc Mon Sep 17 00:00:00 2001 From: pranalidhanavade Date: Tue, 27 Jan 2026 19:37:09 +0530 Subject: [PATCH 1/6] refactor: added isEcosystemEnabled flag in database in platform_config table Signed-off-by: pranalidhanavade --- .../src/ecosystem/dtos/enable-ecosystem.ts | 11 +++++++ .../src/platform/platform.controller.ts | 32 +++++++++++++++++++ .../src/platform/platform.service.ts | 10 ++++++ .../repositories/ecosystem.repository.ts | 21 ++++++++++++ apps/ecosystem/src/ecosystem.controller.ts | 11 +++++++ apps/ecosystem/src/ecosystem.service.ts | 22 +++++++++++++ libs/common/src/response-messages/index.ts | 6 ++-- .../prisma/data/credebl-master-table.json | 1 + .../migration.sql | 2 ++ libs/prisma-service/prisma/schema.prisma | 1 + 10 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 apps/api-gateway/src/ecosystem/dtos/enable-ecosystem.ts create mode 100644 libs/prisma-service/prisma/migrations/20260127123306_added_is_ecosystem_enabled_column_in_platform_config/migration.sql diff --git a/apps/api-gateway/src/ecosystem/dtos/enable-ecosystem.ts b/apps/api-gateway/src/ecosystem/dtos/enable-ecosystem.ts new file mode 100644 index 000000000..9d6043a54 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/enable-ecosystem.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean } from 'class-validator'; + +export class EnableEcosystemDto { + @ApiProperty({ + example: true, + description: 'Enable or disable ecosystem creation' + }) + @IsBoolean() + isEcosystemEnabled: boolean; +} diff --git a/apps/api-gateway/src/platform/platform.controller.ts b/apps/api-gateway/src/platform/platform.controller.ts index cfd77a1a0..150b420ca 100644 --- a/apps/api-gateway/src/platform/platform.controller.ts +++ b/apps/api-gateway/src/platform/platform.controller.ts @@ -7,6 +7,7 @@ import { Logger, Param, Post, + Put, Query, Res, UseFilters, @@ -33,6 +34,7 @@ import { OrgRoles } from 'libs/org-roles/enums'; import { Roles } from '../authz/decorators/roles.decorator'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { CreateEcosystemInvitationDto } from '../ecosystem/dtos/send-ecosystem-invitation'; +import { EnableEcosystemDto } from '../ecosystem/dtos/enable-ecosystem'; @Controller('') @UseFilters(CustomExceptionFilter) @@ -275,4 +277,34 @@ export class PlatformController { data: invitations }); } + + /** + * Update ecosystem enable/disable flag + */ + @Put('/config/ecosystem') + @Roles(OrgRoles.PLATFORM_ADMIN) + @ApiOperation({ + summary: 'Enable or disable ecosystem feature', + description: 'Platform admin can enable or disable ecosystem feature on the platform' + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Ecosystem configuration updated successfully', + type: ApiResponseDto + }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiBearerAuth() + async updateEcosystemConfig( + @Body() platformConfigDto: EnableEcosystemDto, + @User() reqUser: user, + @Res() res: Response + ): Promise { + await this.platformService.updateEcosystemConfig(platformConfigDto.isEcosystemEnabled, reqUser.id); + + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.updateEcosystemConfig + }; + return res.status(HttpStatus.OK).json(finalResponse); + } } diff --git a/apps/api-gateway/src/platform/platform.service.ts b/apps/api-gateway/src/platform/platform.service.ts index d44ab3166..1c6a084c4 100644 --- a/apps/api-gateway/src/platform/platform.service.ts +++ b/apps/api-gateway/src/platform/platform.service.ts @@ -65,4 +65,14 @@ export class PlatformService extends BaseService { async getInvitationsByUserId(userId: string): Promise { return this.natsClient.sendNatsMessage(this.platformServiceProxy, 'get-ecosystem-invitations-by-user', { userId }); } + + /** + * Update ecosystem enable/disable flag + */ + async updateEcosystemConfig(isEcosystemEnabled: boolean, platformAdminId: string): Promise<{ message: string }> { + return this.natsClient.sendNatsMessage(this.platformServiceProxy, 'update-ecosystem-config', { + isEcosystemEnabled, + platformAdminId + }); + } } diff --git a/apps/ecosystem/repositories/ecosystem.repository.ts b/apps/ecosystem/repositories/ecosystem.repository.ts index dff5c228c..775ded962 100644 --- a/apps/ecosystem/repositories/ecosystem.repository.ts +++ b/apps/ecosystem/repositories/ecosystem.repository.ts @@ -1167,4 +1167,25 @@ export class EcosystemRepository { return intent; } + + /** + * Update ecosystem enable/disable flag + */ + async upsertEcosystemConfig(payload: { isEcosystemEnabled: boolean; userId: string }): Promise { + const { isEcosystemEnabled } = payload; + + const existingConfig = await this.prisma.platform_config.findFirst(); + + if (!existingConfig) { + throw new RpcException({ + statusCode: 500, + message: 'Platform config not found' + }); + } + + await this.prisma.platform_config.update({ + where: { id: existingConfig.id }, + data: { isEcosystemEnabled } + }); + } } diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index 8a0b88bc0..c2bcfab94 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -291,4 +291,15 @@ export class EcosystemController { async deleteIntent(payload: { ecosystemId: string; intentId: string; user: { id: string } }): Promise { return this.ecosystemService.deleteIntent(payload.ecosystemId, payload.intentId, payload.user); } + + /** + * Update ecosystem platform configuration + */ + @MessagePattern({ cmd: 'update-ecosystem-config' }) + async updateEcosystemConfig(payload: { + isEcosystemEnabled: boolean; + platformAdminId: string; + }): Promise<{ message: string }> { + return this.ecosystemService.updateEcosystemConfig(payload); + } } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index 8cfb487c1..fb043f03c 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -676,4 +676,26 @@ export class EcosystemService { intentId }); } + /** + * Update ecosystem enable/disable flag + */ + async updateEcosystemConfig(payload: { + isEcosystemEnabled: boolean; + platformAdminId: string; + }): Promise<{ message: string }> { + const { isEcosystemEnabled, platformAdminId } = payload; + + if ('boolean' !== typeof isEcosystemEnabled) { + throw new BadRequestException(ResponseMessages.ecosystem.error.invalidEcosystemEnabledFlag); + } + + await this.ecosystemRepository.upsertEcosystemConfig({ + isEcosystemEnabled, + userId: platformAdminId + }); + + return { + message: ResponseMessages.ecosystem.success.updateEcosystemConfig + }; + } } diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index afa96cfa8..eb7a7386e 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -197,7 +197,8 @@ export const ResponseMessages = { deleteIntent: 'Ecosystem intent deleted successfully', fetchIntents: 'Ecosystem intents fetched successfully', fetchIntentTemplates: 'Ecosystem intent templates fetched successfully', - fetchVerificationTemplates: 'Verification templates fetched successfully' + fetchVerificationTemplates: 'Verification templates fetched successfully', + updateEcosystemConfig: 'Ecosystem configuration updated successfully' }, error: { memberInviteFailed: 'Failed to send invitation for the member', @@ -279,7 +280,8 @@ export const ResponseMessages = { ecosystemOrgsFetchFailed: 'Failed to fetch ecosystem orgs', ecosystemMemberStatusFail: 'Failed to update ecosystem member status', failedEcosystemOrgUpdate: 'Failed to update ecosystem org', - invitationMemberfail: 'Failed to fetch invitation members' + invitationMemberfail: 'Failed to fetch invitation members', + invalidEcosystemEnabledFlag: 'Invalid ecosystem enabled flag' } }, schema: { diff --git a/libs/prisma-service/prisma/data/credebl-master-table.json b/libs/prisma-service/prisma/data/credebl-master-table.json index c4167aa18..d040514ad 100644 --- a/libs/prisma-service/prisma/data/credebl-master-table.json +++ b/libs/prisma-service/prisma/data/credebl-master-table.json @@ -5,6 +5,7 @@ "username": "credebl", "sgApiKey": "###Sendgrid Key###", "emailFrom": "##Senders Mail ID##", + "isEcosystemEnabled": false, "apiEndpoint": "## Platform API Ip Address##", "tailsFileServer": "##Machine Ip Address for agent setup##" }, diff --git a/libs/prisma-service/prisma/migrations/20260127123306_added_is_ecosystem_enabled_column_in_platform_config/migration.sql b/libs/prisma-service/prisma/migrations/20260127123306_added_is_ecosystem_enabled_column_in_platform_config/migration.sql new file mode 100644 index 000000000..d279c04e9 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20260127123306_added_is_ecosystem_enabled_column_in_platform_config/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "platform_config" ADD COLUMN "isEcosystemEnabled" BOOLEAN NOT NULL DEFAULT false; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index cdce0028e..dfbc42f8f 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -198,6 +198,7 @@ model platform_config { username String @db.VarChar sgApiKey String @db.VarChar emailFrom String @db.VarChar + isEcosystemEnabled Boolean @default(false) apiEndpoint String @db.VarChar tailsFileServer String @db.VarChar createDateTime DateTime @default(now()) @db.Timestamptz(6) From 82baa0a78ab773417ecd35ca6a02b56fda7d9507 Mon Sep 17 00:00:00 2001 From: pranalidhanavade Date: Thu, 29 Jan 2026 19:06:54 +0530 Subject: [PATCH 2/6] refactor: enable/disable ecosystem feature from database Signed-off-by: pranalidhanavade --- apps/api-gateway/src/app.module.ts | 2 ++ .../authz/guards/ecosystem-feature-guard.ts | 19 ++++++++++++ .../authz/guards/ecosystem-swagger.filter.ts | 30 +++++++++++++++++++ .../src/ecosystem/ecosystem.controller.ts | 29 ++++++++++++++---- .../src/ecosystem/ecosystem.module.ts | 2 +- .../src/ecosystem/ecosystem.service.ts | 4 +-- apps/api-gateway/src/main.ts | 12 +++++--- .../repositories/ecosystem.repository.ts | 13 +++++++- apps/ecosystem/src/ecosystem.controller.ts | 4 +-- apps/ecosystem/src/ecosystem.service.ts | 10 +++---- 10 files changed, 104 insertions(+), 21 deletions(-) create mode 100644 apps/api-gateway/src/authz/guards/ecosystem-feature-guard.ts create mode 100644 apps/api-gateway/src/authz/guards/ecosystem-swagger.filter.ts diff --git a/apps/api-gateway/src/app.module.ts b/apps/api-gateway/src/app.module.ts index 784704ce9..7e2ac5f08 100644 --- a/apps/api-gateway/src/app.module.ts +++ b/apps/api-gateway/src/app.module.ts @@ -15,6 +15,7 @@ import { ConnectionModule } from './connection/connection.module'; import { ContextModule } from '@credebl/context/contextModule'; import { CredentialDefinitionModule } from './credential-definition/credential-definition.module'; import { EcosystemModule } from './ecosystem/ecosystem.module'; +import { EcosystemSwaggerFilter } from './authz/guards/ecosystem-swagger.filter'; import { FidoModule } from './fido/fido.module'; import { GeoLocationModule } from './geo-location/geo-location.module'; import { GlobalConfigModule } from '@credebl/config/global-config.module'; @@ -77,6 +78,7 @@ import { shouldLoadOidcModules } from '@credebl/common/common.utils'; controllers: [AppController], providers: [ AppService, + EcosystemSwaggerFilter, { provide: MICRO_SERVICE_NAME, useValue: 'APIGATEWAY' diff --git a/apps/api-gateway/src/authz/guards/ecosystem-feature-guard.ts b/apps/api-gateway/src/authz/guards/ecosystem-feature-guard.ts new file mode 100644 index 000000000..e0f1da88f --- /dev/null +++ b/apps/api-gateway/src/authz/guards/ecosystem-feature-guard.ts @@ -0,0 +1,19 @@ +import { CanActivate, ForbiddenException, Injectable, Scope } from '@nestjs/common'; + +import { EcosystemRepository } from 'apps/ecosystem/repositories/ecosystem.repository'; + +@Injectable({ scope: Scope.REQUEST }) +export class EcosystemFeatureGuard implements CanActivate { + constructor(private readonly ecosystemRepository: EcosystemRepository) {} + + async canActivate(): Promise { + const config = await this.ecosystemRepository.getPlatformConfig(); + const enabled = Boolean(config?.isEcosystemEnabled); + + if (!enabled) { + throw new ForbiddenException(`You don't have access to this feature`); + } + + return true; + } +} diff --git a/apps/api-gateway/src/authz/guards/ecosystem-swagger.filter.ts b/apps/api-gateway/src/authz/guards/ecosystem-swagger.filter.ts new file mode 100644 index 000000000..c57c0c432 --- /dev/null +++ b/apps/api-gateway/src/authz/guards/ecosystem-swagger.filter.ts @@ -0,0 +1,30 @@ +import { EcosystemRepository } from 'apps/ecosystem/repositories/ecosystem.repository'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class EcosystemSwaggerFilter { + constructor(private readonly ecosystemRepository: EcosystemRepository) {} + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async filterDocument(document: any): Promise { + const config = await this.ecosystemRepository.getPlatformConfig(); + const enabled = Boolean(config?.isEcosystemEnabled); + + if (!enabled) { + Object.keys(document.paths).forEach((path) => { + Object.keys(document.paths[path]).forEach((method) => { + const operation = document.paths[path][method]; + + if (operation.tags?.includes('ecosystem')) { + delete document.paths[path][method]; + } + }); + + if (0 === Object.keys(document.paths[path]).length) { + delete document.paths[path]; + } + }); + } + + return document; + } +} diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index f3b4fec08..ed898caee 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -52,6 +52,7 @@ import { GetAllIntentTemplatesResponseDto } from '../utilities/dtos/get-all-inte import { GetAllIntentTemplatesDto } from '../utilities/dtos/get-all-intent-templates.dto'; import { GetIntentTemplateByIntentAndOrgDto } from '../utilities/dtos/get-intent-template-by-intent-and-org.dto'; import { CreateIntentTemplateDto, UpdateIntentTemplateDto } from '../utilities/dtos/intent-template.dto'; +import { EcosystemFeatureGuard } from '../authz/guards/ecosystem-feature-guard'; @UseFilters(CustomExceptionFilter) @Controller('ecosystem') @@ -77,7 +78,7 @@ export class EcosystemController { description: 'Invitation sent successfully for member invitation' }) @Roles(OrgRoles.ECOSYSTEM_LEAD) - @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, EcosystemFeatureGuard) @ApiBearerAuth() async inviteMemberToEcosystem( @Body() inviteMemberToEcosystem: InviteMemberToEcosystemDto, @@ -800,7 +801,6 @@ export class EcosystemController { return res.status(HttpStatus.OK).json(finalResponse); } - /** * Delete intent * @param id Intent ID @@ -820,12 +820,29 @@ export class EcosystemController { type: ApiResponseDto }) async deleteIntent( - @Param('ecosystemId') ecosystemId: string, - @Param('intentId') intentId: string, - @User() user: IUserRequest, + @Param( + 'ecosystemId', + new ParseUUIDPipe({ + exceptionFactory: (): Error => { + throw new BadRequestException(ResponseMessages.ecosystem.error.invalidFormatOfEcosystemId); + } + }) + ) + ecosystemId: string, + @Param( + 'intentId', + new ParseUUIDPipe({ + exceptionFactory: (): Error => { + throw new BadRequestException(ResponseMessages.ecosystem.error.invalidFormatOfIntentId); + } + }) + ) + intentId: string, + + @User() user: user, @Res() res: Response ): Promise { - const intent = await this.ecosystemService.deleteIntent(ecosystemId, intentId, user); + const intent = await this.ecosystemService.deleteIntent(ecosystemId, intentId, user.id); return res.status(HttpStatus.OK).json({ statusCode: HttpStatus.OK, diff --git a/apps/api-gateway/src/ecosystem/ecosystem.module.ts b/apps/api-gateway/src/ecosystem/ecosystem.module.ts index eb6a1a4e7..1b42d9028 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.module.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.module.ts @@ -21,6 +21,6 @@ import { getNatsOptions } from '@credebl/common/nats.config'; ], controllers: [EcosystemController], providers: [EcosystemService, NATSClient], - exports: [EcosystemService] + exports: [EcosystemService, EcosystemServiceModule] }) export class EcosystemModule {} diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index 28dbd5403..b07ac034f 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -199,11 +199,11 @@ export class EcosystemService { * @param id Intent ID * @returns Deleted intent */ - async deleteIntent(ecosystemId: string, intentId: string, user: IUserRequest): Promise { + async deleteIntent(ecosystemId: string, intentId: string, userId: string): Promise { return this.natsClient.sendNatsMessage(this.serviceProxy, 'delete-intent', { ecosystemId, intentId, - user: { id: user.userId } + userId }); } } diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index b0021fe5f..236713471 100644 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -16,6 +16,7 @@ import { CommonConstants } from '@credebl/common/common.constant'; import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; import { UpdatableValidationPipe } from '@credebl/common/custom-overrideable-validation-pipe'; import * as useragent from 'express-useragent'; +import { EcosystemSwaggerFilter } from './authz/guards/ecosystem-swagger.filter'; dotenv.config(); @@ -81,7 +82,11 @@ async function bootstrap(): Promise { defaultVersion: ['1'] }); - const document = SwaggerModule.createDocument(app, options); + // Create Swagger document + let document = SwaggerModule.createDocument(app, options); + const ecosystemFilter = app.get(EcosystemSwaggerFilter); + document = await ecosystemFilter.filterDocument(document); + SwaggerModule.setup('api', app, document); const httpAdapter: HttpAdapterHost = app.get(HttpAdapterHost) as HttpAdapterHost; app.useGlobalFilters(new AllExceptionsFilter(httpAdapter)); @@ -122,7 +127,7 @@ async function bootstrap(): Promise { if ('true' === process.env.DB_ALERT_ENABLE?.trim()?.toLowerCase()) { // in case it is enabled, log that Logger.log( - 'We have enabled DB alert for \'ledger_null\' instances. This would send email in case the \'ledger_id\' column in \'org_agents\' table is set to null', + "We have enabled DB alert for 'ledger_null' instances. This would send email in case the 'ledger_id' column in 'org_agents' table is set to null", 'DB alert enabled' ); } @@ -130,9 +135,8 @@ async function bootstrap(): Promise { if ('true' === (process.env.HIDE_EXPERIMENTAL_OIDC_CONTROLLERS || 'true').trim().toLowerCase()) { Logger.warn('Hiding experimental OIDC Controllers: OID4VC, OID4VP, x509 in OpenAPI docs'); Logger.verbose( - 'To enable the use of experimental OIDC controllers. Set, \'HIDE_EXPERIMENTAL_OIDC_CONTROLLERS\' env variable to false' + "To enable the use of experimental OIDC controllers. Set, 'HIDE_EXPERIMENTAL_OIDC_CONTROLLERS' env variable to false" ); } - } bootstrap(); diff --git a/apps/ecosystem/repositories/ecosystem.repository.ts b/apps/ecosystem/repositories/ecosystem.repository.ts index 775ded962..6ea24fcec 100644 --- a/apps/ecosystem/repositories/ecosystem.repository.ts +++ b/apps/ecosystem/repositories/ecosystem.repository.ts @@ -1146,7 +1146,7 @@ export class EcosystemRepository { } } - async deleteIntent(data: { ecosystemId: string; intentId: string }): Promise { + async deleteIntent(data: { ecosystemId: string; intentId: string; userId: string }): Promise { const intent = await this.prisma.intents.findFirst({ where: { id: data.intentId, @@ -1188,4 +1188,15 @@ export class EcosystemRepository { data: { isEcosystemEnabled } }); } + + /** + * Fetches the global platform configuration + */ + async getPlatformConfig(): Promise<{ isEcosystemEnabled: boolean } | null> { + return this.prisma.platform_config.findFirst({ + select: { + isEcosystemEnabled: true + } + }); + } } diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index c2bcfab94..da6d669b0 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -288,8 +288,8 @@ export class EcosystemController { * @returns Deleted intent */ @MessagePattern({ cmd: 'delete-intent' }) - async deleteIntent(payload: { ecosystemId: string; intentId: string; user: { id: string } }): Promise { - return this.ecosystemService.deleteIntent(payload.ecosystemId, payload.intentId, payload.user); + async deleteIntent(payload: { ecosystemId: string; intentId: string; userId: string }): Promise { + return this.ecosystemService.deleteIntent(payload); } /** diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index fb043f03c..e3d59df08 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -666,16 +666,16 @@ export class EcosystemService { /** * Delete an intent */ - async deleteIntent(ecosystemId: string, intentId: string, user: { id: string }): Promise { - if (!ecosystemId || !intentId || !user?.id) { - throw new BadRequestException('ecosystemId, intentId and user are required'); - } + async deleteIntent(data: { ecosystemId: string; intentId: string; userId: string }): Promise { + const { ecosystemId, intentId, userId } = data; return this.ecosystemRepository.deleteIntent({ ecosystemId, - intentId + intentId, + userId }); } + /** * Update ecosystem enable/disable flag */ From 0ef80f7ca4c0ec2b4e9ab157dc452e2e7dbd85b3 Mon Sep 17 00:00:00 2001 From: pranalidhanavade Date: Fri, 30 Jan 2026 00:08:34 +0530 Subject: [PATCH 3/6] fix: coderabbit suggestions Signed-off-by: pranalidhanavade --- .../src/authz/guards/ecosystem-feature-guard.ts | 3 ++- .../src/ecosystem/ecosystem.controller.ts | 3 ++- apps/api-gateway/src/main.ts | 8 ++++++-- .../src/platform/platform.controller.ts | 5 +++-- apps/api-gateway/src/platform/platform.module.ts | 15 ++++++++++----- libs/common/src/response-messages/index.ts | 1 + 6 files changed, 24 insertions(+), 11 deletions(-) diff --git a/apps/api-gateway/src/authz/guards/ecosystem-feature-guard.ts b/apps/api-gateway/src/authz/guards/ecosystem-feature-guard.ts index e0f1da88f..d70744700 100644 --- a/apps/api-gateway/src/authz/guards/ecosystem-feature-guard.ts +++ b/apps/api-gateway/src/authz/guards/ecosystem-feature-guard.ts @@ -1,6 +1,7 @@ import { CanActivate, ForbiddenException, Injectable, Scope } from '@nestjs/common'; import { EcosystemRepository } from 'apps/ecosystem/repositories/ecosystem.repository'; +import { ResponseMessages } from '@credebl/common/response-messages'; @Injectable({ scope: Scope.REQUEST }) export class EcosystemFeatureGuard implements CanActivate { @@ -11,7 +12,7 @@ export class EcosystemFeatureGuard implements CanActivate { const enabled = Boolean(config?.isEcosystemEnabled); if (!enabled) { - throw new ForbiddenException(`You don't have access to this feature`); + throw new ForbiddenException(ResponseMessages.ecosystem.error.featureIsDisabled); } return true; diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index ed898caee..cb96ca25b 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -57,6 +57,7 @@ import { EcosystemFeatureGuard } from '../authz/guards/ecosystem-feature-guard'; @UseFilters(CustomExceptionFilter) @Controller('ecosystem') @ApiTags('ecosystem') +@UseGuards(EcosystemFeatureGuard) @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto @@ -78,7 +79,7 @@ export class EcosystemController { description: 'Invitation sent successfully for member invitation' }) @Roles(OrgRoles.ECOSYSTEM_LEAD) - @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, EcosystemFeatureGuard) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard) @ApiBearerAuth() async inviteMemberToEcosystem( @Body() inviteMemberToEcosystem: InviteMemberToEcosystemDto, diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index 236713471..8fb86c465 100644 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -84,8 +84,12 @@ async function bootstrap(): Promise { // Create Swagger document let document = SwaggerModule.createDocument(app, options); - const ecosystemFilter = app.get(EcosystemSwaggerFilter); - document = await ecosystemFilter.filterDocument(document); + try { + const ecosystemFilter = app.get(EcosystemSwaggerFilter); + document = await ecosystemFilter.filterDocument(document); + } catch (err) { + Logger.warn('Skipping EcosystemSwaggerFilter due to error', err as Error); + } SwaggerModule.setup('api', app, document); const httpAdapter: HttpAdapterHost = app.get(HttpAdapterHost) as HttpAdapterHost; diff --git a/apps/api-gateway/src/platform/platform.controller.ts b/apps/api-gateway/src/platform/platform.controller.ts index 150b420ca..a1b860b68 100644 --- a/apps/api-gateway/src/platform/platform.controller.ts +++ b/apps/api-gateway/src/platform/platform.controller.ts @@ -35,6 +35,7 @@ import { Roles } from '../authz/decorators/roles.decorator'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { CreateEcosystemInvitationDto } from '../ecosystem/dtos/send-ecosystem-invitation'; import { EnableEcosystemDto } from '../ecosystem/dtos/enable-ecosystem'; +import { EcosystemFeatureGuard } from '../authz/guards/ecosystem-feature-guard'; @Controller('') @UseFilters(CustomExceptionFilter) @@ -236,7 +237,7 @@ export class PlatformController { description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemFeatureGuard) @ApiBearerAuth() async createInvitation( @Body() createEcosystemInvitationDto: CreateEcosystemInvitationDto, @@ -266,7 +267,7 @@ export class PlatformController { description: 'Invitations fetched successfully' }) @Roles(OrgRoles.PLATFORM_ADMIN) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemFeatureGuard) @ApiBearerAuth() async getInvitations(@User() reqUser: user, @Res() res: Response): Promise { const invitations = await this.platformService.getInvitationsByUserId(reqUser.id); diff --git a/apps/api-gateway/src/platform/platform.module.ts b/apps/api-gateway/src/platform/platform.module.ts index d169ee4cf..bfffd4fb0 100644 --- a/apps/api-gateway/src/platform/platform.module.ts +++ b/apps/api-gateway/src/platform/platform.module.ts @@ -1,13 +1,17 @@ +import { ClientsModule, Transport } from '@nestjs/microservices'; + +import { CommonConstants } from '@credebl/common/common.constant'; +import { ConfigModule } from '@nestjs/config'; +import { EcosystemModule as EcosystemServiceModule } from 'apps/ecosystem/src/ecosystem.module'; import { Module } from '@nestjs/common'; +import { NATSClient } from '@credebl/common/NATSClient'; import { PlatformController } from './platform.controller'; import { PlatformService } from './platform.service'; -import { ClientsModule, Transport } from '@nestjs/microservices'; -import { ConfigModule } from '@nestjs/config'; import { getNatsOptions } from '@credebl/common/nats.config'; -import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; + @Module({ imports: [ + EcosystemServiceModule, ConfigModule.forRoot(), ClientsModule.register([ { @@ -18,6 +22,7 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]) ], controllers: [PlatformController], - providers: [PlatformService, NATSClient] + providers: [PlatformService, NATSClient], + exports: [EcosystemServiceModule] }) export class PlatformModule {} diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index eb7a7386e..a11da874a 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -201,6 +201,7 @@ export const ResponseMessages = { updateEcosystemConfig: 'Ecosystem configuration updated successfully' }, error: { + featureIsDisabled: `You don't have access to this feature`, memberInviteFailed: 'Failed to send invitation for the member', failInvitationUpdate: 'Failed to update Ecosystem Invitation', ecosystemIdIsRequired: 'ecosystemId is required', From db735a4acd1a52966efabe9cfb097261497a63a3 Mon Sep 17 00:00:00 2001 From: pranalidhanavade Date: Fri, 30 Jan 2026 12:25:40 +0530 Subject: [PATCH 4/6] refactor: enable/disable ecosystem feature and delete intent API issue Signed-off-by: pranalidhanavade --- .../src/ecosystem/ecosystem.controller.ts | 21 ++++------ .../src/ecosystem/ecosystem.service.ts | 5 ++- .../repositories/ecosystem.repository.ts | 38 +++++++++++++++++++ apps/ecosystem/src/ecosystem.controller.ts | 6 +-- apps/ecosystem/src/ecosystem.service.ts | 11 ++++-- 5 files changed, 60 insertions(+), 21 deletions(-) diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts index cb96ca25b..2f7bea8bc 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -37,7 +37,6 @@ import { ResponseMessages } from '@credebl/common/response-messages'; import { Roles } from '../authz/decorators/roles.decorator'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { InviteMemberToEcosystemDto, UpdateEcosystemInvitationDto } from './dtos/send-ecosystem-invitation'; -import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { EcosystemRolesGuard } from '../authz/guards/ecosystem-roles.guard'; import { user } from '@prisma/client'; import { User } from '../authz/decorators/user.decorator'; @@ -200,20 +199,16 @@ export class EcosystemController { * Get all ecosystems (platform admin) * @returns All ecosystems from platform */ - @Get() + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard) + @ApiBearerAuth() + @Get('/all-ecosystem') @ApiOperation({ - summary: 'Get all ecosystems (platform admin)', - description: 'Fetch all ecosystems available on the platform' - }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Ecosystems fetched successfully' + summary: 'Get ecosystems', + description: 'Fetch ecosystems for Platform Admin or Ecosystem Lead' }) - @Roles(OrgRoles.PLATFORM_ADMIN) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiBearerAuth() - async getAllEcosystems(@User() reqUser: user, @Res() res: Response): Promise { - const ecosystems = await this.ecosystemService.getAllEcosystems(); + @Roles(OrgRoles.PLATFORM_ADMIN, OrgRoles.ECOSYSTEM_LEAD) + async getEcosystems(@User() reqUser: user, @Res() res: Response): Promise { + const ecosystems = await this.ecosystemService.getEcosystems(reqUser.id); return res.status(HttpStatus.OK).json({ statusCode: HttpStatus.OK, diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts index b07ac034f..69e61aadb 100644 --- a/apps/api-gateway/src/ecosystem/ecosystem.service.ts +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -39,9 +39,10 @@ export class EcosystemService { * @param userId * @returns All ecosystems from platform */ - async getAllEcosystems(): Promise { - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-all-ecosystems', {}); + async getEcosystems(userId: string): Promise { + return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-ecosystems', { userId }); } + /** * * @param ecosystemId diff --git a/apps/ecosystem/repositories/ecosystem.repository.ts b/apps/ecosystem/repositories/ecosystem.repository.ts index 6ea24fcec..d9366a0eb 100644 --- a/apps/ecosystem/repositories/ecosystem.repository.ts +++ b/apps/ecosystem/repositories/ecosystem.repository.ts @@ -1199,4 +1199,42 @@ export class EcosystemRepository { } }); } + async getEcosystemsForEcosystemLead(userId: string): Promise { + return this.prisma.ecosystem.findMany({ + where: { + deletedAt: null, + ecosystemOrgs: { + some: { + userId, + deletedAt: null, + ecosystemRole: { + name: OrgRoles.ECOSYSTEM_LEAD + } + } + } + }, + orderBy: { + createDateTime: 'desc' + }, + include: { + ecosystemOrgs: { + where: { + userId, + deletedAt: null + }, + include: { + ecosystemRole: true, + organisation: { + select: { + id: true, + name: true, + orgSlug: true, + logoUrl: true + } + } + } + } + } + }); + } } diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts index da6d669b0..33d0017aa 100644 --- a/apps/ecosystem/src/ecosystem.controller.ts +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -67,9 +67,9 @@ export class EcosystemController { * Used by Platform Admin * @returns List of ecosystems */ - @MessagePattern({ cmd: 'get-all-ecosystems' }) - async getAllEcosystems(): Promise { - return this.ecosystemService.getAllEcosystems(); + @MessagePattern({ cmd: 'get-ecosystems' }) + async getEcosystems(payload: { userId: string }): Promise { + return this.ecosystemService.getEcosystems(payload.userId); } /** diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index e3d59df08..d745cbb2f 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -280,11 +280,16 @@ export class EcosystemService { } } - async getAllEcosystems(): Promise { + async getEcosystems(userId: string): Promise { try { - return await this.ecosystemRepository.getAllEcosystems(); + const leadEcosystems = await this.ecosystemRepository.getEcosystemsForEcosystemLead(userId); + + if (0 < leadEcosystems.length) { + return leadEcosystems; + } + return this.ecosystemRepository.getAllEcosystems(); } catch (error) { - this.logger.error('getAllEcosystems error', error); + this.logger.error('getEcosystems error', error); throw new InternalServerErrorException(ResponseMessages.ecosystem.error.fetch); } } From 09a62d759d75609a63928adb13e60a482aff89b1 Mon Sep 17 00:00:00 2001 From: pranalidhanavade Date: Fri, 30 Jan 2026 12:50:41 +0530 Subject: [PATCH 5/6] comments on PR Signed-off-by: pranalidhanavade --- .../src/authz/guards/ecosystem-swagger.filter.ts | 3 ++- apps/ecosystem/repositories/ecosystem.repository.ts | 6 +++--- apps/ecosystem/src/ecosystem.service.ts | 2 +- libs/common/src/response-messages/index.ts | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/api-gateway/src/authz/guards/ecosystem-swagger.filter.ts b/apps/api-gateway/src/authz/guards/ecosystem-swagger.filter.ts index c57c0c432..55c2ddf4b 100644 --- a/apps/api-gateway/src/authz/guards/ecosystem-swagger.filter.ts +++ b/apps/api-gateway/src/authz/guards/ecosystem-swagger.filter.ts @@ -1,11 +1,12 @@ import { EcosystemRepository } from 'apps/ecosystem/repositories/ecosystem.repository'; import { Injectable } from '@nestjs/common'; +import { OpenAPIObject } from '@nestjs/swagger'; @Injectable() export class EcosystemSwaggerFilter { constructor(private readonly ecosystemRepository: EcosystemRepository) {} // eslint-disable-next-line @typescript-eslint/no-explicit-any - async filterDocument(document: any): Promise { + async filterDocument(document: OpenAPIObject): Promise { const config = await this.ecosystemRepository.getPlatformConfig(); const enabled = Boolean(config?.isEcosystemEnabled); diff --git a/apps/ecosystem/repositories/ecosystem.repository.ts b/apps/ecosystem/repositories/ecosystem.repository.ts index d9366a0eb..3b2f3e755 100644 --- a/apps/ecosystem/repositories/ecosystem.repository.ts +++ b/apps/ecosystem/repositories/ecosystem.repository.ts @@ -1171,15 +1171,15 @@ export class EcosystemRepository { /** * Update ecosystem enable/disable flag */ - async upsertEcosystemConfig(payload: { isEcosystemEnabled: boolean; userId: string }): Promise { + async updateEcosystemConfig(payload: { isEcosystemEnabled: boolean; userId: string }): Promise { const { isEcosystemEnabled } = payload; const existingConfig = await this.prisma.platform_config.findFirst(); if (!existingConfig) { throw new RpcException({ - statusCode: 500, - message: 'Platform config not found' + statusCode: HttpStatus.NOT_FOUND, + message: ResponseMessages.ecosystem.error.platformConfigNotFound }); } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index d745cbb2f..c00d19a15 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -694,7 +694,7 @@ export class EcosystemService { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidEcosystemEnabledFlag); } - await this.ecosystemRepository.upsertEcosystemConfig({ + await this.ecosystemRepository.updateEcosystemConfig({ isEcosystemEnabled, userId: platformAdminId }); diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index a11da874a..aa44b0b74 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -201,6 +201,7 @@ export const ResponseMessages = { updateEcosystemConfig: 'Ecosystem configuration updated successfully' }, error: { + platformConfigNotFound: 'Platform config not found', featureIsDisabled: `You don't have access to this feature`, memberInviteFailed: 'Failed to send invitation for the member', failInvitationUpdate: 'Failed to update Ecosystem Invitation', From 43d80b0885dff506d8b1b77dc75135f3ce0cac31 Mon Sep 17 00:00:00 2001 From: pranalidhanavade Date: Fri, 30 Jan 2026 13:00:19 +0530 Subject: [PATCH 6/6] coderabbit comments on PR Signed-off-by: pranalidhanavade --- .../src/authz/guards/ecosystem-swagger.filter.ts | 3 +++ apps/ecosystem/repositories/ecosystem.repository.ts | 6 +++++- apps/ecosystem/src/ecosystem.service.ts | 7 ++++++- libs/common/src/response-messages/index.ts | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/api-gateway/src/authz/guards/ecosystem-swagger.filter.ts b/apps/api-gateway/src/authz/guards/ecosystem-swagger.filter.ts index 55c2ddf4b..1c4d62d4b 100644 --- a/apps/api-gateway/src/authz/guards/ecosystem-swagger.filter.ts +++ b/apps/api-gateway/src/authz/guards/ecosystem-swagger.filter.ts @@ -11,6 +11,9 @@ export class EcosystemSwaggerFilter { const enabled = Boolean(config?.isEcosystemEnabled); if (!enabled) { + if (!document.paths) { + return document; + } Object.keys(document.paths).forEach((path) => { Object.keys(document.paths[path]).forEach((method) => { const operation = document.paths[path][method]; diff --git a/apps/ecosystem/repositories/ecosystem.repository.ts b/apps/ecosystem/repositories/ecosystem.repository.ts index 3b2f3e755..11d53d526 100644 --- a/apps/ecosystem/repositories/ecosystem.repository.ts +++ b/apps/ecosystem/repositories/ecosystem.repository.ts @@ -1185,7 +1185,11 @@ export class EcosystemRepository { await this.prisma.platform_config.update({ where: { id: existingConfig.id }, - data: { isEcosystemEnabled } + data: { + isEcosystemEnabled, + lastChangedBy: payload.userId, + lastChangedDateTime: new Date() + } }); } diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts index c00d19a15..728b3f89e 100644 --- a/apps/ecosystem/src/ecosystem.service.ts +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -281,6 +281,9 @@ export class EcosystemService { } async getEcosystems(userId: string): Promise { + if (!userId) { + throw new BadRequestException(ResponseMessages.ecosystem.error.userIdMissing); + } try { const leadEcosystems = await this.ecosystemRepository.getEcosystemsForEcosystemLead(userId); @@ -689,7 +692,9 @@ export class EcosystemService { platformAdminId: string; }): Promise<{ message: string }> { const { isEcosystemEnabled, platformAdminId } = payload; - + if (!platformAdminId) { + throw new BadRequestException(ResponseMessages.ecosystem.error.platformIdRequired); + } if ('boolean' !== typeof isEcosystemEnabled) { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidEcosystemEnabledFlag); } diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index aa44b0b74..fbcde1508 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -201,7 +201,6 @@ export const ResponseMessages = { updateEcosystemConfig: 'Ecosystem configuration updated successfully' }, error: { - platformConfigNotFound: 'Platform config not found', featureIsDisabled: `You don't have access to this feature`, memberInviteFailed: 'Failed to send invitation for the member', failInvitationUpdate: 'Failed to update Ecosystem Invitation', @@ -267,6 +266,7 @@ export const ResponseMessages = { invalidFormatOfEcosystemId: 'Invalid format of ecosystemId', invalidFormatOfIntentId: 'Invalid format of ecosystemId', emailOrPlatformAdminIdMissing: 'Email or platformAdminId missing', + platformIdRequired: 'PlatformId is required', userIdMissing: 'UserId is required', fetch: 'Error while fetching ecosystems', invitationRequired: 'Accepted invitation is required to create ecosystem',