feat(api-service): context bound preferences (admin facing API) fixes NV-6974#9821
feat(api-service): context bound preferences (admin facing API) fixes NV-6974#9821
Conversation
✅ Deploy Preview for dashboard-v2-novu-staging canceled.
|
WalkthroughThis pull request implements context-aware subscriber preferences functionality across the API and SDK. Changes include adding optional context fields to preference-related DTOs and commands, introducing ContextRepository and FeatureFlagsService dependencies for context handling, and updating database indexes to support contextKeys. The e2e test script is narrowed to subscriber-v2 tests, and new test cases validate context-based filtering and isolation. Multiple SDK functions are refactored to accept request objects instead of individual parameters, and spell-check ignore patterns are broadened. Database schema indexes are modified to include contextKeys for unique constraint enforcement. 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @apps/api/package.json:
- Line 33: The npm script "test:e2e:novu-v2" narrows the test glob to
"src/**/subscribers-v2/e2e/**/*.e2e{,-ee}.ts" which excludes many #novu-v2
tests; update that script to use the broader glob "src/**/*.e2e{,-ee}.ts"
(keeping the existing flags like --grep '#novu-v2', NODE_OPTIONS, and other env
vars) so all v2-tagged end-to-end tests (workflows-v2, topics-v2, layouts-v2,
contexts, environments-v2, etc.) are included when running the script.
In @libs/internal-sdk/src/funcs/subscribersPreferencesList.ts:
- Around line 126-137: The fallback "|| { strategy: 'none' }" is unreachable
because the object literal before it is always truthy; remove that final
fallback or replace the chained ORs with nullish coalescing to preserve intended
semantics. Update the expression that sets retryConfig (the piece using
options?.retries || client._options.retryConfig || { strategy: 'backoff', ... }
|| { strategy: 'none' }) to either drop the trailing "|| { strategy: 'none' }"
or use ?? between operands (options?.retries ?? client._options.retryConfig ?? {
strategy: 'backoff', ... }) so the default backoff object is only used when
prior values are null/undefined; apply the same fix in other generated files
with the same pattern (notificationsList.ts, activityWorkflowRunsList.ts).
🧹 Nitpick comments (5)
apps/api/src/app/subscribers-v2/e2e/patch-subscriber-preferences.e2e.ts (1)
268-282: Consider verifying context persistence.The test creates a preference with context but doesn't verify the context was actually stored. Consider adding a
listcall withcontextKeysto confirm persistence, similar to the pattern used in the bulk update test (lines 356-360).💡 Suggested verification
expect(response.result.workflows).to.have.lengthOf(1); expect(response.result.workflows[0].channels).to.deep.equal({ inApp: true, email: false }); + + // Verify context was persisted + const listResponse = await novuClient.subscribers.preferences.list({ + subscriberId: subscriber.subscriberId, + contextKeys: ['tenant:acme'], + }); + expect(listResponse.result.workflows[0].channels.email).to.equal(false); });.cspell.json (1)
854-855: Verify intent of broadened spell-check ignore patterns.Replacing specific file ignores with
**/*.e2e.tsand**/*.spec.tsexcludes all e2e and spec files repository-wide from spell checking. While this reduces noise from test-specific identifiers and mock data, it may also hide legitimate spelling errors in user-facing test descriptions or error messages.apps/api/src/app/subscribers-v2/e2e/get-subscriber-preferences.e2e.ts (1)
17-17: Consider using a test utility for feature flag management.Directly manipulating
process.envwith type assertion is fragile and could leak state between tests if an exception occurs beforeafterEachruns. Consider wrapping this in a try/finally pattern or using a dedicated test helper for feature flag mocking.♻️ Suggested improvement
+ let originalEnvValue: string | undefined; + beforeEach(async () => { - (process.env as any).IS_CONTEXT_PREFERENCES_ENABLED = 'true'; + originalEnvValue = process.env.IS_CONTEXT_PREFERENCES_ENABLED; + process.env.IS_CONTEXT_PREFERENCES_ENABLED = 'true'; const uuid = randomBytes(4).toString('hex'); // ... }); afterEach(() => { - delete (process.env as any).IS_CONTEXT_PREFERENCES_ENABLED; + if (originalEnvValue === undefined) { + delete process.env.IS_CONTEXT_PREFERENCES_ENABLED; + } else { + process.env.IS_CONTEXT_PREFERENCES_ENABLED = originalEnvValue; + } });Also applies to: 28-30
apps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.usecase.ts (2)
140-167: Consider extractingresolveContextsto a shared service.This method appears to duplicate logic that exists in
UpdateSubscriberPreferences(per the AI summary). Extracting this to a shared service would reduce duplication and ensure consistent behavior across use cases.#!/bin/bash # Verify if similar resolveContexts logic exists elsewhere echo "=== Searching for similar resolveContexts implementations ===" ast-grep --pattern $'async resolveContexts($$$) { $$$ }'
146-150: Consider aligning feature flag call signature with other usages.The feature flag check here only passes
organizationId, but other usages in the codebase (e.g., inidempotency.interceptor.ts) includeenvironmentIdanduser._idfor more granular control. Consider whether environment-level or user-level feature gating is needed.♻️ Suggested improvement
const isEnabled = await this.featureFlagsService.getFlag({ key: FeatureFlagsKeysEnum.IS_CONTEXT_PREFERENCES_ENABLED, defaultValue: false, + environment: { _id: environmentId }, organization: { _id: organizationId }, });
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (28)
.cspell.jsonapps/api/package.jsonapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.command.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.spec.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.usecase.tsapps/api/src/app/subscribers-v2/dtos/bulk-update-subscriber-preferences.dto.tsapps/api/src/app/subscribers-v2/dtos/get-subscriber-preferences-request.dto.tsapps/api/src/app/subscribers-v2/dtos/patch-subscriber-preferences.dto.tsapps/api/src/app/subscribers-v2/e2e/get-subscriber-preferences.e2e.tsapps/api/src/app/subscribers-v2/e2e/patch-subscriber-preferences.e2e.tsapps/api/src/app/subscribers-v2/subscribers.controller.tsapps/api/src/app/subscribers-v2/subscribers.module.tsapps/api/src/app/subscribers-v2/usecases/get-subscriber-preferences/get-subscriber-preferences.usecase.tsapps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.command.tsapps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.usecase.tslibs/dal/src/repositories/preferences/preferences.schema.tslibs/internal-sdk/.speakeasy/gen.yamllibs/internal-sdk/src/funcs/subscribersPreferencesList.tslibs/internal-sdk/src/lib/config.tslibs/internal-sdk/src/lib/sdks.tslibs/internal-sdk/src/models/components/bulkupdatesubscriberpreferencesdto.tslibs/internal-sdk/src/models/components/createchannelconnectionrequestdto.tslibs/internal-sdk/src/models/components/patchsubscriberpreferencesdto.tslibs/internal-sdk/src/models/components/previewpayloaddto.tslibs/internal-sdk/src/models/operations/subscriberscontrollergetsubscriberpreferences.tslibs/internal-sdk/src/react-query/subscribersPreferencesList.core.tslibs/internal-sdk/src/react-query/subscribersPreferencesList.tslibs/internal-sdk/src/sdk/preferences.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write concise, technical TypeScript code with accurate examples
Use descriptive variable names with auxiliary verbs (isLoading, hasError)
Add blank lines before return statements
Import motion components from 'motion/react' instead of 'motion-react'
**/*.{ts,tsx}: Write concise, technical TypeScript code with accurate examples
Use functional and declarative programming patterns; avoid classes
Prefer iteration and modularization over code duplication, minimize code duplication as possible
Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
Structure files: exported component, subcomponents, helpers, static content, types
Don't leave comments in code, unless they explain something complex and not trivial
Don't use nested ternaries
Favor named exports for components
Use TypeScript for all code; prefer interfaces over types
In front end code, use types over interfaces
Use functional components with TypeScript types
Use the "function" keyword for pure functions
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements
Add blank lines before return statements
When importing "motion-react" package, import it from "motion/react"
Files:
apps/api/src/app/subscribers-v2/subscribers.module.tsapps/api/src/app/subscribers-v2/e2e/patch-subscriber-preferences.e2e.tsapps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.usecase.tsapps/api/src/app/subscribers-v2/subscribers.controller.tsapps/api/src/app/subscribers-v2/dtos/bulk-update-subscriber-preferences.dto.tslibs/internal-sdk/src/lib/sdks.tslibs/internal-sdk/src/models/components/patchsubscriberpreferencesdto.tsapps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.command.tslibs/internal-sdk/src/funcs/subscribersPreferencesList.tsapps/api/src/app/subscribers-v2/dtos/patch-subscriber-preferences.dto.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.usecase.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.command.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.spec.tsapps/api/src/app/subscribers-v2/e2e/get-subscriber-preferences.e2e.tsapps/api/src/app/subscribers-v2/dtos/get-subscriber-preferences-request.dto.tslibs/internal-sdk/src/react-query/subscribersPreferencesList.tsapps/api/src/app/subscribers-v2/usecases/get-subscriber-preferences/get-subscriber-preferences.usecase.tslibs/internal-sdk/src/models/components/bulkupdatesubscriberpreferencesdto.tslibs/internal-sdk/src/react-query/subscribersPreferencesList.core.tslibs/internal-sdk/src/lib/config.tslibs/internal-sdk/src/sdk/preferences.tslibs/internal-sdk/src/models/operations/subscriberscontrollergetsubscriberpreferences.tslibs/internal-sdk/src/models/components/previewpayloaddto.tslibs/internal-sdk/src/models/components/createchannelconnectionrequestdto.tslibs/dal/src/repositories/preferences/preferences.schema.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use functional and declarative programming patterns; avoid classes
Files:
apps/api/src/app/subscribers-v2/subscribers.module.tsapps/api/src/app/subscribers-v2/e2e/patch-subscriber-preferences.e2e.tsapps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.usecase.tsapps/api/src/app/subscribers-v2/subscribers.controller.tsapps/api/src/app/subscribers-v2/dtos/bulk-update-subscriber-preferences.dto.tslibs/internal-sdk/src/lib/sdks.tslibs/internal-sdk/src/models/components/patchsubscriberpreferencesdto.tsapps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.command.tslibs/internal-sdk/src/funcs/subscribersPreferencesList.tsapps/api/src/app/subscribers-v2/dtos/patch-subscriber-preferences.dto.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.usecase.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.command.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.spec.tsapps/api/src/app/subscribers-v2/e2e/get-subscriber-preferences.e2e.tsapps/api/src/app/subscribers-v2/dtos/get-subscriber-preferences-request.dto.tslibs/internal-sdk/src/react-query/subscribersPreferencesList.tsapps/api/src/app/subscribers-v2/usecases/get-subscriber-preferences/get-subscriber-preferences.usecase.tslibs/internal-sdk/src/models/components/bulkupdatesubscriberpreferencesdto.tslibs/internal-sdk/src/react-query/subscribersPreferencesList.core.tslibs/internal-sdk/src/lib/config.tslibs/internal-sdk/src/sdk/preferences.tslibs/internal-sdk/src/models/operations/subscriberscontrollergetsubscriberpreferences.tslibs/internal-sdk/src/models/components/previewpayloaddto.tslibs/internal-sdk/src/models/components/createchannelconnectionrequestdto.tslibs/dal/src/repositories/preferences/preferences.schema.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
Favor named exports for components
Files:
apps/api/src/app/subscribers-v2/subscribers.module.tsapps/api/src/app/subscribers-v2/e2e/patch-subscriber-preferences.e2e.tsapps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.usecase.tsapps/api/src/app/subscribers-v2/subscribers.controller.tsapps/api/src/app/subscribers-v2/dtos/bulk-update-subscriber-preferences.dto.tslibs/internal-sdk/src/lib/sdks.tslibs/internal-sdk/src/models/components/patchsubscriberpreferencesdto.tsapps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.command.tslibs/internal-sdk/src/funcs/subscribersPreferencesList.tsapps/api/src/app/subscribers-v2/dtos/patch-subscriber-preferences.dto.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.usecase.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.command.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.spec.tsapps/api/src/app/subscribers-v2/e2e/get-subscriber-preferences.e2e.tsapps/api/src/app/subscribers-v2/dtos/get-subscriber-preferences-request.dto.tslibs/internal-sdk/src/react-query/subscribersPreferencesList.tsapps/api/src/app/subscribers-v2/usecases/get-subscriber-preferences/get-subscriber-preferences.usecase.tslibs/internal-sdk/src/models/components/bulkupdatesubscriberpreferencesdto.tslibs/internal-sdk/src/react-query/subscribersPreferencesList.core.tslibs/internal-sdk/src/lib/config.tslibs/internal-sdk/src/sdk/preferences.tslibs/internal-sdk/src/models/operations/subscriberscontrollergetsubscriberpreferences.tslibs/internal-sdk/src/models/components/previewpayloaddto.tslibs/internal-sdk/src/models/components/createchannelconnectionrequestdto.tslibs/dal/src/repositories/preferences/preferences.schema.ts
apps/api/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Prefer interfaces over types in backend code
Files:
apps/api/src/app/subscribers-v2/subscribers.module.tsapps/api/src/app/subscribers-v2/e2e/patch-subscriber-preferences.e2e.tsapps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.usecase.tsapps/api/src/app/subscribers-v2/subscribers.controller.tsapps/api/src/app/subscribers-v2/dtos/bulk-update-subscriber-preferences.dto.tsapps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.command.tsapps/api/src/app/subscribers-v2/dtos/patch-subscriber-preferences.dto.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.usecase.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.command.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.spec.tsapps/api/src/app/subscribers-v2/e2e/get-subscriber-preferences.e2e.tsapps/api/src/app/subscribers-v2/dtos/get-subscriber-preferences-request.dto.tsapps/api/src/app/subscribers-v2/usecases/get-subscriber-preferences/get-subscriber-preferences.usecase.ts
**
📄 CodeRabbit inference engine (.cursor/rules/novu.mdc)
Use lowercase with dashes for directories and files (e.g., components/auth-wizard)
Files:
apps/api/src/app/subscribers-v2/subscribers.module.tsapps/api/src/app/subscribers-v2/e2e/patch-subscriber-preferences.e2e.tsapps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.usecase.tsapps/api/src/app/subscribers-v2/subscribers.controller.tsapps/api/src/app/subscribers-v2/dtos/bulk-update-subscriber-preferences.dto.tslibs/internal-sdk/src/lib/sdks.tslibs/internal-sdk/src/models/components/patchsubscriberpreferencesdto.tsapps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.command.tsapps/api/package.jsonlibs/internal-sdk/src/funcs/subscribersPreferencesList.tsapps/api/src/app/subscribers-v2/dtos/patch-subscriber-preferences.dto.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.usecase.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.command.tsapps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.spec.tsapps/api/src/app/subscribers-v2/e2e/get-subscriber-preferences.e2e.tsapps/api/src/app/subscribers-v2/dtos/get-subscriber-preferences-request.dto.tslibs/internal-sdk/src/react-query/subscribersPreferencesList.tsapps/api/src/app/subscribers-v2/usecases/get-subscriber-preferences/get-subscriber-preferences.usecase.tslibs/internal-sdk/src/models/components/bulkupdatesubscriberpreferencesdto.tslibs/internal-sdk/src/react-query/subscribersPreferencesList.core.tslibs/internal-sdk/src/lib/config.tslibs/internal-sdk/src/sdk/preferences.tslibs/internal-sdk/src/models/operations/subscriberscontrollergetsubscriberpreferences.tslibs/internal-sdk/src/models/components/previewpayloaddto.tslibs/internal-sdk/src/models/components/createchannelconnectionrequestdto.tslibs/dal/src/repositories/preferences/preferences.schema.ts
🧠 Learnings (8)
📚 Learning: 2026-01-07T13:09:45.904Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: .cursor/rules/novu.mdc:0-0
Timestamp: 2026-01-07T13:09:45.904Z
Learning: Applies to **/*.{ts,tsx} : Structure files: exported component, subcomponents, helpers, static content, types
Applied to files:
.cspell.json
📚 Learning: 2026-01-07T13:09:45.904Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: .cursor/rules/novu.mdc:0-0
Timestamp: 2026-01-07T13:09:45.904Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript for all code; prefer interfaces over types
Applied to files:
.cspell.json
📚 Learning: 2025-11-25T11:29:52.304Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T11:29:52.304Z
Learning: Applies to apps/dashboard/**/*.{ts,tsx} : Use lowercase with dashes for directories and files in dashboard (e.g., components/auth-wizard)
Applied to files:
.cspell.json
📚 Learning: 2026-01-07T13:09:45.904Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: .cursor/rules/novu.mdc:0-0
Timestamp: 2026-01-07T13:09:45.904Z
Learning: Applies to **/*.{ts,tsx} : Prefer iteration and modularization over code duplication, minimize code duplication as possible
Applied to files:
.cspell.json
📚 Learning: 2025-12-22T14:14:49.363Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-12-22T14:14:49.363Z
Learning: Applies to apps/dashboard/**/components/**/*.{ts,tsx} : Favor named exports for components
Applied to files:
.cspell.json
📚 Learning: 2025-11-25T11:29:52.304Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T11:29:52.304Z
Learning: Applies to apps/dashboard/**/*.{tsx,ts} : Favor named exports for components in dashboard
Applied to files:
.cspell.json
📚 Learning: 2025-11-25T11:29:52.304Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T11:29:52.304Z
Learning: Applies to apps/{dashboard,web}/**/*.{ts,tsx} : Prefer types over interfaces in frontend code
Applied to files:
.cspell.json
📚 Learning: 2026-01-07T13:09:45.904Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: .cursor/rules/novu.mdc:0-0
Timestamp: 2026-01-07T13:09:45.904Z
Learning: Applies to **/*.{ts,tsx} : Favor named exports for components
Applied to files:
.cspell.json
🧬 Code graph analysis (12)
apps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.usecase.ts (2)
libs/dal/src/repositories/context/context.repository.ts (1)
ContextRepository(8-90)apps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.command.ts (1)
UpdateSubscriberPreferencesCommand(9-25)
apps/api/src/app/subscribers-v2/dtos/bulk-update-subscriber-preferences.dto.ts (2)
apps/api/src/app/shared/framework/swagger/context-payload.decorator.ts (1)
ApiContextPayload(32-39)libs/application-generic/src/decorators/is-valid-context-payload.decorator.ts (1)
IsValidContextPayload(157-161)
libs/internal-sdk/src/lib/sdks.ts (4)
libs/internal-sdk/src/lib/security.ts (1)
SecurityState(43-49)libs/internal-sdk/src/hooks/hooks.ts (1)
SDKHooks(23-132)libs/internal-sdk/src/lib/encodings.ts (1)
encodeForm(180-180)libs/internal-sdk/src/lib/config.ts (1)
SDK_METADATA(60-66)
libs/internal-sdk/src/models/components/patchsubscriberpreferencesdto.ts (2)
libs/internal-sdk/src/models/components/scheduledto.ts (1)
ScheduleDto$Outbound(476-479)apps/api/src/app/subscribers-v2/dtos/patch-subscriber-preferences.dto.ts (1)
PatchSubscriberPreferencesDto(26-50)
apps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.command.ts (1)
libs/application-generic/src/decorators/is-valid-context-payload.decorator.ts (1)
IsValidContextPayload(157-161)
apps/api/src/app/subscribers-v2/dtos/patch-subscriber-preferences.dto.ts (2)
apps/api/src/app/shared/framework/swagger/context-payload.decorator.ts (1)
ApiContextPayload(32-39)libs/application-generic/src/decorators/is-valid-context-payload.decorator.ts (1)
IsValidContextPayload(157-161)
apps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.usecase.ts (2)
libs/dal/src/repositories/context/context.repository.ts (1)
ContextRepository(8-90)apps/api/src/app/shared/framework/idempotency.interceptor.ts (1)
isEnabled(51-75)
libs/internal-sdk/src/react-query/subscribersPreferencesList.ts (3)
libs/internal-sdk/src/models/operations/subscriberscontrollergetsubscriberpreferences.ts (1)
SubscribersControllerGetSubscriberPreferencesRequest(20-31)libs/internal-sdk/src/react-query/_types.ts (2)
QueryHookOptions(95-100)SuspenseQueryHookOptions(102-107)libs/internal-sdk/src/react-query/subscribersPreferencesList.core.ts (2)
SubscribersPreferencesListQueryData(12-12)buildSubscribersPreferencesListQuery(25-50)
libs/internal-sdk/src/models/components/bulkupdatesubscriberpreferencesdto.ts (1)
libs/internal-sdk/src/models/components/bulkupdatesubscriberpreferenceitemdto.ts (2)
BulkUpdateSubscriberPreferenceItemDto(12-21)BulkUpdateSubscriberPreferenceItemDto$Outbound(24-27)
libs/internal-sdk/src/react-query/subscribersPreferencesList.core.ts (5)
libs/internal-sdk/src/react-query/subscribersPreferencesList.ts (4)
SubscribersPreferencesListQueryData(26-26)prefetchSubscribersPreferencesList(24-24)buildSubscribersPreferencesListQuery(23-23)queryKeySubscribersPreferencesList(25-25)libs/internal-sdk/src/models/operations/subscriberscontrollergetsubscriberpreferences.ts (2)
SubscribersControllerGetSubscriberPreferencesResponse(33-36)SubscribersControllerGetSubscriberPreferencesRequest(20-31)libs/internal-sdk/src/core.ts (1)
NovuCore(13-13)libs/internal-sdk/src/lib/sdks.ts (1)
RequestOptions(30-56)libs/internal-sdk/src/funcs/subscribersPreferencesList.ts (1)
subscribersPreferencesList(35-55)
libs/internal-sdk/src/models/operations/subscriberscontrollergetsubscriberpreferences.ts (1)
libs/internal-sdk/src/models/errors/sdkvalidationerror.ts (1)
SDKValidationError(7-50)
libs/internal-sdk/src/models/components/createchannelconnectionrequestdto.ts (2)
libs/internal-sdk/src/models/components/workspacedto.ts (1)
WorkspaceDto$outboundSchema(31-38)libs/internal-sdk/src/models/components/authdto.ts (1)
AuthDto$outboundSchema(25-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
- GitHub Check: Unit test backend services (@novu/ws)
- GitHub Check: Unit test backend services (@novu/api-service)
- GitHub Check: E2E test API / Test E2E
- GitHub Check: Unit test backend services (@novu/worker)
- GitHub Check: Unit test backend services (@novu/webhook)
- GitHub Check: Validate OpenAPI
- GitHub Check: Unit test backend services (@novu/inbound-mail)
- GitHub Check: Unit test @novu internal packages
- GitHub Check: Unit test @novu public NPM packages (except providers)
- GitHub Check: Redirect rules - dashboard-v2-novu-staging
- GitHub Check: Header rules - dashboard-v2-novu-staging
- GitHub Check: Pages changed - dashboard-v2-novu-staging
- GitHub Check: Analyze (javascript)
- GitHub Check: Analyze (typescript)
- GitHub Check: publish_preview_packages
🔇 Additional comments (57)
libs/internal-sdk/src/models/components/createchannelconnectionrequestdto.ts (5)
1-7: LGTM! Generated code with correct imports.This file is auto-generated by Speakeasy. The imports correctly reference
zod/v3and the related DTO modules forAuthDtoandWorkspaceDto.
12-38: Type definitions are well-structured.The
CreateChannelConnectionRequestDtoContext2provides a rich context object with requiredidand optionaldata, while the union type allows flexibility for simple string identifiers. The main DTO properly incorporates the context map and required fields.
47-62: Outbound schema and serialization follow consistent patterns.The schema correctly uses
z.record(z.any())for the flexibledatafield, and the JSON serialization function properly validates through the outbound schema before stringifying.
70-82: Union schema usesz.lazy()appropriately.The use of
z.lazy()for theCreateChannelConnectionRequestDtoContext2$outboundSchemareference is a good practice to handle schema evaluation order and potential circular dependencies.
99-118: Main DTO outbound schema correctly composes nested schemas.The schema properly references
WorkspaceDto$outboundSchemaandAuthDto$outboundSchemafor nested object validation, and the context record uses the appropriate union schema for flexibility.libs/internal-sdk/src/models/components/previewpayloaddto.ts (4)
1-14: Auto-generated file — no manual edits recommended.This file is generated by Speakeasy and marked "DO NOT EDIT". Any manual changes will be overwritten on the next SDK regeneration. If there are issues with the generated output, they should be addressed in the API spec or Speakeasy configuration.
The imports and file structure look correct for the intended purpose.
19-43: Type definitions are consistent and correctly structured.The renaming from generic names (
Two,Context) to more descriptive names (PreviewPayloadDtoContext2,PreviewPayloadDtoContext) improves code readability. The union type allowing both rich context objects and simple strings provides flexibility for the API consumers.
46-128: Schema definitions are correctly implemented.The Zod schemas properly mirror the type definitions with appropriate inbound/outbound variants. The use of
z.lazy()for the union schemas (while not strictly necessary for non-recursive types) ensures proper schema evaluation order and is a safe pattern in generated code.
130-139: JSON helper functions follow consistent patterns.The serialization helpers correctly use the outbound schemas for stringification and inbound schemas for parsing, with appropriate error messages for debugging failed parses.
libs/internal-sdk/src/models/operations/subscriberscontrollergetsubscriberpreferences.ts (5)
1-3: Auto-generated file - changes appear consistent with PR objectives.This file is auto-generated by Speakeasy and marked as "DO NOT EDIT". The changes add
contextKeyssupport for context-aware preferences filtering. Any modifications to this file should be made through the Speakeasy configuration/spec rather than direct edits.
20-31: LGTM - contextKeys field properly added to request type.The new
contextKeysfield is correctly typed asArray<string> | undefinedwith helpful JSDoc documentation explaining the filtering use case (e.g.,["tenant:acme"]).
38-47: LGTM - Outbound schema and type correctly updated.The
z.nativeEnum(Criticality)is the appropriate Zod schema for TypeScript const enums. The outbound type correctly includescontextKeysand uses the hyphenated'idempotency-key'format for HTTP header compatibility.
50-65: LGTM - Outbound schema correctly implements contextKeys validation.The schema correctly:
- Validates
contextKeysas an optional array of strings- Defaults
criticalityto'nonCritical'- Remaps
idempotencyKeyto the HTTP header format'idempotency-key'
78-101: LGTM - Inbound schema and parsing function correctly implemented.The inbound schema correctly transforms PascalCase response fields (
Headers,Result) to camelCase (headers,result). ThesubscribersControllerGetSubscriberPreferencesResponseFromJSONfunction properly returns aSafeParseResultwithSDKValidationErrorfor type-safe error handling.libs/internal-sdk/src/models/components/bulkupdatesubscriberpreferencesdto.ts (1)
1-87: Generated code — changes look correct.This file is auto-generated by Speakeasy. The additions correctly introduce the
Context2rich context type and integrate it intoBulkUpdateSubscriberPreferencesDtowith:
- Proper outbound schemas for serialization
- Union type support (
Context2 | string) for flexibility- Optional context field maintaining backward compatibility
Any modifications should be made in the OpenAPI spec or Speakeasy configuration rather than directly in this file.
libs/internal-sdk/src/models/components/patchsubscriberpreferencesdto.ts (1)
1-95: Auto-generated code - no changes recommended.This file is auto-generated by Speakeasy and marked "DO NOT EDIT". The generated types and schemas for the new context-aware preferences functionality appear structurally correct:
- The
TwoandContexttypes correctly model the rich context object with optional data payload- The outbound schemas properly validate the union type (
Two | string) for context values- The JSON serialization functions delegate to the schemas appropriately
The generic type name
Twois an artifact of OpenAPI union type generation (representing the second variant in a union), which could be addressed in the OpenAPI spec if more descriptive naming is desired.libs/internal-sdk/.speakeasy/gen.yaml (2)
25-26: LGTM!The
persistentEditsconfiguration change toenabled: neveris appropriate for SDK generation, ensuring clean regeneration without preserving manual edits.
95-95: LGTM!Setting
preserveModelFieldNames: falseallows the SDK generator to apply consistent naming conventions (e.g., camelCase transformation) to model fields, which aligns with TypeScript conventions.apps/api/src/app/subscribers-v2/e2e/patch-subscriber-preferences.e2e.ts (4)
27-40: LGTM!Proper feature flag setup and teardown for test isolation. Using
beforeEach/afterEachensures the flag state doesn't leak between tests.
284-317: LGTM!Excellent test coverage for context isolation. Properly verifies that preferences with different contexts coexist independently and can be retrieved using their respective context keys.
319-334: LGTM!Good negative test case validating the business rule that context cannot be used with global preferences. The error message assertion ensures clear API feedback.
336-361: LGTM!Complete test with both immediate response verification and persistence verification via list. Good pattern demonstrating the context-aware bulk update flow end-to-end.
libs/internal-sdk/src/lib/config.ts (1)
62-65: LGTM!Auto-generated SDK metadata version updates reflecting the OpenAPI spec changes from this PR. The version bumps (
3.11.0→3.12.0for OpenAPI,2.791.1→2.792.3for generator) are consistent with adding new context-related fields..cspell.json (1)
890-891: LGTM!Standard ignore entries for generated worker configuration type definition files.
libs/dal/src/repositories/preferences/preferences.schema.ts (3)
148-166: LGTM with same migration considerations.Consistent addition of
contextKeysto the subscription-based index. The same migration and query consistency considerations from the SUBSCRIBER_WORKFLOW index apply here.
110-112: LGTM!Comments clearly document the intent: enabling multiple preferences per context while maintaining per-type uniqueness constraints.
109-128: Index migration and contextKeys handling are properly implemented.The codebase already addresses the concerns raised:
Migration Strategy – A dedicated
preferences-uniqueness-migration.tsruns before the new index takes effect, deduplicating existing preferences by grouping on{_environmentId, _subscriberId, _templateId, type}and keeping the oldest record.Undefined vs. Empty Array vs. Populated Array – The
buildContextExactMatchQuery()method (used consistently in both upsert and get operations) handles all three cases:
undefinedor[]→ matches{$or: [{contextKeys: {$exists: false}}, {contextKeys: []}]}['tenant:acme']→ matches{contextKeys: {$all: [...], $size: ...}}Query Consistency – Both upsert and get flows use identical query patterns, ensuring no mismatches during updates or lookups.
Feature Flag Control –
resolveContexts()explicitly controls contextKeys population based on the feature flag (IS_CONTEXT_PREFERENCES_ENABLED), returningundefinedwhen disabled (backward compatible) and[]or an array of context keys when enabled.Uniqueness Constraint – The partial filter expression on the index ensures it only applies to
SUBSCRIBER_WORKFLOWtype, preventing conflicts with other preference types.apps/api/src/app/subscribers-v2/e2e/get-subscriber-preferences.e2e.ts (3)
94-134: Good test coverage for contextKeys filtering.This test properly validates that:
- All workflows are returned regardless of context filter
- Context-specific preferences are applied correctly (workflow1 with tenant:acme → email:false)
- Workflows without matching context fall back to defaults (workflow2 → email:true)
136-157: Good coverage for default fallback behavior.This test verifies that when querying with a context that has no specific preference, the default/inherited settings are returned correctly.
159-192: Good isolation test for per-context preferences.This test effectively validates that:
- Context-specific overrides work independently
- Global preferences remain unchanged
- Different contexts see their respective values
apps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.usecase.ts (1)
35-36: LGTM: Context resolution integrated correctly.The
contextKeysare resolved early in the execution flow and properly passed toUpdatePreferencesCommand, ensuring context-aware preference updates.Also applies to: 110-110
apps/api/src/app/subscribers-v2/subscribers.module.ts (1)
18-18: LGTM: ContextRepository properly registered.The
ContextRepositoryis correctly imported and added toDAL_MODELS, making it available for dependency injection in the subscribers module.Also applies to: 75-75
apps/api/src/app/subscribers-v2/usecases/get-subscriber-preferences/get-subscriber-preferences.usecase.ts (1)
52-62: LGTM: contextKeys properly forwarded.The
contextKeysfrom the incoming command are correctly forwarded toGetSubscriberPreferenceCommand, enabling context-aware preference filtering in the downstream use case.apps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.spec.ts (1)
88-108: LGTM on mock setup, but consider adding context-related test coverage.The new
contextRepositoryMockandfeatureFlagsServiceMockare correctly initialized and wired into theBulkUpdatePreferencesconstructor. However, no tests exercise the context-aware flows introduced by this PR.Consider adding test cases that verify:
- Context validation when
contextis provided in the command- Feature flag behavior affecting context resolution
libs/internal-sdk/src/lib/sdks.ts (1)
1-3: Auto-generated file – skip manual review.This file is generated by Speakeasy and marked "DO NOT EDIT." The changes appear to be quote normalization from double to single quotes, likely from regenerating the SDK. No manual review is necessary for auto-generated code.
apps/api/src/app/subscribers-v2/dtos/bulk-update-subscriber-preferences.dto.ts (1)
36-39: LGTM! Context field properly added with appropriate validation.The optional
contextfield is correctly decorated with:
@ApiContextPayload()for Swagger documentation@IsOptional()for optional validation@IsValidContextPayload({ maxCount: 5 })for context-specific validationThis aligns with the validation pattern used in related DTOs and commands.
apps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.command.ts (1)
21-24: LGTM! Context field correctly added to command.The optional
contextfield follows the established validation pattern with@IsValidContextPayload({ maxCount: 5 }), consistent with other commands in this PR.apps/api/src/app/subscribers-v2/dtos/get-subscriber-preferences-request.dto.ts (2)
16-25: Good defensive transform logic for query parameter normalization.The transform handles multiple edge cases well:
undefined→undefined(no filter)''→[](filter for default/no context)- Single value → array coercion
- Filters out empty strings from arrays
This is a robust approach for handling query string variations.
15-34: No changes needed—code is correct.The
@IsOptional()decorator correctly skips validation whencontextKeysisundefined. This is the documented behavior of class-validator, and the pattern of combining@IsOptional()with@IsArray()is used throughout the codebase (e.g.,notification.step.ts,notification-step-variant.command.ts). Requests withoutcontextKeyswill not fail validation.Likely an incorrect or invalid review comment.
apps/api/src/app/subscribers-v2/subscribers.controller.ts (3)
261-265: LGTM!Context keys are correctly passed through from the query DTO to the command. The validation is handled by the DTO layer, and the controller appropriately forwards the data.
320-328: LGTM!Context is properly extracted from the request body and passed to the
BulkUpdatePreferencesCommand. This aligns with the pattern used in the update endpoint.
348-358: LGTM!Context is correctly propagated from the request body to
UpdateSubscriberPreferencesCommand, maintaining consistency with the bulk update endpoint.apps/api/src/app/subscribers-v2/dtos/patch-subscriber-preferences.dto.ts (1)
45-49: LGTM!The context field is properly defined with appropriate validation decorators. The
@IsValidContextPayload({ maxCount: 5 })constraint is consistent with the command layer validation.apps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.command.ts (1)
16-19: LGTM!The context field is properly added with consistent validation (
maxCount: 5) matching the DTO layer. Thereadonlymodifier is appropriate for command objects.libs/internal-sdk/src/sdk/preferences.ts (2)
1-3: Generated code - verify regeneration consistency.This file is marked as generated by Speakeasy. Ensure the OpenAPI spec changes that drive this generation are committed and that the SDK is regenerated consistently.
21-26: Breaking change inlist()method signature.The
list()method now accepts a single request object instead of individual parameters (subscriberId,criticality,idempotencyKey). This is a breaking change for SDK consumers.Ensure this breaking change is documented and SDK versioning is updated appropriately (e.g., major version bump or migration guide).
apps/api/src/app/subscribers-v2/usecases/update-subscriber-preferences/update-subscriber-preferences.usecase.ts (2)
67-73: LGTM!The validation correctly prevents context from being used with global preferences, which aligns with the feature design. The error message is clear and actionable.
75-102: Clarify the semantic distinction betweenundefined(feature flag off) and[](feature on, no context).The methods
buildContextExactMatchQueryin bothUpdatePreferencesandGetSubscriberPreferencetreatundefinedand[]identically (matching only "no context" preferences). While the feature flag is rechecked in these methods, creating correct behavior, the code conflates the semantic intent at lines 338-341 and line 339 respectively.The current flow works correctly because the feature flag is checked twice, but the logic should be clarified. Consider adding explicit documentation or restructuring to make it clear that the semantic distinction between "FF off (ignore context)" and "FF on but no context provided" is intentionally unified at the query level.
libs/internal-sdk/src/react-query/subscribersPreferencesList.ts (3)
1-3: Generated code - verify consistency.This file is Speakeasy-generated. The changes align with the SDK layer updates for context support.
36-45: LGTM!The hook signature correctly accepts the request object pattern, maintaining consistency with the SDK's
Preferences.list()method.
82-99: LGTM!Query key structure correctly includes
contextKeysin the parameters, ensuring proper cache isolation and invalidation for context-specific preferences.libs/internal-sdk/src/react-query/subscribersPreferencesList.core.ts (3)
1-3: Generated code - verify consistency.This core file is Speakeasy-generated and provides the foundational query building for subscriber preferences.
33-38: LGTM!The query key construction correctly includes
contextKeysfrom the request, ensuring React Query properly caches and invalidates preferences based on context filters.
52-60: LGTM!The
queryKeySubscribersPreferencesListfunction signature correctly includescontextKeysin the parameters type, maintaining type safety for cache key construction.libs/internal-sdk/src/funcs/subscribersPreferencesList.ts (3)
1-3: Generated code: manual edits are discouraged.This file is auto-generated by Speakeasy. Any issues identified below should ideally be addressed through generator configuration or templates rather than direct modification, as changes will be overwritten on regeneration.
99-102: New query parameters for context-aware preferences.The addition of
contextKeysandcriticalityquery parameters aligns with the PR objective for context-bound preferences functionality.
160-181: Comprehensive error code handling.The error codes array covers a wide range of HTTP status codes including client errors (4XX) and server errors (5XX). The retry configuration properly excludes non-retryable errors.
| retryConfig: options?.retries || | ||
| client._options.retryConfig || { | ||
| strategy: 'backoff', | ||
| backoff: { | ||
| initialInterval: 1000, | ||
| maxInterval: 30000, | ||
| exponent: 1.5, | ||
| maxElapsedTime: 3600000, | ||
| }, | ||
| retryConnectionErrors: true, | ||
| } | ||
| || { strategy: "none" }, | ||
| retryCodes: options?.retryCodes || ["408", "409", "429", "5XX"], | ||
| } || { strategy: 'none' }, | ||
| retryCodes: options?.retryCodes || ['408', '409', '429', '5XX'], |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the actual file content around the specified lines
fd "subscribersPreferencesList.ts" libs/internal-sdk/src/funcs/ --type fRepository: novuhq/novu
Length of output: 112
🏁 Script executed:
# Once we locate the file, read the exact content
cat -n libs/internal-sdk/src/funcs/subscribersPreferencesList.ts | sed -n '120,140p'Repository: novuhq/novu
Length of output: 826
🏁 Script executed:
# Check if similar dead code pattern exists in other generated SDK files
rg -n "strategy: 'none'" libs/internal-sdk/src/funcs/Repository: novuhq/novu
Length of output: 328
Remove unreachable fallback: || { strategy: 'none' } is dead code.
The object literal spanning lines 127–135 is always truthy, making the fallback at line 136 unreachable. This pattern appears in multiple generated SDK files (notificationsList.ts, activityWorkflowRunsList.ts), suggesting a generator template issue to address in Speakeasy.
Diff
retryConfig: options?.retries ||
client._options.retryConfig || {
strategy: 'backoff',
backoff: {
initialInterval: 1000,
maxInterval: 30000,
exponent: 1.5,
maxElapsedTime: 3600000,
},
retryConnectionErrors: true,
- } || { strategy: 'none' },
+ },📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| retryConfig: options?.retries || | |
| client._options.retryConfig || { | |
| strategy: 'backoff', | |
| backoff: { | |
| initialInterval: 1000, | |
| maxInterval: 30000, | |
| exponent: 1.5, | |
| maxElapsedTime: 3600000, | |
| }, | |
| retryConnectionErrors: true, | |
| } | |
| || { strategy: "none" }, | |
| retryCodes: options?.retryCodes || ["408", "409", "429", "5XX"], | |
| } || { strategy: 'none' }, | |
| retryCodes: options?.retryCodes || ['408', '409', '429', '5XX'], | |
| retryConfig: options?.retries || | |
| client._options.retryConfig || { | |
| strategy: 'backoff', | |
| backoff: { | |
| initialInterval: 1000, | |
| maxInterval: 30000, | |
| exponent: 1.5, | |
| maxElapsedTime: 3600000, | |
| }, | |
| retryConnectionErrors: true, | |
| }, | |
| retryCodes: options?.retryCodes || ['408', '409', '429', '5XX'], |
🤖 Prompt for AI Agents
In @libs/internal-sdk/src/funcs/subscribersPreferencesList.ts around lines 126 -
137, The fallback "|| { strategy: 'none' }" is unreachable because the object
literal before it is always truthy; remove that final fallback or replace the
chained ORs with nullish coalescing to preserve intended semantics. Update the
expression that sets retryConfig (the piece using options?.retries ||
client._options.retryConfig || { strategy: 'backoff', ... } || { strategy:
'none' }) to either drop the trailing "|| { strategy: 'none' }" or use ??
between operands (options?.retries ?? client._options.retryConfig ?? { strategy:
'backoff', ... }) so the default backoff object is only used when prior values
are null/undefined; apply the same fix in other generated files with the same
pattern (notificationsList.ts, activityWorkflowRunsList.ts).
What changed? Why was the change needed?
This pull request introduces support for context-aware subscriber preferences, allowing preferences to be set and filtered based on custom context keys (such as tenant or environment). It updates DTOs, use cases, and tests to handle context payloads, and adds feature flag controls for enabling or disabling this functionality. The changes also include improvements to test coverage for context-specific behavior and minor updates to code style and configuration.
Context-aware preferences support:
contextfield (with validation and max count) to DTOs for bulk update, patch, and get subscriber preferences, enabling context-specific preference operations. (apps/api/src/app/inbox/usecases/bulk-update-preferences/bulk-update-preferences.command.ts[1] [2];apps/api/src/app/subscribers-v2/dtos/bulk-update-subscriber-preferences.dto.ts[3] [4];apps/api/src/app/subscribers-v2/dtos/patch-subscriber-preferences.dto.ts[5] [6]GetSubscriberPreferencesRequestDtoto allow filtering bycontextKeysvia query parameters, with normalization and transformation logic for flexible usage. (apps/api/src/app/subscribers-v2/dtos/get-subscriber-preferences-request.dto.ts[1] [2]Test coverage for context-aware behavior:
apps/api/src/app/subscribers-v2/e2e/get-subscriber-preferences.e2e.ts[1] [2] [3] [4] [5] [6];apps/api/src/app/subscribers-v2/e2e/patch-subscriber-preferences.e2e.ts[7]