Skip to content

Commit

Permalink
fix(api): Add a Patch Workflow endpoint (#7019)
Browse files Browse the repository at this point in the history
  • Loading branch information
tatarco authored Nov 15, 2024
1 parent 1dd3114 commit f4a8985
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 80 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { JSONSchemaDto, UiComponentEnum, UiSchema, UiSchemaGroupEnum, UiSchemaProperty } from '@novu/shared';

const ABSOLUTE_AND_RELATIVE_URL_REGEX = '^(?!mailto:)(?:(https?):\\/\\/[^\\s/$.?#].[^\\s]*)|^(\\/[^\\s]*)$';
import { JSONSchemaDto, UiComponentEnum, UiSchema, UiSchemaGroupEnum } from '@novu/shared';

const redirectSchema = {
type: 'object',
properties: {
url: {
type: 'string',
pattern: ABSOLUTE_AND_RELATIVE_URL_REGEX,
},
target: {
type: 'string',
Expand All @@ -34,7 +31,7 @@ export const inAppControlSchema = {
properties: {
subject: { type: 'string' },
body: { type: 'string' },
avatar: { type: 'string', format: 'uri' },
avatar: { type: 'string' },
primaryAction: actionSchema, // Nested primaryAction
secondaryAction: actionSchema, // Nested secondaryAction
data: { type: 'object', additionalProperties: true },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './patch-step-data.command';
export * from './patch-step-data.usecase';
export * from './patch-step.command';
export * from './patch-step.usecase';
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { EnvironmentWithUserObjectCommand } from '@novu/application-generic';
import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { IdentifierOrInternalId, PatchStepFieldEnum } from '@novu/shared';
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { IdentifierOrInternalId } from '@novu/shared';

export class PatchStepDataCommand extends EnvironmentWithUserObjectCommand {
export class PatchStepCommand extends EnvironmentWithUserObjectCommand {
@IsString()
@IsNotEmpty()
identifierOrInternalId: IdentifierOrInternalId;

@IsString()
@IsNotEmpty()
stepId: IdentifierOrInternalId;
@IsArray()
fieldsToUpdate: PatchStepFieldEnum[];

@IsString()
@IsOptional()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/* eslint-disable no-param-reassign */
import { BadRequestException, Injectable } from '@nestjs/common';
import { PatchStepFieldEnum, StepDataDto, UserSessionData } from '@novu/shared';
import { StepDataDto, UserSessionData } from '@novu/shared';
import { NotificationStepEntity, NotificationTemplateEntity, NotificationTemplateRepository } from '@novu/dal';
import {
GetWorkflowByIdsUseCase,
UpsertControlValuesCommand,
UpsertControlValuesUseCase,
} from '@novu/application-generic';
import { PatchStepDataCommand } from './patch-step-data.command';
import { PatchStepCommand } from './patch-step.command';
import { BuildStepDataUsecase } from '../build-step-data';
import { PostProcessWorkflowUpdate } from '../post-process-workflow-update';

Expand All @@ -16,7 +16,7 @@ type ValidNotificationWorkflow = {
workflow: NotificationTemplateEntity;
};
@Injectable()
export class PatchStepDataUsecase {
export class PatchStepUsecase {
constructor(
private getWorkflowByIdsUseCase: GetWorkflowByIdsUseCase,
private buildStepDataUsecase: BuildStepDataUsecase,
Expand All @@ -25,15 +25,9 @@ export class PatchStepDataUsecase {
private postProcessWorkflowUpdate: PostProcessWorkflowUpdate
) {}

async execute(command: PatchStepDataCommand): Promise<StepDataDto> {
async execute(command: PatchStepCommand): Promise<StepDataDto> {
const persistedItems = await this.loadPersistedItems(command);
if (command.fieldsToUpdate.includes(PatchStepFieldEnum.NAME)) {
await this.updateName(persistedItems, command);
}

if (command.fieldsToUpdate.includes(PatchStepFieldEnum.CONTROL_VALUES)) {
await this.updateControlValues(persistedItems, command);
}
await this.patchFieldsOnPersistedItems(command, persistedItems);
const updatedWorkflow = await this.postProcessWorkflowUpdate.execute({
workflow: persistedItems.workflow,
user: command.user,
Expand All @@ -43,15 +37,25 @@ export class PatchStepDataUsecase {
return await this.buildStepDataUsecase.execute({ ...command });
}

private async loadPersistedItems(command: PatchStepDataCommand) {
private async patchFieldsOnPersistedItems(command: PatchStepCommand, persistedItems: ValidNotificationWorkflow) {
if (command.name !== undefined) {
await this.updateName(persistedItems, command);
}

if (command.controlValues !== undefined) {
await this.updateControlValues(persistedItems, command);
}
}

private async loadPersistedItems(command: PatchStepCommand) {
const workflow = await this.fetchWorkflow(command);

const { currentStep } = await this.findStepWithSameMemoryPointer(command, workflow);

return { workflow, currentStep };
}

private async updateName(persistedItems: ValidNotificationWorkflow, command: PatchStepDataCommand) {
private async updateName(persistedItems: ValidNotificationWorkflow, command: PatchStepCommand) {
persistedItems.currentStep.name = command.name;
}
private async persistWorkflow(
Expand All @@ -69,7 +73,7 @@ export class PatchStepDataUsecase {
);
}

private async fetchWorkflow(command: PatchStepDataCommand) {
private async fetchWorkflow(command: PatchStepCommand) {
return await this.getWorkflowByIdsUseCase.execute({
identifierOrInternalId: command.identifierOrInternalId,
environmentId: command.user.environmentId,
Expand All @@ -78,7 +82,7 @@ export class PatchStepDataUsecase {
});
}

private async findStepWithSameMemoryPointer(command: PatchStepDataCommand, workflow: NotificationTemplateEntity) {
private async findStepWithSameMemoryPointer(command: PatchStepCommand, workflow: NotificationTemplateEntity) {
const currentStep = workflow.steps.find(
(stepItem) => stepItem._id === command.stepId || stepItem.stepId === command.stepId
);
Expand All @@ -93,7 +97,7 @@ export class PatchStepDataUsecase {

return { currentStep };
}
private async updateControlValues(persistedItems: ValidNotificationWorkflow, command: PatchStepDataCommand) {
private async updateControlValues(persistedItems: ValidNotificationWorkflow, command: PatchStepCommand) {
return await this.upsertControlValuesUseCase.execute(
UpsertControlValuesCommand.create({
organizationId: command.user.organizationId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './patch-workflow.command';
export * from './patch-workflow.usecase';
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { EnvironmentWithUserObjectCommand } from '@novu/application-generic';
import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { IdentifierOrInternalId } from '@novu/shared';

export class PatchWorkflowCommand extends EnvironmentWithUserObjectCommand {
@IsString()
@IsNotEmpty()
identifierOrInternalId: IdentifierOrInternalId;
@IsBoolean()
@IsOptional()
active?: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Injectable } from '@nestjs/common';
import { UserSessionData, WorkflowResponseDto } from '@novu/shared';
import { NotificationTemplateEntity, NotificationTemplateRepository } from '@novu/dal';
import { GetWorkflowByIdsUseCase } from '@novu/application-generic';
import { PostProcessWorkflowUpdate } from '../post-process-workflow-update';
import { PatchWorkflowCommand } from './patch-workflow.command';
import { GetWorkflowUseCase } from '../get-workflow';

@Injectable()
export class PatchWorkflowUsecase {
constructor(
private getWorkflowByIdsUseCase: GetWorkflowByIdsUseCase,
private notificationTemplateRepository: NotificationTemplateRepository,
private postProcessWorkflowUpdate: PostProcessWorkflowUpdate,
private getWorkflowUseCase: GetWorkflowUseCase
) {}

async execute(command: PatchWorkflowCommand): Promise<WorkflowResponseDto> {
const persistedWorkflow = await this.fetchWorkflow(command);
let transientWorkflow = this.patchWorkflowFields(persistedWorkflow, command);

transientWorkflow = await this.postProcessWorkflowUpdate.execute({
workflow: transientWorkflow,
user: command.user,
});
await this.persistWorkflow(transientWorkflow, command.user);

return await this.getWorkflowUseCase.execute({
identifierOrInternalId: command.identifierOrInternalId,
user: command.user,
});
}

private patchWorkflowFields(persistedWorkflow, command: PatchWorkflowCommand) {
const transientWorkflow = { ...persistedWorkflow };
if (command.active !== undefined) {
// @ts-ignore
transientWorkflow.active = command.active;
}

return transientWorkflow;
}

private async persistWorkflow(
workflowWithIssues: Partial<NotificationTemplateEntity>,
userSessionData: UserSessionData
) {
await this.notificationTemplateRepository.update(
{
_id: workflowWithIssues._id,
_environmentId: userSessionData.environmentId,
},
{
...workflowWithIssues,
}
);
}

private async fetchWorkflow(command: PatchWorkflowCommand) {
return await this.getWorkflowByIdsUseCase.execute({
identifierOrInternalId: command.identifierOrInternalId,
environmentId: command.user.environmentId,
organizationId: command.user.organizationId,
userId: command.user._id,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
CreateWorkflowDto,
DEFAULT_WORKFLOW_PREFERENCES,
IdentifierOrInternalId,
PatchStepFieldEnum,
slugify,
StepCreateDto,
StepDto,
Expand Down Expand Up @@ -41,7 +40,7 @@ import { UpsertWorkflowCommand } from './upsert-workflow.command';
import { toResponseWorkflowDto } from '../../mappers/notification-template-mapper';
import { stepTypeToDefaultDashboardControlSchema } from '../../shared';
import { WorkflowNotFoundException } from '../../exceptions/workflow-not-found-exception';
import { PatchStepDataUsecase } from '../patch-step-data';
import { PatchStepUsecase } from '../patch-step-data';
import { PostProcessWorkflowUpdate } from '../post-process-workflow-update';

@Injectable()
Expand All @@ -55,7 +54,7 @@ export class UpsertWorkflowUseCase {
private getWorkflowByIdsUseCase: GetWorkflowByIdsUseCase,
private getPreferencesUseCase: GetPreferences,
private notificationTemplateRepository: NotificationTemplateRepository,
private patchStepDataUsecase: PatchStepDataUsecase
private patchStepDataUsecase: PatchStepUsecase
) {}
async execute(command: UpsertWorkflowCommand): Promise<WorkflowResponseDto> {
const workflowForUpdate = await this.queryWorkflow(command);
Expand Down Expand Up @@ -357,7 +356,6 @@ export class UpsertWorkflowUseCase {
}
await this.patchStepDataUsecase.execute({
controlValues,
fieldsToUpdate: [PatchStepFieldEnum.CONTROL_VALUES],
identifierOrInternalId: workflow._id,
name: step.name,
stepId: step._templateId,
Expand Down
Loading

0 comments on commit f4a8985

Please sign in to comment.