Skip to content
Closed
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .source
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@ The following modules and folders are licensed under the enterprise license:

Thanks a lot for spending your time helping Novu grow. Keep rocking 🥂

<a href="https://novu.co/contributors?utm_source=github" target="_blank" rel="noopener noreferrer"
>
<a href="https://novu.co/contributors?utm_source=github" target="_blank" rel="noopener noreferrer">
<img src="https://contributors-img.web.app/image?repo=novuhq/novu" alt="Contributors"/>
</a>
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
"handlebars": "4.7.9",
"helmet": "^6.0.1",
"i18next": "^23.7.6",
"ioredis": "5.3.2",
"ioredis": "5.10.1",
"json-logic-js": "^2.0.5",
"json-schema-faker": "^0.5.6",
"json-schema-to-ts": "^3.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { BadRequestException, Injectable } from '@nestjs/common';
import { InstrumentUsecase } from '@novu/application-generic';
import {
ChannelConnectionDBModel,
Expand All @@ -16,6 +16,10 @@ export class ListChannelConnections {

@InstrumentUsecase()
async execute(command: ListChannelConnectionsCommand) {
if (command.before && command.after) {
throw new BadRequestException('Cannot specify both "before" and "after" cursors at the same time.');
}

const filter: FilterQuery<ChannelConnectionDBModel> & EnforceEnvOrOrgIds = {
_environmentId: command.user.environmentId,
_organizationId: command.user.organizationId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { BadRequestException, Injectable } from '@nestjs/common';
import { InstrumentUsecase } from '@novu/application-generic';
import type { EnforceEnvOrOrgIds } from '@novu/dal';
import { ChannelEndpointDBModel, ChannelEndpointEntity, ChannelEndpointRepository } from '@novu/dal';
Expand All @@ -12,6 +12,10 @@ export class ListChannelEndpoints {

@InstrumentUsecase()
async execute(command: ListChannelEndpointsCommand) {
if (command.before && command.after) {
throw new BadRequestException('Cannot specify both "before" and "after" cursors at the same time.');
}

const filter: FilterQuery<ChannelEndpointDBModel> & EnforceEnvOrOrgIds = {
_environmentId: command.user.environmentId,
_organizationId: command.user.organizationId,
Expand Down
18 changes: 17 additions & 1 deletion apps/api/src/app/contexts/e2e/list-contexts.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,27 @@ describe('List Contexts - /contexts (GET) #novu-v2', () => {
data: {},
});

const response = await novuClient.contexts.list({ search: 'list-test-4.*acme' });
const response = await novuClient.contexts.list({ search: 'acme' });

expect(response.result.data.length).to.equal(2);
});

it('should handle regex metacharacters in search without crashing', async () => {
await contextRepository.create({
_organizationId: session.organization._id,
_environmentId: session.environment._id,
type: 'tenant',
id: 'list-test-regex-org-1',
key: 'tenant:list-test-regex-org-1',
data: {},
});

const response = await novuClient.contexts.list({ search: '[invalid' });

expect(response.result.data).to.be.an('array');
expect(response.result.data.length).to.equal(0);
});

it('should support cursor-based pagination with limit', async () => {
for (let i = 0; i < 15; i += 1) {
await contextRepository.create({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { BadRequestException, Injectable } from '@nestjs/common';
import { ContextEntity, ContextRepository, EnforceEnvOrOrgIds } from '@novu/dal';
import { DirectionEnum } from '@novu/shared';
import { FilterQuery } from 'mongoose';
Expand All @@ -9,6 +9,10 @@ export class ListContexts {
constructor(private contextRepository: ContextRepository) {}

async execute(command: ListContextsCommand) {
if (command.before && command.after) {
throw new BadRequestException('Cannot specify both "before" and "after" cursors at the same time.');
}

const filter: FilterQuery<ContextEntity> & EnforceEnvOrOrgIds = {
_environmentId: command.user.environmentId,
_organizationId: command.user.organizationId,
Expand All @@ -22,9 +26,9 @@ export class ListContexts {
filter.id = command.id;
}

// Search across the composite key field (format: "type:id")
if (command.search) {
filter.key = { $regex: command.search, $options: 'i' };
const escapedSearch = command.search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
filter.key = { $regex: escapedSearch, $options: 'i' };
}

// Handle cursor-based pagination
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/app/environments-v1/novu-bridge-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ConstructFrameworkWorkflow, ConstructFrameworkWorkflowCommand } from '.
* A custom framework name is specified for the Novu-managed Bridge endpoint
* to provide a clear distinction between Novu-managed and self-managed Bridge endpoints.
*/
export const frameworkName = 'novu-nest';
const frameworkName = 'novu-nest';

/**
* This class overrides the default NestJS Novu Bridge Client to allow for dynamic construction of
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/app/inbox/utils/validate-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function validateDataStructure(data: unknown): void {
* - Value is a scalar (string, number, boolean, null)
* - String values are limited to 256 characters
*/
export function validateScalarValue(key: string, value: unknown): void {
function validateScalarValue(key: string, value: unknown): void {
if (typeof value === 'string' && value.length > 256) {
throw new BadRequestException(`String value for ${key} exceeds 256 characters`);
}
Expand Down
9 changes: 9 additions & 0 deletions apps/api/src/app/subscribers-v2/subscribers.controller.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,15 @@ describe('Subscriber Controller E2E API Testing #novu-v2', () => {
expect(firstPage.next).to.exist;
expect(firstPage.previous).to.not.exist;
});

it('should return 400 when both before and after cursors are provided', async () => {
const response = await session.testAgent
.get('/v2/subscribers')
.query({ before: '000000000000000000000001', after: '000000000000000000000002' });

expect(response.status).to.equal(400);
expect(response.body.message).to.contain('Cannot specify both "before" and "after" cursors');
});
});

describe('List Subscriber Sorting', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { BadRequestException, Injectable } from '@nestjs/common';
import { InstrumentUsecase } from '@novu/application-generic';
import { SubscriberRepository } from '@novu/dal';
import { DirectionEnum } from '../../../shared/dtos/base-responses';
Expand All @@ -12,6 +12,10 @@ export class ListSubscribersUseCase {

@InstrumentUsecase()
async execute(command: ListSubscribersCommand): Promise<ListSubscribersResponseDto> {
if (command.before && command.after) {
throw new BadRequestException('Cannot specify both "before" and "after" cursors at the same time.');
}

const pagination = await this.subscriberRepository.listSubscribers({
after: command.after,
before: command.before,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { InstrumentUsecase } from '@novu/application-generic';
import { SubscriberRepository, TopicSubscribersEntity, TopicSubscribersRepository } from '@novu/dal';
import { DirectionEnum, EnvironmentId } from '@novu/shared';
Expand All @@ -24,7 +24,7 @@ export class ListSubscriberSubscriptionsUseCase {
}

if (command.before && command.after) {
throw new Error('Cannot specify both "before" and "after" cursors at the same time.');
throw new BadRequestException('Cannot specify both "before" and "after" cursors at the same time.');
}

// Use the repository method for pagination
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { InstrumentUsecase } from '@novu/application-generic';
import {
SubscriberRepository,
Expand Down Expand Up @@ -33,6 +33,10 @@ export class ListTopicSubscriptionsUseCase {
throw new NotFoundException(`Topic with key ${command.topicKey} not found`);
}

if (command.before && command.after) {
throw new BadRequestException('Cannot specify both "before" and "after" cursors at the same time.');
}

const subscriptionsPagination = await this.topicSubscribersRepository.findTopicSubscriptionsWithPagination({
environmentId: command.environmentId,
organizationId: command.organizationId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { BadRequestException, Injectable } from '@nestjs/common';
import { InstrumentUsecase } from '@novu/application-generic';
import { TopicRepository } from '@novu/dal';
import { DirectionEnum } from '../../../shared/dtos/base-responses';
Expand All @@ -12,6 +12,10 @@ export class ListTopicsUseCase {

@InstrumentUsecase()
async execute(command: ListTopicsCommand): Promise<ListTopicsResponseDto> {
if (command.before && command.after) {
throw new BadRequestException('Cannot specify both "before" and "after" cursors at the same time.');
}

const pagination = await this.topicRepository.listTopics({
after: command.after,
before: command.before,
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/app/widgets/widgets.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ export class WidgetsController {
@SubscriberSession() subscriberSession: SubscriberSession,
@Body() body: { feedId?: string | string[] }
) {
const feedIds = this.toArray(body.feedId);
const feedIds = this.toArray(body?.feedId);

return await this.markAllMessagesAsUsecase.execute(
MarkAllMessagesAsCommand.create({
Expand All @@ -367,7 +367,7 @@ export class WidgetsController {
@SubscriberSession() subscriberSession: SubscriberSession,
@Body() body: { feedId?: string | string[] }
): Promise<number> {
const feedIds = this.toArray(body.feedId);
const feedIds = this.toArray(body?.feedId);

return await this.markAllMessagesAsUsecase.execute(
MarkAllMessagesAsCommand.create({
Expand Down
4 changes: 2 additions & 2 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
"@types/react-dom": "^19.2.3",
"@types/react-window": "^1.8.8",
"@types/uuid": "^8.3.4",
"@vitejs/plugin-react": "^4.3.1",
"@vitejs/plugin-react": "^4.7.0",
"cross-fetch": "^4.0.0",
"dotenv": "^16.4.5",
"express": "^4.21.0",
Expand All @@ -184,7 +184,7 @@
"rimraf": "^3.0.2",
"tailwindcss": "^4.1.18",
"typescript": "5.6.2",
"vite": "^5.4.21",
"vite": "^6.4.2",
"vite-plugin-ejs": "^1.7.0",
"vite-plugin-static-copy": "^2.3.2"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Operator } from 'react-querybuilder';
import type { FieldDataType } from '@/utils/parseStepVariables';

export const FIELD_TYPE_OPERATORS: Record<FieldDataType, Operator[]> = {
const FIELD_TYPE_OPERATORS: Record<FieldDataType, Operator[]> = {
string: [
{ name: '=', label: 'equals' },
{ name: '!=', label: 'does not equal' },
Expand Down Expand Up @@ -86,7 +86,7 @@ export function getOperatorsForFieldType(dataType: FieldDataType): Operator[] {
return FIELD_TYPE_OPERATORS[dataType] || FIELD_TYPE_OPERATORS.string;
}

export const RELATIVE_DATE_OPERATORS = [
const RELATIVE_DATE_OPERATORS = [
'moreThanXAgo',
'lessThanXAgo',
'withinLast',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BaseOption, isOptionGroupArray, OptionList } from 'react-querybuilder';
import { SelectGroup, SelectItem, SelectLabel } from '@/components/primitives/select';
import { capitalize } from '@/utils/string';

export const EMPTY_SELECT_VALUE = '__empty__';
const EMPTY_SELECT_VALUE = '__empty__';

export function toSafeValue(value: string | null | undefined): string {
if (!value) return EMPTY_SELECT_VALUE;
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/components/create-workflow-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type CreateWorkflowTab = 'guided' | 'manual';
const WORKFLOW_SUGGESTIONS = [
'Welcome email workflow',
'Order confirmation workflow',
'Payment failed',
'Payment failed workflow',
'Password reset workflow',
];

Expand Down
12 changes: 0 additions & 12 deletions apps/dashboard/src/components/icons/arrow-right.tsx

This file was deleted.

1 change: 0 additions & 1 deletion apps/dashboard/src/components/icons/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './arrow-right';
export * from './bell';
export * from './inbox-arrow-down';
export * from './inbox-bell';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ExternalToast, toast } from 'sonner';
import { Toast, ToastIcon, ToastProps } from './sonner';

// Consistent toast options for bottom-center positioning like inbox-usecase-page
export const CONSISTENT_TOAST_OPTIONS: ExternalToast = {
const CONSISTENT_TOAST_OPTIONS: ExternalToast = {
position: 'bottom-center',
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const PropertyListItemSchema = z.object({
export type PropertyListItem = z.infer<typeof PropertyListItemSchema>;

// This is the overall shape of the form data for the SchemaEditor
export const SchemaEditorFormValuesSchema = z.object({
const SchemaEditorFormValuesSchema = z.object({
propertyList: z.array(PropertyListItemSchema).superRefine((list, ctx) => {
// Check for unique keyNames among properties
const names = new Set<string>();
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/components/topics/topic-drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ function TopicTabs(props: TopicTabsProps) {
);
}

export const TopicListBlank = () => {
const TopicListBlank = () => {
return (
<div className="mt-[100px] flex h-full w-full flex-col items-center justify-center gap-6">
<EmptyTopicsIllustration />
Expand Down
12 changes: 1 addition & 11 deletions apps/dashboard/src/hooks/use-feature-flag.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FeatureFlags, FeatureFlagsKeysEnum, prepareBooleanStringFeatureFlag } from '@novu/shared';
import { FeatureFlagsKeysEnum, prepareBooleanStringFeatureFlag } from '@novu/shared';
import { useFlags } from 'launchdarkly-react-client-sdk';
import { IS_ENTERPRISE, IS_SELF_HOSTED, LAUNCH_DARKLY_CLIENT_SIDE_ID } from '../config';

Expand All @@ -10,16 +10,6 @@ function isLaunchDarklyEnabled() {
return !!LAUNCH_DARKLY_CLIENT_SIDE_ID && !(IS_SELF_HOSTED && IS_ENTERPRISE);
}

export const useFeatureFlagMap = (defaultValue = false): FeatureFlags => {
const flags = useFlags();

return Object.keys(flags).reduce((acc: FeatureFlags, flag: string) => {
acc[flag as keyof FeatureFlags] = flags[flag] ?? defaultValue;

return acc;
}, {} as FeatureFlags);
};

export const useFeatureFlag = (key: FeatureFlagsKeysEnum, defaultValue = false): boolean => {
const flags = useFlags();

Expand Down
2 changes: 0 additions & 2 deletions apps/dashboard/src/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ export * from './integrations-list-page';
export * from './invitation-accept';
export * from './layouts';
export * from './organization-list';
export * from './questionnaire-page';
export * from './settings';
export * from './sign-in';
export * from './sign-up';
export * from './sso-sign-in';
export * from './translations';
export * from './usecase-select-page';
export * from './verify-email';
export * from './welcome-page';
export * from './workflows';
20 changes: 0 additions & 20 deletions apps/dashboard/src/pages/questionnaire-page.tsx

This file was deleted.

Loading
Loading