Skip to content

Commit 0931b7d

Browse files
v1.3.0 (#1039)
2 parents 70509fe + b9563d2 commit 0931b7d

File tree

40 files changed

+656
-129
lines changed

40 files changed

+656
-129
lines changed

apps/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@impler/api",
3-
"version": "1.2.0",
3+
"version": "1.3.0",
44
"author": "implerhq",
55
"license": "MIT",
66
"private": true,

apps/api/src/app/import-jobs/usecase/create-userjob/create-userjob.usecase.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export class CreateUserJob {
2525

2626
try {
2727
const mimeType = await getMimeType(url);
28-
console.log('mime type is >>', mimeType);
2928

3029
if (isValidXMLMimeType(mimeType)) {
3130
const abortController = new AbortController();

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export class ReviewController {
8686
const uploadData = await this.getUpload.execute({
8787
uploadId: _uploadId,
8888
});
89+
8990
if (!uploadData) throw new BadRequestException(APIMessages.UPLOAD_NOT_FOUND);
9091

9192
return await this.getFileInvalidData.execute(
@@ -123,7 +124,7 @@ export class ReviewController {
123124
@ApiOperation({
124125
summary: 'Confirm review data for uploaded file',
125126
})
126-
async doConfirmReview(@Param('uploadId', ValidateMongoId) _uploadId: string) {
127+
async doConfirmReview(@Param('uploadId', ValidateMongoId) _uploadId: string, @Body() body: { maxRecords?: number }) {
127128
const uploadInformation = await this.getUpload.execute({
128129
uploadId: _uploadId,
129130
select: 'status _validDataFileId _invalidDataFileId totalRecords invalidRecords _templateId',
@@ -135,7 +136,7 @@ export class ReviewController {
135136
// upload files with status reviewing can only be confirmed
136137
validateUploadStatus(uploadInformation.status as UploadStatusEnum, [UploadStatusEnum.REVIEWING]);
137138

138-
return this.startProcess.execute(_uploadId);
139+
return this.startProcess.execute(_uploadId, body.maxRecords);
139140
}
140141

141142
@Put(':uploadId/record')
@@ -168,7 +169,7 @@ export class ReviewController {
168169
@Body() { indexes, valid, invalid }: DeleteRecordsDto,
169170
@Param('uploadId', ValidateMongoId) _uploadId: string
170171
) {
171-
await this.deleteRecord.execute(_uploadId, indexes, valid, invalid);
172+
return await this.deleteRecord.execute(_uploadId, indexes, valid, invalid);
172173
}
173174

174175
@Put(':uploadId/replace')

apps/api/src/app/review/usecases/delete-record/delete-record.usecase.ts

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,61 @@ export class DeleteRecord {
88
private uploadRepository: UploadRepository
99
) {}
1010

11-
async execute(_uploadId: string, indexes: number[], valid: number, invalid: number) {
12-
await this.dalService.deleteRecords(_uploadId, indexes);
13-
if (typeof valid !== 'undefined' && typeof invalid !== 'undefined') {
14-
await this.uploadRepository.update(
15-
{
16-
_id: _uploadId,
17-
},
18-
{
19-
$inc: {
20-
totalRecords: -indexes.length,
21-
validRecords: -valid,
22-
invalidRecords: -invalid,
11+
async execute(
12+
uploadId: string,
13+
recordIndexesToDelete: number[],
14+
validRecordsToDelete: number,
15+
invalidRecordsToDelete: number
16+
) {
17+
await this.dalService.deleteRecords(uploadId, recordIndexesToDelete);
18+
19+
if (typeof validRecordsToDelete !== 'undefined' && typeof invalidRecordsToDelete !== 'undefined') {
20+
// Get current upload statistics to prevent negative values
21+
const currentUploadData = await this.uploadRepository.findOne({ _id: uploadId });
22+
23+
if (currentUploadData) {
24+
// Calculate safe decrement values to prevent database corruption
25+
const safeValidRecordsDecrement = Math.min(validRecordsToDelete, currentUploadData.validRecords || 0);
26+
const safeInvalidRecordsDecrement = Math.min(invalidRecordsToDelete, currentUploadData.invalidRecords || 0);
27+
const safeTotalRecordsDecrement = Math.min(recordIndexesToDelete.length, currentUploadData.totalRecords || 0);
28+
29+
await this.uploadRepository.update(
30+
{
31+
_id: uploadId,
2332
},
33+
{
34+
$inc: {
35+
totalRecords: -safeTotalRecordsDecrement,
36+
validRecords: -safeValidRecordsDecrement,
37+
invalidRecords: -safeInvalidRecordsDecrement,
38+
},
39+
}
40+
);
41+
42+
// Double-check and ensure no negative values exist in database
43+
const updatedUploadData = await this.uploadRepository.findOne({ _id: uploadId });
44+
if (updatedUploadData) {
45+
const fieldsToCorrect: any = {};
46+
let requiresCorrection = false;
47+
48+
if ((updatedUploadData.totalRecords || 0) < 0) {
49+
fieldsToCorrect.totalRecords = 0;
50+
requiresCorrection = true;
51+
}
52+
if ((updatedUploadData.validRecords || 0) < 0) {
53+
fieldsToCorrect.validRecords = 0;
54+
requiresCorrection = true;
55+
}
56+
if ((updatedUploadData.invalidRecords || 0) < 0) {
57+
fieldsToCorrect.invalidRecords = 0;
58+
requiresCorrection = true;
59+
}
60+
61+
if (requiresCorrection) {
62+
await this.uploadRepository.update({ _id: uploadId }, { $set: fieldsToCorrect });
63+
}
2464
}
25-
);
65+
}
2666
}
2767
}
2868
}

apps/api/src/app/review/usecases/get-upload-data/get-upload-data.usecase.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export class GetUploadData {
1616
const data = await this.dalService.getRecords(_uploadId, page, limit, type);
1717

1818
return {
19-
data,
19+
data: data.sort((a, b) => a.index - b.index),
2020
limit,
2121
page,
2222
totalRecords,

apps/api/src/app/review/usecases/start-process/start-process.usecase.ts

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { PaymentAPIService } from '@impler/services';
1212
import { QueueService } from '@shared/services/queue.service';
1313
import { AmplitudeService } from '@shared/services/amplitude.service';
1414
import { DalService, TemplateEntity, TemplateRepository, UploadEntity, UploadRepository } from '@impler/dal';
15+
import { MaxRecordsExceededException } from '@shared/exceptions/max-records.exception';
1516

1617
@Injectable()
1718
export class StartProcess {
@@ -24,7 +25,7 @@ export class StartProcess {
2425
private templateRepository: TemplateRepository
2526
) {}
2627

27-
async execute(_uploadId: string) {
28+
async execute(_uploadId: string, maxRecords?: number) {
2829
let uploadInfo = await this.uploadRepository.getUploadWithTemplate(_uploadId, ['destination']);
2930
let importedData;
3031
const destination = (uploadInfo._templateId as unknown as TemplateEntity)?.destination;
@@ -44,7 +45,7 @@ export class StartProcess {
4445
});
4546
}
4647

47-
await this.updateTemplateStatistics({ uploadInfo, userEmail });
48+
await this.updateTemplateStatistics({ uploadInfo, userEmail, maxRecords });
4849

4950
// if destination is frontend or not defined then complete the upload process
5051
if (
@@ -117,32 +118,59 @@ export class StartProcess {
117118
return importedData;
118119
}
119120

120-
private async updateTemplateStatistics({ uploadInfo, userEmail }: { uploadInfo: UploadEntity; userEmail: string }) {
121-
//if its a file based import do-review will handle the further process
121+
private async updateTemplateStatistics({
122+
uploadInfo,
123+
userEmail,
124+
maxRecords,
125+
}: {
126+
uploadInfo: UploadEntity;
127+
userEmail: string;
128+
maxRecords?: number;
129+
}) {
130+
// If it's a file based import do-review will handle the further process
122131
if (uploadInfo._uploadedFileId || uploadInfo.originalFileName) {
123132
return;
124133
}
125-
await this.templateRepository.findOneAndUpdate(
126-
{
127-
_id: uploadInfo._templateId,
128-
},
129-
{
130-
$inc: {
131-
totalUploads: uploadInfo.totalRecords,
132-
totalRecords: uploadInfo.totalRecords,
133-
totalInvalidRecords: uploadInfo.invalidRecords,
134+
135+
// Check max records limit BEFORE updating statistics
136+
if (maxRecords && uploadInfo.validRecords > maxRecords) {
137+
throw new MaxRecordsExceededException({
138+
maxAllowed: maxRecords,
139+
});
140+
}
141+
142+
// Validate that we're not updating with negative values
143+
const recordsToAdd = Math.max(0, uploadInfo.totalRecords || 0);
144+
const invalidRecordsToAdd = Math.max(0, uploadInfo.invalidRecords || 0);
145+
const validRecordsToAdd = Math.max(0, uploadInfo.validRecords || 0);
146+
147+
// Only update if we have positive values to add
148+
if (validRecordsToAdd > 0) {
149+
await this.templateRepository.findOneAndUpdate(
150+
{
151+
_id: uploadInfo._templateId,
134152
},
135-
}
136-
);
137-
138-
await this.paymentAPIService.createEvent(
139-
{
140-
uploadId: uploadInfo._id,
141-
totalRecords: uploadInfo.totalRecords,
142-
validRecords: uploadInfo.validRecords,
143-
invalidRecords: uploadInfo.invalidRecords,
144-
},
145-
userEmail
146-
);
153+
{
154+
$inc: {
155+
totalUploads: uploadInfo.totalRecords,
156+
totalRecords: uploadInfo.totalRecords,
157+
totalInvalidRecords: Math.max(0, uploadInfo.invalidRecords),
158+
},
159+
}
160+
);
161+
}
162+
163+
// Only create payment event if we have valid records
164+
if (validRecordsToAdd > 0 || recordsToAdd > 0) {
165+
await this.paymentAPIService.createEvent(
166+
{
167+
uploadId: uploadInfo._id,
168+
totalRecords: recordsToAdd,
169+
validRecords: validRecordsToAdd,
170+
invalidRecords: invalidRecordsToAdd,
171+
},
172+
userEmail
173+
);
174+
}
147175
}
148176
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { BadRequestException } from '@nestjs/common';
2+
import { numberFormatter } from '@impler/shared';
3+
4+
export class MaxRecordsExceededException extends BadRequestException {
5+
constructor({ maxAllowed, customMessage = null }: { maxAllowed: number; customMessage?: string }) {
6+
super(customMessage || `You can nor import records more than ${numberFormatter(maxAllowed)} records.`);
7+
}
8+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export class GetUploadCommand {
22
uploadId: string;
3-
3+
maxRecords?: number;
44
select?: string;
55
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ApiProperty } from '@nestjs/swagger';
2-
import { IsJSON, IsMongoId, IsOptional, IsString } from 'class-validator';
2+
import { IsJSON, IsMongoId, IsNumberString, IsOptional, IsString } from 'class-validator';
33

44
export class UploadRequestDto {
55
@ApiProperty({
@@ -61,4 +61,12 @@ export class UploadRequestDto {
6161
@IsOptional()
6262
@IsJSON()
6363
imageSchema?: string;
64+
65+
@ApiProperty({
66+
description: 'Max Number of records to import',
67+
required: false,
68+
})
69+
@IsOptional()
70+
@IsNumberString()
71+
maxRecords?: string;
6472
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,14 @@ export class UploadController {
7878
@UploadedFile('file', ValidImportFile) file: Express.Multer.File
7979
) {
8080
return this.makeUploadEntry.execute({
81-
file: file,
81+
file,
8282
templateId,
8383
extra: body.extra,
8484
schema: body.schema,
8585
output: body.output,
8686
importId: body.importId,
8787
imageSchema: body.imageSchema,
88-
88+
maxRecords: parseInt(body.maxRecords),
8989
authHeaderValue: body.authHeaderValue,
9090
selectedSheetName: body.selectedSheetName,
9191
});

0 commit comments

Comments
 (0)