Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Valid Invalid data save #16

Merged
merged 3 commits into from
Oct 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { SupportedFileMimeTypesEnum } from '@impler/shared';
import { FileMimeTypesEnum } from '@impler/shared';
import { ColumnRepository, TemplateRepository } from '@impler/dal';
import { UpdateColumnCommand } from './update-columns.command';
import { StorageService } from '../../../shared/storage/storage.service';
Expand All @@ -25,7 +25,7 @@ export class UpdateColumns {
const csvContent = this.createCSVFileHeadingContent(data);
const fileName = this.fileNameService.getSampleFileName(templateId);
const sampleFileUrl = this.fileNameService.getSampleFileUrl(templateId);
await this.storageService.uploadFile(fileName, csvContent, SupportedFileMimeTypesEnum.CSV, true);
await this.storageService.uploadFile(fileName, csvContent, FileMimeTypesEnum.CSV, true);
await this.templateRepository.update({ _id: templateId }, { sampleFileUrl });
}

Expand Down
36 changes: 32 additions & 4 deletions apps/api/src/app/review/review.controller.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,48 @@
import { Controller, Get, Param, UseGuards } from '@nestjs/common';
import { BadRequestException, Controller, Get, Param, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiTags, ApiSecurity } from '@nestjs/swagger';
import { FileEntity } from '@impler/dal';
import { UploadStatusEnum } from '@impler/shared';
import { APIMessages } from '../shared/constants';
import { APIKeyGuard } from '../shared/framework/auth.gaurd';
import { validateUploadStatus } from '../shared/helpers/upload.helpers';
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';

@Controller('/review')
@ApiTags('Review')
@ApiSecurity('ACCESS_KEY')
@UseGuards(APIKeyGuard)
export class ReviewController {
constructor(private doReview: DoReview) {}
constructor(
private doReview: DoReview,
private saveReviewData: SaveReviewData,
private getFileInvalidData: GetFileInvalidData,
private getUploadInvalidData: GetUploadInvalidData
) {}

@Get(':uploadId')
@ApiOperation({
summary: 'Get Review data for uploaded file',
})
async getReview(@Param('uploadId') uploadId: string) {
return await this.doReview.execute(uploadId);
async getReview(@Param('uploadId') _uploadId: string) {
const uploadData = await this.getUploadInvalidData.execute(_uploadId);
if (!uploadData) throw new BadRequestException(APIMessages.UPLOAD_NOT_FOUND);

// Only Mapped & Reviewing status are allowed
validateUploadStatus(uploadData.status as UploadStatusEnum, [UploadStatusEnum.MAPPED, UploadStatusEnum.REVIEWING]);

if (uploadData.status === UploadStatusEnum.MAPPED) {
// uploaded file is mapped, do review
const reviewData = await this.doReview.execute(_uploadId);
// save invalid data to storage
this.saveReviewData.execute(_uploadId, reviewData.invalid, reviewData.valid);

return reviewData.invalid;
} else {
// Uploaded file is already reviewed, return reviewed data
return this.getFileInvalidData.execute((uploadData._invalidDataFileId as unknown as FileEntity).path);
}
}
}
2 changes: 1 addition & 1 deletion apps/api/src/app/review/service/AJV.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ ajv.addFormat('custom-date-time', function (dateTimeString) {

@Injectable()
export class AJVService {
async validate(columns: ColumnEntity[], mappings: MappingEntity[], data: any) {
validate(columns: ColumnEntity[], mappings: MappingEntity[], data: any) {
const schema = this.buildAJVSchema(columns, mappings);
const validator = ajv.compile(schema);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UploadStatusEnum } from '@impler/shared';
import { FileEncodingsEnum, UploadStatusEnum } from '@impler/shared';
import { Injectable, BadRequestException } from '@nestjs/common';
import { ColumnRepository, UploadRepository, MappingRepository, FileEntity } from '@impler/dal';
import { StorageService } from '../../../shared/storage/storage.service';
Expand Down Expand Up @@ -31,13 +31,15 @@ export class DoReview {
{ _templateId: uploadInfo._templateId },
'isRequired isUnique selectValues type regex'
);
const reviewData = this.ajvService.validate(columns, mappings, dataContent);
this.uploadRepository.update({ _id: uploadId }, { status: UploadStatusEnum.REVIEWING });

return this.ajvService.validate(columns, mappings, dataContent);
return reviewData;
}

async getFileContent(path): Promise<string> {
try {
const dataContent = await this.storageService.getFileContent(path, 'utf8');
const dataContent = await this.storageService.getFileContent(path, FileEncodingsEnum.JSON);

return JSON.parse(dataContent);
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Injectable } from '@nestjs/common';
import { FileEncodingsEnum } from '@impler/shared';
import { StorageService } from '../../../shared/storage/storage.service';

@Injectable()
export class GetFileInvalidData {
constructor(private storageService: StorageService) {}

async execute(path: string) {
const stringContent = await this.storageService.getFileContent(path, FileEncodingsEnum.JSON);

return JSON.parse(stringContent);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Injectable } from '@nestjs/common';
import { UploadRepository } from '@impler/dal';

@Injectable()
export class GetUploadInvalidData {
constructor(private uploadRepository: UploadRepository) {}

async execute(_uploadId: string) {
return this.uploadRepository.getUploadInvalidDataInformation(_uploadId);
}
}
6 changes: 6 additions & 0 deletions apps/api/src/app/review/usecases/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
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';

export const USE_CASES = [
DoReview,
SaveReviewData,
GetFileInvalidData,
GetUploadInvalidData,
//
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Injectable } from '@nestjs/common';
import { FileRepository, UploadRepository } from '@impler/dal';
import { FileMimeTypesEnum } from '@impler/shared';
import { FileNameService } from '../../../shared/file/name.service';
import { StorageService } from '../../../shared/storage/storage.service';

@Injectable()
export class SaveReviewData {
constructor(
private fileNameService: FileNameService,
private storageService: StorageService,
private fileRepository: FileRepository,
private uploadRepository: UploadRepository
) {}

async execute(_uploadId: string, invalidData: any[], validData: any[]) {
const _invalidDataFileId = await this.storeInvalidFile(_uploadId, invalidData);
const _validDataFileId = await this.storeValidFile(_uploadId, validData);
await this.uploadRepository.update({ _id: _uploadId }, { _invalidDataFileId, _validDataFileId });
}

private async storeValidFile(_uploadId: string, validData: any[]): Promise<string> {
if (validData.length < 1) return null;

const strinValidData = JSON.stringify(validData);
const validFilePath = this.fileNameService.getValidDataFilePath(_uploadId);
await this.storeFile(validFilePath, strinValidData);
const validFileName = this.fileNameService.getValidDataFileName();
const entry = await this.makeFileEntry(validFileName, validFilePath);

return entry._id;
}

private async storeInvalidFile(_uploadId: string, invalidData: any[]): Promise<string> {
if (invalidData.length < 1) return null;

const stringInvalidData = JSON.stringify(invalidData);
const invalidFilePath = this.fileNameService.getInvalidDataFilePath(_uploadId);
await this.storeFile(invalidFilePath, stringInvalidData);
const invalidFileName = this.fileNameService.getInvalidDataFileName();
const entry = await this.makeFileEntry(invalidFileName, invalidFilePath);

return entry._id;
}

private async storeFile(invalidFilePath: string, data: string) {
await this.storageService.uploadFile(invalidFilePath, data, FileMimeTypesEnum.JSON);
}

private async makeFileEntry(fileName: string, filePath: string) {
return await this.fileRepository.create({
mimeType: FileMimeTypesEnum.JSON,
name: fileName,
originalName: fileName,
path: filePath,
});
}
}
4 changes: 4 additions & 0 deletions apps/api/src/app/shared/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ export const APIMessages = {
UPLOAD_NOT_FOUND: 'Upload information not found with specified uploadId.',
FILE_NOT_FOUND_IN_STORAGE:
"File not found, make sure you're using the same storage provider, that you were using before.",
DO_MAPPING_FIRST: 'You may landed to wrong place, Please finalize mapping and proceed ahead.',
DO_REVIEW_FIRST: 'You may landed to wrong place, Please review data and proceed ahead.',
DO_CONFIRM_FIRST: 'You may landed to wrong place, Please confirm data and proceed ahead.',
ALREADY_CONFIRMED: '`You may landed to wrong place, This upload file is confirmed already.',
};
16 changes: 14 additions & 2 deletions apps/api/src/app/shared/file/name.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class FileNameService {
return fileName.split('.').pop();
}
getUploadedFilePath(uploadId: string, fileName: string): string {
return `${uploadId}/uploaded.${this.getFileExtension(fileName)}`;
return `${uploadId}/${this.getUploadedFileName(fileName)}`;
}
getUploadedFileName(fileName: string): string {
return `uploaded.${this.getFileExtension(fileName)}`;
Expand All @@ -22,6 +22,18 @@ export class FileNameService {
return `all-data.json`;
}
getAllJsonDataFilePath(uploadId: string): string {
return `${uploadId}/all-data.json`;
return `${uploadId}/${this.getAllJsonDataFileName()}`;
}
getInvalidDataFileName(): string {
return `invalid-data.json`;
}
getInvalidDataFilePath(uploadId: string): string {
return `${uploadId}/${this.getInvalidDataFileName()}`;
}
getValidDataFileName(): string {
return `valid-data.json`;
}
getValidDataFilePath(uploadId: string): string {
return `${uploadId}/${this.getValidDataFileName()}`;
}
}
6 changes: 3 additions & 3 deletions apps/api/src/app/shared/helpers/file.helper.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { SupportedFileMimeTypesEnum } from '@impler/shared';
import { FileMimeTypesEnum } from '@impler/shared';
import { APIMessages } from '../constants';
import { FileService, CSVFileService, ExcelFileService } from '../file/file.service';

export const getFileService = (mimeType: string): FileService => {
if (mimeType === SupportedFileMimeTypesEnum.CSV) {
if (mimeType === FileMimeTypesEnum.CSV) {
return new CSVFileService();
} else if (mimeType === SupportedFileMimeTypesEnum.EXCEL || mimeType === SupportedFileMimeTypesEnum.EXCELX) {
} else if (mimeType === FileMimeTypesEnum.EXCEL || mimeType === FileMimeTypesEnum.EXCELX) {
return new ExcelFileService();
}

Expand Down
18 changes: 18 additions & 0 deletions apps/api/src/app/shared/helpers/upload.helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { UploadStatusEnum } from '@impler/shared';
import { BadRequestException } from '@nestjs/common';
import { APIMessages } from '../constants';

export function validateUploadStatus(currentStatus: UploadStatusEnum, expectedStatus: UploadStatusEnum[]): boolean {
if (expectedStatus.includes(currentStatus)) return true;
else {
if (currentStatus === UploadStatusEnum.UPLOADED) {
throw new BadRequestException(APIMessages.DO_MAPPING_FIRST);
} else if (currentStatus === UploadStatusEnum.MAPPED) {
throw new BadRequestException(APIMessages.DO_REVIEW_FIRST);
} else if (currentStatus === UploadStatusEnum.REVIEWED) {
throw new BadRequestException(APIMessages.DO_CONFIRM_FIRST);
} else if (currentStatus === UploadStatusEnum.CONFIRMED) {
throw new BadRequestException(APIMessages.ALREADY_CONFIRMED);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
import _whatever from 'multer';
import { SupportedFileMimeTypesEnum } from '@impler/shared';
import { SupportedFileMimeTypes } from '@impler/shared';
import { Injectable, PipeTransform } from '@nestjs/common';
import { FileNotValidError } from '../exceptions/file-not-valid.exception';

@Injectable()
export class ValidImportFile implements PipeTransform<Express.Multer.File> {
transform(value: Express.Multer.File) {
if (!(Object.values(SupportedFileMimeTypesEnum) as string[]).includes(value.mimetype)) {
if (!SupportedFileMimeTypes.includes(value.mimetype)) {
throw new FileNotValidError();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { UploadStatusEnum } from '@impler/shared';
import { FileMimeTypesEnum, UploadStatusEnum } from '@impler/shared';
import { CommonRepository, FileEntity, FileRepository, UploadRepository } from '@impler/dal';
import { MakeUploadEntryCommand } from './make-upload-entry.command';
import { FileNameService } from '../../../shared/file/name.service';
Expand Down Expand Up @@ -79,10 +79,10 @@ export class MakeUploadEntry {
private async addAllDataEntry(uploadId: string, data: Record<string, unknown>[]): Promise<FileEntity> {
const allDataFileName = this.fileNameService.getAllJsonDataFileName();
const allDataFilePath = this.fileNameService.getAllJsonDataFilePath(uploadId);
await this.storageService.uploadFile(allDataFilePath, JSON.stringify(data), 'application/json');
await this.storageService.uploadFile(allDataFilePath, JSON.stringify(data), FileMimeTypesEnum.JSON);

return await this.fileRepository.create({
mimeType: 'application/json',
mimeType: FileMimeTypesEnum.JSON,
path: allDataFilePath,
name: allDataFileName,
originalName: allDataFileName,
Expand Down
3 changes: 3 additions & 0 deletions libs/dal/src/repositories/upload/upload.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ export class UploadRepository extends BaseRepository<UploadEntity> {
async getUploadInformation(uploadId: string): Promise<UploadEntity> {
return await Upload.findById(uploadId).populate('_allDataFileId', 'path name');
}
async getUploadInvalidDataInformation(uploadId: string): Promise<UploadEntity> {
return await Upload.findById(uploadId).populate('_invalidDataFileId', 'path name');
}
}
10 changes: 9 additions & 1 deletion libs/shared/src/types/upload/upload.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,24 @@ export enum UploadStatusEnum {
'COMPLETED' = 'Completed',
}

export enum SupportedFileMimeTypesEnum {
export const SupportedFileMimeTypes = [
'text/csv', // csv
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // EXCELX
'application/vnd.ms-excel', // EXCEL
];

export enum FileMimeTypesEnum {
'CSV' = 'text/csv',
'EXCEL' = 'application/vnd.ms-excel',
'EXCELX' = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'JSON' = 'application/json',
}

export enum FileEncodingsEnum {
'CSV' = 'utf8',
'EXCEL' = 'base64',
'EXCELX' = 'base64',
'JSON' = 'utf8',
}

export interface IFileInformation {
Expand Down