Skip to content

Commit 21a37d3

Browse files
Merge branch 'next' into feat/password-validation
Signed-off-by: Bhavik Chavda <[email protected]>
2 parents 3c7c8b3 + a9e2e6e commit 21a37d3

File tree

140 files changed

+17557
-18846
lines changed

Some content is hidden

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

140 files changed

+17557
-18846
lines changed

apps/api/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@impler/api",
3-
"version": "0.27.1",
3+
"version": "0.28.0",
44
"author": "implerhq",
55
"license": "MIT",
66
"private": true,
@@ -48,6 +48,7 @@
4848
"compression": "^1.7.4",
4949
"cookie-parser": "^1.4.6",
5050
"cron": "^3.1.9",
51+
"cron-parser": "^4.9.0",
5152
"date-fns": "^2.30.0",
5253
"dayjs": "^1.11.11",
5354
"dotenv": "^16.0.2",

apps/api/src/app.module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { ActivityModule } from './app/activity/activity.module';
1616
import { UserModule } from './app/user/user.module';
1717
import { ImportJobsModule } from 'app/import-jobs/import-jobs.module';
1818
import { TeamModule } from 'app/team/team.module';
19+
import { AutoImportJobsSchedularModule } from 'app/auto-import-jobs-schedular/auto-import-jobs-schedular.module';
20+
import { FailedWebhookRetryModule } from 'app/failed-webhook-request-retry/failed-webhook-retry.module';
1921

2022
const modules: Array<Type | DynamicModule | Promise<DynamicModule> | ForwardReference> = [
2123
ProjectModule,
@@ -33,6 +35,8 @@ const modules: Array<Type | DynamicModule | Promise<DynamicModule> | ForwardRefe
3335
ActivityModule,
3436
ImportJobsModule,
3537
TeamModule,
38+
AutoImportJobsSchedularModule,
39+
FailedWebhookRetryModule,
3640
];
3741

3842
const providers = [Logger];

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

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,13 @@ export class AuthController {
157157
@UserSession() user: IJwtPayload,
158158
@Res({ passthrough: true }) res: Response
159159
) {
160-
const projectWithEnvironment = await this.onboardUser.execute(
161-
{
162-
_userId: user._id,
163-
projectName: body.projectName,
164-
role: body.role,
165-
companySize: body.companySize,
166-
source: body.source,
167-
},
168-
user.email
169-
);
160+
const projectWithEnvironment = await this.onboardUser.execute({
161+
_userId: user._id,
162+
projectName: body.projectName,
163+
role: body.role,
164+
companySize: body.companySize,
165+
source: body.source,
166+
});
170167

171168
const userApiKey = projectWithEnvironment.environment.apiKeys.find(
172169
(apiKey) => apiKey._userId.toString() === user._id

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ import { RequestForgotPasswordCommand } from './request-forgot-password/request-
1414
import { CreateProject } from 'app/project/usecases';
1515
import { SaveSampleFile, UpdateImageColumns } from '@shared/usecases';
1616
import { CreateEnvironment, GenerateUniqueApiKey } from 'app/environment/usecases';
17-
import { CreateTemplate, UpdateCustomization, UpdateTemplateColumns } from 'app/template/usecases';
17+
import {
18+
AddColumn,
19+
CreateTemplate,
20+
GetImportFileSchema,
21+
UpdateCustomization,
22+
UpdateTemplateColumns,
23+
} from 'app/template/usecases';
1824

1925
export const USE_CASES = [
2026
Verify,
@@ -24,6 +30,8 @@ export const USE_CASES = [
2430
RegisterUser,
2531
ResetPassword,
2632
CreateProject,
33+
GetImportFileSchema,
34+
AddColumn,
2735
SaveSampleFile,
2836
CreateTemplate,
2937
CreateEnvironment,

apps/api/src/app/auth/usecases/onboard-user/onboard-user.usecase.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@ export class OnboardUser {
1616
private paymentAPIService: PaymentAPIService
1717
) {}
1818

19-
async execute(command: OnboardUserCommand, email: string) {
19+
async execute(command: OnboardUserCommand) {
2020
const createdProject = await this.createProject.execute(
2121
CreateProjectCommand.create({
2222
_userId: command._userId,
2323
name: command.projectName,
2424
onboarding: true,
25-
}),
26-
email
25+
})
2726
);
2827

2928
await this.userRepository.update(
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Module } from '@nestjs/common';
2+
import { SharedModule } from '@shared/shared.module';
3+
import { ScheduleModule } from '@nestjs/schedule';
4+
import { USE_CASES } from './usecase';
5+
6+
@Module({
7+
imports: [ScheduleModule.forRoot(), SharedModule],
8+
providers: [...USE_CASES],
9+
})
10+
export class AutoImportJobsSchedularModule {}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { UserJobEntity, UserJobRepository, WebhookDestinationRepository } from '@impler/dal';
2+
import { Injectable } from '@nestjs/common';
3+
import * as dayjs from 'dayjs';
4+
import * as parser from 'cron-parser';
5+
import { Cron } from '@nestjs/schedule';
6+
import { ScheduleUserJob, UpdateUserJob } from 'app/import-jobs/usecase';
7+
import { UserJobImportStatusEnum } from '@impler/shared';
8+
import { CRON_SCHEDULE } from '@shared/constants';
9+
const parseCronExpression = require('@impler/shared/src/utils/cronstrue');
10+
11+
@Injectable()
12+
export class AutoImportJobsSchedular {
13+
constructor(
14+
private readonly userJobRepository: UserJobRepository,
15+
private readonly webhookDestinationRepository: WebhookDestinationRepository,
16+
private readonly updateUserJob: UpdateUserJob,
17+
private readonly scheduleUserJob: ScheduleUserJob
18+
) {}
19+
20+
@Cron(CRON_SCHEDULE.DEFAULT_CRON_TIME)
21+
async handleCronSchedular() {
22+
await this.fetchAndExecuteScheduledJobs();
23+
}
24+
25+
private async fetchAndExecuteScheduledJobs() {
26+
const now = dayjs();
27+
const userJobs = await this.userJobRepository.find({});
28+
29+
for (const userJob of userJobs) {
30+
if (await this.shouldCroneRun({ userJob })) {
31+
try {
32+
const interval = parser.parseExpression(userJob.cron);
33+
34+
const nextScheduledTime = dayjs(interval.next().toDate().toDateString());
35+
36+
if (this.isJobDueToday(userJob.cron, now)) {
37+
await this.scheduleUpdateNextRun(userJob._id, nextScheduledTime, dayjs(userJob.endsOn));
38+
39+
await this.updateUserJob.execute(userJob._id, userJob);
40+
41+
await this.scheduleUserJob.execute(userJob._id, userJob.cron);
42+
}
43+
} catch (error) {}
44+
}
45+
}
46+
}
47+
48+
async scheduleUpdateNextRun(userJobId: string, nextRunSchedule: dayjs.Dayjs, endsOn: dayjs.Dayjs) {
49+
const nextRunValue = dayjs(nextRunSchedule).isAfter(endsOn) && endsOn ? undefined : nextRunSchedule;
50+
51+
await this.userJobRepository.update({ _id: userJobId }, { $set: { nextRun: nextRunValue } });
52+
}
53+
54+
async parseCronExpression(userCronExpression: string) {
55+
if (!userCronExpression || userCronExpression.trim() === '') {
56+
throw new Error('Cron expression is empty');
57+
}
58+
59+
return parseCronExpression.toString(userCronExpression);
60+
}
61+
62+
private isJobDueToday(cronExpression: string, currentDate: dayjs.Dayjs): boolean {
63+
try {
64+
const interval = parser.parseExpression(cronExpression);
65+
66+
const nextScheduledTime = dayjs(interval.next().toDate());
67+
68+
return nextScheduledTime.isSame(currentDate, 'd');
69+
} catch (error) {
70+
return false;
71+
}
72+
}
73+
74+
async fetchDestination(templateId: string) {
75+
const webhookDestination = await this.webhookDestinationRepository.findOne({
76+
_templateId: templateId,
77+
});
78+
79+
if (!webhookDestination) {
80+
return false;
81+
}
82+
83+
const { _id, callbackUrl } = webhookDestination;
84+
85+
return { _id, callbackUrl };
86+
}
87+
88+
async shouldCroneRun({ userJob }: { userJob: UserJobEntity }): Promise<boolean> {
89+
const now = dayjs();
90+
91+
if (
92+
(userJob.cron && userJob.status === UserJobImportStatusEnum.SCHEDULING) ||
93+
userJob.status === UserJobImportStatusEnum.RUNNING ||
94+
(userJob.status === 'Completed' && (await this.fetchDestination(userJob._templateId)) && !userJob.endsOn) ||
95+
!dayjs(userJob.endsOn).isSame(now, 'd')
96+
) {
97+
return true;
98+
}
99+
100+
return false;
101+
}
102+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { ScheduleUserJob, UpdateUserJob, UserJobTriggerService } from 'app/import-jobs/usecase';
2+
import { AutoImportJobsSchedular } from './auto-import-jobs-schedular';
3+
import { QueueService } from '@shared/services/queue.service';
4+
5+
export const USE_CASES = [
6+
AutoImportJobsSchedular,
7+
UpdateUserJob,
8+
UserJobTriggerService,
9+
UserJobTriggerService,
10+
ScheduleUserJob,
11+
QueueService,
12+
//
13+
];
14+
export { AutoImportJobsSchedular, UpdateUserJob, UserJobTriggerService, QueueService };
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Module } from '@nestjs/common';
2+
import { SharedModule } from '@shared/shared.module';
3+
import { ScheduleModule } from '@nestjs/schedule';
4+
import { USE_CASES } from './usecase';
5+
6+
@Module({
7+
imports: [ScheduleModule.forRoot(), SharedModule],
8+
providers: [...USE_CASES],
9+
})
10+
export class FailedWebhookRetryModule {}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { Cron, CronExpression } from '@nestjs/schedule';
3+
4+
import { QueuesEnum } from '@impler/shared';
5+
import { QueueService } from '@shared/services/queue.service';
6+
import { FailedWebhookRetryRequestsEntity, FailedWebhookRetryRequestsRepository } from '@impler/dal';
7+
8+
@Injectable()
9+
export class FailedWebhookRetry {
10+
constructor(
11+
private failedWebhookRetryRequestsRepository: FailedWebhookRetryRequestsRepository = new FailedWebhookRetryRequestsRepository(),
12+
private queueService: QueueService
13+
) {}
14+
15+
@Cron(CronExpression.EVERY_5_MINUTES)
16+
async processWebhookRetries() {
17+
try {
18+
const failedWebhooks: FailedWebhookRetryRequestsEntity[] = await this.failedWebhookRetryRequestsRepository.find({
19+
nextRequestTime: { $lt: new Date() },
20+
});
21+
22+
if (!failedWebhooks.length) {
23+
return;
24+
}
25+
26+
await Promise.allSettled(failedWebhooks.map((wbh) => this.processWebhook(wbh)));
27+
} catch (error) {
28+
throw error;
29+
}
30+
}
31+
32+
private async processWebhook(webhook: FailedWebhookRetryRequestsEntity) {
33+
try {
34+
this.queueService.publishToQueue(QueuesEnum.SEND_FAILED_WEBHOOK_DATA, webhook._id as string);
35+
} catch (error) {
36+
throw error;
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)