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

feat(api): Add automatic quota throttling #5485

Merged
merged 40 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
f508668
fix(web): Rename Billing plans to Billing
rifont May 2, 2024
fc456ba
refactor(shared): Tidy up builders and add comments
rifont May 2, 2024
c4c9323
chore(shared): Add feature flags for quota limiting
rifont May 2, 2024
322e892
test(shared): Update key builder test
rifont May 2, 2024
caeaac1
fix(api): Use new ff key
rifont May 2, 2024
90bf53d
chore(shared): Remove uncessary out-of-line exports
rifont May 2, 2024
34b47a8
chore(app-gen): Fix typo
rifont May 2, 2024
f295e9e
refactor(app-gen): Separate file for key builder identifiers
rifont May 2, 2024
5f64d9f
chore: Update per PR comments
rifont May 6, 2024
8caabdb
fix: App gen imports
rifont May 7, 2024
8af48e0
fix: testing controller import
rifont May 7, 2024
61f823f
fix: imports
rifont May 7, 2024
3d30dfd
fix: naming
rifont May 13, 2024
2ff0773
chore: Upgrade to Redlock with typescript
rifont May 13, 2024
76d0395
chore: Remove unused decorator
rifont May 13, 2024
793f4d9
feat: Add locking to cached entity interceptor
rifont May 13, 2024
ff336bb
test: fix create-usage-records test
rifont May 13, 2024
9044b66
test: fix customer subscription updated test
rifont May 13, 2024
1ed11f8
test(api): add evaluate-event-resource-limit tests
rifont May 13, 2024
9326c64
chore: remove redundant resource guard
rifont May 13, 2024
1b58c90
feat(api): Add safe EE import for quota throttler
rifont May 13, 2024
0d6bfdf
chore: Add nestjs imports for billing
rifont May 13, 2024
9daeb62
test(api): Add ci ee flag to api ee tests
rifont May 13, 2024
19ac014
test(api): Use enums for test
rifont May 13, 2024
29e1cf8
test(api): Add quota throttler guard test
rifont May 13, 2024
9af0be5
chore: update submodule
rifont May 13, 2024
1729934
revert(api): Accidental package script change
rifont May 13, 2024
9483c1a
chore: Update submodule
rifont May 13, 2024
3c98b6c
chore: submodule
rifont May 13, 2024
97f64cb
chore: submodule update
rifont May 13, 2024
e0c4cee
chore: update submodule
rifont May 14, 2024
0e97783
Merge branch 'next' into ent-5-api-ingress-blocking-cache-keys
rifont May 14, 2024
0fb97d1
test(web): Update cypress tests
rifont May 14, 2024
f416326
chore: update submodule
rifont May 14, 2024
3371125
fix(billing): Add mongoose dependency
rifont May 14, 2024
76df417
test(web): Update billing widget assertion
rifont May 14, 2024
57c7eeb
fix(app-gen): Separate method resolution and cache set
rifont May 15, 2024
361a8cb
chore: update submodule
rifont May 15, 2024
7327bb4
fix(app-gen): unlock only on error
rifont May 15, 2024
b016f39
Merge branch 'next' into ent-5-api-ingress-blocking-cache-keys
rifont May 15, 2024
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
2 changes: 1 addition & 1 deletion .source
4 changes: 2 additions & 2 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"lint:openapi": "spectral lint http://127.0.0.1:${PORT:-3000}/openapi.yaml",
"test": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' TZ=UTC NODE_ENV=test E2E_RUNNER=true mocha --require ts-node/register --exit --file e2e/setup.ts src/**/**/*.spec.ts",
"test:e2e": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' TZ=UTC NODE_ENV=test E2E_RUNNER=true mocha --require ts-node/register --exit --file e2e/setup.ts src/**/*.e2e.ts",
"test:e2e:ee": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' TZ=UTC NODE_ENV=test E2E_RUNNER=true mocha --require ts-node/register --exit --file e2e/setup.ts src/**/*.e2e-ee.ts",
"test:e2e:ee": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' TZ=UTC NODE_ENV=test E2E_RUNNER=true CI_EE_TEST=true mocha --require ts-node/register --exit --file e2e/setup.ts src/**/*.e2e-ee.ts",
"migration": "cross-env NODE_ENV=local MIGRATION=true ts-node --transpileOnly",
"link:submodules": "pnpm link ../../enterprise/packages/auth && pnpm link ../../enterprise/packages/translation && pnpm link ../../enterprise/packages/billing",
"admin:remove-user-account": "cross-env NODE_ENV=local MIGRATION=true ts-node --transpileOnly ./admin/remove-user-account.ts",
Expand Down Expand Up @@ -81,7 +81,7 @@
"passport-jwt": "^4.0.0",
"passport-oauth2": "^1.6.1",
"recursive-diff": "^1.0.8",
"redlock": "4.2.0",
"redlock": "5.0.0-beta.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "7.8.1",
Expand Down
17 changes: 12 additions & 5 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import { WorkflowOverridesModule } from './app/workflow-overrides/workflow-overr
import { ApiRateLimitInterceptor } from './app/rate-limiting/guards';
import { RateLimitingModule } from './app/rate-limiting/rate-limiting.module';
import { ProductFeatureInterceptor } from './app/shared/interceptors/product-feature.interceptor';
import { ResourceThrottlerInterceptor } from './app/resource-limiting/guards';
import { AnalyticsModule } from './app/analytics/analytics.module';

const enterpriseImports = (): Array<Type | DynamicModule | Promise<DynamicModule> | ForwardReference> => {
Expand All @@ -60,6 +59,17 @@ const enterpriseImports = (): Array<Type | DynamicModule | Promise<DynamicModule
return modules;
};

const enterpriseQuotaThrottlerInterceptor =
(process.env.NOVU_ENTERPRISE === 'true' || process.env.CI_EE_TEST === 'true') &&
require('@novu/ee-billing')?.QuotaThrottlerInterceptor
? [
{
provide: APP_INTERCEPTOR,
useClass: require('@novu/ee-billing')?.QuotaThrottlerInterceptor,
},
]
: [];

const baseModules: Array<Type | DynamicModule | Promise<DynamicModule> | ForwardReference> = [
InboundParseModule,
OrganizationModule,
Expand Down Expand Up @@ -106,10 +116,7 @@ const providers: Provider[] = [
provide: APP_INTERCEPTOR,
useClass: ProductFeatureInterceptor,
},
{
provide: APP_INTERCEPTOR,
useClass: ResourceThrottlerInterceptor,
},
...enterpriseQuotaThrottlerInterceptor,
{
provide: APP_INTERCEPTOR,
useClass: IdempotencyInterceptor,
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/app/events/events.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ResourceEnum,
TriggerRequestCategoryEnum,
} from '@novu/shared';
import { ResourceCategory } from '@novu/application-generic';

import {
BulkTriggerEventDto,
Expand All @@ -29,7 +30,6 @@ import { UserAuthGuard } from '../auth/framework/user.auth.guard';
import { ApiCommonResponses, ApiResponse, ApiOkResponse } from '../shared/framework/response.decorator';
import { DataBooleanDto } from '../shared/dtos/data-wrapper-dto';
import { ThrottlerCategory, ThrottlerCost } from '../rate-limiting/guards';
import { ResourceCategory } from '../resource-limiting/guards';

@ThrottlerCategory(ApiRateLimitCategoryEnum.TRIGGER)
@ResourceCategory(ResourceEnum.EVENTS)
Expand Down
2 changes: 0 additions & 2 deletions apps/api/src/app/resource-limiting/guards/index.ts

This file was deleted.

This file was deleted.

This file was deleted.

11 changes: 0 additions & 11 deletions apps/api/src/app/resource-limiting/resource-limiting.module.ts

This file was deleted.

3 changes: 0 additions & 3 deletions apps/api/src/app/resource-limiting/usecases/index.ts

This file was deleted.

18 changes: 17 additions & 1 deletion apps/api/src/app/testing/billing/create-usage-records.e2e-ee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,22 @@ describe('CreateUsageRecords', () => {
notificationsCount: 100,
},
]);
const mockNoMeteredSubscription = {
id: 'subscription_id',
items: {
data: [
{
id: 'item_id_flat',
price: { lookup_key: 'business_flat_monthly', recurring: { usage_type: StripeUsageTypeEnum.LICENSED } },
},
],
},
};
getCustomerStub.resolves({
subscriptions: {
data: [mockNoMeteredSubscription],
},
});
const useCase = createUseCase();

await useCase.execute(
Expand All @@ -246,7 +262,7 @@ describe('CreateUsageRecords', () => {
);

expect(logStub.lastCall.args[0].message).to.equal(
"Subscription item not found for subscriptionId: 'subscription_id' and price lookup key: 'free_usage_notifications'"
"No metered subscription found for organizationId: 'organization_id_1'"
);

logStub.restore();
Expand Down
Loading
Loading