Skip to content

Commit

Permalink
Merge branch 'next' into support-controller
Browse files Browse the repository at this point in the history
  • Loading branch information
jainpawan21 committed Nov 5, 2024
2 parents 587c574 + 0999d22 commit ca1307d
Show file tree
Hide file tree
Showing 86 changed files with 975 additions and 974 deletions.
44 changes: 0 additions & 44 deletions apps/api/.dockerignore

This file was deleted.

74 changes: 46 additions & 28 deletions apps/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,54 +1,72 @@
# Use the base image for development
FROM ghcr.io/novuhq/novu/base:1.0.0 AS dev
FROM node:20-alpine3.19 AS dev_base
RUN apk add g++ make py3-pip

ENV NX_DAEMON=false

RUN npm i pm2 -g
RUN npm --no-update-notifier --no-fund --global install [email protected]
RUN pnpm --version

USER 1000
WORKDIR /usr/src/app

# ------- DEV BUILD ----------
FROM dev_base AS dev
ARG PACKAGE_PATH

# Copy necessary directories to the image
COPY --chown=1000:1000 ./meta ./deps ./pkg ./
COPY --chown=1000:1000 ./meta .
COPY --chown=1000:1000 ./deps .
COPY --chown=1000:1000 ./pkg .

RUN --mount=type=secret,id=BULL_MQ_PRO_NPM_TOKEN,uid=1000 export BULL_MQ_PRO_NPM_TOKEN=$(cat /run/secrets/BULL_MQ_PRO_NPM_TOKEN) && \
if [ -n "${BULL_MQ_PRO_NPM_TOKEN}" ] ; then echo 'Building with Enterprise Edition of Novu'; rm -f .npmrc ; cp .npmrc-cloud .npmrc ; fi

RUN --mount=type=cache,id=pnpm-store-api,target=/root/.pnpm-store\
--mount=type=secret,id=BULL_MQ_PRO_NPM_TOKEN,uid=1000 export BULL_MQ_PRO_NPM_TOKEN=$(cat /run/secrets/BULL_MQ_PRO_NPM_TOKEN) && \
pnpm install --filter "novuhq" --filter "{${PACKAGE_PATH}}..."\
--frozen-lockfile\
--unsafe-perm

# Install dependencies and build the project
RUN --mount=type=secret,id=BULL_MQ_PRO_NPM_TOKEN,uid=1000 \
BULL_MQ_PRO_NPM_TOKEN=$(cat /run/secrets/BULL_MQ_PRO_NPM_TOKEN) && \
[ -n "$BULL_MQ_PRO_NPM_TOKEN" ] && echo 'Building with Enterprise Edition of Novu' && \
rm -f .npmrc && cp .npmrc-cloud .npmrc || true && \
pnpm install --filter "novuhq" --filter "{${PACKAGE_PATH}}..." --frozen-lockfile --unsafe-perm && \
NODE_ENV=production NX_DAEMON=false pnpm build:api
RUN --mount=type=secret,id=BULL_MQ_PRO_NPM_TOKEN,uid=1000 export BULL_MQ_PRO_NPM_TOKEN=$(cat /run/secrets/BULL_MQ_PRO_NPM_TOKEN) && NODE_ENV=production NX_DAEMON=false pnpm build:api

# Set the working directory to the API app and copy example environment file
WORKDIR /usr/src/app/apps/api

RUN cp src/.example.env dist/.env
RUN cp src/.env.test dist/.env.test
RUN cp src/.env.development dist/.env.development
RUN cp src/.env.production dist/.env.production

# Set the working directory to the root of the app
WORKDIR /usr/src/app

# ------- ASSETS BUILD ----------
# Create a new stage for building assets
FROM dev AS assets

# Remove node_modules and source directories
WORKDIR /usr/src/app

# Remove all dependencies so later we can only install prod dependencies without devDependencies
RUN rm -rf node_modules && pnpm recursive exec -- rm -rf ./src ./node_modules

# ------- PRODUCTION BUILD ----------
# Use the base image for production
FROM ghcr.io/novuhq/novu/base:1.0.0 AS prod
FROM dev_base AS prod

ARG PACKAGE_PATH

# Set environment variables for production
ENV CI=true
ENV NEW_RELIC_NO_CONFIG_FILE=true

# Set the working directory to the root of the app
WORKDIR /usr/src/app

# Copy necessary directories from the build stage
COPY --chown=1000:1000 ./meta ./
COPY --chown=1000:1000 ./meta .

# Get the build artifacts that only include dist folders
COPY --chown=1000:1000 --from=assets /usr/src/app .

# Install production dependencies
RUN --mount=type=cache,id=pnpm-store-api,target=/root/.pnpm-store \
--mount=type=secret,id=BULL_MQ_PRO_NPM_TOKEN,uid=1000 \
pnpm install --filter "{${PACKAGE_PATH}}..." --frozen-lockfile --unsafe-perm --prod
RUN --mount=type=cache,id=pnpm-store-api,target=/root/.pnpm-store\
--mount=type=secret,id=BULL_MQ_PRO_NPM_TOKEN,uid=1000 export BULL_MQ_PRO_NPM_TOKEN=$(cat /run/secrets/BULL_MQ_PRO_NPM_TOKEN) && \
pnpm install --filter "{${PACKAGE_PATH}}..." \
--frozen-lockfile \
--unsafe-perm

ENV NEW_RELIC_NO_CONFIG_FILE=true

# Set the working directory to the API app and start the application using pm2-runtime
WORKDIR /usr/src/app/apps/api
CMD [ "pm2-runtime", "start", "dist/main.js" ]
CMD [ "pm2-runtime","start", "dist/main.js" ]
65 changes: 32 additions & 33 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,51 @@
/* eslint-disable global-require */
import { DynamicModule, Logger, Module, Provider } from '@nestjs/common';
import { ForwardReference } from '@nestjs/common/interfaces/modules/forward-reference.interface';
import { Type } from '@nestjs/common/interfaces/type.interface';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { GracefulShutdownConfigModule, ProfilingModule, TracingModule } from '@novu/application-generic';
import { Type } from '@nestjs/common/interfaces/type.interface';
import { ForwardReference } from '@nestjs/common/interfaces/modules/forward-reference.interface';
import { ProfilingModule, TracingModule } from '@novu/application-generic';
import { isClerkEnabled } from '@novu/shared';
import { SentryModule } from '@sentry/nestjs/setup';
import packageJson from '../package.json';
import { AnalyticsModule } from './app/analytics/analytics.module';
import { SharedModule } from './app/shared/shared.module';
import { UserModule } from './app/user/user.module';
import { AuthModule } from './app/auth/auth.module';
import { BlueprintModule } from './app/blueprint/blueprint.module';
import { BridgeModule } from './app/bridge/bridge.module';
import { ChangeModule } from './app/change/change.module';
import { ContentTemplatesModule } from './app/content-templates/content-templates.module';
import { EnvironmentsModuleV1 } from './app/environments-v1/environments-v1.module';
import { EnvironmentsModule } from './app/environments-v2/environments.module';
import { SupportModule } from './app/support/support.module';
import { EventsModule } from './app/events/events.module';
import { ExecutionDetailsModule } from './app/execution-details/execution-details.module';
import { FeedsModule } from './app/feeds/feeds.module';
import { TestingModule } from './app/testing/testing.module';
import { HealthModule } from './app/health/health.module';
import { InboundParseModule } from './app/inbound-parse/inbound-parse.module';
import { InboxModule } from './app/inbox/inbox.module';
import { IntegrationModule } from './app/integrations/integrations.module';
import { OrganizationModule } from './app/organization/organization.module';
import { ExecutionDetailsModule } from './app/execution-details/execution-details.module';
import { EventsModule } from './app/events/events.module';
import { WidgetsModule } from './app/widgets/widgets.module';
import { NotificationModule } from './app/notifications/notification.module';
import { StorageModule } from './app/storage/storage.module';
import { NotificationGroupsModule } from './app/notification-groups/notification-groups.module';
import { InvitesModule } from './app/invites/invites.module';
import { ContentTemplatesModule } from './app/content-templates/content-templates.module';
import { IntegrationModule } from './app/integrations/integrations.module';
import { ChangeModule } from './app/change/change.module';
import { SubscribersModule } from './app/subscribers/subscribers.module';
import { FeedsModule } from './app/feeds/feeds.module';
import { LayoutsModule } from './app/layouts/layouts.module';
import { MessagesModule } from './app/messages/messages.module';
import { NotificationGroupsModule } from './app/notification-groups/notification-groups.module';
import { NotificationModule } from './app/notifications/notification.module';
import { OrganizationModule } from './app/organization/organization.module';
import { PartnerIntegrationsModule } from './app/partner-integrations/partner-integrations.module';
import { PreferencesModule } from './app/preferences';
import { TopicsModule } from './app/topics/topics.module';
import { InboundParseModule } from './app/inbound-parse/inbound-parse.module';
import { BlueprintModule } from './app/blueprint/blueprint.module';
import { TenantModule } from './app/tenant/tenant.module';
import { IdempotencyInterceptor } from './app/shared/framework/idempotency.interceptor';
import { WorkflowOverridesModule } from './app/workflow-overrides/workflow-overrides.module';
import { ApiRateLimitInterceptor } from './app/rate-limiting/guards';
import { RateLimitingModule } from './app/rate-limiting/rate-limiting.module';
import { IdempotencyInterceptor } from './app/shared/framework/idempotency.interceptor';
import { ProductFeatureInterceptor } from './app/shared/interceptors/product-feature.interceptor';
import { SharedModule } from './app/shared/shared.module';
import { StorageModule } from './app/storage/storage.module';
import { SubscribersModule } from './app/subscribers/subscribers.module';
import { TenantModule } from './app/tenant/tenant.module';
import { TestingModule } from './app/testing/testing.module';
import { TopicsModule } from './app/topics/topics.module';
import { UserModule } from './app/user/user.module';
import { WidgetsModule } from './app/widgets/widgets.module';
import { WorkflowOverridesModule } from './app/workflow-overrides/workflow-overrides.module';
import { WorkflowModuleV1 } from './app/workflows-v1/workflow-v1.module';
import { AnalyticsModule } from './app/analytics/analytics.module';
import { InboxModule } from './app/inbox/inbox.module';
import { BridgeModule } from './app/bridge/bridge.module';
import { PreferencesModule } from './app/preferences';
import { WorkflowModule } from './app/workflows-v2/workflow.module';
import { WorkflowModuleV1 } from './app/workflows-v1/workflow-v1.module';
import { EnvironmentsModuleV1 } from './app/environments-v1/environments-v1.module';
import { EnvironmentsModule } from './app/environments-v2/environments.module';
import { SupportModule } from './app/support/support.module';

const enterpriseImports = (): Array<Type | DynamicModule | Promise<DynamicModule> | ForwardReference> => {
const modules: Array<Type | DynamicModule | Promise<DynamicModule> | ForwardReference> = [];
Expand Down Expand Up @@ -107,7 +107,6 @@ const baseModules: Array<Type | DynamicModule | Promise<DynamicModule> | Forward
BridgeModule,
PreferencesModule,
WorkflowModule,
GracefulShutdownConfigModule.forRootAsync(),
EnvironmentsModule,
SupportModule,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export class EnvironmentResponseDto {

@ApiProperty()
_parentId: string;

@ApiPropertyOptional()
slug?: string;
}

export interface IApiKeyDto {
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/app/environments-v1/novu-bridge.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Controller, Req, Res, Inject, Get, Post, Options } from '@nestjs/common';
import type { Request, Response } from 'express';
import { Request, Response } from 'express';
import { ApiExcludeController } from '@nestjs/swagger';
import { NovuClient } from '@novu/framework/nest';
import { NovuBridgeClient } from './novu-bridge-client';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { Injectable, Logger, NotFoundException, Scope } from '@nestjs/common';

import { EnvironmentEntity, EnvironmentRepository } from '@novu/dal';
import { decryptApiKey } from '@novu/application-generic';
import { ShortIsPrefixEnum, EnvironmentEnum } from '@novu/shared';

import { GetMyEnvironmentsCommand } from './get-my-environments.command';
import { EnvironmentResponseDto } from '../../dtos/environment-response.dto';
import { buildSlug } from '../../../shared/helpers/build-slug';

@Injectable({
scope: Scope.REQUEST,
Expand Down Expand Up @@ -42,6 +44,19 @@ export class GetMyEnvironments {
};
});

return decryptedApiKeysEnvironment;
const shortEnvName = shortenEnvironmentName(decryptedApiKeysEnvironment.name);

return {
...decryptedApiKeysEnvironment,
slug: buildSlug(shortEnvName, ShortIsPrefixEnum.ENVIRONMENT, decryptedApiKeysEnvironment._id),
};
}
}
function shortenEnvironmentName(name: string): string {
const mapToShotEnvName: Record<EnvironmentEnum, string> = {
[EnvironmentEnum.PRODUCTION]: 'prod',
[EnvironmentEnum.DEVELOPMENT]: 'dev',
};

return mapToShotEnvName[name] || name;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable global-require */
import { BadRequestException, Inject, Injectable, Logger, Scope } from '@nestjs/common';
import { OrganizationEntity, OrganizationRepository, UserRepository } from '@novu/dal';
import { ApiServiceLevelEnum, JobTitleEnum, MemberRoleEnum } from '@novu/shared';
import { ApiServiceLevelEnum, JobTitleEnum, MemberRoleEnum, EnvironmentEnum } from '@novu/shared';
import { AnalyticsService } from '@novu/application-generic';

import { ModuleRef } from '@nestjs/core';
Expand Down Expand Up @@ -59,7 +59,7 @@ export class CreateOrganization {
const devEnv = await this.createEnvironmentUsecase.execute(
CreateEnvironmentCommand.create({
userId: user._id,
name: 'Development',
name: EnvironmentEnum.DEVELOPMENT,
organizationId: createdOrganization._id,
})
);
Expand All @@ -75,7 +75,7 @@ export class CreateOrganization {
const prodEnv = await this.createEnvironmentUsecase.execute(
CreateEnvironmentCommand.create({
userId: user._id,
name: 'Production',
name: EnvironmentEnum.PRODUCTION,
organizationId: createdOrganization._id,
parentEnvironmentId: devEnv._id,
})
Expand Down
12 changes: 12 additions & 0 deletions apps/api/src/app/shared/helpers/build-slug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ShortIsPrefixEnum, Slug, slugify } from '@novu/shared';
import { encodeBase62 } from './base62';

const SLUG_DELIMITER = '_';

/**
* Builds a slug for a step based on the step name, the short prefix and the internal ID.
* @returns The slug for the entity, example: slug: "workflow-name_wf_AbC1Xyz9KlmNOpQr"
*/
export function buildSlug(entityName: string, shortIsPrefix: ShortIsPrefixEnum, internalId: string): Slug {
return `${slugify(entityName)}${SLUG_DELIMITER}${shortIsPrefix}${encodeBase62(internalId)}`;
}
3 changes: 2 additions & 1 deletion apps/api/src/app/workflows-v2/clients/workflows-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const createWorkflowClient = (baseUrl: string, headers: HeadersInit = {})
const getWorkflow = async (workflowId: string): Promise<NovuRestResult<WorkflowResponseDto, HttpError>> => {
return await baseClient.safeGet<WorkflowResponseDto>(`/v2/workflows/${workflowId}`);
};

const getWorkflowStepData = async (
workflowId: string,
stepId: string
Expand Down Expand Up @@ -95,6 +96,6 @@ export const createWorkflowClient = (baseUrl: string, headers: HeadersInit = {})
deleteWorkflow,
searchWorkflows,
getWorkflowTestData,
getWorkflowStepMetadata: getWorkflowStepData,
getWorkflowStepData,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import {
PreferencesResponseDto,
PreferencesTypeEnum,
ShortIsPrefixEnum,
Slug,
slugify,
StepResponseDto,
StepTypeEnum,
WorkflowListResponseDto,
Expand All @@ -15,9 +13,7 @@ import {
} from '@novu/shared';
import { NotificationStepEntity, NotificationTemplateEntity } from '@novu/dal';
import { GetPreferencesResponseDto } from '@novu/application-generic';
import { encodeBase62 } from '../../shared/helpers';

const SLUG_DELIMITER = '_';
import { buildSlug } from '../../shared/helpers/build-slug';

export function toResponseWorkflowDto(
template: NotificationTemplateEntity,
Expand Down Expand Up @@ -89,14 +85,6 @@ function toStepResponseDto(step: NotificationStepEntity): StepResponseDto {
} satisfies StepResponseDto;
}

/**
* Builds a slug for a step based on the step name, the short prefix and the internal ID.
* @returns The slug for the entity, example: slug: "workflow-name_wf_AbC1Xyz9KlmNOpQr"
*/
function buildSlug(entityName: string, shortIsPrefix: ShortIsPrefixEnum, internalId: string): Slug {
return `${slugify(entityName)}${SLUG_DELIMITER}${shortIsPrefix}${encodeBase62(internalId)}`;
}

function buildStepTypeOverview(step: NotificationStepEntity): StepTypeEnum | undefined {
return step.template?.type;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { IdentifierOrInternalId } from '@novu/shared';
export class GetStepDataCommand extends EnvironmentWithUserObjectCommand {
@IsString()
@IsNotEmpty()
workflowId: IdentifierOrInternalId;
identifierOrInternalId: IdentifierOrInternalId;

@IsString()
@IsNotEmpty()
Expand Down
Loading

0 comments on commit ca1307d

Please sign in to comment.