Skip to content

Commit

Permalink
Merge pull request #3074 from novuhq/cache-notification-template-by-t…
Browse files Browse the repository at this point in the history
…rigger

feat: cache notification template by trigger identifier
  • Loading branch information
djabarovgeorge authored Mar 30, 2023
2 parents d2c102c + c8e217a commit ae2747c
Show file tree
Hide file tree
Showing 45 changed files with 632 additions and 445 deletions.
35 changes: 16 additions & 19 deletions apps/api/src/app/auth/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ import { ANALYTICS_SERVICE } from '../../shared/shared.module';
import { CachedEntity } from '../../shared/interceptors/cached-entity.interceptor';
import { normalizeEmail } from '../../shared/helpers/email-normalization.service';
import { ApiException } from '../../shared/exceptions/api.exception';
import { buildCommonKey, buildKeyById, CacheKeyPrefixEnum, CacheKeyTypeEnum } from '../../shared/services/cache/keys';
import {
buildEnvironmentByApiKey,
buildSubscriberKey,
buildUserKey,
} from '../../shared/services/cache/key-builders/entities';

@Injectable()
export class AuthService {
Expand Down Expand Up @@ -127,7 +131,7 @@ export class AuthService {
}

async apiKeyAuthenticate(apiKey: string) {
const environment = await this.getEnvironment({ _id: apiKey });
const environment = await this.getEnvironment({ apiKey: apiKey });
if (!environment) throw new UnauthorizedException('API Key not found');

const key = environment.apiKeys.find((i) => i.key === apiKey);
Expand Down Expand Up @@ -287,36 +291,29 @@ export class AuthService {

@CachedEntity({
builder: (command: { _id: string }) =>
buildKeyById({
type: CacheKeyTypeEnum.ENTITY,
keyEntity: CacheKeyPrefixEnum.USER,
identifier: command._id,
buildUserKey({
_id: command._id,
}),
})
private async getUser({ _id }: { _id: string }) {
return await this.userRepository.findById(_id);
}

@CachedEntity({
builder: (command: { _id: string }) =>
buildKeyById({
type: CacheKeyTypeEnum.ENTITY,
keyEntity: CacheKeyPrefixEnum.ENVIRONMENT_BY_API_KEY,
identifier: command._id,
builder: ({ apiKey }: { apiKey: string }) =>
buildEnvironmentByApiKey({
apiKey: apiKey,
}),
})
private async getEnvironment({ _id }: { _id: string }) {
return await this.environmentRepository.findByApiKey(_id);
private async getEnvironment({ apiKey }: { apiKey: string }) {
return await this.environmentRepository.findByApiKey(apiKey);
}

@CachedEntity({
builder: (command: { subscriberId: string; _environmentId: string }) =>
buildCommonKey({
type: CacheKeyTypeEnum.ENTITY,
keyEntity: CacheKeyPrefixEnum.SUBSCRIBER,
environmentId: command._environmentId,
identifier: command.subscriberId,
identifierPrefix: 's',
buildSubscriberKey({
_environmentId: command._environmentId,
subscriberId: command.subscriberId,
}),
})
private async getSubscriber({ subscriberId, _environmentId }: { subscriberId: string; _environmentId: string }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { differenceInHours, differenceInSeconds, parseISO } from 'date-fns';
import { normalizeEmail } from '../../../shared/helpers/email-normalization.service';
import { PasswordResetRequestCommand } from './password-reset-request.command';
import { InvalidateCacheService } from '../../../shared/services/cache';
import { entityBuilder } from '../../../shared/services/cache/keys';
import { buildUserKey } from '../../../shared/services/cache/key-builders/entities';

@Injectable()
export class PasswordResetRequest {
Expand All @@ -27,7 +27,7 @@ export class PasswordResetRequest {
const token = uuidv4();

await this.invalidateCache.invalidateByKey({
key: entityBuilder().user({
key: buildUserKey({
_id: foundUser._id,
}),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { PasswordResetCommand } from './password-reset.command';
import { ApiException } from '../../../shared/exceptions/api.exception';
import { AuthService } from '../../services/auth.service';
import { InvalidateCacheService } from '../../../shared/services/cache';
import { entityBuilder } from '../../../shared/services/cache/keys';
import { buildUserKey } from '../../../shared/services/cache/key-builders/entities';

@Injectable()
export class PasswordReset {
Expand All @@ -29,7 +29,7 @@ export class PasswordReset {
const passwordHash = await bcrypt.hash(command.password, 10);

await this.invalidateCache.invalidateByKey({
key: entityBuilder().user({
key: buildUserKey({
_id: user._id,
}),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import { ChangeEntityTypeEnum } from '@novu/shared';
import { ApplyChange, ApplyChangeCommand } from '../apply-change';
import { PromoteTypeChangeCommand } from '../promote-type-change.command';
import { InvalidateCacheService } from '../../../shared/services/cache';
import { entityBuilder } from '../../../shared/services/cache/keys';
import {
buildNotificationTemplateIdentifierKey,
buildNotificationTemplateKey,
} from '../../../shared/services/cache/key-builders/entities';

@Injectable()
export class PromoteNotificationTemplateChange {
Expand Down Expand Up @@ -137,8 +140,15 @@ export class PromoteNotificationTemplateChange {
}

await this.invalidateCache.invalidateByKey({
key: entityBuilder().notificationTemplate({
_id: item._id,
key: buildNotificationTemplateKey({
_id: newItem._id,
_environmentId: command.environmentId,
}),
});

await this.invalidateCache.invalidateByKey({
key: buildNotificationTemplateIdentifierKey({
templateIdentifier: newItem.triggers[0].identifier,
_environmentId: command.environmentId,
}),
});
Expand Down
14 changes: 12 additions & 2 deletions apps/api/src/app/events/e2e/process-subscriber.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import axios from 'axios';
import { ChannelTypeEnum, ISubscribersDefine, StepTypeEnum } from '@novu/shared';
import { UpdateSubscriberPreferenceRequestDto } from '../../widgets/dtos/update-subscriber-preference-request.dto';
import { CacheService, InvalidateCacheService } from '../../shared/services/cache';
import { entityBuilder } from '../../shared/services/cache/keys';
import {
buildNotificationTemplateIdentifierKey,
buildNotificationTemplateKey,
} from '../../shared/services/cache/key-builders/entities';

const axiosInstance = axios.create();

Expand Down Expand Up @@ -195,12 +198,19 @@ describe('Trigger event - process subscriber /v1/events/trigger (POST)', functio
await updateSubscriberPreference(updateData, session.subscriberToken, template._id);

await invalidateCache.invalidateByKey({
key: entityBuilder().notificationTemplate({
key: buildNotificationTemplateKey({
_id: template._id,
_environmentId: session.environment._id,
}),
});

await invalidateCache.invalidateByKey({
key: buildNotificationTemplateIdentifierKey({
templateIdentifier: template.triggers[0].identifier,
_environmentId: session.environment._id,
}),
});

await notificationTemplateRepository.update(
{
_id: template._id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import { v4 as uuidv4 } from 'uuid';

import { ANALYTICS_SERVICE } from '../../../shared/shared.module';
import { ApiException } from '../../../shared/exceptions/api.exception';
import { VerifyPayload } from '../verify-payload/verify-payload.usecase';
import { VerifyPayloadCommand } from '../verify-payload/verify-payload.command';
import { VerifyPayload, VerifyPayloadCommand } from '../verify-payload';
import { StorageHelperService } from '../../services/storage-helper-service/storage-helper.service';
import { ParseEventRequestCommand } from './parse-event-request.command';
import { TriggerHandlerQueueService } from '../../services/workflow-queue/trigger-handler-queue.service';
import { MapTriggerRecipients, MapTriggerRecipientsCommand } from '../map-trigger-recipients';
import { buildNotificationTemplateIdentifierKey } from '../../../shared/services/cache/key-builders/entities';
import { CachedEntity } from '../../../shared/interceptors/cached-entity.interceptor';

@Injectable()
export class ParseEventRequest {
Expand Down Expand Up @@ -48,10 +49,10 @@ export class ParseEventRequest {

await this.validateSubscriberIdProperty(mappedRecipients);

const template = await this.notificationTemplateRepository.findByTriggerIdentifier(
command.environmentId,
command.identifier
);
const template = await this.getNotificationTemplateByTriggerIdentifier({
environmentId: command.environmentId,
triggerIdentifier: command.identifier,
});

if (!template) {
throw new UnprocessableEntityException('template_not_found');
Expand Down Expand Up @@ -136,6 +137,23 @@ export class ParseEventRequest {
};
}

@CachedEntity({
builder: (command: { triggerIdentifier: string; environmentId: string }) =>
buildNotificationTemplateIdentifierKey({
_environmentId: command.environmentId,
templateIdentifier: command.triggerIdentifier,
}),
})
private async getNotificationTemplateByTriggerIdentifier(command: {
triggerIdentifier: string;
environmentId: string;
}) {
return await this.notificationTemplateRepository.findByTriggerIdentifier(
command.environmentId,
command.triggerIdentifier
);
}

private async validateSubscriberIdProperty(to: ISubscribersDefine[]): Promise<boolean> {
for (const subscriber of to) {
const subscriberIdExists = typeof subscriber === 'string' ? subscriber : subscriber.subscriberId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { InvalidateCacheService } from '../../../shared/services/cache';
import { SendMessageBase } from './send-message.base';
import { ApiException } from '../../../shared/exceptions/api.exception';
import { GetDecryptedIntegrations } from '../../../integrations/usecases/get-decrypted-integrations';
import { queryBuilder } from '../../../shared/services/cache/keys';
import { buildFeedKey, buildMessageCountKey } from '../../../shared/services/cache/key-builders/queries';

@Injectable()
export class SendMessageInApp extends SendMessageBase {
Expand Down Expand Up @@ -148,14 +148,14 @@ export class SendMessageInApp extends SendMessageBase {
let message: MessageEntity | null = null;

await this.invalidateCache.invalidateQuery({
key: queryBuilder().feed().invalidate({
key: buildFeedKey().invalidate({
subscriberId: subscriber.subscriberId,
_environmentId: command.environmentId,
}),
});

await this.invalidateCache.invalidateQuery({
key: queryBuilder().messageCount().invalidate({
key: buildMessageCountKey().invalidate({
subscriberId: subscriber.subscriberId,
_environmentId: command.environmentId,
}),
Expand Down
28 changes: 9 additions & 19 deletions apps/api/src/app/events/usecases/send-message/send-message.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,9 @@ import {
} from '../../../execution-details/usecases/create-execution-details';
import { DetailEnum } from '../../../execution-details/types';
import { CachedEntity } from '../../../shared/interceptors/cached-entity.interceptor';
import {
buildCommonKey,
buildQueryKey,
CacheKeyPrefixEnum,
CacheKeyTypeEnum,
} from '../../../shared/services/cache/keys';
import { buildSubscriberKey } from '../../../shared/services/cache/key-builders/entities';
import { CachedQuery } from '../../../shared/interceptors/cached-query.interceptor';
import { buildIntegrationKey } from '../../../shared/services/cache/key-builders/queries';

export abstract class SendMessageBase extends SendMessageType {
abstract readonly channelType: ChannelTypeEnum;
Expand All @@ -35,12 +31,9 @@ export abstract class SendMessageBase extends SendMessageType {

@CachedEntity({
builder: (command: { subscriberId: string; _environmentId: string }) =>
buildCommonKey({
type: CacheKeyTypeEnum.ENTITY,
keyEntity: CacheKeyPrefixEnum.SUBSCRIBER,
environmentId: command._environmentId,
identifier: command.subscriberId,
identifierPrefix: 's',
buildSubscriberKey({
_environmentId: command._environmentId,
subscriberId: command.subscriberId,
}),
})
protected async getSubscriberBySubscriberId({
Expand All @@ -57,13 +50,10 @@ export abstract class SendMessageBase extends SendMessageType {
}

@CachedQuery({
builder: (command: GetDecryptedIntegrationsCommand) =>
buildQueryKey({
type: CacheKeyTypeEnum.QUERY,
keyEntity: CacheKeyPrefixEnum.INTEGRATION,
environmentId: command.environmentId,
identifier: command.userId,
query: command as any,
builder: ({ environmentId, ...command }: GetDecryptedIntegrationsCommand) =>
buildIntegrationKey().cache({
_environmentId: environmentId,
...command,
}),
})
protected async getIntegration(getDecryptedIntegrationsCommand: GetDecryptedIntegrationsCommand) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {
import { ANALYTICS_SERVICE } from '../../../shared/shared.module';
import { ApiException } from '../../../shared/exceptions/api.exception';
import { CachedEntity } from '../../../shared/interceptors/cached-entity.interceptor';
import { buildCommonKey, CacheKeyPrefixEnum, CacheKeyTypeEnum } from '../../../shared/services/cache/keys';
import { buildNotificationTemplateKey, buildSubscriberKey } from '../../../shared/services/cache/key-builders/entities';

@Injectable()
export class SendMessage {
Expand Down Expand Up @@ -173,12 +173,9 @@ export class SendMessage {

@CachedEntity({
builder: (command: { subscriberId: string; _environmentId: string }) =>
buildCommonKey({
type: CacheKeyTypeEnum.ENTITY,
keyEntity: CacheKeyPrefixEnum.SUBSCRIBER,
environmentId: command._environmentId,
identifier: command.subscriberId,
identifierPrefix: 's',
buildSubscriberKey({
_environmentId: command._environmentId,
subscriberId: command.subscriberId,
}),
})
private async getSubscriberBySubscriberId({
Expand Down Expand Up @@ -235,11 +232,9 @@ export class SendMessage {

@CachedEntity({
builder: (command: { _id: string; environmentId: string }) =>
buildCommonKey({
type: CacheKeyTypeEnum.ENTITY,
keyEntity: CacheKeyPrefixEnum.NOTIFICATION_TEMPLATE,
environmentId: command.environmentId,
identifier: command._id,
buildNotificationTemplateKey({
_environmentId: command.environmentId,
_id: command._id,
}),
})
private async getNotificationTemplate({ _id, environmentId }: { _id: string; environmentId: string }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { ApiException } from '../../../shared/exceptions/api.exception';
const LOG_CONTEXT = 'TriggerEventUseCase';

import { PinoLogger } from '@novu/application-generic';
import { buildNotificationTemplateIdentifierKey } from '../../../shared/services/cache/key-builders/entities';
import { CachedEntity } from '../../../shared/interceptors/cached-entity.interceptor';

@Injectable()
export class TriggerEvent {
Expand Down Expand Up @@ -54,10 +56,10 @@ export class TriggerEvent {
organizationId: command.organizationId,
});

const template = await this.notificationTemplateRepository.findByTriggerIdentifier(
command.environmentId,
command.identifier
);
const template = await this.getNotificationTemplateByTriggerIdentifier({
environmentId: command.environmentId,
triggerIdentifier: command.identifier,
});

/*
* Makes no sense to execute anything if template doesn't exist
Expand Down Expand Up @@ -136,6 +138,23 @@ export class TriggerEvent {
this.performanceService.setEnd(mark);
}

@CachedEntity({
builder: (command: { triggerIdentifier: string; environmentId: string }) =>
buildNotificationTemplateIdentifierKey({
_environmentId: command.environmentId,
templateIdentifier: command.triggerIdentifier,
}),
})
private async getNotificationTemplateByTriggerIdentifier(command: {
triggerIdentifier: string;
environmentId: string;
}) {
return await this.notificationTemplateRepository.findByTriggerIdentifier(
command.environmentId,
command.triggerIdentifier
);
}

private async validateTransactionIdProperty(
transactionId: string,
organizationId: string,
Expand Down
Loading

0 comments on commit ae2747c

Please sign in to comment.