Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
894ef81
feat(api): add channel connections and endpoints management to inbox …
djabarovgeorge Apr 12, 2026
bc313bb
feat(api,react): enhance Slack integration and add new DM endpoint fu…
djabarovgeorge Apr 12, 2026
1adf817
chore(api): remove unused Slack connection API endpoint
djabarovgeorge Apr 12, 2026
6193c6d
refactor(connect-chat): remove debug logging from Connect Chat page
djabarovgeorge Apr 12, 2026
1900527
feat(connect-chat): enhance Slack user ID input handling in Connect C…
djabarovgeorge Apr 12, 2026
ff02913
feat(link-user): update LinkUser component to use type and endpoint p…
djabarovgeorge Apr 12, 2026
e7117ef
feat(api): extend ListChannelConnectionsArgs and update InboxService …
djabarovgeorge Apr 12, 2026
b68dcd1
feat(api): add endpointType and endpointData to chat OAuth flow
djabarovgeorge Apr 13, 2026
d1b5c61
feat(ui): add SlackConnectButton component and related types
djabarovgeorge Apr 13, 2026
8a14ea8
refactor(api): remove endpointType and endpointData from chat OAuth flow
djabarovgeorge Apr 13, 2026
1850db1
feat(api): enhance chat OAuth flow with userScope and mode parameters
djabarovgeorge Apr 13, 2026
2b7ed60
fix(link-slack-user): make subscriberId optional and update context h…
djabarovgeorge Apr 13, 2026
12d9446
feat(ui): introduce SlackLinkUser component and update related types
djabarovgeorge Apr 14, 2026
cb93f51
feat(slack): add constants for default connection and integration ide…
djabarovgeorge Apr 14, 2026
4592967
feat(connection): introduce connectionMode for channel connections
djabarovgeorge Apr 14, 2026
b60512d
refactor(slack): rename SlackConnectButton to ChannelConnectButton an…
djabarovgeorge Apr 14, 2026
971484c
feat(ui): update LinkUser and SlackLinkUser components for improved u…
djabarovgeorge Apr 14, 2026
842b5b6
feat(ui): enhance SlackLinkUser component with new appearance callbacks
djabarovgeorge Apr 14, 2026
8185213
refactor(api): update HTTP client calls in InboxService to include ad…
djabarovgeorge Apr 14, 2026
56291a3
Merge branch 'next' into chat-new-connect-components
djabarovgeorge Apr 14, 2026
209b7e3
fix(api): correct Slack OAuth URL generation to use dynamic base URL
djabarovgeorge Apr 14, 2026
d410968
Merge branch 'chat-new-connect-components' of https://github.com/novu…
djabarovgeorge Apr 14, 2026
f09a56b
refactor(ui): enhance type safety and cleanup in channel connection c…
djabarovgeorge Apr 14, 2026
97342fa
Merge branch 'next' into chat-new-connect-components
djabarovgeorge Apr 14, 2026
8b96e8c
feat(api): introduce connection mode validation utility and refactor …
djabarovgeorge Apr 15, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export class ChannelConnectionsController {
integrationIdentifier: body.integrationIdentifier,
subscriberId: body.subscriberId,
context: body.context,
connectionMode: body.connectionMode,
workspace: body.workspace,
auth: body.auth,
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { ApiContextPayload, IsValidContextPayload } from '@novu/application-generic';
import { ContextPayload } from '@novu/shared';
import { ConnectionMode, ContextPayload } from '@novu/shared';
import { Type } from 'class-transformer';
import { IsDefined, IsOptional, IsString, ValidateNested } from 'class-validator';
import { IsDefined, IsIn, IsOptional, IsString, ValidateNested } from 'class-validator';
import { AuthDto, WorkspaceDto } from './shared.dto';

export class CreateChannelConnectionRequestDto {
Expand Down Expand Up @@ -30,6 +30,20 @@ export class CreateChannelConnectionRequestDto {
@IsValidContextPayload({ maxCount: 5 })
context?: ContextPayload;

@ApiPropertyOptional({
description:
'Connection mode that determines how the channel connection is scoped. ' +
'Use "subscriber" (default) to associate the connection with a specific subscriber. ' +
'Use "shared" to associate the connection with a context instead of a subscriber — ' +
'subscriberId will not be stored on the connection.',
enum: ['subscriber', 'shared'],
example: 'shared',
})
@IsOptional()
@IsString()
@IsIn(['subscriber', 'shared'])
connectionMode?: ConnectionMode;

@ApiProperty({
description: 'The identifier of the integration to use for this channel connection.',
type: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { BadRequestException } from '@nestjs/common';
import { ConnectionMode, ContextPayload } from '@novu/shared';

/**
* Validates that the subscriber/context combination is consistent with the
* requested connectionMode. Throws a BadRequestException when the caller
* violates the scoping rules for the chosen mode.
*
* Called from both CreateChannelConnection and GenerateSlackOauthUrl so the
* rules are enforced in one place.
*/
export function validateConnectionMode({
connectionMode,
subscriberId,
context,
}: {
connectionMode?: ConnectionMode;
subscriberId?: string;
context?: ContextPayload;
}): void {
if (connectionMode === 'shared') {
if (!context) {
throw new BadRequestException('context is required when connectionMode is "shared"');
}

if (subscriberId) {
throw new BadRequestException('subscriberId must not be provided when connectionMode is "shared"');
}

return;
}

if (connectionMode === 'subscriber') {
if (!subscriberId) {
throw new BadRequestException('subscriberId is required when connectionMode is "subscriber"');
}

return;
}

if (!subscriberId && !context) {
throw new BadRequestException('Either subscriberId or context must be provided');
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IsValidContextPayload } from '@novu/application-generic';
import { ContextPayload } from '@novu/shared';
import { ConnectionMode, ContextPayload } from '@novu/shared';
import { Type } from 'class-transformer';
import { IsDefined, IsOptional, IsString, ValidateNested } from 'class-validator';
import { IsDefined, IsIn, IsOptional, IsString, ValidateNested } from 'class-validator';
import { EnvironmentCommand } from '../../../shared/commands/project.command';
import { AuthDto, WorkspaceDto } from '../../dtos/shared.dto';

Expand All @@ -22,6 +22,11 @@ export class CreateChannelConnectionCommand extends EnvironmentCommand {
@IsValidContextPayload({ maxCount: 5 })
context?: ContextPayload;

@IsOptional()
@IsString()
@IsIn(['subscriber', 'shared'])
connectionMode?: ConnectionMode;

@IsDefined()
@ValidateNested()
@Type(() => WorkspaceDto)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BadRequestException, ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { InstrumentUsecase, shortId } from '@novu/application-generic';
import {
ChannelConnectionEntity,
Expand All @@ -8,6 +8,7 @@ import {
IntegrationRepository,
SubscriberRepository,
} from '@novu/dal';
import { validateConnectionMode } from '../channel-connection.utils';
import { CreateChannelConnectionCommand } from './create-channel-connection.command';

@Injectable()
Expand Down Expand Up @@ -50,11 +51,11 @@ export class CreateChannelConnection {
}

private validateResourceOrContext(command: CreateChannelConnectionCommand) {
const { subscriberId, context } = command;

if (!subscriberId && !context) {
throw new BadRequestException('Either subscriberId or context must be provided');
}
validateConnectionMode({
connectionMode: command.connectionMode,
subscriberId: command.subscriberId,
context: command.context,
});
}

private async resolveContexts(command: CreateChannelConnectionCommand): Promise<string[]> {
Expand Down
Loading
Loading