Skip to content

Commit

Permalink
chore(root): Release 2024-12-03 08:06 (#7195)
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] authored Dec 3, 2024
2 parents 8e6e39c + a4115c8 commit e30bc32
Show file tree
Hide file tree
Showing 25 changed files with 391 additions and 223 deletions.
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"ABNF",
"addrs",
"adresses",
"sdkerror",
"africas",
"africastalking",
"Aland",
Expand Down
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"@sentry/tracing": "^7.40.0",
"@types/newrelic": "^9.14.0",
"@upstash/ratelimit": "^0.4.4",
"@novu/api": "^0.0.1-alpha.39",
"axios": "^1.6.8",
"liquidjs": "^10.13.1",
"bcrypt": "^5.0.0",
Expand Down
25 changes: 24 additions & 1 deletion apps/api/src/app/bridge/bridge.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,23 @@ import {
UpsertControlValuesUseCase,
UpsertPreferences,
DeletePreferencesUseCase,
TierRestrictionsValidateUsecase,
} from '@novu/application-generic';
import { PreferencesRepository } from '@novu/dal';
import { CommunityOrganizationRepository, PreferencesRepository } from '@novu/dal';
import { SharedModule } from '../shared/shared.module';
import { BridgeController } from './bridge.controller';
import { USECASES } from './usecases';
import { PostProcessWorkflowUpdate } from '../workflows-v2/usecases/post-process-workflow-update';
import { OverloadContentDataOnWorkflowUseCase } from '../workflows-v2/usecases/overload-content-data';
import {
BuildDefaultPayloadUsecase,
CollectPlaceholderWithDefaultsUsecase,
PrepareAndValidateContentUsecase,
ValidatePlaceholderUsecase,
} from '../workflows-v2/usecases/validate-content';
import { BuildAvailableVariableSchemaUsecase } from '../workflows-v2/usecases/build-variable-schema';
import { ExtractDefaultValuesFromSchemaUsecase } from '../workflows-v2/usecases/extract-default-values-from-schema';
import { HydrateEmailSchemaUseCase } from '../environments-v1/usecases/output-renderers/hydrate-email-schema.usecase';

const PROVIDERS = [
CreateWorkflow,
Expand All @@ -35,6 +47,17 @@ const PROVIDERS = [
UpsertPreferences,
DeletePreferencesUseCase,
UpsertControlValuesUseCase,
PostProcessWorkflowUpdate,
OverloadContentDataOnWorkflowUseCase,
PrepareAndValidateContentUsecase,
BuildAvailableVariableSchemaUsecase,
BuildDefaultPayloadUsecase,
ValidatePlaceholderUsecase,
CollectPlaceholderWithDefaultsUsecase,
ExtractDefaultValuesFromSchemaUsecase,
TierRestrictionsValidateUsecase,
HydrateEmailSchemaUseCase,
CommunityOrganizationRepository,
];

@Module({
Expand Down
177 changes: 112 additions & 65 deletions apps/api/src/app/bridge/usecases/sync/sync.usecase.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BadRequestException, HttpException, Injectable } from '@nestjs/common';

import {
EnvironmentEntity,
EnvironmentRepository,
NotificationGroupRepository,
NotificationTemplateEntity,
Expand All @@ -20,6 +21,7 @@ import {
import {
buildWorkflowPreferences,
JSONSchemaDto,
UserSessionData,
WorkflowCreationSourceEnum,
WorkflowOriginEnum,
WorkflowPreferences,
Expand All @@ -29,6 +31,7 @@ import { DiscoverOutput, DiscoverStepOutput, DiscoverWorkflowOutput, GetActionEn

import { SyncCommand } from './sync.command';
import { CreateBridgeResponseDto } from '../../dtos/create-bridge-response.dto';
import { PostProcessWorkflowUpdate } from '../../../workflows-v2/usecases/post-process-workflow-update';

@Injectable()
export class Sync {
Expand All @@ -40,15 +43,35 @@ export class Sync {
private notificationGroupRepository: NotificationGroupRepository,
private environmentRepository: EnvironmentRepository,
private executeBridgeRequest: ExecuteBridgeRequest,
private workflowUpdatePostProcess: PostProcessWorkflowUpdate,
private analyticsService: AnalyticsService
) {}
async execute(command: SyncCommand): Promise<CreateBridgeResponseDto> {
const environment = await this.environmentRepository.findOne({ _id: command.environmentId });
const environment = await this.findEnvironment(command);
const discover = await this.executeDiscover(command);
this.sendAnalytics(command, environment, discover);
const persistedWorkflowsInBridge = await this.processWorkflows(command, discover.workflows);

if (!environment) {
throw new BadRequestException('Environment not found');
await this.disposeOldWorkflows(command, persistedWorkflowsInBridge);
await this.updateBridgeUrl(command);

return persistedWorkflowsInBridge;
}

private sendAnalytics(command: SyncCommand, environment: EnvironmentEntity, discover: DiscoverOutput) {
if (command.source !== 'sample-workspace') {
this.analyticsService.track('Sync Request - [Bridge API]', command.userId, {
_organization: command.organizationId,
_environment: command.environmentId,
environmentName: environment.name,
workflowsCount: discover.workflows?.length || 0,
localEnvironment: !!command.bridgeUrl?.includes('novu.sh'),
source: command.source,
});
}
}

private async executeDiscover(command: SyncCommand): Promise<DiscoverOutput> {
let discover: DiscoverOutput | undefined;
try {
discover = (await this.executeBridgeRequest.execute({
Expand All @@ -70,24 +93,17 @@ export class Sync {
throw new BadRequestException('Invalid Bridge URL Response');
}

if (command.source !== 'sample-workspace') {
this.analyticsService.track('Sync Request - [Bridge API]', command.userId, {
_organization: command.organizationId,
_environment: command.environmentId,
environmentName: environment.name,
workflowsCount: discover.workflows?.length || 0,
localEnvironment: !!command.bridgeUrl?.includes('novu.sh'),
source: command.source,
});
}

const persistedWorkflowsInBridge = await this.createWorkflows(command, discover.workflows);
return discover;
}

await this.disposeOldWorkflows(command, persistedWorkflowsInBridge);
private async findEnvironment(command: SyncCommand): Promise<EnvironmentEntity> {
const environment = await this.environmentRepository.findOne({ _id: command.environmentId });

await this.updateBridgeUrl(command);
if (!environment) {
throw new BadRequestException('Environment not found');
}

return persistedWorkflowsInBridge;
return environment;
}

private async updateBridgeUrl(command: SyncCommand): Promise<void> {
Expand Down Expand Up @@ -142,46 +158,79 @@ export class Sync {
});
}

private async createWorkflows(
private async processWorkflows(
command: SyncCommand,
workflowsFromBridge: DiscoverWorkflowOutput[]
): Promise<NotificationTemplateEntity[]> {
return Promise.all(
workflowsFromBridge.map(async (workflow) => {
const workflowExist = await this.notificationTemplateRepository.findByTriggerIdentifier(
command.environmentId,
workflow.workflowId
let savedWorkflow = await this.upsertWorkflow(command, workflow);

const validatedWorkflowWithIssues = await this.workflowUpdatePostProcess.execute({
user: {
_id: command.userId,
environmentId: command.environmentId,
organizationId: command.organizationId,
} as UserSessionData,
workflow: {
...savedWorkflow,
userPreferences: null,
defaultPreferences: this.getWorkflowPreferences(workflow),
},
});

savedWorkflow = await this.updateWorkflowUsecase.execute(
UpdateWorkflowCommand.create({
...validatedWorkflowWithIssues,
id: validatedWorkflowWithIssues._id,
type: WorkflowTypeEnum.BRIDGE,
environmentId: command.environmentId,
organizationId: command.organizationId,
userId: command.userId,
})
);

let savedWorkflow: NotificationTemplateEntity | undefined;
return savedWorkflow;
})
);
}

if (workflowExist) {
savedWorkflow = await this.updateWorkflow(workflowExist, command, workflow);
} else {
const notificationGroupId = await this.getNotificationGroup(
this.castToAnyNotSupportedParam(workflow)?.notificationGroupId,
command.environmentId
);
private async upsertWorkflow(
command: SyncCommand,
workflow: DiscoverWorkflowOutput
): Promise<NotificationTemplateEntity> {
const workflowExist = await this.notificationTemplateRepository.findByTriggerIdentifier(
command.environmentId,
workflow.workflowId
);

if (!notificationGroupId) {
throw new BadRequestException('Notification group not found');
}
const isWorkflowActive = this.castToAnyNotSupportedParam(workflow)?.active ?? true;
let savedWorkflow: NotificationTemplateEntity | undefined;

savedWorkflow = await this.createWorkflow(notificationGroupId, isWorkflowActive, command, workflow);
}
if (workflowExist) {
savedWorkflow = await this.updateWorkflowUsecase.execute(
UpdateWorkflowCommand.create(this.mapDiscoverWorkflowToUpdateWorkflowCommand(workflowExist, command, workflow))
);
} else {
savedWorkflow = await this.createWorkflow(command, workflow);
}

return savedWorkflow;
})
);
return savedWorkflow;
}

private async createWorkflow(
notificationGroupId: string,
isWorkflowActive: boolean,
command: SyncCommand,
workflow: DiscoverWorkflowOutput
): Promise<NotificationTemplateEntity> {
const notificationGroupId = await this.getNotificationGroup(
this.castToAnyNotSupportedParam(workflow)?.notificationGroupId,
command.environmentId
);

if (!notificationGroupId) {
throw new BadRequestException('Notification group not found');
}
const isWorkflowActive = this.castToAnyNotSupportedParam(workflow)?.active ?? true;

return await this.createWorkflowUsecase.execute(
CreateWorkflowCommand.create({
origin: WorkflowOriginEnum.EXTERNAL,
Expand Down Expand Up @@ -209,33 +258,31 @@ export class Sync {
);
}

private async updateWorkflow(
private mapDiscoverWorkflowToUpdateWorkflowCommand(
workflowExist: NotificationTemplateEntity,
command: SyncCommand,
workflow: DiscoverWorkflowOutput
): Promise<NotificationTemplateEntity> {
return await this.updateWorkflowUsecase.execute(
UpdateWorkflowCommand.create({
id: workflowExist._id,
environmentId: command.environmentId,
organizationId: command.organizationId,
userId: command.userId,
name: this.getWorkflowName(workflow),
workflowId: workflow.workflowId,
steps: this.mapSteps(workflow.steps, workflowExist),
controls: {
schema: workflow.controls?.schema as JSONSchemaDto,
},
rawData: workflow,
payloadSchema: workflow.payload?.schema as unknown as JSONSchemaDto,
type: WorkflowTypeEnum.BRIDGE,
description: this.getWorkflowDescription(workflow),
data: this.castToAnyNotSupportedParam(workflow)?.data,
tags: this.getWorkflowTags(workflow),
active: this.castToAnyNotSupportedParam(workflow)?.active ?? true,
defaultPreferences: this.getWorkflowPreferences(workflow),
})
);
): UpdateWorkflowCommand {
return {
id: workflowExist._id,
environmentId: command.environmentId,
organizationId: command.organizationId,
userId: command.userId,
name: this.getWorkflowName(workflow),
workflowId: workflow.workflowId,
steps: this.mapSteps(workflow.steps, workflowExist),
controls: {
schema: workflow.controls?.schema as JSONSchemaDto,
},
rawData: workflow,
payloadSchema: workflow.payload?.schema as unknown as JSONSchemaDto,
type: WorkflowTypeEnum.BRIDGE,
description: this.getWorkflowDescription(workflow),
data: this.castToAnyNotSupportedParam(workflow)?.data,
tags: this.getWorkflowTags(workflow),
active: this.castToAnyNotSupportedParam(workflow)?.active ?? true,
defaultPreferences: this.getWorkflowPreferences(workflow),
};
}

private mapSteps(
Expand Down
12 changes: 9 additions & 3 deletions apps/api/src/app/events/dtos/trigger-event-request.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ export class TriggerEventRequestDto {
},
},
})
@ApiProperty({
type: 'object',
description: 'An optional payload object that can contain any properties',
required: false,
additionalProperties: true,
})
@IsObject()
@IsOptional()
payload?: Record<string, unknown>;
Expand Down Expand Up @@ -87,14 +93,14 @@ export class TriggerEventRequestDto {
{
$ref: getSchemaPath(SubscriberPayloadDto),
},
{
$ref: getSchemaPath(TopicPayloadDto),
},
{
type: 'string',
description: 'Unique identifier of a subscriber in your systems',
example: 'SUBSCRIBER_ID',
},
{
$ref: getSchemaPath(TopicPayloadDto),
},
],
},
})
Expand Down
Loading

0 comments on commit e30bc32

Please sign in to comment.