From 720ba1b61b0d34de869235ad73a41cf988a59e5a Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Mon, 3 Oct 2022 09:53:08 +0530 Subject: [PATCH 01/12] feat: Added API to create project --- .eslintrc.js | 4 +-- .../dtos/create-project-response.dto.ts | 25 ++++++++++++++++++ .../app/project/dtos/create-project.dto.ts | 22 ++++++++++++++++ .../api/src/app/project/project.controller.ts | 26 ++++++++++++++++--- apps/api/src/app/project/project.module.ts | 5 ++-- .../create-project/create-project.command.ts | 12 +++++++++ .../create-project/create-project.usecase.ts | 12 +++++++++ apps/api/src/app/project/usecases/index.ts | 2 ++ .../src/app/shared/commands/base.command.ts | 21 +++++++++++++++ .../app/shared/framework/IsUniqueValidator.ts | 21 +++++++++++++++ libs/dal/src/index.ts | 1 + .../repositories/common/common.repository.ts | 14 ++++++++++ libs/dal/src/repositories/common/index.ts | 1 + 13 files changed, 159 insertions(+), 7 deletions(-) create mode 100644 apps/api/src/app/project/dtos/create-project-response.dto.ts create mode 100644 apps/api/src/app/project/dtos/create-project.dto.ts create mode 100644 apps/api/src/app/project/usecases/create-project/create-project.command.ts create mode 100644 apps/api/src/app/project/usecases/create-project/create-project.usecase.ts create mode 100644 apps/api/src/app/shared/commands/base.command.ts create mode 100644 apps/api/src/app/shared/framework/IsUniqueValidator.ts create mode 100644 libs/dal/src/repositories/common/common.repository.ts create mode 100644 libs/dal/src/repositories/common/index.ts diff --git a/.eslintrc.js b/.eslintrc.js index 361b9cd5d..041ccaaaa 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -33,8 +33,8 @@ module.exports = { '@typescript-eslint/explicit-function-return-type': 'off', 'react/jsx-closing-bracket-location': 'off', '@typescript-eslint/no-var-requires': 'off', - 'no-unused-vars': 'off', - '@typescript-eslint/no-unused-vars': ['off'], + 'no-unused-vars': 'error', + '@typescript-eslint/no-unused-vars': ['error'], 'mocha/no-mocha-arrows': 'off', '@typescript-eslint/default-param-last': 'off', 'no-return-await': 'off', diff --git a/apps/api/src/app/project/dtos/create-project-response.dto.ts b/apps/api/src/app/project/dtos/create-project-response.dto.ts new file mode 100644 index 000000000..b04252120 --- /dev/null +++ b/apps/api/src/app/project/dtos/create-project-response.dto.ts @@ -0,0 +1,25 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsDefined, IsString } from 'class-validator'; + +export class CreateProjectResponseDto { + @ApiPropertyOptional({ + description: 'Id of the project', + }) + @IsString() + @IsDefined() + _id?: string; + + @ApiProperty({ + description: 'Name of the project', + }) + @IsString() + @IsDefined() + name: string; + + @ApiProperty({ + description: 'Code of the project', + }) + @IsString() + @IsDefined() + code: string; +} diff --git a/apps/api/src/app/project/dtos/create-project.dto.ts b/apps/api/src/app/project/dtos/create-project.dto.ts new file mode 100644 index 000000000..224e87e77 --- /dev/null +++ b/apps/api/src/app/project/dtos/create-project.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsDefined, IsString, Validate } from 'class-validator'; +import { UniqueValidator } from '../../shared/framework/IsUniqueValidator'; + +export class CreateProjectDto { + @ApiProperty({ + description: 'Name of the project', + }) + @IsString() + @IsDefined() + name: string; + + @ApiProperty({ + description: 'Code of the project', + }) + @IsString() + @IsDefined() + @Validate(UniqueValidator, ['Project', 'code'], { + message: 'Code is already taken', + }) + code: string; +} diff --git a/apps/api/src/app/project/project.controller.ts b/apps/api/src/app/project/project.controller.ts index 44867e87f..2fa93a8e3 100644 --- a/apps/api/src/app/project/project.controller.ts +++ b/apps/api/src/app/project/project.controller.ts @@ -1,12 +1,16 @@ -import { Controller, Get } from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Body, Controller, Get, Post } from '@nestjs/common'; +import { ApiOperation, ApiTags, ApiOkResponse } from '@nestjs/swagger'; +import { CreateProjectDto } from './dtos/create-project.dto'; import { ProjectResponseDto } from './dtos/projects-response.dto'; import { GetProjects } from './usecases/get-projects/get-projects.usecase'; +import { CreateProject } from './usecases/create-project/create-project.usecase'; +import { CreateProjectCommand } from './usecases/create-project/create-project.command'; +import { CreateProjectResponseDto } from './dtos/create-project-response.dto'; @Controller('/project') @ApiTags('Project') export class ProjectController { - constructor(private getProjectsUsecase: GetProjects) {} + constructor(private getProjectsUsecase: GetProjects, private createProjectUsecase: CreateProject) {} @Get('') @ApiOperation({ @@ -15,4 +19,20 @@ export class ProjectController { getProjects(): Promise { return this.getProjectsUsecase.execute(); } + + @Post('') + @ApiOperation({ + summary: 'Create project', + }) + @ApiOkResponse({ + type: [CreateProjectResponseDto], + }) + createProject(@Body() body: CreateProjectDto): Promise { + return this.createProjectUsecase.execute( + CreateProjectCommand.create({ + code: body.code, + name: body.name, + }) + ); + } } diff --git a/apps/api/src/app/project/project.module.ts b/apps/api/src/app/project/project.module.ts index 68afc5017..39279abb7 100644 --- a/apps/api/src/app/project/project.module.ts +++ b/apps/api/src/app/project/project.module.ts @@ -1,11 +1,12 @@ import { Module } from '@nestjs/common'; import { USE_CASES } from './usecases'; -import { ProjectController } from './project.controller'; import { SharedModule } from '../shared/shared.module'; +import { ProjectController } from './project.controller'; +import { UniqueValidator } from '../shared/framework/IsUniqueValidator'; @Module({ imports: [SharedModule], - providers: [...USE_CASES], + providers: [...USE_CASES, UniqueValidator], controllers: [ProjectController], }) export class ProjectModule {} diff --git a/apps/api/src/app/project/usecases/create-project/create-project.command.ts b/apps/api/src/app/project/usecases/create-project/create-project.command.ts new file mode 100644 index 000000000..4c5fd7c5a --- /dev/null +++ b/apps/api/src/app/project/usecases/create-project/create-project.command.ts @@ -0,0 +1,12 @@ +import { IsDefined, IsString } from 'class-validator'; +import { BaseCommand } from '../../../shared/commands/base.command'; + +export class CreateProjectCommand extends BaseCommand { + @IsDefined() + @IsString() + name: string; + + @IsDefined() + @IsString() + code: string; +} diff --git a/apps/api/src/app/project/usecases/create-project/create-project.usecase.ts b/apps/api/src/app/project/usecases/create-project/create-project.usecase.ts new file mode 100644 index 000000000..61ed4030a --- /dev/null +++ b/apps/api/src/app/project/usecases/create-project/create-project.usecase.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@nestjs/common'; +import { ProjectRepository } from '@impler/dal'; +import { CreateProjectCommand } from './create-project.command'; + +@Injectable() +export class CreateProject { + constructor(private projectRepository: ProjectRepository) {} + + async execute(command: CreateProjectCommand) { + return this.projectRepository.create(command); + } +} diff --git a/apps/api/src/app/project/usecases/index.ts b/apps/api/src/app/project/usecases/index.ts index 6cdb5770a..f61f1ca44 100644 --- a/apps/api/src/app/project/usecases/index.ts +++ b/apps/api/src/app/project/usecases/index.ts @@ -1,6 +1,8 @@ +import { CreateProject } from './create-project/create-project.usecase'; import { GetProjects } from './get-projects/get-projects.usecase'; export const USE_CASES = [ GetProjects, + CreateProject, // ]; diff --git a/apps/api/src/app/shared/commands/base.command.ts b/apps/api/src/app/shared/commands/base.command.ts new file mode 100644 index 000000000..0044a4b75 --- /dev/null +++ b/apps/api/src/app/shared/commands/base.command.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { plainToClass } from 'class-transformer'; +import { validateSync } from 'class-validator'; +import { BadRequestException, flatten } from '@nestjs/common'; + +export abstract class BaseCommand { + static create(this: new (...args: any[]) => T, data: T): T { + const convertedObject = plainToClass(this, { + ...data, + }); + + const errors = validateSync(convertedObject as unknown as object); + if (errors?.length) { + const mappedErrors = flatten(errors.map((item) => Object.values(item.constraints))); + + throw new BadRequestException(mappedErrors); + } + + return convertedObject; + } +} diff --git a/apps/api/src/app/shared/framework/IsUniqueValidator.ts b/apps/api/src/app/shared/framework/IsUniqueValidator.ts new file mode 100644 index 000000000..e8d645f94 --- /dev/null +++ b/apps/api/src/app/shared/framework/IsUniqueValidator.ts @@ -0,0 +1,21 @@ +import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator'; +import { CommonRepository, ProjectEntity } from '@impler/dal'; + +@ValidatorConstraint({ name: 'IsUniqueUser', async: true }) +export class UniqueValidator implements ValidatorConstraintInterface { + private commonRepository: CommonRepository; + constructor() { + this.commonRepository = new CommonRepository(); + } + + async validate(value: any, args: ValidationArguments) { + const [modelName, field] = args.constraints; + const count = await this.commonRepository.count(modelName, { [field]: value }); + + return !count; + } + + defaultMessage(args: ValidationArguments) { + return `${args.value} is already taken`; + } +} diff --git a/libs/dal/src/index.ts b/libs/dal/src/index.ts index eb53cc71f..a2ca8901b 100644 --- a/libs/dal/src/index.ts +++ b/libs/dal/src/index.ts @@ -1,2 +1,3 @@ export * from './dal.service'; export * from './repositories/project'; +export * from './repositories/common'; diff --git a/libs/dal/src/repositories/common/common.repository.ts b/libs/dal/src/repositories/common/common.repository.ts new file mode 100644 index 000000000..123998bcf --- /dev/null +++ b/libs/dal/src/repositories/common/common.repository.ts @@ -0,0 +1,14 @@ +import { FilterQuery, models } from 'mongoose'; + +export class CommonRepository { + async count(name: string, query: FilterQuery): Promise { + const model = models[name]; + if (model) { + const count = await model.count(query); + + return count; + } + + throw new Error(`Model ${name} does not exists`); + } +} diff --git a/libs/dal/src/repositories/common/index.ts b/libs/dal/src/repositories/common/index.ts new file mode 100644 index 000000000..8c96f8234 --- /dev/null +++ b/libs/dal/src/repositories/common/index.ts @@ -0,0 +1 @@ +export * from './common.repository'; From f56bfc2ae43c75af26ad408a40564851cf873234 Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Mon, 3 Oct 2022 09:57:26 +0530 Subject: [PATCH 02/12] fix: swagger response --- apps/api/src/app/project/project.controller.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/api/src/app/project/project.controller.ts b/apps/api/src/app/project/project.controller.ts index 2fa93a8e3..5e038862a 100644 --- a/apps/api/src/app/project/project.controller.ts +++ b/apps/api/src/app/project/project.controller.ts @@ -16,6 +16,9 @@ export class ProjectController { @ApiOperation({ summary: 'Get projects', }) + @ApiOkResponse({ + type: [ProjectResponseDto], + }) getProjects(): Promise { return this.getProjectsUsecase.execute(); } @@ -25,7 +28,7 @@ export class ProjectController { summary: 'Create project', }) @ApiOkResponse({ - type: [CreateProjectResponseDto], + type: CreateProjectResponseDto, }) createProject(@Body() body: CreateProjectDto): Promise { return this.createProjectUsecase.execute( From 3a752ad09954e862c9252a058a56dceb85270e33 Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Mon, 3 Oct 2022 10:18:33 +0530 Subject: [PATCH 03/12] feat: Added Update API and made DTO reusable --- ...t.dto.ts => create-project-request.dto.ts} | 2 +- ...esponse.dto.ts => project-response.dto.ts} | 2 +- .../app/project/dtos/projects-response.dto.ts | 8 ----- .../dtos/update-project-request.dto.ts | 11 ++++++ .../api/src/app/project/project.controller.ts | 34 +++++++++++++++---- .../get-projects/get-projects.usecase.ts | 2 +- apps/api/src/app/project/usecases/index.ts | 2 ++ .../update-project/update-project.command.ts | 8 +++++ .../update-project/update-project.usecase.ts | 12 +++++++ libs/dal/src/repositories/base-repository.ts | 10 +++++- 10 files changed, 72 insertions(+), 19 deletions(-) rename apps/api/src/app/project/dtos/{create-project.dto.ts => create-project-request.dto.ts} (92%) rename apps/api/src/app/project/dtos/{create-project-response.dto.ts => project-response.dto.ts} (91%) delete mode 100644 apps/api/src/app/project/dtos/projects-response.dto.ts create mode 100644 apps/api/src/app/project/dtos/update-project-request.dto.ts create mode 100644 apps/api/src/app/project/usecases/update-project/update-project.command.ts create mode 100644 apps/api/src/app/project/usecases/update-project/update-project.usecase.ts diff --git a/apps/api/src/app/project/dtos/create-project.dto.ts b/apps/api/src/app/project/dtos/create-project-request.dto.ts similarity index 92% rename from apps/api/src/app/project/dtos/create-project.dto.ts rename to apps/api/src/app/project/dtos/create-project-request.dto.ts index 224e87e77..1e63b5798 100644 --- a/apps/api/src/app/project/dtos/create-project.dto.ts +++ b/apps/api/src/app/project/dtos/create-project-request.dto.ts @@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsDefined, IsString, Validate } from 'class-validator'; import { UniqueValidator } from '../../shared/framework/IsUniqueValidator'; -export class CreateProjectDto { +export class CreateProjectRequestDto { @ApiProperty({ description: 'Name of the project', }) diff --git a/apps/api/src/app/project/dtos/create-project-response.dto.ts b/apps/api/src/app/project/dtos/project-response.dto.ts similarity index 91% rename from apps/api/src/app/project/dtos/create-project-response.dto.ts rename to apps/api/src/app/project/dtos/project-response.dto.ts index b04252120..3293ea503 100644 --- a/apps/api/src/app/project/dtos/create-project-response.dto.ts +++ b/apps/api/src/app/project/dtos/project-response.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsDefined, IsString } from 'class-validator'; -export class CreateProjectResponseDto { +export class ProjectResponseDto { @ApiPropertyOptional({ description: 'Id of the project', }) diff --git a/apps/api/src/app/project/dtos/projects-response.dto.ts b/apps/api/src/app/project/dtos/projects-response.dto.ts deleted file mode 100644 index 0cfe9632f..000000000 --- a/apps/api/src/app/project/dtos/projects-response.dto.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class ProjectResponseDto { - @ApiProperty() - name: string; - @ApiProperty() - code: string; -} diff --git a/apps/api/src/app/project/dtos/update-project-request.dto.ts b/apps/api/src/app/project/dtos/update-project-request.dto.ts new file mode 100644 index 000000000..7ce5f1056 --- /dev/null +++ b/apps/api/src/app/project/dtos/update-project-request.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsDefined, IsString } from 'class-validator'; + +export class UpdateProjectRequestDto { + @ApiProperty({ + description: 'Name of the project', + }) + @IsString() + @IsDefined() + name: string; +} diff --git a/apps/api/src/app/project/project.controller.ts b/apps/api/src/app/project/project.controller.ts index 5e038862a..a437fd95f 100644 --- a/apps/api/src/app/project/project.controller.ts +++ b/apps/api/src/app/project/project.controller.ts @@ -1,16 +1,22 @@ -import { Body, Controller, Get, Post } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post, Put } from '@nestjs/common'; import { ApiOperation, ApiTags, ApiOkResponse } from '@nestjs/swagger'; -import { CreateProjectDto } from './dtos/create-project.dto'; -import { ProjectResponseDto } from './dtos/projects-response.dto'; +import { CreateProjectRequestDto } from './dtos/create-project-request.dto'; +import { ProjectResponseDto } from './dtos/project-response.dto'; import { GetProjects } from './usecases/get-projects/get-projects.usecase'; import { CreateProject } from './usecases/create-project/create-project.usecase'; import { CreateProjectCommand } from './usecases/create-project/create-project.command'; -import { CreateProjectResponseDto } from './dtos/create-project-response.dto'; +import { UpdateProjectRequestDto } from './dtos/update-project-request.dto'; +import { UpdateProject } from './usecases/update-project/update-project.usecase'; +import { UpdateProjectCommand } from './usecases/update-project/update-project.command'; @Controller('/project') @ApiTags('Project') export class ProjectController { - constructor(private getProjectsUsecase: GetProjects, private createProjectUsecase: CreateProject) {} + constructor( + private getProjectsUsecase: GetProjects, + private createProjectUsecase: CreateProject, + private updateProjectUsecase: UpdateProject + ) {} @Get('') @ApiOperation({ @@ -28,9 +34,9 @@ export class ProjectController { summary: 'Create project', }) @ApiOkResponse({ - type: CreateProjectResponseDto, + type: ProjectResponseDto, }) - createProject(@Body() body: CreateProjectDto): Promise { + createProject(@Body() body: CreateProjectRequestDto): Promise { return this.createProjectUsecase.execute( CreateProjectCommand.create({ code: body.code, @@ -38,4 +44,18 @@ export class ProjectController { }) ); } + + @Put(':projectId') + @ApiOperation({ + summary: 'Update project', + }) + @ApiOkResponse({ + type: ProjectResponseDto, + }) + updateProject( + @Body() body: UpdateProjectRequestDto, + @Param('projectId') projectId: string + ): Promise { + return this.updateProjectUsecase.execute(UpdateProjectCommand.create({ name: body.name }), projectId); + } } diff --git a/apps/api/src/app/project/usecases/get-projects/get-projects.usecase.ts b/apps/api/src/app/project/usecases/get-projects/get-projects.usecase.ts index 1bdc7d1a0..c25927e91 100644 --- a/apps/api/src/app/project/usecases/get-projects/get-projects.usecase.ts +++ b/apps/api/src/app/project/usecases/get-projects/get-projects.usecase.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { ProjectRepository } from '@impler/dal'; -import { ProjectResponseDto } from '../../dtos/projects-response.dto'; +import { ProjectResponseDto } from '../../dtos/project-response.dto'; @Injectable() export class GetProjects { diff --git a/apps/api/src/app/project/usecases/index.ts b/apps/api/src/app/project/usecases/index.ts index f61f1ca44..1d50b0d67 100644 --- a/apps/api/src/app/project/usecases/index.ts +++ b/apps/api/src/app/project/usecases/index.ts @@ -1,8 +1,10 @@ import { CreateProject } from './create-project/create-project.usecase'; import { GetProjects } from './get-projects/get-projects.usecase'; +import { UpdateProject } from './update-project/update-project.usecase'; export const USE_CASES = [ GetProjects, CreateProject, + UpdateProject, // ]; diff --git a/apps/api/src/app/project/usecases/update-project/update-project.command.ts b/apps/api/src/app/project/usecases/update-project/update-project.command.ts new file mode 100644 index 000000000..1ec6db4a3 --- /dev/null +++ b/apps/api/src/app/project/usecases/update-project/update-project.command.ts @@ -0,0 +1,8 @@ +import { IsDefined, IsString } from 'class-validator'; +import { BaseCommand } from '../../../shared/commands/base.command'; + +export class UpdateProjectCommand extends BaseCommand { + @IsDefined() + @IsString() + name: string; +} diff --git a/apps/api/src/app/project/usecases/update-project/update-project.usecase.ts b/apps/api/src/app/project/usecases/update-project/update-project.usecase.ts new file mode 100644 index 000000000..01eba6063 --- /dev/null +++ b/apps/api/src/app/project/usecases/update-project/update-project.usecase.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@nestjs/common'; +import { ProjectRepository } from '@impler/dal'; +import { UpdateProjectCommand } from './update-project.command'; + +@Injectable() +export class UpdateProject { + constructor(private projectRepository: ProjectRepository) {} + + async execute(command: UpdateProjectCommand, id: string) { + return this.projectRepository.findOneAndUpdate({ _id: id }, command); + } +} diff --git a/libs/dal/src/repositories/base-repository.ts b/libs/dal/src/repositories/base-repository.ts index d20623947..9504ac103 100644 --- a/libs/dal/src/repositories/base-repository.ts +++ b/libs/dal/src/repositories/base-repository.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ClassConstructor, plainToClass } from 'class-transformer'; -import { Document, FilterQuery, Model, Types } from 'mongoose'; +import { Document, FilterQuery, Model, QueryOptions, Types, UpdateQuery } from 'mongoose'; export class BaseRepository { public _model: Model; @@ -105,6 +105,14 @@ export class BaseRepository { }; } + async findOneAndUpdate( + query: FilterQuery, + updateBody: UpdateQuery, + options: QueryOptions = { new: true } // By default return updated document + ): Promise { + return this.MongooseModel.findOneAndUpdate(query, updateBody, options); + } + protected mapEntity(data: any): T { return plainToClass(this.entity, JSON.parse(JSON.stringify(data))) as any; } From b618d2d9a5de2508e1c5ba4c2fb5e33985587084 Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Mon, 3 Oct 2022 10:38:57 +0530 Subject: [PATCH 04/12] feat: Added Delete project API --- .../api/src/app/project/project.controller.ts | 23 +++++++++++++++++-- .../delete-project/delete-project.usecase.ts | 11 +++++++++ apps/api/src/app/project/usecases/index.ts | 2 ++ .../document-not-found.exception.ts | 7 ++++++ libs/dal/src/repositories/base-repository.ts | 2 +- 5 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 apps/api/src/app/project/usecases/delete-project/delete-project.usecase.ts create mode 100644 apps/api/src/app/shared/exceptions/document-not-found.exception.ts diff --git a/apps/api/src/app/project/project.controller.ts b/apps/api/src/app/project/project.controller.ts index a437fd95f..c5e857973 100644 --- a/apps/api/src/app/project/project.controller.ts +++ b/apps/api/src/app/project/project.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Param, Post, Put } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; import { ApiOperation, ApiTags, ApiOkResponse } from '@nestjs/swagger'; import { CreateProjectRequestDto } from './dtos/create-project-request.dto'; import { ProjectResponseDto } from './dtos/project-response.dto'; @@ -8,6 +8,8 @@ import { CreateProjectCommand } from './usecases/create-project/create-project.c import { UpdateProjectRequestDto } from './dtos/update-project-request.dto'; import { UpdateProject } from './usecases/update-project/update-project.usecase'; import { UpdateProjectCommand } from './usecases/update-project/update-project.command'; +import { DeleteProject } from './usecases/delete-project/delete-project.usecase'; +import { DocumentNotFoundException } from '../shared/exceptions/document-not-found.exception'; @Controller('/project') @ApiTags('Project') @@ -15,7 +17,8 @@ export class ProjectController { constructor( private getProjectsUsecase: GetProjects, private createProjectUsecase: CreateProject, - private updateProjectUsecase: UpdateProject + private updateProjectUsecase: UpdateProject, + private deleteProjectUsecase: DeleteProject ) {} @Get('') @@ -58,4 +61,20 @@ export class ProjectController { ): Promise { return this.updateProjectUsecase.execute(UpdateProjectCommand.create({ name: body.name }), projectId); } + + @Delete(':projectId') + @ApiOperation({ + summary: 'Delete project', + }) + @ApiOkResponse({ + type: ProjectResponseDto, + }) + async deleteProject(@Param('projectId') projectId: string): Promise { + const document = await this.deleteProjectUsecase.execute(projectId); + if (!document) { + throw new DocumentNotFoundException('Project', projectId); + } + + return document; + } } diff --git a/apps/api/src/app/project/usecases/delete-project/delete-project.usecase.ts b/apps/api/src/app/project/usecases/delete-project/delete-project.usecase.ts new file mode 100644 index 000000000..9b43729f9 --- /dev/null +++ b/apps/api/src/app/project/usecases/delete-project/delete-project.usecase.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; +import { ProjectRepository } from '@impler/dal'; + +@Injectable() +export class DeleteProject { + constructor(private projectRepository: ProjectRepository) {} + + async execute(id: string) { + return this.projectRepository.delete({ _id: id }); + } +} diff --git a/apps/api/src/app/project/usecases/index.ts b/apps/api/src/app/project/usecases/index.ts index 1d50b0d67..6a77192e0 100644 --- a/apps/api/src/app/project/usecases/index.ts +++ b/apps/api/src/app/project/usecases/index.ts @@ -1,10 +1,12 @@ import { CreateProject } from './create-project/create-project.usecase'; import { GetProjects } from './get-projects/get-projects.usecase'; import { UpdateProject } from './update-project/update-project.usecase'; +import { DeleteProject } from './delete-project/delete-project.usecase'; export const USE_CASES = [ GetProjects, CreateProject, UpdateProject, + DeleteProject, // ]; diff --git a/apps/api/src/app/shared/exceptions/document-not-found.exception.ts b/apps/api/src/app/shared/exceptions/document-not-found.exception.ts new file mode 100644 index 000000000..a8832950c --- /dev/null +++ b/apps/api/src/app/shared/exceptions/document-not-found.exception.ts @@ -0,0 +1,7 @@ +import { NotFoundException } from '@nestjs/common'; + +export class DocumentNotFoundException extends NotFoundException { + constructor(name: string, id: string) { + super(`${name} with id ${id} does not exist`); + } +} diff --git a/libs/dal/src/repositories/base-repository.ts b/libs/dal/src/repositories/base-repository.ts index 9504ac103..01fe4d8ff 100644 --- a/libs/dal/src/repositories/base-repository.ts +++ b/libs/dal/src/repositories/base-repository.ts @@ -36,7 +36,7 @@ export class BaseRepository { } async delete(query: FilterQuery) { - const data = await this.MongooseModel.remove(query); + const data = await this.MongooseModel.findOneAndDelete(query); return data; } From d8367a415f51efcd5c229d35e0e2a7bcd793d94a Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Mon, 3 Oct 2022 10:43:32 +0530 Subject: [PATCH 05/12] fix: Updated module imports --- apps/api/src/app/project/project.module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/src/app/project/project.module.ts b/apps/api/src/app/project/project.module.ts index 39279abb7..53402a1e2 100644 --- a/apps/api/src/app/project/project.module.ts +++ b/apps/api/src/app/project/project.module.ts @@ -5,8 +5,8 @@ import { ProjectController } from './project.controller'; import { UniqueValidator } from '../shared/framework/IsUniqueValidator'; @Module({ - imports: [SharedModule], - providers: [...USE_CASES, UniqueValidator], + imports: [SharedModule, UniqueValidator], + providers: [...USE_CASES], controllers: [ProjectController], }) export class ProjectModule {} From 208124b63bff6241ac031edb6a1114daeafd9804 Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Mon, 3 Oct 2022 10:53:52 +0530 Subject: [PATCH 06/12] feat: Added Template repository --- libs/dal/src/index.ts | 1 + libs/dal/src/repositories/template/index.ts | 3 ++ .../repositories/template/template.entity.ts | 13 ++++++++ .../template/template.repository.ts | 9 ++++++ .../repositories/template/template.schema.ts | 32 +++++++++++++++++++ 5 files changed, 58 insertions(+) create mode 100644 libs/dal/src/repositories/template/index.ts create mode 100644 libs/dal/src/repositories/template/template.entity.ts create mode 100644 libs/dal/src/repositories/template/template.repository.ts create mode 100644 libs/dal/src/repositories/template/template.schema.ts diff --git a/libs/dal/src/index.ts b/libs/dal/src/index.ts index a2ca8901b..3aa9bf4a5 100644 --- a/libs/dal/src/index.ts +++ b/libs/dal/src/index.ts @@ -1,3 +1,4 @@ export * from './dal.service'; export * from './repositories/project'; export * from './repositories/common'; +export * from './repositories/template'; diff --git a/libs/dal/src/repositories/template/index.ts b/libs/dal/src/repositories/template/index.ts new file mode 100644 index 000000000..a5693e67a --- /dev/null +++ b/libs/dal/src/repositories/template/index.ts @@ -0,0 +1,3 @@ +export * from './template.entity'; +export * from './template.repository'; +export * from './template.schema'; diff --git a/libs/dal/src/repositories/template/template.entity.ts b/libs/dal/src/repositories/template/template.entity.ts new file mode 100644 index 000000000..7495a708e --- /dev/null +++ b/libs/dal/src/repositories/template/template.entity.ts @@ -0,0 +1,13 @@ +export class TemplateEntity { + _id?: string; + + name: string; + + code: string; + + callbackUrl: string; + + chunkSize: number; + + _projectId: string; +} diff --git a/libs/dal/src/repositories/template/template.repository.ts b/libs/dal/src/repositories/template/template.repository.ts new file mode 100644 index 000000000..696fdf71c --- /dev/null +++ b/libs/dal/src/repositories/template/template.repository.ts @@ -0,0 +1,9 @@ +import { BaseRepository } from '../base-repository'; +import { TemplateEntity } from './template.entity'; +import { Template } from './template.schema'; + +export class TemplateRepository extends BaseRepository { + constructor() { + super(Template, TemplateEntity); + } +} diff --git a/libs/dal/src/repositories/template/template.schema.ts b/libs/dal/src/repositories/template/template.schema.ts new file mode 100644 index 000000000..2cdb8906e --- /dev/null +++ b/libs/dal/src/repositories/template/template.schema.ts @@ -0,0 +1,32 @@ +import { Schema, Document, model, models } from 'mongoose'; +import { schemaOptions } from '../schema-default.options'; +import { TemplateEntity } from './template.entity'; + +const templateSchema = new Schema( + { + name: { + type: Schema.Types.String, + }, + code: { + type: Schema.Types.String, + }, + callbackUrl: { + type: Schema.Types.String, + }, + chunkSize: { + type: Schema.Types.Number, + }, + _projectId: { + type: Schema.Types.ObjectId, + ref: 'Project', + index: true, + }, + }, + { ...schemaOptions } +); + +interface ITemplateDocument extends TemplateEntity, Document { + _id: never; +} + +export const Template = models.Template || model('Template', templateSchema); From bec1cbca5d09178028d5716b500ec082d1836e44 Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Mon, 3 Oct 2022 11:03:15 +0530 Subject: [PATCH 07/12] fix: updated response to return _id --- .../app/project/usecases/get-projects/get-projects.usecase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/app/project/usecases/get-projects/get-projects.usecase.ts b/apps/api/src/app/project/usecases/get-projects/get-projects.usecase.ts index c25927e91..3d8123fd5 100644 --- a/apps/api/src/app/project/usecases/get-projects/get-projects.usecase.ts +++ b/apps/api/src/app/project/usecases/get-projects/get-projects.usecase.ts @@ -11,7 +11,7 @@ export class GetProjects { return projects.map((project) => { return { - id: project._id, + _id: project._id, name: project.name, code: project.code, }; From aad1a23bd8e84e0f4d162f869026c32f04d3938d Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Mon, 3 Oct 2022 11:25:37 +0530 Subject: [PATCH 08/12] feat: Added setup for templates API with get-templates route --- apps/api/src/app.module.ts | 7 ++- apps/api/src/app/shared/shared.module.ts | 4 +- .../template/dtos/template-response.dto.ts | 46 +++++++++++++++++++ .../src/app/template/template.controller.ts | 21 +++++++++ apps/api/src/app/template/template.module.ts | 11 +++++ .../get-templates/get-templates.usecase.ts | 21 +++++++++ apps/api/src/app/template/usecases/index.ts | 6 +++ 7 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 apps/api/src/app/template/dtos/template-response.dto.ts create mode 100644 apps/api/src/app/template/template.controller.ts create mode 100644 apps/api/src/app/template/template.module.ts create mode 100644 apps/api/src/app/template/usecases/get-templates/get-templates.usecase.ts create mode 100644 apps/api/src/app/template/usecases/index.ts diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index dd9b1ec62..de481564a 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -3,8 +3,13 @@ import { Type } from '@nestjs/common/interfaces/type.interface'; import { ForwardReference } from '@nestjs/common/interfaces/modules/forward-reference.interface'; import { SharedModule } from './app/shared/shared.module'; import { ProjectModule } from './app/project/project.module'; +import { TemplateModule } from './app/template/template.module'; -const modules: Array | ForwardReference> = [ProjectModule, SharedModule]; +const modules: Array | ForwardReference> = [ + ProjectModule, + SharedModule, + TemplateModule, +]; const providers = []; diff --git a/apps/api/src/app/shared/shared.module.ts b/apps/api/src/app/shared/shared.module.ts index e45b7e63f..89e4a66a8 100644 --- a/apps/api/src/app/shared/shared.module.ts +++ b/apps/api/src/app/shared/shared.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; -import { DalService, ProjectRepository } from '@impler/dal'; +import { DalService, ProjectRepository, TemplateRepository } from '@impler/dal'; -const DAL_MODELS = [ProjectRepository]; +const DAL_MODELS = [ProjectRepository, TemplateRepository]; const dalService = new DalService(); diff --git a/apps/api/src/app/template/dtos/template-response.dto.ts b/apps/api/src/app/template/dtos/template-response.dto.ts new file mode 100644 index 000000000..5468c6c36 --- /dev/null +++ b/apps/api/src/app/template/dtos/template-response.dto.ts @@ -0,0 +1,46 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsDefined, IsNumber, IsString } from 'class-validator'; + +export class TemplateResponseDto { + @ApiPropertyOptional({ + description: 'Id of the template', + }) + @IsString() + @IsDefined() + _id?: string; + + @ApiProperty({ + description: 'Name of the template', + }) + @IsString() + @IsDefined() + name: string; + + @ApiProperty({ + description: 'Code of the template', + }) + @IsString() + @IsDefined() + code: string; + + @ApiProperty({ + description: 'Callback URL of the template, gets called when sending data to the application', + }) + @IsString() + @IsDefined() + callbackUrl: string; + + @ApiProperty({ + description: 'Size of data in rows that gets sent to the application', + }) + @IsNumber() + @IsDefined() + chunkSize: number; + + @ApiProperty({ + description: 'Id of project related to the template', + }) + @IsString() + @IsDefined() + _projectId: string; +} diff --git a/apps/api/src/app/template/template.controller.ts b/apps/api/src/app/template/template.controller.ts new file mode 100644 index 000000000..354d0cf08 --- /dev/null +++ b/apps/api/src/app/template/template.controller.ts @@ -0,0 +1,21 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { ApiOperation, ApiTags, ApiOkResponse } from '@nestjs/swagger'; +import { TemplateResponseDto } from './dtos/template-response.dto'; +import { GetTemplates } from './usecases/get-templates/get-templates.usecase'; + +@Controller('/template') +@ApiTags('Template') +export class TemplateController { + constructor(private getTemplatesUsecase: GetTemplates) {} + + @Get(':projectId') + @ApiOperation({ + summary: 'Get project templates', + }) + @ApiOkResponse({ + type: [TemplateResponseDto], + }) + getTemplates(@Param('projectId') projectId: string): Promise { + return this.getTemplatesUsecase.execute(projectId); + } +} diff --git a/apps/api/src/app/template/template.module.ts b/apps/api/src/app/template/template.module.ts new file mode 100644 index 000000000..acd0f4b64 --- /dev/null +++ b/apps/api/src/app/template/template.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { USE_CASES } from './usecases'; +import { TemplateController } from './template.controller'; +import { SharedModule } from '../shared/shared.module'; + +@Module({ + imports: [SharedModule], + providers: [...USE_CASES], + controllers: [TemplateController], +}) +export class TemplateModule {} diff --git a/apps/api/src/app/template/usecases/get-templates/get-templates.usecase.ts b/apps/api/src/app/template/usecases/get-templates/get-templates.usecase.ts new file mode 100644 index 000000000..d34053c48 --- /dev/null +++ b/apps/api/src/app/template/usecases/get-templates/get-templates.usecase.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common'; +import { TemplateRepository } from '@impler/dal'; +import { TemplateResponseDto } from '../../dtos/template-response.dto'; + +@Injectable() +export class GetTemplates { + constructor(private templateRepository: TemplateRepository) {} + + async execute(projectId: string): Promise { + const templates = await this.templateRepository.find({ projectId }); + + return templates.map((template) => ({ + _projectId: template._projectId, + callbackUrl: template.callbackUrl, + chunkSize: template.chunkSize, + code: template.code, + name: template.name, + _id: template._id, + })); + } +} diff --git a/apps/api/src/app/template/usecases/index.ts b/apps/api/src/app/template/usecases/index.ts new file mode 100644 index 000000000..9c6e09aa3 --- /dev/null +++ b/apps/api/src/app/template/usecases/index.ts @@ -0,0 +1,6 @@ +import { GetTemplates } from './get-templates/get-templates.usecase'; + +export const USE_CASES = [ + GetTemplates, + // +]; From 14778af6f83ed97e1b3f77aedf7bb05a7e2388e4 Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Mon, 3 Oct 2022 11:42:54 +0530 Subject: [PATCH 09/12] feat: Added create-template API --- .../dtos/create-template-request.dto.ts | 43 +++++++++++++++++++ .../src/app/template/template.controller.ts | 26 ++++++++++- .../create-template.command.ts | 24 +++++++++++ .../create-template.usecase.ts | 12 ++++++ apps/api/src/app/template/usecases/index.ts | 2 + 5 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 apps/api/src/app/template/dtos/create-template-request.dto.ts create mode 100644 apps/api/src/app/template/usecases/create-template/create-template.command.ts create mode 100644 apps/api/src/app/template/usecases/create-template/create-template.usecase.ts diff --git a/apps/api/src/app/template/dtos/create-template-request.dto.ts b/apps/api/src/app/template/dtos/create-template-request.dto.ts new file mode 100644 index 000000000..9805915ce --- /dev/null +++ b/apps/api/src/app/template/dtos/create-template-request.dto.ts @@ -0,0 +1,43 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsDefined, IsString, Validate, IsNumber } from 'class-validator'; +import { UniqueValidator } from '../../shared/framework/IsUniqueValidator'; + +export class CreateTemplateRequestDto { + @ApiProperty({ + description: 'Name of the template', + }) + @IsString() + @IsDefined() + name: string; + + @ApiProperty({ + description: 'Code of the template', + }) + @IsString() + @IsDefined() + @Validate(UniqueValidator, ['Template', 'code'], { + message: 'Code is already taken', + }) + code: string; + + @ApiProperty({ + description: 'Callback URL of the template, gets called when sending data to the application', + }) + @IsString() + @IsDefined() + callbackUrl: string; + + @ApiProperty({ + description: 'Size of data in rows that gets sent to the application', + }) + @IsNumber() + @IsDefined() + chunkSize: number; + + @ApiProperty({ + description: 'Id of project related to the template', + }) + @IsString() + @IsDefined() + _projectId: string; +} diff --git a/apps/api/src/app/template/template.controller.ts b/apps/api/src/app/template/template.controller.ts index 354d0cf08..105d62a2c 100644 --- a/apps/api/src/app/template/template.controller.ts +++ b/apps/api/src/app/template/template.controller.ts @@ -1,12 +1,15 @@ -import { Controller, Get, Param } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post } from '@nestjs/common'; import { ApiOperation, ApiTags, ApiOkResponse } from '@nestjs/swagger'; +import { CreateTemplateRequestDto } from './dtos/create-template-request.dto'; import { TemplateResponseDto } from './dtos/template-response.dto'; +import { CreateTemplateCommand } from './usecases/create-template/create-template.command'; +import { CreateTemplate } from './usecases/create-template/create-template.usecase'; import { GetTemplates } from './usecases/get-templates/get-templates.usecase'; @Controller('/template') @ApiTags('Template') export class TemplateController { - constructor(private getTemplatesUsecase: GetTemplates) {} + constructor(private getTemplatesUsecase: GetTemplates, private createTemplateUsecase: CreateTemplate) {} @Get(':projectId') @ApiOperation({ @@ -18,4 +21,23 @@ export class TemplateController { getTemplates(@Param('projectId') projectId: string): Promise { return this.getTemplatesUsecase.execute(projectId); } + + @Post('') + @ApiOperation({ + summary: 'Create template', + }) + @ApiOkResponse({ + type: TemplateResponseDto, + }) + createTemplate(@Body() body: CreateTemplateRequestDto): Promise { + return this.createTemplateUsecase.execute( + CreateTemplateCommand.create({ + _projectId: body._projectId, + callbackUrl: body.callbackUrl, + chunkSize: body.chunkSize, + code: body.code, + name: body.name, + }) + ); + } } diff --git a/apps/api/src/app/template/usecases/create-template/create-template.command.ts b/apps/api/src/app/template/usecases/create-template/create-template.command.ts new file mode 100644 index 000000000..694e4e8e6 --- /dev/null +++ b/apps/api/src/app/template/usecases/create-template/create-template.command.ts @@ -0,0 +1,24 @@ +import { IsDefined, IsString, IsNumber } from 'class-validator'; +import { BaseCommand } from '../../../shared/commands/base.command'; + +export class CreateTemplateCommand extends BaseCommand { + @IsDefined() + @IsString() + name: string; + + @IsDefined() + @IsString() + code: string; + + @IsString() + @IsDefined() + callbackUrl: string; + + @IsNumber() + @IsDefined() + chunkSize: number; + + @IsString() + @IsDefined() + _projectId: string; +} diff --git a/apps/api/src/app/template/usecases/create-template/create-template.usecase.ts b/apps/api/src/app/template/usecases/create-template/create-template.usecase.ts new file mode 100644 index 000000000..5f8769046 --- /dev/null +++ b/apps/api/src/app/template/usecases/create-template/create-template.usecase.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@nestjs/common'; +import { TemplateRepository } from '@impler/dal'; +import { CreateTemplateCommand } from './create-template.command'; + +@Injectable() +export class CreateTemplate { + constructor(private templateRepository: TemplateRepository) {} + + async execute(command: CreateTemplateCommand) { + return this.templateRepository.create(command); + } +} diff --git a/apps/api/src/app/template/usecases/index.ts b/apps/api/src/app/template/usecases/index.ts index 9c6e09aa3..be087b2b9 100644 --- a/apps/api/src/app/template/usecases/index.ts +++ b/apps/api/src/app/template/usecases/index.ts @@ -1,6 +1,8 @@ import { GetTemplates } from './get-templates/get-templates.usecase'; +import { CreateTemplate } from './create-template/create-template.usecase'; export const USE_CASES = [ GetTemplates, + CreateTemplate, // ]; From 3a30aaf05dcb1b2498d2b64e64e5dc91bb174a4a Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Mon, 3 Oct 2022 13:43:30 +0530 Subject: [PATCH 10/12] feat: Added Update template API --- .../validations/valid-mongo-id.validation.ts | 17 ++++++++ .../dtos/update-template-request.dto.ts | 39 +++++++++++++++++++ .../src/app/template/template.controller.ts | 34 +++++++++++++++- apps/api/src/app/template/usecases/index.ts | 2 + .../update-template.command.ts | 26 +++++++++++++ .../update-template.usecase.ts | 12 ++++++ .../repositories/common/common.repository.ts | 6 ++- package.json | 1 + pnpm-lock.yaml | 2 + 9 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 apps/api/src/app/shared/validations/valid-mongo-id.validation.ts create mode 100644 apps/api/src/app/template/dtos/update-template-request.dto.ts create mode 100644 apps/api/src/app/template/usecases/update-template/update-template.command.ts create mode 100644 apps/api/src/app/template/usecases/update-template/update-template.usecase.ts diff --git a/apps/api/src/app/shared/validations/valid-mongo-id.validation.ts b/apps/api/src/app/shared/validations/valid-mongo-id.validation.ts new file mode 100644 index 000000000..5b1339ad8 --- /dev/null +++ b/apps/api/src/app/shared/validations/valid-mongo-id.validation.ts @@ -0,0 +1,17 @@ +import { CommonRepository } from '@impler/dal'; +import { BadRequestException, Injectable, PipeTransform } from '@nestjs/common'; + +@Injectable() +export class ValidateMongoId implements PipeTransform { + private commonRepository: CommonRepository; + constructor() { + this.commonRepository = new CommonRepository(); + } + + transform(value: string): string { + if (value && this.commonRepository.validMongoId(value)) { + return value; + } + throw new BadRequestException(); + } +} diff --git a/apps/api/src/app/template/dtos/update-template-request.dto.ts b/apps/api/src/app/template/dtos/update-template-request.dto.ts new file mode 100644 index 000000000..c2071d22c --- /dev/null +++ b/apps/api/src/app/template/dtos/update-template-request.dto.ts @@ -0,0 +1,39 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsOptional, IsNumber, IsNotEmpty } from 'class-validator'; + +export class UpdateTemplateRequestDto { + @ApiProperty({ + description: 'Name of the template', + nullable: false, + }) + @IsOptional() + name?: string; + + @ApiProperty({ + description: 'Callback URL of the template, gets called when sending data to the application', + nullable: false, + }) + @IsString() + @IsOptional() + callbackUrl?: string; + + @ApiProperty({ + description: 'Size of data in rows that gets sent to the application', + format: 'number', + nullable: false, + }) + @IsNumber({ + allowNaN: false, + }) + @IsOptional() + @IsNotEmpty() + chunkSize?: number; + + @ApiProperty({ + description: 'Id of project related to the template', + nullable: false, + }) + @IsString() + @IsOptional() + _projectId?: string; +} diff --git a/apps/api/src/app/template/template.controller.ts b/apps/api/src/app/template/template.controller.ts index 105d62a2c..5f37976dd 100644 --- a/apps/api/src/app/template/template.controller.ts +++ b/apps/api/src/app/template/template.controller.ts @@ -1,15 +1,23 @@ -import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post, Put } from '@nestjs/common'; import { ApiOperation, ApiTags, ApiOkResponse } from '@nestjs/swagger'; +import { ValidateMongoId } from '../shared/validations/valid-mongo-id.validation'; import { CreateTemplateRequestDto } from './dtos/create-template-request.dto'; import { TemplateResponseDto } from './dtos/template-response.dto'; +import { UpdateTemplateRequestDto } from './dtos/update-template-request.dto'; import { CreateTemplateCommand } from './usecases/create-template/create-template.command'; import { CreateTemplate } from './usecases/create-template/create-template.usecase'; import { GetTemplates } from './usecases/get-templates/get-templates.usecase'; +import { UpdateTemplateCommand } from './usecases/update-template/update-template.command'; +import { UpdateTemplate } from './usecases/update-template/update-template.usecase'; @Controller('/template') @ApiTags('Template') export class TemplateController { - constructor(private getTemplatesUsecase: GetTemplates, private createTemplateUsecase: CreateTemplate) {} + constructor( + private getTemplatesUsecase: GetTemplates, + private createTemplateUsecase: CreateTemplate, + private updateTemplateUsecase: UpdateTemplate + ) {} @Get(':projectId') @ApiOperation({ @@ -40,4 +48,26 @@ export class TemplateController { }) ); } + + @Put(':templateId') + @ApiOperation({ + summary: 'Update template', + }) + @ApiOkResponse({ + type: TemplateResponseDto, + }) + updateTemplate( + @Param('templateId', ValidateMongoId) templateId: string, + @Body() body: UpdateTemplateRequestDto + ): Promise { + return this.updateTemplateUsecase.execute( + UpdateTemplateCommand.create({ + _projectId: body._projectId, + callbackUrl: body.callbackUrl, + chunkSize: body.chunkSize, + name: body.name, + }), + templateId + ); + } } diff --git a/apps/api/src/app/template/usecases/index.ts b/apps/api/src/app/template/usecases/index.ts index be087b2b9..39832eccf 100644 --- a/apps/api/src/app/template/usecases/index.ts +++ b/apps/api/src/app/template/usecases/index.ts @@ -1,8 +1,10 @@ import { GetTemplates } from './get-templates/get-templates.usecase'; import { CreateTemplate } from './create-template/create-template.usecase'; +import { UpdateTemplate } from './update-template/update-template.usecase'; export const USE_CASES = [ GetTemplates, CreateTemplate, + UpdateTemplate, // ]; diff --git a/apps/api/src/app/template/usecases/update-template/update-template.command.ts b/apps/api/src/app/template/usecases/update-template/update-template.command.ts new file mode 100644 index 000000000..0474e19b9 --- /dev/null +++ b/apps/api/src/app/template/usecases/update-template/update-template.command.ts @@ -0,0 +1,26 @@ +import { IsString, IsNumber, IsOptional, IsNotEmpty, IsMongoId, Min } from 'class-validator'; +import { BaseCommand } from '../../../shared/commands/base.command'; + +export class UpdateTemplateCommand extends BaseCommand { + @IsString() + @IsOptional() + name?: string; + + @IsString() + @IsOptional() + callbackUrl?: string; + + @IsNumber({ + allowNaN: false, + }) + @IsOptional() + @IsNotEmpty() + @Min(1) + chunkSize?: number; + + @IsMongoId({ + message: '_projectId is not valid', + }) + @IsOptional() + _projectId?: string; +} diff --git a/apps/api/src/app/template/usecases/update-template/update-template.usecase.ts b/apps/api/src/app/template/usecases/update-template/update-template.usecase.ts new file mode 100644 index 000000000..b96f0589d --- /dev/null +++ b/apps/api/src/app/template/usecases/update-template/update-template.usecase.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@nestjs/common'; +import { TemplateRepository } from '@impler/dal'; +import { UpdateTemplateCommand } from './update-template.command'; + +@Injectable() +export class UpdateTemplate { + constructor(private templateRepository: TemplateRepository) {} + + async execute(command: UpdateTemplateCommand, id: string) { + return this.templateRepository.findOneAndUpdate({ _id: id }, command); + } +} diff --git a/libs/dal/src/repositories/common/common.repository.ts b/libs/dal/src/repositories/common/common.repository.ts index 123998bcf..2acbf46e0 100644 --- a/libs/dal/src/repositories/common/common.repository.ts +++ b/libs/dal/src/repositories/common/common.repository.ts @@ -1,4 +1,4 @@ -import { FilterQuery, models } from 'mongoose'; +import { FilterQuery, models, Types } from 'mongoose'; export class CommonRepository { async count(name: string, query: FilterQuery): Promise { @@ -11,4 +11,8 @@ export class CommonRepository { throw new Error(`Model ${name} does not exists`); } + + validMongoId(id: string): boolean { + return Types.ObjectId.isValid(id); + } } diff --git a/package.json b/package.json index 3f5ac6169..3b41cc765 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "eslint-plugin-promise": "^6.0.1", "eslint-plugin-react": "^7.31.8", "eslint-plugin-react-hooks": "^4.6.0", + "fs-extra": "^10.1.0", "husky": "^8.0.1", "lint-staged": "^13.0.3", "nx": "^14.7.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d19eaabe..0ea816961 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,7 @@ importers: eslint-plugin-promise: ^6.0.1 eslint-plugin-react: ^7.31.8 eslint-plugin-react-hooks: ^4.6.0 + fs-extra: ^10.1.0 husky: ^8.0.1 lint-staged: ^13.0.3 nx: ^14.7.11 @@ -45,6 +46,7 @@ importers: eslint-plugin-promise: 6.0.1_eslint@8.23.1 eslint-plugin-react: 7.31.8_eslint@8.23.1 eslint-plugin-react-hooks: 4.6.0_eslint@8.23.1 + fs-extra: 10.1.0 husky: 8.0.1 lint-staged: 13.0.3 nx: 14.7.11 From deb720f99e88643d8c3b6f1bfd6199edee912755 Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Mon, 3 Oct 2022 13:45:12 +0530 Subject: [PATCH 11/12] feat: Added valid mongodb id validation to params --- apps/api/src/app/project/project.controller.ts | 5 +++-- apps/api/src/app/template/template.controller.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/api/src/app/project/project.controller.ts b/apps/api/src/app/project/project.controller.ts index c5e857973..e6deabb40 100644 --- a/apps/api/src/app/project/project.controller.ts +++ b/apps/api/src/app/project/project.controller.ts @@ -10,6 +10,7 @@ import { UpdateProject } from './usecases/update-project/update-project.usecase' import { UpdateProjectCommand } from './usecases/update-project/update-project.command'; import { DeleteProject } from './usecases/delete-project/delete-project.usecase'; import { DocumentNotFoundException } from '../shared/exceptions/document-not-found.exception'; +import { ValidateMongoId } from '../shared/validations/valid-mongo-id.validation'; @Controller('/project') @ApiTags('Project') @@ -57,7 +58,7 @@ export class ProjectController { }) updateProject( @Body() body: UpdateProjectRequestDto, - @Param('projectId') projectId: string + @Param('projectId', ValidateMongoId) projectId: string ): Promise { return this.updateProjectUsecase.execute(UpdateProjectCommand.create({ name: body.name }), projectId); } @@ -69,7 +70,7 @@ export class ProjectController { @ApiOkResponse({ type: ProjectResponseDto, }) - async deleteProject(@Param('projectId') projectId: string): Promise { + async deleteProject(@Param('projectId', ValidateMongoId) projectId: string): Promise { const document = await this.deleteProjectUsecase.execute(projectId); if (!document) { throw new DocumentNotFoundException('Project', projectId); diff --git a/apps/api/src/app/template/template.controller.ts b/apps/api/src/app/template/template.controller.ts index 5f37976dd..3f355f7d2 100644 --- a/apps/api/src/app/template/template.controller.ts +++ b/apps/api/src/app/template/template.controller.ts @@ -26,7 +26,7 @@ export class TemplateController { @ApiOkResponse({ type: [TemplateResponseDto], }) - getTemplates(@Param('projectId') projectId: string): Promise { + getTemplates(@Param('projectId', ValidateMongoId) projectId: string): Promise { return this.getTemplatesUsecase.execute(projectId); } From 3d8d33ba0298bda65c5adce3fbb9e1b19bce2d41 Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Mon, 3 Oct 2022 13:56:11 +0530 Subject: [PATCH 12/12] feat: Added delete API and added error-handling for not-found documents --- .../api/src/app/project/project.controller.ts | 12 +++++-- .../src/app/template/template.controller.ts | 32 ++++++++++++++++--- .../delete-template.usecase.ts | 11 +++++++ apps/api/src/app/template/usecases/index.ts | 2 ++ 4 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 apps/api/src/app/template/usecases/delete-template/delete-template.usecase.ts diff --git a/apps/api/src/app/project/project.controller.ts b/apps/api/src/app/project/project.controller.ts index e6deabb40..cbcdc9f9a 100644 --- a/apps/api/src/app/project/project.controller.ts +++ b/apps/api/src/app/project/project.controller.ts @@ -56,11 +56,19 @@ export class ProjectController { @ApiOkResponse({ type: ProjectResponseDto, }) - updateProject( + async updateProject( @Body() body: UpdateProjectRequestDto, @Param('projectId', ValidateMongoId) projectId: string ): Promise { - return this.updateProjectUsecase.execute(UpdateProjectCommand.create({ name: body.name }), projectId); + const document = await this.updateProjectUsecase.execute( + UpdateProjectCommand.create({ name: body.name }), + projectId + ); + if (!document) { + throw new DocumentNotFoundException('Project', projectId); + } + + return document; } @Delete(':projectId') diff --git a/apps/api/src/app/template/template.controller.ts b/apps/api/src/app/template/template.controller.ts index 3f355f7d2..658f05244 100644 --- a/apps/api/src/app/template/template.controller.ts +++ b/apps/api/src/app/template/template.controller.ts @@ -1,11 +1,13 @@ -import { Body, Controller, Get, Param, Post, Put } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; import { ApiOperation, ApiTags, ApiOkResponse } from '@nestjs/swagger'; +import { DocumentNotFoundException } from '../shared/exceptions/document-not-found.exception'; import { ValidateMongoId } from '../shared/validations/valid-mongo-id.validation'; import { CreateTemplateRequestDto } from './dtos/create-template-request.dto'; import { TemplateResponseDto } from './dtos/template-response.dto'; import { UpdateTemplateRequestDto } from './dtos/update-template-request.dto'; import { CreateTemplateCommand } from './usecases/create-template/create-template.command'; import { CreateTemplate } from './usecases/create-template/create-template.usecase'; +import { DeleteTemplate } from './usecases/delete-template/delete-template.usecase'; import { GetTemplates } from './usecases/get-templates/get-templates.usecase'; import { UpdateTemplateCommand } from './usecases/update-template/update-template.command'; import { UpdateTemplate } from './usecases/update-template/update-template.usecase'; @@ -16,7 +18,8 @@ export class TemplateController { constructor( private getTemplatesUsecase: GetTemplates, private createTemplateUsecase: CreateTemplate, - private updateTemplateUsecase: UpdateTemplate + private updateTemplateUsecase: UpdateTemplate, + private deleteTemplateUsecase: DeleteTemplate ) {} @Get(':projectId') @@ -56,11 +59,11 @@ export class TemplateController { @ApiOkResponse({ type: TemplateResponseDto, }) - updateTemplate( + async updateTemplate( @Param('templateId', ValidateMongoId) templateId: string, @Body() body: UpdateTemplateRequestDto ): Promise { - return this.updateTemplateUsecase.execute( + const document = await this.updateTemplateUsecase.execute( UpdateTemplateCommand.create({ _projectId: body._projectId, callbackUrl: body.callbackUrl, @@ -69,5 +72,26 @@ export class TemplateController { }), templateId ); + if (!document) { + throw new DocumentNotFoundException('Template', templateId); + } + + return document; + } + + @Delete(':templateId') + @ApiOperation({ + summary: 'Delete template', + }) + @ApiOkResponse({ + type: TemplateResponseDto, + }) + async deleteTemplate(@Param('templateId', ValidateMongoId) templateId: string): Promise { + const document = await this.deleteTemplateUsecase.execute(templateId); + if (!document) { + throw new DocumentNotFoundException('Template', templateId); + } + + return document; } } diff --git a/apps/api/src/app/template/usecases/delete-template/delete-template.usecase.ts b/apps/api/src/app/template/usecases/delete-template/delete-template.usecase.ts new file mode 100644 index 000000000..d33666314 --- /dev/null +++ b/apps/api/src/app/template/usecases/delete-template/delete-template.usecase.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; +import { TemplateRepository } from '@impler/dal'; + +@Injectable() +export class DeleteTemplate { + constructor(private templateRepository: TemplateRepository) {} + + async execute(id: string) { + return this.templateRepository.delete({ _id: id }); + } +} diff --git a/apps/api/src/app/template/usecases/index.ts b/apps/api/src/app/template/usecases/index.ts index 39832eccf..c98a08823 100644 --- a/apps/api/src/app/template/usecases/index.ts +++ b/apps/api/src/app/template/usecases/index.ts @@ -1,10 +1,12 @@ import { GetTemplates } from './get-templates/get-templates.usecase'; import { CreateTemplate } from './create-template/create-template.usecase'; import { UpdateTemplate } from './update-template/update-template.usecase'; +import { DeleteTemplate } from './delete-template/delete-template.usecase'; export const USE_CASES = [ GetTemplates, CreateTemplate, UpdateTemplate, + DeleteTemplate, // ];