Skip to content

Commit

Permalink
Merge pull request novuhq#3994 from novuhq/nv-2403-tenant-id-trigger-…
Browse files Browse the repository at this point in the history
…engine-integration

Nv 2403 tenant id trigger engine integration
  • Loading branch information
ainouzgali authored Aug 23, 2023
2 parents ca6b16a + 7189ac0 commit 16603e3
Show file tree
Hide file tree
Showing 60 changed files with 747 additions and 107 deletions.
41 changes: 38 additions & 3 deletions apps/api/src/app/events/dtos/trigger-event-request.dto.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import { ArrayMaxSize, ArrayNotEmpty, IsArray, IsDefined, IsObject, IsOptional, IsString } from 'class-validator';
import {
ArrayMaxSize,
ArrayNotEmpty,
IsArray,
IsDefined,
IsObject,
IsOptional,
IsString,
ValidateIf,
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
import { ApiExtraModels, ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger';
import { TopicKey, TriggerRecipientSubscriber, TriggerRecipients, TriggerRecipientsTypeEnum } from '@novu/shared';
import { CreateSubscriberRequestDto } from '../../subscribers/dtos/create-subscriber-request.dto';
import {
TopicKey,
TriggerRecipientSubscriber,
TriggerRecipients,
TriggerRecipientsTypeEnum,
TriggerTenantContext,
} from '@novu/shared';
import { CreateSubscriberRequestDto } from '../../subscribers/dtos';
import { UpdateTenantRequestDto } from '../../tenant/dtos';

export class SubscriberPayloadDto extends CreateSubscriberRequestDto {}
export class TenantPayloadDto extends UpdateTenantRequestDto {}

export class TopicPayloadDto {
@ApiProperty()
Expand All @@ -14,6 +33,7 @@ export class TopicPayloadDto {
}

@ApiExtraModels(SubscriberPayloadDto)
@ApiExtraModels(TenantPayloadDto)
@ApiExtraModels(TopicPayloadDto)
export class TriggerEventRequestDto {
@ApiProperty({
Expand Down Expand Up @@ -93,6 +113,21 @@ export class TriggerEventRequestDto {
})
@IsOptional()
actor?: TriggerRecipientSubscriber;

@ApiProperty({
description: `It is used to specify a tenant context during trigger event.
If a new tenant object is provided, we will create a new tenant.
`,
oneOf: [
{ type: 'string', description: 'Unique identifier of a tenant in your system' },
{ $ref: getSchemaPath(TenantPayloadDto) },
],
})
@IsOptional()
@ValidateIf((_, value) => typeof value !== 'string')
@ValidateNested()
@Type(() => TenantPayloadDto)
tenant?: TriggerTenantContext;
}

export class BulkTriggerEventDto {
Expand Down
22 changes: 19 additions & 3 deletions apps/api/src/app/events/dtos/trigger-event-to-all-request.dto.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { IsDefined, IsObject, IsOptional, IsString } from 'class-validator';
import { IsDefined, IsObject, IsOptional, IsString, ValidateIf, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger';
import { TriggerRecipientSubscriber } from '@novu/shared';
import { TriggerRecipientSubscriber, TriggerTenantContext } from '@novu/shared';

import { SubscriberPayloadDto } from './trigger-event-request.dto';
import { SubscriberPayloadDto, TenantPayloadDto } from './trigger-event-request.dto';

export class TriggerEventToAllRequestDto {
@ApiProperty({
Expand Down Expand Up @@ -58,4 +59,19 @@ export class TriggerEventToAllRequestDto {
})
@IsOptional()
actor?: TriggerRecipientSubscriber;

@ApiProperty({
description: `It is used to specify a tenant context during trigger event.
If a new tenant object is provided, we will create a new tenant.
`,
oneOf: [
{ type: 'string', description: 'Unique identifier of a tenant in your system' },
{ $ref: getSchemaPath(TenantPayloadDto) },
],
})
@IsOptional()
@ValidateIf((_, value) => typeof value !== 'string')
@ValidateNested()
@Type(() => TenantPayloadDto)
tenant?: TriggerTenantContext;
}
19 changes: 18 additions & 1 deletion apps/api/src/app/events/events.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Body, Controller, Delete, Param, Post, Scope, UseGuards } from '@nestjs/common';
import { ApiOkResponse, ApiExcludeEndpoint, ApiOperation, ApiTags } from '@nestjs/swagger';
import { v4 as uuidv4 } from 'uuid';
import { IJwtPayload, ISubscribersDefine, TriggerRecipientSubscriber } from '@novu/shared';
import {
IJwtPayload,
ISubscribersDefine,
ITenantDefine,
TriggerRecipientSubscriber,
TriggerTenantContext,
} from '@novu/shared';
import { SendTestEmail, SendTestEmailCommand } from '@novu/application-generic';

import {
Expand Down Expand Up @@ -53,6 +59,8 @@ export class EventsController {
@UserSession() user: IJwtPayload,
@Body() body: TriggerEventRequestDto
): Promise<TriggerEventResponseDto> {
const mappedTenant = body.tenant ? this.mapTenant(body.tenant) : null;

const result = await this.parseEventRequest.execute(
ParseEventRequestCommand.create({
userId: user._id,
Expand All @@ -63,6 +71,7 @@ export class EventsController {
overrides: body.overrides || {},
to: body.to,
actor: body.actor,
tenant: mappedTenant,
transactionId: body.transactionId,
})
);
Expand Down Expand Up @@ -110,6 +119,7 @@ export class EventsController {
): Promise<TriggerEventResponseDto> {
const transactionId = body.transactionId || uuidv4();
const mappedActor = body.actor ? this.mapActor(body.actor) : null;
const mappedTenant = body.tenant ? this.mapTenant(body.tenant) : null;

return this.triggerEventToAll.execute(
TriggerEventToAllCommand.create({
Expand All @@ -118,6 +128,7 @@ export class EventsController {
organizationId: user.organizationId,
identifier: body.name,
payload: body.payload,
tenant: mappedTenant,
transactionId,
overrides: body.overrides || {},
actor: mappedActor,
Expand Down Expand Up @@ -177,4 +188,10 @@ export class EventsController {

return this.mapTriggerRecipients.mapSubscriber(actor);
}

private mapTenant(tenant?: TriggerTenantContext | null): ITenantDefine | null {
if (!tenant) return null;

return this.parseEventRequest.mapTenant(tenant);
}
}
2 changes: 2 additions & 0 deletions apps/api/src/app/events/events.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { IntegrationModule } from '../integrations/integrations.module';
import { ExecutionDetailsModule } from '../execution-details/execution-details.module';
import { TopicsModule } from '../topics/topics.module';
import { LayoutsModule } from '../layouts/layouts.module';
import { TenantModule } from '../tenant/tenant.module';

@Module({
imports: [
Expand All @@ -37,6 +38,7 @@ import { LayoutsModule } from '../layouts/layouts.module';
ExecutionDetailsModule,
TopicsModule,
LayoutsModule,
TenantModule,
],
controllers: [EventsController],
providers: [
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/app/events/usecases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ProcessSubscriber,
CreateNotificationJobs,
StoreSubscriberJobs,
ProcessTenant,
} from '@novu/application-generic';

import { CancelDelayed } from './cancel-delayed';
Expand Down Expand Up @@ -37,4 +38,5 @@ export const USE_CASES = [
ParseEventRequest,
ProcessBulkTrigger,
StoreSubscriberJobs,
ProcessTenant,
];
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IsDefined, IsString, IsOptional } from 'class-validator';
import { ISubscribersDefine, TriggerRecipients, TriggerRecipientSubscriber } from '@novu/shared';
import { IsDefined, IsString, IsOptional, ValidateNested } from 'class-validator';
import { TriggerRecipients, TriggerRecipientSubscriber, TriggerTenantContext } from '@novu/shared';

import { EnvironmentWithUserCommand } from '../../../shared/commands/project.command';

Expand All @@ -23,4 +23,8 @@ export class ParseEventRequestCommand extends EnvironmentWithUserCommand {

@IsOptional()
actor?: TriggerRecipientSubscriber | null;

@IsOptional()
@ValidateNested()
tenant?: TriggerTenantContext | null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ import * as hat from 'hat';
import { merge } from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import { Instrument, InstrumentUsecase } from '@novu/application-generic';
import { NotificationTemplateRepository } from '@novu/dal';
import { ISubscribersDefine } from '@novu/shared';
import { NotificationTemplateRepository, NotificationTemplateEntity } from '@novu/dal';
import {
ISubscribersDefine,
ITenantDefine,
ReservedVariablesMap,
TriggerContextTypeEnum,
TriggerTenantContext,
} from '@novu/shared';
import { StorageHelperService, CachedEntity, buildNotificationTemplateIdentifierKey } from '@novu/application-generic';

import { ApiException } from '../../../shared/exceptions/api.exception';
Expand Down Expand Up @@ -55,6 +61,9 @@ export class ParseEventRequest {
throw new UnprocessableEntityException('workflow_not_found');
}

const reservedVariablesTypes = this.getReservedVariablesTypes(template);
this.validateTriggerContext(command, reservedVariablesTypes);

if (!template.active) {
return {
acknowledged: true,
Expand Down Expand Up @@ -142,14 +151,42 @@ export class ParseEventRequest {

if (!subscriberIdExists) {
throw new ApiException(
'subscriberId under property to is not configured, please make sure all the subscriber contains subscriberId property'
'subscriberId under property to is not configured, please make sure all subscribers contains subscriberId property'
);
}
}

return true;
}

@Instrument()
private validateTriggerContext(
command: ParseEventRequestCommand,
reservedVariablesTypes: TriggerContextTypeEnum[]
): void {
const invalidKeys: string[] = [];

for (const reservedVariableType of reservedVariablesTypes) {
const payload = command[reservedVariableType];
if (!payload) {
invalidKeys.push(`${reservedVariableType} object`);
continue;
}
const reservedVariableFields = ReservedVariablesMap[reservedVariableType].map((variable) => variable.name);
for (const variableName of reservedVariableFields) {
const variableNameExists = payload[variableName];

if (!variableNameExists) {
invalidKeys.push(`${variableName} property of ${reservedVariableType}`);
}
}
}

if (invalidKeys.length) {
throw new ApiException(`Trigger is missing: ${invalidKeys.join(', ')}`);
}
}

private modifyAttachments(command: ParseEventRequestCommand) {
command.payload.attachments = command.payload.attachments.map((attachment) => ({
...attachment,
Expand All @@ -158,4 +195,18 @@ export class ParseEventRequest {
storagePath: `${command.organizationId}/${command.environmentId}/${hat()}/${attachment.name}`,
}));
}

public mapTenant(tenant: TriggerTenantContext): ITenantDefine {
if (typeof tenant === 'string') {
return { identifier: tenant };
}

return tenant;
}

public getReservedVariablesTypes(template: NotificationTemplateEntity): TriggerContextTypeEnum[] {
const reservedVariables = template.triggers[0].reservedVariables;

return reservedVariables?.map((reservedVariable) => reservedVariable.type) || [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class ProcessBulkTrigger {

for (const event of command.events) {
let result: TriggerEventResponseDto;
const mappedTenant = event.tenant ? this.parseEventRequest.mapTenant(event.tenant) : null;

try {
result = (await this.parseEventRequest.execute(
Expand All @@ -26,6 +27,7 @@ export class ProcessBulkTrigger {
overrides: event.overrides || {},
to: event.to,
actor: event.actor,
tenant: mappedTenant,
transactionId: event.transactionId,
})
)) as unknown as TriggerEventResponseDto;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IsDefined, IsObject, IsOptional, IsString } from 'class-validator';
import { ISubscribersDefine } from '@novu/shared';
import { ISubscribersDefine, ITenantDefine } from '@novu/shared';

import { EnvironmentWithUserCommand } from '../../../shared/commands/project.command';

Expand All @@ -21,4 +21,7 @@ export class TriggerEventToAllCommand extends EnvironmentWithUserCommand {

@IsOptional()
actor?: ISubscribersDefine | null;

@IsOptional()
tenant?: ITenantDefine | null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class TriggerEventToAll {
transactionId: command.transactionId,
overrides: command.overrides,
actor: command.actor,
tenant: command.tenant,
})
);
}
Expand Down
Loading

0 comments on commit 16603e3

Please sign in to comment.