Skip to content

Commit

Permalink
feat(api): add missing files
Browse files Browse the repository at this point in the history
  • Loading branch information
djabarovgeorge committed Nov 19, 2024
1 parent 4a3d9da commit 7e2d344
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ValidatedPlaceholderAggregation, ValidatePlaceholderUsecase } from '../
import { CollectPlaceholderWithDefaultsUsecase, PlaceholderAggregation } from '../collect-placeholders';
import { ExtractDefaultValuesFromSchemaUsecase } from '../../extract-default-values-from-schema';
import { ValidatedContentResponse } from './validated-content.response';
import { ValidateControlByTierUsecase } from '../validateControlByTier/validate-control-by-tier.usecase';
import { ValidateControlByTierUsecase } from '../validate-control-by-tier/validate-control-by-tier.usecase';

/**
* Validates and prepares workflow step content by collecting placeholders,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { EnvironmentWithUserObjectCommand } from '@novu/application-generic';
import { StepTypeEnum } from '@novu/shared/';

export class ValidateControlByTierCommand extends EnvironmentWithUserObjectCommand {
controlValues: Record<string, unknown>;
stepType?: StepTypeEnum;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Injectable } from '@nestjs/common';
import { OrganizationEntity, OrganizationRepository } from '@novu/dal';
import { ApiServiceLevelEnum, ContentIssue, DigestUnitEnum, StepContentIssueEnum, StepTypeEnum } from '@novu/shared';
import { ValidateControlByTierCommand } from './validate-control-by-tier.command';

const MAX_DELAY_DAYS_FREE_TIER = 30;
const MAX_DELAY_DAYS_BUSINESS_TIER = 90;

type controlKey = string;
type ValidateControlByTierUsecaseResponse = Record<controlKey, ContentIssue[]> | {};

@Injectable()
export class ValidateControlByTierUsecase {
constructor(private readonly organizationRepository: OrganizationRepository) {}

async execute(command: ValidateControlByTierCommand): Promise<ValidateControlByTierUsecaseResponse> {
return this.validateControlValuesByTierLimits(command.controlValues, command.user.organizationId, command.stepType);
}

private async validateControlValuesByTierLimits(
defaultControlValues: Record<string, unknown>,
organizationId: string,
stepType?: StepTypeEnum
): Promise<ValidateControlByTierUsecaseResponse> {
const controlValueNeedTierValidation =
stepType === StepTypeEnum.DIGEST || stepType === StepTypeEnum.DELAY || stepType === StepTypeEnum.IN_APP;

if (!controlValueNeedTierValidation) {
return {};
}

const issues: Record<string, ContentIssue[]> = {};
let organization: OrganizationEntity | null = null;
const controlValues = defaultControlValues;
if (
controlValues.unit &&
controlValues.amount &&
this.isNumber(controlValues.amount) &&
this.isValidDigestUnit(controlValues.unit)
) {
organization = await this.getOrganization(organization, organizationId);

const delayInDays = this.calculateDaysFromUnit(controlValues.amount, controlValues.unit);

const tier = organization?.apiServiceLevel;
if (tier === undefined || tier === ApiServiceLevelEnum.BUSINESS || tier === ApiServiceLevelEnum.ENTERPRISE) {
if (delayInDays > MAX_DELAY_DAYS_BUSINESS_TIER) {
issues.tier = [
...(issues.tier || []),
{
issueType: StepContentIssueEnum.TIER_LIMIT_EXCEEDED,
message:
`The maximum delay allowed is ${MAX_DELAY_DAYS_BUSINESS_TIER} days.` +
'Please contact our support team to discuss extending this limit for your use case.',
},
];
}
}

if (tier === ApiServiceLevelEnum.FREE) {
if (delayInDays > MAX_DELAY_DAYS_FREE_TIER) {
issues.tier = [
...(issues.tier || []),
{
issueType: StepContentIssueEnum.TIER_LIMIT_EXCEEDED,
message:
`The maximum delay allowed is ${MAX_DELAY_DAYS_FREE_TIER} days.` +
'Please contact our support team to discuss extending this limit for your use case.',
},
];
}
}
}

return issues;
}
private isValidDigestUnit(unit: unknown): unit is DigestUnitEnum {
return Object.values(DigestUnitEnum).includes(unit as DigestUnitEnum);
}

private isNumber(value: unknown): value is number {
return !Number.isNaN(Number(value));
}

private calculateDaysFromUnit(amount: number, unit: DigestUnitEnum): number {
switch (unit) {
case DigestUnitEnum.SECONDS:
return amount / (24 * 60 * 60);
case DigestUnitEnum.MINUTES:
return amount / (24 * 60);
case DigestUnitEnum.HOURS:
return amount / 24;
case DigestUnitEnum.DAYS:
return amount;
case DigestUnitEnum.WEEKS:
return amount * 7;
case DigestUnitEnum.MONTHS:
return amount * 30; // Using 30 days as an approximation for a month
default:
return 0;
}
}

private async getOrganization(
organization: OrganizationEntity | null,
organizationId: string
): Promise<OrganizationEntity | null> {
if (organization === null) {
return await this.organizationRepository.findById(organizationId);
}

return organization;
}
}
2 changes: 1 addition & 1 deletion apps/api/src/app/workflows-v2/workflow.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { HydrateEmailSchemaUseCase } from '../environments-v1/usecases/output-re
import { OverloadContentDataOnWorkflowUseCase } from './usecases/overload-content-data';
import { PatchWorkflowUsecase } from './usecases/patch-workflow';
import { PatchStepUsecase } from './usecases/patch-step-data/patch-step.usecase';
import { ValidateControlByTierUsecase } from './usecases/validate-content/validateControlByTier/validate-control-by-tier.usecase';
import { ValidateControlByTierUsecase } from './usecases/validate-content/validate-control-by-tier/validate-control-by-tier.usecase';

@Module({
imports: [SharedModule, MessageTemplateModule, ChangeModule, AuthModule, BridgeModule, IntegrationModule],
Expand Down

0 comments on commit 7e2d344

Please sign in to comment.