Skip to content

feat(dashboard,api-service): add self-hosted-auth #7755

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

Merged
merged 73 commits into from
May 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
7d1d507
feat(dashboard,api-service): add self-hosted-auth
djabarovgeorge Feb 20, 2025
5806874
fix:
scopsy Feb 20, 2025
2b694ce
fix: opted in status
scopsy Feb 21, 2025
92a8620
Merge branch 'next' into self-hosted-auth
djabarovgeorge Apr 20, 2025
03c53a4
refactor(api-service,dashaboard): integrate SystemOrganizationService…
djabarovgeorge Apr 21, 2025
15a96d4
refactor(api-service): rename variable 'admins' to 'users' for clarit…
djabarovgeorge Apr 21, 2025
4b699ab
refactor(dashboard): update self-hosted configuration and remove unus…
djabarovgeorge Apr 21, 2025
f085a27
refactor(dashboard): export OrganizationSwitcher from self-hosted uti…
djabarovgeorge Apr 21, 2025
e48c021
refactor(dashboard): export UserButton from self-hosted utils and cle…
djabarovgeorge Apr 21, 2025
0fe32b2
refactor(dashboard, api): enhance error handling in content-templates…
djabarovgeorge Apr 22, 2025
9716462
refactor(dashboard): implement navigation for self-hosted environment…
djabarovgeorge Apr 22, 2025
d6df4e5
refactor(dashboard): update workflow row and general settings for sel…
djabarovgeorge Apr 22, 2025
d9685f5
refactor(api, dashboard): enhance organization creation with API serv…
djabarovgeorge Apr 22, 2025
19ca2db
refactor(dashboard): update activity filters to use API service level…
djabarovgeorge Apr 23, 2025
c7b3150
Merge branch 'next' into self-hosted-auth
djabarovgeorge Apr 27, 2025
1652325
fix(dashboard): validate Clerk Publishable Key only when not self-hosted
djabarovgeorge Apr 27, 2025
0d91285
refactor(dashboard): streamline organization switcher component and e…
djabarovgeorge Apr 27, 2025
4030708
fix(dashboard): improve tooltip content and enhance avatar animation …
djabarovgeorge Apr 27, 2025
c16270d
feat(dashboard): add UserAvatar component and integrate it into UserB…
djabarovgeorge Apr 27, 2025
6fdbc74
feat(dashboard): enhance workflow row with tooltip for unsupported wo…
djabarovgeorge Apr 27, 2025
1266c9e
fix(dashboard): update NovuBrandingSwitch to handle self-hosted scena…
djabarovgeorge Apr 27, 2025
77f3328
fix(dashboard): update FreeTierState and NovuBrandingSwitch for self-…
djabarovgeorge Apr 27, 2025
da6f1fa
refactor(dashboard): standardize SVG attributes and enhance SideNavig…
djabarovgeorge Apr 27, 2025
a1501f0
fix(dashboard): update FreeTierState component to enhance messaging a…
djabarovgeorge Apr 27, 2025
82bb22e
fix(dashboard): handle undefined subscription in UsageCard to prevent…
djabarovgeorge Apr 27, 2025
8c1da05
refactor(dashboard): remove unused imports in restricted-switch and o…
djabarovgeorge Apr 27, 2025
2ac18d1
refactor(root): remove unused imports and dependencies for cleaner co…
djabarovgeorge Apr 28, 2025
2915109
feat(docker): add Dockerfile and update package.json for Docker build…
merrcury Apr 28, 2025
802c72a
Merge branch 'self-hosted-auth' of github.com:novuhq/novu into self-h…
merrcury Apr 28, 2025
a7dd8e5
Merge branch 'self-hosted-auth' of github.com:novuhq/novu into self-h…
djabarovgeorge Apr 28, 2025
b058e0d
refactor(code-snippets): enhance server URL handling for self-hosted …
djabarovgeorge Apr 28, 2025
d7bb330
refactor(auth): rename SystemOrganizationService to CommunityEditionS…
djabarovgeorge Apr 28, 2025
efefe2d
refactor(auth): typo
djabarovgeorge Apr 28, 2025
fe533c5
fix(auth): update COMMUNITY_EDITION_USER_EMAIL to use example domain …
djabarovgeorge Apr 28, 2025
0d00312
refactor(dashboard): replace RestrictedSwitch with Popover and LinkBu…
djabarovgeorge Apr 28, 2025
aa4d70c
refactor(dashboard): update NovuBrandingSwitch to include self-hosted…
djabarovgeorge Apr 28, 2025
b604655
refactor(dashboard): enhance organization and user buttons with self-…
djabarovgeorge Apr 28, 2025
3c289e5
Merge branch 'next' into self-hosted-auth
djabarovgeorge Apr 28, 2025
9a70325
fix(dashboard): after next merge
djabarovgeorge Apr 28, 2025
4ec6bbf
Merge branch 'next' into self-hosted-auth
djabarovgeorge Apr 29, 2025
a555ff9
refactor(organization-switcher): update button styles and text for im…
djabarovgeorge Apr 29, 2025
80c1d57
refactor(dashboard): implement self-hosted redirection for welcome an…
djabarovgeorge Apr 29, 2025
cb8a9c5
refactor(header-navigation): update customer support button to handle…
djabarovgeorge Apr 29, 2025
60517af
refactor(user-button): enhance dropdown menu with new self-hosted upg…
djabarovgeorge Apr 29, 2025
be8f315
refactor(workflow-row): update self-hosted upgrade link to use dynami…
djabarovgeorge Apr 29, 2025
1540978
refactor(auth): replace CommunityEditionService with SelfHostUsecase …
djabarovgeorge Apr 30, 2025
145cf23
refactor(auth): update SelfHostUsecase to use CommunityOrganizationRe…
djabarovgeorge Apr 30, 2025
2e31aed
refactor(api): simplify isMember check by passing organizationId dire…
djabarovgeorge Apr 30, 2025
d95ad6f
refactor(root): after pr comments
djabarovgeorge Apr 30, 2025
3aa7b4b
refactor(user-button): remove user email display from dropdown menu f…
djabarovgeorge Apr 30, 2025
4899262
feat(dashboard): update after pr comments
djabarovgeorge Apr 30, 2025
ba2cc2f
Merge branch 'next' into self-hosted-auth
djabarovgeorge May 5, 2025
623f319
feat(root, dashboard): new dockerfile for the dashboard service (#8221)
merrcury May 5, 2025
eef014d
fix(dashboard): remove email as user name
djabarovgeorge May 5, 2025
2299dce
Merge branch 'self-hosted-auth' of github.com:novuhq/novu into self-h…
djabarovgeorge May 5, 2025
198c861
feat(api): implement self-host secret guard and update auth controlle…
djabarovgeorge May 5, 2025
f4c539f
Merge branch 'next' into self-hosted-auth
djabarovgeorge May 6, 2025
14a245e
refactor(dashboard): modularize demo workflow initialization and clea…
djabarovgeorge May 6, 2025
8e10051
Merge branch 'next' into self-hosted-auth
djabarovgeorge May 6, 2025
4a22cb4
fix(api): adjust max snooze duration logic based on NOVU_ENTERPRISE e…
djabarovgeorge May 6, 2025
92f60d7
refactor(auth): rename self-host secret key to self-host token and up…
djabarovgeorge May 6, 2025
6e262f5
chore(docker): add SELF_HOSTED_TOKEN and VITE_SELF_HOSTED_TOKEN to do…
djabarovgeorge May 6, 2025
e137711
fix(auth): update error messages in SelfHostSecretGuard for clarity
djabarovgeorge May 7, 2025
6b6f1f3
fix(dashboard): add bell for test page
djabarovgeorge May 7, 2025
690730a
Merge branch 'next' into self-hosted-auth
djabarovgeorge May 7, 2025
a6eb8b4
Merge branch 'next' into self-hosted-auth
djabarovgeorge May 7, 2025
c00e159
fix(dashboard): remove TypeScript error suppression in maily-config
djabarovgeorge May 7, 2025
e579651
fix(api-service): ci run
djabarovgeorge May 8, 2025
1482cc9
fix(api): update test command and simplify maxSnoozeDurationHours logic
djabarovgeorge May 8, 2025
08b78dc
Merge branch 'next' into self-hosted-auth
djabarovgeorge May 8, 2025
4a9a17e
refactor(api): replace hardcoded COMMUNITY_EDITION_NAME with helper f…
djabarovgeorge May 8, 2025
fa7a6d6
fix(api-service): ci test
djabarovgeorge May 8, 2025
f0439a2
fix(api-service): ci tests
djabarovgeorge May 8, 2025
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: 2 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,8 @@
"oklch",
"Duplicable",
"Radek",
"automators",
"VITE",
"snooze",
"unsnooze",
"snoozed",
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/on-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,18 @@ jobs:
steps:
- run: echo ${{ matrix.projectName }}
- uses: actions/checkout@v4
name: Checkout with submodules
with:
submodules: true
token: ${{ secrets.SUBMODULES_TOKEN }}

- uses: ./.github/actions/setup-project
with:
# Don't run redis and etc... for other unit tests
slim: ${{ !contains(matrix.projectName, '@novu/api-service') && !contains(matrix.projectName, '@novu/worker') && !contains(matrix.projectName, '@novu/ws') && !contains(matrix.projectName, '@novu/inbound-mail')}}

- uses: ./.github/actions/setup-redis-cluster

- uses: mansagroup/nrwl-nx-action@v3
name: Lint and build and test
with:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/prepare-self-hosted-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
fail-fast: false
matrix:
name: ['novu/api', 'novu/worker', 'novu/web', 'novu/webhook', 'novu/ws']
name: ['novu/api', 'novu/worker', 'novu/dashboard', 'novu/webhook', 'novu/ws']
steps:
- name: Git Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -97,8 +97,8 @@ jobs:

if [ "${{ env.SERVICE_NAME }}" == "worker" ]; then
cd src/ && echo -e "\nIS_SELF_HOSTED=true\nOS_TELEMETRY_URL=\"${{ secrets.OS_TELEMETRY_URL }}\"" >> .example.env && cd ..
elif [ "${{ env.SERVICE_NAME }}" == "web" ]; then
echo -e "\nIS_V2_ENABLED=true" >> .env.sample
elif [ "${{ env.SERVICE_NAME }}" == "dashboard" ]; then
echo -e "\nVITE_SELF_HOSTED=true" >> .env
fi

pnpm run docker:build
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/reusable-api-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ jobs:
id-token: write
packages: write
steps:
# Checkout with EE-submodule if token provided
- uses: actions/checkout@v4
name: Checkout with submodules
if: ${{ inputs.ee }}
Expand Down
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"pretest": "pnpm build:metadata",
"generate:swagger": "cross-env NODE_ENV=test ts-node exportOpenAPIJSON.ts",
"generate:sdk": " (cd ../../libs/internal-sdk && speakeasy run --skip-compile --minimal --skip-versioning) && (cd ../../libs/internal-sdk && pnpm build) ",
"test": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' NODE_ENV=test mocha --timeout 5000 --require ts-node/register --exit 'src/**/*.spec.ts'",
"test": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' NODE_ENV=test NOVU_ENTERPRISE=true CLERK_ENABLED=true mocha --timeout 5000 --require ts-node/register --exit 'src/**/*.spec.ts'",
"test:e2e:novu-v0": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' NODE_ENV=test mocha --timeout 5000 --retries 3 --grep '#novu-v0' --require ts-node/register --exit --file e2e/setup.ts src/**/*.e2e{,-ee}.ts",
"test:e2e:novu-v2": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' NODE_ENV=test CI_EE_TEST=true CLERK_ENABLED=true NODE_OPTIONS=--max_old_space_size=8192 mocha --timeout 5000 --retries 3 --grep '#novu-v2' --require ts-node/register --exit --file e2e/setup.ts src/**/*.e2e{,-ee}.ts",
"migration": "cross-env NODE_ENV=local MIGRATION=true ts-node --transpileOnly",
Expand Down
13 changes: 10 additions & 3 deletions apps/api/src/app/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ import { UpdatePasswordBodyDto } from './dtos/update-password.dto';
import { UpdatePassword } from './usecases/update-password/update-password.usecase';
import { UpdatePasswordCommand } from './usecases/update-password/update-password.command';
import { UserAuthentication } from '../shared/framework/swagger/api.key.security';
import { SwitchEnvironmentCommand } from './usecases/switch-environment/switch-environment.command';
import { SwitchEnvironment } from './usecases/switch-environment/switch-environment.usecase';
import { SwitchOrganizationCommand } from './usecases/switch-organization/switch-organization.command';
import { SwitchOrganization } from './usecases/switch-organization/switch-organization.usecase';
import { AuthService } from './services/auth.service';
import { SelfHostUsecase } from './usecases/self-host/self-host.usecase';
import { SelfHostSecretGuard } from './framework/self-host-secret.guard';

@ApiCommonResponses()
@Controller('/auth')
Expand All @@ -60,7 +60,8 @@ export class AuthController {
private passwordResetRequestUsecase: PasswordResetRequest,
private passwordResetUsecase: PasswordReset,
private updatePasswordUsecase: UpdatePassword,
private logger: PinoLogger
private logger: PinoLogger,
private selfHostUsecase: SelfHostUsecase
) {
this.logger.setContext(this.constructor.name);
}
Expand Down Expand Up @@ -190,4 +191,10 @@ export class AuthController {

return await this.authService.getSignedToken(user, organizationId, member as MemberEntity);
}

@Get('/self-hosted')
@UseGuards(SelfHostSecretGuard)
async logMeIn() {
return this.selfHostUsecase.execute();
}
}
12 changes: 11 additions & 1 deletion apps/api/src/app/auth/community.auth.module.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { AuthService } from './services/auth.service';
import { RolesGuard } from './framework/roles.guard';
import { CommunityAuthService } from './services/community.auth.service';
import { CommunityUserAuthGuard } from './framework/community.user.auth.guard';
import { CommunityEditionService } from './services/system-organization.service';
import { SelfHostUsecase } from './usecases/self-host/self-host.usecase';

const AUTH_STRATEGIES: Provider[] = [JwtStrategy, ApiKeyStrategy, JwtSubscriberStrategy];

Expand All @@ -39,7 +41,14 @@ export function getCommunityAuthModuleConfig(): ModuleMetadata {
}),
];

const baseProviders = [...AUTH_STRATEGIES, AuthService, RolesGuard, RootEnvironmentGuard];
const baseProviders = [
...AUTH_STRATEGIES,
AuthService,
RolesGuard,
RootEnvironmentGuard,
CommunityEditionService,
SelfHostUsecase,
];

// Wherever is the string token used, override it with the provider
const injectableProviders = [
Expand Down Expand Up @@ -78,6 +87,7 @@ export function getCommunityAuthModuleConfig(): ModuleMetadata {
'USER_REPOSITORY',
'MEMBER_REPOSITORY',
'ORGANIZATION_REPOSITORY',
CommunityEditionService,
],
};
}
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/app/auth/e2e/link-entities.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import sinon from 'sinon';
import { CLERK_ORGANIZATION_1, CLERK_USER_1, ClerkClientMock } from '@novu/testing';
import mongoose from 'mongoose';
import { AnalyticsService } from '@novu/application-generic';
import { AnalyticsService, createNestLoggingModuleOptions, LoggerModule, PinoLogger } from '@novu/application-generic';
import { GetOrganization } from '../../organization/usecases/get-organization/get-organization.usecase';
import { SyncExternalOrganization } from '../../organization/usecases/create-organization/sync-external-organization/sync-external-organization.usecase';
import { CreateEnvironment } from '../../environments-v1/usecases/create-environment/create-environment.usecase';
Expand Down Expand Up @@ -60,6 +60,7 @@ describe('Link external and internal entities #novu-v2', () => {

beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [LoggerModule.forRoot(createNestLoggingModuleOptions({ serviceName: 'test', version: '0.0.1' }))],
providers: [
LinkEntitiesService,
CommunityUserRepository,
Expand Down
25 changes: 25 additions & 0 deletions apps/api/src/app/auth/framework/self-host-secret.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { HttpRequestHeaderKeysEnum } from '@novu/application-generic';

@Injectable()
export class SelfHostSecretGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const secretKey = process.env.SELF_HOSTED_TOKEN;
if (!secretKey) return true;

const request = context.switchToHttp().getRequest();
const headerKey = request.headers[HttpRequestHeaderKeysEnum.NOVU_SELF_HOSTED_TOKEN.toLowerCase()];

if (!headerKey) {
throw new UnauthorizedException('Missing self-hosted token');
}

if (headerKey !== secretKey) {
throw new UnauthorizedException(
'Invalid self-hosted token, please validate that the API and Dashboard have the same token'
);
}

return true;
}
}
131 changes: 131 additions & 0 deletions apps/api/src/app/auth/services/system-organization.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { CommunityOrganizationRepository, UserEntity, UserRepository } from '@novu/dal';
import { PinoLogger } from '@novu/application-generic';
import { ApiServiceLevelEnum } from '@novu/shared';
import { CreateOrganization } from '../../organization/usecases/create-organization/create-organization.usecase';
import { CreateOrganizationCommand } from '../../organization/usecases/create-organization/create-organization.command';
import { UserRegister } from '../usecases/register/user-register.usecase';
import { UserRegisterCommand } from '../usecases/register/user-register.command';
import { COMMUNITY_EDITION_NAME, getSelfHostedFindQuery } from '../../shared/helpers/self-hosted';

@Injectable()
export class CommunityEditionService implements OnModuleInit {
private readonly E11000_DUPLICATE_KEY_ERROR_CODE = 'E11000';
private readonly COMMUNITY_EDITION_USER_EMAIL = '[email protected]';

constructor(
@Inject('ORGANIZATION_REPOSITORY')
private organizationRepository: CommunityOrganizationRepository,
private createOrganizationUsecase: CreateOrganization,
private userRegisterUsecase: UserRegister,
private userRepository: UserRepository,
private logger: PinoLogger
) {}

async onModuleInit() {
try {
await this.initializeCommunityEditionOrganization();
} catch (error) {
this.logger.error({ err: error }, 'Failed to initialize Self-Hosted Community Edition Setup during module init');
throw error;
}
}

private async initializeCommunityEditionOrganization(): Promise<void> {
await this.organizationRepository.withTransaction(async () => {
let communityEditionOrg = await this.organizationRepository.findOne(getSelfHostedFindQuery());

if (communityEditionOrg) {
this.logger.info(
'Self Hosted is already initialized, skipping Community Edition creation. ' +
`Organization already exists with ID: ${communityEditionOrg._id}`
);

return;
}

this.logger.info('Community Edition not found, creating it');

try {
let user = await this.userRepository.findByEmail(this.COMMUNITY_EDITION_USER_EMAIL);
if (!user) {
user = await this.createCommunityEditionUser();
}

this.logger.debug(`Retrieved Community User with ID: ${user._id}`);

const organization = await this.createOrganizationUsecase.execute(
CreateOrganizationCommand.create({
userId: user._id,
name: COMMUNITY_EDITION_NAME,
apiServiceLevel: ApiServiceLevelEnum.UNLIMITED,
})
);

this.logger.debug(`Retrieved Community Edition with ID: ${organization?._id}`);
} catch (error) {
const isDuplicateKeyError =
error instanceof Error &&
error.message.includes(this.E11000_DUPLICATE_KEY_ERROR_CODE) &&
error.message.includes(COMMUNITY_EDITION_NAME);

if (!isDuplicateKeyError) {
throw error;
}

this.logger.warn('Duplicate key error, another instance may have created the Community Edition');
communityEditionOrg = await this.organizationRepository.findOne(getSelfHostedFindQuery());
if (!communityEditionOrg) {
this.logger.error('Failed to retrieve Community Edition after duplicate key error');
throw error;
}

this.logger.info(`Retrieved Community Edition created by another instance with ID: ${communityEditionOrg._id}`);
}
});
}

private async createCommunityEditionUser(): Promise<UserEntity> {
try {
const { user } = await this.userRegisterUsecase.execute(
UserRegisterCommand.create({
email: this.COMMUNITY_EDITION_USER_EMAIL,
firstName: 'Community',
lastName: 'User',
password: 'communityUser1q@W#',
})
);

if (!user?._id) {
throw new Error('Failed to create community user');
}

return user;
} catch (error) {
const isDuplicateKeyDatabaseError =
error instanceof Error &&
error.message.includes(this.E11000_DUPLICATE_KEY_ERROR_CODE) &&
error.message.includes(this.COMMUNITY_EDITION_USER_EMAIL);
const isUserAlreadyExistsUsecaseError = error.message.includes('User already exists');

if (!isDuplicateKeyDatabaseError && !isUserAlreadyExistsUsecaseError) {
throw error;
}

this.logger.warn('Duplicate key error, another instance may have created the Community User');
const user = await this.userRepository.findByEmail(this.COMMUNITY_EDITION_USER_EMAIL);
if (!user) {
this.logger.error('Failed to retrieve Community User after duplicate key error');
throw error;
}

this.logger.info(`Retrieved Community User created by another instance with ID: ${user._id}`);

if (!user?._id) {
throw new Error('Failed to create Community user');
}

return user;
}
}
}
Empty file.
37 changes: 37 additions & 0 deletions apps/api/src/app/auth/usecases/self-host/self-host.usecase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Inject, NotFoundException } from '@nestjs/common';
import { CommunityOrganizationRepository, MemberRepository } from '@novu/dal';
import { SwitchOrganization } from '../switch-organization/switch-organization.usecase';
import { SwitchOrganizationCommand } from '../switch-organization';
import { getSelfHostedFindQuery } from '../../../shared/helpers/self-hosted';

export class SelfHostUsecase {
constructor(
@Inject('ORGANIZATION_REPOSITORY')
private organizationRepository: CommunityOrganizationRepository,
private memberRepository: MemberRepository,
private readonly switchOrganizationUsecase: SwitchOrganization
) {}

async execute() {
const communityEditionOrg = await this.organizationRepository.findOne(getSelfHostedFindQuery());

if (!communityEditionOrg) {
throw new NotFoundException('Community Edition not found');
}

const users = await this.memberRepository.getOrganizationMembers(communityEditionOrg._id);

if (!users || users.length === 0) {
throw new NotFoundException('No admin users found for Community Edition');
}

const token = await this.switchOrganizationUsecase.execute(
SwitchOrganizationCommand.create({
newOrganizationId: communityEditionOrg._id!,
userId: users[0]._userId,
})
);

return { token };
}
}
3 changes: 2 additions & 1 deletion apps/api/src/app/inbox/usecases/session/session.usecase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ export class Session {
const token = await this.authService.getSubscriberWidgetToken(subscriber);

const removeNovuBranding = inAppIntegration.removeNovuBranding || false;
const maxSnoozeDurationHours = await this.getMaxSnoozeDurationHours(environment);
const maxSnoozeDurationHours =
process.env.NOVU_ENTERPRISE === 'true' ? await this.getMaxSnoozeDurationHours(environment) : 0;

/**
* We want to prevent the playground inbox demo from marking the integration as connected
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IsDefined, IsEnum, IsOptional, IsString } from 'class-validator';

import { JobTitleEnum } from '@novu/shared';
import { ApiServiceLevelEnum, JobTitleEnum } from '@novu/shared';

import { AuthenticatedCommand } from '../../../shared/commands/authenticated.command';

Expand All @@ -23,4 +23,8 @@ export class CreateOrganizationCommand extends AuthenticatedCommand {

@IsOptional()
language?: string[];

@IsOptional()
@IsEnum(ApiServiceLevelEnum)
apiServiceLevel?: ApiServiceLevelEnum;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable, Scope, BadRequestException } from '@nestjs/common';
import { Inject, BadRequestException, Injectable } from '@nestjs/common';
import { AnalyticsService } from '@novu/application-generic';
import { OrganizationEntity, OrganizationRepository, UserRepository } from '@novu/dal';
import { ApiServiceLevelEnum, EnvironmentEnum, JobTitleEnum, MemberRoleEnum } from '@novu/shared';
Expand All @@ -11,9 +11,7 @@ import { AddMemberCommand } from '../membership/add-member/add-member.command';
import { AddMember } from '../membership/add-member/add-member.usecase';
import { CreateOrganizationCommand } from './create-organization.command';

@Injectable({
scope: Scope.REQUEST,
})
@Injectable()
export class CreateOrganization {
constructor(
private readonly organizationRepository: OrganizationRepository,
Expand All @@ -31,7 +29,7 @@ export class CreateOrganization {
const createdOrganization = await this.organizationRepository.create({
logo: command.logo,
name: command.name,
apiServiceLevel: ApiServiceLevelEnum.FREE,
apiServiceLevel: command.apiServiceLevel || ApiServiceLevelEnum.FREE,
domain: command.domain,
language: command.language,
});
Expand Down
Loading
Loading