Skip to content

Commit

Permalink
feat: 🔥 [EXL-67] support policies page of a group
Browse files Browse the repository at this point in the history
support policies page of a group
  • Loading branch information
tal-rofe committed Sep 22, 2022
1 parent be14516 commit 0693db4
Show file tree
Hide file tree
Showing 68 changed files with 1,357 additions and 39 deletions.
40 changes: 37 additions & 3 deletions apps/backend/src/modules/database/group.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export class DBGroupService {
await this.prisma.group.update({ where: { id: groupId }, data: { label } });
}

public async editGroupDescription(groupId: string, description: string | null) {
await this.prisma.group.update({ where: { id: groupId }, data: { description } });
}

public async deleteGroup(groupId: string) {
await this.prisma.group.delete({ where: { id: groupId } });
}
Expand All @@ -50,12 +54,42 @@ export class DBGroupService {
return record === null;
}

public getUserGroup(userId: string, groupId: string) {
return this.prisma.group.findFirst({
where: { userId, id: groupId },
public getUserGroup(groupId: string) {
return this.prisma.group.findUnique({
where: { id: groupId },
select: {
label: true,
},
});
}

public async getInlinePoliciesAndDescription(groupId: string, page: number) {
const [count, policies] = await this.prisma.$transaction([
this.prisma.inlinePolicy.count({
where: {
groupId,
},
}),
this.prisma.group.findUniqueOrThrow({
where: { id: groupId },
select: {
description: true,
inlinePolicies: {
select: {
id: true,
label: true,
library: true,
},
take: 10,
skip: 10 * (page - 1),
},
},
}),
]);

return {
...policies,
count,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, MinLength } from 'class-validator';

import { IsNullable } from '@/decorators/is-nullable.decorator';

export class EditDescriptionDto {
@ApiProperty({
type: String,
nullable: true,
description: 'The new description for a group',
example: 'Yazif Group Description',
})
@IsString()
@IsNullable()
@MinLength(1)
readonly description!: string | null;
}
47 changes: 47 additions & 0 deletions apps/backend/src/modules/user/modules/groups/classes/responses.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ApiResponseProperty } from '@nestjs/swagger';
import { PolicyLibrary } from '@prisma/client';

import type { IGroupInlinePolicies, IGroupInlinePolicy } from '../interfaces/group-policies';

import type { IUserGroupGetAll, IUserGroupInlinePolicy } from '../interfaces/user-group';

class UserGroupInlinePolicyGetAll implements IUserGroupInlinePolicy {
Expand Down Expand Up @@ -47,6 +49,31 @@ class UserGroupGetAll implements IUserGroupGetAll {
public librariesNames!: PolicyLibrary[];
}

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

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

@ApiResponseProperty({
enum: PolicyLibrary,
})
public library!: PolicyLibrary;

@ApiResponseProperty({
type: String,
example: 'JavaScript',
})
public language!: string;
}

export class CreateGroupResponse {
@ApiResponseProperty({
type: String,
Expand Down Expand Up @@ -77,3 +104,23 @@ export class GetResponse {
})
public label!: string;
}

export class GetInlinePoliciesResponse implements IGroupInlinePolicies {
@ApiResponseProperty({
type: String,
example: 'Yazif Group Description',
})
public description!: string | null;

@ApiResponseProperty({
type: Number,
example: 10,
})
public count!: number;

@ApiResponseProperty({
type: [GroupInlinePolicy],
example: 'Yazif Group Description',
})
public inlinePolicies!: IGroupInlinePolicy[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class EditDescriptionContract {
constructor(public readonly groupId: string, public readonly description: string | null) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';

import { DBGroupService } from '@/modules/database/group.service';

import { EditDescriptionContract } from '../contracts/edit-description.contact';

@CommandHandler(EditDescriptionContract)
export class EditDescriptionHandler implements ICommandHandler<EditDescriptionContract> {
constructor(private readonly dbGroupService: DBGroupService) {}

async execute(contract: EditDescriptionContract) {
await this.dbGroupService.editGroupDescription(contract.groupId, contract.description || null);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DeleteHandler } from './delete.handler';
import { EditDescriptionHandler } from './edit-description.handler';
import { EditLabelHandler } from './edit-label.handler';

export const CommandHandlers = [EditLabelHandler, DeleteHandler];
export const CommandHandlers = [EditLabelHandler, DeleteHandler, EditDescriptionHandler];
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { PolicyLibrary } from '@prisma/client';

export const libariesLanguages: Record<PolicyLibrary, string> = {
ESLint: 'JavaScript',
Stylelint: 'CSS',
Depcheck: 'JavaScript',
Inflint: 'Agnostic',
Prettier: 'Agnostic',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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 { BelongingGroupGuard } from '@/guards/belonging-group.guard';

import Routes from './groups.routes';
import { EditDescriptionDto } from './classes/edit-description.dto';
import { EditDescriptionContract } from './commands/contracts/edit-description.contact';

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

constructor(private readonly commandBus: CommandBus) {}

@ApiOperation({ description: "Edit a group's description with a new description and its identifier" })
@ApiBearerAuth('access-token')
@ApiOkResponse({ description: 'If successfully edited the description of the group' })
@ApiUnauthorizedResponse({
description: 'If access token is either missing or invalid, or group does not belong to user',
})
@ApiInternalServerErrorResponse({ description: 'If failed to edit the description of the group' })
@UseGuards(BelongingGroupGuard)
@Patch(Routes.EDIT_DESCRIPTION)
@HttpCode(HttpStatus.OK)
public async editDescription(
@CurrentUserId() userId: string,
@Body() editDescriptionDto: EditDescriptionDto,
@Param('group_id') groupId: string,
): Promise<void> {
this.logger.log(`Will try to edit a group with an Id ${groupId} for a user with an Id: ${userId}`);

await this.commandBus.execute<EditDescriptionContract, void>(
new EditDescriptionContract(groupId, editDescriptionDto.description),
);

this.logger.log(
`Successfully edited a group with an Id: ${groupId} for a user with an Id: ${userId}`,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class GetAllController {
@Get(Routes.GET_ALL)
@HttpCode(HttpStatus.OK)
public async getAll(@CurrentUserId() userId: string): Promise<GetAllGroupsResponse> {
this.logger.log(`Will try to fetch all groups belong to use with an Id: "${userId}"`);
this.logger.log(`Will try to fetch all groups belong to user with an Id: "${userId}"`);

const userGroups = await this.queryBus.execute<GetAllGroupsContract, IUserGroupGetAll[]>(
new GetAllGroupsContract(userId),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Controller, Get, HttpCode, HttpStatus, Logger, Param, Query, 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 { BelongingGroupGuard } from '@/guards/belonging-group.guard';

import Routes from './groups.routes';
import { GetInlinePoliciesResponse } from './classes/responses';
import { GetInlinePoliciesContract } from './queries/contracts/get-inline-policies.contract';
import type { IGroupInlinePolicies } from './interfaces/group-policies';

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

constructor(private readonly queryBus: QueryBus) {}

@ApiBearerAuth('access-token')
@ApiOperation({ description: 'Get all inline policies of a group' })
@ApiOkResponse({
description: 'If successfully got all inline policies of a group',
type: GetInlinePoliciesResponse,
})
@ApiUnauthorizedResponse({
description: 'If access token is either missing or invalid',
})
@ApiInternalServerErrorResponse({ description: 'If failed to fetch all inline policies of a group' })
@UseGuards(BelongingGroupGuard)
@Get(Routes.GET_INLINE_POLICIES)
@HttpCode(HttpStatus.OK)
public async getInlinePolicies(
@CurrentUserId() userId: string,
@Param('group_id') groupId: string,
@Query('p') page?: string,
): Promise<GetInlinePoliciesResponse> {
this.logger.log(
`Will try to fetch all inline policies of a group with an ID: "${groupId}" belong to user with an Id: "${userId}"`,
);

const inlinePolicies = await this.queryBus.execute<GetInlinePoliciesContract, IGroupInlinePolicies>(
new GetInlinePoliciesContract(groupId, page),
);

this.logger.log(
`Successfully got all inline policies of a group with an ID: "${groupId}" belong to user with an Id: "${userId}"`,
);

return inlinePolicies;
}
}
12 changes: 8 additions & 4 deletions apps/backend/src/modules/user/modules/groups/get.contoller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,25 @@ export class GetController {
@ApiInternalServerErrorResponse({ description: "If failed to fetch a user's group" })
@Get(Routes.GET)
@HttpCode(HttpStatus.OK)
public async getAll(
public async get(
@CurrentUserId() userId: string,
@Param('group_id') groupId: string,
): Promise<GetResponse> {
this.logger.log(`Will try to fetch all groups belong to use with an Id: "${userId}"`);
this.logger.log(
`Will try to fetch a user group with an ID: "${groupId}" belongs to user with an Id: "${userId}"`,
);

const userGroup = await this.queryBus.execute<GetGroupContract, GetResponse | null>(
new GetGroupContract(userId, groupId),
new GetGroupContract(groupId),
);

if (!userGroup) {
throw new NotFoundException();
}

this.logger.log(`Successfully got all groups belong to user with an Id: "${userId}"`);
this.logger.log(
`Successfully got user group with an ID: "${groupId}" belong to user with an Id: "${userId}"`,
);

return userGroup;
}
Expand Down
4 changes: 4 additions & 0 deletions apps/backend/src/modules/user/modules/groups/groups.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { EventHandlers } from './events/handlers';
import { GetAllController } from './get-all.controller';
import { AvailableLabelController } from './available-label.controller';
import { GetController } from './get.contoller';
import { GetInlinePoliciesController } from './get-inline-policies.controller';
import { EditDescriptionController } from './edit-description.controller';

@Module({
imports: [CqrsModule],
Expand All @@ -22,6 +24,8 @@ import { GetController } from './get.contoller';
GetAllController,
AvailableLabelController,
GetController,
GetInlinePoliciesController,
EditDescriptionController,
],
providers: [...QueryHandlers, ...CommandHandlers, ...EventHandlers, BelongingGroupGuard],
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
const Routes = {
CONTROLLER: 'groups',
CREATE: '',
EDIT_LABEL: ':group_id',
EDIT_LABEL: 'label/:group_id',
EDIT_DESCRIPTION: 'description/:group_id',
DELETE: ':group_id',
GET_ALL: '',
AVAILABLE_LABEL: 'available/:label',
GET: ':group_id',
GET_INLINE_POLICIES: 'inline-policies/:group_id',
};

export default Routes;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Group, InlinePolicy } from '@prisma/client';

export type IGroupInlinePolicy = Pick<InlinePolicy, 'id' | 'label' | 'library'> & {
readonly language: string;
};

export interface IGroupInlinePolicies extends Pick<Group, 'description'> {
count: number;
inlinePolicies: IGroupInlinePolicy[];
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export class GetGroupContract {
constructor(public readonly userId: string, public readonly groupId: string) {}
constructor(public readonly groupId: string) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class GetInlinePoliciesContract {
constructor(public readonly groupId: string, public readonly page?: string) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ export class GetGroupHandler implements IQueryHandler<GetGroupContract> {
constructor(private readonly dbGroupService: DBGroupService) {}

execute(contract: GetGroupContract) {
return this.dbGroupService.getUserGroup(contract.userId, contract.groupId);
return this.dbGroupService.getUserGroup(contract.groupId);
}
}
Loading

0 comments on commit 0693db4

Please sign in to comment.