diff --git a/api-gateway/src/api/service/schema-rules.ts b/api-gateway/src/api/service/schema-rules.ts new file mode 100644 index 000000000..710bb5639 --- /dev/null +++ b/api-gateway/src/api/service/schema-rules.ts @@ -0,0 +1,336 @@ +import { IAuthUser, PinoLogger } from '@guardian/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Post, Put, Query, Response } from '@nestjs/common'; +import { Permissions } from '@guardian/interfaces'; +import { ApiBody, ApiInternalServerErrorResponse, ApiOkResponse, ApiOperation, ApiTags, ApiQuery, ApiExtraModels, ApiParam } from '@nestjs/swagger'; +import { Examples, InternalServerErrorDTO, SchemaRuleDTO, SchemaRuleRelationshipsDTO, pageHeader } from '#middlewares'; +import { Guardians, InternalException, EntityOwner } from '#helpers'; +import { AuthUser, Auth } from '#auth'; + +@Controller('schema-rules') +@ApiTags('schema-rules') +export class SchemaRulesApi { + constructor(private readonly logger: PinoLogger) { } + + /** + * Creates a new schema rule + */ + @Post('/') + @Auth(Permissions.SCHEMAS_RULE_CREATE) + @ApiOperation({ + summary: 'Creates a new schema rule.', + description: 'Creates a new schema rule.', + }) + @ApiBody({ + description: 'Configuration.', + type: SchemaRuleDTO, + required: true + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: SchemaRuleDTO, + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(SchemaRuleDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.CREATED) + async createSchemaRule( + @AuthUser() user: IAuthUser, + @Body() rule: SchemaRuleDTO + ): Promise { + try { + if (!rule) { + throw new HttpException('Invalid config.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.createSchemaRule(rule, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get page + */ + @Get('/') + @Auth(Permissions.SCHEMAS_RULE_READ) + @ApiOperation({ + summary: 'Return a list of all schema rules.', + description: 'Returns all schema rules.', + }) + @ApiQuery({ + name: 'pageIndex', + type: Number, + description: 'The number of pages to skip before starting to collect the result set', + required: false, + example: 0 + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + description: 'The numbers of items to return', + required: false, + example: 20 + }) + @ApiQuery({ + name: 'policyInstanceTopicId', + type: String, + description: 'Policy Instance Topic Id', + required: false, + example: Examples.ACCOUNT_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + isArray: true, + headers: pageHeader, + type: SchemaRuleDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(SchemaRuleDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getSchemaRules( + @AuthUser() user: IAuthUser, + @Response() res: any, + @Query('pageIndex') pageIndex?: number, + @Query('pageSize') pageSize?: number, + @Query('policyInstanceTopicId') policyInstanceTopicId?: string + ): Promise { + try { + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const { items, count } = await guardians.getSchemaRules({ policyInstanceTopicId, pageIndex, pageSize }, owner); + return res.header('X-Total-Count', count).send(items); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get statistic by id + */ + @Get('/:ruleId') + @Auth(Permissions.SCHEMAS_RULE_READ) + @ApiOperation({ + summary: 'Retrieves schema rule.', + description: 'Retrieves schema rule for the specified ID.' + }) + @ApiParam({ + name: 'ruleId', + type: String, + description: 'Schema rule Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: SchemaRuleDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(SchemaRuleDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getSchemaRuleById( + @AuthUser() user: IAuthUser, + @Param('ruleId') ruleId: string + ): Promise { + try { + if (!ruleId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.getSchemaRuleById(ruleId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Update schema rule + */ + @Put('/:ruleId') + @Auth(Permissions.SCHEMAS_RULE_CREATE) + @ApiOperation({ + summary: 'Updates schema rule.', + description: 'Updates schema rule configuration for the specified rule ID.', + }) + @ApiParam({ + name: 'ruleId', + type: 'string', + required: true, + description: 'Schema Rule Identifier', + example: Examples.DB_ID, + }) + @ApiBody({ + description: 'Object that contains a configuration.', + required: true, + type: SchemaRuleDTO + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: SchemaRuleDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(SchemaRuleDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async updateSchemaRule( + @AuthUser() user: IAuthUser, + @Param('ruleId') ruleId: string, + @Body() item: SchemaRuleDTO + ): Promise { + try { + if (!ruleId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const oldItem = await guardians.getSchemaRuleById(ruleId, owner); + if (!oldItem) { + throw new HttpException('Item not found.', HttpStatus.NOT_FOUND); + } + return await guardians.updateSchemaRule(ruleId, item, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Delete schema rule + */ + @Delete('/:ruleId') + @Auth(Permissions.SCHEMAS_RULE_CREATE) + @ApiOperation({ + summary: 'Deletes the schema rule.', + description: 'Deletes the schema rule with the provided ID.', + }) + @ApiParam({ + name: 'ruleId', + type: 'string', + required: true, + description: 'Schema Rule Identifier', + example: Examples.DB_ID, + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: Boolean + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async deleteSchemaRule( + @AuthUser() user: IAuthUser, + @Param('ruleId') ruleId: string + ): Promise { + try { + if (!ruleId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY) + } + const owner = new EntityOwner(user); + const guardians = new Guardians(); + return await guardians.deleteSchemaRule(ruleId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Publish schema rule + */ + @Put('/:ruleId/activate') + @Auth(Permissions.SCHEMAS_RULE_CREATE) + @ApiOperation({ + summary: 'Activates schema rule.', + description: 'Activates schema rule for the specified rule ID.', + }) + @ApiParam({ + name: 'ruleId', + type: 'string', + required: true, + description: 'Schema Rule Identifier', + example: Examples.DB_ID, + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: SchemaRuleDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @ApiExtraModels(SchemaRuleDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async activateSchemaRule( + @AuthUser() user: IAuthUser, + @Param('ruleId') ruleId: string + ): Promise { + try { + if (!ruleId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const oldItem = await guardians.getSchemaRuleById(ruleId, owner); + if (!oldItem) { + throw new HttpException('Item not found.', HttpStatus.NOT_FOUND); + } + return await guardians.activateSchemaRule(ruleId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } + + /** + * Get relationships by id + */ + @Get('/:ruleId/relationships') + @Auth(Permissions.SCHEMAS_RULE_READ) + @ApiOperation({ + summary: 'Retrieves schema rule relationships.', + description: 'Retrieves schema rule relationships for the specified ID.' + }) + @ApiParam({ + name: 'ruleId', + type: String, + description: 'Schema Rule Identifier', + required: true, + example: Examples.DB_ID + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: SchemaRuleRelationshipsDTO + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(SchemaRuleRelationshipsDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getSchemaRuleRelationships( + @AuthUser() user: IAuthUser, + @Param('ruleId') ruleId: string + ): Promise { + try { + if (!ruleId) { + throw new HttpException('Invalid ID.', HttpStatus.UNPROCESSABLE_ENTITY); + } + const owner = new EntityOwner(user); + const guardian = new Guardians(); + return await guardian.getSchemaRuleRelationships(ruleId, owner); + } catch (error) { + await InternalException(error, this.logger); + } + } +} diff --git a/api-gateway/src/helpers/guardians.ts b/api-gateway/src/helpers/guardians.ts index 8d9fbe971..141cd6122 100644 --- a/api-gateway/src/helpers/guardians.ts +++ b/api-gateway/src/helpers/guardians.ts @@ -38,7 +38,9 @@ import { StatisticDefinitionDTO, StatisticAssessmentDTO, StatisticAssessmentRelationshipsDTO, - StatisticDefinitionRelationshipsDTO + StatisticDefinitionRelationshipsDTO, + SchemaRuleDTO, + SchemaRuleRelationshipsDTO } from '#middlewares'; /** @@ -591,9 +593,9 @@ export class Guardians extends NatsService { */ public async getSchemaByType(type: string, owner?: string): Promise { if (owner) { - return await this.sendMessage(MessageAPI.GET_SCHEMA, {type, owner}); + return await this.sendMessage(MessageAPI.GET_SCHEMA, { type, owner }); } else { - return await this.sendMessage(MessageAPI.GET_SCHEMA, {type}); + return await this.sendMessage(MessageAPI.GET_SCHEMA, { type }); } } @@ -2880,7 +2882,7 @@ export class Guardians extends NatsService { /** * Get statistic definition * - * @param id + * @param definitionId * @param owner * @returns Operation Success */ @@ -2891,7 +2893,7 @@ export class Guardians extends NatsService { /** * Get relationships * - * @param id + * @param definitionId * @param owner * * @returns Relationships @@ -2903,7 +2905,7 @@ export class Guardians extends NatsService { /** * Return documents * - * @param id + * @param definitionId * @param owner * @param pageIndex * @param pageSize @@ -2922,7 +2924,7 @@ export class Guardians extends NatsService { /** * Update statistic definition * - * @param id + * @param definitionId * @param definition * @param owner * @@ -2939,7 +2941,7 @@ export class Guardians extends NatsService { /** * Delete statistic definition * - * @param id + * @param definitionId * @param owner * * @returns Operation Success @@ -2949,9 +2951,9 @@ export class Guardians extends NatsService { } /** - * Delete statistic definition + * Publish statistic definition * - * @param id + * @param definitionId * @param owner * * @returns Operation Success @@ -2963,8 +2965,8 @@ export class Guardians extends NatsService { /** * Create statistic assessment * - * @param id - * @param report + * @param definitionId + * @param assessment * @param owner * * @returns statistic report @@ -3027,4 +3029,108 @@ export class Guardians extends NatsService { ): Promise { return await this.sendMessage(MessageAPI.GET_STATISTIC_ASSESSMENT_RELATIONSHIPS, { definitionId, assessmentId, owner }); } + + + + + + + + + + + + + + + + + + + /** + * Create schema rule + * + * @param rule + * @param owner + * @returns schema rule + */ + public async createSchemaRule(rule: SchemaRuleDTO, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.CREATE_SCHEMA_RULE, { rule, owner }); + } + + /** + * Return schema rules + * + * @param filters + * @param owner + * + * @returns {ResponseAndCount} + */ + public async getSchemaRules(filters: IFilter, owner: IOwner): Promise> { + return await this.sendMessage(MessageAPI.GET_SCHEMA_RULES, { filters, owner }); + } + + /** + * Get schema rule + * + * @param ruleId + * @param owner + * @returns Operation Success + */ + public async getSchemaRuleById(ruleId: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.GET_SCHEMA_RULE, { ruleId, owner }); + } + + /** + * Get relationships + * + * @param ruleId + * @param owner + * + * @returns Relationships + */ + public async getSchemaRuleRelationships(ruleId: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.GET_SCHEMA_RULE_RELATIONSHIPS, { ruleId, owner }); + } + + /** + * Update schema rule + * + * @param ruleId + * @param definition + * @param owner + * + * @returns theme + */ + public async updateSchemaRule( + ruleId: string, + rule: SchemaRuleDTO, + owner: IOwner + ): Promise { + return await this.sendMessage(MessageAPI.UPDATE_SCHEMA_RULE, { ruleId, rule, owner }); + } + + /** + * Delete schema rule + * + * @param ruleId + * @param owner + * + * @returns Operation Success + */ + public async deleteSchemaRule(ruleId: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.DELETE_SCHEMA_RULE, { ruleId, owner }); + } + + /** + * Activate schema rule + * + * @param ruleId + * @param owner + * + * @returns Operation Success + */ + public async activateSchemaRule(ruleId: string, owner: IOwner): Promise { + return await this.sendMessage(MessageAPI.ACTIVATE_SCHEMA_RULE, { ruleId, owner }); + } } diff --git a/api-gateway/src/middlewares/validation/schemas/index.ts b/api-gateway/src/middlewares/validation/schemas/index.ts index bbcaf1a40..1042cbd57 100644 --- a/api-gateway/src/middlewares/validation/schemas/index.ts +++ b/api-gateway/src/middlewares/validation/schemas/index.ts @@ -29,4 +29,5 @@ export * from './schemas.dto.js' export * from './policies.dto.js' export * from './profiles.dto.js' export * from './worker-tasks.dto.js' -export * from './policy-statistics.dto.js' \ No newline at end of file +export * from './policy-statistics.dto.js' +export * from './schema-rules.dto.js' \ No newline at end of file diff --git a/api-gateway/src/middlewares/validation/schemas/schema-rules.dto.ts b/api-gateway/src/middlewares/validation/schemas/schema-rules.dto.ts new file mode 100644 index 000000000..8d97c4e6a --- /dev/null +++ b/api-gateway/src/middlewares/validation/schemas/schema-rules.dto.ts @@ -0,0 +1,107 @@ +import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; +import { Examples } from '../examples.js'; +import { IsArray, IsObject, IsOptional, IsString } from 'class-validator'; +import { EntityStatus } from '@guardian/interfaces'; + +export class SchemaRuleDTO { + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + id?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.UUID + }) + @IsOptional() + @IsString() + uuid?: string; + + @ApiProperty({ + type: 'string', + required: true, + example: 'Tool name' + }) + @IsString() + name: string; + + @ApiProperty({ + type: 'string', + required: false, + example: 'Description' + }) + @IsOptional() + @IsString() + description?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DID + }) + @IsOptional() + @IsString() + creator?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DID + }) + @IsOptional() + @IsString() + owner?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.DB_ID + }) + @IsOptional() + @IsString() + policyId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.ACCOUNT_ID + }) + @IsOptional() + @IsString() + policyTopicId?: string; + + @ApiProperty({ + type: 'string', + required: false, + example: Examples.ACCOUNT_ID + }) + @IsOptional() + @IsString() + policyInstanceTopicId?: string; + + @ApiProperty({ + type: 'string', + required: false, + enum: EntityStatus, + example: EntityStatus.DRAFT + }) + @IsOptional() + @IsString() + status?: EntityStatus; + + @ApiProperty({ + type: 'object', + nullable: true, + required: false + }) + @IsOptional() + @IsObject() + config?: any; +} + +export class SchemaRuleRelationshipsDTO { + +} diff --git a/common/src/database-modules/database-server.ts b/common/src/database-modules/database-server.ts index 9ff6e6433..311d4e0d5 100644 --- a/common/src/database-modules/database-server.ts +++ b/common/src/database-modules/database-server.ts @@ -39,7 +39,8 @@ import { PolicyTest, Artifact, PolicyStatistic, - PolicyStatisticDocument + PolicyStatisticDocument, + SchemaRule } from '../entity/index.js'; import { Binary } from 'bson'; import { @@ -397,7 +398,7 @@ export class DatabaseServer extends AbstractDatabaseServer { * @param filter */ async save(entityClass: new () => T, item: unknown | unknown[], filter?: FilterObject): Promise { - if(Array.isArray(item)) { + if (Array.isArray(item)) { return await this.saveMany(entityClass, item, filter) as any } @@ -434,7 +435,7 @@ export class DatabaseServer extends AbstractDatabaseServer { criteria: FilterQuery, row: unknown | unknown[] ): Promise { - if(Array.isArray(criteria)) { + if (Array.isArray(criteria)) { return await this.updateMany(entityClass, row as unknown as T[], criteria) as any } @@ -3919,4 +3920,51 @@ export class DatabaseServer extends AbstractDatabaseServer { ): Promise { return await new DataBaseHelper(PolicyStatisticDocument).count(filters); } + + /** + * Create Schema Rule + * @param rule + */ + public static async createSchemaRule( + rule: FilterObject + ): Promise { + const item = new DataBaseHelper(SchemaRule).create(rule); + return await new DataBaseHelper(SchemaRule).save(item); + } + + /** + * Get Schema Rule + * @param filters + * @param options + */ + public static async getSchemaRulesAndCount( + filters?: FilterObject, + options?: FindOptions + ): Promise<[SchemaRule[], number]> { + return await new DataBaseHelper(SchemaRule).findAndCount(filters, options); + } + + /** + * Get Schema Rule By ID + * @param id + */ + public static async getSchemaRuleById(id: string): Promise { + return await new DataBaseHelper(SchemaRule).findOne(id); + } + + /** + * Update Schema Rule + * @param rule + */ + public static async updateSchemaRule(rule: SchemaRule): Promise { + return await new DataBaseHelper(SchemaRule).update(rule); + } + + /** + * Delete Schema Rule + * @param rule + */ + public static async removeSchemaRule(rule: SchemaRule): Promise { + return await new DataBaseHelper(SchemaRule).remove(rule); + } } diff --git a/common/src/entity/index.ts b/common/src/entity/index.ts index d27640e78..880d72cb2 100644 --- a/common/src/entity/index.ts +++ b/common/src/entity/index.ts @@ -44,4 +44,5 @@ export * from './assign-entity.js'; export * from './policy-test.js'; export * from './log.js'; export * from './policy-statistic.js'; -export * from './policy-statistic-document.js'; \ No newline at end of file +export * from './policy-statistic-document.js'; +export * from './schema-rule.js'; \ No newline at end of file diff --git a/common/src/entity/schema-rule.ts b/common/src/entity/schema-rule.ts new file mode 100644 index 000000000..7939034e3 --- /dev/null +++ b/common/src/entity/schema-rule.ts @@ -0,0 +1,90 @@ +import { BeforeCreate, Entity, Property } from '@mikro-orm/core'; +import { BaseEntity } from '../models/index.js'; +import { EntityStatus, GenerateUUIDv4, IStatisticConfig } from '@guardian/interfaces'; + +/** + * SchemaRule collection + */ +@Entity() +export class SchemaRule extends BaseEntity { + /** + * ID + */ + @Property({ nullable: true }) + uuid?: string; + + /** + * Label + */ + @Property({ nullable: true }) + name?: string; + + /** + * Description + */ + @Property({ nullable: true }) + description?: string; + + /** + * Owner + */ + @Property({ + nullable: true, + index: true + }) + owner?: string; + + /** + * Creator + */ + @Property({ nullable: true }) + creator?: string; + + /** + * Status + */ + @Property({ nullable: true }) + status?: EntityStatus; + + /** + * Policy id + */ + @Property({ + nullable: true, + index: true + }) + policyId?: string; + + /** + * Policy Topic id + */ + @Property({ + nullable: true, + index: true + }) + policyTopicId?: string; + + /** + * Policy Instance Topic id + */ + @Property({ + nullable: true, + index: true + }) + policyInstanceTopicId?: string; + + /** + * Config + */ + @Property({ nullable: true, type: 'unknown' }) + config?: IStatisticConfig; + + /** + * Set defaults + */ + @BeforeCreate() + setDefaults() { + this.uuid = this.uuid || GenerateUUIDv4(); + this.status = this.status || EntityStatus.DRAFT; + } +} diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 75188743a..08096d84c 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -50,6 +50,8 @@ import { StatisticAssessmentsComponent } from './modules/policy-statistics/stati import { StatisticAssessmentConfigurationComponent } from './modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component'; import { StatisticDefinitionConfigurationComponent } from './modules/policy-statistics/statistic-definition-configuration/statistic-definition-configuration.component'; import { StatisticDefinitionsComponent } from './modules/policy-statistics/statistic-definitions/statistic-definitions.component'; +import { SchemaRulesComponent } from './modules/schema-rules/schema-rules/schema-rules.component'; +import { SchemaRuleConfigurationComponent } from './modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component'; @Injectable({ providedIn: 'root' @@ -499,7 +501,13 @@ const routes: Routes = [ component: StatisticDefinitionsComponent, canActivate: [PermissionsGuard], data: { - roles: [UserRole.USER] + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.STATISTICS_STATISTIC_READ + ] } }, { @@ -507,7 +515,13 @@ const routes: Routes = [ component: StatisticDefinitionConfigurationComponent, canActivate: [PermissionsGuard], data: { - roles: [UserRole.USER] + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.STATISTICS_STATISTIC_READ + ] } }, { @@ -515,7 +529,13 @@ const routes: Routes = [ component: StatisticAssessmentConfigurationComponent, canActivate: [PermissionsGuard], data: { - roles: [UserRole.USER] + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.STATISTICS_STATISTIC_READ + ] } }, { @@ -523,7 +543,13 @@ const routes: Routes = [ component: StatisticAssessmentsComponent, canActivate: [PermissionsGuard], data: { - roles: [UserRole.USER] + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.STATISTICS_STATISTIC_READ + ] } }, { @@ -531,11 +557,44 @@ const routes: Routes = [ component: StatisticAssessmentViewComponent, canActivate: [PermissionsGuard], data: { - roles: [UserRole.USER] + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.STATISTICS_STATISTIC_READ + ] } }, - + { + path: 'schema-rules', + component: SchemaRulesComponent, + canActivate: [PermissionsGuard], + data: { + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.SCHEMAS_RULE_READ + ] + } + }, + { + path: 'schema-rule/:ruleId', + component: SchemaRuleConfigurationComponent, + canActivate: [PermissionsGuard], + data: { + roles: [ + UserRole.STANDARD_REGISTRY, + UserRole.USER + ], + permissions: [ + Permissions.SCHEMAS_RULE_READ + ] + } + }, { path: '', component: HomeComponent }, { path: 'info', component: InfoComponent }, diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 33cef8172..a73c816aa 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -35,6 +35,8 @@ import { MapService } from './services/map.service'; import { WizardService } from './modules/policy-engine/services/wizard.service'; import { NotificationService } from './services/notify.service'; import { PermissionsService } from './services/permissions.service'; +import { WorkerTasksService } from './services/worker-tasks.service'; +import { SchemaRulesService } from './services/schema-rules.service'; //Views import { UserProfileComponent } from './views/user-profile/user-profile.component'; import { LoginComponent } from './views/login/login.component'; @@ -74,6 +76,7 @@ import { SchemaEngineModule } from './modules/schema-engine/schema-engine.module import { ThemeService } from './services/theme.service'; import { RecordService } from './services/record.service'; import { PolicyStatisticsModule } from './modules/policy-statistics/policy-statistics.module'; +import { SchemaRulesModule } from './modules/schema-rules/schema-rules.module'; // Injectors import { GET_SCHEMA_NAME } from './injectors/get-schema-name.injector'; import { BLOCK_TYPE_TIPS, BLOCK_TYPE_TIPS_VALUE, } from './injectors/block-type-tips.injector'; @@ -126,7 +129,6 @@ import '../prototypes/date-prototype'; import { OnlyForDemoDirective } from './directives/onlyfordemo.directive'; import { UseWithServiceDirective } from './directives/use-with-service.directive'; import { WorkerTasksComponent } from './views/worker-tasks/worker-tasks.component'; -import { WorkerTasksService } from './services/worker-tasks.service'; @NgModule({ declarations: [ @@ -187,6 +189,7 @@ import { WorkerTasksService } from './services/worker-tasks.service'; SchemaEngineModule, PolicyEngineModule, PolicyStatisticsModule, + SchemaRulesModule, TagEngineModule, CompareModule, ToastrModule.forRoot(), @@ -229,6 +232,7 @@ import { WorkerTasksService } from './services/worker-tasks.service'; AuditService, PolicyEngineService, PolicyStatisticsService, + SchemaRulesService, PolicyHelper, IPFSService, ArtifactService, diff --git a/frontend/src/app/modules/policy-statistics/models/grid.ts b/frontend/src/app/modules/common/models/grid.ts similarity index 100% rename from frontend/src/app/modules/policy-statistics/models/grid.ts rename to frontend/src/app/modules/common/models/grid.ts diff --git a/frontend/src/app/modules/policy-statistics/models/assessment.ts b/frontend/src/app/modules/policy-statistics/models/assessment.ts index 83edd8032..c62258e7a 100644 --- a/frontend/src/app/modules/policy-statistics/models/assessment.ts +++ b/frontend/src/app/modules/policy-statistics/models/assessment.ts @@ -1,5 +1,5 @@ import { IVCDocument } from "@guardian/interfaces"; -import { IColumn } from "./grid"; +import { IColumn } from "../../common/models/grid"; export interface IOption { id: string; diff --git a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts index c90ab28cd..912e50404 100644 --- a/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts +++ b/frontend/src/app/modules/policy-statistics/statistic-assessment-configuration/statistic-assessment-configuration.component.ts @@ -8,7 +8,7 @@ import { SchemaService } from 'src/app/services/schema.service'; import { DialogService } from 'primeng/dynamicdialog'; import { Formula } from 'src/app/utils'; import { IDocument, IFormula, IOption, IScore, IVariable } from '../models/assessment'; -import { IColumn } from '../models/grid'; +import { IColumn } from '../../common/models/grid'; @Component({ selector: 'app-statistic-assessment-configuration', diff --git a/frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.html b/frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.html new file mode 100644 index 000000000..5bfdfc5d6 --- /dev/null +++ b/frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.html @@ -0,0 +1,75 @@ +
+
+
Create New
+
{{policy.name}}
+
+
+ +
+
+
+
+
+
+
+ +
+
+ + +
+
+ + +
+ +
+ + + +
+ Policy + {{ currentPolicy.name }} +
+
+ + +
+
+ +
+ {{policy.name}} + ({{policy.version}}) +
+
+ +
+
+
+
+
+ +
+
+ \ No newline at end of file diff --git a/frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.scss b/frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.scss new file mode 100644 index 000000000..2c8009ba8 --- /dev/null +++ b/frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.scss @@ -0,0 +1,51 @@ +.context { + position: relative; + overflow-y: auto; + padding: 14px 0 20px 0; + display: flex; + flex-direction: row; + height: 100%; + font-family: Inter, serif; + font-style: normal; +} + +.dialog-header { + .header-item { + padding: 6px 12px; + margin: 0px 16px; + border-radius: 6px; + font-size: var(--guardian-primary-font-size); + color: var(--guardian-dry-run-color); + background: var(--guardian-dry-run-background); + user-select: none; + cursor: default; + } +} + +form { + width: 100%; +} + +.guardian-input-container { + margin-bottom: 24px; +} + +.dialog-body { + height: 300px; +} + +.action-buttons { + display: flex; + justify-content: flex-end; + user-select: none; + + &>div { + margin-left: 15px; + margin-right: 0px; + } +} + +.close-icon-color { + fill: #848FA9; + color: #848FA9; +} \ No newline at end of file diff --git a/frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.ts b/frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.ts new file mode 100644 index 000000000..862ebf5c1 --- /dev/null +++ b/frontend/src/app/modules/schema-rules/dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component.ts @@ -0,0 +1,62 @@ +import { Component } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; + +@Component({ + selector: 'new-schema-rule-dialog', + templateUrl: './new-schema-rule-dialog.component.html', + styleUrls: ['./new-schema-rule-dialog.component.scss'], +}) +export class NewSchemaRuleDialog { + public loading = true; + public policy: any; + public policies: any[]; + public dataForm = new FormGroup({ + name: new FormControl('', Validators.required), + description: new FormControl(''), + policy: new FormControl(null, Validators.required) + }); + + constructor( + public ref: DynamicDialogRef, + public config: DynamicDialogConfig, + private dialogService: DialogService, + ) { + this.policies = this.config.data?.policies || []; + this.policies = this.policies.filter((p) => p.instanceTopicId); + const instanceTopicId = this.config.data?.policy?.instanceTopicId; + this.policy = this.policies.find((p) => p.instanceTopicId === instanceTopicId) || null; + this.dataForm.setValue({ + name: '', + description: '', + policy: this.policy + }) + } + + public get currentPolicy(): any { + return this.dataForm.value.policy; + } + + ngOnInit() { + this.loading = false; + } + + ngOnDestroy(): void { + } + + public onClose(): void { + this.ref.close(null); + } + + public onSubmit(): void { + if (this.dataForm.valid) { + const { name, description, policy } = this.dataForm.value; + this.ref.close({ + name, + description, + policyId: policy?.id, + instanceTopicId: policy?.instanceTopicId, + }); + } + } +} diff --git a/frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.html b/frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.html new file mode 100644 index 000000000..184ebcdb4 --- /dev/null +++ b/frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.html @@ -0,0 +1,42 @@ +
+
+
+
+ +
+ Before starting work you need to get DID + here +
+ +
+
+ +
+ +
+ {{item?.name}} +
+ Policy Name: {{policy.name}} + Version: {{policy.version}} +
+
+
+ +
+ + +
+
+ +
+
\ No newline at end of file diff --git a/frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.scss b/frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.scss new file mode 100644 index 000000000..4650383bd --- /dev/null +++ b/frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.scss @@ -0,0 +1,64 @@ +.guardian-page { + position: relative; + padding: 0px; + user-select: none; + background: var(--guardian-grey-background, #F9FAFC); + + .header-container { + padding: 56px 48px 10px 48px; + background: var(--guardian-primary-color, #4169E2); + min-height: 178px; + height: 178px; + + .guardian-user-back-button { + button { + border-color: var(--guardian-background, #FFFFFF); + } + } + + .guardian-user-page-header { + color: var(--guardian-background, #FFFFFF); + } + + .policy-name { + color: var(--guardian-background, #FFFFFF); + font-size: 14px; + font-weight: 500; + line-height: 16px; + position: absolute; + top: 34px; + right: 0; + + .policy-version { + padding-left: 16px; + } + } + } + + .actions-container { + min-height: 64px; + height: 64px; + width: 100%; + display: flex; + justify-content: flex-end; + padding: 12px 48px; + background: var(--guardian-background, #FFFFFF); + border-top: 1px solid var(--guardian-border-color, #E1E7EF); + position: relative; + + button { + height: 40px; + width: 135px; + margin-left: 16px; + } + } + + .body-container { + display: flex; + width: 100%; + height: 100%; + overflow: hidden; + + + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.ts b/frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.ts new file mode 100644 index 000000000..ce4a64256 --- /dev/null +++ b/frontend/src/app/modules/schema-rules/schema-rule-configuration/schema-rule-configuration.component.ts @@ -0,0 +1,97 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { GenerateUUIDv4, IStatistic, Schema, UserPermissions } from '@guardian/interfaces'; +import { forkJoin, Subscription } from 'rxjs'; +import { ProfileService } from 'src/app/services/profile.service'; +import { SchemaService } from 'src/app/services/schema.service'; +import { DialogService } from 'primeng/dynamicdialog'; +import { SchemaRulesService } from 'src/app/services/schema-rules.service'; + +@Component({ + selector: 'app-schema-rule-configuration', + templateUrl: './schema-rule-configuration.component.html', + styleUrls: ['./schema-rule-configuration.component.scss'], +}) +export class SchemaRuleConfigurationComponent implements OnInit { + public readonly title: string = 'Configuration'; + + public loading: boolean = true; + public isConfirmed: boolean = false; + public user: UserPermissions = new UserPermissions(); + public owner: string; + public ruleId: string; + public item: IStatistic | undefined; + public policy: any; + public stepper = [true, false, false, false]; + public stepIndex = 0; + + private subscription = new Subscription(); + + constructor( + private profileService: ProfileService, + private schemaService: SchemaService, + private schemaRulesService: SchemaRulesService, + private dialogService: DialogService, + private router: Router, + private route: ActivatedRoute + ) { + } + + ngOnInit() { + this.subscription.add( + this.route.queryParams.subscribe((queryParams) => { + this.loadProfile(); + }) + ); + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } + + private loadProfile() { + this.isConfirmed = false; + this.loading = true; + this.profileService + .getProfile() + .subscribe((profile) => { + this.isConfirmed = !!(profile && profile.confirmed); + this.user = new UserPermissions(profile); + this.owner = this.user.did; + + if (this.isConfirmed) { + this.loadData(); + } else { + setTimeout(() => { + this.loading = false; + }, 500); + } + }, (e) => { + this.loading = false; + }); + } + + private loadData() { + this.ruleId = this.route.snapshot.params['ruleId']; + this.loading = true; + forkJoin([ + this.schemaRulesService.getRule(this.ruleId), + this.schemaRulesService.getRelationships(this.ruleId) + ]).subscribe(([item, relationships]) => { + this.updateMetadata(item, relationships) + }, (e) => { + this.loading = false; + }); + } + + private updateMetadata( + item: any, + relationships: any + ) { + + } + + public onBack() { + this.router.navigate(['/policy-statistics']); + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/schema-rules/schema-rules.module.ts b/frontend/src/app/modules/schema-rules/schema-rules.module.ts new file mode 100644 index 000000000..92e469272 --- /dev/null +++ b/frontend/src/app/modules/schema-rules/schema-rules.module.ts @@ -0,0 +1,55 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MaterialModule } from 'src/app/modules/common/material.module'; +import { FormsModule } from '@angular/forms'; +import { InputTextModule } from 'primeng/inputtext'; +import { AppRoutingModule } from 'src/app/app-routing.module'; +import { SchemaEngineModule } from '../schema-engine/schema-engine.module'; +import { CommonComponentsModule } from '../common/common-components.module'; +import { AngularSvgIconModule } from 'angular-svg-icon'; +import { DialogService, DynamicDialogModule } from 'primeng/dynamicdialog'; +import { TableModule } from 'primeng/table'; +import { TooltipModule } from 'primeng/tooltip'; +import { DropdownModule } from 'primeng/dropdown'; +import { TabViewModule } from 'primeng/tabview'; +import { CheckboxModule } from 'primeng/checkbox'; +import { RadioButtonModule } from 'primeng/radiobutton'; +import { CodemirrorModule } from '@ctrl/ngx-codemirror'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { OverlayPanelModule } from 'primeng/overlaypanel'; +import { SchemaRulesComponent } from './schema-rules/schema-rules.component'; +import { NewSchemaRuleDialog } from './dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component'; +import { SchemaRuleConfigurationComponent } from './schema-rule-configuration/schema-rule-configuration.component'; + +@NgModule({ + declarations: [ + SchemaRulesComponent, + SchemaRuleConfigurationComponent, + NewSchemaRuleDialog + ], + imports: [ + CommonModule, + FormsModule, + MaterialModule, + CommonComponentsModule, + SchemaEngineModule, + AppRoutingModule, + DynamicDialogModule, + TableModule, + TooltipModule, + InputTextModule, + DropdownModule, + TabViewModule, + CheckboxModule, + RadioButtonModule, + CodemirrorModule, + MultiSelectModule, + OverlayPanelModule, + AngularSvgIconModule.forRoot(), + ], + exports: [], + providers: [ + DialogService + ], +}) +export class SchemaRulesModule { } diff --git a/frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.html b/frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.html new file mode 100644 index 000000000..f9c982973 --- /dev/null +++ b/frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.html @@ -0,0 +1,161 @@ +
+
+
+
+ +
+ Before starting work you need to get DID + here +
+ +
+ +
+ +
+ {{title}} +
+ +
+
+ + +
+ Policy + {{ currentPolicy.name }} +
+
+ Policy + All +
+
+ +
+ {{policy.name}} + ({{policy.version}}) +
+
+ All +
+
+
+
+
+ +
+
+ +
+ +
+ + + + + {{column.title}} + + + + + + + + +
+ + +
+
+ + + + + +
+ + +
+
+ + {{row[column.id]}} + + +
+ +
+
+
+ +
+
+
+ +
+ + There were no rule created yet + Please create new rule to see the data +
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.scss b/frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.scss new file mode 100644 index 000000000..0443d14a4 --- /dev/null +++ b/frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.scss @@ -0,0 +1,28 @@ +.policy-name { + color: var(--guardian-disabled-color, #848FA9); + font-size: 14px; + font-weight: 500; + line-height: 16px; + position: absolute; + top: 34px; + right: 0; + + .policy-version { + padding-left: 16px; + } +} +.guardian-dropdown { + &::ng-deep .p-dropdown { + width: 250px; + } +} + +.grid-btn { + width: 80px; + height: 30px; + margin-right: 16px; + + &:last-child { + margin-right: 0px; + } +} \ No newline at end of file diff --git a/frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.ts b/frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.ts new file mode 100644 index 000000000..d3e4e63f6 --- /dev/null +++ b/frontend/src/app/modules/schema-rules/schema-rules/schema-rules.component.ts @@ -0,0 +1,266 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { IStatistic, UserPermissions } from '@guardian/interfaces'; +import { forkJoin, Subscription } from 'rxjs'; +import { PolicyEngineService } from 'src/app/services/policy-engine.service'; +import { ProfileService } from 'src/app/services/profile.service'; +import { DialogService } from 'primeng/dynamicdialog'; +import { SchemaRulesService } from 'src/app/services/schema-rules.service'; +import { CustomCustomDialogComponent } from '../../common/custom-confirm-dialog/custom-confirm-dialog.component'; +import { NewSchemaRuleDialog } from '../dialogs/new-schema-rule-dialog/new-schema-rule-dialog.component'; + +interface IColumn { + id: string; + title: string; + type: string; + size: string; + tooltip: boolean; + permissions?: (user: UserPermissions) => boolean; + canDisplay?: () => boolean; +} + +@Component({ + selector: 'app-schema-rules', + templateUrl: './schema-rules.component.html', + styleUrls: ['./schema-rules.component.scss'], +}) +export class SchemaRulesComponent implements OnInit { + public readonly title: string = 'Statistics'; + + public loading: boolean = true; + public isConfirmed: boolean = false; + public user: UserPermissions = new UserPermissions(); + public owner: string; + public page: any[]; + public pageIndex: number; + public pageSize: number; + public pageCount: number; + public columns: IColumn[]; + public allPolicies: any[] = []; + public currentPolicy: any = null; + + private subscription = new Subscription(); + + constructor( + private profileService: ProfileService, + private schemaRulesService: SchemaRulesService, + private policyEngineService: PolicyEngineService, + private dialogService: DialogService, + private router: Router, + private route: ActivatedRoute + ) { + this.columns = [{ + id: 'name', + title: 'Name', + type: 'text', + size: 'auto', + tooltip: true + }, { + id: 'policy', + title: 'Policy', + type: 'text', + size: 'auto', + tooltip: false + }, { + id: 'topicId', + title: 'Topic', + type: 'text', + size: '135', + tooltip: false + }, { + id: 'status', + title: 'Status', + type: 'text', + size: '180', + tooltip: false + }, { + id: 'documents', + title: 'Documents', + type: 'text', + size: '125', + tooltip: false + }, { + id: 'edit', + title: '', + type: 'text', + size: '56', + tooltip: false + }, { + id: 'options', + title: '', + type: 'text', + size: '210', + tooltip: false + }, { + id: 'delete', + title: '', + type: 'text', + size: '64', + tooltip: false + }] + } + + ngOnInit() { + this.page = []; + this.pageIndex = 0; + this.pageSize = 10; + this.pageCount = 0; + this.subscription.add( + this.route.queryParams.subscribe((queryParams) => { + this.loadProfile(); + }) + ); + // this.loadProfile(); + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } + + private loadProfile() { + // const policyId = this.route.snapshot.params['policyId']; + this.isConfirmed = false; + this.loading = true; + forkJoin([ + this.profileService.getProfile(), + this.policyEngineService.all(), + ]).subscribe(([profile, policies]) => { + this.isConfirmed = !!(profile && profile.confirmed); + this.user = new UserPermissions(profile); + this.owner = this.user.did; + this.allPolicies = policies || []; + this.allPolicies.unshift({ + name: 'All', + instanceTopicId: null + }); + this.allPolicies.forEach((p: any) => p.label = p.name); + + const topic = this.route.snapshot.queryParams['topic']; + this.currentPolicy = + this.allPolicies.find((p) => p.instanceTopicId === topic) || + this.allPolicies[0]; + + if (this.isConfirmed) { + this.loadData(); + } else { + setTimeout(() => { + this.loading = false; + }, 500); + } + }, (e) => { + this.loading = false; + }); + } + + private loadData() { + const filters: any = {}; + if (this.currentPolicy?.instanceTopicId) { + filters.policyInstanceTopicId = this.currentPolicy?.instanceTopicId; + } + this.loading = true; + this.schemaRulesService + .getRules( + this.pageIndex, + this.pageSize, + filters + ) + .subscribe((response) => { + const { page, count } = this.schemaRulesService.parsePage(response); + this.page = page; + this.pageCount = count; + for (const item of this.page) { + item.policy = this.allPolicies.find((p) => p.id && p.id === item.policyId)?.name; + } + setTimeout(() => { + this.loading = false; + }, 500); + }, (e) => { + this.loading = false; + }); + } + + public onBack() { + this.router.navigate(['/policy-viewer']); + } + + public onPage(event: any): void { + if (this.pageSize != event.pageSize) { + this.pageIndex = 0; + this.pageSize = event.pageSize; + } else { + this.pageIndex = event.pageIndex; + this.pageSize = event.pageSize; + } + this.loadData(); + } + + public onFilter(event: any) { + if (event.value === null) { + this.currentPolicy = this.allPolicies[0]; + } + this.pageIndex = 0; + const topic = this.currentPolicy?.instanceTopicId || 'all' + this.router.navigate(['/schema-rules'], { queryParams: { topic } }); + this.loadData(); + } + + public onCreate() { + const dialogRef = this.dialogService.open(NewSchemaRuleDialog, { + showHeader: false, + header: 'Create New', + width: '640px', + styleClass: 'guardian-dialog', + data: { + policies: this.allPolicies, + policy: this.currentPolicy, + } + }); + dialogRef.onClose.subscribe(async (result) => { + if (result) { + this.loading = true; + this.schemaRulesService + .createRules(result) + .subscribe((newItem) => { + this.loadData(); + }, (e) => { + this.loading = false; + }); + } + }); + } + + public onEdit(item: any) { + this.router.navigate(['/schema-rules', item.id]); + } + + public onDelete(item: any) { + const dialogRef = this.dialogService.open(CustomCustomDialogComponent, { + showHeader: false, + width: '640px', + styleClass: 'guardian-dialog', + data: { + header: 'Delete Rules', + text: `Are you sure want to delete rules (${item.name})?`, + buttons: [{ + name: 'Close', + class: 'secondary' + }, { + name: 'Delete', + class: 'delete' + }] + }, + }); + dialogRef.onClose.subscribe((result: string) => { + if (result === 'Delete') { + this.loading = true; + this.schemaRulesService + .deleteRule(item) + .subscribe((result) => { + this.loadData(); + }, (e) => { + this.loading = false; + }); + } + }); + } +} \ No newline at end of file diff --git a/frontend/src/app/services/schema-rules.service.ts b/frontend/src/app/services/schema-rules.service.ts new file mode 100644 index 000000000..e14b544cc --- /dev/null +++ b/frontend/src/app/services/schema-rules.service.ts @@ -0,0 +1,75 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { API_BASE_URL } from './api'; + +/** + * Services for working from statistics and separate blocks. + */ +@Injectable() +export class SchemaRulesService { + private readonly url: string = `${API_BASE_URL}/schema-rules`; + + constructor(private http: HttpClient) { + } + + public static getOptions( + filters: any, + pageIndex?: number, + pageSize?: number + ): HttpParams { + let params = new HttpParams(); + if (filters && typeof filters === 'object') { + for (const key of Object.keys(filters)) { + if (filters[key]) { + params = params.set(key, filters[key]); + } + } + } + if (Number.isInteger(pageIndex) && Number.isInteger(pageSize)) { + params = params.set('pageIndex', String(pageIndex)); + params = params.set('pageSize', String(pageSize)); + } + return params; + } + + + public parsePage(response: HttpResponse) { + const page = response.body || []; + const count = Number(response.headers.get('X-Total-Count')) || page.length; + return { page, count }; + } + + public getRules( + pageIndex?: number, + pageSize?: number, + filters?: any + ): Observable> { + const params = SchemaRulesService.getOptions(filters, pageIndex, pageSize); + return this.http.get(`${this.url}`, { observe: 'response', params }); + } + + public createRules(item: any): Observable { + return this.http.post(`${this.url}/`, item); + } + + public getRule(ruleId: string): Observable { + return this.http.get(`${this.url}/${ruleId}`); + } + + public deleteRule(item: any): Observable { + return this.http.delete(`${this.url}/${item.id}`); + } + + public updateRule(item: any): Observable { + return this.http.put(`${this.url}/${item.id}`, item); + } + + public activateRule(item: any): Observable { + return this.http.put(`${this.url}/${item.id}/activate`, item); + } + + public getRelationships(ruleId: string): Observable { + return this.http.get(`${this.url}/${ruleId}/relationships`); + } +} diff --git a/frontend/src/app/views/new-header/menu.model.ts b/frontend/src/app/views/new-header/menu.model.ts index 1fa8f6f0d..297def75d 100644 --- a/frontend/src/app/views/new-header/menu.model.ts +++ b/frontend/src/app/views/new-header/menu.model.ts @@ -149,6 +149,12 @@ function customMenu(user: UserPermissions): NavbarMenuItem[] { routerLink: '/policy-statistics' }); } + if (user.SCHEMAS_RULE_READ) { + childItems.push({ + title: 'Schema Rules', + routerLink: '/schema-rules' + }); + } } if ( user.SCHEMAS_SCHEMA_READ || diff --git a/guardian-service/src/api/schema-rules.service.ts b/guardian-service/src/api/schema-rules.service.ts new file mode 100644 index 000000000..a90a0decf --- /dev/null +++ b/guardian-service/src/api/schema-rules.service.ts @@ -0,0 +1,272 @@ +import { ApiResponse } from './helpers/api-response.js'; +import { DatabaseServer, MessageError, MessageResponse, PinoLogger, PolicyImportExport, SchemaRule } from '@guardian/common'; +import { EntityStatus, IOwner, MessageAPI, PolicyType, SchemaStatus } from '@guardian/interfaces'; + +/** + * Connect to the message broker methods of working with schema rules. + */ +export async function schemaRulesAPI(logger: PinoLogger): Promise { + /** + * Create new schema rule + * + * @param payload - schema rule + * + * @returns {any} new schema rule + */ + ApiResponse(MessageAPI.CREATE_SCHEMA_RULE, + async (msg: { rule: SchemaRule, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { rule, owner } = msg; + + if (!rule) { + return new MessageError('Invalid object.'); + } + + const policyId = rule.policyId; + const policy = await DatabaseServer.getPolicyById(policyId); + if (!policy || policy.status !== PolicyType.PUBLISH) { + return new MessageError('Item does not exist.'); + } + + delete rule._id; + delete rule.id; + delete rule.status; + delete rule.owner; + rule.creator = owner.creator; + rule.owner = owner.owner; + rule.policyTopicId = policy.topicId; + rule.policyInstanceTopicId = policy.instanceTopicId; + // rule.status = EntityStatus.DRAFT; + // rule.config = validateConfig(definition.config); + const row = await DatabaseServer.createSchemaRule(rule); + return new MessageResponse(row); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get schema rules + * + * @param {any} msg - filters + * + * @returns {any} - schema rules + */ + ApiResponse(MessageAPI.GET_SCHEMA_RULES, + async (msg: { filters: any, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { filters, owner } = msg; + const { policyInstanceTopicId, pageIndex, pageSize } = filters; + + const otherOptions: any = {}; + const _pageSize = parseInt(pageSize, 10); + const _pageIndex = parseInt(pageIndex, 10); + if (Number.isInteger(_pageSize) && Number.isInteger(_pageIndex)) { + otherOptions.orderBy = { createDate: 'DESC' }; + otherOptions.limit = _pageSize; + otherOptions.offset = _pageIndex * _pageSize; + } else { + otherOptions.orderBy = { createDate: 'DESC' }; + otherOptions.limit = 100; + } + otherOptions.fields = [ + 'id', + 'creator', + 'owner', + 'name', + 'description', + 'status', + 'topicId', + 'messageId', + 'policyId', + 'config' + ]; + const query: any = { + $or: [ + { status: EntityStatus.PUBLISHED }, + { creator: owner.creator } + ] + }; + if (policyInstanceTopicId) { + query.policyInstanceTopicId = policyInstanceTopicId; + } + const [items, count] = await DatabaseServer.getSchemaRulesAndCount(query, otherOptions); + return new MessageResponse({ items, count }); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get schema rule + * + * @param {any} msg - schema rule id + * + * @returns {any} - schema rule + */ + ApiResponse(MessageAPI.GET_SCHEMA_RULE, + async (msg: { ruleId: string, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { ruleId, owner } = msg; + const item = await DatabaseServer.getSchemaRuleById(ruleId); + if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { + return new MessageError('Item does not exist.'); + } + return new MessageResponse(item); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Get relationships + * + * @param {any} msg - schema rule id + * + * @returns {any} - relationships + */ + ApiResponse(MessageAPI.GET_SCHEMA_RULE_RELATIONSHIPS, + async (msg: { ruleId: string, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { ruleId, owner } = msg; + const item = await DatabaseServer.getSchemaRuleById(ruleId); + if (!(item && (item.creator === owner.creator || item.status === EntityStatus.PUBLISHED))) { + return new MessageError('Item does not exist.'); + } + const policyId = item.policyId; + const policy = await DatabaseServer.getPolicyById(policyId); + if (!policy || policy.status !== PolicyType.PUBLISH) { + return new MessageError('Item does not exist.'); + } + const { schemas, toolSchemas } = await PolicyImportExport.loadAllSchemas(policy); + const all = [] + .concat(schemas, toolSchemas) + .filter((s) => s.status === SchemaStatus.PUBLISHED && s.entity !== 'EVC'); + + return new MessageResponse({ + policy, + schemas: all, + }); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Update schema rule + * + * @param payload - schema rule + * + * @returns schema rule + */ + ApiResponse(MessageAPI.UPDATE_SCHEMA_RULE, + async (msg: { + ruleId: string, + rule: SchemaRule, + owner: IOwner + }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { ruleId, rule, owner } = msg; + + const item = await DatabaseServer.getSchemaRuleById(ruleId); + if (!item || item.creator !== owner.creator) { + return new MessageError('Item does not exist.'); + } + if (item.status === EntityStatus.PUBLISHED) { + return new MessageError('Item published.'); + } + + item.name = rule.name; + item.description = rule.description; + // item.config = validateConfig(definition.config); + const result = await DatabaseServer.updateSchemaRule(item); + return new MessageResponse(result); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Delete schema rule + * + * @param {any} msg - schema rule id + * + * @returns {boolean} - Operation success + */ + ApiResponse(MessageAPI.DELETE_SCHEMA_RULE, + async (msg: { ruleId: string, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { ruleId, owner } = msg; + const item = await DatabaseServer.getSchemaRuleById(ruleId); + if (!item || item.creator !== owner.creator) { + return new MessageError('Item does not exist.'); + } + if (item.status === EntityStatus.PUBLISHED) { + return new MessageError('Item published.'); + } + await DatabaseServer.removeSchemaRule(item); + return new MessageResponse(true); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + + /** + * Activate schema rule + * + * @param {any} msg - schema rule id + * + * @returns {any} - schema rule + */ + ApiResponse(MessageAPI.ACTIVATE_SCHEMA_RULE, + async (msg: { ruleId: string, owner: IOwner }) => { + try { + if (!msg) { + return new MessageError('Invalid parameters.'); + } + const { ruleId, owner } = msg; + + const item = await DatabaseServer.getSchemaRuleById(ruleId); + if (!item || item.creator !== owner.creator) { + return new MessageError('Item does not exist.'); + } + if (item.status === EntityStatus.PUBLISHED) { + return new MessageError(`Item already published.`); + } + + item.status = EntityStatus.PUBLISHED; + + + const result = await DatabaseServer.updateSchemaRule(item); + return new MessageResponse(result); + + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); +} \ No newline at end of file diff --git a/guardian-service/src/app.ts b/guardian-service/src/app.ts index 6678e49c0..f649c469b 100644 --- a/guardian-service/src/app.ts +++ b/guardian-service/src/app.ts @@ -51,6 +51,7 @@ import { PolicyRoles, PolicyStatistic, PolicyStatisticDocument, + SchemaRule, PolicyTest, PolicyTool, Record, @@ -88,6 +89,7 @@ import { PolicyEngine } from './policy-engine/policy-engine.js'; import { modulesAPI } from './api/module.service.js'; import { toolsAPI } from './api/tool.service.js'; import { statisticsAPI } from './api/policy-statistics.service.js'; +import { schemaRulesAPI } from './api/schema-rules.service.js'; import { GuardiansService } from './helpers/guardians.js'; import { mapAPI } from './api/map.service.js'; import { tagsAPI } from './api/tag.service.js'; @@ -158,7 +160,8 @@ const necessaryEntity = [ AssignEntity, PolicyTest, PolicyStatistic, - PolicyStatisticDocument + PolicyStatisticDocument, + SchemaRule ] Promise.all([ @@ -262,6 +265,7 @@ Promise.all([ await AssignedEntityAPI(logger) await permissionAPI(logger); await statisticsAPI(logger); + await schemaRulesAPI(logger); } catch (error) { console.error(error.message); process.exit(0); diff --git a/interfaces/src/helpers/permissions-helper.ts b/interfaces/src/helpers/permissions-helper.ts index 118682f65..829172792 100644 --- a/interfaces/src/helpers/permissions-helper.ts +++ b/interfaces/src/helpers/permissions-helper.ts @@ -507,6 +507,15 @@ export class UserPermissions { return this.check(Permissions.STATISTICS_STATISTIC_READ); } + //SCHEMA RULES + public get SCHEMAS_RULE_CREATE(): boolean { + return this.check(Permissions.SCHEMAS_RULE_CREATE); + } + + public get SCHEMAS_RULE_READ(): boolean { + return this.check(Permissions.SCHEMAS_RULE_READ); + } + public static isPolicyAdmin(user: any): boolean { return ( UserPermissions.has(user, Permissions.POLICIES_MIGRATION_CREATE) || diff --git a/interfaces/src/type/entity-status.type.ts b/interfaces/src/type/entity-status.type.ts index 83011777e..75d9fa875 100644 --- a/interfaces/src/type/entity-status.type.ts +++ b/interfaces/src/type/entity-status.type.ts @@ -4,5 +4,6 @@ export enum EntityStatus { DRAFT = 'DRAFT', PUBLISHED = 'PUBLISHED', - ERROR = 'ERROR' -} + ERROR = 'ERROR', + ACTIVE = 'ACTIVE' +} \ No newline at end of file diff --git a/interfaces/src/type/messages/message-api.type.ts b/interfaces/src/type/messages/message-api.type.ts index 0314c58c6..b0ceedcaa 100644 --- a/interfaces/src/type/messages/message-api.type.ts +++ b/interfaces/src/type/messages/message-api.type.ts @@ -229,6 +229,13 @@ export enum MessageAPI { GET_STATISTIC_ASSESSMENT = 'GET_STATISTIC_ASSESSMENT', CREATE_STATISTIC_ASSESSMENT = 'CREATE_STATISTIC_ASSESSMENT', GET_STATISTIC_ASSESSMENT_RELATIONSHIPS = 'GET_STATISTIC_ASSESSMENT_RELATIONSHIPS', + GET_SCHEMA_RULES = 'GET_SCHEMA_RULES', + GET_SCHEMA_RULE = 'GET_SCHEMA_RULE', + CREATE_SCHEMA_RULE = 'CREATE_SCHEMA_RULE', + UPDATE_SCHEMA_RULE = 'UPDATE_SCHEMA_RULE', + DELETE_SCHEMA_RULE = 'DELETE_SCHEMA_RULE', + ACTIVATE_SCHEMA_RULE = 'ACTIVATE_SCHEMA_RULE', + GET_SCHEMA_RULE_RELATIONSHIPS = 'GET_SCHEMA_RULE_RELATIONSHIPS', } /** diff --git a/interfaces/src/type/permissions.type.ts b/interfaces/src/type/permissions.type.ts index 1c16eb2ea..8951ba4ec 100644 --- a/interfaces/src/type/permissions.type.ts +++ b/interfaces/src/type/permissions.type.ts @@ -64,7 +64,8 @@ export enum PermissionEntities { TOKEN = 'TOKEN', TRUST_CHAIN = 'TRUST_CHAIN', ROLE = 'ROLE', - STATISTIC = 'STATISTIC' + STATISTIC = 'STATISTIC', + RULE = 'RULE' } /** @@ -219,7 +220,10 @@ export enum Permissions { DELEGATION_ROLE_MANAGE = 'DELEGATION_ROLE_MANAGE', //STATISTICS STATISTICS_STATISTIC_CREATE = 'STATISTICS_STATISTIC_CREATE', - STATISTICS_STATISTIC_READ = 'STATISTICS_STATISTIC_READ' + STATISTICS_STATISTIC_READ = 'STATISTICS_STATISTIC_READ', + //SCHEMA RULES + SCHEMAS_RULE_CREATE = 'SCHEMAS_RULE_CREATE', + SCHEMAS_RULE_READ = 'SCHEMAS_RULE_READ' } /** @@ -1140,6 +1144,21 @@ export const PermissionsArray: { action: PermissionActions.CREATE, disabled: false }, + //SCHEMA RULE + { + name: Permissions.SCHEMAS_RULE_READ, + category: PermissionCategories.SCHEMAS, + entity: PermissionEntities.RULE, + action: PermissionActions.READ, + disabled: false + }, + { + name: Permissions.SCHEMAS_RULE_CREATE, + category: PermissionCategories.SCHEMAS, + entity: PermissionEntities.RULE, + action: PermissionActions.CREATE, + disabled: false + }, //ACCESS { name: Permissions.ACCESS_POLICY_ALL, @@ -1278,7 +1297,9 @@ export const SRDefaultPermission: Permissions[] = [ Permissions.PERMISSIONS_ROLE_UPDATE, Permissions.PERMISSIONS_ROLE_DELETE, Permissions.PERMISSIONS_ROLE_MANAGE, - Permissions.ACCESS_POLICY_ALL + Permissions.ACCESS_POLICY_ALL, + Permissions.SCHEMAS_RULE_CREATE, + Permissions.SCHEMAS_RULE_READ ]; export const AuditDefaultPermission: Permissions[] = [