Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(api): wip provider smaller slug support #7029

Draft
wants to merge 1 commit into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
import { createHash } from 'crypto';

import { EnvironmentRepository, NotificationGroupRepository } from '@novu/dal';
import { encryptApiKey } from '@novu/application-generic';
import { encryptApiKey, generateUniqueId } from '@novu/application-generic';

import { CreateEnvironmentCommand } from './create-environment.command';
import { GenerateUniqueApiKey } from '../generate-unique-api-key/generate-unique-api-key.usecase';
Expand All @@ -22,10 +22,19 @@ export class CreateEnvironment {
const key = await this.generateUniqueApiKey.execute();
const encryptedApiKey = encryptApiKey(key);
const hashedApiKey = createHash('sha256').update(key).digest('hex');
const shortId = await generateUniqueId(
nanoid(6),
async (candidateId) => {
return (await this.environmentRepository.findOne({ shortId: candidateId })) !== null;
},
() => nanoid(6),
'ENVIRONMENT_SHORT_ID_GENERATION'
);

const environment = await this.environmentRepository.create({
_organizationId: command.organizationId,
name: command.name,
shortId,
identifier: nanoid(12),
_parentId: command.parentEnvironmentId,
apiKeys: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ShortIsPrefixEnum, EnvironmentEnum } from '@novu/shared';
import { GetMyEnvironmentsCommand } from './get-my-environments.command';
import { EnvironmentResponseDto } from '../../dtos/environment-response.dto';
import { buildSlug } from '../../../shared/helpers/build-slug';
import { encodeBase62 } from '../../../shared/helpers/base62';

@Injectable({
scope: Scope.REQUEST,
Expand Down Expand Up @@ -45,10 +46,11 @@ export class GetMyEnvironments {
});

const shortEnvName = shortenEnvironmentName(decryptedApiKeysEnvironment.name);
const shortId = decryptedApiKeysEnvironment.shortId || encodeBase62(decryptedApiKeysEnvironment._id);

return {
...decryptedApiKeysEnvironment,
slug: buildSlug(shortEnvName, ShortIsPrefixEnum.ENVIRONMENT, decryptedApiKeysEnvironment._id),
slug: buildSlug(shortEnvName, ShortIsPrefixEnum.ENVIRONMENT, shortId),
};
}
}
Expand Down
10 changes: 6 additions & 4 deletions apps/api/src/app/shared/helpers/build-slug.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { ShortIsPrefixEnum, Slug, slugify } from '@novu/shared';
import { encodeBase62 } from './base62';
import { Base62Id, ShortIsPrefixEnum, Slug, slugify } from '@novu/shared';

const SLUG_DELIMITER = '_';

/**
* Builds a slug for a step based on the step name, the short prefix and the internal ID.
* @returns The slug for the entity, example: slug: "workflow-name_wf_AbC1Xyz9KlmNOpQr"
*/
export function buildSlug(entityName: string, shortIsPrefix: ShortIsPrefixEnum, internalId: string): Slug {
return `${slugify(entityName)}${SLUG_DELIMITER}${shortIsPrefix}${encodeBase62(internalId)}`;
export function buildSlug(entityName: string, shortIsPrefix: ShortIsPrefixEnum, shortId: string): Slug {
const slugifiedEntityName = slugify(entityName);
const clientId: `${ShortIsPrefixEnum}${Base62Id}` = `${shortIsPrefix}${shortId}`;

return `${slugifiedEntityName}${SLUG_DELIMITER}${clientId}`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { NotificationStepEntity, NotificationTemplateEntity } from '@novu/dal';
import { GetPreferencesResponseDto } from '@novu/application-generic';
import { buildSlug } from '../../shared/helpers/build-slug';
import { encodeBase62 } from '../../shared/helpers/base62';

export function toResponseWorkflowDto(
template: NotificationTemplateEntity,
Expand All @@ -29,7 +30,7 @@ export function toResponseWorkflowDto(

return {
_id: template._id,
slug: buildSlug(workflowName, ShortIsPrefixEnum.WORKFLOW, template._id),
slug: buildSlug(workflowName, ShortIsPrefixEnum.WORKFLOW, buildShortId(template.shortId, template._id)),
workflowId: template.triggers[0].identifier,
name: workflowName,
tags: template.tags,
Expand Down Expand Up @@ -61,7 +62,7 @@ function toMinifiedWorkflowDto(template: NotificationTemplateEntity): WorkflowLi
return {
_id: template._id,
workflowId: template.triggers[0].identifier,
slug: buildSlug(workflowName, ShortIsPrefixEnum.WORKFLOW, template._id),
slug: buildSlug(workflowName, ShortIsPrefixEnum.WORKFLOW, buildShortId(template.shortId, template._id)),
name: workflowName,
origin: computeOrigin(template),
tags: template.tags,
Expand All @@ -81,14 +82,18 @@ function toStepResponseDto(persistedStep: NotificationStepEntity): StepResponseD

return {
_id: persistedStep._templateId,
slug: buildSlug(stepName, ShortIsPrefixEnum.STEP, persistedStep._templateId),
slug: buildSlug(stepName, ShortIsPrefixEnum.STEP, buildShortId(persistedStep.shortId, persistedStep._templateId)),
name: stepName,
stepId: persistedStep.stepId || 'Missing Step Id',
type: persistedStep.template?.type || StepTypeEnum.EMAIL,
issues: persistedStep.issues,
} satisfies StepResponseDto;
}

function buildShortId(shortId: string | undefined, internalId: string) {
return shortId || encodeBase62(internalId);
}

function buildStepTypeOverview(step: NotificationStepEntity): StepTypeEnum | undefined {
return step.template?.type;
}
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/app/workflows-v2/pipes/parse-slug-env-id.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
import { UserSessionData } from '@novu/shared';
import { isValidShortId } from '@novu/application-generic';
import { EnvironmentRepository } from '@novu/dal';
import { parseSlugId } from './parse-slug-id';

@Injectable()
Expand Down
36 changes: 30 additions & 6 deletions apps/api/src/app/workflows-v2/pipes/parse-slug-id.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { BaseRepository } from '@novu/dal';
import { ShortIsPrefixEnum } from '@novu/shared';
import { isValidShortId } from '@novu/application-generic';
import { decodeBase62 } from '../../shared/helpers';

export type InternalId = string;
Expand All @@ -18,10 +20,6 @@ function lookoutForId(value: string): string | null {
return value;
}

if (isWorkflowId(value)) {
return value;
}

return null;
}

Expand All @@ -31,14 +29,26 @@ export function parseSlugId(value: string): InternalId {
}

const validId = lookoutForId(value);
// if contains only a internal id or a workflow id
if (validId) {
return validId;
}

const encodedValue = value.slice(-ENCODED_ID_LENGTH);
// result is emhJxofizJJEcIN3 / h2khu3
const prefixedId = extractPrefixedId(value);

// only case is that this is a workflow id
if (!prefixedId) {
return value;
}

if (isValidShortId(prefixedId, 6)) {
return prefixedId;
}

let decodedValue: string;
try {
decodedValue = decodeBase62(encodedValue);
decodedValue = decodeBase62(prefixedId);
} catch (error) {
return value;
}
Expand All @@ -49,3 +59,17 @@ export function parseSlugId(value: string): InternalId {

return value;
}

function extractPrefixedId(value: string): string | null {
const prefixes = Object.values(ShortIsPrefixEnum);
let id: string | null = null;
for (const prefix of prefixes) {
const parts = value.split(`_${prefix}`);
if (parts.length > 1) {
id = parts[parts.length - 1];
break;
}
}

return id;
}
Loading
Loading