Skip to content

Commit

Permalink
feat: 🔥 [EXL-74] support configure rule without enabling
Browse files Browse the repository at this point in the history
support configure rule without enabling
  • Loading branch information
tal-rofe committed Nov 15, 2022
1 parent add4dd9 commit cca4c1d
Show file tree
Hide file tree
Showing 45 changed files with 842 additions and 280 deletions.
9 changes: 7 additions & 2 deletions apps/backend/src/modules/database/inline-policy.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,18 @@ export class DBInlinePolicyService {
public async getPolicyRules(policyId: string, page: number) {
const [count, rulesData] = await this.prisma.$transaction([
this.prisma.rule.count({
where: { policyId },
where: { policyId, isEnabled: true },
}),
this.prisma.inlinePolicy.findUniqueOrThrow({
where: { id: policyId },
select: {
isFormConfiguration: true,
rules: { select: { id: true, name: true }, take: 10, skip: 10 * (page - 1) },
rules: {
where: { isEnabled: true },
select: { id: true, name: true },
take: 10,
skip: 10 * (page - 1),
},
description: true,
createdAt: true,
},
Expand Down
25 changes: 21 additions & 4 deletions apps/backend/src/modules/database/rule.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Injectable } from '@nestjs/common';
import type { Prisma } from '@prisma/client';

import { PrismaService } from './prisma.service';

Expand All @@ -18,17 +19,33 @@ export class DBRuleService {
await this.prisma.rule.delete({ where: { id: ruleId } });
}

public getEnabledRules(policyId: string) {
public getConfiguredRules(policyId: string) {
return this.prisma.rule.findMany({
where: { policyId },
select: { id: true, configuration: true, name: true },
select: { id: true, configuration: true, name: true, isEnabled: true },
});
}

public enableRule(policyId: string, name: string) {
public async enableExistRule(ruleId: string) {
await this.prisma.rule.update({ where: { id: ruleId }, data: { isEnabled: true } });
}

public enableMissingRule(policyId: string, name: string) {
return this.prisma.rule.create({
data: { policyId, name },
data: { policyId, name, isEnabled: true },
select: { id: true },
});
}

public async disableRule(ruleId: string) {
await this.prisma.rule.update({ where: { id: ruleId }, data: { isEnabled: false } });
}

public async updateRuleConfiguration(ruleId: string, configuration: Prisma.JsonArray) {
await this.prisma.rule.update({ where: { id: ruleId }, data: { configuration } });
}

public configureMissing(policyId: string, name: string, configuration: Prisma.JsonArray) {
return this.prisma.rule.create({ data: { policyId, name, configuration, isEnabled: false } });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ApiProperty, ApiResponseProperty } from '@nestjs/swagger';
import type { IConfigureMissingRuleDto, IConfigureMissingRuleResponseData } from '@exlint-dashboard/common';
import { IsArray, IsString } from 'class-validator';
import type { Rule } from '@prisma/client';

export class ConfigureMissingRuleDto implements IConfigureMissingRuleDto {
@ApiProperty({ type: String, description: 'The new rule name to configure', example: 'yazif-array' })
@IsString()
readonly name!: Rule['name'];

@ApiProperty({
type: Array,
description: 'The configuration to apply a rule with',
example: ['error'],
})
@IsArray()
readonly configuration!: IConfigureMissingRuleDto['configuration'];
}

export class ConfigureMissingRuleResponse implements IConfigureMissingRuleResponseData {
@ApiResponseProperty({
type: String,
example: '62e5362119bea07115434f4a',
})
public id!: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApiProperty, ApiResponseProperty } from '@nestjs/swagger';
import type { IEnableMissingRuleDto, IEnableMissingRuleResponseData } from '@exlint-dashboard/common';
import { IsString } from 'class-validator';
import type { Rule } from '@prisma/client';

export class EnableMissingDto implements IEnableMissingRuleDto {
@ApiProperty({ type: String, description: 'The rule name to enable', example: 'yazif-array' })
@IsString()
readonly name!: Rule['name'];
}

export class EnableMissingResponse implements IEnableMissingRuleResponseData {
@ApiResponseProperty({
type: String,
example: '62e5362119bea07115434f4a',
})
public id!: string;
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsArray } from 'class-validator';
import type { IUpdateRuleConfiguratinoDto } from '@exlint-dashboard/common';

export class UpdateRuleConfigurationDto implements IUpdateRuleConfiguratinoDto {
@ApiProperty({
type: Array,
description: 'The configuration to update a rule with',
example: ['error'],
})
@IsArray()
readonly configuration!: IUpdateRuleConfiguratinoDto['configuration'];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class DisableContract {
constructor(public readonly ruleId: string) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class EnableExistContract {
constructor(public readonly ruleId: string) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { Prisma } from '@prisma/client';

export class UpdateConfigurationContract {
constructor(public readonly ruleId: string, public readonly configuration: Prisma.JsonArray) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';

import { DBRuleService } from '@/modules/database/rule.service';

import { DisableContract } from '../contracts/disable.contract';

@CommandHandler(DisableContract)
export class DisableHandler implements ICommandHandler<DisableContract> {
constructor(private readonly dbRuleService: DBRuleService) {}

async execute(contract: DisableContract) {
await this.dbRuleService.disableRule(contract.ruleId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';

import { DBRuleService } from '@/modules/database/rule.service';

import { EnableExistContract } from '../contracts/enable-exist.contract';

@CommandHandler(EnableExistContract)
export class EnableExistHandler implements ICommandHandler<EnableExistContract> {
constructor(private readonly dbRuleService: DBRuleService) {}

async execute(contract: EnableExistContract) {
await this.dbRuleService.enableExistRule(contract.ruleId);
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import { DeleteHandler } from './delete.handler';
import { DisableHandler } from './disable.handler';
import { EnableExistHandler } from './enable-exist.handler';
import { UpdateConfigurationHandler } from './update-configuration.handler';

export const CommandHandlers = [DeleteHandler];
export const CommandHandlers = [
DeleteHandler,
EnableExistHandler,
DisableHandler,
UpdateConfigurationHandler,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';

import { DBRuleService } from '@/modules/database/rule.service';

import { UpdateConfigurationContract } from '../contracts/update-configuration.contract';

@CommandHandler(UpdateConfigurationContract)
export class UpdateConfigurationHandler implements ICommandHandler<UpdateConfigurationContract> {
constructor(private readonly dbRuleService: DBRuleService) {}

async execute(contract: UpdateConfigurationContract) {
await this.dbRuleService.updateRuleConfiguration(contract.ruleId, contract.configuration);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Body, Controller, HttpCode, HttpStatus, Logger, Param, Post, UseGuards } from '@nestjs/common';
import { QueryBus } from '@nestjs/cqrs';
import {
ApiBadRequestResponse,
ApiBearerAuth,
ApiCreatedResponse,
ApiInternalServerErrorResponse,
ApiOperation,
ApiTags,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';
import { PolicyLibrary } from '@prisma/client';

import { CurrentUserId } from '@/decorators/current-user-id.decorator';
import { Library } from '@/decorators/library.decorator';
import { RuleablePolicyGuard } from '@/guards/ruleable-policy.guard';

import Routes from './rules.routes';
import { EnableMissingResponse } from './classes/enable-missing.dto';
import { ConfigureMissingRuleDto, type ConfigureMissingRuleResponse } from './classes/configure-missing.dto';
import { ConfigureMissingContract } from './queries/contracts/configure-missing.contract';

@ApiTags('Rules')
@Controller(Routes.CONTROLLER)
export class ConfigureMissingController {
private readonly logger = new Logger(ConfigureMissingController.name);

constructor(private readonly queryBus: QueryBus) {}

@ApiOperation({ description: 'Configure a missing rule for a policy' })
@ApiBearerAuth('access-token')
@ApiCreatedResponse({
description: 'If successfully configured the missing rule',
type: EnableMissingResponse,
})
@ApiBadRequestResponse({ description: "If rule name is invalid or policy's library has no rules" })
@ApiUnauthorizedResponse({
description: 'If access token is either missing or invalid, or policy does not belong to user',
})
@ApiInternalServerErrorResponse({ description: 'If failed to configure the missing rule' })
@UseGuards(RuleablePolicyGuard)
@Post(Routes.CONFIGURE_MISSING)
@HttpCode(HttpStatus.CREATED)
public async configureMissing(
@CurrentUserId() userId: string,
@Param('policy_id') policyId: string,
@Body() configureMissingRuleDto: ConfigureMissingRuleDto,
@Library() policyLibrary: PolicyLibrary,
): Promise<ConfigureMissingRuleResponse> {
this.logger.log(
`Will try to configure a missing rule for a policy with an ID: "${policyId}" for a user with an ID: "${userId}"`,
);

const createdRuleId = await this.queryBus.execute<
ConfigureMissingContract,
ConfigureMissingRuleResponse['id']
>(
new ConfigureMissingContract(
policyId,
configureMissingRuleDto.name,
configureMissingRuleDto.configuration,
policyLibrary,
),
);

this.logger.log(
`Successfully configured a missing rule with an ID: "${createdRuleId}" for a user with an ID: "${userId}"`,
);

return { id: createdRuleId };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class DeleteController {
})
@ApiInternalServerErrorResponse({ description: 'If failed to delete the rule' })
@UseGuards(BelongingRuleGuard)
@Delete(Routes.DELETE)
@Delete(Routes.DELETE_RULE)
@HttpCode(HttpStatus.OK)
public async delete(@CurrentUserId() userId: string, @Param('rule_id') ruleId: string): Promise<void> {
this.logger.log(
Expand Down
46 changes: 46 additions & 0 deletions apps/backend/src/modules/user/modules/rules/disable.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Controller, HttpCode, HttpStatus, Logger, Param, Patch, UseGuards } from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import {
ApiBearerAuth,
ApiInternalServerErrorResponse,
ApiOkResponse,
ApiOperation,
ApiTags,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';

import { CurrentUserId } from '@/decorators/current-user-id.decorator';

import Routes from './rules.routes';
import { BelongingRuleGuard } from './guards/belonging-rule.guard';
import { DisableContract } from './commands/contracts/disable.contract';

@ApiTags('Rules')
@Controller(Routes.CONTROLLER)
export class DisableController {
private readonly logger = new Logger(DisableController.name);

constructor(private readonly commandBus: CommandBus) {}

@ApiOperation({ description: 'Disable a rule with provided identifier' })
@ApiBearerAuth('access-token')
@ApiOkResponse({ description: 'If successfully disabled the rule' })
@ApiUnauthorizedResponse({
description: 'If access token is either missing or invalid, or rule does not belong to user',
})
@ApiInternalServerErrorResponse({ description: 'If failed to disable the rule' })
@UseGuards(BelongingRuleGuard)
@Patch(Routes.DISABLE_RULE)
@HttpCode(HttpStatus.OK)
public async disable(@CurrentUserId() userId: string, @Param('rule_id') ruleId: string): Promise<void> {
this.logger.log(
`Will try to disable a rule with an ID: "${ruleId}" for a user with an ID: "${userId}"`,
);

await this.commandBus.execute<DisableContract, void>(new DisableContract(ruleId));

this.logger.log(
`Successfully disabled a rule with an ID: "${ruleId}" for a user with an ID: "${userId}"`,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Controller, HttpCode, HttpStatus, Logger, Param, Patch, UseGuards } from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import {
ApiBearerAuth,
ApiInternalServerErrorResponse,
ApiOkResponse,
ApiOperation,
ApiTags,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';

import { CurrentUserId } from '@/decorators/current-user-id.decorator';
import { RuleablePolicyGuard } from '@/guards/ruleable-policy.guard';

import Routes from './rules.routes';
import { EnableExistContract } from './commands/contracts/enable-exist.contract';

@ApiTags('Rules')
@Controller(Routes.CONTROLLER)
export class EnableExistController {
private readonly logger = new Logger(EnableExistController.name);

constructor(private readonly commandBus: CommandBus) {}

@ApiOperation({ description: 'Enable an exist rule for a policy' })
@ApiBearerAuth('access-token')
@ApiOkResponse({ description: 'If successfully enabled the rule' })
@ApiUnauthorizedResponse({
description: 'If access token is either missing or invalid, or rule does not belong to user',
})
@ApiInternalServerErrorResponse({ description: 'If failed to enable the rule' })
@UseGuards(RuleablePolicyGuard)
@Patch(Routes.ENABLE_EXIST_RULE)
@HttpCode(HttpStatus.OK)
public async enableRule(
@CurrentUserId() userId: string,
@Param('rule_id') ruleId: string,
): Promise<void> {
this.logger.log(
`Will try to enable a rule with an ID: "${ruleId}" for a user with an ID: "${userId}"`,
);

await this.commandBus.execute<EnableExistContract, void>(new EnableExistContract(ruleId));

this.logger.log(
`Successfully enabled a rule with an ID: "${ruleId}" for a user with an ID: "${userId}"`,
);
}
}
Loading

0 comments on commit cca4c1d

Please sign in to comment.