Skip to content

Commit

Permalink
refactor(api-service): use new ff flow
Browse files Browse the repository at this point in the history
  • Loading branch information
djabarovgeorge committed Feb 13, 2025
1 parent 1d4ab84 commit cf785ca
Show file tree
Hide file tree
Showing 15 changed files with 223 additions and 124 deletions.
2 changes: 1 addition & 1 deletion .source
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { IntegrationRepository } from '@novu/dal';
import { EnvironmentEntity, IntegrationRepository, OrganizationEntity, UserEntity } from '@novu/dal';
import {
areNovuEmailCredentialsSet,
areNovuSmsCredentialsSet,
Expand Down Expand Up @@ -112,9 +112,9 @@ export class CreateNovuIntegrations {
if (inAppIntegrationCount === 0) {
const isV2Enabled = await this.getFeatureFlag.execute(
GetFeatureFlagCommand.create({
userId: command.userId,
environmentId: command.environmentId,
organizationId: command.organizationId,
user: { _id: command.userId } as UserEntity,
environment: { _id: command.environmentId } as EnvironmentEntity,
organization: { _id: command.organizationId } as OrganizationEntity,
key: FeatureFlagsKeysEnum.IS_V2_ENABLED,
})
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { BadRequestException, ConflictException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { CommunityOrganizationRepository, IntegrationEntity, IntegrationRepository } from '@novu/dal';
import {
CommunityOrganizationRepository,
EnvironmentEntity,
IntegrationEntity,
IntegrationRepository,
OrganizationEntity,
UserEntity,
} from '@novu/dal';
import {
AnalyticsService,
buildIntegrationKey,
Expand Down Expand Up @@ -150,9 +157,7 @@ export class UpdateIntegration {

const isInvalidationDisabled = await this.getFeatureFlag.execute(
GetFeatureFlagCommand.create({
userId: 'system',
environmentId: 'system',
organizationId: command.organizationId,
organization: { _id: command.organizationId } as OrganizationEntity,
key: FeatureFlagsKeysEnum.IS_INTEGRATION_INVALIDATION_DISABLED,
})
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { MessageEntity, MessageRepository, SubscriberEntity } from '@novu/dal';
import { MessageEntity, MessageRepository, OrganizationEntity, SubscriberEntity } from '@novu/dal';
import { ActorTypeEnum, FeatureFlagsKeysEnum } from '@novu/shared';

import { GetFeatureFlag, GetFeatureFlagCommand } from '@novu/application-generic';
Expand Down Expand Up @@ -62,9 +62,7 @@ export class GetMessages {
const isEnabled = await this.getFeatureFlag.execute(
GetFeatureFlagCommand.create({
key: FeatureFlagsKeysEnum.IS_NEW_MESSAGES_API_RESPONSE_ENABLED,
organizationId: command.organizationId,
userId: 'system',
environmentId: 'system',
organization: { _id: command.organizationId } as OrganizationEntity,
})
);

Expand Down
15 changes: 8 additions & 7 deletions apps/api/src/app/rate-limiting/guards/throttler.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ import {
FeatureFlagsKeysEnum,
UserSessionData,
} from '@novu/shared';
import { EvaluateApiRateLimit, EvaluateApiRateLimitCommand } from '../usecases/evaluate-api-rate-limit';
import { UserEntity, OrganizationEntity, EnvironmentEntity } from '@novu/dal';
import { ThrottlerCategory, ThrottlerCost } from './throttler.decorator';
import { EvaluateApiRateLimit, EvaluateApiRateLimitCommand } from '../usecases/evaluate-api-rate-limit';

export const THROTTLED_EXCEPTION_MESSAGE = 'API rate limit exceeded';
export const ALLOWED_AUTH_SCHEMES = [ApiAuthSchemeEnum.API_KEY];
Expand Down Expand Up @@ -74,9 +75,9 @@ export class ApiRateLimitInterceptor extends ThrottlerGuard implements NestInter

const isEnabled = await this.getFeatureFlag.execute(
GetFeatureFlagCommand.create({
environmentId,
organizationId,
userId: _id,
environment: { _id: environmentId } as EnvironmentEntity,
organization: { _id: organizationId } as OrganizationEntity,
user: { _id } as UserEntity,
key: FeatureFlagsKeysEnum.IS_API_RATE_LIMITING_ENABLED,
})
);
Expand Down Expand Up @@ -129,9 +130,9 @@ export class ApiRateLimitInterceptor extends ThrottlerGuard implements NestInter
*/
const isDryRun = await this.getFeatureFlag.execute(
GetFeatureFlagCommand.create({
environmentId,
organizationId,
userId: _id,
environment: { _id: environmentId } as EnvironmentEntity,
organization: { _id: organizationId } as OrganizationEntity,
user: { _id } as UserEntity,
key: FeatureFlagsKeysEnum.IS_API_RATE_LIMITING_DRY_RUN_ENABLED,
})
);
Expand Down
7 changes: 4 additions & 3 deletions apps/api/src/app/shared/framework/idempotency.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Observable, of, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { createHash } from 'crypto';
import { ApiAuthSchemeEnum, FeatureFlagsKeysEnum, UserSessionData } from '@novu/shared';
import { EnvironmentEntity, OrganizationEntity, UserEntity } from '@novu/dal';

const LOG_CONTEXT = 'IdempotencyInterceptor';
const IDEMPOTENCY_CACHE_TTL = 60 * 60 * 24; // 24h
Expand Down Expand Up @@ -56,9 +57,9 @@ export class IdempotencyInterceptor implements NestInterceptor {
return await this.getFeatureFlag.execute(
GetFeatureFlagCommand.create({
key: FeatureFlagsKeysEnum.IS_API_IDEMPOTENCY_ENABLED,
environmentId,
organizationId,
userId: _id,
environment: { _id: environmentId } as EnvironmentEntity,
organization: { _id: organizationId } as OrganizationEntity,
user: { _id } as UserEntity,
})
);
}
Expand Down
7 changes: 4 additions & 3 deletions apps/api/src/app/tenant/tenant.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
UpdateTenantCommand,
} from '@novu/application-generic';
import { ApiExcludeController } from '@nestjs/swagger/dist/decorators/api-exclude-controller.decorator';
import { EnvironmentEntity, OrganizationEntity, UserEntity } from '@novu/dal';
import { UserSession } from '../shared/framework/user.decorator';
import { ExternalApiAccessible } from '../auth/framework/external-api.decorator';
import {
Expand Down Expand Up @@ -214,9 +215,9 @@ export class TenantController {
private async verifyTenantsApiAvailability(user: UserSessionData) {
const isV2Enabled = await this.getFeatureFlag.execute(
GetFeatureFlagCommand.create({
userId: user._id,
environmentId: user.environmentId,
organizationId: user.organizationId,
user: { _id: user._id } as UserEntity,
environment: { _id: user.environmentId } as EnvironmentEntity,
organization: { _id: user.organizationId } as OrganizationEntity,
key: FeatureFlagsKeysEnum.IS_V2_ENABLED,
})
);
Expand Down
14 changes: 14 additions & 0 deletions libs/application-generic/src/services/feature-flags.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
IFeatureFlagsService,
IContextualFeatureFlag,
IGlobalFeatureFlag,
GetFlagData,
} from './types';

const LOG_CONTEXT = 'FeatureFlagsService';
Expand Down Expand Up @@ -71,6 +72,10 @@ export class FeatureFlagsService {
}
}

/**
* @deprecated This method is deprecated.
* Please use the more flexible `getFlag()` method instead, with the context data.
*/
public async getWithContext<T>(
contextualFeatureFlag: IContextualFeatureFlag<T>,
): Promise<T> {
Expand All @@ -86,10 +91,19 @@ export class FeatureFlagsService {
return await this.get(key, defaultValue, context);
}

public async getFlag<Result>(
getFlagData: GetFlagData<Result>,
): Promise<Result> {
return await this.service.getFlag<Result>(getFlagData);
}

/**
* When we want to retrieve a global feature flag that shouldn't be dependant on any context
* we will use this functionality. Helpful for setting feature flags that discriminate
* the Novu Cloud service offerings with the self hosted users.
*
* @deprecated This method is deprecated.
* Please use the more flexible `getFlag()` method instead, with the context data.
*/
public async getGlobal<T>(
globalFeatureFlag: IGlobalFeatureFlag<T>,
Expand Down
75 changes: 38 additions & 37 deletions libs/application-generic/src/services/launch-darkly.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import {
init,
LDClient,
LDMultiKindContext,
LDSingleKindContext,
} from 'launchdarkly-node-server-sdk';
import { Injectable, Logger } from '@nestjs/common';

import {
prepareBooleanStringFeatureFlag,
prepareNumberStringFeatureFlag,
} from '@novu/shared';
import {
EnvironmentId,
FeatureFlagContext,
Expand Down Expand Up @@ -82,6 +79,10 @@ export class LaunchDarklyService implements IFeatureFlagsService {
return result;
}

/**
* @deprecated This method is deprecated.
* Please use the more flexible `getFlag()` method instead, with the context data.
*/
public async getWithEnvironmentContext<T>(
key: FeatureFlagsKeysEnum,
defaultValue: T,
Expand All @@ -92,6 +93,10 @@ export class LaunchDarklyService implements IFeatureFlagsService {
return await this.get(key, context, defaultValue);
}

/**
* @deprecated This method is deprecated.
* Please use the more flexible `getFlag()` method instead, with the context data.
*/
public async getWithOrganizationContext<T>(
key: FeatureFlagsKeysEnum,
defaultValue: T,
Expand All @@ -102,6 +107,10 @@ export class LaunchDarklyService implements IFeatureFlagsService {
return await this.get(key, context, defaultValue);
}

/**
* @deprecated This method is deprecated.
* Please use the more flexible `getFlag()` method instead, with the context data.
*/
public async getWithUserContext<T>(
key: FeatureFlagsKeysEnum,
defaultValue: T,
Expand All @@ -112,46 +121,40 @@ export class LaunchDarklyService implements IFeatureFlagsService {
return await this.get(key, context, defaultValue);
}

public async getWithFullContext<T>({
public async getFlag<T>({
key,
defaultValue,
contextKey,
contextId,
fallbackToDefault,
attributes,
environment,
organization,
user,
anonymous,
}: FeatureFlagContext<T>): Promise<T> {
const value = process.env[key];

let parsedDefaultValue: T = defaultValue;
if (typeof defaultValue === 'boolean') {
parsedDefaultValue = prepareBooleanStringFeatureFlag(
value,
defaultValue,
) as T;
const mappedContext: LDMultiKindContext = {
kind: 'multi',
};

if (environment) {
mappedContext.environment = {
...environment,
key: environment._id,
};
}

if (typeof defaultValue === 'number') {
parsedDefaultValue = prepareNumberStringFeatureFlag(
value,
defaultValue,
) as T;
if (organization) {
mappedContext.organization = {
...organization,
key: organization._id,
};
}

const result = await this.client.variation(
key,
{
...attributes,
kind: contextKey,
key: contextId,
},
parsedDefaultValue,
);

if (result === fallbackToDefault) {
return defaultValue;
if (user) {
mappedContext.user = {
...user,
key: user._id,
};
}

return result;
return await this.client.variation(key, mappedContext, defaultValue);
}

public async gracefullyShutdown(): Promise<void> {
Expand All @@ -174,7 +177,6 @@ export class LaunchDarklyService implements IFeatureFlagsService {
}
}

// TODO: Unused for now.
private mapToEnvironmentContext(
environmentId: EnvironmentId,
): LDSingleKindContext {
Expand All @@ -186,7 +188,6 @@ export class LaunchDarklyService implements IFeatureFlagsService {
return launchDarklyContext;
}

// TODO: Unused for now
private mapToOrganizationContext(
organizationId: OrganizationId,
): LDSingleKindContext {
Expand Down
Loading

0 comments on commit cf785ca

Please sign in to comment.