Skip to content

Commit 06631f2

Browse files
Feat/subos itegration and new pricing (#1117)
2 parents 4f9a66e + 22af81f commit 06631f2

File tree

82 files changed

+7152
-8408
lines changed

Some content is hidden

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

82 files changed

+7152
-8408
lines changed

.env.example

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# SubOS Configuration
2+
NEXT_PUBLIC_SUBOS_API_ENDPOINT=
3+
NEXT_PUBLIC_SUBOS_PROJECT_ID=
4+
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
5+
6+
# App Configuration
7+
NEXT_PUBLIC_APP_NAME=Impler.io
8+
NEXT_PUBLIC_APP_VERSION=1.0.0
9+
10+
# Environment is automatically set by NODE_ENV
11+
# NODE_ENV=development

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { ValidationTypesEnum } from '@impler/client';
1717
import { IsValidRegex } from '@shared/framework/is-valid-regex.validator';
1818
import { IsValidDigitsConstraint } from '@shared/framework/is-valid-digits.validator';
1919
import { IsNumberOrString } from '@shared/framework/number-or-string.validator';
20-
import { ColumnDelimiterEnum, ColumnTypesEnum, Defaults } from '@impler/shared';
20+
import { ColumnDelimiterEnum, ColumnTypesEnum } from '@impler/shared';
2121

2222
export class ValidationDto {
2323
@ApiProperty({
@@ -154,7 +154,7 @@ export class ColumnRequestDto {
154154
})
155155
@ValidateIf((object) => object.type === ColumnTypesEnum.DATE)
156156
@Type(() => Array<string>)
157-
dateFormats: string[] = Defaults.DATE_FORMATS;
157+
dateFormats: string[];
158158

159159
@ApiProperty({
160160
description: 'Sequence of column',

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import {
1212
UploadedFile,
1313
} from '@nestjs/common';
1414

15-
import { ACCESS_KEY_NAME } from '@impler/shared';
15+
import { ACCESS_KEY_NAME, IImportConfig } from '@impler/shared';
1616
import { JwtAuthGuard } from '@shared/framework/auth.gaurd';
17-
import { ValidRequestDto, SignedUrlDto, ImportConfigResponseDto } from './dtos';
17+
import { ValidRequestDto, SignedUrlDto } from './dtos';
1818
import { ValidImportFile } from '@shared/validations/valid-import-file.validation';
1919
import { ValidRequestCommand, GetSignedUrl, ValidRequest, GetImportConfig, GetSheetNames } from './usecases';
2020

@@ -60,7 +60,7 @@ export class CommonController {
6060
async getImportConfigRoute(
6161
@Query('projectId') projectId: string,
6262
@Query('templateId') templateId: string
63-
): Promise<ImportConfigResponseDto> {
63+
): Promise<IImportConfig> {
6464
if (!projectId) {
6565
throw new BadRequestException();
6666
}

apps/api/src/app/common/dtos/Schema.dto.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
ValidateNested,
1414
} from 'class-validator';
1515
import { Type } from 'class-transformer';
16-
import { ColumnTypesEnum, Defaults } from '@impler/shared';
16+
import { ColumnTypesEnum } from '@impler/shared';
1717
import { IsValidRegex } from '@shared/framework/is-valid-regex.validator';
1818
import { ValidationDto } from 'app/column/dtos/column-request.dto';
1919

@@ -101,7 +101,7 @@ export class SchemaDto {
101101
@Type(() => Array<string>)
102102
@IsArray({ message: "'dateFormats' must be an array, when type is Date" })
103103
@ArrayMinSize(1, { message: "'dateFormats' must not be empty, when type is Date" })
104-
dateFormats: string[] = Defaults.DATE_FORMATS;
104+
dateFormats: string[];
105105

106106
@ApiProperty({
107107
description: 'Sequence of column',

apps/api/src/app/common/dtos/import-config-response.dto.ts

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export { SignedUrlDto } from './signed-url.dto';
22
export { ValidRequestDto } from './valid.dto';
33
export { SheetNamesDto } from './sheet-names.dto';
4-
export { ImportConfigResponseDto } from './import-config-response.dto';

apps/api/src/app/common/usecases/get-import-config/get-import-config.usecase.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { BadRequestException, Injectable } from '@nestjs/common';
22
import { UserRepository, TemplateRepository, TemplateEntity } from '@impler/dal';
3-
import { AVAILABLE_BILLABLEMETRIC_CODE_ENUM, IImportConfig } from '@impler/shared';
3+
import { BILLABLEMETRIC_CODE_ENUM, IImportConfig } from '@impler/shared';
44
import { PaymentAPIService } from '@impler/services';
55
import { APIMessages } from '@shared/constants';
66

@@ -14,12 +14,22 @@ export class GetImportConfig {
1414

1515
async execute(projectId: string, templateId?: string): Promise<IImportConfig> {
1616
const userEmail = await this.userRepository.findUserEmailFromProjectId(projectId);
17+
const isFeatureAvailableMap = new Map<string, boolean>();
1718

18-
const removeBrandingAvailable = await this.paymentAPIService.checkEvent({
19-
email: userEmail,
20-
billableMetricCode: AVAILABLE_BILLABLEMETRIC_CODE_ENUM.REMOVE_BRANDING,
19+
Object.values(BILLABLEMETRIC_CODE_ENUM).forEach((code) => {
20+
isFeatureAvailableMap.set(code, false);
2121
});
2222

23+
try {
24+
for (const billableMetricCode of Object.keys(BILLABLEMETRIC_CODE_ENUM)) {
25+
const isAvailable = await this.paymentAPIService.checkEvent({
26+
email: userEmail,
27+
billableMetricCode: BILLABLEMETRIC_CODE_ENUM[billableMetricCode],
28+
});
29+
isFeatureAvailableMap.set(billableMetricCode, isAvailable);
30+
}
31+
} catch (error) {}
32+
2333
let template: TemplateEntity;
2434
if (templateId) {
2535
template = await this.templateRepository.findOne({
@@ -32,6 +42,11 @@ export class GetImportConfig {
3242
}
3343
}
3444

35-
return { showBranding: !removeBrandingAvailable, mode: template?.mode, title: template?.name };
45+
return {
46+
...Object.fromEntries(isFeatureAvailableMap),
47+
showBranding: !isFeatureAvailableMap.get(BILLABLEMETRIC_CODE_ENUM.REMOVE_BRANDING),
48+
mode: template?.mode,
49+
title: template?.name,
50+
};
3651
}
3752
}

apps/api/src/app/common/usecases/valid-request/valid-request.usecase.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { PaymentAPIService } from '@impler/services';
88
import { ValidRequestCommand } from './valid-request.command';
99
import { ProjectRepository, TemplateRepository, UserEntity } from '@impler/dal';
1010
import { UniqueColumnException } from '@shared/exceptions/unique-column.exception';
11-
import { AVAILABLE_BILLABLEMETRIC_CODE_ENUM, ColumnTypesEnum } from '@impler/shared';
11+
import { BILLABLEMETRIC_CODE_ENUM, ColumnTypesEnum } from '@impler/shared';
1212
import { DocumentNotFoundException } from '@shared/exceptions/document-not-found.exception';
1313

1414
@Injectable()
@@ -98,7 +98,7 @@ export class ValidRequest {
9898
if (hasImageColumns && email) {
9999
const imageImportAvailable = await this.paymentAPIService.checkEvent({
100100
email,
101-
billableMetricCode: AVAILABLE_BILLABLEMETRIC_CODE_ENUM.IMAGE_IMPORT,
101+
billableMetricCode: BILLABLEMETRIC_CODE_ENUM.IMAGE_IMPORT,
102102
});
103103

104104
if (!imageImportAvailable) {
@@ -108,7 +108,7 @@ export class ValidRequest {
108108
if (hasValidations && email) {
109109
const validationsAvailable = await this.paymentAPIService.checkEvent({
110110
email,
111-
billableMetricCode: AVAILABLE_BILLABLEMETRIC_CODE_ENUM.ADVANCED_VALIDATORS,
111+
billableMetricCode: BILLABLEMETRIC_CODE_ENUM.ADVANCED_VALIDATORS,
112112
});
113113

114114
if (!validationsAvailable) {

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ReanameFileHeadings } from './rename-file-headings/rename-file-headings
88

99
import { DoMappingCommand } from './do-mapping/do-mapping.command';
1010
import { ValidateMappingCommand } from './validate-mapping/validate-mapping.command';
11+
import { PaymentAPIService } from '@impler/services';
1112

1213
export const USE_CASES = [
1314
DoMapping,
@@ -17,9 +18,19 @@ export const USE_CASES = [
1718
ValidateMapping,
1819
ReanameFileHeadings,
1920
GetUpload,
21+
PaymentAPIService,
2022
//
2123
];
2224

23-
export { DoMapping, ValidateMapping, GetMappings, UpdateMappings, FinalizeUpload, ReanameFileHeadings, GetUpload };
25+
export {
26+
DoMapping,
27+
ValidateMapping,
28+
GetMappings,
29+
UpdateMappings,
30+
FinalizeUpload,
31+
ReanameFileHeadings,
32+
GetUpload,
33+
PaymentAPIService,
34+
};
2435

2536
export { DoMappingCommand, ValidateMappingCommand };

apps/api/src/app/review/usecases/do-review/do-review.usecase.ts

Lines changed: 87 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
/* eslint-disable multiline-comment-style */
12
import { Model } from 'mongoose';
23
import { Writable } from 'stream';
34
import { Injectable, BadRequestException, InternalServerErrorException } from '@nestjs/common';
45

56
import { APIMessages } from '@shared/constants';
6-
import { EMAIL_SUBJECT } from '@impler/shared';
7+
import { BILLABLEMETRIC_CODE_ENUM, EMAIL_SUBJECT } from '@impler/shared';
78
import { BaseReview } from './base-review.usecase';
89
import { UniqueWithValidationType, ValidationTypesEnum } from '@impler/client';
910
import { BATCH_LIMIT } from '@shared/services/sandbox';
@@ -16,7 +17,9 @@ import {
1617
DalService,
1718
TemplateEntity,
1819
TemplateRepository,
20+
EnvironmentRepository,
1921
} from '@impler/dal';
22+
import { UsageLimitExceededException } from '@shared/exceptions/import-limit-exceeded.exception';
2023

2124
interface ISaveResults {
2225
uploadId: string;
@@ -32,6 +35,7 @@ export class DoReview extends BaseReview {
3235

3336
constructor(
3437
private templateRepository: TemplateRepository,
38+
private environmentRepository: EnvironmentRepository,
3539
private storageService: StorageService,
3640
private uploadRepository: UploadRepository,
3741
private validatorRepository: ValidatorRepository,
@@ -44,6 +48,7 @@ export class DoReview extends BaseReview {
4448
}
4549

4650
async execute(_uploadId: string) {
51+
console.log('Called The Do Review.execute');
4752
this._modal = this.dalService.getRecordCollection(_uploadId);
4853
const userEmail = await this.uploadRepository.getUserEmailFromUploadId(_uploadId);
4954

@@ -205,7 +210,28 @@ export class DoReview extends BaseReview {
205210
throw new InternalServerErrorException(APIMessages.ERROR_DURING_VALIDATION);
206211
}
207212

208-
await this.saveResults(response);
213+
try {
214+
await this.saveResults(response);
215+
} catch (error) {
216+
const emailContents = this.emailService.getEmailContent({
217+
type: 'IMPORT_LIMIT_EXCEEDED_EMAIL',
218+
data: {
219+
limitType: 'Import Rows',
220+
currentUsage: 'All available units including grace percentage',
221+
planName: 'Current Plan',
222+
upgradeUrl: `${process.env.WEB_BASE_URL}/pricing`,
223+
},
224+
});
225+
await this.emailService.sendEmail({
226+
from: process.env.EMAIL_FROM,
227+
html: emailContents,
228+
subject: 'Usage limit exceeded',
229+
to: userEmail,
230+
senderName: process.env.EMAIL_FROM_NAME,
231+
});
232+
233+
throw new UsageLimitExceededException(error.message);
234+
}
209235

210236
return response;
211237
}
@@ -235,32 +261,66 @@ export class DoReview extends BaseReview {
235261
}
236262

237263
private async saveResults({ uploadId, totalRecords, validRecords, invalidRecords, _templateId }: ISaveResults) {
238-
await this.uploadRepository.update(
239-
{ _id: uploadId },
240-
{
241-
status: UploadStatusEnum.REVIEWING,
242-
totalRecords,
243-
validRecords,
244-
invalidRecords,
245-
}
246-
);
247-
await this.templateRepository.findOneAndUpdate(
248-
{
249-
_id: _templateId,
250-
},
251-
{
252-
$inc: {
253-
totalUploads: 1,
254-
totalRecords: totalRecords,
255-
totalInvalidRecords: invalidRecords,
256-
},
257-
}
258-
);
259264
const userExternalIdOrEmail = await this.uploadRepository.getUserEmailFromUploadId(uploadId);
260265

261-
await this.paymentAPIService.createEvent(
262-
{ uploadId, totalRecords, validRecords, invalidRecords },
263-
userExternalIdOrEmail
264-
);
266+
try {
267+
await this.paymentAPIService.createEvent(
268+
{ units: totalRecords, billableMetricCode: BILLABLEMETRIC_CODE_ENUM.ROWS },
269+
userExternalIdOrEmail
270+
);
271+
272+
// Only update database if payment event creation succeeds
273+
await this.uploadRepository.update(
274+
{ _id: uploadId },
275+
{
276+
status: UploadStatusEnum.REVIEWING,
277+
totalRecords,
278+
validRecords,
279+
invalidRecords,
280+
}
281+
);
282+
283+
await this.templateRepository.findOneAndUpdate(
284+
{
285+
_id: _templateId,
286+
},
287+
{
288+
$inc: {
289+
totalUploads: 1,
290+
totalRecords: totalRecords,
291+
totalInvalidRecords: invalidRecords,
292+
},
293+
}
294+
);
295+
} catch (error) {
296+
const template = await this.templateRepository.findById(_templateId);
297+
const environment = await this.environmentRepository.getProjectTeamMembers(template._projectId);
298+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
299+
// @ts-ignore
300+
const teamMemberEmails = environment.map((teamMember) => teamMember._userId.email);
301+
console.log(teamMemberEmails);
302+
303+
const emailContents = this.emailService.getEmailContent({
304+
type: 'IMPORT_LIMIT_EXCEEDED_EMAIL',
305+
data: {
306+
limitType: 'Import Rows',
307+
currentUsage: 'All available units including grace percentage',
308+
planName: 'Current Plan',
309+
upgradeUrl: `${process.env.WEB_BASE_URL}/pricing`,
310+
},
311+
});
312+
313+
teamMemberEmails.forEach(async (email) => {
314+
await this.emailService.sendEmail({
315+
to: email,
316+
subject: EMAIL_SUBJECT.IMPORT_LIMIT_EXCEEDED,
317+
html: emailContents,
318+
from: process.env.ALERT_EMAIL_FROM,
319+
senderName: process.env.EMAIL_FROM_NAME,
320+
});
321+
});
322+
323+
throw new UsageLimitExceededException(error.message);
324+
}
265325
}
266326
}

0 commit comments

Comments
 (0)