From 588edd4b38e5d5b997f5fb30be29ae69521c3844 Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Tue, 18 Oct 2022 09:50:06 +0530 Subject: [PATCH 1/5] feat: Added stages validation to mapping API --- apps/api/src/app/mapping/mapping.controller.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/apps/api/src/app/mapping/mapping.controller.ts b/apps/api/src/app/mapping/mapping.controller.ts index f2f6aefb8..e27ed878b 100644 --- a/apps/api/src/app/mapping/mapping.controller.ts +++ b/apps/api/src/app/mapping/mapping.controller.ts @@ -15,6 +15,7 @@ import { UpdateMappings } from './usecases/update-mappings/update-mappings.useca import { FinalizeUpload } from './usecases/finalize-upload/finalize-upload.usecase'; import { UpdateMappingDto } from './dtos/update-columns.dto'; import { ValidateMapping } from './usecases/validate-mapping/validate-mapping.usecase'; +import { validateUploadStatus } from '../shared/helpers/upload.helpers'; @Controller('/mapping') @ApiTags('Mappings') @@ -42,6 +43,12 @@ export class MappingController { }) ); + // Get mappings can be called only when file is uploaded or it's mapping in progress + validateUploadStatus(uploadInformation.status as UploadStatusEnum, [ + UploadStatusEnum.UPLOADED, + UploadStatusEnum.MAPPING, + ]); + if (uploadInformation.status === UploadStatusEnum.UPLOADED) { await this.doMapping.execute( DoMappingCommand.create({ @@ -64,6 +71,16 @@ export class MappingController { @Param('uploadId', ValidateMongoId) _uploadId: string, @Body(new ParseArrayPipe({ items: UpdateMappingDto, optional: true })) body: UpdateMappingDto[] ) { + const uploadInformation = await this.getUpload.execute( + GetUploadCommand.create({ + uploadId: _uploadId, + select: 'status', + }) + ); + + // Finalize mapping can only be called after the mapping has been completed + validateUploadStatus(uploadInformation.status as UploadStatusEnum, [UploadStatusEnum.MAPPING]); + // validate mapping data await this.validateMapping.execute(body, _uploadId); From 7c7144237dbd5a091bf0d29ba6009291199cd0f6 Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Tue, 18 Oct 2022 09:56:34 +0530 Subject: [PATCH 2/5] feat: Added field to whether proceed for invalid data or not --- libs/dal/src/repositories/upload/upload.entity.ts | 2 ++ libs/dal/src/repositories/upload/upload.schema.ts | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/libs/dal/src/repositories/upload/upload.entity.ts b/libs/dal/src/repositories/upload/upload.entity.ts index 6e89a16f3..d9572da9f 100644 --- a/libs/dal/src/repositories/upload/upload.entity.ts +++ b/libs/dal/src/repositories/upload/upload.entity.ts @@ -26,4 +26,6 @@ export class UploadEntity { status: string; extra: string; + + processInvalidRecords: boolean; } diff --git a/libs/dal/src/repositories/upload/upload.schema.ts b/libs/dal/src/repositories/upload/upload.schema.ts index 2107dfcc1..a7ef10245 100644 --- a/libs/dal/src/repositories/upload/upload.schema.ts +++ b/libs/dal/src/repositories/upload/upload.schema.ts @@ -38,6 +38,10 @@ const uploadSchema = new Schema( authHeaderValue: String, status: String, extra: String, + processInvalidRecords: { + type: Boolean, + default: false, + }, }, { ...schemaOptions } ); From fb5c367a8fea6c4261a3ec6e007b33e927e004f4 Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Tue, 18 Oct 2022 10:17:17 +0530 Subject: [PATCH 3/5] feat: Reused get-upload usecase --- apps/api/src/app/mapping/mapping.controller.ts | 4 ++-- apps/api/src/app/mapping/mapping.module.ts | 3 +-- apps/api/src/app/mapping/usecases/index.ts | 2 ++ .../usecases/get-upload/get-upload.command.ts | 2 +- .../usecases/get-upload/get-upload.usecase.ts | 0 apps/api/src/app/upload/upload.controller.ts | 4 ++-- apps/api/src/app/upload/usecases/index.ts | 2 +- 7 files changed, 9 insertions(+), 8 deletions(-) rename apps/api/src/app/{upload => shared}/usecases/get-upload/get-upload.command.ts (77%) rename apps/api/src/app/{upload => shared}/usecases/get-upload/get-upload.usecase.ts (100%) diff --git a/apps/api/src/app/mapping/mapping.controller.ts b/apps/api/src/app/mapping/mapping.controller.ts index e27ed878b..76176f8ea 100644 --- a/apps/api/src/app/mapping/mapping.controller.ts +++ b/apps/api/src/app/mapping/mapping.controller.ts @@ -5,10 +5,10 @@ import { MappingEntity } from '@impler/dal'; import { APIKeyGuard } from '../shared/framework/auth.gaurd'; import { ValidateMongoId } from '../shared/validations/valid-mongo-id.validation'; -import { GetUploadCommand } from './../upload/usecases/get-upload/get-upload.command'; +import { GetUploadCommand } from '../shared/usecases/get-upload/get-upload.command'; import { DoMapping } from './usecases/do-mapping/do-mapping.usecase'; import { DoMappingCommand } from './usecases/do-mapping/do-mapping.command'; -import { GetUpload } from './../upload/usecases/get-upload/get-upload.usecase'; +import { GetUpload } from '../shared/usecases/get-upload/get-upload.usecase'; import { GetMappings } from './usecases/get-mappings/get-mappings.usecase'; import { UpdateMappingCommand } from './usecases/update-mappings/update-mappings.command'; import { UpdateMappings } from './usecases/update-mappings/update-mappings.usecase'; diff --git a/apps/api/src/app/mapping/mapping.module.ts b/apps/api/src/app/mapping/mapping.module.ts index 160126f06..02443ad4a 100644 --- a/apps/api/src/app/mapping/mapping.module.ts +++ b/apps/api/src/app/mapping/mapping.module.ts @@ -1,12 +1,11 @@ import { Module } from '@nestjs/common'; import { USE_CASES } from './usecases'; -import { GetUpload } from './../upload/usecases/get-upload/get-upload.usecase'; import { MappingController } from './mapping.controller'; import { SharedModule } from '../shared/shared.module'; @Module({ imports: [SharedModule], - providers: [...USE_CASES, GetUpload], + providers: [...USE_CASES], controllers: [MappingController], }) export class MappingModule {} diff --git a/apps/api/src/app/mapping/usecases/index.ts b/apps/api/src/app/mapping/usecases/index.ts index d7f20480e..f1557f27e 100644 --- a/apps/api/src/app/mapping/usecases/index.ts +++ b/apps/api/src/app/mapping/usecases/index.ts @@ -3,6 +3,7 @@ import { GetMappings } from './get-mappings/get-mappings.usecase'; import { UpdateMappings } from './update-mappings/update-mappings.usecase'; import { FinalizeUpload } from './finalize-upload/finalize-upload.usecase'; import { ValidateMapping } from './validate-mapping/validate-mapping.usecase'; +import { GetUpload } from '../../shared/usecases/get-upload/get-upload.usecase'; export const USE_CASES = [ DoMapping, @@ -10,5 +11,6 @@ export const USE_CASES = [ UpdateMappings, FinalizeUpload, ValidateMapping, + GetUpload, // ]; diff --git a/apps/api/src/app/upload/usecases/get-upload/get-upload.command.ts b/apps/api/src/app/shared/usecases/get-upload/get-upload.command.ts similarity index 77% rename from apps/api/src/app/upload/usecases/get-upload/get-upload.command.ts rename to apps/api/src/app/shared/usecases/get-upload/get-upload.command.ts index 31f51fc08..9abd8f9b8 100644 --- a/apps/api/src/app/upload/usecases/get-upload/get-upload.command.ts +++ b/apps/api/src/app/shared/usecases/get-upload/get-upload.command.ts @@ -1,5 +1,5 @@ import { IsDefined, IsMongoId, IsOptional, IsString } from 'class-validator'; -import { BaseCommand } from '../../../shared/commands/base.command'; +import { BaseCommand } from '../../commands/base.command'; export class GetUploadCommand extends BaseCommand { @IsDefined() diff --git a/apps/api/src/app/upload/usecases/get-upload/get-upload.usecase.ts b/apps/api/src/app/shared/usecases/get-upload/get-upload.usecase.ts similarity index 100% rename from apps/api/src/app/upload/usecases/get-upload/get-upload.usecase.ts rename to apps/api/src/app/shared/usecases/get-upload/get-upload.usecase.ts diff --git a/apps/api/src/app/upload/upload.controller.ts b/apps/api/src/app/upload/upload.controller.ts index f48ad9281..6ddd15df8 100644 --- a/apps/api/src/app/upload/upload.controller.ts +++ b/apps/api/src/app/upload/upload.controller.ts @@ -11,8 +11,8 @@ import { ValidImportFile } from '../shared/validations/valid-import-file.validat import { MakeUploadEntry } from './usecases/make-upload-entry/make-upload-entry.usecase'; import { MakeUploadEntryCommand } from './usecases/make-upload-entry/make-upload-entry.command'; import { ValidateMongoId } from '../shared/validations/valid-mongo-id.validation'; -import { GetUpload } from './usecases/get-upload/get-upload.usecase'; -import { GetUploadCommand } from './usecases/get-upload/get-upload.command'; +import { GetUpload } from '../shared/usecases/get-upload/get-upload.usecase'; +import { GetUploadCommand } from '../shared/usecases/get-upload/get-upload.command'; import { GetUploads } from './usecases/get-uploads/get-uploads.usecase'; import { GetUploadsCommand } from './usecases/get-uploads/get-uploads.command'; import { ValidateTemplate } from '../shared/validations/valid-template.validation'; diff --git a/apps/api/src/app/upload/usecases/index.ts b/apps/api/src/app/upload/usecases/index.ts index 00fbf4ef9..eb307b79c 100644 --- a/apps/api/src/app/upload/usecases/index.ts +++ b/apps/api/src/app/upload/usecases/index.ts @@ -1,5 +1,5 @@ import { MakeUploadEntry } from './make-upload-entry/make-upload-entry.usecase'; -import { GetUpload } from './get-upload/get-upload.usecase'; +import { GetUpload } from '../../shared/usecases/get-upload/get-upload.usecase'; import { GetUploads } from './get-uploads/get-uploads.usecase'; export const USE_CASES = [ From df13705a6e8b1b49d667862b942e9b390ec3ad8e Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Tue, 18 Oct 2022 10:46:22 +0530 Subject: [PATCH 4/5] feat: Added not-found upload validation in mapping --- apps/api/src/app/mapping/mapping.controller.ts | 7 +++++++ apps/api/src/app/shared/helpers/common.helper.ts | 14 ++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 apps/api/src/app/shared/helpers/common.helper.ts diff --git a/apps/api/src/app/mapping/mapping.controller.ts b/apps/api/src/app/mapping/mapping.controller.ts index 76176f8ea..4ec45512e 100644 --- a/apps/api/src/app/mapping/mapping.controller.ts +++ b/apps/api/src/app/mapping/mapping.controller.ts @@ -16,6 +16,7 @@ import { FinalizeUpload } from './usecases/finalize-upload/finalize-upload.useca import { UpdateMappingDto } from './dtos/update-columns.dto'; import { ValidateMapping } from './usecases/validate-mapping/validate-mapping.usecase'; import { validateUploadStatus } from '../shared/helpers/upload.helpers'; +import { validateNotFound } from '../shared/helpers/common.helper'; @Controller('/mapping') @ApiTags('Mappings') @@ -43,6 +44,9 @@ export class MappingController { }) ); + // throw error if upload information not found + validateNotFound(uploadInformation, 'upload'); + // Get mappings can be called only when file is uploaded or it's mapping in progress validateUploadStatus(uploadInformation.status as UploadStatusEnum, [ UploadStatusEnum.UPLOADED, @@ -78,6 +82,9 @@ export class MappingController { }) ); + // throw error if upload information not found + validateNotFound(uploadInformation, 'upload'); + // Finalize mapping can only be called after the mapping has been completed validateUploadStatus(uploadInformation.status as UploadStatusEnum, [UploadStatusEnum.MAPPING]); diff --git a/apps/api/src/app/shared/helpers/common.helper.ts b/apps/api/src/app/shared/helpers/common.helper.ts new file mode 100644 index 000000000..22fda0599 --- /dev/null +++ b/apps/api/src/app/shared/helpers/common.helper.ts @@ -0,0 +1,14 @@ +import { BadRequestException } from '@nestjs/common'; +import { APIMessages } from '../constants'; + +export function validateNotFound(data: any, entityName: 'upload'): boolean { + if (data) return true; + else { + switch (entityName) { + case 'upload': + throw new BadRequestException(APIMessages.UPLOAD_NOT_FOUND); + default: + throw new BadRequestException(); + } + } +} From ca753e76e1bd7cd1d1960375b40ce05a38ae1489 Mon Sep 17 00:00:00 2001 From: chavda-bhavik Date: Tue, 18 Oct 2022 11:04:10 +0530 Subject: [PATCH 5/5] feat: Added confirm review API --- .../review/dtos/confirm-review-request.dto.ts | 13 ++++++ apps/api/src/app/review/review.controller.ts | 42 ++++++++++++++++++- .../confirm-review/confirm-review.command.ts | 12 ++++++ .../confirm-review/confirm-review.usecase.ts | 16 +++++++ apps/api/src/app/review/usecases/index.ts | 4 ++ 5 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 apps/api/src/app/review/dtos/confirm-review-request.dto.ts create mode 100644 apps/api/src/app/review/usecases/confirm-review/confirm-review.command.ts create mode 100644 apps/api/src/app/review/usecases/confirm-review/confirm-review.usecase.ts diff --git a/apps/api/src/app/review/dtos/confirm-review-request.dto.ts b/apps/api/src/app/review/dtos/confirm-review-request.dto.ts new file mode 100644 index 000000000..040edc4ef --- /dev/null +++ b/apps/api/src/app/review/dtos/confirm-review-request.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsOptional } from 'class-validator'; + +export class ConfirmReviewRequestDto { + @ApiProperty({ + description: 'Boolean value indicating whether to process the invalid data or not.', + default: false, + required: false, + }) + @IsBoolean() + @IsOptional() + processInvalidRecords: boolean; +} diff --git a/apps/api/src/app/review/review.controller.ts b/apps/api/src/app/review/review.controller.ts index 75ad39183..f528484c1 100644 --- a/apps/api/src/app/review/review.controller.ts +++ b/apps/api/src/app/review/review.controller.ts @@ -1,6 +1,6 @@ -import { BadRequestException, Controller, Get, Param, UseGuards } from '@nestjs/common'; +import { BadRequestException, Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; import { ApiOperation, ApiTags, ApiSecurity } from '@nestjs/swagger'; -import { FileEntity } from '@impler/dal'; +import { FileEntity, UploadEntity } from '@impler/dal'; import { UploadStatusEnum } from '@impler/shared'; import { APIMessages } from '../shared/constants'; import { APIKeyGuard } from '../shared/framework/auth.gaurd'; @@ -9,6 +9,13 @@ import { DoReview } from './usecases/do-review/do-review.usecase'; import { GetUploadInvalidData } from './usecases/get-upload-invalid-data/get-upload-invalid-data.usecase'; import { SaveReviewData } from './usecases/save-review-data/save-review-data.usecase'; import { GetFileInvalidData } from './usecases/get-file-invalid-data/get-file-invalid-data.usecase'; +import { ValidateMongoId } from '../shared/validations/valid-mongo-id.validation'; +import { ConfirmReviewRequestDto } from './dtos/confirm-review-request.dto'; +import { GetUploadCommand } from '../shared/usecases/get-upload/get-upload.command'; +import { GetUpload } from '../shared/usecases/get-upload/get-upload.usecase'; +import { validateNotFound } from '../shared/helpers/common.helper'; +import { ConfirmReview } from './usecases/confirm-review/confirm-review.usecase'; +import { ConfirmReviewCommand } from './usecases/confirm-review/confirm-review.command'; @Controller('/review') @ApiTags('Review') @@ -17,6 +24,8 @@ import { GetFileInvalidData } from './usecases/get-file-invalid-data/get-file-in export class ReviewController { constructor( private doReview: DoReview, + private getUpload: GetUpload, + private confirmReview: ConfirmReview, private saveReviewData: SaveReviewData, private getFileInvalidData: GetFileInvalidData, private getUploadInvalidData: GetUploadInvalidData @@ -45,4 +54,33 @@ export class ReviewController { return this.getFileInvalidData.execute((uploadData._invalidDataFileId as unknown as FileEntity).path); } } + + @Post(':uploadId/confirm') + @ApiOperation({ + summary: 'Confirm review data for uploaded file', + }) + async doConfirmReview( + @Param('uploadId', ValidateMongoId) _uploadId: string, + @Body() body: ConfirmReviewRequestDto + ): Promise { + const uploadInformation = await this.getUpload.execute( + GetUploadCommand.create({ + uploadId: _uploadId, + select: 'status', + }) + ); + + // throw error if upload information not found + validateNotFound(uploadInformation, 'upload'); + + // upload files with status reviewing can only be confirmed + validateUploadStatus(uploadInformation.status as UploadStatusEnum, [UploadStatusEnum.REVIEWING]); + + return this.confirmReview.execute( + ConfirmReviewCommand.create({ + _uploadId: _uploadId, + processInvalidRecords: body.processInvalidRecords, + }) + ); + } } diff --git a/apps/api/src/app/review/usecases/confirm-review/confirm-review.command.ts b/apps/api/src/app/review/usecases/confirm-review/confirm-review.command.ts new file mode 100644 index 000000000..80e0b97c5 --- /dev/null +++ b/apps/api/src/app/review/usecases/confirm-review/confirm-review.command.ts @@ -0,0 +1,12 @@ +import { IsBoolean, IsDefined, IsMongoId } from 'class-validator'; +import { BaseCommand } from '../../../shared/commands/base.command'; + +export class ConfirmReviewCommand extends BaseCommand { + @IsDefined() + @IsMongoId() + _uploadId: string; + + @IsDefined() + @IsBoolean() + processInvalidRecords: boolean; +} diff --git a/apps/api/src/app/review/usecases/confirm-review/confirm-review.usecase.ts b/apps/api/src/app/review/usecases/confirm-review/confirm-review.usecase.ts new file mode 100644 index 000000000..681db332f --- /dev/null +++ b/apps/api/src/app/review/usecases/confirm-review/confirm-review.usecase.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { UploadEntity, UploadRepository } from '@impler/dal'; +import { ConfirmReviewCommand } from './confirm-review.command'; +import { UploadStatusEnum } from '@impler/shared'; + +@Injectable() +export class ConfirmReview { + constructor(private uploadRepository: UploadRepository) {} + + execute(command: ConfirmReviewCommand): Promise { + return this.uploadRepository.findOneAndUpdate( + { _id: command._uploadId }, + { status: UploadStatusEnum.CONFIRMED, processInvalidRecords: command.processInvalidRecords } + ); + } +} diff --git a/apps/api/src/app/review/usecases/index.ts b/apps/api/src/app/review/usecases/index.ts index 85a02fe90..aa8c1650f 100644 --- a/apps/api/src/app/review/usecases/index.ts +++ b/apps/api/src/app/review/usecases/index.ts @@ -2,9 +2,13 @@ import { DoReview } from './do-review/do-review.usecase'; import { SaveReviewData } from './save-review-data/save-review-data.usecase'; import { GetUploadInvalidData } from './get-upload-invalid-data/get-upload-invalid-data.usecase'; import { GetFileInvalidData } from './get-file-invalid-data/get-file-invalid-data.usecase'; +import { GetUpload } from '../../shared/usecases/get-upload/get-upload.usecase'; +import { ConfirmReview } from './confirm-review/confirm-review.usecase'; export const USE_CASES = [ DoReview, + GetUpload, + ConfirmReview, SaveReviewData, GetFileInvalidData, GetUploadInvalidData,