Skip to content

Commit

Permalink
feat: 🔥 [EXL-73] support settings page for policy
Browse files Browse the repository at this point in the history
support settings page for policy
  • Loading branch information
tal-rofe committed Oct 7, 2022
1 parent 1ad2b05 commit 408140c
Show file tree
Hide file tree
Showing 48 changed files with 1,175 additions and 25 deletions.
23 changes: 22 additions & 1 deletion apps/backend/src/modules/database/inline-policy.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ export class DBInlinePolicyService {
description: string | null,
library: PolicyLibrary,
) {
await this.prisma.inlinePolicy.create({
const createdRecord = await this.prisma.inlinePolicy.create({
data: { groupId, label, description, library },
});

return createdRecord.id;
}

public async doesInlinePolicyBelongUser(inlinePolicyId: string, userId: string) {
Expand Down Expand Up @@ -43,4 +45,23 @@ export class DBInlinePolicyService {

return record !== null;
}

public get(policyId: string) {
return this.prisma.inlinePolicy.findUniqueOrThrow({
where: { id: policyId },
select: {
group: { select: { label: true } },
label: true,
library: true,
},
});
}

public async editPolicyLabel(policyId: string, label: string) {
await this.prisma.inlinePolicy.update({ where: { id: policyId }, data: { label } });
}

public async deletePolicy(policyId: string) {
await this.prisma.inlinePolicy.delete({ where: { id: policyId } });
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, MinLength } from 'class-validator';
import { IsString, MaxLength, MinLength } from 'class-validator';

export class EditLabelDto {
@ApiProperty({ type: String, description: 'The new label for a group', example: 'Yazif Group' })
@IsString()
@MaxLength(30)
@MinLength(1)
readonly label!: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, MaxLength, MinLength } from 'class-validator';

export class EditLabelDto {
@ApiProperty({ type: String, description: 'The new label for a policy', example: 'Yazif Policy' })
@IsString()
@MaxLength(30)
@MinLength(1)
readonly label!: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { type ICategory, ILanguage, type ILibraryData, type IType } from '@/inte
class GetLibrary implements Omit<ILibraryData, 'rules'> {
@ApiResponseProperty({
enum: PolicyLibrary,
example: PolicyLibrary.ESLint,
})
public name!: PolicyLibrary;

Expand Down Expand Up @@ -54,3 +55,31 @@ export class GetLibrariesResponse {
})
public libraries!: Omit<ILibraryData, 'rules'>[];
}

export class CreateResponse {
@ApiResponseProperty({
type: String,
example: '62e5362119bea07115434f4a',
})
public policyId!: string;
}

export class GetResponse {
@ApiResponseProperty({
type: String,
example: 'Yazif Group',
})
public groupLabel!: string;

@ApiResponseProperty({
type: String,
example: 'Yazif Policy',
})
public policyLabel!: string;

@ApiResponseProperty({
enum: PolicyLibrary,
example: PolicyLibrary.ESLint,
})
public library!: PolicyLibrary;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class DeleteContract {
constructor(public readonly policyId: string) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class EditLabelContract {
constructor(public readonly policyId: string, public readonly label: string) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';

import { DBInlinePolicyService } from '@/modules/database/inline-policy.service';

import { DeleteContract } from '../contracts/delete.contract';

@CommandHandler(DeleteContract)
export class DeleteHandler implements ICommandHandler<DeleteContract> {
constructor(private readonly dbInlinePolicyService: DBInlinePolicyService) {}

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

import { DBInlinePolicyService } from '@/modules/database/inline-policy.service';

import { EditLabelContract } from '../contracts/edit-label.contract';

@CommandHandler(EditLabelContract)
export class EditLabelHandler implements ICommandHandler<EditLabelContract> {
constructor(private readonly dbInlinePolicyService: DBInlinePolicyService) {}

async execute(contract: EditLabelContract) {
await this.dbInlinePolicyService.editPolicyLabel(contract.policyId, contract.label);
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CreateInlineHandler } from './create.handler';
import { DeleteHandler } from './delete.handler';
import { EditLabelHandler } from './edit-label.handler';

export const CommandHandlers = [CreateInlineHandler];
export const CommandHandlers = [EditLabelHandler, DeleteHandler];
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Post,
UseGuards,
} from '@nestjs/common';
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { QueryBus } from '@nestjs/cqrs';
import { RealIP } from 'nestjs-real-ip';
import {
ApiBearerAuth,
Expand All @@ -25,20 +25,22 @@ import { BelongingGroupGuard } from '@/guards/belonging-group.guard';

import Routes from './inline-policies.routes';
import { CreateDto } from './classes/create.dto';
import { CreateContract } from './commands/contracts/create.contract';
import { CreateContract } from './queries/contracts/create.contract';
import { GroupHasLibraryContract } from './queries/contracts/group-has-library.contract';
import { CreateResponse } from './classes/responses';

@ApiTags('Inline Policies')
@Controller(Routes.CONTROLLER)
export class createController {
private readonly logger = new Logger(createController.name);
export class CreateController {
private readonly logger = new Logger(CreateController.name);

constructor(private readonly commandBus: CommandBus, private readonly queryBus: QueryBus) {}
constructor(private readonly queryBus: QueryBus) {}

@ApiOperation({ description: 'Create a new inline policy with label, description and chosen library' })
@ApiBearerAuth('access-token')
@ApiCreatedResponse({
description: 'If successfully created the inline policy',
type: CreateResponse,
})
@ApiUnauthorizedResponse({
description: 'If access token is missing or invalid',
Expand All @@ -52,7 +54,7 @@ export class createController {
@Param('group_id') groupId: string,
@Body() createDto: CreateDto,
@RealIP() ip: string,
): Promise<void> {
): Promise<CreateResponse> {
this.logger.log(
`Will try to create an inline policy for a user with an Id: "${userId}" and for group with Id: "${groupId}". Label is "${createDto.label}"`,
);
Expand All @@ -65,7 +67,7 @@ export class createController {
throw new BadRequestException();
}

await this.commandBus.execute<CreateContract, void>(
const createdPolicyId = await this.queryBus.execute<CreateContract, string>(
new CreateContract(
userId,
groupId,
Expand All @@ -79,5 +81,7 @@ export class createController {
this.logger.log(
`Successfully created an inline policy for a user with an Id: "${userId}" and for group with Id: "${groupId}". Label is "${createDto.label}"`,
);

return { policyId: createdPolicyId };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Controller, Delete, HttpCode, HttpStatus, Logger, Param, 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 './inline-policies.routes';
import { DeleteContract } from './commands/contracts/delete.contract';
import { BelongingInlinePolicyGuard } from './guards/belonging-inline-policy.guard';

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

constructor(private readonly commandBus: CommandBus) {}

@ApiOperation({ description: 'Deleting a policy with provided identifier' })
@ApiBearerAuth('access-token')
@ApiOkResponse({ description: 'If successfully deleted the policy' })
@ApiUnauthorizedResponse({
description: 'If access token is either missing or invalid, or policy does not belong to user',
})
@ApiInternalServerErrorResponse({ description: 'If failed to delete the policy' })
@UseGuards(BelongingInlinePolicyGuard)
@Delete(Routes.DELETE)
@HttpCode(HttpStatus.OK)
public async delete(
@CurrentUserId() userId: string,
@Param('policy_id') policyId: string,
): Promise<void> {
this.logger.log(
`Will try to delete a policy with an ID: "${policyId}" for a user with an ID: "${userId}"`,
);

await this.commandBus.execute<DeleteContract, void>(new DeleteContract(policyId));

this.logger.log(
`Successfully deleted a policy with an ID: "${policyId}" for a user with an ID: "${userId}"`,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Body, 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 './inline-policies.routes';
import { EditLabelDto } from './classes/edit-label.dto';
import { EditLabelContract } from './commands/contracts/edit-label.contract';
import { BelongingInlinePolicyGuard } from './guards/belonging-inline-policy.guard';

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

constructor(private readonly commandBus: CommandBus) {}

@ApiOperation({ description: "Edit a policy's label with a new label and its identifier" })
@ApiBearerAuth('access-token')
@ApiOkResponse({ description: 'If successfully edited the label of the policy' })
@ApiUnauthorizedResponse({
description: 'If access token is either missing or invalid, or policy does not belong to user',
})
@ApiInternalServerErrorResponse({ description: 'If failed to edit the label of the policy' })
@UseGuards(BelongingInlinePolicyGuard)
@Patch(Routes.EDIT_LABEL)
@HttpCode(HttpStatus.OK)
public async editLabel(
@CurrentUserId() userId: string,
@Body() editLabelDto: EditLabelDto,
@Param('policy_id') policyId: string,
): Promise<void> {
this.logger.log(
`Will try to edit a policy with an ID: "${policyId}" for a user with an ID: "${userId}"`,
);

await this.commandBus.execute<EditLabelContract, void>(
new EditLabelContract(policyId, editLabelDto.label),
);

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

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

import Routes from './inline-policies.routes';
import { GetResponse } from './classes/responses';
import { GetContract } from './queries/contracts/get.contract';
import { BelongingInlinePolicyGuard } from './guards/belonging-inline-policy.guard';

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

constructor(private readonly queryBus: QueryBus) {}

@ApiOperation({ description: 'Get data of policy' })
@ApiBearerAuth('access-token')
@ApiOkResponse({
description: 'Returns data of policy',
type: GetResponse,
})
@ApiUnauthorizedResponse({
description: 'If access token is invalid or missing, or provided policy does not belong to user',
})
@ApiInternalServerErrorResponse({ description: 'If failed to get data of policy' })
@UseGuards(BelongingInlinePolicyGuard)
@Get(Routes.GET)
@HttpCode(HttpStatus.OK)
public async get(
@CurrentUserId() userId: string,
@Param('policy_id') policyId: string,
): Promise<GetResponse> {
this.logger.log(
`Will try to get data of a policy with an ID: "${policyId}" with a user ID: "${userId}"`,
);

const policyData = await this.queryBus.execute<GetContract, GetResponse>(new GetContract(policyId));

this.logger.log(
`Successfully got data of a policy with an ID: "${policyId}" with a user ID: "${userId}"`,
);

return policyData;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Injectable, type CanActivate, type ExecutionContext } from '@nestjs/common';

import type { IJwtTokenPayload } from '@/interfaces/jwt-token';
import { DBInlinePolicyService } from '@/modules/database/inline-policy.service';

@Injectable()
export class BelongingInlinePolicyGuard implements CanActivate {
constructor(private readonly dbInlinePolicyService: DBInlinePolicyService) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const user = request.user as IJwtTokenPayload;
const userId = user.sub;
const inlinePolicyId = request.params.policy_id as string;

const inlinePolicyBelongsUser = await this.dbInlinePolicyService.doesInlinePolicyBelongUser(
inlinePolicyId,
userId,
);

return inlinePolicyBelongsUser;
}
}
Loading

0 comments on commit 408140c

Please sign in to comment.