From 9d20fb82751b3e4add905ee0f25803fc2a33a799 Mon Sep 17 00:00:00 2001 From: Sokratis Vidros Date: Fri, 27 Dec 2024 18:41:02 +0200 Subject: [PATCH] fix(api-service): Remove team member invitation nudge logic (#7397) --- .../parse-event-request.usecase.ts | 111 ------------------ .../api/src/app/invites/invites.controller.ts | 16 +-- apps/api/src/app/invites/usecases/index.ts | 3 +- .../invite-nudge/invite-nudge.command.ts | 14 --- .../invite-nudge/invite-nudge.usecase.ts | 69 ----------- .../mark-message-as.usecase.ts | 11 -- 6 files changed, 2 insertions(+), 222 deletions(-) delete mode 100644 apps/api/src/app/invites/usecases/invite-nudge/invite-nudge.command.ts delete mode 100644 apps/api/src/app/invites/usecases/invite-nudge/invite-nudge.usecase.ts diff --git a/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.usecase.ts b/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.usecase.ts index 8b75d261466..664e8affeff 100644 --- a/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.usecase.ts +++ b/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.usecase.ts @@ -183,8 +183,6 @@ export class ParseEventRequest { // eslint-disable-next-line no-param-reassign command.payload = merge({}, defaultPayload, command.payload); - await this.sendInAppNudgeForTeamMemberInvite(command); - return await this.dispatchEvent(command, transactionId); } @@ -308,113 +306,4 @@ export class ParseEventRequest { return reservedVariables?.map((reservedVariable) => reservedVariable.type) || []; } - - @Instrument() - @CachedEntity({ - builder: (command: ParseEventRequestCommand) => - buildHasNotificationKey({ - _organizationId: command.organizationId, - }), - }) - private async getNotificationCount(command: ParseEventRequestCommand): Promise { - return await this.notificationRepository.count( - { - _organizationId: command.organizationId, - }, - 1 - ); - } - - @Instrument() - private async sendInAppNudgeForTeamMemberInvite(command: ParseEventRequestCommand): Promise { - try { - const isEnabled = await this.getFeatureFlag.execute( - GetFeatureFlagCommand.create({ - key: FeatureFlagsKeysEnum.IS_TEAM_MEMBER_INVITE_NUDGE_ENABLED, - organizationId: command.organizationId, - userId: 'system', - environmentId: 'system', - }) - ); - - if (!isEnabled) return; - - // check if this is first trigger - const notificationCount = await this.getNotificationCount(command); - - if (notificationCount > 0) return; - - /* - * After the first trigger, we invalidate the cache to ensure the next event trigger - * will update the cache with a count of 1. - */ - this.invalidateCacheService.invalidateByKey({ - key: buildHasNotificationKey({ - _organizationId: command.organizationId, - }), - }); - - // check if user is using personal email - const user = await this.userRepository.findById(command.userId); - - if (!user) throw new ApiException('User not found'); - - if (this.isBlockedEmail(user.email)) return; - - // check if organization has more than 1 member - const membersCount = await this.memberRepository.count( - { - _organizationId: command.organizationId, - }, - 2 - ); - - if (membersCount > 1) return; - - Logger.log('No notification found', LOG_CONTEXT); - - if (process.env.NOVU_API_KEY) { - if (!command.payload[INVITE_TEAM_MEMBER_NUDGE_PAYLOAD_KEY]) { - const novu = new Novu({ apiKey: process.env.NOVU_API_KEY }); - - await novu.trigger({ - name: process.env.NOVU_INVITE_TEAM_MEMBER_NUDGE_TRIGGER_IDENTIFIER, - to: [ - { - subscriberId: command.userId, - email: user?.email as string, - }, - ], - payload: { - [INVITE_TEAM_MEMBER_NUDGE_PAYLOAD_KEY]: true, - webhookUrl: `${process.env.API_ROOT_URL}/v1/invites/webhook`, - organizationId: command.organizationId, - }, - }); - - this.analyticsService.track('Invite Nudge Sent', command.userId, { - _organization: command.organizationId, - }); - } - } - } catch (error) { - Logger.error(error, 'Invite nudge failed', LOG_CONTEXT); - } - } - - private isBlockedEmail(email: string): boolean { - return BLOCKED_DOMAINS.some((domain) => email.includes(domain)); - } } - -const BLOCKED_DOMAINS = [ - '@gmail', - '@outlook', - '@yahoo', - '@icloud', - '@mail', - '@hotmail', - '@protonmail', - '@gmx', - '@novu', -]; diff --git a/apps/api/src/app/invites/invites.controller.ts b/apps/api/src/app/invites/invites.controller.ts index f3edcdb0b1b..21447a1ec73 100644 --- a/apps/api/src/app/invites/invites.controller.ts +++ b/apps/api/src/app/invites/invites.controller.ts @@ -32,8 +32,6 @@ import { ResendInviteCommand } from './usecases/resend-invite/resend-invite.comm import { ResendInvite } from './usecases/resend-invite/resend-invite.usecase'; import { ThrottlerCost } from '../rate-limiting/guards'; import { ApiCommonResponses } from '../shared/framework/response.decorator'; -import { InviteNudgeWebhookCommand } from './usecases/invite-nudge/invite-nudge.command'; -import { InviteNudgeWebhook } from './usecases/invite-nudge/invite-nudge.usecase'; import { UserAuthentication } from '../shared/framework/swagger/api.key.security'; @UseInterceptors(ClassSerializerInterceptor) @@ -47,8 +45,7 @@ export class InvitesController { private bulkInviteUsecase: BulkInvite, private acceptInviteUsecase: AcceptInvite, private getInvite: GetInvite, - private resendInviteUsecase: ResendInvite, - private inviteNudgeWebhookUsecase: InviteNudgeWebhook + private resendInviteUsecase: ResendInvite ) {} @Get('/:inviteToken') @@ -130,15 +127,4 @@ export class InvitesController { return response; } - - @Post('/webhook') - async inviteCheckWebhook(@Headers('nv-hmac-256') hmacHeader: string, @Body() body: InviteWebhookDto) { - const command = InviteNudgeWebhookCommand.create({ - hmacHeader, - subscriber: body.subscriber, - organizationId: body.payload.organizationId, - }); - - return await this.inviteNudgeWebhookUsecase.execute(command); - } } diff --git a/apps/api/src/app/invites/usecases/index.ts b/apps/api/src/app/invites/usecases/index.ts index bb924b94162..db0bcef968b 100644 --- a/apps/api/src/app/invites/usecases/index.ts +++ b/apps/api/src/app/invites/usecases/index.ts @@ -3,6 +3,5 @@ import { GetInvite } from './get-invite/get-invite.usecase'; import { BulkInvite } from './bulk-invite/bulk-invite.usecase'; import { InviteMember } from './invite-member/invite-member.usecase'; import { ResendInvite } from './resend-invite/resend-invite.usecase'; -import { InviteNudgeWebhook } from './invite-nudge/invite-nudge.usecase'; -export const USE_CASES = [AcceptInvite, GetInvite, BulkInvite, InviteMember, ResendInvite, InviteNudgeWebhook]; +export const USE_CASES = [AcceptInvite, GetInvite, BulkInvite, InviteMember, ResendInvite]; diff --git a/apps/api/src/app/invites/usecases/invite-nudge/invite-nudge.command.ts b/apps/api/src/app/invites/usecases/invite-nudge/invite-nudge.command.ts deleted file mode 100644 index bec28afdb42..00000000000 --- a/apps/api/src/app/invites/usecases/invite-nudge/invite-nudge.command.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { IsObject, IsString } from 'class-validator'; -import { SubscriberEntity } from '@novu/dal'; -import { BaseCommand } from '@novu/application-generic'; - -export class InviteNudgeWebhookCommand extends BaseCommand { - @IsString() - hmacHeader: string; - - @IsObject() - subscriber: SubscriberEntity; - - @IsString() - organizationId: string; -} diff --git a/apps/api/src/app/invites/usecases/invite-nudge/invite-nudge.usecase.ts b/apps/api/src/app/invites/usecases/invite-nudge/invite-nudge.usecase.ts deleted file mode 100644 index 638376c9b1f..00000000000 --- a/apps/api/src/app/invites/usecases/invite-nudge/invite-nudge.usecase.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Injectable, Scope, Logger } from '@nestjs/common'; -import { MemberRepository } from '@novu/dal'; -import { GetFeatureFlag, GetFeatureFlagCommand, createHash } from '@novu/application-generic'; -import { FeatureFlagsKeysEnum } from '@novu/shared'; -import axios from 'axios'; - -import { InviteNudgeWebhookCommand } from './invite-nudge.command'; - -const axiosInstance = axios.create(); - -@Injectable({ - scope: Scope.REQUEST, -}) -export class InviteNudgeWebhook { - constructor( - private memberRepository: MemberRepository, - private getFeatureFlag: GetFeatureFlag - ) {} - - async execute(command: InviteNudgeWebhookCommand) { - const isEnabled = await this.getFeatureFlag.execute( - GetFeatureFlagCommand.create({ - key: FeatureFlagsKeysEnum.IS_TEAM_MEMBER_INVITE_NUDGE_ENABLED, - organizationId: command.organizationId, - userId: 'system', - environmentId: 'system', - }) - ); - - if (isEnabled && process.env.NOVU_API_KEY) { - const hmacHash = createHash(process.env.NOVU_API_KEY, command.subscriber._environmentId); - const hmacHashFromWebhook = command.hmacHeader; - - if (hmacHash !== hmacHashFromWebhook) { - throw new Error('Unauthorized request'); - } - - const membersCount = await this.memberRepository.count({ - _organizationId: command.organizationId, - }); - - Logger.log( - `membersCount: ${membersCount} for organization ${command.organizationId} and user/subscriber ${command.subscriber.subscriberId}` - ); - - if (membersCount === 1) { - const hubspotAddUserIntoListResponse = await axiosInstance.post( - `https://api.hubapi.com/contacts/v1/lists/${process.env.HUBSPOT_INVITE_NUDGE_EMAIL_USER_LIST_ID}/add`, - { - emails: [command.subscriber.email], - }, - { - headers: { - Authorization: `Bearer ${process.env.HUBSPOT_PRIVATE_APP_ACCESS_TOKEN}`, - }, - } - ); - Logger.log(JSON.stringify(hubspotAddUserIntoListResponse.data)); - if (hubspotAddUserIntoListResponse.data.updated.length !== 1) { - Logger.log( - `Failed to add user ${command.subscriber.email} into list ${process.env.HUBSPOT_INVITE_NUDGE_EMAIL_USER_LIST_ID}` - ); - } - } - } - - return { send: false }; - } -} diff --git a/apps/api/src/app/widgets/usecases/mark-message-as/mark-message-as.usecase.ts b/apps/api/src/app/widgets/usecases/mark-message-as/mark-message-as.usecase.ts index d4b0c8e2fc5..34d0003e58e 100644 --- a/apps/api/src/app/widgets/usecases/mark-message-as/mark-message-as.usecase.ts +++ b/apps/api/src/app/widgets/usecases/mark-message-as/mark-message-as.usecase.ts @@ -62,18 +62,7 @@ export class MarkMessageAs { $in: command.messageIds, }, }); - const isEnabled = await this.getFeatureFlag.execute( - GetFeatureFlagCommand.create({ - key: FeatureFlagsKeysEnum.IS_TEAM_MEMBER_INVITE_NUDGE_ENABLED, - organizationId: command.organizationId, - userId: 'system', - environmentId: 'system', - }) - ); if (command.mark.seen != null) { - if (isEnabled && (process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'production')) { - await this.sendAnalyticsEventForInviteTeamNudge(messages); - } await this.updateServices(command, subscriber, messages, MarkEnum.SEEN); }