Skip to content

Commit

Permalink
Merge branch 'next' into nv-4512-configure-step-delete-functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
ChmaraX committed Nov 5, 2024
2 parents 5bc1878 + 0999d22 commit 8fac515
Show file tree
Hide file tree
Showing 53 changed files with 732 additions and 818 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" ]
63 changes: 31 additions & 32 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,50 @@
/* 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 { 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';

const enterpriseImports = (): Array<Type | DynamicModule | Promise<DynamicModule> | ForwardReference> => {
const modules: Array<Type | DynamicModule | Promise<DynamicModule> | ForwardReference> = [];
Expand Down Expand Up @@ -106,7 +106,6 @@ const baseModules: Array<Type | DynamicModule | Promise<DynamicModule> | Forward
BridgeModule,
PreferencesModule,
WorkflowModule,
GracefulShutdownConfigModule.forRootAsync(),
EnvironmentsModule,
];

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
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 @@ -5,7 +5,7 @@ import { IdentifierOrInternalId } from '@novu/shared';
export class GetStepDataCommand extends EnvironmentWithUserObjectCommand {
@IsString()
@IsNotEmpty()
workflowId: IdentifierOrInternalId;
identifierOrInternalId: IdentifierOrInternalId;

@IsString()
@IsNotEmpty()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { ControlValuesLevelEnum, StepDataDto } from '@novu/shared';
import { JSONSchema } from 'json-schema-to-ts';
import { ControlValuesRepository, NotificationStepEntity } from '@novu/dal';
import { ControlValuesRepository, NotificationStepEntity, NotificationTemplateEntity } from '@novu/dal';
import { GetStepDataCommand } from './get-step-data.command';
import { mapStepTypeToResult } from '../../shared';
import { GetWorkflowByIdsUseCase } from '../get-workflow-by-ids/get-workflow-by-ids.usecase';
Expand All @@ -15,7 +15,9 @@ export class GetStepDataUsecase {
) {}

async execute(command: GetStepDataCommand): Promise<StepDataDto> {
const { currentStep, previousSteps } = await this.findSteps(command);
const workflow = await this.fetchWorkflow(command);

const { currentStep, previousSteps } = await this.findSteps(command, workflow);
if (!currentStep.name || !currentStep._templateId || !currentStep.stepId) {
throw new InvalidStepException(currentStep);
}
Expand All @@ -24,7 +26,7 @@ export class GetStepDataUsecase {
controls: {
dataSchema: currentStep.template?.controls?.schema,
uiSchema: currentStep.template?.controls?.uiSchema,
values: await this.getValues(command, currentStep),
values: await this.getValues(command, currentStep, workflow._id),
},
variables: buildVariablesSchema(previousSteps),
name: currentStep.name,
Expand All @@ -33,38 +35,44 @@ export class GetStepDataUsecase {
};
}

private async getValues(command: GetStepDataCommand, currentStep: NotificationStepEntity) {
const controlValuesEntity = await this.controlValuesRepository.findOne({
_environmentId: command.user.environmentId,
_organizationId: command.user.organizationId,
_workflowId: command.workflowId,
_stepId: currentStep._templateId,
level: ControlValuesLevelEnum.STEP_CONTROLS,
});

return controlValuesEntity?.controls || {};
}

private async findSteps(command: GetStepDataCommand) {
private async fetchWorkflow(command: GetStepDataCommand) {
const workflow = await this.getWorkflowByIdsUseCase.execute({
identifierOrInternalId: command.workflowId,
identifierOrInternalId: command.identifierOrInternalId,
user: command.user,
});

if (!workflow) {
throw new BadRequestException({
message: 'No workflow found',
workflowId: command.workflowId,
workflowId: command.identifierOrInternalId,
});
}

const currentStep = workflow.steps.find((stepItem) => stepItem._id === command.stepId);
return workflow;
}

private async getValues(command: GetStepDataCommand, currentStep: NotificationStepEntity, _workflowId: string) {
const controlValuesEntity = await this.controlValuesRepository.findOne({
_environmentId: command.user.environmentId,
_organizationId: command.user.organizationId,
_workflowId,
_stepId: currentStep._templateId,
level: ControlValuesLevelEnum.STEP_CONTROLS,
});

return controlValuesEntity?.controls || {};
}

private async findSteps(command: GetStepDataCommand, workflow: NotificationTemplateEntity) {
const currentStep = workflow.steps.find(
(stepItem) => stepItem._id === command.stepId || stepItem.stepId === command.stepId
);

if (!currentStep) {
throw new BadRequestException({
message: 'No step found',
stepId: command.stepId,
workflowId: command.workflowId,
workflowId: command.identifierOrInternalId,
});
}

Expand Down
Loading

0 comments on commit 8fac515

Please sign in to comment.