Skip to content

Commit

Permalink
feat(api): encode workflow and step ids on response
Browse files Browse the repository at this point in the history
  • Loading branch information
djabarovgeorge committed Oct 21, 2024
1 parent 541510d commit ff8388f
Show file tree
Hide file tree
Showing 18 changed files with 234 additions and 142 deletions.
28 changes: 15 additions & 13 deletions apps/api/src/app/step-schemas/e2e/get-step-schema.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { expect } from 'chai';

import { UserSession } from '@novu/testing';
import { StepTypeEnum, WorkflowResponseDto } from '@novu/shared';
import { encodeBase62 } from '../../shared/helpers';

describe('Get Step Schema - /step-schemas?workflowId=:workflowId&stepId=:stepId&stepType=:stepType (GET)', async () => {
let session: UserSession;
Expand Down Expand Up @@ -94,8 +95,8 @@ describe('Get Step Schema - /step-schemas?workflowId=:workflowId&stepId=:stepId&
const { data } = (
await getStepSchema({
session,
workflowId: createdWorkflow._id,
stepId: createdWorkflow.steps[0].stepUuid,
workflowId: createdWorkflow.id,
stepId: createdWorkflow.steps[0].id,
})
).body;

Expand Down Expand Up @@ -135,8 +136,8 @@ describe('Get Step Schema - /step-schemas?workflowId=:workflowId&stepId=:stepId&
const { data } = (
await getStepSchema({
session,
workflowId: createdWorkflow._id,
stepId: createdWorkflow.steps[0].stepUuid,
workflowId: createdWorkflow.id,
stepId: createdWorkflow.steps[0].id,
})
).body;

Expand All @@ -148,8 +149,8 @@ describe('Get Step Schema - /step-schemas?workflowId=:workflowId&stepId=:stepId&
await getStepSchema({
session,
stepType: StepTypeEnum.IN_APP,
workflowId: createdWorkflow._id,
stepId: createdWorkflow.steps[1].stepUuid,
workflowId: createdWorkflow.id,
stepId: createdWorkflow.steps[1].id,
})
).body;

Expand All @@ -159,10 +160,11 @@ describe('Get Step Schema - /step-schemas?workflowId=:workflowId&stepId=:stepId&
const variableStepKey = variableStepKeys[0];
const createdWorkflowPreviousSteps = createdWorkflow.steps.slice(
0,
createdWorkflow.steps.findIndex((stepItem) => stepItem.stepUuid === createdWorkflow.steps[1].stepUuid)
createdWorkflow.steps.findIndex((stepItem) => stepItem.id === createdWorkflow.steps[1].id)
);
const variableStepKeyFoundInCreatedWorkflow = createdWorkflowPreviousSteps.find(
(step) => step.stepUuid === variableStepKey
// todo change step.id to step.stepId once we merge pr 6689, encodeBase62 wont be needed
(step) => step.id === encodeBase62(variableStepKey)
);
const isValidVariableStepKey = !!variableStepKeyFoundInCreatedWorkflow;
expect(isValidVariableStepKey).to.be.true;
Expand All @@ -187,28 +189,28 @@ describe('Get Step Schema - /step-schemas?workflowId=:workflowId&stepId=:stepId&
});

it('should get error for invalid step id', async function () {
const invalidStepUuid = `${createdWorkflow.steps[0].stepUuid}0`;
const invalidStepUuid = `${createdWorkflow.id}`;

const response = await getStepSchema({
session,
workflowId: createdWorkflow._id,
workflowId: createdWorkflow.id,
stepId: invalidStepUuid,
});

expect(response.status).to.equal(400);
expect(response.body.message).to.equal('No step found');
expect(response.body.stepId).to.equal(invalidStepUuid);
expect(response.body.workflowId).to.equal(createdWorkflow._id);
expect(response.body.workflowId).to.equal(createdWorkflow.id);
expect(response.body.statusCode).to.equal(400);
});

it('should get error for invalid workflow id', async function () {
const invalidWorkflowId = createdWorkflow.steps[0].stepUuid;
const invalidWorkflowId = createdWorkflow.steps[0].id;

const response = await getStepSchema({
session,
workflowId: invalidWorkflowId,
stepId: createdWorkflow.steps[0].stepUuid,
stepId: createdWorkflow.steps[0].id,
});

expect(response.status).to.equal(400);
Expand Down
7 changes: 4 additions & 3 deletions apps/api/src/app/step-schemas/step-schemas.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { createGetStepSchemaCommand } from './usecases/get-step-schema/get-step-
import { UserAuthentication } from '../shared/framework/swagger/api.key.security';
import { GetStepSchema } from './usecases/get-step-schema/get-step-schema.usecase';
import { StepSchemaDto } from './dtos/step-schema.dto';
import { ParseSlugIdPipe } from '../workflows-v2/pipes/parse-slug-Id.pipe';

@Controller('/step-schemas')
@UserAuthentication()
Expand All @@ -19,9 +20,9 @@ export class StepSchemasController {
@ExternalApiAccessible()
async getWorkflowStepSchema(
@UserSession() user: UserSessionData,
@Query('stepType') stepType: StepType,
@Query('workflowId') workflowId: string,
@Query('stepId') stepId: string
@Query('stepType') stepType?: StepType,
@Query('workflowId', ParseSlugIdPipe) workflowId?: string,
@Query('stepId', ParseSlugIdPipe) stepId?: string
): Promise<StepSchemaDto> {
return await this.getStepDefaultSchemaUsecase.execute(
createGetStepSchemaCommand(user, stepType, workflowId, stepId)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BadRequestException } from '@nestjs/common';
import { EnvironmentWithUserCommand } from '@novu/application-generic';
import { ActionStepEnum, ChannelStepEnum, StepType } from '@novu/framework';
import { UserSessionData } from '@novu/shared';
Expand Down Expand Up @@ -25,10 +26,10 @@ export type GetStepSchemaCommand = GetStepTypeSchemaCommand | GetExistingStepSch

export function createGetStepSchemaCommand(
user: UserSessionData,
stepType: StepType,
workflowId: string,
stepId: string
) {
stepType?: StepType,
workflowId?: string,
stepId?: string
): GetExistingStepSchemaCommand | GetStepTypeSchemaCommand {
if (workflowId && stepId) {
return GetExistingStepSchemaCommand.create({
organizationId: user.organizationId,
Expand All @@ -39,10 +40,14 @@ export function createGetStepSchemaCommand(
});
}

return GetStepTypeSchemaCommand.create({
organizationId: user.organizationId,
environmentId: user.environmentId,
userId: user._id,
stepType,
});
if (stepType) {
return GetStepTypeSchemaCommand.create({
organizationId: user.organizationId,
environmentId: user.environmentId,
userId: user._id,
stepType,
});
}

throw new BadRequestException('Invalid command, either workflowId and stepId or stepType is required');
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from './get-step-schema.command';
import { StepSchemaDto } from '../../dtos/step-schema.dto';
import { mapStepTypeToOutput, mapStepTypeToResult } from '../../shared';
import { encodeBase62 } from '../../../shared/helpers';

@Injectable()
export class GetStepSchema {
Expand Down Expand Up @@ -42,7 +43,7 @@ export class GetStepSchema {
if (!workflow) {
throw new BadRequestException({
message: 'No workflow found',
workflowId: command.workflowId,
workflowId: encodeBase62(command.workflowId),
});
}

Expand All @@ -51,8 +52,8 @@ export class GetStepSchema {
if (!currentStep) {
throw new BadRequestException({
message: 'No step found',
stepId: command.stepId,
workflowId: command.workflowId,
stepId: encodeBase62(command.stepId),
workflowId: encodeBase62(command.workflowId),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { ControlValuesEntity, NotificationStepEntity, NotificationTemplateEntity } from '@novu/dal';
import { GetPreferencesResponseDto } from '@novu/application-generic';
import { BadRequestException } from '@nestjs/common';
import { encodeBase62 } from '../../shared/helpers';

export function toResponseWorkflowDto(
template: NotificationTemplateEntity,
Expand All @@ -26,7 +27,7 @@ export function toResponseWorkflowDto(
};

return {
_id: template._id,
id: encodeBase62(template._id),
tags: template.tags,
active: template.active,
preferences: preferencesDto,
Expand Down Expand Up @@ -60,7 +61,7 @@ function toMinifiedWorkflowDto(template: NotificationTemplateEntity): WorkflowLi
return {
origin: template.origin || WorkflowOriginEnum.EXTERNAL,
type: template.type || ('MISSING-TYPE-ISSUE' as unknown as WorkflowTypeEnum),
_id: template._id,
id: encodeBase62(template._id),
name: template.name,
tags: template.tags,
updatedAt: template.updatedAt || 'Missing Updated At',
Expand All @@ -76,12 +77,14 @@ export function toWorkflowsMinifiedDtos(templates: NotificationTemplateEntity[])

function toStepResponseDto(step: NotificationStepEntity): StepResponseDto {
return {
id: encodeBase62(step._templateId),
// todo - tmp solution uncomment once pr 6689 is merged, remove any cast
// stepId: step.stepId || 'Missing Step Id',
name: step.name || 'Missing Name',
stepUuid: step._templateId,
type: step.template?.type || StepTypeEnum.EMAIL,
controls: convertControls(step),
controlValues: step.controlVariables || {},
};
} as any;
}

function convertControls(step: NotificationStepEntity): ControlsSchema {
Expand Down
40 changes: 40 additions & 0 deletions apps/api/src/app/workflows-v2/pipes/parse-request-workflow.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';

import { isStepCreateBody, isStepUpdateBody, UpsertStepBody, UpsertWorkflowBody, UpdateStepBody } from '@novu/shared';

import { parseSlugId } from './parse-slug-Id.pipe';

@Injectable()
export class ParseRequestWorkflowPipe implements PipeTransform<UpsertWorkflowBody> {
transform(value: UpsertWorkflowBody, metadata: ArgumentMetadata) {
if (!value) {
return value;
}

return decodeSteps(value);
}
}

function decodeSteps(value: UpsertWorkflowBody) {
const steps = value.steps.map((step: UpsertStepBody) => {
if (isStepCreateBody(step)) {
return step;
}

if (isStepUpdateBody(step)) {
const { id, ...rest } = step as UpdateStepBody;

return {
...rest,
_id: parseSlugId(id),
};
}

return step;
});

return {
...value,
steps,
};
}
4 changes: 4 additions & 0 deletions apps/api/src/app/workflows-v2/pipes/parse-slug-Id.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ function lookoutForId(value: string): string | null {
}

export function parseSlugId(value: string): InternalId {
if (!value) {
return value;
}

const validId = lookoutForId(value);
if (validId) {
return validId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { EnvironmentWithUserObjectCommand } from '@novu/application-generic';
import { CreateWorkflowDto, UpdateWorkflowDto } from '@novu/shared';
import { CreateWorkflowDto, IdentifierOrInternalId, UpsertWorkflowDto } from '@novu/shared';

export class UpsertWorkflowCommand extends EnvironmentWithUserObjectCommand {
identifierOrInternalId?: string;
identifierOrInternalId?: IdentifierOrInternalId;

workflowDto: CreateWorkflowDto | UpdateWorkflowDto;
workflowDto: CreateWorkflowDto | UpsertWorkflowDto;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
NotificationGroupRepository,
NotificationStepEntity,
NotificationTemplateEntity,
NotificationTemplateRepository,
PreferencesEntity,
} from '@novu/dal';
import {
Expand Down Expand Up @@ -287,14 +286,14 @@ export class UpsertWorkflowUseCase {
}

for (const persistedStep of persistedWorkflow.steps) {
if (this.isStepUpdateDto(stepUpdateRequest) && persistedStep._templateId === stepUpdateRequest.stepUuid) {
if (this.isStepUpdateDto(stepUpdateRequest) && persistedStep._templateId === stepUpdateRequest._id) {
return persistedStep;
}
}
}

private isStepUpdateDto(obj: StepDto): obj is StepUpdateDto {
return typeof obj === 'object' && obj !== null && 'stepUuid' in obj;
private isStepUpdateDto(obj: StepUpdateDto | StepCreateDto): obj is StepUpdateDto {
return typeof obj === 'object' && obj !== null && !!(obj as StepUpdateDto)._id;
}

private async getNotificationGroup(environmentId: string): Promise<string | undefined> {
Expand Down
Loading

0 comments on commit ff8388f

Please sign in to comment.