Skip to content

Commit d24ac37

Browse files
fix: Enable/disable ecosystem (#1550)
* refactor: added isEcosystemEnabled flag in database in platform_config table Signed-off-by: pranalidhanavade <pranali.dhanavade@ayanworks.com> * refactor: enable/disable ecosystem feature from database Signed-off-by: pranalidhanavade <pranali.dhanavade@ayanworks.com> * fix: coderabbit suggestions Signed-off-by: pranalidhanavade <pranali.dhanavade@ayanworks.com> * refactor: enable/disable ecosystem feature and delete intent API issue Signed-off-by: pranalidhanavade <pranali.dhanavade@ayanworks.com> * comments on PR Signed-off-by: pranalidhanavade <pranali.dhanavade@ayanworks.com> * coderabbit comments on PR Signed-off-by: pranalidhanavade <pranali.dhanavade@ayanworks.com> --------- Signed-off-by: pranalidhanavade <pranali.dhanavade@ayanworks.com>
1 parent 47bb2f2 commit d24ac37

18 files changed

Lines changed: 312 additions & 50 deletions

File tree

apps/api-gateway/src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { ConnectionModule } from './connection/connection.module';
1515
import { ContextModule } from '@credebl/context/contextModule';
1616
import { CredentialDefinitionModule } from './credential-definition/credential-definition.module';
1717
import { EcosystemModule } from './ecosystem/ecosystem.module';
18+
import { EcosystemSwaggerFilter } from './authz/guards/ecosystem-swagger.filter';
1819
import { FidoModule } from './fido/fido.module';
1920
import { GeoLocationModule } from './geo-location/geo-location.module';
2021
import { GlobalConfigModule } from '@credebl/config/global-config.module';
@@ -77,6 +78,7 @@ import { shouldLoadOidcModules } from '@credebl/common/common.utils';
7778
controllers: [AppController],
7879
providers: [
7980
AppService,
81+
EcosystemSwaggerFilter,
8082
{
8183
provide: MICRO_SERVICE_NAME,
8284
useValue: 'APIGATEWAY'
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { CanActivate, ForbiddenException, Injectable, Scope } from '@nestjs/common';
2+
3+
import { EcosystemRepository } from 'apps/ecosystem/repositories/ecosystem.repository';
4+
import { ResponseMessages } from '@credebl/common/response-messages';
5+
6+
@Injectable({ scope: Scope.REQUEST })
7+
export class EcosystemFeatureGuard implements CanActivate {
8+
constructor(private readonly ecosystemRepository: EcosystemRepository) {}
9+
10+
async canActivate(): Promise<boolean> {
11+
const config = await this.ecosystemRepository.getPlatformConfig();
12+
const enabled = Boolean(config?.isEcosystemEnabled);
13+
14+
if (!enabled) {
15+
throw new ForbiddenException(ResponseMessages.ecosystem.error.featureIsDisabled);
16+
}
17+
18+
return true;
19+
}
20+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { EcosystemRepository } from 'apps/ecosystem/repositories/ecosystem.repository';
2+
import { Injectable } from '@nestjs/common';
3+
import { OpenAPIObject } from '@nestjs/swagger';
4+
5+
@Injectable()
6+
export class EcosystemSwaggerFilter {
7+
constructor(private readonly ecosystemRepository: EcosystemRepository) {}
8+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
9+
async filterDocument(document: OpenAPIObject): Promise<OpenAPIObject> {
10+
const config = await this.ecosystemRepository.getPlatformConfig();
11+
const enabled = Boolean(config?.isEcosystemEnabled);
12+
13+
if (!enabled) {
14+
if (!document.paths) {
15+
return document;
16+
}
17+
Object.keys(document.paths).forEach((path) => {
18+
Object.keys(document.paths[path]).forEach((method) => {
19+
const operation = document.paths[path][method];
20+
21+
if (operation.tags?.includes('ecosystem')) {
22+
delete document.paths[path][method];
23+
}
24+
});
25+
26+
if (0 === Object.keys(document.paths[path]).length) {
27+
delete document.paths[path];
28+
}
29+
});
30+
}
31+
32+
return document;
33+
}
34+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsBoolean } from 'class-validator';
3+
4+
export class EnableEcosystemDto {
5+
@ApiProperty({
6+
example: true,
7+
description: 'Enable or disable ecosystem creation'
8+
})
9+
@IsBoolean()
10+
isEcosystemEnabled: boolean;
11+
}

apps/api-gateway/src/ecosystem/ecosystem.controller.ts

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import { ResponseMessages } from '@credebl/common/response-messages';
3737
import { Roles } from '../authz/decorators/roles.decorator';
3838
import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto';
3939
import { InviteMemberToEcosystemDto, UpdateEcosystemInvitationDto } from './dtos/send-ecosystem-invitation';
40-
import { OrgRolesGuard } from '../authz/guards/org-roles.guard';
4140
import { EcosystemRolesGuard } from '../authz/guards/ecosystem-roles.guard';
4241
import { user } from '@prisma/client';
4342
import { User } from '../authz/decorators/user.decorator';
@@ -52,10 +51,12 @@ import { GetAllIntentTemplatesResponseDto } from '../utilities/dtos/get-all-inte
5251
import { GetAllIntentTemplatesDto } from '../utilities/dtos/get-all-intent-templates.dto';
5352
import { GetIntentTemplateByIntentAndOrgDto } from '../utilities/dtos/get-intent-template-by-intent-and-org.dto';
5453
import { CreateIntentTemplateDto, UpdateIntentTemplateDto } from '../utilities/dtos/intent-template.dto';
54+
import { EcosystemFeatureGuard } from '../authz/guards/ecosystem-feature-guard';
5555

5656
@UseFilters(CustomExceptionFilter)
5757
@Controller('ecosystem')
5858
@ApiTags('ecosystem')
59+
@UseGuards(EcosystemFeatureGuard)
5960
@ApiUnauthorizedResponse({
6061
description: 'Unauthorized',
6162
type: UnauthorizedErrorDto
@@ -198,20 +199,16 @@ export class EcosystemController {
198199
* Get all ecosystems (platform admin)
199200
* @returns All ecosystems from platform
200201
*/
201-
@Get()
202+
@UseGuards(AuthGuard('jwt'), EcosystemRolesGuard)
203+
@ApiBearerAuth()
204+
@Get('/all-ecosystem')
202205
@ApiOperation({
203-
summary: 'Get all ecosystems (platform admin)',
204-
description: 'Fetch all ecosystems available on the platform'
205-
})
206-
@ApiResponse({
207-
status: HttpStatus.OK,
208-
description: 'Ecosystems fetched successfully'
206+
summary: 'Get ecosystems',
207+
description: 'Fetch ecosystems for Platform Admin or Ecosystem Lead'
209208
})
210-
@Roles(OrgRoles.PLATFORM_ADMIN)
211-
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
212-
@ApiBearerAuth()
213-
async getAllEcosystems(@User() reqUser: user, @Res() res: Response): Promise<Response> {
214-
const ecosystems = await this.ecosystemService.getAllEcosystems();
209+
@Roles(OrgRoles.PLATFORM_ADMIN, OrgRoles.ECOSYSTEM_LEAD)
210+
async getEcosystems(@User() reqUser: user, @Res() res: Response): Promise<Response> {
211+
const ecosystems = await this.ecosystemService.getEcosystems(reqUser.id);
215212

216213
return res.status(HttpStatus.OK).json({
217214
statusCode: HttpStatus.OK,
@@ -800,7 +797,6 @@ export class EcosystemController {
800797

801798
return res.status(HttpStatus.OK).json(finalResponse);
802799
}
803-
804800
/**
805801
* Delete intent
806802
* @param id Intent ID
@@ -820,12 +816,29 @@ export class EcosystemController {
820816
type: ApiResponseDto
821817
})
822818
async deleteIntent(
823-
@Param('ecosystemId') ecosystemId: string,
824-
@Param('intentId') intentId: string,
825-
@User() user: IUserRequest,
819+
@Param(
820+
'ecosystemId',
821+
new ParseUUIDPipe({
822+
exceptionFactory: (): Error => {
823+
throw new BadRequestException(ResponseMessages.ecosystem.error.invalidFormatOfEcosystemId);
824+
}
825+
})
826+
)
827+
ecosystemId: string,
828+
@Param(
829+
'intentId',
830+
new ParseUUIDPipe({
831+
exceptionFactory: (): Error => {
832+
throw new BadRequestException(ResponseMessages.ecosystem.error.invalidFormatOfIntentId);
833+
}
834+
})
835+
)
836+
intentId: string,
837+
838+
@User() user: user,
826839
@Res() res: Response
827840
): Promise<Response> {
828-
const intent = await this.ecosystemService.deleteIntent(ecosystemId, intentId, user);
841+
const intent = await this.ecosystemService.deleteIntent(ecosystemId, intentId, user.id);
829842

830843
return res.status(HttpStatus.OK).json({
831844
statusCode: HttpStatus.OK,

apps/api-gateway/src/ecosystem/ecosystem.module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ import { getNatsOptions } from '@credebl/common/nats.config';
2121
],
2222
controllers: [EcosystemController],
2323
providers: [EcosystemService, NATSClient],
24-
exports: [EcosystemService]
24+
exports: [EcosystemService, EcosystemServiceModule]
2525
})
2626
export class EcosystemModule {}

apps/api-gateway/src/ecosystem/ecosystem.service.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ export class EcosystemService {
3939
* @param userId
4040
* @returns All ecosystems from platform
4141
*/
42-
async getAllEcosystems(): Promise<IEcosystem[]> {
43-
return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-all-ecosystems', {});
42+
async getEcosystems(userId: string): Promise<IEcosystem[]> {
43+
return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-ecosystems', { userId });
4444
}
45+
4546
/**
4647
*
4748
* @param ecosystemId
@@ -199,11 +200,11 @@ export class EcosystemService {
199200
* @param id Intent ID
200201
* @returns Deleted intent
201202
*/
202-
async deleteIntent(ecosystemId: string, intentId: string, user: IUserRequest): Promise<object> {
203+
async deleteIntent(ecosystemId: string, intentId: string, userId: string): Promise<object> {
203204
return this.natsClient.sendNatsMessage(this.serviceProxy, 'delete-intent', {
204205
ecosystemId,
205206
intentId,
206-
user: { id: user.userId }
207+
userId
207208
});
208209
}
209210
}

apps/api-gateway/src/main.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { CommonConstants } from '@credebl/common/common.constant';
1616
import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter';
1717
import { UpdatableValidationPipe } from '@credebl/common/custom-overrideable-validation-pipe';
1818
import * as useragent from 'express-useragent';
19+
import { EcosystemSwaggerFilter } from './authz/guards/ecosystem-swagger.filter';
1920

2021
dotenv.config();
2122

@@ -81,7 +82,15 @@ async function bootstrap(): Promise<void> {
8182
defaultVersion: ['1']
8283
});
8384

84-
const document = SwaggerModule.createDocument(app, options);
85+
// Create Swagger document
86+
let document = SwaggerModule.createDocument(app, options);
87+
try {
88+
const ecosystemFilter = app.get(EcosystemSwaggerFilter);
89+
document = await ecosystemFilter.filterDocument(document);
90+
} catch (err) {
91+
Logger.warn('Skipping EcosystemSwaggerFilter due to error', err as Error);
92+
}
93+
8594
SwaggerModule.setup('api', app, document);
8695
const httpAdapter: HttpAdapterHost = app.get(HttpAdapterHost) as HttpAdapterHost;
8796
app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));
@@ -122,17 +131,16 @@ async function bootstrap(): Promise<void> {
122131
if ('true' === process.env.DB_ALERT_ENABLE?.trim()?.toLowerCase()) {
123132
// in case it is enabled, log that
124133
Logger.log(
125-
'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',
134+
"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",
126135
'DB alert enabled'
127136
);
128137
}
129138

130139
if ('true' === (process.env.HIDE_EXPERIMENTAL_OIDC_CONTROLLERS || 'true').trim().toLowerCase()) {
131140
Logger.warn('Hiding experimental OIDC Controllers: OID4VC, OID4VP, x509 in OpenAPI docs');
132141
Logger.verbose(
133-
'To enable the use of experimental OIDC controllers. Set, \'HIDE_EXPERIMENTAL_OIDC_CONTROLLERS\' env variable to false'
142+
"To enable the use of experimental OIDC controllers. Set, 'HIDE_EXPERIMENTAL_OIDC_CONTROLLERS' env variable to false"
134143
);
135144
}
136-
137145
}
138146
bootstrap();

apps/api-gateway/src/platform/platform.controller.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Logger,
88
Param,
99
Post,
10+
Put,
1011
Query,
1112
Res,
1213
UseFilters,
@@ -33,6 +34,8 @@ import { OrgRoles } from 'libs/org-roles/enums';
3334
import { Roles } from '../authz/decorators/roles.decorator';
3435
import { OrgRolesGuard } from '../authz/guards/org-roles.guard';
3536
import { CreateEcosystemInvitationDto } from '../ecosystem/dtos/send-ecosystem-invitation';
37+
import { EnableEcosystemDto } from '../ecosystem/dtos/enable-ecosystem';
38+
import { EcosystemFeatureGuard } from '../authz/guards/ecosystem-feature-guard';
3639

3740
@Controller('')
3841
@UseFilters(CustomExceptionFilter)
@@ -234,7 +237,7 @@ export class PlatformController {
234237
description: 'Success',
235238
type: ApiResponseDto
236239
})
237-
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
240+
@UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemFeatureGuard)
238241
@ApiBearerAuth()
239242
async createInvitation(
240243
@Body() createEcosystemInvitationDto: CreateEcosystemInvitationDto,
@@ -264,7 +267,7 @@ export class PlatformController {
264267
description: 'Invitations fetched successfully'
265268
})
266269
@Roles(OrgRoles.PLATFORM_ADMIN)
267-
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
270+
@UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemFeatureGuard)
268271
@ApiBearerAuth()
269272
async getInvitations(@User() reqUser: user, @Res() res: Response): Promise<Response> {
270273
const invitations = await this.platformService.getInvitationsByUserId(reqUser.id);
@@ -275,4 +278,34 @@ export class PlatformController {
275278
data: invitations
276279
});
277280
}
281+
282+
/**
283+
* Update ecosystem enable/disable flag
284+
*/
285+
@Put('/config/ecosystem')
286+
@Roles(OrgRoles.PLATFORM_ADMIN)
287+
@ApiOperation({
288+
summary: 'Enable or disable ecosystem feature',
289+
description: 'Platform admin can enable or disable ecosystem feature on the platform'
290+
})
291+
@ApiResponse({
292+
status: HttpStatus.OK,
293+
description: 'Ecosystem configuration updated successfully',
294+
type: ApiResponseDto
295+
})
296+
@UseGuards(AuthGuard('jwt'), OrgRolesGuard)
297+
@ApiBearerAuth()
298+
async updateEcosystemConfig(
299+
@Body() platformConfigDto: EnableEcosystemDto,
300+
@User() reqUser: user,
301+
@Res() res: Response
302+
): Promise<Response> {
303+
await this.platformService.updateEcosystemConfig(platformConfigDto.isEcosystemEnabled, reqUser.id);
304+
305+
const finalResponse: IResponse = {
306+
statusCode: HttpStatus.OK,
307+
message: ResponseMessages.ecosystem.success.updateEcosystemConfig
308+
};
309+
return res.status(HttpStatus.OK).json(finalResponse);
310+
}
278311
}
Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1+
import { ClientsModule, Transport } from '@nestjs/microservices';
2+
3+
import { CommonConstants } from '@credebl/common/common.constant';
4+
import { ConfigModule } from '@nestjs/config';
5+
import { EcosystemModule as EcosystemServiceModule } from 'apps/ecosystem/src/ecosystem.module';
16
import { Module } from '@nestjs/common';
7+
import { NATSClient } from '@credebl/common/NATSClient';
28
import { PlatformController } from './platform.controller';
39
import { PlatformService } from './platform.service';
4-
import { ClientsModule, Transport } from '@nestjs/microservices';
5-
import { ConfigModule } from '@nestjs/config';
610
import { getNatsOptions } from '@credebl/common/nats.config';
7-
import { CommonConstants } from '@credebl/common/common.constant';
8-
import { NATSClient } from '@credebl/common/NATSClient';
11+
912
@Module({
1013
imports: [
14+
EcosystemServiceModule,
1115
ConfigModule.forRoot(),
1216
ClientsModule.register([
1317
{
@@ -18,6 +22,7 @@ import { NATSClient } from '@credebl/common/NATSClient';
1822
])
1923
],
2024
controllers: [PlatformController],
21-
providers: [PlatformService, NATSClient]
25+
providers: [PlatformService, NATSClient],
26+
exports: [EcosystemServiceModule]
2227
})
2328
export class PlatformModule {}

0 commit comments

Comments
 (0)