Skip to content

Commit 7971dee

Browse files
feat streaming & custom code validator support (#308)
2 parents dd132de + ec5f707 commit 7971dee

File tree

59 files changed

+8719
-9247
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+8719
-9247
lines changed

apps/api/package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"ajv": "^8.11.0",
3434
"ajv-formats": "^2.1.1",
3535
"ajv-keywords": "^5.1.0",
36-
"axios": "^0.26.1",
36+
"async-mutex": "^0.4.0",
37+
"axios": "1.4.0",
3738
"bcryptjs": "^2.4.3",
3839
"body-parser": "^1.20.0",
3940
"class-transformer": "^0.5.1",
@@ -47,6 +48,7 @@
4748
"hat": "^0.0.3",
4849
"jsonwebtoken": "^9.0.0",
4950
"nodemailer": "^6.9.3",
51+
"papaparse": "^5.4.1",
5052
"passport": "^0.6.0",
5153
"passport-github2": "^0.1.12",
5254
"passport-jwt": "^4.0.1",
@@ -69,6 +71,7 @@
6971
"@types/multer": "^1.4.7",
7072
"@types/node": "^18.7.18",
7173
"@types/nodemailer": "^6.4.8",
74+
"@types/papaparse": "^5.3.7",
7275
"@types/passport": "^1.0.11",
7376
"@types/passport-github2": "^1.2.5",
7477
"@types/passport-jwt": "^3.0.8",
@@ -86,5 +89,8 @@
8689
"*.{js,jsx,ts,tsx}": [
8790
"eslint --fix"
8891
]
89-
}
92+
},
93+
"main": ".eslintrc.js",
94+
"keywords": [],
95+
"description": ""
9096
}

apps/api/src/app/column/dtos/column-request.dto.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
22
import {
3-
ArrayMinSize,
43
IsArray,
54
IsBoolean,
65
IsOptional,
@@ -12,7 +11,7 @@ import {
1211
Validate,
1312
} from 'class-validator';
1413
import { Type } from 'class-transformer';
15-
import { ColumnTypesEnum, Defaults } from '@impler/shared';
14+
import { ColumnTypesEnum } from '@impler/shared';
1615
import { IsValidRegex } from '@shared/framework/is-valid-regex.validator';
1716

1817
export class ColumnRequestDto {
@@ -81,10 +80,8 @@ export class ColumnRequestDto {
8180
description: 'List of possible values for column if type is Select',
8281
})
8382
@ValidateIf((object) => object.type === ColumnTypesEnum.SELECT)
84-
@IsArray()
85-
@ArrayMinSize(Defaults.ONE)
8683
@Type(() => Array<string>)
87-
selectValues: string[];
84+
selectValues: string[] = [];
8885

8986
@ApiProperty({
9087
description: 'Sequence of column',

apps/api/src/app/mapping/mapping.controller.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@ import { ACCESS_KEY_NAME, Defaults, UploadStatusEnum } from '@impler/shared';
44
import { MappingEntity } from '@impler/dal';
55

66
import { JwtAuthGuard } from '@shared/framework/auth.gaurd';
7+
import { validateNotFound } from '@shared/helpers/common.helper';
8+
import { validateUploadStatus } from '@shared/helpers/upload.helpers';
9+
import { GetUpload } from '@shared/usecases/get-upload/get-upload.usecase';
710
import { ValidateMongoId } from '@shared/validations/valid-mongo-id.validation';
811
import { GetUploadCommand } from '@shared/usecases/get-upload/get-upload.command';
12+
13+
import { UpdateMappingDto } from './dtos/update-columns.dto';
914
import { DoMapping } from './usecases/do-mapping/do-mapping.usecase';
10-
import { DoMappingCommand } from './usecases/do-mapping/do-mapping.command';
11-
import { GetUpload } from '@shared/usecases/get-upload/get-upload.usecase';
1215
import { GetMappings } from './usecases/get-mappings/get-mappings.usecase';
13-
import { UpdateMappingCommand } from './usecases/update-mappings/update-mappings.command';
16+
import { DoMappingCommand } from './usecases/do-mapping/do-mapping.command';
1417
import { UpdateMappings } from './usecases/update-mappings/update-mappings.usecase';
1518
import { FinalizeUpload } from './usecases/finalize-upload/finalize-upload.usecase';
16-
import { UpdateMappingDto } from './dtos/update-columns.dto';
1719
import { ValidateMapping } from './usecases/validate-mapping/validate-mapping.usecase';
18-
import { validateUploadStatus } from '@shared/helpers/upload.helpers';
19-
import { validateNotFound } from '@shared/helpers/common.helper';
20+
import { UpdateMappingCommand } from './usecases/update-mappings/update-mappings.command';
21+
import { ReanameFileHeadings } from './usecases/rename-file-headings/rename-file-headings.usecase';
2022

2123
@Controller('/mapping')
2224
@ApiTags('Mappings')
@@ -29,7 +31,8 @@ export class MappingController {
2931
private getMappings: GetMappings,
3032
private updateMappings: UpdateMappings,
3133
private finalizeUpload: FinalizeUpload,
32-
private validateMapping: ValidateMapping
34+
private validateMapping: ValidateMapping,
35+
private renameFileHeadings: ReanameFileHeadings
3336
) {}
3437

3538
@Get(':uploadId')
@@ -93,7 +96,7 @@ export class MappingController {
9396

9497
// save mapping
9598
if (Array.isArray(body) && body.length > Defaults.ZERO) {
96-
this.updateMappings.execute(
99+
await this.updateMappings.execute(
97100
body
98101
.filter((columnDataItem) => !!columnDataItem.columnHeading)
99102
.map((updateColumnData) =>
@@ -107,7 +110,9 @@ export class MappingController {
107110
);
108111
}
109112

113+
const { headings } = await this.renameFileHeadings.execute(_uploadId);
114+
110115
// update mapping status
111-
return this.finalizeUpload.execute(_uploadId);
116+
return this.finalizeUpload.execute(_uploadId, headings);
112117
}
113118
}

apps/api/src/app/mapping/usecases/finalize-upload/finalize-upload.usecase.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import { UploadStatusEnum } from '@impler/shared';
66
export class FinalizeUpload {
77
constructor(private uploadRepository: UploadRepository) {}
88

9-
async execute(_uploadId: string) {
10-
return await this.uploadRepository.findOneAndUpdate({ _id: _uploadId }, { status: UploadStatusEnum.MAPPED });
9+
async execute(_uploadId: string, headings: string[]) {
10+
return await this.uploadRepository.findOneAndUpdate(
11+
{ _id: _uploadId },
12+
{ status: UploadStatusEnum.MAPPED, headings }
13+
);
1114
}
1215
}

apps/api/src/app/mapping/usecases/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { GetMappings } from './get-mappings/get-mappings.usecase';
33
import { UpdateMappings } from './update-mappings/update-mappings.usecase';
44
import { FinalizeUpload } from './finalize-upload/finalize-upload.usecase';
55
import { ValidateMapping } from './validate-mapping/validate-mapping.usecase';
6+
import { ReanameFileHeadings } from './rename-file-headings/rename-file-headings.usecase';
67
import { GetUpload } from '@shared/usecases/get-upload/get-upload.usecase';
78

89
export const USE_CASES = [
@@ -11,6 +12,7 @@ export const USE_CASES = [
1112
UpdateMappings,
1213
FinalizeUpload,
1314
ValidateMapping,
15+
ReanameFileHeadings,
1416
GetUpload,
1517
//
1618
];
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { MappingRepository, UploadRepository } from '@impler/dal';
3+
4+
@Injectable()
5+
export class ReanameFileHeadings {
6+
constructor(private uploadRepository: UploadRepository, private mappingRepository: MappingRepository) {}
7+
8+
async execute(_uploadId: string): Promise<{ headings: string[] }> {
9+
return new Promise(async (resolve, reject) => {
10+
try {
11+
const uploadInfo = await this.uploadRepository.findById(_uploadId, 'headings _uploadedFileId');
12+
const mappingInfo = await this.mappingRepository.getMappingWithColumnInfo(_uploadId);
13+
14+
const newHeadings = uploadInfo.headings.reduce((headingsArr, heading) => {
15+
const foundMapping = mappingInfo.find((mapping) => mapping.columnHeading === heading);
16+
if (foundMapping) headingsArr.push(foundMapping.column.key);
17+
else headingsArr.push(heading);
18+
19+
return headingsArr;
20+
}, []);
21+
22+
return resolve({ headings: newHeadings });
23+
} catch (error) {
24+
reject(error);
25+
}
26+
});
27+
}
28+
}

apps/api/src/app/review/review.controller.ts

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import { Response } from 'express';
12
import { ApiOperation, ApiTags, ApiSecurity, ApiQuery, ApiOkResponse } from '@nestjs/swagger';
2-
import { BadRequestException, Body, Controller, Get, Param, Post, Query, UseGuards } from '@nestjs/common';
3+
import { BadRequestException, Body, Controller, Get, Param, Post, Query, Res, UseGuards } from '@nestjs/common';
34

45
import { APIMessages } from '@shared/constants';
56
import { FileEntity, UploadEntity } from '@impler/dal';
@@ -11,10 +12,8 @@ import {
1112
DoReview,
1213
GetUpload,
1314
StartProcess,
14-
SaveReviewData,
1515
UpdateImportCount,
1616
GetFileInvalidData,
17-
ReanameFileHeadings,
1817
StartProcessCommand,
1918
GetUploadInvalidData,
2019
UpdateImportCountCommand,
@@ -29,14 +28,11 @@ import { PaginationResponseDto } from '@shared/dtos/pagination-response.dto';
2928
@Controller('/review')
3029
@ApiTags('Review')
3130
@ApiSecurity(ACCESS_KEY_NAME)
32-
@UseGuards(JwtAuthGuard)
3331
export class ReviewController {
3432
constructor(
3533
private doReview: DoReview,
3634
private getUpload: GetUpload,
3735
private startProcess: StartProcess,
38-
private renameFileHeadings: ReanameFileHeadings,
39-
private saveReviewData: SaveReviewData,
4036
private updateImportCount: UpdateImportCount,
4137
private getFileInvalidData: GetFileInvalidData,
4238
private getUploadInvalidData: GetUploadInvalidData
@@ -63,36 +59,39 @@ export class ReviewController {
6359
type: PaginationResponseDto,
6460
})
6561
async getReview(
62+
@Res() res: Response,
6663
@Param('uploadId') _uploadId: string,
6764
@Query('page') page = Defaults.ONE,
6865
@Query('limit') limit = Defaults.PAGE_LIMIT
69-
): Promise<PaginationResponseDto> {
66+
) {
7067
const uploadData = await this.getUploadInvalidData.execute(_uploadId);
7168
if (!uploadData) throw new BadRequestException(APIMessages.UPLOAD_NOT_FOUND);
7269

73-
// Only Mapped & Reviewing status are allowed
74-
validateUploadStatus(uploadData.status as UploadStatusEnum, [UploadStatusEnum.MAPPED, UploadStatusEnum.REVIEWING]);
70+
res.setHeader('Content-Type', 'text/event-stream');
71+
res.setHeader('Access-Control-Allow-Origin', '*');
72+
res.setHeader('Access-Control-Allow-Credentials', 'true');
73+
res.setHeader('Connection', 'keep-alive');
74+
res.setHeader('Cache-Control', 'no-cache');
7575

76-
// Get Invalid Data either from Validation or Validation Result
77-
let invalidData = [];
76+
let invalidDataFilePath: string;
7877
if (uploadData.status === UploadStatusEnum.MAPPED) {
79-
// uploaded file is mapped, do review
80-
const reviewData = await this.doReview.execute(_uploadId);
81-
// save invalid data to storage
82-
await this.saveReviewData.execute(_uploadId, reviewData.invalid, reviewData.valid);
83-
84-
invalidData = reviewData.invalid;
78+
invalidDataFilePath = await this.doReview.execute(_uploadId);
8579
} else {
86-
// Uploaded file is already reviewed, return reviewed data
87-
invalidData = await this.getFileInvalidData.execute(
88-
(uploadData._invalidDataFileId as unknown as FileEntity).path
89-
);
80+
invalidDataFilePath = (uploadData._invalidDataFileId as unknown as FileEntity).path;
9081
}
9182

92-
return paginateRecords(invalidData, page, limit);
83+
// Uploaded file is already reviewed, return reviewed data
84+
const invalidData = await this.getFileInvalidData.execute(invalidDataFilePath);
85+
const { data, ...rest } = paginateRecords(invalidData, page, limit);
86+
for (const item of data) {
87+
res.write(`data: ${JSON.stringify(item)}\n\n`);
88+
}
89+
res.write(`data: ${JSON.stringify(rest)}\n\n`);
90+
res.end();
9391
}
9492

9593
@Post(':uploadId/confirm')
94+
@UseGuards(JwtAuthGuard)
9695
@ApiOperation({
9796
summary: 'Confirm review data for uploaded file',
9897
})
@@ -113,13 +112,6 @@ export class ReviewController {
113112
// upload files with status reviewing can only be confirmed
114113
validateUploadStatus(uploadInformation.status as UploadStatusEnum, [UploadStatusEnum.REVIEWING]);
115114

116-
// rename file headings
117-
await this.renameFileHeadings.execute(
118-
_uploadId,
119-
uploadInformation._validDataFileId,
120-
uploadInformation._invalidDataFileId
121-
);
122-
123115
await this.updateImportCount.execute(
124116
uploadInformation._templateId,
125117
UpdateImportCountCommand.create({

apps/api/src/app/review/review.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import { USE_CASES } from './usecases';
33
import { ReviewController } from './review.controller';
44
import { SharedModule } from '@shared/shared.module';
55
import { AJVService } from './service/AJV.service';
6+
import { Sandbox, SManager } from './service/Sandbox';
67
import { QueueService } from '@shared/services/queue.service';
78

89
@Module({
910
imports: [SharedModule],
10-
providers: [...USE_CASES, AJVService, QueueService],
11+
providers: [...USE_CASES, AJVService, QueueService, SManager, Sandbox],
1112
controllers: [ReviewController],
1213
})
1314
export class ReviewModule {}

0 commit comments

Comments
 (0)