diff --git a/.github/workflows/dev-deploy-api.yml b/.github/workflows/dev-deploy-api.yml index e2badbda237..ba86751ca37 100644 --- a/.github/workflows/dev-deploy-api.yml +++ b/.github/workflows/dev-deploy-api.yml @@ -41,10 +41,6 @@ jobs: steps: - run: echo ${{ matrix.projectName }} - uses: actions/checkout@v3 - - uses: ./.github/actions/checkout-submodules - with: - submodule_token: ${{ secrets.SUBMODULES_TOKEN }} - submodule_branch: "next" - uses: ./.github/actions/setup-project - uses: ./.github/actions/setup-redis-cluster - uses: mansagroup/nrwl-nx-action@v3 @@ -88,11 +84,6 @@ jobs: name: ['novu/api-ee', 'novu/api'] steps: - uses: actions/checkout@v3 - - uses: ./.github/actions/checkout-submodules - with: - enabled: ${{ contains (matrix.name,'ee') }} - submodule_token: ${{ secrets.SUBMODULES_TOKEN }} - submodule_branch: "next" - uses: ./.github/actions/setup-project - uses: ./.github/actions/docker/build-api id: docker_build diff --git a/.github/workflows/dev-deploy-worker.yml b/.github/workflows/dev-deploy-worker.yml index 0180cf43a2b..dabe27c4e7b 100644 --- a/.github/workflows/dev-deploy-worker.yml +++ b/.github/workflows/dev-deploy-worker.yml @@ -50,11 +50,6 @@ jobs: name: ['novu/worker-ee', 'novu/worker'] steps: - uses: actions/checkout@v3 - - uses: ./.github/actions/checkout-submodules - with: - enabled: ${{ contains (matrix.name,'ee') }} - submodule_token: ${{ secrets.SUBMODULES_TOKEN }} - submodule_branch: "next" - uses: ./.github/actions/setup-project - uses: ./.github/actions/docker/build-worker id: docker_build diff --git a/.github/workflows/prod-deploy-api.yml b/.github/workflows/prod-deploy-api.yml index b1d8b1fa3c6..8f886ad620f 100644 --- a/.github/workflows/prod-deploy-api.yml +++ b/.github/workflows/prod-deploy-api.yml @@ -30,10 +30,6 @@ jobs: steps: - run: echo ${{ matrix.projectName }} - uses: actions/checkout@v3 - - uses: ./.github/actions/checkout-submodules - with: - submodule_token: ${{ secrets.SUBMODULES_TOKEN }} - submodule_branch: "next" - uses: ./.github/actions/setup-project - uses: ./.github/actions/setup-redis-cluster - uses: mansagroup/nrwl-nx-action@v3 @@ -79,11 +75,6 @@ jobs: id-token: write steps: - uses: actions/checkout@v3 - - uses: ./.github/actions/checkout-submodules - with: - enabled: ${{ contains (matrix.name,'ee') }} - submodule_token: ${{ secrets.SUBMODULES_TOKEN }} - submodule_branch: "main" - uses: ./.github/actions/setup-project - name: build api diff --git a/.github/workflows/prod-deploy-worker.yml b/.github/workflows/prod-deploy-worker.yml index cd3b480e09c..8ce444cf0c9 100644 --- a/.github/workflows/prod-deploy-worker.yml +++ b/.github/workflows/prod-deploy-worker.yml @@ -38,11 +38,6 @@ jobs: id-token: write steps: - uses: actions/checkout@v3 - - uses: ./.github/actions/checkout-submodules - with: - enabled: ${{ contains (matrix.name,'ee') }} - submodule_token: ${{ secrets.SUBMODULES_TOKEN }} - submodule_branch: "main" - uses: ./.github/actions/setup-project - name: build worker diff --git a/.github/workflows/prod-deploy-ws.yml b/.github/workflows/prod-deploy-ws.yml index aef0b169635..87b98c2b825 100644 --- a/.github/workflows/prod-deploy-ws.yml +++ b/.github/workflows/prod-deploy-ws.yml @@ -23,6 +23,8 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 80 environment: Production + needs: + - test_ws strategy: matrix: name: [ 'novu/ws-ee', 'novu/ws' ] diff --git a/.github/workflows/reusable-api-e2e.yml b/.github/workflows/reusable-api-e2e.yml index 703863658f3..15ec2ecf64e 100644 --- a/.github/workflows/reusable-api-e2e.yml +++ b/.github/workflows/reusable-api-e2e.yml @@ -46,11 +46,6 @@ jobs: fi - uses: actions/checkout@v3 # checkout with submodules if token is provided - - uses: ./.github/actions/checkout-submodules - with: - enabled: ${{ steps.setup.outputs.has_token == 'true' }} - submodule_token: ${{ secrets.SUBMODULES_TOKEN }} - submodule_branch: ${{ inputs.submodule_branch }} - uses: ./.github/actions/setup-project - uses: ./.github/actions/setup-redis-cluster - uses: mansagroup/nrwl-nx-action@v3 diff --git a/.github/workflows/reusable-inbound-mail-e2e.yml b/.github/workflows/reusable-inbound-mail-e2e.yml index efb72234c81..35d50ef1fa8 100644 --- a/.github/workflows/reusable-inbound-mail-e2e.yml +++ b/.github/workflows/reusable-inbound-mail-e2e.yml @@ -38,11 +38,6 @@ jobs: steps: # checkout with submodules if token is provided - uses: actions/checkout@v3 - - uses: ./.github/actions/checkout-submodules - with: - enabled: ${{ steps.setup.outputs.has_token == 'true' }} - submodule_token: ${{ secrets.SUBMODULES_TOKEN }} - submodule_branch: ${{ inputs.submodule_branch }} - uses: ./.github/actions/setup-project - uses: ./.github/actions/setup-redis-cluster - uses: mansagroup/nrwl-nx-action@v3 diff --git a/.github/workflows/reusable-web-e2e.yml b/.github/workflows/reusable-web-e2e.yml index 4cbc5f16302..3970dc3d776 100644 --- a/.github/workflows/reusable-web-e2e.yml +++ b/.github/workflows/reusable-web-e2e.yml @@ -52,11 +52,6 @@ jobs: - uses: actions/checkout@v3 # checkout with submodules if token is provided - - uses: ./.github/actions/checkout-submodules - with: - enabled: ${{ steps.setup.outputs.has_token == 'true' }} - submodule_token: ${{ secrets.SUBMODULES_TOKEN }} - submodule_branch: ${{ inputs.submodule_branch }} - uses: ./.github/actions/setup-project id: setup-project with: diff --git a/.github/workflows/reusable-widget-e2e.yml b/.github/workflows/reusable-widget-e2e.yml index 2e786ce7ed2..f619bc64b39 100644 --- a/.github/workflows/reusable-widget-e2e.yml +++ b/.github/workflows/reusable-widget-e2e.yml @@ -51,12 +51,6 @@ jobs: fi - uses: actions/checkout@v3 # checkout with submodules if token is provided - - uses: ./.github/actions/checkout-submodules - with: - enabled: ${{ steps.setup.outputs.has_token == 'true' }} - submodule_token: ${{ secrets.SUBMODULES_TOKEN }} - submodule_branch: ${{ inputs.submodule_branch }} - - uses: ./.github/actions/setup-project id: setup-project with: diff --git a/.github/workflows/reusable-worker-e2e.yml b/.github/workflows/reusable-worker-e2e.yml index 976b6db593b..ce4a2bd04c9 100644 --- a/.github/workflows/reusable-worker-e2e.yml +++ b/.github/workflows/reusable-worker-e2e.yml @@ -46,11 +46,7 @@ jobs: fi - uses: actions/checkout@v3 # checkout with submodules if token is provided - - uses: ./.github/actions/checkout-submodules - with: - enabled: ${{ steps.setup.outputs.has_token == 'true' }} - submodule_token: ${{ secrets.SUBMODULES_TOKEN }} - submodule_branch: ${{ inputs.submodule_branch }} + - uses: ./.github/actions/setup-project - uses: ./.github/actions/setup-redis-cluster diff --git a/.github/workflows/reusable-ws-e2e.yml b/.github/workflows/reusable-ws-e2e.yml index 580ee7fee25..df4dd129fce 100644 --- a/.github/workflows/reusable-ws-e2e.yml +++ b/.github/workflows/reusable-ws-e2e.yml @@ -44,12 +44,6 @@ jobs: echo "has_token=false" >> $GITHUB_OUTPUT fi - uses: actions/checkout@v3 - # checkout with submodules if token is provided - - uses: ./.github/actions/checkout-submodules - with: - enabled: ${{ steps.setup.outputs.has_token == 'true' }} - submodule_token: ${{ secrets.SUBMODULES_TOKEN }} - submodule_branch: ${{ inputs.submodule_branch }} - uses: ./.github/actions/setup-project - uses: mansagroup/nrwl-nx-action@v3 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a90eb1a312a..241f4d9ab9b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -241,7 +241,7 @@ jobs: else echo "::set-output name=has_token::false" fi - + test_e2e_ee: name: Test E2E EE runs-on: ubuntu-latest @@ -260,10 +260,6 @@ jobs: steps: - run: echo ${{ matrix.projectName }} - uses: actions/checkout@v3 - - uses: ./.github/actions/checkout-submodules - with: - submodule_token: ${{ secrets.SUBMODULES_TOKEN }} - submodule_branch: "next" - uses: ./.github/actions/setup-project - uses: ./.github/actions/setup-redis-cluster - uses: mansagroup/nrwl-nx-action@v3 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 099f7c724d3..794e2d8b681 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,7 @@ You can open a new issue with this [issue form](https://github.com/novuhq/novu/i ### Requirements -- Node.js version v14.19.3 +- Node.js version v16.15.1 - MongoDB - Redis. To install Redis on your O.S, please follow the below guides - [To install Redis on Windows](https://redis.io/docs/getting-started/installation/install-redis-on-windows/) @@ -33,8 +33,8 @@ You can open a new issue with this [issue form](https://github.com/novuhq/novu/i The project is a monorepo, meaning that it is a collection of multiple packages managed in the same repository. -To learn more about the project structure visit [https://docs.novu.co/community/monorepo-structure](https://docs.novu.co/community/monorepo-structure). +To learn more about the project structure and running the project locally, please have a look [here](https://docs.novu.co/community-support/introduction#run-novu-locally). After cloning your fork, you will need to run the `npm run setup:project` command to install and build all dependencies. To learn a detailed guide on running the project locally, checkout our guide on [how to run novu in local machine](https://docs.novu.co/community/run-in-local-machine). diff --git a/apps/api/migrations/subscriber-preferences-level/subscriber-preferences-level.migration.ts b/apps/api/migrations/subscriber-preferences-level/subscriber-preferences-level.migration.ts new file mode 100644 index 00000000000..76c5fdc4761 --- /dev/null +++ b/apps/api/migrations/subscriber-preferences-level/subscriber-preferences-level.migration.ts @@ -0,0 +1,33 @@ +import '../../src/config'; + +import { NestFactory } from '@nestjs/core'; +import { PreferenceLevelEnum, SubscriberPreferenceRepository } from '@novu/dal'; + +import { AppModule } from '../../src/app.module'; + +export async function addLevelPropertyToSubscriberPreferences() { + // eslint-disable-next-line no-console + console.log('start migration - add level property to subscriber preferences'); + const app = await NestFactory.create(AppModule, { + logger: false, + }); + + const subscriberPreferenceRepository = app.get(SubscriberPreferenceRepository); + // eslint-disable-next-line no-console + console.log('add level: PreferenceLevelEnum.TEMPLATE to all subscriber preferences without level property'); + + await subscriberPreferenceRepository._model.collection.updateMany( + { level: { $exists: false }, _templateId: { $exists: true } }, + { + $set: { level: PreferenceLevelEnum.TEMPLATE }, + } + ); + + // eslint-disable-next-line no-console + console.log('end migration- add level property to subscriber preferences'); + + app.close(); + process.exit(0); +} + +addLevelPropertyToSubscriberPreferences(); diff --git a/apps/api/package.json b/apps/api/package.json index 2059dbabbac..04b50c466e5 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,6 +1,6 @@ { "name": "@novu/api", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "description", "author": "", "private": "true", @@ -38,12 +38,12 @@ "@nestjs/platform-express": "^10.2.2", "@nestjs/swagger": "^7.1.8", "@nestjs/terminus": "^10.0.1", - "@novu/application-generic": "^0.19.0", - "@novu/dal": "^0.19.0", - "@novu/node": "^0.19.0", - "@novu/shared": "^0.19.0", - "@novu/stateless": "^0.19.0", - "@novu/testing": "^0.19.0", + "@novu/application-generic": "^0.20.0-alpha.0", + "@novu/dal": "^0.20.0-alpha.0", + "@novu/node": "^0.20.0-alpha.0", + "@novu/shared": "^0.20.0-alpha.0", + "@novu/stateless": "^0.20.0-alpha.0", + "@novu/testing": "^0.20.0-alpha.0", "@sendgrid/mail": "^7.6.0", "@sentry/hub": "^7.40.0", "@sentry/node": "^7.40.0", @@ -113,7 +113,9 @@ "@novu/ee-auth": "^0.19.0" }, "nx": { - "implicitDependencies": ["@novu/ee-auth"] + "implicitDependencies": [ + "@novu/ee-auth" + ] }, "lint-staged": { "*.{js,jsx,ts,tsx}": [ diff --git a/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.e2e.ts b/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.e2e.ts new file mode 100644 index 00000000000..cc38c8cc882 --- /dev/null +++ b/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.e2e.ts @@ -0,0 +1,71 @@ +import { Test } from '@nestjs/testing'; +import { expect } from 'chai'; +import { v4 as uuid } from 'uuid'; + +import { SubscribersService, UserSession } from '@novu/testing'; +import { SubscriberRepository, NotificationTemplateEntity } from '@novu/dal'; +import { TriggerRecipients } from '@novu/shared'; + +import { SharedModule } from '../../../shared/shared.module'; +import { EventsModule } from '../../events.module'; +import { ParseEventRequestCommand } from './parse-event-request.command'; +import { ParseEventRequest } from './parse-event-request.usecase'; + +describe('ParseEventRequest Usecase', () => { + let session: UserSession; + let subscribersService: SubscribersService; + let parseEventRequestUsecase: ParseEventRequest; + let template: NotificationTemplateEntity; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [SharedModule, EventsModule], + providers: [], + }).compile(); + + session = new UserSession(); + await session.initialize(); + + template = await session.createTemplate(); + parseEventRequestUsecase = moduleRef.get(ParseEventRequest); + subscribersService = new SubscribersService(session.organization._id, session.environment._id); + }); + + it('should throw exception when subscriber id sent as array', async () => { + const transactionId = uuid(); + const subscriberId = [SubscriberRepository.createObjectId()]; + + const command = buildCommand( + session, + transactionId, + [{ subscriberId: subscriberId } as unknown as string], + template.triggers[0].identifier + ); + + try { + await parseEventRequestUsecase.execute(command); + } catch (error) { + expect(error.message).to.be.eql( + 'subscriberId under property to is type array, which is not allowed please make sure all subscribers ids are strings' + ); + } + }); +}); + +const buildCommand = ( + session: UserSession, + transactionId: string, + to: TriggerRecipients, + identifier: string +): ParseEventRequestCommand => { + return ParseEventRequestCommand.create({ + organizationId: session.organization._id, + environmentId: session.environment._id, + to, + transactionId, + userId: session.user._id, + identifier, + payload: {}, + overrides: {}, + }); +}; 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 c0546cd5240..32853c50ebc 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 @@ -173,6 +173,12 @@ export class ParseEventRequest { for (const subscriber of to) { const subscriberIdExists = typeof subscriber === 'string' ? subscriber : subscriber.subscriberId; + if (Array.isArray(subscriberIdExists)) { + throw new ApiException( + 'subscriberId under property to is type array, which is not allowed please make sure all subscribers ids are strings' + ); + } + if (!subscriberIdExists) { throw new ApiException( 'subscriberId under property to is not configured, please make sure all subscribers contains subscriberId property' diff --git a/apps/api/src/app/inbound-parse/e2e/inbound-email-parse.e2e.ts b/apps/api/src/app/inbound-parse/e2e/inbound-email-parse.e2e.ts index 5bc3218bc44..ef8f8a974d2 100644 --- a/apps/api/src/app/inbound-parse/e2e/inbound-email-parse.e2e.ts +++ b/apps/api/src/app/inbound-parse/e2e/inbound-email-parse.e2e.ts @@ -75,39 +75,38 @@ describe('Should handle the new arrived mail', () => { }); it('should not send webhook request with missing transactionId', async () => { - const message = await triggerEmail(); - - const mail = getMailData(message, false); - - const getStub = sandbox.stub(axios, 'post').resolves(); - - await inboundEmailParseUsecase.execute(InboundEmailParseCommand.create(mail)); - - sinon.assert.notCalled(getStub); + try { + const message = await triggerEmail(); + const mail = getMailData(message, false); + + await inboundEmailParseUsecase.execute(InboundEmailParseCommand.create(mail)); + } catch (e) { + expect(e.message).to.contains('Missing transactionId on address'); + } }); it('should not send webhook request with when domain white list', async () => { - const message = await triggerEmail(true, false); + try { + const message = await triggerEmail(true, false); - const mail = getMailData(message); + const mail = getMailData(message); - const getStub = sandbox.stub(axios, 'post').resolves(); - - await inboundEmailParseUsecase.execute(InboundEmailParseCommand.create(mail)); - - sinon.assert.notCalled(getStub); + await inboundEmailParseUsecase.execute(InboundEmailParseCommand.create(mail)); + } catch (e) { + expect(e.message).to.equal('Domain is not in environment white list'); + } }); it('should not send webhook request when missing replay callback url', async () => { - const message = await triggerEmail(true, true, true, false); - - const mail = getMailData(message); - - const getStub = sandbox.stub(axios, 'post').resolves(); + try { + const message = await triggerEmail(true, true, true, false); - await inboundEmailParseUsecase.execute(InboundEmailParseCommand.create(mail)); + const mail = getMailData(message); - sinon.assert.notCalled(getStub); + await inboundEmailParseUsecase.execute(InboundEmailParseCommand.create(mail)); + } catch (e) { + expect(e.message).to.contains('Missing parse webhook on template'); + } }); async function triggerEmail( diff --git a/apps/api/src/app/inbound-parse/services/inbound-parse.queue.service.ts b/apps/api/src/app/inbound-parse/services/inbound-parse.queue.service.ts index 2848eafbe72..dc4b3cf24ca 100644 --- a/apps/api/src/app/inbound-parse/services/inbound-parse.queue.service.ts +++ b/apps/api/src/app/inbound-parse/services/inbound-parse.queue.service.ts @@ -7,11 +7,13 @@ import { WorkerOptions, } from '@novu/application-generic'; import { JobTopicNameEnum } from '@novu/shared'; -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { InboundEmailParse } from '../usecases/inbound-email-parse/inbound-email-parse.usecase'; import { InboundEmailParseCommand } from '../usecases/inbound-email-parse/inbound-email-parse.command'; +const LOG_CONTEXT = 'InboundParseQueueService'; + @Injectable() export class InboundParseQueueService { public readonly queue: Queue; @@ -35,6 +37,7 @@ export class InboundParseQueueService { public getWorkerProcessor() { return async ({ data }: { data: InboundEmailParseCommand }) => { + Logger.verbose({ data }, 'Processing the inbound parsed email', LOG_CONTEXT); await this.emailParseUsecase.execute(InboundEmailParseCommand.create({ ...data })); }; } diff --git a/apps/api/src/app/inbound-parse/usecases/inbound-email-parse/inbound-email-parse.usecase.ts b/apps/api/src/app/inbound-parse/usecases/inbound-email-parse/inbound-email-parse.usecase.ts index c0545391f66..682dc6abbd0 100644 --- a/apps/api/src/app/inbound-parse/usecases/inbound-email-parse/inbound-email-parse.usecase.ts +++ b/apps/api/src/app/inbound-parse/usecases/inbound-email-parse/inbound-email-parse.usecase.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import { InboundEmailParseCommand } from './inbound-email-parse.command'; import { JobEntity, @@ -12,6 +12,8 @@ import axios from 'axios'; import { createHash } from '../../../shared/helpers/hmac.service'; import { CompileTemplate, CompileTemplateCommand } from '@novu/application-generic'; +const LOG_CONTEXT = 'InboundEmailParse'; + @Injectable() export class InboundEmailParse { constructor( @@ -21,55 +23,38 @@ export class InboundEmailParse { ) {} async execute(command: InboundEmailParseCommand) { - const { toDomain, toTransactionId, toEnvironmentId } = this.splitTo(command.to[0].address); - - Logger.debug('toDomain in InboundEmailParse is: ' + toDomain); - Logger.debug('toTransactionId in InboundEmailParse is: ' + toTransactionId); - - if (!toTransactionId) { - Logger.warn(`missing transactionId on address ${command.to[0].address}`); - - return; - } + const { domain, transactionId, environmentId } = this.splitTo(command.to[0].address); - Logger.debug('toEnvironmentId in InboundEmailParse is: ' + toEnvironmentId); - - if (!toEnvironmentId) { - Logger.warn(`missing environmentId on address ${command.to[0].address}`); - - return; - } + Logger.debug({ domain, transactionId, environmentId }, `Received new email to parse`, LOG_CONTEXT); const { template, notification, subscriber, environment, job, message } = await this.getEntities( - toTransactionId, - toEnvironmentId + transactionId, + environmentId ); - if (toDomain !== environment?.dns?.inboundParseDomain) { - Logger.warn('to domain is not in environment white list'); - - return; + if (domain !== environment?.dns?.inboundParseDomain) { + this.throwMiddleware('Domain is not in environment white list'); } const currentParseWebhook = template.steps.find((step) => step?._id?.toString() === job?.step?._id)?.replyCallback ?.url; if (!currentParseWebhook) { - Logger.warn(`missing parse webhook on template ${template._id} job ${job._id} transactionId ${toTransactionId}.`); - - return; + this.throwMiddleware( + `Missing parse webhook on template ${template._id} job ${job._id} transactionId ${transactionId}.` + ); } const compiledDomain = await this.compileTemplate.execute( CompileTemplateCommand.create({ - template: currentParseWebhook, + template: currentParseWebhook as string, data: job.payload, }) ); const userPayload: IUserWebhookPayload = { hmac: createHash(environment?.apiKeys[0]?.key, subscriber.subscriberId), - transactionId: toTransactionId, + transactionId: transactionId, payload: job.payload, templateIdentifier: job.identifier, template, @@ -84,11 +69,29 @@ export class InboundEmailParse { private splitTo(address: string) { const userNameDelimiter = '-nv-e='; - const [toUser, toDomain] = address.split('@'); - const toMetaIds = toUser.split('+')[1]; - const [toTransactionId, toEnvironmentId] = toMetaIds.split(userNameDelimiter); + const [user, domain] = address.split('@'); + const toMetaIds = user.split('+')[1]; + const [transactionId, environmentId] = toMetaIds.split(userNameDelimiter); + + if (!transactionId) { + this.throwMiddleware(`Missing transactionId on address ${address}`); + } + + if (!domain) { + this.throwMiddleware(`Missing domain on address ${address}`); + } + + if (!environmentId) { + this.throwMiddleware(`Missing environmentId on address ${address}`); + } + + return { domain, transactionId, environmentId }; + } + + private throwMiddleware(error: string) { + Logger.error(error, LOG_CONTEXT); - return { toDomain, toTransactionId, toEnvironmentId }; + throw new BadRequestException(error); } private async getEntities(transactionId: string, environmentId: string) { diff --git a/apps/api/src/app/shared/helpers/content.service.spec.ts b/apps/api/src/app/shared/helpers/content.service.spec.ts index 1dd0833410d..70f80455447 100644 --- a/apps/api/src/app/shared/helpers/content.service.spec.ts +++ b/apps/api/src/app/shared/helpers/content.service.spec.ts @@ -335,4 +335,99 @@ describe('ContentService', function () { expect(extractVariables[0].name).to.include('lastName'); }); }); + + describe('extractStepVariables', () => { + it('should not fail if no filters available', () => { + const contentService = new ContentService(); + const messages = [ + { + template: { + type: StepTypeEnum.EMAIL, + subject: 'Test {{subscriber.firstName}}', + content: [ + { + content: 'Test of {{subscriber.firstName}} {{lastName}}', + type: 'text', + }, + ], + }, + }, + ] as INotificationTemplateStep[]; + const variables = contentService.extractStepVariables(messages); + + expect(variables.length).to.equal(0); + }); + + it('should not fail if filters are set as non array', () => { + const contentService = new ContentService(); + const messages = [ + { + template: { + type: StepTypeEnum.EMAIL, + subject: 'Test {{subscriber.firstName}}', + content: [ + { + content: 'Test of {{subscriber.firstName}} {{lastName}}', + type: 'text', + }, + ], + }, + filters: {}, + }, + ] as INotificationTemplateStep[]; + const variables = contentService.extractStepVariables(messages); + + expect(variables.length).to.equal(0); + }); + + it('should not fail if filters are an empty array', () => { + const contentService = new ContentService(); + const messages = [ + { + template: { + type: StepTypeEnum.EMAIL, + subject: 'Test {{subscriber.firstName}}', + content: [ + { + content: 'Test of {{subscriber.firstName}} {{lastName}}', + type: 'text', + }, + ], + }, + filters: [], + }, + ] as INotificationTemplateStep[]; + const variables = contentService.extractStepVariables(messages); + + expect(variables.length).to.equal(0); + }); + + it('should not fail if filters have some wrong settings like missing children in filters', () => { + const contentService = new ContentService(); + const messages = [ + { + template: { + type: StepTypeEnum.EMAIL, + subject: 'Test {{subscriber.firstName}}', + content: [ + { + content: 'Test of {{subscriber.firstName}} {{lastName}}', + type: 'text', + }, + ], + }, + filters: [ + { + isNegated: false, + type: 'GROUP', + value: 'AND', + }, + ], + }, + ] as INotificationTemplateStep[]; + const variables = contentService.extractStepVariables(messages); + + expect(variables.length).to.equal(0); + }); + }); }); diff --git a/apps/api/src/app/shared/helpers/content.service.ts b/apps/api/src/app/shared/helpers/content.service.ts index 6dfcff38f89..4e5c14d9730 100644 --- a/apps/api/src/app/shared/helpers/content.service.ts +++ b/apps/api/src/app/shared/helpers/content.service.ts @@ -73,17 +73,19 @@ export class ContentService { for (const message of messages) { if (message.filters) { - const filterVariables = message.filters.flatMap((filter) => - filter.children - .filter((item) => item.on === FilterPartTypeEnum.PAYLOAD) - .map((item: IFieldFilterPart) => { - return { - name: item.field, - type: TemplateVariableTypeEnum.STRING, - }; - }) - ); - variables.push(...filterVariables); + const filters = Array.isArray(message.filters) ? message.filters : []; + const filteredVariables = filters.flatMap((filter) => { + const filteredChildren = filter.children?.filter((item) => item.on === FilterPartTypeEnum.PAYLOAD) || []; + const mappedChildren = filteredChildren.map((item: IFieldFilterPart) => { + return { + name: item.field, + type: TemplateVariableTypeEnum.STRING, + }; + }); + + return mappedChildren; + }); + variables.push(...filteredVariables); } if (message.metadata?.type === DelayTypeEnum.SCHEDULED && message.metadata.delayPath) { diff --git a/apps/api/src/app/subscribers/dtos/get-subscriber-preferences-response.dto.ts b/apps/api/src/app/subscribers/dtos/get-subscriber-preferences-response.dto.ts new file mode 100644 index 00000000000..52ea48a0af5 --- /dev/null +++ b/apps/api/src/app/subscribers/dtos/get-subscriber-preferences-response.dto.ts @@ -0,0 +1,71 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { ChannelTypeEnum, PreferenceOverrideSourceEnum } from '@novu/shared'; +import { PreferenceChannels } from '../../shared/dtos/preference-channels'; + +class TemplateResponse { + @ApiProperty({ + description: 'Unique identifier of the workflow', + type: String, + }) + _id: string; + + @ApiProperty({ + description: 'Name of the workflow', + type: String, + }) + name: string; + + @ApiProperty({ + description: + 'Critical templates will always be delivered to the end user and should be hidden from the subscriber preferences screen', + type: Boolean, + }) + critical: boolean; +} + +class Overrides { + @ApiProperty({ + type: ChannelTypeEnum, + description: 'The channel type which is overridden', + }) + channel: ChannelTypeEnum; + @ApiProperty({ + type: PreferenceOverrideSourceEnum, + description: 'The source of overrides', + }) + source: PreferenceOverrideSourceEnum; +} + +class Preference { + @ApiProperty({ + description: 'Sets if the workflow is fully enabled for all channels or not for the subscriber.', + type: Boolean, + }) + enabled: boolean; + + @ApiProperty({ + type: PreferenceChannels, + description: 'Subscriber preferences for the different channels regarding this workflow', + }) + channels: PreferenceChannels; + + @ApiPropertyOptional({ + type: Overrides, + description: 'Overrides for subscriber preferences for the different channels regarding this workflow', + }) + overrides?: Overrides; +} + +export class GetSubscriberPreferencesResponseDto { + @ApiPropertyOptional({ + type: TemplateResponse, + description: 'The workflow information and if it is critical or not', + }) + template?: TemplateResponse; + + @ApiProperty({ + type: Preference, + description: 'The preferences of the subscriber regarding the related workflow', + }) + preference: Preference; +} diff --git a/apps/api/src/app/subscribers/dtos/index.ts b/apps/api/src/app/subscribers/dtos/index.ts index 0593dafc420..85bbe277b7d 100644 --- a/apps/api/src/app/subscribers/dtos/index.ts +++ b/apps/api/src/app/subscribers/dtos/index.ts @@ -4,3 +4,5 @@ export * from './subscriber-response.dto'; export * from './subscribers-response.dto'; export * from './update-subscriber-channel-request.dto'; export * from './update-subscriber-request.dto'; +export * from './get-subscriber-preferences-response.dto'; +export * from './update-subscriber-global-preferences-request.dto'; diff --git a/apps/api/src/app/subscribers/dtos/update-subscriber-global-preferences-request.dto.ts b/apps/api/src/app/subscribers/dtos/update-subscriber-global-preferences-request.dto.ts new file mode 100644 index 00000000000..0572b978e0f --- /dev/null +++ b/apps/api/src/app/subscribers/dtos/update-subscriber-global-preferences-request.dto.ts @@ -0,0 +1,23 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsOptional, ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; +import { ChannelPreference } from '../../shared/dtos/channel-preference'; + +export class UpdateSubscriberGlobalPreferencesRequestDto { + @ApiPropertyOptional({ + description: 'Enable or disable the subscriber global preferences.', + type: Boolean, + }) + @IsBoolean() + @IsOptional() + enabled?: boolean; + + @ApiPropertyOptional({ + type: [ChannelPreference], + description: 'The subscriber global preferences for every ChannelTypeEnum.', + }) + @IsOptional() + @ValidateNested() + @Type(() => ChannelPreference) + preferences?: ChannelPreference[]; +} diff --git a/apps/api/src/app/subscribers/e2e/helpers/index.ts b/apps/api/src/app/subscribers/e2e/helpers/index.ts index 4f7ea77b384..0ab317653f7 100644 --- a/apps/api/src/app/subscribers/e2e/helpers/index.ts +++ b/apps/api/src/app/subscribers/e2e/helpers/index.ts @@ -4,6 +4,7 @@ import axios from 'axios'; import { UpdateSubscriberOnlineFlagRequestDto } from '../../dtos/update-subscriber-online-flag-request.dto'; import { UpdateSubscriberPreferenceRequestDto } from '../../../widgets/dtos/update-subscriber-preference-request.dto'; +import { UpdateSubscriberGlobalPreferencesRequestDto } from '../../dtos/update-subscriber-global-preferences-request.dto'; const axiosInstance = axios.create(); @@ -65,3 +66,11 @@ export async function updatePreference( } ); } + +export async function updateGlobalPreferences(data: UpdateSubscriberGlobalPreferencesRequestDto, session: UserSession) { + return await axiosInstance.patch(`${session.serverUrl}/v1/subscribers/${session.subscriberId}/preferences`, data, { + headers: { + authorization: `ApiKey ${session.apiKey}`, + }, + }); +} diff --git a/apps/api/src/app/subscribers/e2e/update-global-preference.e2e.ts b/apps/api/src/app/subscribers/e2e/update-global-preference.e2e.ts new file mode 100644 index 00000000000..2da13942648 --- /dev/null +++ b/apps/api/src/app/subscribers/e2e/update-global-preference.e2e.ts @@ -0,0 +1,115 @@ +import { ChannelTypeEnum } from '@novu/shared'; +import { UserSession } from '@novu/testing'; +import { expect } from 'chai'; + +import { updateGlobalPreferences } from './helpers'; + +describe('Update Subscribers global preferences - /subscribers/:subscriberId/preferences (PATCH)', function () { + let session: UserSession; + + beforeEach(async () => { + session = new UserSession(); + await session.initialize(); + }); + + it('should validate the payload', async function () { + const badPayload = { + enabled: true, + preferences: false, + }; + + try { + const firstResponse = await updateGlobalPreferences(badPayload as any, session); + expect(firstResponse).to.not.be.ok; + } catch (error) { + expect(error.toJSON()).to.have.include({ + status: 400, + name: 'AxiosError', + message: 'Request failed with status code 400', + }); + } + + const yetAnotherBadPayload = { + enabled: 'hello', + preferences: [{ type: ChannelTypeEnum.EMAIL, enabled: true }], + }; + + try { + const secondResponse = await updateGlobalPreferences(yetAnotherBadPayload as any, session); + expect(secondResponse).to.not.be.ok; + } catch (error) { + expect(error.toJSON()).to.have.include({ + status: 400, + name: 'AxiosError', + message: 'Request failed with status code 400', + }); + } + }); + + it('should update user global preferences', async function () { + const payload = { + enabled: true, + preferences: [{ type: ChannelTypeEnum.EMAIL, enabled: true }], + }; + + const response = await updateGlobalPreferences(payload, session); + + expect(response.data.data.preference.enabled).to.eql(true); + expect(response.data.data.preference.channels).to.not.eql({ + [ChannelTypeEnum.IN_APP]: true, + }); + expect(response.data.data.preference.channels).to.eql({ + [ChannelTypeEnum.EMAIL]: true, + [ChannelTypeEnum.SMS]: true, + [ChannelTypeEnum.CHAT]: true, + [ChannelTypeEnum.PUSH]: true, + [ChannelTypeEnum.IN_APP]: true, + }); + }); + + it('should update user global preferences for multiple channels', async function () { + const payload = { + enabled: true, + preferences: [ + { type: ChannelTypeEnum.PUSH, enabled: true }, + { type: ChannelTypeEnum.IN_APP, enabled: false }, + { type: ChannelTypeEnum.SMS, enabled: true }, + ], + }; + + const response = await updateGlobalPreferences(payload, session); + + expect(response.data.data.preference.enabled).to.eql(true); + expect(response.data.data.preference.channels).to.eql({ + [ChannelTypeEnum.PUSH]: true, + [ChannelTypeEnum.IN_APP]: false, + [ChannelTypeEnum.SMS]: true, + [ChannelTypeEnum.EMAIL]: true, + [ChannelTypeEnum.CHAT]: true, + }); + }); + + it('should update user global preference and disable the flag for the future channels update', async function () { + const disablePreferenceData = { + enabled: false, + }; + + const response = await updateGlobalPreferences(disablePreferenceData, session); + + expect(response.data.data.preference.enabled).to.eql(false); + + const preferenceChannel = { + preferences: [{ type: ChannelTypeEnum.EMAIL, enabled: true }], + }; + + const res = await updateGlobalPreferences(preferenceChannel, session); + + expect(res.data.data.preference.channels).to.eql({ + [ChannelTypeEnum.EMAIL]: true, + [ChannelTypeEnum.SMS]: true, + [ChannelTypeEnum.CHAT]: true, + [ChannelTypeEnum.PUSH]: true, + [ChannelTypeEnum.IN_APP]: true, + }); + }); +}); diff --git a/apps/api/src/app/subscribers/params/get-subscriber-preferences-by-level.params.ts b/apps/api/src/app/subscribers/params/get-subscriber-preferences-by-level.params.ts new file mode 100644 index 00000000000..18196b59147 --- /dev/null +++ b/apps/api/src/app/subscribers/params/get-subscriber-preferences-by-level.params.ts @@ -0,0 +1,10 @@ +import { IsEnum, IsString } from 'class-validator'; +import { PreferenceLevelEnum } from '@novu/dal'; + +export class GetSubscriberPreferencesByLevelParams { + @IsEnum(PreferenceLevelEnum) + level: PreferenceLevelEnum; + + @IsString() + subscriberId: string; +} diff --git a/apps/api/src/app/subscribers/params/index.ts b/apps/api/src/app/subscribers/params/index.ts new file mode 100644 index 00000000000..cdca2379644 --- /dev/null +++ b/apps/api/src/app/subscribers/params/index.ts @@ -0,0 +1 @@ +export * from './get-subscriber-preferences-by-level.params'; diff --git a/apps/api/src/app/subscribers/subscribers.controller.ts b/apps/api/src/app/subscribers/subscribers.controller.ts index 85f0b987bb1..efc5eb97242 100644 --- a/apps/api/src/app/subscribers/subscribers.controller.ts +++ b/apps/api/src/app/subscribers/subscribers.controller.ts @@ -21,9 +21,9 @@ import { UpdateSubscriber, UpdateSubscriberCommand, } from '@novu/application-generic'; -import { ApiOperation, ApiTags, ApiNoContentResponse } from '@nestjs/swagger'; +import { ApiOperation, ApiTags, ApiNoContentResponse, ApiParam } from '@nestjs/swagger'; import { ButtonTypeEnum, ChatProviderIdEnum, IJwtPayload } from '@novu/shared'; -import { MessageEntity } from '@novu/dal'; +import { MessageEntity, PreferenceLevelEnum } from '@novu/dal'; import { RemoveSubscriber, RemoveSubscriberCommand } from './usecases/remove-subscriber'; import { JwtAuthGuard } from '../auth/framework/auth.guard'; @@ -33,15 +33,17 @@ import { BulkSubscriberCreateDto, CreateSubscriberRequestDto, DeleteSubscriberResponseDto, + GetSubscriberPreferencesResponseDto, SubscriberResponseDto, UpdateSubscriberChannelRequestDto, + UpdateSubscriberGlobalPreferencesRequestDto, UpdateSubscriberRequestDto, } from './dtos'; import { UpdateSubscriberChannel, UpdateSubscriberChannelCommand } from './usecases/update-subscriber-channel'; import { GetSubscribers, GetSubscribersCommand } from './usecases/get-subscribers'; import { GetSubscriber, GetSubscriberCommand } from './usecases/get-subscriber'; -import { GetPreferencesCommand } from './usecases/get-preferences/get-preferences.command'; -import { GetPreferences } from './usecases/get-preferences/get-preferences.usecase'; +import { GetPreferencesByLevelCommand } from './usecases/get-preferences-by-level/get-preferences-by-level.command'; +import { GetPreferencesByLevel } from './usecases/get-preferences-by-level/get-preferences-by-level.usecase'; import { UpdatePreference } from './usecases/update-preference/update-preference.usecase'; import { UpdateSubscriberPreferenceCommand } from './usecases/update-subscriber-preference'; import { UpdateSubscriberPreferenceResponseDto } from '../widgets/dtos/update-subscriber-preference-response.dto'; @@ -83,6 +85,11 @@ import { MarkAllMessagesAs } from '../widgets/usecases/mark-all-messages-as/mark import { MarkAllMessageAsRequestDto } from './dtos/mark-all-messages-as-request.dto'; import { BulkCreateSubscribers } from './usecases/bulk-create-subscribers/bulk-create-subscribers.usecase'; import { BulkCreateSubscribersCommand } from './usecases/bulk-create-subscribers'; +import { + UpdateSubscriberGlobalPreferences, + UpdateSubscriberGlobalPreferencesCommand, +} from './usecases/update-subscriber-global-preferences'; +import { GetSubscriberPreferencesByLevelParams } from './params'; @Controller('/subscribers') @ApiTags('Subscribers') @@ -95,8 +102,9 @@ export class SubscribersController { private removeSubscriberUsecase: RemoveSubscriber, private getSubscriberUseCase: GetSubscriber, private getSubscribersUsecase: GetSubscribers, - private getPreferenceUsecase: GetPreferences, + private getPreferenceUsecase: GetPreferencesByLevel, private updatePreferenceUsecase: UpdatePreference, + private updateGlobalPreferenceUsecase: UpdateSubscriberGlobalPreferences, private getNotificationsFeedUsecase: GetNotificationsFeed, private getFeedCountUsecase: GetFeedCount, private markMessageAsUsecase: MarkMessageAs, @@ -336,10 +344,34 @@ export class SubscribersController { @UserSession() user: IJwtPayload, @Param('subscriberId') subscriberId: string ): Promise { - const command = GetPreferencesCommand.create({ + const command = GetPreferencesByLevelCommand.create({ + organizationId: user.organizationId, + subscriberId: subscriberId, + environmentId: user.environmentId, + level: PreferenceLevelEnum.TEMPLATE, + }); + + return (await this.getPreferenceUsecase.execute(command)) as UpdateSubscriberPreferenceResponseDto[]; + } + + @Get('/:subscriberId/preferences/:level') + @ExternalApiAccessible() + @UseGuards(JwtAuthGuard) + @ApiResponse(GetSubscriberPreferencesResponseDto, 200, true) + @ApiOperation({ + summary: 'Get subscriber preferences by level', + }) + @ApiParam({ name: 'subscriberId', type: String, required: true }) + @ApiParam({ name: 'level', type: String, required: true }) + async getSubscriberPreferenceByLevel( + @UserSession() user: IJwtPayload, + @Param() { level, subscriberId }: GetSubscriberPreferencesByLevelParams + ): Promise { + const command = GetPreferencesByLevelCommand.create({ organizationId: user.organizationId, subscriberId: subscriberId, environmentId: user.environmentId, + level: level, }); return await this.getPreferenceUsecase.execute(command); @@ -370,6 +402,29 @@ export class SubscribersController { return await this.updatePreferenceUsecase.execute(command); } + @Patch('/:subscriberId/preferences') + @ExternalApiAccessible() + @UseGuards(JwtAuthGuard) + @ApiResponse(UpdateSubscriberPreferenceResponseDto) + @ApiOperation({ + summary: 'Update subscriber global preferences', + }) + async updateSubscriberGlobalPreferences( + @UserSession() user: IJwtPayload, + @Param('subscriberId') subscriberId: string, + @Body() body: UpdateSubscriberGlobalPreferencesRequestDto + ) { + const command = UpdateSubscriberGlobalPreferencesCommand.create({ + organizationId: user.organizationId, + subscriberId: subscriberId, + environmentId: user.environmentId, + enabled: body.enabled, + preferences: body.preferences, + }); + + return await this.updateGlobalPreferenceUsecase.execute(command); + } + @ExternalApiAccessible() @UseGuards(JwtAuthGuard) @Get('/:subscriberId/notifications/feed') diff --git a/apps/api/src/app/subscribers/usecases/get-preferences-by-level/get-preferences-by-level.command.ts b/apps/api/src/app/subscribers/usecases/get-preferences-by-level/get-preferences-by-level.command.ts new file mode 100644 index 00000000000..f1956f68bff --- /dev/null +++ b/apps/api/src/app/subscribers/usecases/get-preferences-by-level/get-preferences-by-level.command.ts @@ -0,0 +1,13 @@ +import { IsDefined, IsEnum, IsString } from 'class-validator'; +import { PreferenceLevelEnum } from '@novu/dal'; +import { EnvironmentCommand } from '../../../shared/commands/project.command'; + +export class GetPreferencesByLevelCommand extends EnvironmentCommand { + @IsString() + @IsDefined() + subscriberId: string; + + @IsEnum(PreferenceLevelEnum) + @IsDefined() + level: PreferenceLevelEnum; +} diff --git a/apps/api/src/app/subscribers/usecases/get-preferences-by-level/get-preferences-by-level.usecase.ts b/apps/api/src/app/subscribers/usecases/get-preferences-by-level/get-preferences-by-level.usecase.ts new file mode 100644 index 00000000000..47358c4e172 --- /dev/null +++ b/apps/api/src/app/subscribers/usecases/get-preferences-by-level/get-preferences-by-level.usecase.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { + GetSubscriberGlobalPreference, + GetSubscriberGlobalPreferenceCommand, + GetSubscriberPreference, + GetSubscriberPreferenceCommand, +} from '@novu/application-generic'; +import { PreferenceLevelEnum } from '@novu/dal'; + +import { GetPreferencesByLevelCommand } from './get-preferences-by-level.command'; + +@Injectable() +export class GetPreferencesByLevel { + constructor( + private getSubscriberPreferenceUsecase: GetSubscriberPreference, + private getSubscriberGlobalPreference: GetSubscriberGlobalPreference + ) {} + + async execute(command: GetPreferencesByLevelCommand) { + if (command.level === PreferenceLevelEnum.GLOBAL) { + const globalPreferenceCommand = GetSubscriberGlobalPreferenceCommand.create({ + organizationId: command.organizationId, + environmentId: command.environmentId, + subscriberId: command.subscriberId, + }); + const globalPreferences = await this.getSubscriberGlobalPreference.execute(globalPreferenceCommand); + + return [globalPreferences]; + } + + const preferenceCommand = GetSubscriberPreferenceCommand.create({ + organizationId: command.organizationId, + environmentId: command.environmentId, + subscriberId: command.subscriberId, + }); + + return await this.getSubscriberPreferenceUsecase.execute(preferenceCommand); + } +} diff --git a/apps/api/src/app/subscribers/usecases/get-preferences/get-preferences.command.ts b/apps/api/src/app/subscribers/usecases/get-preferences/get-preferences.command.ts deleted file mode 100644 index 6a9e257168d..00000000000 --- a/apps/api/src/app/subscribers/usecases/get-preferences/get-preferences.command.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IsDefined, IsString } from 'class-validator'; -import { EnvironmentCommand } from '../../../shared/commands/project.command'; - -export class GetPreferencesCommand extends EnvironmentCommand { - @IsString() - @IsDefined() - subscriberId: string; -} diff --git a/apps/api/src/app/subscribers/usecases/get-preferences/get-preferences.usecase.ts b/apps/api/src/app/subscribers/usecases/get-preferences/get-preferences.usecase.ts deleted file mode 100644 index 57f8c647492..00000000000 --- a/apps/api/src/app/subscribers/usecases/get-preferences/get-preferences.usecase.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { GetSubscriberPreference, GetSubscriberPreferenceCommand } from '@novu/application-generic'; - -import { GetPreferencesCommand } from './get-preferences.command'; - -@Injectable() -export class GetPreferences { - constructor(private getSubscriberPreferenceUsecase: GetSubscriberPreference) {} - - async execute(command: GetPreferencesCommand) { - const preferenceCommand = GetSubscriberPreferenceCommand.create({ - organizationId: command.organizationId, - environmentId: command.environmentId, - subscriberId: command.subscriberId, - }); - - return await this.getSubscriberPreferenceUsecase.execute(preferenceCommand); - } -} diff --git a/apps/api/src/app/subscribers/usecases/index.ts b/apps/api/src/app/subscribers/usecases/index.ts index 2035c03160e..7569c1263fc 100644 --- a/apps/api/src/app/subscribers/usecases/index.ts +++ b/apps/api/src/app/subscribers/usecases/index.ts @@ -3,11 +3,12 @@ import { GetSubscriberTemplatePreference, UpdateSubscriber, CreateSubscriber, + GetSubscriberGlobalPreference, } from '@novu/application-generic'; import { GetSubscribers } from './get-subscribers'; import { GetSubscriber } from './get-subscriber'; -import { GetPreferences } from './get-preferences/get-preferences.usecase'; +import { GetPreferencesByLevel } from './get-preferences-by-level/get-preferences-by-level.usecase'; import { RemoveSubscriber } from './remove-subscriber'; import { SearchByExternalSubscriberIds } from './search-by-external-subscriber-ids'; import { UpdatePreference } from './update-preference/update-preference.usecase'; @@ -18,6 +19,7 @@ import { ChatOauth } from './chat-oauth/chat-oauth.usecase'; import { ChatOauthCallback } from './chat-oauth-callback/chat-oauth-callback.usecase'; import { DeleteSubscriberCredentials } from './delete-subscriber-credentials/delete-subscriber-credentials.usecase'; import { BulkCreateSubscribers } from './bulk-create-subscribers/bulk-create-subscribers.usecase'; +import { UpdateSubscriberGlobalPreferences } from './update-subscriber-global-preferences'; export { SearchByExternalSubscriberIds, @@ -30,7 +32,7 @@ export const USE_CASES = [ GetSubscriber, GetSubscriberPreference, GetSubscriberTemplatePreference, - GetPreferences, + GetPreferencesByLevel, RemoveSubscriber, SearchByExternalSubscriberIds, UpdatePreference, @@ -42,4 +44,6 @@ export const USE_CASES = [ ChatOauth, DeleteSubscriberCredentials, BulkCreateSubscribers, + UpdateSubscriberGlobalPreferences, + GetSubscriberGlobalPreference, ]; diff --git a/apps/api/src/app/subscribers/usecases/update-subscriber-global-preferences/index.ts b/apps/api/src/app/subscribers/usecases/update-subscriber-global-preferences/index.ts new file mode 100644 index 00000000000..59adeb0c6ff --- /dev/null +++ b/apps/api/src/app/subscribers/usecases/update-subscriber-global-preferences/index.ts @@ -0,0 +1,2 @@ +export * from './update-subscriber-global-preferences.command'; +export * from './update-subscriber-global-preferences.usecase'; diff --git a/apps/api/src/app/subscribers/usecases/update-subscriber-global-preferences/update-subscriber-global-preferences.command.ts b/apps/api/src/app/subscribers/usecases/update-subscriber-global-preferences/update-subscriber-global-preferences.command.ts new file mode 100644 index 00000000000..9c3cf879b18 --- /dev/null +++ b/apps/api/src/app/subscribers/usecases/update-subscriber-global-preferences/update-subscriber-global-preferences.command.ts @@ -0,0 +1,15 @@ +import { Type } from 'class-transformer'; +import { IsBoolean, IsOptional, ValidateNested } from 'class-validator'; +import { EnvironmentWithSubscriber } from '../../../shared/commands/project.command'; +import { ChannelPreference } from '../../../shared/dtos/channel-preference'; + +export class UpdateSubscriberGlobalPreferencesCommand extends EnvironmentWithSubscriber { + @IsBoolean() + @IsOptional() + enabled?: boolean; + + @IsOptional() + @ValidateNested() + @Type(() => ChannelPreference) + preferences?: ChannelPreference[]; +} diff --git a/apps/api/src/app/subscribers/usecases/update-subscriber-global-preferences/update-subscriber-global-preferences.usecase.ts b/apps/api/src/app/subscribers/usecases/update-subscriber-global-preferences/update-subscriber-global-preferences.usecase.ts new file mode 100644 index 00000000000..580e5672423 --- /dev/null +++ b/apps/api/src/app/subscribers/usecases/update-subscriber-global-preferences/update-subscriber-global-preferences.usecase.ts @@ -0,0 +1,105 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { GetSubscriberGlobalPreference, GetSubscriberGlobalPreferenceCommand } from '@novu/application-generic'; +import { + ChannelTypeEnum, + PreferenceLevelEnum, + SubscriberEntity, + SubscriberPreferenceEntity, + SubscriberPreferenceRepository, + SubscriberRepository, +} from '@novu/dal'; + +import { UpdateSubscriberGlobalPreferencesCommand } from './update-subscriber-global-preferences.command'; + +@Injectable() +export class UpdateSubscriberGlobalPreferences { + constructor( + private subscriberPreferenceRepository: SubscriberPreferenceRepository, + private subscriberRepository: SubscriberRepository, + private getSubscriberGlobalPreference: GetSubscriberGlobalPreference + ) {} + + async execute(command: UpdateSubscriberGlobalPreferencesCommand) { + const subscriber = await this.subscriberRepository.findBySubscriberId(command.environmentId, command.subscriberId); + if (!subscriber) throw new NotFoundException(`Subscriber not found`); + + const userGlobalPreference = await this.subscriberPreferenceRepository.findOne({ + _organizationId: command.organizationId, + _environmentId: command.environmentId, + _subscriberId: subscriber._id, + level: PreferenceLevelEnum.GLOBAL, + }); + + if (!userGlobalPreference) { + await this.createUserPreference(command, subscriber); + } else { + await this.updateUserPreference(command, subscriber); + } + + return await this.getSubscriberGlobalPreference.execute( + GetSubscriberGlobalPreferenceCommand.create({ + organizationId: command.organizationId, + environmentId: command.environmentId, + subscriberId: command.subscriberId, + }) + ); + } + + private async createUserPreference( + command: UpdateSubscriberGlobalPreferencesCommand, + subscriber: SubscriberEntity + ): Promise { + const channelObj = {} as Record; + if (command.preferences && command.preferences.length > 0) { + for (const preference of command.preferences) { + if (preference.type) { + channelObj[preference.type] = preference.enabled; + } + } + } + + await this.subscriberPreferenceRepository.create({ + _environmentId: command.environmentId, + _organizationId: command.organizationId, + _subscriberId: subscriber._id, + /* + * Unless explicitly set to false when creating a user preference we want it to be enabled + * even if not passing at first enabled to true. + */ + enabled: command.enabled !== false, + channels: command.preferences && command.preferences.length > 0 ? channelObj : null, + level: PreferenceLevelEnum.GLOBAL, + }); + } + + private async updateUserPreference( + command: UpdateSubscriberGlobalPreferencesCommand, + subscriber: SubscriberEntity + ): Promise { + const updatePayload: Partial = {}; + + if (command.enabled != null) { + updatePayload.enabled = command.enabled; + } + + if (command.preferences && command.preferences.length > 0) { + for (const preference of command.preferences) { + if (preference.type) { + updatePayload[`channels.${preference.type}`] = preference.enabled; + } + } + } + + await this.subscriberPreferenceRepository.update( + { + _environmentId: command.environmentId, + _organizationId: command.organizationId, + _subscriberId: subscriber._id, + level: PreferenceLevelEnum.GLOBAL, + }, + { + $set: updatePayload, + } + ); + } +} diff --git a/apps/api/src/app/subscribers/usecases/update-subscriber-preference/update-subscriber-preference.usecase.ts b/apps/api/src/app/subscribers/usecases/update-subscriber-preference/update-subscriber-preference.usecase.ts index 9af25798015..ceac6dbf431 100644 --- a/apps/api/src/app/subscribers/usecases/update-subscriber-preference/update-subscriber-preference.usecase.ts +++ b/apps/api/src/app/subscribers/usecases/update-subscriber-preference/update-subscriber-preference.usecase.ts @@ -6,6 +6,7 @@ import { SubscriberEntity, SubscriberRepository, MemberRepository, + PreferenceLevelEnum, } from '@novu/dal'; import { AnalyticsService, @@ -91,6 +92,7 @@ export class UpdateSubscriberPreference { */ enabled: command.enabled !== false, channels: command.channel?.type ? channelObj : null, + level: PreferenceLevelEnum.TEMPLATE, }); } @@ -118,6 +120,7 @@ export class UpdateSubscriberPreference { _organizationId: command.organizationId, _subscriberId: subscriber._id, _templateId: command.templateId, + level: PreferenceLevelEnum.TEMPLATE, }, { $set: updatePayload, diff --git a/apps/api/src/app/widgets/widgets.controller.ts b/apps/api/src/app/widgets/widgets.controller.ts index decc1d30260..cdff461f9b3 100644 --- a/apps/api/src/app/widgets/widgets.controller.ts +++ b/apps/api/src/app/widgets/widgets.controller.ts @@ -16,7 +16,7 @@ import { import { AuthGuard } from '@nestjs/passport'; import { ApiExcludeController, ApiNoContentResponse, ApiOperation, ApiQuery } from '@nestjs/swagger'; import { AnalyticsService, GetSubscriberPreference, GetSubscriberPreferenceCommand } from '@novu/application-generic'; -import { MessageEntity, SubscriberEntity } from '@novu/dal'; +import { MessageEntity, PreferenceLevelEnum, SubscriberEntity } from '@novu/dal'; import { MarkMessagesAsEnum, ButtonTypeEnum, MessageActionStatusEnum } from '@novu/shared'; import { SubscriberSession } from '../shared/framework/user.decorator'; @@ -54,6 +54,13 @@ import { LimitPipe } from './pipes/limit-pipe/limit-pipe'; import { RemoveAllMessagesCommand } from './usecases/remove-messages/remove-all-messages.command'; import { RemoveAllMessages } from './usecases/remove-messages/remove-all-messages.usecase'; import { RemoveAllMessagesDto } from './dtos/remove-all-messages.dto'; +import { + UpdateSubscriberGlobalPreferences, + UpdateSubscriberGlobalPreferencesCommand, +} from '../subscribers/usecases/update-subscriber-global-preferences'; +import { UpdateSubscriberGlobalPreferencesRequestDto } from '../subscribers/dtos/update-subscriber-global-preferences-request.dto'; +import { GetPreferencesByLevel } from '../subscribers/usecases/get-preferences-by-level/get-preferences-by-level.usecase'; +import { GetPreferencesByLevelCommand } from '../subscribers/usecases/get-preferences-by-level/get-preferences-by-level.command'; @Controller('/widgets') @ApiExcludeController() @@ -68,7 +75,9 @@ export class WidgetsController { private updateMessageActionsUsecase: UpdateMessageActions, private getOrganizationUsecase: GetOrganizationData, private getSubscriberPreferenceUsecase: GetSubscriberPreference, + private getSubscriberPreferenceByLevelUsecase: GetPreferencesByLevel, private updateSubscriberPreferenceUsecase: UpdateSubscriberPreference, + private updateSubscriberGlobalPreferenceUsecase: UpdateSubscriberGlobalPreferences, private markAllMessagesAsUsecase: MarkAllMessagesAs, private analyticsService: AnalyticsService ) {} @@ -350,6 +359,22 @@ export class WidgetsController { return await this.getSubscriberPreferenceUsecase.execute(command); } + @UseGuards(AuthGuard('subscriberJwt')) + @Get('/preferences/:level') + async getSubscriberPreferenceByLevel( + @SubscriberSession() subscriberSession: SubscriberEntity, + @Param('level') level: PreferenceLevelEnum + ) { + const command = GetPreferencesByLevelCommand.create({ + organizationId: subscriberSession._organizationId, + subscriberId: subscriberSession.subscriberId, + environmentId: subscriberSession._environmentId, + level, + }); + + return await this.getSubscriberPreferenceByLevelUsecase.execute(command); + } + @UseGuards(AuthGuard('subscriberJwt')) @Patch('/preferences/:templateId') async updateSubscriberPreference( @@ -369,6 +394,23 @@ export class WidgetsController { return await this.updateSubscriberPreferenceUsecase.execute(command); } + @UseGuards(AuthGuard('subscriberJwt')) + @Patch('/preferences') + async updateSubscriberGlobalPreference( + @SubscriberSession() subscriberSession: SubscriberEntity, + @Body() body: UpdateSubscriberGlobalPreferencesRequestDto + ) { + const command = UpdateSubscriberGlobalPreferencesCommand.create({ + organizationId: subscriberSession._organizationId, + subscriberId: subscriberSession.subscriberId, + environmentId: subscriberSession._environmentId, + preferences: body.preferences, + enabled: body.enabled, + }); + + return await this.updateSubscriberGlobalPreferenceUsecase.execute(command); + } + @UseGuards(AuthGuard('subscriberJwt')) @Post('/usage/log') async logUsage( diff --git a/apps/inbound-mail/package.json b/apps/inbound-mail/package.json index c6b33eddec1..d3631b027b5 100644 --- a/apps/inbound-mail/package.json +++ b/apps/inbound-mail/package.json @@ -1,6 +1,6 @@ { "name": "@novu/inbound-mail", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "", "author": "", "private": true, @@ -19,8 +19,8 @@ "test": "cross-env TS_NODE_COMPILER_OPTIONS='{\"strictNullChecks\": false}' TZ=UTC NODE_ENV=test E2E_RUNNER=true mocha --trace-warnings --timeout 10000 --require ts-node/register --exit --file e2e/setup.ts src/**/**/*.spec.ts" }, "dependencies": { - "@novu/application-generic": "^0.19.0", - "@novu/shared": "^0.19.0", + "@novu/application-generic": "^0.20.0-alpha.0", + "@novu/shared": "^0.20.0-alpha.0", "@sentry/node": "^7.12.1", "bluebird": "^2.9.30", "dotenv": "^8.6.0", @@ -39,7 +39,7 @@ "winston": "^3.9.0" }, "devDependencies": { - "@novu/testing": "^0.19.0", + "@novu/testing": "^0.20.0-alpha.0", "@types/chai": "^4.2.11", "@types/express": "^4.17.8", "@types/html-to-text": "^9.0.1", diff --git a/apps/web/.env b/apps/web/.env index 8977904e773..23c860488a3 100644 --- a/apps/web/.env +++ b/apps/web/.env @@ -1,5 +1,6 @@ SKIP_PREFLIGHT_CHECK=true REACT_APP_ENVIRONMENT=dev +REACT_APP_VERSION=$npm_package_version REACT_APP_API_URL= REACT_APP_WS_URL= REACT_APP_WEBHOOK_URL= diff --git a/apps/web/package.json b/apps/web/package.json index 1fa2b8c0434..1a8ebcc7f19 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@novu/web", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "private": true, "scripts": { "start": "cross-env PORT=4200 react-app-rewired start", @@ -49,8 +49,8 @@ "@mantine/notifications": "^5.7.1", "@mantine/prism": "^5.7.1", "@mantine/spotlight": "^5.7.1", - "@novu/notification-center": "^0.19.0", - "@novu/shared": "^0.19.0", + "@novu/notification-center": "^0.20.0-alpha.0", + "@novu/shared": "^0.20.0-alpha.0", "@segment/analytics-next": "^1.48.0", "@sentry/react": "^7.40.0", "@sentry/tracing": "^7.40.0", @@ -126,8 +126,8 @@ "@babel/preset-react": "^7.13.13", "@babel/preset-typescript": "^7.13.0", "@babel/runtime": "^7.20.13", - "@novu/dal": "^0.19.0", - "@novu/testing": "^0.19.0", + "@novu/dal": "^0.20.0-alpha.0", + "@novu/testing": "^0.20.0-alpha.0", "@storybook/addon-actions": "^7.4.2", "@storybook/addon-essentials": "^7.4.2", "@storybook/addon-links": "^7.4.2", diff --git a/apps/web/src/components/layout/components/HeaderNav.tsx b/apps/web/src/components/layout/components/HeaderNav.tsx index 214d5d62a0f..fb12825af38 100644 --- a/apps/web/src/components/layout/components/HeaderNav.tsx +++ b/apps/web/src/components/layout/components/HeaderNav.tsx @@ -5,7 +5,7 @@ import { Link } from 'react-router-dom'; import { useIntercom } from 'react-use-intercom'; import LogRocket from 'logrocket'; -import { CONTEXT_PATH, INTERCOM_APP_ID, IS_DOCKER_HOSTED, LOGROCKET_ID } from '../../../config'; +import { CONTEXT_PATH, INTERCOM_APP_ID, IS_DOCKER_HOSTED, LOGROCKET_ID, REACT_APP_VERSION } from '../../../config'; import { ROUTES } from '../../../constants/routes.enum'; import { colors, Dropdown, shadows, Text, Tooltip } from '../../../design-system'; import { Ellipse, Mail, Moon, Question, Sun, Trash } from '../../../design-system/icons'; @@ -158,6 +158,19 @@ export function HeaderNav({ isIntercomOpened }: Props) { , ]; + isSelfHosted && + profileMenuMantine.push( + + Version: {REACT_APP_VERSION} + + ); + return (
Sign In with GitHub - Sign In with Google - + */} Or} color={colors.B30} labelPosition="center" my="md" /> diff --git a/apps/webhook/package.json b/apps/webhook/package.json index 4448c955d19..26de79fcf22 100644 --- a/apps/webhook/package.json +++ b/apps/webhook/package.json @@ -1,6 +1,6 @@ { "name": "@novu/webhook", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "", "author": "", "private": true, @@ -25,11 +25,11 @@ "@nestjs/core": "^10.2.2", "@nestjs/platform-express": "^10.2.2", "@nestjs/terminus": "^10.0.1", - "@novu/application-generic": "^0.19.0", - "@novu/dal": "^0.19.0", - "@novu/shared": "^0.19.0", - "@novu/stateless": "^0.19.0", - "@novu/testing": "^0.19.0", + "@novu/application-generic": "^0.20.0-alpha.0", + "@novu/dal": "^0.20.0-alpha.0", + "@novu/shared": "^0.20.0-alpha.0", + "@novu/stateless": "^0.20.0-alpha.0", + "@novu/testing": "^0.20.0-alpha.0", "@sentry/node": "^7.66.0", "axios": "^1.3.3", "class-transformer": "^0.5.1", diff --git a/apps/widget/package.json b/apps/widget/package.json index 64f36674b15..faf9f58a3af 100644 --- a/apps/widget/package.json +++ b/apps/widget/package.json @@ -1,6 +1,6 @@ { "name": "@novu/widget", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "private": true, "scripts": { "start": "craco start", @@ -29,8 +29,8 @@ "@emotion/styled": "^11.6.0", "@mantine/core": "4.2.12", "@mantine/hooks": "4.2.12", - "@novu/notification-center": "^0.19.0", - "@novu/shared": "^0.19.0", + "@novu/notification-center": "^0.20.0-alpha.0", + "@novu/shared": "^0.20.0-alpha.0", "antd": "^4.10.0", "babel-plugin-import": "^1.13.3", "chroma-js": "^2.4.2", @@ -55,8 +55,8 @@ "@craco/craco": "^7.0.0", "@emotion/babel-plugin": "^11.7.2", "@faker-js/faker": "^6.0.0", - "@novu/dal": "^0.19.0", - "@novu/testing": "^0.19.0", + "@novu/dal": "^0.20.0-alpha.0", + "@novu/testing": "^0.20.0-alpha.0", "@types/jest": "^29.5.0", "@types/node": "^12.0.0", "@types/react": "17.0.62", @@ -74,7 +74,7 @@ "less-loader": "4.1.0", "typescript": "4.9.5", "webpack-dev-server": "4.11.1", - "webpack": "^5.78.0" + "webpack": "5.78.0" }, "browserslist": { "production": [ diff --git a/apps/worker/package.json b/apps/worker/package.json index 1ed87723264..5ea2385c5b8 100644 --- a/apps/worker/package.json +++ b/apps/worker/package.json @@ -1,6 +1,6 @@ { "name": "@novu/worker", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "description", "author": "", "private": "true", @@ -29,11 +29,11 @@ "@nestjs/platform-express": "^10.2.2", "@nestjs/swagger": "^7.1.9", "@nestjs/terminus": "^10.0.1", - "@novu/application-generic": "^0.19.0", - "@novu/dal": "^0.19.0", - "@novu/shared": "^0.19.0", - "@novu/stateless": "^0.19.0", - "@novu/testing": "^0.19.0", + "@novu/application-generic": "^0.20.0-alpha.0", + "@novu/dal": "^0.20.0-alpha.0", + "@novu/shared": "^0.20.0-alpha.0", + "@novu/stateless": "^0.20.0-alpha.0", + "@novu/testing": "^0.20.0-alpha.0", "@sentry/node": "^7.40.0", "@sentry/tracing": "^7.40.0", "@types/newrelic": "^9.13.0", diff --git a/apps/worker/src/app/workflow/services/cold-start.service.ts b/apps/worker/src/app/workflow/services/cold-start.service.ts index 191223d7024..f132c7decf3 100644 --- a/apps/worker/src/app/workflow/services/cold-start.service.ts +++ b/apps/worker/src/app/workflow/services/cold-start.service.ts @@ -3,6 +3,7 @@ import { INovuWorker, ReadinessService } from '@novu/application-generic'; import { StandardWorker } from './standard.worker'; import { WorkflowWorker } from './workflow.worker'; +import { OldInstanceStandardWorker } from './old-instance-standard.worker'; import { OldInstanceWorkflowWorker } from './old-instance-workflow.worker'; /** @@ -11,9 +12,10 @@ import { OldInstanceWorkflowWorker } from './old-instance-workflow.worker'; const getWorkers = (app: INestApplication): INovuWorker[] => { const standardWorker = app.get(StandardWorker, { strict: false }); const workflowWorker = app.get(WorkflowWorker, { strict: false }); + const oldInstanceStandardWorker = app.get(OldInstanceStandardWorker, { strict: false }); const oldInstanceWorkflowWorker = app.get(OldInstanceWorkflowWorker, { strict: false }); - const workers: INovuWorker[] = [standardWorker, workflowWorker, oldInstanceWorkflowWorker]; + const workers: INovuWorker[] = [standardWorker, workflowWorker, oldInstanceStandardWorker, oldInstanceWorkflowWorker]; return workers; }; diff --git a/apps/worker/src/app/workflow/services/index.ts b/apps/worker/src/app/workflow/services/index.ts index 228b77b38c1..ee0d3b62794 100644 --- a/apps/worker/src/app/workflow/services/index.ts +++ b/apps/worker/src/app/workflow/services/index.ts @@ -2,4 +2,5 @@ export * from './active-jobs-metric.service'; export * from './completed-jobs-metric.service'; export * from './standard.worker'; export * from './workflow.worker'; +export * from './old-instance-standard.worker'; export * from './old-instance-workflow.worker'; diff --git a/apps/worker/src/app/workflow/services/old-instance-standard.worker.ts b/apps/worker/src/app/workflow/services/old-instance-standard.worker.ts new file mode 100644 index 00000000000..e8fdde4afd8 --- /dev/null +++ b/apps/worker/src/app/workflow/services/old-instance-standard.worker.ts @@ -0,0 +1,199 @@ +const nr = require('newrelic'); +import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common'; +import { IJobData, ObservabilityBackgroundTransactionEnum } from '@novu/shared'; +import { + INovuWorker, + Job, + OldInstanceBullMqService, + PinoLogger, + storage, + Store, + OldInstanceStandardWorkerService, + WorkerOptions, +} from '@novu/application-generic'; + +import { + RunJob, + RunJobCommand, + SetJobAsCommand, + SetJobAsCompleted, + SetJobAsFailed, + SetJobAsFailedCommand, + WebhookFilterBackoffStrategy, + HandleLastFailedJobCommand, + HandleLastFailedJob, +} from '../usecases'; + +const LOG_CONTEXT = 'OldInstanceStandardWorker'; + +/** + * TODO: Temporary for migration to MemoryDB + */ +@Injectable() +export class OldInstanceStandardWorker extends OldInstanceStandardWorkerService implements INovuWorker { + constructor( + private handleLastFailedJob: HandleLastFailedJob, + private runJob: RunJob, + @Inject(forwardRef(() => SetJobAsCompleted)) private setJobAsCompleted: SetJobAsCompleted, + @Inject(forwardRef(() => SetJobAsFailed)) private setJobAsFailed: SetJobAsFailed, + @Inject(forwardRef(() => WebhookFilterBackoffStrategy)) + private webhookFilterBackoffStrategy: WebhookFilterBackoffStrategy + ) { + super(); + + this.initWorker(this.getWorkerProcessor(), this.getWorkerOptions()); + + if (this.bullMqService.enabled) { + this.worker.on('completed', async (job: Job): Promise => { + await this.jobHasCompleted(job); + }); + + this.worker.on('failed', async (job: Job, error: Error): Promise => { + await this.jobHasFailed(job, error); + }); + } + } + + private getWorkerOptions(): WorkerOptions { + return { + lockDuration: 90000, + concurrency: 200, + settings: { + backoffStrategy: this.getBackoffStrategies(), + }, + }; + } + + private extractMinimalJobData(job: any): { + environmentId: string; + organizationId: string; + jobId: string; + userId: string; + } { + const { _environmentId: environmentId, _id: jobId, _organizationId: organizationId, _userId: userId } = job; + + if (!environmentId || !jobId || !organizationId || !userId) { + const message = job.payload.message; + + return { + environmentId: message._environmentId, + jobId: message._jobId, + organizationId: message._organizationId, + userId: job.userId, + }; + } + + return { + environmentId, + jobId, + organizationId, + userId, + }; + } + + private getWorkerProcessor() { + return async ({ data }: { data: IJobData | any }) => { + const minimalJobData = this.extractMinimalJobData(data); + + Logger.verbose(`Job ${minimalJobData.jobId} is being processed in the old instance standard worker`, LOG_CONTEXT); + + return await new Promise(async (resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const _this = this; + + nr.startBackgroundTransaction( + ObservabilityBackgroundTransactionEnum.JOB_PROCESSING_QUEUE, + 'Trigger Engine', + function () { + const transaction = nr.getTransaction(); + + storage.run(new Store(PinoLogger.root), () => { + _this.runJob + .execute(RunJobCommand.create(minimalJobData)) + .then(resolve) + .catch((error) => { + Logger.error( + error, + `Failed to run the job ${minimalJobData.jobId} during worker processing`, + LOG_CONTEXT + ); + + return reject(error); + }) + .finally(() => { + transaction.end(); + }); + }); + } + ); + }); + }; + } + + private async jobHasCompleted(job: Job): Promise { + let jobId; + + try { + const minimalData = this.extractMinimalJobData(job.data); + jobId = minimalData.jobId; + const environmentId = minimalData.environmentId; + const userId = minimalData.userId; + + await this.setJobAsCompleted.execute( + SetJobAsCommand.create({ + environmentId, + jobId, + userId, + }) + ); + Logger.verbose({ job }, `Job ${jobId} set as completed`, LOG_CONTEXT); + } catch (error) { + Logger.error(error, `Failed to set job ${jobId} as completed`, LOG_CONTEXT); + } + } + + private async jobHasFailed(job: Job, error: Error): Promise { + let jobId; + + try { + const minimalData = this.extractMinimalJobData(job.data); + jobId = minimalData.jobId; + + const hasToBackoff = this.runJob.shouldBackoff(error); + const hasReachedMaxAttempts = job.attemptsMade >= this.DEFAULT_ATTEMPTS; + const shouldHandleLastFailedJob = hasToBackoff && hasReachedMaxAttempts; + + const shouldBeSetAsFailed = !hasToBackoff || shouldHandleLastFailedJob; + if (shouldBeSetAsFailed) { + await this.setJobAsFailed.execute(SetJobAsFailedCommand.create(minimalData), error); + } + + if (shouldHandleLastFailedJob) { + const handleLastFailedJobCommand = HandleLastFailedJobCommand.create({ + ...minimalData, + error, + }); + + await this.handleLastFailedJob.execute(handleLastFailedJobCommand); + } + Logger.verbose({ job }, `Job ${jobId} set as failed`, LOG_CONTEXT); + } catch (anotherError) { + Logger.error(anotherError, `Failed to set job ${jobId} as failed`, LOG_CONTEXT); + } + } + + private getBackoffStrategies = () => { + return async (attemptsMade: number, type: string, eventError: Error, eventJob: Job): Promise => { + const command = { + attemptsMade, + environmentId: eventJob?.data?._environmentId, + eventError, + eventJob, + organizationId: eventJob?.data?._organizationId, + userId: eventJob?.data?._userId, + }; + + return await this.webhookFilterBackoffStrategy.execute(command); + }; + }; +} diff --git a/apps/worker/src/app/workflow/services/old-instance-workflow.worker.ts b/apps/worker/src/app/workflow/services/old-instance-workflow.worker.ts index 56e7fa41ac0..60d6535c07f 100644 --- a/apps/worker/src/app/workflow/services/old-instance-workflow.worker.ts +++ b/apps/worker/src/app/workflow/services/old-instance-workflow.worker.ts @@ -9,21 +9,10 @@ import { storage, Store, OldInstanceWorkflowWorkerService, + TriggerEvent, WorkerOptions, } from '@novu/application-generic'; -import { - RunJob, - RunJobCommand, - SetJobAsCommand, - SetJobAsCompleted, - SetJobAsFailed, - SetJobAsFailedCommand, - WebhookFilterBackoffStrategy, - HandleLastFailedJobCommand, - HandleLastFailedJob, -} from '../usecases'; - const LOG_CONTEXT = 'OldInstanceWorkflowWorker'; /** @@ -31,93 +20,38 @@ const LOG_CONTEXT = 'OldInstanceWorkflowWorker'; */ @Injectable() export class OldInstanceWorkflowWorker extends OldInstanceWorkflowWorkerService implements INovuWorker { - constructor( - private handleLastFailedJob: HandleLastFailedJob, - private runJob: RunJob, - @Inject(forwardRef(() => SetJobAsCompleted)) private setJobAsCompleted: SetJobAsCompleted, - @Inject(forwardRef(() => SetJobAsFailed)) private setJobAsFailed: SetJobAsFailed, - @Inject(forwardRef(() => WebhookFilterBackoffStrategy)) - private webhookFilterBackoffStrategy: WebhookFilterBackoffStrategy - ) { + constructor(private triggerEventUsecase: TriggerEvent) { super(); this.initWorker(this.getWorkerProcessor(), this.getWorkerOptions()); - - if (this.bullMqService.enabled) { - this.worker.on('completed', async (job: Job): Promise => { - await this.jobHasCompleted(job); - }); - - this.worker.on('failed', async (job: Job, error: Error): Promise => { - await this.jobHasFailed(job, error); - }); - } } private getWorkerOptions(): WorkerOptions { return { lockDuration: 90000, concurrency: 200, - settings: { - backoffStrategy: this.getBackoffStrategies(), - }, - }; - } - - private extractMinimalJobData(job: any): { - environmentId: string; - organizationId: string; - jobId: string; - userId: string; - } { - const { _environmentId: environmentId, _id: jobId, _organizationId: organizationId, _userId: userId } = job; - - if (!environmentId || !jobId || !organizationId || !userId) { - const message = job.payload.message; - - return { - environmentId: message._environmentId, - jobId: message._jobId, - organizationId: message._organizationId, - userId: job.userId, - }; - } - - return { - environmentId, - jobId, - organizationId, - userId, }; } private getWorkerProcessor() { return async ({ data }: { data: IJobData | any }) => { - const minimalJobData = this.extractMinimalJobData(data); - return await new Promise(async (resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-this-alias const _this = this; + Logger.verbose(`Job ${data._id} is being processed in the old instance workflow worker`, LOG_CONTEXT); + nr.startBackgroundTransaction( - ObservabilityBackgroundTransactionEnum.JOB_PROCESSING_QUEUE, + ObservabilityBackgroundTransactionEnum.TRIGGER_HANDLER_QUEUE, 'Trigger Engine', function () { const transaction = nr.getTransaction(); storage.run(new Store(PinoLogger.root), () => { - _this.runJob - .execute(RunJobCommand.create(minimalJobData)) + _this.triggerEventUsecase + .execute(data) .then(resolve) - .catch((error) => { - Logger.error( - error, - `Failed to run the job ${minimalJobData.jobId} during worker processing`, - LOG_CONTEXT - ); - - return reject(error); - }) + .catch(reject) .finally(() => { transaction.end(); }); @@ -127,71 +61,4 @@ export class OldInstanceWorkflowWorker extends OldInstanceWorkflowWorkerService }); }; } - - private async jobHasCompleted(job: Job): Promise { - let jobId; - - try { - const minimalData = this.extractMinimalJobData(job.data); - jobId = minimalData.jobId; - const environmentId = minimalData.environmentId; - const userId = minimalData.userId; - - await this.setJobAsCompleted.execute( - SetJobAsCommand.create({ - environmentId, - jobId, - userId, - }) - ); - Logger.verbose({ job }, `Job ${jobId} set as completed`, LOG_CONTEXT); - } catch (error) { - Logger.error(error, `Failed to set job ${jobId} as completed`, LOG_CONTEXT); - } - } - - private async jobHasFailed(job: Job, error: Error): Promise { - let jobId; - - try { - const minimalData = this.extractMinimalJobData(job.data); - jobId = minimalData.jobId; - - const hasToBackoff = this.runJob.shouldBackoff(error); - const hasReachedMaxAttempts = job.attemptsMade >= this.DEFAULT_ATTEMPTS; - const shouldHandleLastFailedJob = hasToBackoff && hasReachedMaxAttempts; - - const shouldBeSetAsFailed = !hasToBackoff || shouldHandleLastFailedJob; - if (shouldBeSetAsFailed) { - await this.setJobAsFailed.execute(SetJobAsFailedCommand.create(minimalData), error); - } - - if (shouldHandleLastFailedJob) { - const handleLastFailedJobCommand = HandleLastFailedJobCommand.create({ - ...minimalData, - error, - }); - - await this.handleLastFailedJob.execute(handleLastFailedJobCommand); - } - Logger.verbose({ job }, `Job ${jobId} set as failed`, LOG_CONTEXT); - } catch (anotherError) { - Logger.error(anotherError, `Failed to set job ${jobId} as failed`, LOG_CONTEXT); - } - } - - private getBackoffStrategies = () => { - return async (attemptsMade: number, type: string, eventError: Error, eventJob: Job): Promise => { - const command = { - attemptsMade, - environmentId: eventJob?.data?._environmentId, - eventError, - eventJob, - organizationId: eventJob?.data?._organizationId, - userId: eventJob?.data?._userId, - }; - - return await this.webhookFilterBackoffStrategy.execute(command); - }; - }; } diff --git a/apps/worker/src/app/workflow/services/standard.worker.ts b/apps/worker/src/app/workflow/services/standard.worker.ts index a8dc38c9b15..3b4cca0254d 100644 --- a/apps/worker/src/app/workflow/services/standard.worker.ts +++ b/apps/worker/src/app/workflow/services/standard.worker.ts @@ -94,6 +94,8 @@ export class StandardWorker extends StandardWorkerService implements INovuWorker return async ({ data }: { data: IJobData | any }) => { const minimalJobData = this.extractMinimalJobData(data); + Logger.verbose(`Job ${minimalJobData.jobId} is being processed in the new instance standard worker`, LOG_CONTEXT); + return await new Promise(async (resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-this-alias const _this = this; diff --git a/apps/worker/src/app/workflow/services/workflow.worker.ts b/apps/worker/src/app/workflow/services/workflow.worker.ts index 4a7d9b309e7..34c67d1dd45 100644 --- a/apps/worker/src/app/workflow/services/workflow.worker.ts +++ b/apps/worker/src/app/workflow/services/workflow.worker.ts @@ -36,6 +36,8 @@ export class WorkflowWorker extends WorkflowWorkerService implements INovuWorker // eslint-disable-next-line @typescript-eslint/no-this-alias const _this = this; + Logger.verbose(`Job ${data.identifier} is being processed in the new instance workflow worker`, LOG_CONTEXT); + nr.startBackgroundTransaction( ObservabilityBackgroundTransactionEnum.TRIGGER_HANDLER_QUEUE, 'Trigger Engine', diff --git a/apps/worker/src/app/workflow/usecases/run-job/run-job.usecase.ts b/apps/worker/src/app/workflow/usecases/run-job/run-job.usecase.ts index f7b1f2b4e5b..aac6297aff8 100644 --- a/apps/worker/src/app/workflow/usecases/run-job/run-job.usecase.ts +++ b/apps/worker/src/app/workflow/usecases/run-job/run-job.usecase.ts @@ -40,7 +40,7 @@ export class RunJob { jobId: job._id, }); } catch (e) { - Logger.error(e, 'RunJob'); + Logger.error(e, 'RunJob', LOG_CONTEXT); } const canceled = await this.delayedEventIsCanceled(job); diff --git a/apps/worker/src/app/workflow/usecases/send-message/send-message.usecase.ts b/apps/worker/src/app/workflow/usecases/send-message/send-message.usecase.ts index d68528ac9f0..41b4b112b48 100644 --- a/apps/worker/src/app/workflow/usecases/send-message/send-message.usecase.ts +++ b/apps/worker/src/app/workflow/usecases/send-message/send-message.usecase.ts @@ -19,6 +19,8 @@ import { GetSubscriberTemplatePreference, GetSubscriberTemplatePreferenceCommand, Instrument, + GetSubscriberGlobalPreference, + GetSubscriberGlobalPreferenceCommand, } from '@novu/application-generic'; import { JobEntity, @@ -51,6 +53,7 @@ export class SendMessage { private digest: Digest, private createExecutionDetails: CreateExecutionDetails, private getSubscriberTemplatePreferenceUsecase: GetSubscriberTemplatePreference, + private getSubscriberGlobalPreferenceUsecase: GetSubscriberGlobalPreference, private notificationTemplateRepository: NotificationTemplateRepository, private jobRepository: JobRepository, private sendMessageDelay: SendMessageDelay, @@ -85,7 +88,7 @@ export class SendMessage { }; } - this.analyticsService.track('Process Workflow Step - [Triggers]', command.userId, { + this.analyticsService.mixpanelTrack('Process Workflow Step - [Triggers]', '', { _template: command.job._templateId, _organization: command.organizationId, _environment: command.environmentId, @@ -200,6 +203,32 @@ export class SendMessage { }); if (!subscriber) throw new PlatformException('Subscriber not found with id ' + job._subscriberId); + const { preference: globalPreference } = await this.getSubscriberGlobalPreferenceUsecase.execute( + GetSubscriberGlobalPreferenceCommand.create({ + organizationId: job._organizationId, + environmentId: job._environmentId, + subscriberId: job.subscriberId, + }) + ); + + const globalPreferenceResult = this.stepPreferred(globalPreference, job); + + if (!globalPreferenceResult) { + await this.createExecutionDetails.execute( + CreateExecutionDetailsCommand.create({ + ...CreateExecutionDetailsCommand.getDetailsFromJob(job), + detail: DetailEnum.STEP_FILTERED_BY_GLOBAL_PREFERENCES, + source: ExecutionDetailsSourceEnum.INTERNAL, + status: ExecutionDetailsStatusEnum.SUCCESS, + isTest: false, + isRetry: false, + raw: JSON.stringify(globalPreference), + }) + ); + + return false; + } + const buildCommand = GetSubscriberTemplatePreferenceCommand.create({ organizationId: job._organizationId, subscriberId: subscriber.subscriberId, @@ -209,7 +238,6 @@ export class SendMessage { }); const { preference } = await this.getSubscriberTemplatePreferenceUsecase.execute(buildCommand); - const result = this.stepPreferred(preference, job); if (!result) { @@ -244,9 +272,12 @@ export class SendMessage { private stepPreferred(preference: { enabled: boolean; channels: IPreferenceChannels }, job: JobEntity) { const templatePreferred = preference.enabled; - const channelPreferred = Object.keys(preference.channels).some( - (channelKey) => channelKey === job.type && preference.channels[job.type] - ); + const channels = Object.keys(preference.channels); + // Handles the case where the channel is not defined in the preference. i.e, channels = {} + const channelPreferred = + channels.length > 0 + ? channels.some((channelKey) => channelKey === job.type && preference.channels[job.type]) + : true; return templatePreferred && channelPreferred; } diff --git a/apps/worker/src/app/workflow/workflow.module.ts b/apps/worker/src/app/workflow/workflow.module.ts index 5c60966f028..81dbc4731ce 100644 --- a/apps/worker/src/app/workflow/workflow.module.ts +++ b/apps/worker/src/app/workflow/workflow.module.ts @@ -15,6 +15,7 @@ import { GetNovuLayout, GetNovuProviderCredentials, GetSubscriberPreference, + GetSubscriberGlobalPreference, GetSubscriberTemplatePreference, ProcessTenant, OldInstanceBullMqService, @@ -34,6 +35,7 @@ import { StandardWorker, WorkflowWorker, OldInstanceWorkflowWorker, + OldInstanceStandardWorker, } from './services'; import { @@ -80,6 +82,7 @@ const USE_CASES = [ GetNovuProviderCredentials, SelectIntegration, GetSubscriberPreference, + GetSubscriberGlobalPreference, GetSubscriberTemplatePreference, HandleLastFailedJob, MessageMatcher, @@ -111,6 +114,7 @@ const PROVIDERS: Provider[] = [ StandardWorker, WorkflowWorker, OldInstanceBullMqService, + OldInstanceStandardWorker, OldInstanceWorkflowWorker, ]; diff --git a/apps/ws/package.json b/apps/ws/package.json index 50946b41473..69628c19e86 100644 --- a/apps/ws/package.json +++ b/apps/ws/package.json @@ -1,6 +1,6 @@ { "name": "@novu/ws", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "", "author": "", "private": true, @@ -28,10 +28,10 @@ "@nestjs/swagger": "^7.1.9", "@nestjs/terminus": "^10.0.1", "@nestjs/websockets": "^10.2.2", - "@novu/application-generic": "^0.19.0", - "@novu/dal": "^0.19.0", - "@novu/shared": "^0.19.0", - "@novu/testing": "^0.19.0", + "@novu/application-generic": "^0.20.0-alpha.0", + "@novu/dal": "^0.20.0-alpha.0", + "@novu/shared": "^0.20.0-alpha.0", + "@novu/testing": "^0.20.0-alpha.0", "@sentry/node": "^7.30.0", "@socket.io/redis-adapter": "^7.2.0", "class-transformer": "^0.5.1", diff --git a/apps/ws/src/socket/services/index.ts b/apps/ws/src/socket/services/index.ts index b03b9f0b02d..735d2d9ed04 100644 --- a/apps/ws/src/socket/services/index.ts +++ b/apps/ws/src/socket/services/index.ts @@ -1 +1,3 @@ +export { OldInstanceWebSocketsWorker } from './old-instance-web-sockets.worker'; +export { OldInstanceWebSocketsWorkerService } from './old-instance-web-sockets-worker.service'; export { WebSocketWorker } from './web-socket.worker'; diff --git a/apps/ws/src/socket/services/old-instance-web-sockets-worker.service.ts b/apps/ws/src/socket/services/old-instance-web-sockets-worker.service.ts new file mode 100644 index 00000000000..91ba7c714c6 --- /dev/null +++ b/apps/ws/src/socket/services/old-instance-web-sockets-worker.service.ts @@ -0,0 +1,91 @@ +import { IJobData, JobTopicNameEnum } from '@novu/shared'; +import { Inject, Injectable, Logger } from '@nestjs/common'; + +import { JobsOptions, OldInstanceBullMqService, Processor, Worker, WorkerOptions } from '@novu/application-generic'; + +const LOG_CONTEXT = 'OldInstanceWebSocketsWorkerService'; + +type WorkerProcessor = string | Processor | undefined; + +/** + * TODO: Temporary for migration to MemoryDB + */ +export class OldInstanceWebSocketsWorkerService { + private instance: OldInstanceBullMqService; + + public readonly DEFAULT_ATTEMPTS = 3; + public readonly topic: JobTopicNameEnum; + + constructor() { + this.topic = JobTopicNameEnum.WEB_SOCKETS; + this.instance = new OldInstanceBullMqService(); + if (this.instance.enabled) { + Logger.log(`Old instance Worker ${this.topic} instantiated`, LOG_CONTEXT); + } else { + Logger.warn( + `Old instance web sockets worker not instantiated as it is only needed for MemoryDB migration`, + LOG_CONTEXT + ); + } + } + + public get bullMqService(): OldInstanceBullMqService { + return this.instance; + } + + public get worker(): Worker { + return this.instance.worker; + } + + public initWorker(processor: WorkerProcessor, options?: WorkerOptions): void { + if (this.instance.enabled) { + this.createWorker(processor, options); + } + } + + public createWorker(processor: WorkerProcessor, options?: WorkerOptions): void { + if (this.instance.enabled) { + this.instance.createWorker(this.topic, processor, options); + } else { + Logger.log( + { enabled: this.instance.enabled }, + 'We are not running OldInstanceWorkflowWorkerService as it is not needed in this environment', + LOG_CONTEXT + ); + } + } + + public async isRunning(): Promise { + return await this.instance.isWorkerRunning(); + } + + public async isPaused(): Promise { + return await this.instance.isWorkerPaused(); + } + + public async pause(): Promise { + if (this.instance.enabled && this.worker) { + await this.instance.pauseWorker(); + } + } + + public async resume(): Promise { + if (this.instance.enabled && this.worker) { + await this.instance.resumeWorker(); + } + } + + public async gracefulShutdown(): Promise { + if (this.instance.enabled) { + Logger.log('Shutting the old web sockets Worker service down', LOG_CONTEXT); + + await this.instance.gracefulShutdown(); + + Logger.log('Shutting down the old web sockets Worker service has finished', LOG_CONTEXT); + } + } + + async onModuleDestroy(): Promise { + await this.gracefulShutdown(); + } +} diff --git a/apps/ws/src/socket/services/old-instance-web-sockets.worker.ts b/apps/ws/src/socket/services/old-instance-web-sockets.worker.ts new file mode 100644 index 00000000000..fea4f6138c6 --- /dev/null +++ b/apps/ws/src/socket/services/old-instance-web-sockets.worker.ts @@ -0,0 +1,71 @@ +const nr = require('newrelic'); +import { Injectable, Logger } from '@nestjs/common'; + +import { INovuWorker, WebSocketsWorkerService } from '@novu/application-generic'; + +import { ExternalServicesRoute, ExternalServicesRouteCommand } from '../usecases/external-services-route'; +import { ObservabilityBackgroundTransactionEnum } from '@novu/shared'; +import { OldInstanceWebSocketsWorkerService } from './old-instance-web-sockets-worker.service'; + +const LOG_CONTEXT = 'OldInstanceWebSocketsWorker'; + +@Injectable() +export class OldInstanceWebSocketsWorker extends OldInstanceWebSocketsWorkerService implements INovuWorker { + constructor(private externalServicesRoute: ExternalServicesRoute) { + super(); + + this.initWorker(this.getWorkerProcessor(), this.getWorkerOpts()); + } + + private getWorkerProcessor() { + return async (job) => { + return new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const _this = this; + + Logger.verbose( + `Job ${job.id} / ${job.data.event} is being processed in the old instance web sockets worker`, + LOG_CONTEXT + ); + + nr.startBackgroundTransaction( + ObservabilityBackgroundTransactionEnum.WS_SOCKET_QUEUE, + 'WS Service', + function () { + const transaction = nr.getTransaction(); + + _this.externalServicesRoute + .execute( + ExternalServicesRouteCommand.create({ + userId: job.data.userId, + event: job.data.event, + payload: job.data.payload, + _environmentId: job.data._environmentId, + }) + ) + .then(resolve) + .catch((error) => { + Logger.error( + 'Unexpected exception occurred while handling external services route ', + error, + LOG_CONTEXT + ); + + reject(error); + }) + .finally(() => { + transaction.end(); + }); + } + ); + }); + }; + } + + private getWorkerOpts() { + return { + lockDuration: 90000, + concurrency: 100, + }; + } +} diff --git a/apps/ws/src/socket/services/web-socket.worker.ts b/apps/ws/src/socket/services/web-socket.worker.ts index 06bc7597074..dc07a2b3331 100644 --- a/apps/ws/src/socket/services/web-socket.worker.ts +++ b/apps/ws/src/socket/services/web-socket.worker.ts @@ -22,6 +22,11 @@ export class WebSocketWorker extends WebSocketsWorkerService implements INovuWor // eslint-disable-next-line @typescript-eslint/no-this-alias const _this = this; + Logger.verbose( + `Job ${job.id} / ${job.data.event} is being processed in the MemoryDB instance WebSocketWorker`, + LOG_CONTEXT + ); + nr.startBackgroundTransaction( ObservabilityBackgroundTransactionEnum.WS_SOCKET_QUEUE, 'WS Service', diff --git a/apps/ws/src/socket/socket.module.ts b/apps/ws/src/socket/socket.module.ts index 15173f355a1..2b29edf2f24 100644 --- a/apps/ws/src/socket/socket.module.ts +++ b/apps/ws/src/socket/socket.module.ts @@ -6,11 +6,17 @@ import { WSGateway } from './ws.gateway'; import { SharedModule } from '../shared/shared.module'; import { ExternalServicesRoute } from './usecases/external-services-route'; -import { WebSocketWorker } from './services'; +import { OldInstanceWebSocketsWorker, OldInstanceWebSocketsWorkerService, WebSocketWorker } from './services'; const USE_CASES: Provider[] = [ExternalServicesRoute]; -const PROVIDERS: Provider[] = [WSGateway, WebSocketsWorkerService, WebSocketWorker]; +const PROVIDERS: Provider[] = [ + WSGateway, + OldInstanceWebSocketsWorker, + OldInstanceWebSocketsWorkerService, + WebSocketsWorkerService, + WebSocketWorker, +]; @Module({ imports: [SharedModule], diff --git a/enterprise/packages b/enterprise/packages deleted file mode 160000 index 8f0b31fcf98..00000000000 --- a/enterprise/packages +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8f0b31fcf987bd0626b80224d73349fcbf71ceea diff --git a/lerna.json b/lerna.json index d34c472a3d0..da11865f407 100644 --- a/lerna.json +++ b/lerna.json @@ -8,5 +8,5 @@ "message": "chore(release): publish - ci skip" } }, - "version": "0.19.0" + "version": "0.20.0-alpha.0" } diff --git a/libs/dal/package.json b/libs/dal/package.json index 876fa4ebaae..8a34231beeb 100644 --- a/libs/dal/package.json +++ b/libs/dal/package.json @@ -1,6 +1,6 @@ { "name": "@novu/dal", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "", "private": true, "scripts": { @@ -24,7 +24,7 @@ "@aws-sdk/client-s3": "^3.382.0", "@aws-sdk/s3-request-presigner": "^3.382.0", "@faker-js/faker": "^6.0.0", - "@novu/shared": "^0.19.0", + "@novu/shared": "^0.20.0-alpha.0", "@sendgrid/mail": "^7.4.2", "JSONStream": "^1.3.5", "archiver": "^5.0.0", diff --git a/libs/dal/src/repositories/subscriber-preference/subscriber-preference.entity.ts b/libs/dal/src/repositories/subscriber-preference/subscriber-preference.entity.ts index eb8c1a7ac13..c842f4829a4 100644 --- a/libs/dal/src/repositories/subscriber-preference/subscriber-preference.entity.ts +++ b/libs/dal/src/repositories/subscriber-preference/subscriber-preference.entity.ts @@ -13,14 +13,21 @@ export class SubscriberPreferenceEntity { _subscriberId: string; - _templateId: string; + _templateId?: string; enabled: boolean; channels: IPreferenceChannels; + + level: PreferenceLevelEnum; } export type SubscriberPreferenceDBModel = ChangePropsValueType< SubscriberPreferenceEntity, '_environmentId' | '_organizationId' | '_subscriberId' | '_templateId' >; + +export enum PreferenceLevelEnum { + GLOBAL = 'global', + TEMPLATE = 'template', +} diff --git a/libs/dal/src/repositories/subscriber-preference/subscriber-preference.schema.ts b/libs/dal/src/repositories/subscriber-preference/subscriber-preference.schema.ts index 4851e68efbf..f53ccc71729 100644 --- a/libs/dal/src/repositories/subscriber-preference/subscriber-preference.schema.ts +++ b/libs/dal/src/repositories/subscriber-preference/subscriber-preference.schema.ts @@ -2,7 +2,7 @@ import * as mongoose from 'mongoose'; import { Schema } from 'mongoose'; import { schemaOptions } from '../schema-default.options'; -import { SubscriberPreferenceDBModel } from './subscriber-preference.entity'; +import { PreferenceLevelEnum, SubscriberPreferenceDBModel } from './subscriber-preference.entity'; const subscriberPreferenceSchema = new Schema( { @@ -47,6 +47,10 @@ const subscriberPreferenceSchema = new Schema( type: Schema.Types.Boolean, }, }, + level: { + type: Schema.Types.String, + enum: PreferenceLevelEnum, + }, }, schemaOptions ); diff --git a/libs/embed/package.json b/libs/embed/package.json index af638ac532e..1eaa5658b1a 100644 --- a/libs/embed/package.json +++ b/libs/embed/package.json @@ -1,6 +1,6 @@ { "name": "@novu/embed", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "private": true, "description": "", "keywords": [], @@ -118,7 +118,7 @@ "typescript": "4.9.5" }, "dependencies": { - "@novu/notification-center": "^0.19.0", + "@novu/notification-center": "^0.20.0-alpha.0", "@types/iframe-resizer": "^3.5.8", "iframe-resizer": "^4.3.1" } diff --git a/libs/shared/package.json b/libs/shared/package.json index e4e85cfbe3f..a56af53b5ec 100644 --- a/libs/shared/package.json +++ b/libs/shared/package.json @@ -1,6 +1,6 @@ { "name": "@novu/shared", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "", "scripts": { "start": "npm run start:dev", diff --git a/libs/shared/src/entities/subscriber-preference/subscriber-preference.interface.ts b/libs/shared/src/entities/subscriber-preference/subscriber-preference.interface.ts index 4a0628a5e90..10da9d797df 100644 --- a/libs/shared/src/entities/subscriber-preference/subscriber-preference.interface.ts +++ b/libs/shared/src/entities/subscriber-preference/subscriber-preference.interface.ts @@ -28,3 +28,8 @@ export interface ITemplateConfiguration { critical: boolean; tags?: string[]; } + +export enum PreferenceLevelEnum { + GLOBAL = 'global', + TEMPLATE = 'template', +} diff --git a/libs/testing/package.json b/libs/testing/package.json index 8f075822785..fa24b022bb3 100644 --- a/libs/testing/package.json +++ b/libs/testing/package.json @@ -1,6 +1,6 @@ { "name": "@novu/testing", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "", "private": true, "scripts": { @@ -22,8 +22,8 @@ "types": "dist/index.d.ts", "dependencies": { "@faker-js/faker": "^6.0.0", - "@novu/dal": "^0.19.0", - "@novu/shared": "^0.19.0", + "@novu/dal": "^0.20.0-alpha.0", + "@novu/shared": "^0.20.0-alpha.0", "JSONStream": "^1.3.5", "async": "^3.2.0", "axios": "^1.3.3", diff --git a/packages/application-generic/package.json b/packages/application-generic/package.json index 7629b5c35d8..8dbb47e4ffd 100644 --- a/packages/application-generic/package.json +++ b/packages/application-generic/package.json @@ -1,6 +1,6 @@ { "name": "@novu/application-generic", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "Generic backend code used inside of Novu's different services", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -35,10 +35,10 @@ "peerDependencies": { "@nestjs/common": ">=10", "@nestjs/core": ">=10", + "@nestjs/jwt": "^10.1.0", "@nestjs/swagger": ">=6", "@nestjs/terminus": ">=10", "@nestjs/testing": ">=10", - "@nestjs/jwt": "^10.1.0", "newrelic": "^9", "reflect-metadata": "^0.1.13" }, @@ -47,52 +47,52 @@ "@aws-sdk/s3-request-presigner": "^3.382.0", "@azure/storage-blob": "^12.11.0", "@google-cloud/storage": "^6.2.3", - "@novu/africas-talking": "^0.19.0", - "@novu/apns": "^0.19.0", - "@novu/burst-sms": "^0.19.0", - "@novu/clickatell": "^0.19.0", - "@novu/dal": "^0.19.0", - "@novu/discord": "^0.19.0", - "@novu/email-webhook": "^0.19.0", - "@novu/emailjs": "^0.19.0", - "@novu/expo": "^0.19.0", - "@novu/fcm": "^0.19.0", - "@novu/firetext": "^0.19.0", - "@novu/forty-six-elks": "^0.19.0", - "@novu/gupshup": "^0.19.0", - "@novu/infobip": "^0.19.0", - "@novu/kannel": "^0.19.0", - "@novu/mailersend": "^0.19.0", - "@novu/mailgun": "^0.19.0", - "@novu/mailjet": "^0.19.0", - "@novu/mandrill": "^0.19.0", - "@novu/maqsam": "^0.19.0", - "@novu/mattermost": "^0.19.0", - "@novu/ms-teams": "^0.19.0", - "@novu/netcore": "^0.19.0", - "@novu/nodemailer": "^0.19.0", - "@novu/one-signal": "^0.19.0", - "@novu/outlook365": "^0.19.0", - "@novu/plivo": "^0.19.0", - "@novu/plunk": "^0.19.0", - "@novu/postmark": "^0.19.0", - "@novu/push-webhook": "^0.19.0", - "@novu/resend": "^0.19.0", - "@novu/sendchamp": "^0.19.0", - "@novu/sendgrid": "^0.19.0", - "@novu/sendinblue": "^0.19.0", - "@novu/ses": "^0.19.0", - "@novu/shared": "^0.19.0", - "@novu/slack": "^0.19.0", - "@novu/sms-central": "^0.19.0", - "@novu/sms77": "^0.19.0", - "@novu/sns": "^0.19.0", - "@novu/sparkpost": "^0.19.0", - "@novu/stateless": "^0.19.0", - "@novu/telnyx": "^0.19.0", - "@novu/termii": "^0.19.0", - "@novu/testing": "^0.19.0", - "@novu/twilio": "^0.19.0", + "@novu/africas-talking": "^0.20.0-alpha.0", + "@novu/apns": "^0.20.0-alpha.0", + "@novu/burst-sms": "^0.20.0-alpha.0", + "@novu/clickatell": "^0.20.0-alpha.0", + "@novu/dal": "^0.20.0-alpha.0", + "@novu/discord": "^0.20.0-alpha.0", + "@novu/email-webhook": "^0.20.0-alpha.0", + "@novu/emailjs": "^0.20.0-alpha.0", + "@novu/expo": "^0.20.0-alpha.0", + "@novu/fcm": "^0.20.0-alpha.0", + "@novu/firetext": "^0.20.0-alpha.0", + "@novu/forty-six-elks": "^0.20.0-alpha.0", + "@novu/gupshup": "^0.20.0-alpha.0", + "@novu/infobip": "^0.20.0-alpha.0", + "@novu/kannel": "^0.20.0-alpha.0", + "@novu/mailersend": "^0.20.0-alpha.0", + "@novu/mailgun": "^0.20.0-alpha.0", + "@novu/mailjet": "^0.20.0-alpha.0", + "@novu/mandrill": "^0.20.0-alpha.0", + "@novu/maqsam": "^0.20.0-alpha.0", + "@novu/mattermost": "^0.20.0-alpha.0", + "@novu/ms-teams": "^0.20.0-alpha.0", + "@novu/netcore": "^0.20.0-alpha.0", + "@novu/nodemailer": "^0.20.0-alpha.0", + "@novu/one-signal": "^0.20.0-alpha.0", + "@novu/outlook365": "^0.20.0-alpha.0", + "@novu/plivo": "^0.20.0-alpha.0", + "@novu/plunk": "^0.20.0-alpha.0", + "@novu/postmark": "^0.20.0-alpha.0", + "@novu/push-webhook": "^0.20.0-alpha.0", + "@novu/resend": "^0.20.0-alpha.0", + "@novu/sendchamp": "^0.20.0-alpha.0", + "@novu/sendgrid": "^0.20.0-alpha.0", + "@novu/sendinblue": "^0.20.0-alpha.0", + "@novu/ses": "^0.20.0-alpha.0", + "@novu/shared": "^0.20.0-alpha.0", + "@novu/slack": "^0.20.0-alpha.0", + "@novu/sms-central": "^0.20.0-alpha.0", + "@novu/sms77": "^0.20.0-alpha.0", + "@novu/sns": "^0.20.0-alpha.0", + "@novu/sparkpost": "^0.20.0-alpha.0", + "@novu/stateless": "^0.20.0-alpha.0", + "@novu/telnyx": "^0.20.0-alpha.0", + "@novu/termii": "^0.20.0-alpha.0", + "@novu/testing": "^0.20.0-alpha.0", + "@novu/twilio": "^0.20.0-alpha.0", "@sentry/node": "^7.12.1", "analytics-node": "^6.2.0", "bullmq": "^3.10.2", @@ -103,6 +103,7 @@ "ioredis": "^5.2.4", "launchdarkly-node-server-sdk": "^7.0.1", "lodash": "^4.17.15", + "mixpanel": "^0.17.0", "nestjs-pino": "^3.4.0", "node-fetch": "^3.2.10", "pino-http": "^8.3.3", diff --git a/packages/application-generic/src/modules/queues.module.ts b/packages/application-generic/src/modules/queues.module.ts index 63ff7946cde..5d2d1cfe771 100644 --- a/packages/application-generic/src/modules/queues.module.ts +++ b/packages/application-generic/src/modules/queues.module.ts @@ -29,6 +29,7 @@ import { StandardWorkerService, WebSocketsWorkerService, WorkflowWorkerService, + OldInstanceStandardWorkerService, OldInstanceWorkflowWorkerService, } from '../services/workers'; @@ -54,6 +55,7 @@ const PROVIDERS: Provider[] = [ WorkflowQueueService, WorkflowQueueServiceHealthIndicator, WorkflowWorkerService, + OldInstanceStandardWorkerService, OldInstanceWorkflowWorkerService, OldInstanceBullMqService, ]; diff --git a/packages/application-generic/src/services/analytics.service.ts b/packages/application-generic/src/services/analytics.service.ts index f91112773a7..47ecf50d732 100644 --- a/packages/application-generic/src/services/analytics.service.ts +++ b/packages/application-generic/src/services/analytics.service.ts @@ -1,5 +1,6 @@ import Analytics from 'analytics-node'; import { Logger } from '@nestjs/common'; +import * as Mixpanel from 'mixpanel'; // Due to problematic analytics-node types, we need to use require // eslint-disable-next-line @typescript-eslint/naming-convention @@ -23,7 +24,7 @@ const LOG_CONTEXT = 'AnalyticsService'; export class AnalyticsService { private segment: Analytics; - + private mixpanel: Mixpanel.Mixpanel; constructor(private segmentToken?: string | null, private batchSize = 100) {} async initialize() { @@ -32,6 +33,10 @@ export class AnalyticsService { flushAt: this.batchSize, }); } + + if (process.env.MIXPANEL_TOKEN) { + this.mixpanel = Mixpanel.init(process.env.MIXPANEL_TOKEN); + } } upsertGroup( @@ -104,6 +109,7 @@ export class AnalyticsService { { name, data, + source: 'segment', }, LOG_CONTEXT ); @@ -128,7 +134,46 @@ export class AnalyticsService { } } + mixpanelTrack( + name: string, + userId: string, + data: Record = {} + ) { + if (this.mixpanelEnabled) { + Logger.log( + 'Tracking event: ' + name, + { + name, + data, + source: 'mixpanel', + }, + LOG_CONTEXT + ); + + try { + this.mixpanel.track(name, { + distinct_id: userId, + ...data, + }); + } catch (error: any) { + Logger.error( + { + eventName: name, + usedId: userId, + message: error?.message, + }, + 'There has been an error when tracking mixpanel', + LOG_CONTEXT + ); + } + } + } + private get segmentEnabled() { return process.env.NODE_ENV !== 'test' && this.segment; } + + private get mixpanelEnabled() { + return process.env.NODE_ENV !== 'test' && this.mixpanel; + } } diff --git a/packages/application-generic/src/services/index.ts b/packages/application-generic/src/services/index.ts index f3f71f1989b..a6d6f0dcf89 100644 --- a/packages/application-generic/src/services/index.ts +++ b/packages/application-generic/src/services/index.ts @@ -16,6 +16,7 @@ export { BullMqService, Job, JobsOptions, + Processor, Queue, QueueBaseOptions, QueueOptions, diff --git a/packages/application-generic/src/services/workers/index.ts b/packages/application-generic/src/services/workers/index.ts index ee6f6d9b4dd..2a2eb18fb55 100644 --- a/packages/application-generic/src/services/workers/index.ts +++ b/packages/application-generic/src/services/workers/index.ts @@ -10,6 +10,7 @@ import { InboundParseWorkerService } from './inbound-parse-worker.service'; import { StandardWorkerService } from './standard-worker.service'; import { WebSocketsWorkerService } from './web-sockets-worker.service'; import { WorkflowWorkerService } from './workflow-worker.service'; +import { OldInstanceStandardWorkerService } from './old-instance-standard-worker.service'; import { OldInstanceWorkflowWorkerService } from './old-instance-workflow-worker.service'; export { @@ -22,5 +23,6 @@ export { WorkerOptions, WorkerProcessor, WorkflowWorkerService, + OldInstanceStandardWorkerService, OldInstanceWorkflowWorkerService, }; diff --git a/packages/application-generic/src/services/workers/old-instance-standard-worker.service.ts b/packages/application-generic/src/services/workers/old-instance-standard-worker.service.ts new file mode 100644 index 00000000000..03852a75223 --- /dev/null +++ b/packages/application-generic/src/services/workers/old-instance-standard-worker.service.ts @@ -0,0 +1,106 @@ +import { IJobData, JobTopicNameEnum } from '@novu/shared'; +import { Inject, Injectable, Logger } from '@nestjs/common'; + +import { + JobsOptions, + OldInstanceBullMqService, + Processor, + Worker, + WorkerOptions, +} from '../bull-mq'; + +const LOG_CONTEXT = 'OldInstanceStandardWorkerService'; + +type WorkerProcessor = string | Processor | undefined; + +/** + * TODO: Temporary for migration to MemoryDB + */ +export class OldInstanceStandardWorkerService { + private instance: OldInstanceBullMqService; + + public readonly DEFAULT_ATTEMPTS = 3; + public readonly topic: JobTopicNameEnum; + + constructor() { + this.topic = JobTopicNameEnum.STANDARD; + this.instance = new OldInstanceBullMqService(); + if (this.instance.enabled) { + Logger.log(`Worker ${this.topic} instantiated`, LOG_CONTEXT); + } else { + Logger.warn( + `Old instance standard worker not instantiated as it is only needed for MemoryDB migration`, + LOG_CONTEXT + ); + } + } + + public get bullMqService(): OldInstanceBullMqService { + return this.instance; + } + + public get worker(): Worker { + return this.instance.worker; + } + + public initWorker(processor: WorkerProcessor, options?: WorkerOptions): void { + if (this.instance.enabled) { + this.createWorker(processor, options); + } + } + + public createWorker( + processor: WorkerProcessor, + options: WorkerOptions + ): void { + if (this.instance.enabled) { + this.instance.createWorker(this.topic, processor, options); + } else { + Logger.log( + { enabled: this.instance.enabled }, + 'We are not running OldInstanceStandardWorkerService as it is not needed in this environment', + LOG_CONTEXT + ); + } + } + + public async isRunning(): Promise { + return await this.instance.isWorkerRunning(); + } + + public async isPaused(): Promise { + return await this.instance.isWorkerPaused(); + } + + public async pause(): Promise { + if (this.instance.enabled && this.worker) { + await this.instance.pauseWorker(); + } + } + + public async resume(): Promise { + if (this.instance.enabled && this.worker) { + await this.instance.resumeWorker(); + } + } + + public async gracefulShutdown(): Promise { + if (this.instance.enabled) { + Logger.log( + 'Shutting the old instance standard worker service down', + LOG_CONTEXT + ); + + await this.instance.gracefulShutdown(); + + Logger.log( + 'Shutting down the old instance standard worker service has finished', + LOG_CONTEXT + ); + } + } + + async onModuleDestroy(): Promise { + await this.gracefulShutdown(); + } +} diff --git a/packages/application-generic/src/services/workers/old-instance-workflow-worker.service.ts b/packages/application-generic/src/services/workers/old-instance-workflow-worker.service.ts index bca898a0118..74a04372f35 100644 --- a/packages/application-generic/src/services/workers/old-instance-workflow-worker.service.ts +++ b/packages/application-generic/src/services/workers/old-instance-workflow-worker.service.ts @@ -9,7 +9,7 @@ import { WorkerOptions, } from '../bull-mq'; -const LOG_CONTEXT = 'OldInstanceWorkerService'; +const LOG_CONTEXT = 'OldInstanceWorkflowWorkerService'; type WorkerProcessor = string | Processor | undefined; @@ -23,7 +23,7 @@ export class OldInstanceWorkflowWorkerService { public readonly topic: JobTopicNameEnum; constructor() { - this.topic = JobTopicNameEnum.STANDARD; + this.topic = JobTopicNameEnum.WORKFLOW; this.instance = new OldInstanceBullMqService(); if (this.instance.enabled) { Logger.log(`Worker ${this.topic} instantiated`, LOG_CONTEXT); @@ -86,11 +86,17 @@ export class OldInstanceWorkflowWorkerService { public async gracefulShutdown(): Promise { if (this.instance.enabled) { - Logger.log('Shutting the Worker service down', LOG_CONTEXT); + Logger.log( + 'Shutting the old instance workflow worker service down', + LOG_CONTEXT + ); await this.instance.gracefulShutdown(); - Logger.log('Shutting down the Worker service has finished', LOG_CONTEXT); + Logger.log( + 'Shutting down the old instance workflow worker service has finished', + LOG_CONTEXT + ); } } diff --git a/packages/application-generic/src/usecases/create-execution-details/types/index.ts b/packages/application-generic/src/usecases/create-execution-details/types/index.ts index 1c89c8ab2b0..73135d68abd 100644 --- a/packages/application-generic/src/usecases/create-execution-details/types/index.ts +++ b/packages/application-generic/src/usecases/create-execution-details/types/index.ts @@ -29,6 +29,7 @@ export enum DetailEnum { DIGESTED_EVENTS_PROVIDED = 'Steps to get digest events found', DIGEST_TRIGGERED_EVENTS = 'Digest triggered events', STEP_FILTERED_BY_PREFERENCES = 'Step filtered by subscriber preferences', + STEP_FILTERED_BY_GLOBAL_PREFERENCES = 'Step filtered by subscriber global preferences', WEBHOOK_FILTER_FAILED_RETRY = 'Webhook filter failed, retry will be executed', WEBHOOK_FILTER_FAILED_LAST_RETRY = 'Failed to get response from remote webhook filter on last retry', DIGEST_MERGED = 'Digest was merged with other digest', diff --git a/packages/application-generic/src/usecases/get-subscriber-global-preference/get-subscriber-global-preference.command.ts b/packages/application-generic/src/usecases/get-subscriber-global-preference/get-subscriber-global-preference.command.ts new file mode 100644 index 00000000000..54bf8e703cf --- /dev/null +++ b/packages/application-generic/src/usecases/get-subscriber-global-preference/get-subscriber-global-preference.command.ts @@ -0,0 +1,7 @@ +import { SubscriberEntity } from '@novu/dal'; + +import { EnvironmentWithSubscriber } from '../../commands'; + +export class GetSubscriberGlobalPreferenceCommand extends EnvironmentWithSubscriber { + subscriber?: SubscriberEntity; +} diff --git a/packages/application-generic/src/usecases/get-subscriber-global-preference/get-subscriber-global-preference.usecase.ts b/packages/application-generic/src/usecases/get-subscriber-global-preference/get-subscriber-global-preference.usecase.ts new file mode 100644 index 00000000000..cf1c53988b3 --- /dev/null +++ b/packages/application-generic/src/usecases/get-subscriber-global-preference/get-subscriber-global-preference.usecase.ts @@ -0,0 +1,84 @@ +import { Injectable } from '@nestjs/common'; +import { + PreferenceLevelEnum, + SubscriberEntity, + SubscriberPreferenceRepository, + SubscriberRepository, +} from '@novu/dal'; + +import { GetSubscriberGlobalPreferenceCommand } from './get-subscriber-global-preference.command'; +import { buildSubscriberKey, CachedEntity } from '../../services/cache'; +import { ApiException } from '../../utils/exceptions'; +import { IPreferenceChannels } from '@novu/shared'; + +@Injectable() +export class GetSubscriberGlobalPreference { + constructor( + private subscriberPreferenceRepository: SubscriberPreferenceRepository, + private subscriberRepository: SubscriberRepository + ) {} + + async execute(command: GetSubscriberGlobalPreferenceCommand) { + const subscriber = + command.subscriber ?? + (await this.fetchSubscriber({ + subscriberId: command.subscriberId, + _environmentId: command.environmentId, + })); + + if (!subscriber) { + throw new ApiException(`Subscriber ${command.subscriberId} not found`); + } + + const subscriberPreference = + await this.subscriberPreferenceRepository.findOne({ + _environmentId: command.environmentId, + _subscriberId: subscriber._id, + level: PreferenceLevelEnum.GLOBAL, + }); + + const subscriberChannelPreference = subscriberPreference?.channels; + const channels = this.updatePreferenceStateWithDefault( + subscriberChannelPreference ?? {} + ); + + return { + preference: { + enabled: subscriberPreference?.enabled ?? true, + channels, + }, + }; + } + + @CachedEntity({ + builder: (command: { subscriberId: string; _environmentId: string }) => + buildSubscriberKey({ + _environmentId: command._environmentId, + subscriberId: command.subscriberId, + }), + }) + private async fetchSubscriber({ + subscriberId, + _environmentId, + }: { + subscriberId: string; + _environmentId: string; + }): Promise { + return await this.subscriberRepository.findBySubscriberId( + _environmentId, + subscriberId + ); + } + // adds default state for missing channels + private updatePreferenceStateWithDefault(preference: IPreferenceChannels) { + const defaultPreference: IPreferenceChannels = { + email: true, + sms: true, + in_app: true, + chat: true, + push: true, + }; + + return { ...defaultPreference, ...preference }; + } +} diff --git a/packages/application-generic/src/usecases/get-subscriber-global-preference/index.ts b/packages/application-generic/src/usecases/get-subscriber-global-preference/index.ts new file mode 100644 index 00000000000..5393899f274 --- /dev/null +++ b/packages/application-generic/src/usecases/get-subscriber-global-preference/index.ts @@ -0,0 +1,2 @@ +export * from './get-subscriber-global-preference.usecase'; +export * from './get-subscriber-global-preference.command'; diff --git a/packages/application-generic/src/usecases/get-subscriber-template-preference/get-subscriber-template-preference.usecase.ts b/packages/application-generic/src/usecases/get-subscriber-template-preference/get-subscriber-template-preference.usecase.ts index 945343ce2c4..e4e36cc511d 100644 --- a/packages/application-generic/src/usecases/get-subscriber-template-preference/get-subscriber-template-preference.usecase.ts +++ b/packages/application-generic/src/usecases/get-subscriber-template-preference/get-subscriber-template-preference.usecase.ts @@ -5,6 +5,7 @@ import { SubscriberRepository, SubscriberEntity, MessageTemplateRepository, + PreferenceLevelEnum, } from '@novu/dal'; import { ChannelTypeEnum, @@ -53,6 +54,7 @@ export class GetSubscriberTemplatePreference { _environmentId: command.environmentId, _subscriberId: subscriber._id, _templateId: command.template._id, + level: PreferenceLevelEnum.TEMPLATE, }); const subscriberChannelPreference = subscriberPreference?.channels; diff --git a/packages/application-generic/src/usecases/index.ts b/packages/application-generic/src/usecases/index.ts index cec41c573b7..bbbf703b03e 100644 --- a/packages/application-generic/src/usecases/index.ts +++ b/packages/application-generic/src/usecases/index.ts @@ -28,3 +28,4 @@ export * from './conditions-filter'; export * from './switch-environment'; export * from './switch-organization'; export * from './create-user'; +export * from './get-subscriber-global-preference'; diff --git a/packages/application-generic/src/usecases/trigger-event/trigger-event.usecase.ts b/packages/application-generic/src/usecases/trigger-event/trigger-event.usecase.ts index 3a46d709f48..85fdc5aa7aa 100644 --- a/packages/application-generic/src/usecases/trigger-event/trigger-event.usecase.ts +++ b/packages/application-generic/src/usecases/trigger-event/trigger-event.usecase.ts @@ -58,155 +58,172 @@ export class TriggerEvent { @InstrumentUsecase() async execute(command: TriggerEventCommand) { - const { - actor, - environmentId, - identifier, - organizationId, - to, - userId, - tenant, - } = command; + try { + const { + actor, + environmentId, + identifier, + organizationId, + to, + userId, + tenant, + } = command; - await this.validateTransactionIdProperty( - command.transactionId, - environmentId - ); - - Sentry.addBreadcrumb({ - message: 'Sending trigger', - data: { - triggerIdentifier: identifier, - }, - }); + await this.validateTransactionIdProperty( + command.transactionId, + environmentId + ); - this.logger.assign({ - transactionId: command.transactionId, - environmentId: command.environmentId, - organizationId: command.organizationId, - }); + Sentry.addBreadcrumb({ + message: 'Sending trigger', + data: { + triggerIdentifier: identifier, + }, + }); - const template = await this.getNotificationTemplateByTriggerIdentifier({ - environmentId: command.environmentId, - triggerIdentifier: command.identifier, - }); + this.logger.assign({ + transactionId: command.transactionId, + environmentId: command.environmentId, + organizationId: command.organizationId, + }); - /* - * Makes no sense to execute anything if template doesn't exist - * TODO: Send a 404? - */ - if (!template) { - const message = 'Notification template could not be found'; - const error = new ApiException(message); - throw error; - } + const template = await this.getNotificationTemplateByTriggerIdentifier({ + environmentId: command.environmentId, + triggerIdentifier: command.identifier, + }); - const templateProviderIds = await this.getProviderIdsForTemplate( - command.environmentId, - template - ); + /* + * Makes no sense to execute anything if template doesn't exist + * TODO: Send a 404? + */ + if (!template) { + const message = 'Notification template could not be found'; + const error = new ApiException(message); + Logger.error(error, message, LOG_CONTEXT); + throw error; + } - if (tenant) { - const tenantProcessed = await this.processTenant.execute( - ProcessTenantCommand.create({ - environmentId, - organizationId, - userId, - tenant, - }) + const templateProviderIds = await this.getProviderIdsForTemplate( + command.environmentId, + template ); - if (!tenantProcessed) { - Logger.warn( - `Tenant with identifier ${JSON.stringify( - tenant.identifier - )} of organization ${command.organizationId} in transaction ${ - command.transactionId - } could not be processed.`, - LOG_CONTEXT + if (tenant) { + const tenantProcessed = await this.processTenant.execute( + ProcessTenantCommand.create({ + environmentId, + organizationId, + userId, + tenant, + }) ); - } - } - - // We might have a single actor for every trigger, so we only need to check for it once - let actorProcessed; - if (actor) { - actorProcessed = await this.processSubscriber.execute( - ProcessSubscriberCommand.create({ - environmentId, - organizationId, - userId, - subscriber: actor, - }) - ); - } - for (const subscriber of to) { - this.analyticsService.track( - 'Notification event trigger - [Triggers]', - command.userId, - { - transactionId: command.transactionId, - _template: template._id, - _organization: command.organizationId, - channels: template?.steps.map((step) => step.template?.type), - source: command.payload.__source || 'api', + if (!tenantProcessed) { + Logger.warn( + `Tenant with identifier ${JSON.stringify( + tenant.identifier + )} of organization ${command.organizationId} in transaction ${ + command.transactionId + } could not be processed.`, + LOG_CONTEXT + ); } - ); - - const subscriberProcessed = await this.processSubscriber.execute( - ProcessSubscriberCommand.create({ - environmentId, - organizationId, - userId, - subscriber, - }) - ); + } - // If no subscriber makes no sense to try to create notification - if (subscriberProcessed) { - const createNotificationJobsCommand = - CreateNotificationJobsCommand.create({ + // We might have a single actor for every trigger, so we only need to check for it once + let actorProcessed; + if (actor) { + actorProcessed = await this.processSubscriber.execute( + ProcessSubscriberCommand.create({ environmentId, - identifier, organizationId, - overrides: command.overrides, - payload: command.payload, - subscriber: subscriberProcessed, - template, - templateProviderIds, - to: subscriber, - transactionId: command.transactionId, userId, - ...(actor && actorProcessed && { actor: actorProcessed }), - tenant, - }); + subscriber: actor, + }) + ); + } - const notificationJobs = await this.createNotificationJobs.execute( - createNotificationJobsCommand + for (const subscriber of to) { + this.analyticsService.mixpanelTrack( + 'Notification event trigger - [Triggers]', + '', + { + transactionId: command.transactionId, + _template: template._id, + _organization: command.organizationId, + channels: template?.steps.map((step) => step.template?.type), + source: command.payload.__source || 'api', + } ); - const storeSubscriberJobsCommand = StoreSubscriberJobsCommand.create({ - environmentId: command.environmentId, - jobs: notificationJobs, - organizationId: command.organizationId, - }); - await this.storeSubscriberJobs.execute(storeSubscriberJobsCommand); - } else { - /** - * TODO: Potentially add a CreateExecutionDetails entry. Right now we - * have the limitation we need a job to be created for that. Here there - * is no job at this point. - */ - Logger.warn( - `Subscriber ${JSON.stringify( - subscriber.subscriberId - )} of organization ${command.organizationId} in transaction ${ - command.transactionId - } was not processed. No jobs are created.`, - LOG_CONTEXT + const subscriberProcessed = await this.processSubscriber.execute( + ProcessSubscriberCommand.create({ + environmentId, + organizationId, + userId, + subscriber, + }) ); + + // If no subscriber makes no sense to try to create notification + if (subscriberProcessed) { + const createNotificationJobsCommand = + CreateNotificationJobsCommand.create({ + environmentId, + identifier, + organizationId, + overrides: command.overrides, + payload: command.payload, + subscriber: subscriberProcessed, + template, + templateProviderIds, + to: subscriber, + transactionId: command.transactionId, + userId, + ...(actor && actorProcessed && { actor: actorProcessed }), + tenant, + }); + + const notificationJobs = await this.createNotificationJobs.execute( + createNotificationJobsCommand + ); + + const storeSubscriberJobsCommand = StoreSubscriberJobsCommand.create({ + environmentId: command.environmentId, + jobs: notificationJobs, + organizationId: command.organizationId, + }); + await this.storeSubscriberJobs.execute(storeSubscriberJobsCommand); + } else { + /** + * TODO: Potentially add a CreateExecutionDetails entry. Right now we + * have the limitation we need a job to be created for that. Here there + * is no job at this point. + */ + Logger.warn( + `Subscriber ${JSON.stringify( + subscriber.subscriberId + )} of organization ${command.organizationId} in transaction ${ + command.transactionId + } was not processed. No jobs are created.`, + LOG_CONTEXT + ); + } } + } catch (e) { + Logger.error( + { + transactionId: command.transactionId, + organization: command.organizationId, + triggerIdentifier: command.identifier, + userId: command.userId, + error: e, + }, + 'Unexpected error has occurred when triggering event', + LOG_CONTEXT + ); + + throw e; } } diff --git a/packages/cli/package.json b/packages/cli/package.json index 6c6202214ac..4ac9c0f60a0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "novu", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "On-Boarding Cli", "main": "index.js", "scripts": { @@ -31,7 +31,7 @@ "ncp": "^2.0.0" }, "dependencies": { - "@novu/shared": "^0.19.0", + "@novu/shared": "^0.20.0-alpha.0", "analytics-node": "^6.2.0", "axios": "^1.3.3", "chalk": "4.1.2", diff --git a/packages/client/package.json b/packages/client/package.json index 10dc8afdb89..b381d47eab4 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@novu/client", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "repository": "https://github.com/novuhq/novu", "description": "API client to be used in end user environments", "main": "dist/cjs/index.js", @@ -44,7 +44,7 @@ "node": ">=10" }, "dependencies": { - "@novu/shared": "^0.19.0" + "@novu/shared": "^0.20.0-alpha.0" }, "devDependencies": { "@types/jest": "29.5.2", diff --git a/packages/client/src/api/api.service.ts b/packages/client/src/api/api.service.ts index 081323989b3..dbdb9611ac4 100644 --- a/packages/client/src/api/api.service.ts +++ b/packages/client/src/api/api.service.ts @@ -12,6 +12,7 @@ import { IUserPreferenceSettings, IUnseenCountQuery, IUnreadCountQuery, + IUserGlobalPreferenceSettings, } from '../index'; export class ApiService { @@ -157,6 +158,10 @@ export class ApiService { return this.httpClient.get('/widgets/preferences'); } + async getUserGlobalPreference(): Promise { + return this.httpClient.get('/widgets/preferences/global'); + } + async updateSubscriberPreference( templateId: string, channelType: string, @@ -166,4 +171,17 @@ export class ApiService { channel: { type: channelType, enabled }, }); } + + async updateSubscriberGlobalPreference( + preferences: { channelType: string; enabled: boolean }[], + enabled?: boolean + ): Promise { + return await this.httpClient.patch(`/widgets/preferences`, { + preferences: preferences.map((preference) => ({ + ...preference, + type: preference.channelType, + })), + enabled, + }); + } } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 038b8858bd7..0d63330f163 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -38,7 +38,15 @@ export interface IUserPreferenceSettings { tags?: string[]; data?: NotificationTemplateCustomData; }; - preference: { enabled: boolean; channels: IPreferenceChannels }; + preference: PreferenceSettingsType; } +export interface IUserGlobalPreferenceSettings { + preference: PreferenceSettingsType; +} + +export type PreferenceSettingsType = { + enabled: boolean; + channels: IPreferenceChannels; +}; export { ApiService } from './api/api.service'; diff --git a/packages/headless/package.json b/packages/headless/package.json index 07850801eea..37d01208275 100644 --- a/packages/headless/package.json +++ b/packages/headless/package.json @@ -1,6 +1,6 @@ { "name": "@novu/headless", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "repository": "https://github.com/novuhq/novu", "description": "Headless client package that is a thin abstraction layer over the API client + state and socket management", "keywords": [], @@ -28,8 +28,8 @@ "node": ">=10" }, "dependencies": { - "@novu/client": "^0.19.0", - "@novu/shared": "^0.19.0", + "@novu/client": "^0.20.0-alpha.0", + "@novu/shared": "^0.20.0-alpha.0", "@tanstack/query-core": "^4.15.1", "socket.io-client": "4.7.2" }, diff --git a/packages/headless/src/lib/headless.service.test.ts b/packages/headless/src/lib/headless.service.test.ts index 5b214c401ae..b833569ded3 100644 --- a/packages/headless/src/lib/headless.service.test.ts +++ b/packages/headless/src/lib/headless.service.test.ts @@ -1,4 +1,8 @@ -import { ApiService, IUserPreferenceSettings } from '@novu/client'; +import { + ApiService, + IUserGlobalPreferenceSettings, + IUserPreferenceSettings, +} from '@novu/client'; import { WebSocketEventEnum } from '@novu/shared'; import io from 'socket.io-client'; @@ -90,6 +94,20 @@ const mockUserPreferenceSetting: IUserPreferenceSettings = { }, }, }; + +const mockUserGlobalPreferenceSetting: IUserGlobalPreferenceSettings = { + preference: { + enabled: true, + channels: { + email: true, + sms: true, + in_app: true, + chat: true, + push: true, + }, + }, +}; + const mockUserPreferences = [mockUserPreferenceSetting]; const mockServiceInstance = { @@ -108,6 +126,9 @@ const mockServiceInstance = { updateSubscriberPreference: jest.fn(() => promiseResolveTimeout(0, mockUserPreferenceSetting) ), + updateSubscriberGlobalPreference: jest.fn(() => + promiseResolveTimeout(0, mockUserGlobalPreferenceSetting) + ), markMessageAs: jest.fn(), removeMessage: jest.fn(), updateAction: jest.fn(), @@ -923,6 +944,99 @@ describe('headless.service', () => { }); }); + describe('updateUserGlobalPreferences', () => { + test('calls updateUserGlobalPreferences successfully', async () => { + const payload = { + enabled: true, + preferences: [ + { + channelType: ChannelTypeEnum.EMAIL, + enabled: false, + }, + ], + }; + + const updatedUserGlobalPreferenceSetting = { + preference: { + enabled: true, + channels: { + email: false, + }, + }, + }; + mockServiceInstance.updateSubscriberGlobalPreference.mockImplementationOnce( + () => promiseResolveTimeout(0, updatedUserGlobalPreferenceSetting) + ); + const headlessService = new HeadlessService(options); + + const listener = jest.fn(); + const onSuccess = jest.fn(); + (headlessService as any).session = mockSession; + + headlessService.updateUserGlobalPreferences({ + preferences: payload.preferences, + enabled: payload.enabled, + listener, + onSuccess, + }); + + expect(listener).toBeCalledWith( + expect.objectContaining({ isLoading: true, data: undefined }) + ); + await promiseResolveTimeout(100); + + expect( + mockServiceInstance.updateSubscriberGlobalPreference + ).toBeCalledTimes(1); + + expect(onSuccess).toHaveBeenNthCalledWith( + 1, + updatedUserGlobalPreferenceSetting + ); + }); + + test('handles the error', async () => { + const payload = { + enabled: true, + preferences: [ + { + channelType: ChannelTypeEnum.EMAIL, + enabled: true, + }, + ], + }; + + const error = new Error('error'); + mockServiceInstance.updateSubscriberGlobalPreference.mockImplementationOnce( + () => promiseRejectTimeout(0, error) + ); + const headlessService = new HeadlessService(options); + const listener = jest.fn(); + const onError = jest.fn(); + (headlessService as any).session = mockSession; + + headlessService.updateUserGlobalPreferences({ + preferences: payload.preferences, + enabled: payload.enabled, + listener, + onError, + }); + expect(listener).toBeCalledWith( + expect.objectContaining({ isLoading: true, data: undefined }) + ); + await promiseResolveTimeout(100); + + expect( + mockServiceInstance.updateSubscriberGlobalPreference + ).toBeCalledTimes(1); + expect(onError).toHaveBeenCalledWith(error); + expect(listener).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ isLoading: false, data: undefined, error }) + ); + }); + }); + describe('markNotificationsAsRead', () => { test('calls markNotificationsAsRead successfully', async () => { const updatedNotification = { diff --git a/packages/headless/src/lib/headless.service.ts b/packages/headless/src/lib/headless.service.ts index 74d09e7010f..0290b0a192b 100644 --- a/packages/headless/src/lib/headless.service.ts +++ b/packages/headless/src/lib/headless.service.ts @@ -5,7 +5,12 @@ import { MutationObserverResult, } from '@tanstack/query-core'; import io from 'socket.io-client'; -import { ApiService, IUserPreferenceSettings, IStoreQuery } from '@novu/client'; +import { + ApiService, + IUserPreferenceSettings, + IStoreQuery, + IUserGlobalPreferenceSettings, +} from '@novu/client'; import { IOrganizationEntity, IMessage, @@ -21,6 +26,7 @@ import { SESSION_QUERY_KEY, UNREAD_COUNT_QUERY_KEY, UNSEEN_COUNT_QUERY_KEY, + USER_GLOBAL_PREFERENCES_QUERY_KEY, USER_PREFERENCES_QUERY_KEY, } from '../utils'; import { @@ -30,6 +36,7 @@ import { IMessageId, IUpdateActionVariables, IUpdateUserPreferencesVariables, + IUpdateUserGlobalPreferencesVariables, UpdateResult, } from './types'; @@ -104,6 +111,14 @@ export class HeadlessService { queryFn: () => this.api.getUserPreference(), }; + private userGlobalPreferencesQueryOptions: QueryObserverOptions< + IUserGlobalPreferenceSettings[], + unknown + > = { + queryKey: USER_GLOBAL_PREFERENCES_QUERY_KEY, + queryFn: () => this.api.getUserGlobalPreference(), + }; + constructor(private options: IHeadlessServiceOptions) { const backendUrl = options.backendUrl ?? 'https://api.novu.co'; const token = getToken(); @@ -456,6 +471,29 @@ export class HeadlessService { return unsubscribe; } + public fetchUserGlobalPreferences({ + listener, + onSuccess, + onError, + }: { + listener: (result: FetchResult) => void; + onSuccess?: (settings: IUserGlobalPreferenceSettings[]) => void; + onError?: (error: unknown) => void; + }) { + this.assertSessionInitialized(); + + const { unsubscribe } = this.queryService.subscribeQuery({ + options: { + ...this.userGlobalPreferencesQueryOptions, + onSuccess, + onError, + }, + listener: (result) => this.callFetchListener(result, listener), + }); + + return unsubscribe; + } + public async updateUserPreferences({ templateId, channelType, @@ -523,6 +561,63 @@ export class HeadlessService { }); } + public async updateUserGlobalPreferences({ + preferences, + enabled, + listener, + onSuccess, + onError, + }: { + preferences: IUpdateUserGlobalPreferencesVariables['preferences']; + enabled?: IUpdateUserGlobalPreferencesVariables['enabled']; + listener: ( + result: UpdateResult< + IUserGlobalPreferenceSettings, + unknown, + IUpdateUserGlobalPreferencesVariables + > + ) => void; + onSuccess?: (settings: IUserGlobalPreferenceSettings) => void; + onError?: (error: unknown) => void; + }) { + this.assertSessionInitialized(); + + const { result, unsubscribe } = this.queryService.subscribeMutation< + IUserGlobalPreferenceSettings, + unknown, + IUpdateUserGlobalPreferencesVariables + >({ + options: { + mutationFn: (variables) => + this.api.updateSubscriberGlobalPreference( + variables.preferences, + variables.enabled + ), + onSuccess: (data) => { + this.queryClient.setQueryData( + USER_GLOBAL_PREFERENCES_QUERY_KEY, + () => [data] + ); + }, + }, + listener: (res) => this.callUpdateListener(res, listener), + }); + + result + .mutate({ preferences, enabled }) + .then((data) => { + onSuccess?.(data); + + return data; + }) + .catch((error) => { + onError?.(error); + }) + .finally(() => { + unsubscribe(); + }); + } + public async markNotificationsAsRead({ messageId, listener, diff --git a/packages/headless/src/lib/types.ts b/packages/headless/src/lib/types.ts index 1a590248d54..8c654cfc255 100644 --- a/packages/headless/src/lib/types.ts +++ b/packages/headless/src/lib/types.ts @@ -2,7 +2,11 @@ import { QueryObserverResult, MutationObserverResult, } from '@tanstack/query-core'; -import { ButtonTypeEnum, MessageActionStatusEnum } from '@novu/shared'; +import { + ButtonTypeEnum, + ChannelTypeEnum, + MessageActionStatusEnum, +} from '@novu/shared'; export interface IHeadlessServiceOptions { backendUrl?: string; @@ -22,6 +26,11 @@ export interface IUpdateUserPreferencesVariables { checked: boolean; } +export interface IUpdateUserGlobalPreferencesVariables { + preferences?: { channelType: ChannelTypeEnum; enabled: boolean }[]; + enabled?: boolean; +} + export interface IUpdateActionVariables { messageId: string; actionButtonType: ButtonTypeEnum; diff --git a/packages/headless/src/utils/query-keys.ts b/packages/headless/src/utils/query-keys.ts index 2b24642c8e5..7e57fc69779 100644 --- a/packages/headless/src/utils/query-keys.ts +++ b/packages/headless/src/utils/query-keys.ts @@ -2,5 +2,6 @@ export const SESSION_QUERY_KEY = ['session']; export const ORGANIZATION_QUERY_KEY = ['organization']; export const NOTIFICATIONS_QUERY_KEY = ['notifications']; export const USER_PREFERENCES_QUERY_KEY = ['user_preferences']; +export const USER_GLOBAL_PREFERENCES_QUERY_KEY = ['user_global_preferences']; export const UNSEEN_COUNT_QUERY_KEY = ['unseen_count']; export const UNREAD_COUNT_QUERY_KEY = ['unread_count']; diff --git a/packages/nest/package.json b/packages/nest/package.json index 3a22c168038..d5381807bd9 100644 --- a/packages/nest/package.json +++ b/packages/nest/package.json @@ -1,6 +1,6 @@ { "name": "@novu/nest", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A nestjs wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -36,7 +36,7 @@ }, "dependencies": { "@nestjs/common": "^8.2.0", - "@novu/stateless": "^0.19.0" + "@novu/stateless": "^0.20.0-alpha.0" }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.1", diff --git a/packages/node/README.md b/packages/node/README.md index b175ab0410b..07f38a86e08 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -319,6 +319,27 @@ const novu = new Novu(''); await novu.subscribers.getPreference("subscriberId") ``` +- #### Get subscriber global preference +```ts +import { Novu } from '@novu/node'; + +const novu = new Novu(''); + +await novu.subscribers.getGlobalPreference("subscriberId" ) +``` + + +- #### Get subscriber preference by level +```ts +import { Novu, PreferenceLevelEnum } from '@novu/node'; + +const novu = new Novu(''); +// Get global level preference +await novu.subscribers.getPreferenceByLevel("subscriberId", PreferenceLevelEnum.GLOBAL) + +// Get template level preference +await novu.subscribers.getPreferenceByLevel("subscriberId", PreferenceLevelEnum.TEMPLATE) +``` - #### Update subscriber preference for a workflow ```ts import { Novu } from '@novu/node'; @@ -338,11 +359,30 @@ await novu.subscribers.updatePreference("subscriberId", "workflowId", { await novu.subscribers.updatePreference("subscriberId", "workflowId", { channel: { type: "email" - enabled: + enabled: false } }) ``` +- #### Update subscriber preference globally +```ts +import { Novu } from '@novu/node'; + +const novu = new Novu(''); + +// enable in-app channel and disable email channel +await novu.subscribers.updateGlobalPreference("subscriberId", { + enabled: true, + preferences: [{ + type: "in_app" + enabled: true + }, { + type: "email" + enabled: false + }] +}) +``` + - #### Get in-app messages (notifications) feed for a subscriber ```ts diff --git a/packages/node/package.json b/packages/node/package.json index db8274f4aba..95881a06969 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@novu/node", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "Notification Management Framework", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -42,7 +42,7 @@ "node": ">=10" }, "dependencies": { - "@novu/shared": "^0.19.0", + "@novu/shared": "^0.20.0-alpha.0", "handlebars": "^4.7.7", "lodash.get": "^4.4.2", "lodash.merge": "^4.6.2" diff --git a/packages/node/src/lib/subscribers/subscriber.interface.ts b/packages/node/src/lib/subscribers/subscriber.interface.ts index bb0d0a42afc..13a7e97a41d 100644 --- a/packages/node/src/lib/subscribers/subscriber.interface.ts +++ b/packages/node/src/lib/subscribers/subscriber.interface.ts @@ -5,9 +5,15 @@ import { ButtonTypeEnum, MessageActionStatusEnum, ISubscribersDefine, + PreferenceLevelEnum, } from '@novu/shared'; -export { ISubscriberPayload, ButtonTypeEnum, MessageActionStatusEnum }; +export { + ISubscriberPayload, + ButtonTypeEnum, + MessageActionStatusEnum, + PreferenceLevelEnum, +}; export interface ISubscribers { list(page: number, limit: number); @@ -28,11 +34,17 @@ export interface ISubscribers { unsetCredentials(subscriberId: string, providerId: string); updateOnlineStatus(subscriberId: string, online: boolean); getPreference(subscriberId: string); + getGlobalPreference(subscriberId: string); + getPreferenceByLevel(subscriberId: string, level: PreferenceLevelEnum); updatePreference( subscriberId: string, templateId: string, data: IUpdateSubscriberPreferencePayload ); + updateGlobalPreference( + subscriberId: string, + data: IUpdateSubscriberGlobalPreferencePayload + ); getNotificationsFeed( subscriberId: string, params: IGetSubscriberNotificationFeedParams @@ -62,6 +74,14 @@ export interface IUpdateSubscriberPreferencePayload { }; enabled?: boolean; } + +export interface IUpdateSubscriberGlobalPreferencePayload { + preferences?: { + type: ChannelTypeEnum; + enabled: boolean; + }[]; + enabled?: boolean; +} export interface IGetSubscriberNotificationFeedParams { page?: number; limit?: number; diff --git a/packages/node/src/lib/subscribers/subscribers.ts b/packages/node/src/lib/subscribers/subscribers.ts index 50326af4bd2..d54635e14c1 100644 --- a/packages/node/src/lib/subscribers/subscribers.ts +++ b/packages/node/src/lib/subscribers/subscribers.ts @@ -4,13 +4,14 @@ import { IChannelCredentials, ISubscribersDefine, } from '@novu/shared'; -import { MarkMessagesAsEnum } from '@novu/shared'; +import { MarkMessagesAsEnum, PreferenceLevelEnum } from '@novu/shared'; import { IGetSubscriberNotificationFeedParams, IMarkFields, IMarkMessageActionFields, ISubscriberPayload, ISubscribers, + IUpdateSubscriberGlobalPreferencePayload, IUpdateSubscriberPreferencePayload, } from './subscriber.interface'; import { WithHttp } from '../novu.interface'; @@ -91,6 +92,18 @@ export class Subscribers extends WithHttp implements ISubscribers { return await this.http.get(`/subscribers/${subscriberId}/preferences`); } + async getGlobalPreference(subscriberId: string) { + return await this.http.get( + `/subscribers/${subscriberId}/preferences/${PreferenceLevelEnum.GLOBAL}` + ); + } + + async getPreferenceByLevel(subscriberId: string, level: PreferenceLevelEnum) { + return await this.http.get( + `/subscribers/${subscriberId}/preferences/${level}` + ); + } + async updatePreference( subscriberId: string, templateId: string, @@ -104,6 +117,15 @@ export class Subscribers extends WithHttp implements ISubscribers { ); } + async updateGlobalPreference( + subscriberId: string, + data: IUpdateSubscriberGlobalPreferencePayload + ) { + return await this.http.patch(`/subscribers/${subscriberId}/preferences`, { + ...data, + }); + } + async getNotificationsFeed( subscriberId: string, { payload, ...rest }: IGetSubscriberNotificationFeedParams = {} diff --git a/packages/notification-center-angular/package.json b/packages/notification-center-angular/package.json index 255c50b94da..2f67455be9a 100644 --- a/packages/notification-center-angular/package.json +++ b/packages/notification-center-angular/package.json @@ -1,6 +1,6 @@ { "name": "@novu/notification-center-angular", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "scripts": { "ng": "ng", "start": "ng serve", @@ -37,7 +37,7 @@ "@angular/core": "~15.2.0", "@angular/platform-browser": "~15.2.0", "@angular/platform-browser-dynamic": "~15.2.0", - "@novu/notification-center": "^0.19.0", + "@novu/notification-center": "^0.20.0-alpha.0", "react": "^17.0.1", "react-dom": "^17.0.1", "tslib": "^2.3.0", diff --git a/packages/notification-center-vue/package.json b/packages/notification-center-vue/package.json index f7adebc96d6..c87cc31b17d 100644 --- a/packages/notification-center-vue/package.json +++ b/packages/notification-center-vue/package.json @@ -1,7 +1,7 @@ { "name": "@novu/notification-center-vue", "sideEffects": false, - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "Vue specific wrapper for notification-center", "repository": { "type": "git", @@ -22,7 +22,7 @@ "dependencies": { "@emotion/css": "^11.10.5", "@novu/floating-vue": "^2.0.3", - "@novu/notification-center": "^0.19.0", + "@novu/notification-center": "^0.20.0-alpha.0", "react": "^17.0.1", "react-dom": "^17.0.1" }, diff --git a/packages/notification-center/package.json b/packages/notification-center/package.json index 18a421b0847..765ad45c1eb 100644 --- a/packages/notification-center/package.json +++ b/packages/notification-center/package.json @@ -1,6 +1,6 @@ { "name": "@novu/notification-center", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "repository": "https://github.com/novuhq/novu", "description": "", "scripts": { @@ -80,8 +80,8 @@ "@emotion/styled": "^11.6.0", "@mantine/core": "^5.7.1", "@mantine/hooks": "^5.7.1", - "@novu/client": "^0.19.0", - "@novu/shared": "^0.19.0", + "@novu/client": "^0.20.0-alpha.0", + "@novu/shared": "^0.20.0-alpha.0", "@tanstack/react-query": "^4.20.4", "acorn-jsx": "^5.3.2", "axios": "^1.4.0", diff --git a/packages/notification-center/src/components/novu-provider/NovuProvider.tsx b/packages/notification-center/src/components/novu-provider/NovuProvider.tsx index 956f52e120f..8bf018272cb 100644 --- a/packages/notification-center/src/components/novu-provider/NovuProvider.tsx +++ b/packages/notification-center/src/components/novu-provider/NovuProvider.tsx @@ -29,6 +29,7 @@ const DEFAULT_FETCHING_STRATEGY: IFetchingStrategy = { fetchOrganization: true, fetchNotifications: false, fetchUserPreferences: false, + fetchUserGlobalPreferences: false, }; export interface INovuProviderProps { diff --git a/packages/notification-center/src/hooks/index.ts b/packages/notification-center/src/hooks/index.ts index 400f8aa67f4..5bd891e36d8 100644 --- a/packages/notification-center/src/hooks/index.ts +++ b/packages/notification-center/src/hooks/index.ts @@ -6,11 +6,13 @@ export * from './useNovuTheme'; export * from './useNotificationCenter'; export * from './useTranslations'; export * from './useUpdateUserPreferences'; +export * from './useUpdateUserGlobalPreferences'; export * from './useUpdateAction'; export * from './useFetchNotifications'; export * from './useFetchOrganization'; export * from './useFeedUnseenCount'; export * from './useFetchUserPreferences'; +export * from './useFetchUserGlobalPreferences'; export * from './useMarkNotificationsAs'; export * from './useRemoveNotification'; export * from './useRemoveAllNotifications'; diff --git a/packages/notification-center/src/hooks/queryKeys.ts b/packages/notification-center/src/hooks/queryKeys.ts index 78da8305273..86ac597d55b 100644 --- a/packages/notification-center/src/hooks/queryKeys.ts +++ b/packages/notification-center/src/hooks/queryKeys.ts @@ -2,5 +2,6 @@ export const SESSION_QUERY_KEY = ['session']; export const ORGANIZATION_QUERY_KEY = ['organization']; export const INFINITE_NOTIFICATIONS_QUERY_KEY = ['infinite_notifications']; export const USER_PREFERENCES_QUERY_KEY = ['user_preferences']; +export const USER_GLOBAL_PREFERENCES_QUERY_KEY = ['user_global_preferences']; export const UNSEEN_COUNT_QUERY_KEY = ['unseen_count']; export const FEED_UNSEEN_COUNT_QUERY_KEY = ['feed_unseen_count']; diff --git a/packages/notification-center/src/hooks/useFetchUserGlobalPreferences.ts b/packages/notification-center/src/hooks/useFetchUserGlobalPreferences.ts new file mode 100644 index 00000000000..0de51708b17 --- /dev/null +++ b/packages/notification-center/src/hooks/useFetchUserGlobalPreferences.ts @@ -0,0 +1,23 @@ +import type { IUserGlobalPreferenceSettings } from '@novu/client'; +import { useQuery, UseQueryOptions } from '@tanstack/react-query'; + +import { useFetchUserGlobalPreferencesQueryKey } from './useFetchUserGlobalPreferencesQueryKey'; +import { useNovuContext } from './useNovuContext'; + +export const useFetchUserGlobalPreferences = ( + options: UseQueryOptions = {} +) => { + const { apiService, isSessionInitialized, fetchingStrategy } = useNovuContext(); + const userGlobalPreferencesQueryKey = useFetchUserGlobalPreferencesQueryKey(); + + const result = useQuery( + userGlobalPreferencesQueryKey, + () => apiService.getUserGlobalPreference(), + { + ...options, + enabled: isSessionInitialized && fetchingStrategy.fetchUserGlobalPreferences, + } + ); + + return result; +}; diff --git a/packages/notification-center/src/hooks/useFetchUserGlobalPreferencesQueryKey.ts b/packages/notification-center/src/hooks/useFetchUserGlobalPreferencesQueryKey.ts new file mode 100644 index 00000000000..ad77a688a3c --- /dev/null +++ b/packages/notification-center/src/hooks/useFetchUserGlobalPreferencesQueryKey.ts @@ -0,0 +1,11 @@ +import { useMemo } from 'react'; + +import { USER_GLOBAL_PREFERENCES_QUERY_KEY } from './queryKeys'; +import { useSetQueryKey } from './useSetQueryKey'; + +export const useFetchUserGlobalPreferencesQueryKey = () => { + const setQueryKey = useSetQueryKey(); + const queryKey = useMemo(() => setQueryKey([...USER_GLOBAL_PREFERENCES_QUERY_KEY]), [setQueryKey]); + + return queryKey; +}; diff --git a/packages/notification-center/src/hooks/useUpdateUserGlobalPreferences.ts b/packages/notification-center/src/hooks/useUpdateUserGlobalPreferences.ts new file mode 100644 index 00000000000..66108c9f2d4 --- /dev/null +++ b/packages/notification-center/src/hooks/useUpdateUserGlobalPreferences.ts @@ -0,0 +1,71 @@ +import type { IUserGlobalPreferenceSettings } from '@novu/client'; +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'; +import { useCallback } from 'react'; + +import { useFetchUserGlobalPreferencesQueryKey } from './useFetchUserGlobalPreferencesQueryKey'; +import { useNovuContext } from './useNovuContext'; + +interface IUpdateUserGlobalPreferencesVariables { + preferences: { channelType: string; enabled: boolean }[]; + enabled?: boolean; +} + +export const useUpdateUserGlobalPreferences = ({ + onSuccess, + onError, + ...options +}: UseMutationOptions = {}) => { + const queryClient = useQueryClient(); + const { apiService } = useNovuContext(); + const userGlobalPreferencesQueryKey = useFetchUserGlobalPreferencesQueryKey(); + + const updateGlobalPreferenceChecked = useCallback( + ({ enabled, preferences }: IUpdateUserGlobalPreferencesVariables) => { + queryClient.setQueryData(userGlobalPreferencesQueryKey, (old) => { + return { + preference: { + enabled: enabled ?? old.preference.enabled, + channels: { + ...old.preference.channels, + ...preferences.reduce((acc, { channelType, enabled: channelEnabled }) => { + acc[channelType] = channelEnabled; + + return acc; + }, {} as Record), + }, + }, + }; + }); + }, + [queryClient] + ); + + const { mutate, ...result } = useMutation< + IUserGlobalPreferenceSettings, + Error, + IUpdateUserGlobalPreferencesVariables + >((variables) => apiService.updateSubscriberGlobalPreference(variables.preferences, variables.enabled), { + ...options, + onSuccess: (data, variables, context) => { + queryClient.setQueryData(userGlobalPreferencesQueryKey, () => [data]); + onSuccess?.(data, variables, context); + }, + onError: (error, variables, context) => { + updateGlobalPreferenceChecked({ + enabled: !variables.enabled ?? undefined, + preferences: variables.preferences, + }); + onError?.(error, variables, context); + }, + }); + + const updateUserGlobalPreferences = useCallback( + (variables: IUpdateUserGlobalPreferencesVariables) => { + updateGlobalPreferenceChecked(variables); + mutate(variables); + }, + [updateGlobalPreferenceChecked, mutate] + ); + + return { ...result, updateUserGlobalPreferences }; +}; diff --git a/packages/notification-center/src/shared/interfaces/index.ts b/packages/notification-center/src/shared/interfaces/index.ts index 0bc431e0381..f9f15034ba0 100644 --- a/packages/notification-center/src/shared/interfaces/index.ts +++ b/packages/notification-center/src/shared/interfaces/index.ts @@ -78,6 +78,7 @@ export interface IFetchingStrategy { fetchOrganization: boolean; fetchNotifications: boolean; fetchUserPreferences: boolean; + fetchUserGlobalPreferences: boolean; } export interface INovuProviderContext { diff --git a/packages/stateless/package.json b/packages/stateless/package.json index 1134dae4507..e0ba5bbfcff 100644 --- a/packages/stateless/package.json +++ b/packages/stateless/package.json @@ -1,6 +1,6 @@ { "name": "@novu/stateless", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "Notification Management Framework", "main": "build/main/index.js", "typings": "build/main/index.d.ts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f21418e9cc..f68f56bffb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -119,7 +119,7 @@ importers: eslint: 8.38.0 eslint-config-airbnb-typescript: 17.0.0_awz7iagzjrkgdisnaft5immp6i eslint-config-prettier: 8.8.0_eslint@8.38.0 - eslint-import-resolver-webpack: 0.13.2_re6elnmjmviqdg6e3dtzej3oae + eslint-import-resolver-webpack: 0.13.2_2shkfpyvap4zatu4dplbemujdy eslint-plugin-eslint-comments: 3.2.0_eslint@8.38.0 eslint-plugin-functional: 3.7.2_ze6bmax3gcsfve3yrzu6npguhe eslint-plugin-import: 2.27.5_ow2j3yqjjpzplq4flnkwqs3k4u @@ -171,13 +171,13 @@ importers: '@nestjs/swagger': ^7.1.8 '@nestjs/terminus': ^10.0.1 '@nestjs/testing': ^10.2.2 - '@novu/application-generic': ^0.19.0 - '@novu/dal': ^0.19.0 + '@novu/application-generic': ^0.20.0-alpha.0 + '@novu/dal': ^0.20.0-alpha.0 '@novu/ee-auth': ^0.19.0 - '@novu/node': ^0.19.0 - '@novu/shared': ^0.19.0 - '@novu/stateless': ^0.19.0 - '@novu/testing': ^0.19.0 + '@novu/node': ^0.20.0-alpha.0 + '@novu/shared': ^0.20.0-alpha.0 + '@novu/stateless': ^0.20.0-alpha.0 + '@novu/testing': ^0.20.0-alpha.0 '@sendgrid/mail': ^7.6.0 '@sentry/hub': ^7.40.0 '@sentry/node': ^7.40.0 @@ -314,16 +314,16 @@ importers: mocha: 8.4.0 nodemon: 2.0.22 sinon: 9.2.4 - ts-loader: 9.4.2_rggdtlzfqxxwxudp3onsqdyocm + ts-loader: 9.4.2_fejcc7gjbwtmwzggoernzojija ts-node: 10.9.1_wh2cnrlliuuxb2etxm2m3ttgna tsconfig-paths: 4.1.2 typescript: 4.9.5 apps/inbound-mail: specifiers: - '@novu/application-generic': ^0.19.0 - '@novu/shared': ^0.19.0 - '@novu/testing': ^0.19.0 + '@novu/application-generic': ^0.20.0-alpha.0 + '@novu/shared': ^0.20.0-alpha.0 + '@novu/testing': ^0.20.0-alpha.0 '@sentry/node': ^7.12.1 '@types/chai': ^4.2.11 '@types/express': ^4.17.8 @@ -391,7 +391,7 @@ importers: prettier: 2.8.7 sinon: 9.2.4 ts-jest: 27.1.5_4aafjbpmnrfjtrzkyohogv4jce - ts-loader: 9.4.2_rggdtlzfqxxwxudp3onsqdyocm + ts-loader: 9.4.2_fejcc7gjbwtmwzggoernzojija ts-node: 10.9.1_wh2cnrlliuuxb2etxm2m3ttgna tsconfig-paths: 4.1.2 typescript: 4.9.5 @@ -428,10 +428,10 @@ importers: '@mantine/notifications': ^5.7.1 '@mantine/prism': ^5.7.1 '@mantine/spotlight': ^5.7.1 - '@novu/dal': ^0.19.0 - '@novu/notification-center': ^0.19.0 - '@novu/shared': ^0.19.0 - '@novu/testing': ^0.19.0 + '@novu/dal': ^0.20.0-alpha.0 + '@novu/notification-center': ^0.20.0-alpha.0 + '@novu/shared': ^0.20.0-alpha.0 + '@novu/testing': ^0.20.0-alpha.0 '@segment/analytics-next': ^1.48.0 '@sentry/react': ^7.40.0 '@sentry/tracing': ^7.40.0 @@ -663,11 +663,11 @@ importers: '@nestjs/schematics': ^10.0.2 '@nestjs/terminus': ^10.0.1 '@nestjs/testing': ^10.2.2 - '@novu/application-generic': ^0.19.0 - '@novu/dal': ^0.19.0 - '@novu/shared': ^0.19.0 - '@novu/stateless': ^0.19.0 - '@novu/testing': ^0.19.0 + '@novu/application-generic': ^0.20.0-alpha.0 + '@novu/dal': ^0.20.0-alpha.0 + '@novu/shared': ^0.20.0-alpha.0 + '@novu/stateless': ^0.20.0-alpha.0 + '@novu/testing': ^0.20.0-alpha.0 '@sentry/node': ^7.66.0 '@types/chai': ^4.3.4 '@types/express': ^4.17.8 @@ -745,7 +745,7 @@ importers: sinon: 9.2.4 supertest: 6.3.3 ts-jest: 27.1.5_46h2nya7z2u3ruciguirufjle4 - ts-loader: 9.4.2_rggdtlzfqxxwxudp3onsqdyocm + ts-loader: 9.4.2_fejcc7gjbwtmwzggoernzojija ts-node: 10.9.1_wh2cnrlliuuxb2etxm2m3ttgna tsconfig-paths: 4.1.2 typescript: 4.9.5 @@ -760,10 +760,10 @@ importers: '@faker-js/faker': ^6.0.0 '@mantine/core': 4.2.12 '@mantine/hooks': 4.2.12 - '@novu/dal': ^0.19.0 - '@novu/notification-center': ^0.19.0 - '@novu/shared': ^0.19.0 - '@novu/testing': ^0.19.0 + '@novu/dal': ^0.20.0-alpha.0 + '@novu/notification-center': ^0.20.0-alpha.0 + '@novu/shared': ^0.20.0-alpha.0 + '@novu/testing': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 '@types/node': ^12.0.0 '@types/react': 17.0.62 @@ -799,7 +799,7 @@ importers: typescript: 4.9.5 web-vitals: ^0.2.4 webfontloader: ^1.6.28 - webpack: ^5.78.0 + webpack: 5.78.0 webpack-dev-server: 4.11.1 dependencies: '@ant-design/icons': 4.8.0_sfoxds7t5ydpegc3knd667wn6m @@ -839,19 +839,19 @@ importers: '@types/react': 17.0.62 '@types/react-dom': 17.0.20 '@types/react-router-dom': 5.3.3 - craco-antd: 1.19.0_3cpxdta34n6sqgppckwpiuxhwa + craco-antd: 1.19.0_qvigzdrsjhqnr5xqltdekrqjdi cross-env: 7.0.3 cypress: 12.17.3 cypress-intellij-reporter: 0.0.7 cypress-network-idle: 1.14.2 - html-webpack-plugin: 5.5.3_webpack@5.88.2 + html-webpack-plugin: 5.5.3_webpack@5.78.0 http-server: 0.13.0 jest: 27.5.1 less: 4.1.3 - less-loader: 4.1.0_less@4.1.3+webpack@5.88.2 + less-loader: 4.1.0_less@4.1.3+webpack@5.78.0 typescript: 4.9.5 - webpack: 5.88.2 - webpack-dev-server: 4.11.1_webpack@5.88.2 + webpack: 5.78.0 + webpack-dev-server: 4.11.1_webpack@5.78.0 apps/worker: specifiers: @@ -865,11 +865,11 @@ importers: '@nestjs/swagger': ^7.1.9 '@nestjs/terminus': ^10.0.1 '@nestjs/testing': ^10.2.2 - '@novu/application-generic': ^0.19.0 - '@novu/dal': ^0.19.0 - '@novu/shared': ^0.19.0 - '@novu/stateless': ^0.19.0 - '@novu/testing': ^0.19.0 + '@novu/application-generic': ^0.20.0-alpha.0 + '@novu/dal': ^0.20.0-alpha.0 + '@novu/shared': ^0.20.0-alpha.0 + '@novu/stateless': ^0.20.0-alpha.0 + '@novu/testing': ^0.20.0-alpha.0 '@sentry/node': ^7.40.0 '@sentry/tracing': ^7.40.0 '@types/bcrypt': ^3.0.0 @@ -963,7 +963,7 @@ importers: sinon: 9.2.4 superagent-defaults: 0.1.14_superagent@8.0.9 supertest: 5.0.0 - ts-loader: 9.4.2_rggdtlzfqxxwxudp3onsqdyocm + ts-loader: 9.4.2_fejcc7gjbwtmwzggoernzojija ts-node: 10.9.1_wh2cnrlliuuxb2etxm2m3ttgna tsconfig-paths: 4.1.2 typescript: 4.9.5 @@ -982,10 +982,10 @@ importers: '@nestjs/terminus': ^10.0.1 '@nestjs/testing': ^10.2.2 '@nestjs/websockets': ^10.2.2 - '@novu/application-generic': ^0.19.0 - '@novu/dal': ^0.19.0 - '@novu/shared': ^0.19.0 - '@novu/testing': ^0.19.0 + '@novu/application-generic': ^0.20.0-alpha.0 + '@novu/dal': ^0.20.0-alpha.0 + '@novu/shared': ^0.20.0-alpha.0 + '@novu/testing': ^0.20.0-alpha.0 '@sentry/node': ^7.30.0 '@socket.io/redis-adapter': ^7.2.0 '@types/chai': ^4.2.11 @@ -1068,95 +1068,17 @@ importers: nodemon: 2.0.22 prettier: 2.8.7 supertest: 6.3.3 - ts-loader: 9.4.2_rggdtlzfqxxwxudp3onsqdyocm + ts-loader: 9.4.2_fejcc7gjbwtmwzggoernzojija ts-node: 10.9.1_wh2cnrlliuuxb2etxm2m3ttgna tsconfig-paths: 4.1.2 typescript: 4.9.5 - enterprise/packages/auth: - specifiers: - '@nestjs/common': '>=9.3.x' - '@nestjs/jwt': '>=9' - '@nestjs/passport': 9.0.3 - '@novu/application-generic': ^0.19.0 - '@novu/dal': ^0.19.0 - '@novu/shared': ^0.19.0 - '@types/chai': ^4.2.11 - '@types/mocha': ^8.0.1 - '@types/node': ^14.6.0 - '@types/sinon': ^9.0.0 - chai: ^4.2.0 - cross-env: ^7.0.3 - mocha: ^8.1.1 - nodemon: ^2.0.3 - passport: 0.6.0 - passport-google-oauth: ^2.0.0 - passport-oauth2: ^1.6.1 - sinon: ^9.2.4 - ts-node: ~10.9.1 - typescript: 4.9.5 - dependencies: - '@nestjs/common': 10.2.2_atc7tu2sld2m3nk4hmwkqn6qde - '@nestjs/jwt': 10.1.0_@nestjs+common@10.2.2 - '@nestjs/passport': 9.0.3_kn4ljbedllcoqpuu4ifhphsdsu - '@novu/application-generic': link:../../../packages/application-generic - '@novu/dal': link:../../../libs/dal - '@novu/shared': link:../../../libs/shared - passport: 0.6.0 - passport-google-oauth: 2.0.0 - passport-oauth2: 1.7.0 - devDependencies: - '@types/chai': 4.3.4 - '@types/mocha': 8.2.3 - '@types/node': 14.18.42 - '@types/sinon': 9.0.11 - chai: 4.3.7 - cross-env: 7.0.3 - mocha: 8.4.0 - nodemon: 2.0.22 - sinon: 9.2.4 - ts-node: 10.9.1_wh2cnrlliuuxb2etxm2m3ttgna - typescript: 4.9.5 - - enterprise/packages/digest-schedule: - specifiers: - '@novu/shared': ^0.19.0 - '@types/chai': ^4.2.11 - '@types/mocha': ^8.0.1 - '@types/node': ^14.6.0 - '@types/sinon': ^9.0.0 - chai: ^4.2.0 - cross-env: ^7.0.3 - date-fns: ^2.29.2 - mocha: ^8.1.1 - nodemon: ^2.0.3 - rrule: ^2.7.2 - sinon: ^9.2.4 - ts-node: ~10.9.1 - typescript: 4.9.5 - dependencies: - '@novu/shared': link:../../../libs/shared - date-fns: 2.29.3 - rrule: 2.7.2 - devDependencies: - '@types/chai': 4.3.4 - '@types/mocha': 8.2.3 - '@types/node': 14.18.42 - '@types/sinon': 9.0.11 - chai: 4.3.7 - cross-env: 7.0.3 - mocha: 8.4.0 - nodemon: 2.0.22 - sinon: 9.2.4 - ts-node: 10.9.1_wh2cnrlliuuxb2etxm2m3ttgna - typescript: 4.9.5 - libs/dal: specifiers: '@aws-sdk/client-s3': ^3.382.0 '@aws-sdk/s3-request-presigner': ^3.382.0 '@faker-js/faker': ^6.0.0 - '@novu/shared': ^0.19.0 + '@novu/shared': ^0.20.0-alpha.0 '@sendgrid/mail': ^7.4.2 '@types/async': ^3.2.1 '@types/bluebird': ^3.5.30 @@ -1232,7 +1154,7 @@ importers: specifiers: '@commitlint/cli': ^17.0.0 '@commitlint/config-conventional': ^17.0.0 - '@novu/notification-center': ^0.19.0 + '@novu/notification-center': ^0.20.0-alpha.0 '@rollup/plugin-node-resolve': ^6.0.0 '@rollup/plugin-replace': ^5.0.2 '@types/iframe-resizer': ^3.5.8 @@ -1321,8 +1243,8 @@ importers: libs/testing: specifiers: '@faker-js/faker': ^6.0.0 - '@novu/dal': ^0.19.0 - '@novu/shared': ^0.19.0 + '@novu/dal': ^0.20.0-alpha.0 + '@novu/shared': ^0.20.0-alpha.0 '@types/async': ^3.2.1 '@types/bluebird': ^3.5.30 '@types/node': ^14.6.0 @@ -1400,52 +1322,52 @@ importers: '@nestjs/swagger': '>=6' '@nestjs/terminus': '>=10' '@nestjs/testing': '>=10' - '@novu/africas-talking': ^0.19.0 - '@novu/apns': ^0.19.0 - '@novu/burst-sms': ^0.19.0 - '@novu/clickatell': ^0.19.0 - '@novu/dal': ^0.19.0 - '@novu/discord': ^0.19.0 - '@novu/email-webhook': ^0.19.0 - '@novu/emailjs': ^0.19.0 - '@novu/expo': ^0.19.0 - '@novu/fcm': ^0.19.0 - '@novu/firetext': ^0.19.0 - '@novu/forty-six-elks': ^0.19.0 - '@novu/gupshup': ^0.19.0 - '@novu/infobip': ^0.19.0 - '@novu/kannel': ^0.19.0 - '@novu/mailersend': ^0.19.0 - '@novu/mailgun': ^0.19.0 - '@novu/mailjet': ^0.19.0 - '@novu/mandrill': ^0.19.0 - '@novu/maqsam': ^0.19.0 - '@novu/mattermost': ^0.19.0 - '@novu/ms-teams': ^0.19.0 - '@novu/netcore': ^0.19.0 - '@novu/nodemailer': ^0.19.0 - '@novu/one-signal': ^0.19.0 - '@novu/outlook365': ^0.19.0 - '@novu/plivo': ^0.19.0 - '@novu/plunk': ^0.19.0 - '@novu/postmark': ^0.19.0 - '@novu/push-webhook': ^0.19.0 - '@novu/resend': ^0.19.0 - '@novu/sendchamp': ^0.19.0 - '@novu/sendgrid': ^0.19.0 - '@novu/sendinblue': ^0.19.0 - '@novu/ses': ^0.19.0 - '@novu/shared': ^0.19.0 - '@novu/slack': ^0.19.0 - '@novu/sms-central': ^0.19.0 - '@novu/sms77': ^0.19.0 - '@novu/sns': ^0.19.0 - '@novu/sparkpost': ^0.19.0 - '@novu/stateless': ^0.19.0 - '@novu/telnyx': ^0.19.0 - '@novu/termii': ^0.19.0 - '@novu/testing': ^0.19.0 - '@novu/twilio': ^0.19.0 + '@novu/africas-talking': ^0.20.0-alpha.0 + '@novu/apns': ^0.20.0-alpha.0 + '@novu/burst-sms': ^0.20.0-alpha.0 + '@novu/clickatell': ^0.20.0-alpha.0 + '@novu/dal': ^0.20.0-alpha.0 + '@novu/discord': ^0.20.0-alpha.0 + '@novu/email-webhook': ^0.20.0-alpha.0 + '@novu/emailjs': ^0.20.0-alpha.0 + '@novu/expo': ^0.20.0-alpha.0 + '@novu/fcm': ^0.20.0-alpha.0 + '@novu/firetext': ^0.20.0-alpha.0 + '@novu/forty-six-elks': ^0.20.0-alpha.0 + '@novu/gupshup': ^0.20.0-alpha.0 + '@novu/infobip': ^0.20.0-alpha.0 + '@novu/kannel': ^0.20.0-alpha.0 + '@novu/mailersend': ^0.20.0-alpha.0 + '@novu/mailgun': ^0.20.0-alpha.0 + '@novu/mailjet': ^0.20.0-alpha.0 + '@novu/mandrill': ^0.20.0-alpha.0 + '@novu/maqsam': ^0.20.0-alpha.0 + '@novu/mattermost': ^0.20.0-alpha.0 + '@novu/ms-teams': ^0.20.0-alpha.0 + '@novu/netcore': ^0.20.0-alpha.0 + '@novu/nodemailer': ^0.20.0-alpha.0 + '@novu/one-signal': ^0.20.0-alpha.0 + '@novu/outlook365': ^0.20.0-alpha.0 + '@novu/plivo': ^0.20.0-alpha.0 + '@novu/plunk': ^0.20.0-alpha.0 + '@novu/postmark': ^0.20.0-alpha.0 + '@novu/push-webhook': ^0.20.0-alpha.0 + '@novu/resend': ^0.20.0-alpha.0 + '@novu/sendchamp': ^0.20.0-alpha.0 + '@novu/sendgrid': ^0.20.0-alpha.0 + '@novu/sendinblue': ^0.20.0-alpha.0 + '@novu/ses': ^0.20.0-alpha.0 + '@novu/shared': ^0.20.0-alpha.0 + '@novu/slack': ^0.20.0-alpha.0 + '@novu/sms-central': ^0.20.0-alpha.0 + '@novu/sms77': ^0.20.0-alpha.0 + '@novu/sns': ^0.20.0-alpha.0 + '@novu/sparkpost': ^0.20.0-alpha.0 + '@novu/stateless': ^0.20.0-alpha.0 + '@novu/telnyx': ^0.20.0-alpha.0 + '@novu/termii': ^0.20.0-alpha.0 + '@novu/testing': ^0.20.0-alpha.0 + '@novu/twilio': ^0.20.0-alpha.0 '@sentry/node': ^7.12.1 '@taskforcesh/bullmq-pro': 5.1.14 '@types/analytics-node': ^3.1.9 @@ -1464,6 +1386,7 @@ importers: jest: ^27.1.0 launchdarkly-node-server-sdk: ^7.0.1 lodash: ^4.17.15 + mixpanel: ^0.17.0 nestjs-pino: ^3.4.0 newrelic: ^9 node-fetch: ^3.2.10 @@ -1547,6 +1470,7 @@ importers: ioredis: 5.3.1 launchdarkly-node-server-sdk: 7.0.1 lodash: 4.17.21 + mixpanel: 0.17.0 nestjs-pino: 3.4.0_63skzxoikz7i6kgl2v2i7k2p4y newrelic: 9.15.0 node-fetch: 3.3.1 @@ -1577,7 +1501,7 @@ importers: packages/cli: specifiers: - '@novu/shared': ^0.19.0 + '@novu/shared': ^0.20.0-alpha.0 '@types/analytics-node': ^3.1.9 '@types/configstore': ^5.0.1 '@types/inquirer': ^8.2.0 @@ -1626,7 +1550,7 @@ importers: packages/client: specifiers: - '@novu/shared': ^0.19.0 + '@novu/shared': ^0.20.0-alpha.0 '@types/jest': 29.5.2 '@types/node': ^14.6.0 codecov: ^3.5.0 @@ -1653,8 +1577,8 @@ importers: specifiers: '@babel/preset-env': ^7.13.15 '@babel/preset-typescript': ^7.13.0 - '@novu/client': ^0.19.0 - '@novu/shared': ^0.19.0 + '@novu/client': ^0.20.0-alpha.0 + '@novu/shared': ^0.20.0-alpha.0 '@tanstack/query-core': ^4.15.1 '@types/jest': ^29.2.3 '@types/node': ^14.6.0 @@ -1684,7 +1608,7 @@ importers: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 '@nestjs/common': ^8.2.0 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': 29.5.2 codecov: ^3.5.0 cspell: ^4.1.0 @@ -1721,7 +1645,7 @@ importers: packages/node: specifiers: - '@novu/shared': ^0.19.0 + '@novu/shared': ^0.20.0-alpha.0 '@sendgrid/mail': ^7.4.6 '@types/jest': 29.5.2 '@types/lodash.get': ^4.4.6 @@ -1772,8 +1696,8 @@ importers: '@emotion/styled': ^11.6.0 '@mantine/core': ^5.7.1 '@mantine/hooks': ^5.7.1 - '@novu/client': ^0.19.0 - '@novu/shared': ^0.19.0 + '@novu/client': ^0.20.0-alpha.0 + '@novu/shared': ^0.20.0-alpha.0 '@storybook/addon-actions': ^7.4.2 '@storybook/addon-essentials': ^7.4.2 '@storybook/addon-interactions': ^7.4.2 @@ -1887,7 +1811,7 @@ importers: '@angular/core': ~15.2.0 '@angular/platform-browser': ~15.2.0 '@angular/platform-browser-dynamic': ~15.2.0 - '@novu/notification-center': ^0.19.0 + '@novu/notification-center': ^0.20.0-alpha.0 '@types/jasmine': ~4.3.0 jasmine-core: ~4.6.0 karma: ~6.4.0 @@ -1930,7 +1854,7 @@ importers: specifiers: '@emotion/css': ^11.10.5 '@novu/floating-vue': ^2.0.3 - '@novu/notification-center': ^0.19.0 + '@novu/notification-center': ^0.20.0-alpha.0 '@rushstack/eslint-patch': ^1.1.4 '@types/node': ^18.11.12 '@vitejs/plugin-vue': ^4.0.0 @@ -2013,7 +1937,7 @@ importers: providers/africas-talking: specifiers: '@istanbuljs/nyc-config-typescript': ~1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ~29.5.0 africastalking: ^0.6.2 cspell: ~6.19.2 @@ -2044,7 +1968,7 @@ importers: providers/apns: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@parse/node-apn': ^5.2.3 '@types/jest': ^29.5.0 codecov: ^3.5.0 @@ -2081,7 +2005,7 @@ importers: providers/burst-sms: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 axios: ^1.3.3 codecov: ^3.5.0 @@ -2124,7 +2048,7 @@ importers: providers/clickatell: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 axios: ^1.3.3 codecov: ^3.5.0 @@ -2161,7 +2085,7 @@ importers: providers/discord: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 axios: ^1.3.3 codecov: ^3.5.0 @@ -2231,7 +2155,7 @@ importers: providers/emailjs: specifiers: '@istanbuljs/nyc-config-typescript': 1.0.2 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': 29.5.2 codecov: ^3.5.0 cspell: ^4.1.0 @@ -2270,7 +2194,7 @@ importers: providers/expo: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 codecov: ^3.5.0 cspell: ^4.1.0 @@ -2309,7 +2233,7 @@ importers: providers/fcm: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 '@types/mocha': ^8.0.1 codecov: ^3.5.0 @@ -2355,7 +2279,7 @@ importers: specifiers: '@babel/preset-env': ^7.13.15 '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 '@types/uuid': ^8.3.4 codecov: ^3.5.0 @@ -2399,7 +2323,7 @@ importers: providers/forty-six-elks: specifiers: '@istanbuljs/nyc-config-typescript': ~1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ~29.5.0 axios: ^1.3.4 cspell: ~6.19.2 @@ -2431,7 +2355,7 @@ importers: specifiers: '@babel/preset-env': ^7.13.15 '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 codecov: ^3.5.0 cspell: ^4.1.0 @@ -2472,7 +2396,7 @@ importers: specifiers: '@infobip-api/sdk': ^0.2.0 '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 codecov: ^3.5.0 cspell: ^4.1.0 @@ -2510,7 +2434,7 @@ importers: providers/kannel: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 axios: ^0.27.2 codecov: ^3.5.0 @@ -2549,7 +2473,7 @@ importers: providers/mailersend: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 codecov: ^3.5.0 cspell: ^4.1.0 @@ -2588,7 +2512,7 @@ importers: providers/mailgun: specifiers: '@istanbuljs/nyc-config-typescript': 1.0.2 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': 29.5.2 codecov: ^3.5.0 cspell: ^4.1.0 @@ -2631,7 +2555,7 @@ importers: providers/mailjet: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': 29.5.2 '@types/node-mailjet': ^3.3.7 codecov: ^3.5.0 @@ -2673,7 +2597,7 @@ importers: specifiers: '@istanbuljs/nyc-config-typescript': 1.0.2 '@mailchimp/mailchimp_transactional': ^1.0.46 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': 29.5.2 codecov: ^3.5.0 cspell: ^4.1.0 @@ -2715,7 +2639,7 @@ importers: providers/maqsam: specifiers: '@istanbuljs/nyc-config-typescript': ~1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ~29.5.0 axios: ^1.3.4 cspell: ~6.19.2 @@ -2750,7 +2674,7 @@ importers: providers/mattermost: specifiers: '@istanbuljs/nyc-config-typescript': ~1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ~29.5.0 axios: ^1.3.3 cspell: ~6.19.2 @@ -2822,7 +2746,7 @@ importers: providers/netcore: specifiers: '@istanbuljs/nyc-config-typescript': 1.0.2 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': 29.5.2 codecov: ^3.5.0 cspell: ^4.1.0 @@ -2861,7 +2785,7 @@ importers: providers/nexmo: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': 29.5.2 '@vonage/server-sdk': ^2.10.10 codecov: ^3.5.0 @@ -2900,7 +2824,7 @@ importers: providers/nodemailer: specifiers: '@istanbuljs/nyc-config-typescript': 1.0.2 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': 29.5.2 '@types/nodemailer': ^6.4.4 codecov: ^3.5.0 @@ -2941,7 +2865,7 @@ importers: providers/one-signal: specifiers: '@istanbuljs/nyc-config-typescript': ~1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ~29.5.0 cspell: ~6.19.2 jest: ~27.5.1 @@ -2972,7 +2896,7 @@ importers: providers/outlook365: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 codecov: ^3.5.0 cspell: ^4.1.0 @@ -3011,7 +2935,7 @@ importers: providers/plivo: specifiers: '@istanbuljs/nyc-config-typescript': 1.0.2 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': 29.5.2 codecov: ^3.5.0 cspell: ^4.1.0 @@ -3050,7 +2974,7 @@ importers: providers/plunk: specifiers: '@istanbuljs/nyc-config-typescript': ~1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@plunk/node': 2.0.0 '@types/jest': ~27.5.2 cspell: ~6.19.2 @@ -3081,7 +3005,7 @@ importers: providers/postmark: specifiers: '@istanbuljs/nyc-config-typescript': 1.0.2 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': 29.5.2 axios: ^1.3.6 codecov: ^3.5.0 @@ -3122,7 +3046,7 @@ importers: providers/push-webhook: specifiers: '@istanbuljs/nyc-config-typescript': ~1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ~29.5.0 axios: ^1.3.5 cspell: ~6.19.2 @@ -3153,7 +3077,7 @@ importers: providers/resend: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 codecov: ^3.5.0 cspell: ^4.1.0 @@ -3192,7 +3116,7 @@ importers: providers/sendchamp: specifiers: '@istanbuljs/nyc-config-typescript': ~1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ~27.5.2 axios: ^1.4.0 cspell: ~6.19.2 @@ -3223,7 +3147,7 @@ importers: providers/sendgrid: specifiers: '@istanbuljs/nyc-config-typescript': 1.0.2 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@sendgrid/mail': ^7.4.6 '@types/jest': 29.5.2 codecov: ^3.5.0 @@ -3262,7 +3186,7 @@ importers: providers/sendinblue: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@sendinblue/client': ^3.0.1 '@types/jest': 29.5.2 codecov: ^3.5.0 @@ -3302,7 +3226,7 @@ importers: specifiers: '@aws-sdk/client-ses': 3.382.0 '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': 29.5.2 codecov: ^3.5.0 cspell: ^4.1.0 @@ -3342,7 +3266,7 @@ importers: providers/slack: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 axios: ^1.3.3 codecov: ^3.5.0 @@ -3413,7 +3337,7 @@ importers: specifiers: '@babel/preset-env': ^7.13.15 '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': 29.5.2 codecov: ^3.5.0 cspell: ^4.1.0 @@ -3456,7 +3380,7 @@ importers: specifiers: '@aws-sdk/client-sns': ^3.382.0 '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': 29.5.2 codecov: ^3.5.0 cspell: ^4.1.0 @@ -3494,7 +3418,7 @@ importers: providers/sparkpost: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 '@types/sparkpost': ^2.1.5 codecov: ^3.5.0 @@ -3535,7 +3459,7 @@ importers: providers/telnyx: specifiers: '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 codecov: ^3.5.0 cspell: ^4.1.0 @@ -3575,7 +3499,7 @@ importers: specifiers: '@babel/preset-env': ^7.13.15 '@istanbuljs/nyc-config-typescript': ^1.0.1 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': ^29.5.0 codecov: ^3.5.0 cspell: ^4.1.0 @@ -3615,7 +3539,7 @@ importers: providers/twilio: specifiers: '@istanbuljs/nyc-config-typescript': 1.0.2 - '@novu/stateless': ^0.19.0 + '@novu/stateless': ^0.20.0-alpha.0 '@types/jest': 29.5.2 codecov: ^3.5.0 cspell: ^4.1.0 @@ -5625,7 +5549,7 @@ packages: dependencies: '@babel/core': 7.20.12 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.22.5 '@babel/helper-member-expression-to-functions': 7.21.0 '@babel/helper-optimise-call-expression': 7.18.6 @@ -5811,13 +5735,6 @@ packages: '@babel/template': 7.22.15 '@babel/types': 7.22.19 - /@babel/helper-hoist-variables/7.18.6: - resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.19 - dev: true - /@babel/helper-hoist-variables/7.22.5: resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} @@ -5870,6 +5787,20 @@ packages: transitivePeerDependencies: - supports-color + /@babel/helper-module-transforms/7.22.20_@babel+core@7.20.12: + resolution: {integrity: sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/helper-validator-identifier': 7.22.20 + dev: true + /@babel/helper-module-transforms/7.22.20_@babel+core@7.22.11: resolution: {integrity: sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A==} engines: {node: '>=6.9.0'} @@ -5925,9 +5856,9 @@ packages: dependencies: '@babel/core': 7.20.12 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-wrap-function': 7.20.5 - '@babel/types': 7.22.11 + '@babel/types': 7.22.19 transitivePeerDependencies: - supports-color dev: true @@ -6185,7 +6116,7 @@ packages: '@babel/core': ^7.13.0 dependencies: '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 '@babel/plugin-proposal-optional-chaining': 7.21.0_@babel+core@7.20.12 dev: true @@ -6220,7 +6151,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.20.12 - '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-remap-async-to-generator': 7.18.9_@babel+core@7.20.12 '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.20.12 @@ -6541,7 +6472,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.20.12 - '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-create-class-features-plugin': 7.21.4_@babel+core@7.20.12 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.20.12 @@ -7226,8 +7157,8 @@ packages: dependencies: '@babel/core': 7.20.12 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-compilation-targets': 7.22.10 - '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-compilation-targets': 7.22.15 + '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.22.5 '@babel/helper-optimise-call-expression': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 @@ -7655,9 +7586,9 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.20.12 - '@babel/helper-module-transforms': 7.22.9_@babel+core@7.20.12 + '@babel/helper-module-transforms': 7.22.20_@babel+core@7.20.12 '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-simple-access': 7.20.2 + '@babel/helper-simple-access': 7.22.5 dev: true /@babel/plugin-transform-modules-commonjs/7.21.2_@babel+core@7.22.11: @@ -7689,10 +7620,10 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.20.12 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-module-transforms': 7.22.9_@babel+core@7.20.12 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-module-transforms': 7.22.20_@babel+core@7.20.12 '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-validator-identifier': 7.19.1 + '@babel/helper-validator-identifier': 7.22.20 dev: true /@babel/plugin-transform-modules-systemjs/7.20.11_@babel+core@7.22.11: @@ -13103,26 +13034,6 @@ packages: - webpack-cli dev: true - /@nestjs/common/10.2.2_atc7tu2sld2m3nk4hmwkqn6qde: - resolution: {integrity: sha512-TCOJK2K4FDT3GxFfURjngnjBewS/hizKNFSLBXtX4TTQm0dVQOtESnnVdP14sEiPM6suuWlrGnXW9UDqItGWiQ==} - peerDependencies: - class-transformer: '*' - class-validator: '*' - reflect-metadata: ^0.1.12 - rxjs: ^7.1.0 - peerDependenciesMeta: - class-transformer: - optional: true - class-validator: - optional: true - dependencies: - iterare: 1.2.1 - reflect-metadata: 0.1.13 - rxjs: 7.8.1 - tslib: 2.6.2 - uid: 2.0.2 - dev: false - /@nestjs/common/10.2.2_j3td4gnlgk75ora6o6suo62byy: resolution: {integrity: sha512-TCOJK2K4FDT3GxFfURjngnjBewS/hizKNFSLBXtX4TTQm0dVQOtESnnVdP14sEiPM6suuWlrGnXW9UDqItGWiQ==} peerDependencies: @@ -13352,16 +13263,6 @@ packages: passport: 0.6.0 dev: false - /@nestjs/passport/9.0.3_kn4ljbedllcoqpuu4ifhphsdsu: - resolution: {integrity: sha512-HplSJaimEAz1IOZEu+pdJHHJhQyBOPAYWXYHfAPQvRqWtw4FJF1VXl1Qtk9dcXQX1eKytDtH+qBzNQc19GWNEg==} - peerDependencies: - '@nestjs/common': ^8.0.0 || ^9.0.0 - passport: ^0.4.0 || ^0.5.0 || ^0.6.0 - dependencies: - '@nestjs/common': 10.2.2_atc7tu2sld2m3nk4hmwkqn6qde - passport: 0.6.0 - dev: false - /@nestjs/platform-express/10.2.2_h33h3l6i5mruhsbo3bha6vy2fi: resolution: {integrity: sha512-g5AeXgPQrVm62JOl9FXk0w3Tq1tD4f6ouGikLYs/Aahy0q/Z2HNP9NjXZYpqcjHrpafPYnc3bfBuUwedKW1oHg==} peerDependencies: @@ -14896,46 +14797,7 @@ packages: undici: 5.23.0 dev: false - /@pmmmwh/react-refresh-webpack-plugin/0.5.10_2kpgiq4mtlettjqmb64nc4esa4: - resolution: {integrity: sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==} - engines: {node: '>= 10.13'} - peerDependencies: - '@types/webpack': 4.x || 5.x - react-refresh: '>=0.10.0 <1.0.0' - sockjs-client: ^1.4.0 - type-fest: '>=0.17.0 <4.0.0' - webpack: '>=4.43.0 <6.0.0' - webpack-dev-server: 3.x || 4.x - webpack-hot-middleware: 2.x - webpack-plugin-serve: 0.x || 1.x - peerDependenciesMeta: - '@types/webpack': - optional: true - sockjs-client: - optional: true - type-fest: - optional: true - webpack-dev-server: - optional: true - webpack-hot-middleware: - optional: true - webpack-plugin-serve: - optional: true - dependencies: - ansi-html-community: 0.0.8 - common-path-prefix: 3.0.0 - core-js-pure: 3.30.0 - error-stack-parser: 2.1.4 - find-up: 5.0.0 - html-entities: 2.3.3 - loader-utils: 2.0.4 - react-refresh: 0.11.0 - schema-utils: 3.3.0 - source-map: 0.7.4 - webpack: 5.88.2_w67ycjwq2niq3jlxgktvf5aow4 - dev: true - - /@pmmmwh/react-refresh-webpack-plugin/0.5.10_aaw3q2lthy6zceet2ugelootve: + /@pmmmwh/react-refresh-webpack-plugin/0.5.10_kwexxzmt7sjpqjleraytwi4jvu: resolution: {integrity: sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==} engines: {node: '>= 10.13'} peerDependencies: @@ -14971,10 +14833,10 @@ packages: react-refresh: 0.11.0 schema-utils: 3.3.0 source-map: 0.7.4 - webpack: 5.88.2 - webpack-dev-server: 4.11.1_webpack@5.88.2 + webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau + webpack-dev-server: 4.11.1_webpack@5.78.0 - /@pmmmwh/react-refresh-webpack-plugin/0.5.10_kwexxzmt7sjpqjleraytwi4jvu: + /@pmmmwh/react-refresh-webpack-plugin/0.5.10_u6erfrjwlwtxmtcht2oxrgpjdy: resolution: {integrity: sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==} engines: {node: '>= 10.13'} peerDependencies: @@ -15010,8 +14872,8 @@ packages: react-refresh: 0.11.0 schema-utils: 3.3.0 source-map: 0.7.4 - webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau - webpack-dev-server: 4.11.1_webpack@5.78.0 + webpack: 5.78.0_w67ycjwq2niq3jlxgktvf5aow4 + dev: true /@pmmmwh/react-refresh-webpack-plugin/0.5.10_wik2mtfr6ctscewm36ogu5s3j4: resolution: {integrity: sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==} @@ -18791,31 +18653,31 @@ packages: '@swc/core': 1.3.49 '@types/node': 16.11.7 '@types/semver': 7.3.13 - babel-loader: 9.1.2_7nqnrdwtl44yxbgqpombxtkqjy + babel-loader: 9.1.2_5nlhjoewt6bwtyl7wlqhrgzuxm babel-plugin-named-exports-order: 0.0.2 browser-assert: 1.2.1 case-sensitive-paths-webpack-plugin: 2.4.0 constants-browserify: 1.0.0 - css-loader: 6.7.3_webpack@5.88.2 + css-loader: 6.7.3_webpack@5.78.0 express: 4.18.2 - fork-ts-checker-webpack-plugin: 8.0.0_rggdtlzfqxxwxudp3onsqdyocm + fork-ts-checker-webpack-plugin: 8.0.0_fejcc7gjbwtmwzggoernzojija fs-extra: 11.1.1 - html-webpack-plugin: 5.5.3_webpack@5.88.2 + html-webpack-plugin: 5.5.3_webpack@5.78.0 path-browserify: 1.0.1 process: 0.11.10 react: 17.0.2 react-dom: 17.0.2_react@17.0.2 semver: 7.5.4 - style-loader: 3.3.2_webpack@5.88.2 - swc-loader: 0.2.3_zpsjxul5gtyjq5vu5uxru46xsq - terser-webpack-plugin: 5.3.9_2pue2hesc4kyarjonhtsco4zxm + style-loader: 3.3.2_webpack@5.78.0 + swc-loader: 0.2.3_wvz5dn57l37py5yhasbcqqk6hi + terser-webpack-plugin: 5.3.9_dnqqsr3phzjhopay4d6e5ziqz4 ts-dedent: 2.2.0 typescript: 4.9.5 url: 0.11.0 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.88.2_w67ycjwq2niq3jlxgktvf5aow4 - webpack-dev-middleware: 6.1.1_webpack@5.88.2 + webpack: 5.78.0_w67ycjwq2niq3jlxgktvf5aow4 + webpack-dev-middleware: 6.1.1_webpack@5.78.0 webpack-hot-middleware: 2.25.3 webpack-virtual-modules: 0.5.0 transitivePeerDependencies: @@ -18859,31 +18721,31 @@ packages: '@swc/core': 1.3.49 '@types/node': 16.11.7 '@types/semver': 7.3.13 - babel-loader: 9.1.2_7nqnrdwtl44yxbgqpombxtkqjy + babel-loader: 9.1.2_5nlhjoewt6bwtyl7wlqhrgzuxm babel-plugin-named-exports-order: 0.0.2 browser-assert: 1.2.1 case-sensitive-paths-webpack-plugin: 2.4.0 constants-browserify: 1.0.0 - css-loader: 6.7.3_webpack@5.88.2 + css-loader: 6.7.3_webpack@5.78.0 express: 4.18.2 - fork-ts-checker-webpack-plugin: 8.0.0_rggdtlzfqxxwxudp3onsqdyocm + fork-ts-checker-webpack-plugin: 8.0.0_fejcc7gjbwtmwzggoernzojija fs-extra: 11.1.1 - html-webpack-plugin: 5.5.3_webpack@5.88.2 + html-webpack-plugin: 5.5.3_webpack@5.78.0 path-browserify: 1.0.1 process: 0.11.10 react: 17.0.2 react-dom: 17.0.2_react@17.0.2 semver: 7.5.4 - style-loader: 3.3.2_webpack@5.88.2 - swc-loader: 0.2.3_zpsjxul5gtyjq5vu5uxru46xsq - terser-webpack-plugin: 5.3.9_2pue2hesc4kyarjonhtsco4zxm + style-loader: 3.3.2_webpack@5.78.0 + swc-loader: 0.2.3_wvz5dn57l37py5yhasbcqqk6hi + terser-webpack-plugin: 5.3.9_dnqqsr3phzjhopay4d6e5ziqz4 ts-dedent: 2.2.0 typescript: 4.9.5 url: 0.11.0 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.88.2_u5c4qderjagc6tepfyiby6xhau - webpack-dev-middleware: 6.1.1_webpack@5.88.2 + webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau + webpack-dev-middleware: 6.1.1_webpack@5.78.0 webpack-hot-middleware: 2.25.3 webpack-virtual-modules: 0.5.0 transitivePeerDependencies: @@ -19267,12 +19129,12 @@ packages: '@babel/core': 7.22.11 '@babel/preset-flow': 7.22.15_@babel+core@7.22.11 '@babel/preset-react': 7.22.15_@babel+core@7.22.11 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.10_2kpgiq4mtlettjqmb64nc4esa4 + '@pmmmwh/react-refresh-webpack-plugin': 0.5.10_u6erfrjwlwtxmtcht2oxrgpjdy '@storybook/core-webpack': 7.4.2 '@storybook/docs-tools': 7.4.2 '@storybook/node-logger': 7.4.2 '@storybook/react': 7.4.2_jgxnvbe4faw3ohf4h6p42qq6oy - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0_rggdtlzfqxxwxudp3onsqdyocm + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0_fejcc7gjbwtmwzggoernzojija '@types/node': 16.11.7 '@types/semver': 7.3.13 babel-plugin-add-react-displayname: 0.0.5 @@ -19283,7 +19145,7 @@ packages: react-refresh: 0.11.0 semver: 7.5.4 typescript: 4.9.5 - webpack: 5.88.2_w67ycjwq2niq3jlxgktvf5aow4 + webpack: 5.78.0_w67ycjwq2niq3jlxgktvf5aow4 transitivePeerDependencies: - '@swc/core' - '@types/webpack' @@ -19316,12 +19178,12 @@ packages: '@babel/core': 7.22.11 '@babel/preset-flow': 7.22.15_@babel+core@7.22.11 '@babel/preset-react': 7.22.15_@babel+core@7.22.11 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.10_aaw3q2lthy6zceet2ugelootve + '@pmmmwh/react-refresh-webpack-plugin': 0.5.10_kwexxzmt7sjpqjleraytwi4jvu '@storybook/core-webpack': 7.4.2 '@storybook/docs-tools': 7.4.2 '@storybook/node-logger': 7.4.2 '@storybook/react': 7.4.2_jgxnvbe4faw3ohf4h6p42qq6oy - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0_rggdtlzfqxxwxudp3onsqdyocm + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0_fejcc7gjbwtmwzggoernzojija '@types/node': 16.11.7 '@types/semver': 7.3.13 babel-plugin-add-react-displayname: 0.0.5 @@ -19332,7 +19194,7 @@ packages: react-refresh: 0.11.0 semver: 7.5.4 typescript: 4.9.5 - webpack: 5.88.2_u5c4qderjagc6tepfyiby6xhau + webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau transitivePeerDependencies: - '@swc/core' - '@types/webpack' @@ -19389,25 +19251,6 @@ packages: - supports-color dev: true - /@storybook/react-docgen-typescript-plugin/1.0.6--canary.9.0c3f3b7.0_rggdtlzfqxxwxudp3onsqdyocm: - resolution: {integrity: sha512-KUqXC3oa9JuQ0kZJLBhVdS4lOneKTOopnNBK4tUAgoxWQ3u/IjzdueZjFr7gyBrXMoU6duutk3RQR9u8ZpYJ4Q==} - peerDependencies: - typescript: '>= 4.x' - webpack: '>= 4' - dependencies: - debug: 4.3.4 - endent: 2.1.0 - find-cache-dir: 3.3.2 - flat-cache: 3.1.0 - micromatch: 4.0.5 - react-docgen-typescript: 2.2.2_typescript@4.9.5 - tslib: 2.6.2 - typescript: 4.9.5 - webpack: 5.88.2_u5c4qderjagc6tepfyiby6xhau - transitivePeerDependencies: - - supports-color - dev: true - /@storybook/react-dom-shim/7.4.2_sfoxds7t5ydpegc3knd667wn6m: resolution: {integrity: sha512-9Ae2As3Hf//mdFEAv58VgDbi9R5JRGne8Ai6Vspc5FZMCJIjr5kullckBi3n9uKRg2L8V7wjDRK8Cql2tEr0Yg==} peerDependencies: @@ -21583,24 +21426,28 @@ packages: dependencies: '@webassemblyjs/helper-numbers': 1.11.5 '@webassemblyjs/helper-wasm-bytecode': 1.11.5 + dev: true /@webassemblyjs/floating-point-hex-parser/1.11.1: resolution: {integrity: sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==} /@webassemblyjs/floating-point-hex-parser/1.11.5: resolution: {integrity: sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ==} + dev: true /@webassemblyjs/helper-api-error/1.11.1: resolution: {integrity: sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==} /@webassemblyjs/helper-api-error/1.11.5: resolution: {integrity: sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA==} + dev: true /@webassemblyjs/helper-buffer/1.11.1: resolution: {integrity: sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==} /@webassemblyjs/helper-buffer/1.11.5: resolution: {integrity: sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg==} + dev: true /@webassemblyjs/helper-numbers/1.11.1: resolution: {integrity: sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==} @@ -21615,12 +21462,14 @@ packages: '@webassemblyjs/floating-point-hex-parser': 1.11.5 '@webassemblyjs/helper-api-error': 1.11.5 '@xtuc/long': 4.2.2 + dev: true /@webassemblyjs/helper-wasm-bytecode/1.11.1: resolution: {integrity: sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==} /@webassemblyjs/helper-wasm-bytecode/1.11.5: resolution: {integrity: sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA==} + dev: true /@webassemblyjs/helper-wasm-section/1.11.1: resolution: {integrity: sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==} @@ -21637,6 +21486,7 @@ packages: '@webassemblyjs/helper-buffer': 1.11.5 '@webassemblyjs/helper-wasm-bytecode': 1.11.5 '@webassemblyjs/wasm-gen': 1.11.5 + dev: true /@webassemblyjs/ieee754/1.11.1: resolution: {integrity: sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==} @@ -21647,6 +21497,7 @@ packages: resolution: {integrity: sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg==} dependencies: '@xtuc/ieee754': 1.2.0 + dev: true /@webassemblyjs/leb128/1.11.1: resolution: {integrity: sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==} @@ -21657,12 +21508,14 @@ packages: resolution: {integrity: sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ==} dependencies: '@xtuc/long': 4.2.2 + dev: true /@webassemblyjs/utf8/1.11.1: resolution: {integrity: sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==} /@webassemblyjs/utf8/1.11.5: resolution: {integrity: sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ==} + dev: true /@webassemblyjs/wasm-edit/1.11.1: resolution: {integrity: sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==} @@ -21687,6 +21540,7 @@ packages: '@webassemblyjs/wasm-opt': 1.11.5 '@webassemblyjs/wasm-parser': 1.11.5 '@webassemblyjs/wast-printer': 1.11.5 + dev: true /@webassemblyjs/wasm-gen/1.11.1: resolution: {integrity: sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==} @@ -21705,6 +21559,7 @@ packages: '@webassemblyjs/ieee754': 1.11.5 '@webassemblyjs/leb128': 1.11.5 '@webassemblyjs/utf8': 1.11.5 + dev: true /@webassemblyjs/wasm-opt/1.11.1: resolution: {integrity: sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==} @@ -21721,6 +21576,7 @@ packages: '@webassemblyjs/helper-buffer': 1.11.5 '@webassemblyjs/wasm-gen': 1.11.5 '@webassemblyjs/wasm-parser': 1.11.5 + dev: true /@webassemblyjs/wasm-parser/1.11.1: resolution: {integrity: sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==} @@ -21741,6 +21597,7 @@ packages: '@webassemblyjs/ieee754': 1.11.5 '@webassemblyjs/leb128': 1.11.5 '@webassemblyjs/utf8': 1.11.5 + dev: true /@webassemblyjs/wast-printer/1.11.1: resolution: {integrity: sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==} @@ -21753,6 +21610,7 @@ packages: dependencies: '@webassemblyjs/ast': 1.11.5 '@xtuc/long': 4.2.2 + dev: true /@webpack-cli/configtest/2.1.1_g5qtztmog4wkrsqodniltwrmwa: resolution: {integrity: sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==} @@ -23079,21 +22937,7 @@ packages: webpack: 5.82.1_w67ycjwq2niq3jlxgktvf5aow4 dev: true - /babel-loader/8.3.0_vymqytky47ah5a633vjyq2m3sy: - resolution: {integrity: sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==} - engines: {node: '>= 8.9'} - peerDependencies: - '@babel/core': ^7.0.0 - webpack: '>=2' - dependencies: - '@babel/core': 7.21.4 - find-cache-dir: 3.3.2 - loader-utils: 2.0.4 - make-dir: 3.1.0 - schema-utils: 2.7.1 - webpack: 5.88.2 - - /babel-loader/9.1.2_7nqnrdwtl44yxbgqpombxtkqjy: + /babel-loader/9.1.2_5nlhjoewt6bwtyl7wlqhrgzuxm: resolution: {integrity: sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==} engines: {node: '>= 14.15.0'} peerDependencies: @@ -23103,7 +22947,7 @@ packages: '@babel/core': 7.22.11 find-cache-dir: 3.3.2 schema-utils: 4.0.0 - webpack: 5.88.2_u5c4qderjagc6tepfyiby6xhau + webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau dev: true /babel-loader/9.1.2_vbwv3zr3kwaf4v2iytwakh6feu: @@ -23775,7 +23619,7 @@ packages: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} /buffer-equal-constant-time/1.0.1: - resolution: {integrity: sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=} + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} dev: false /buffer-from/1.1.2: @@ -24868,7 +24712,7 @@ packages: resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} /concat-map/0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} /concat-stream/1.6.2: resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} @@ -25323,7 +25167,7 @@ packages: - supports-color dev: true - /craco-antd/1.19.0_3cpxdta34n6sqgppckwpiuxhwa: + /craco-antd/1.19.0_qvigzdrsjhqnr5xqltdekrqjdi: resolution: {integrity: sha512-MpbF2LQxb/POiR003oOkuAhHwpyRx1w5opyg7SA4/2og/FMRR/2oca/eqKYQ7vre2dOpt64gkQ5xWETktHWCQQ==} peerDependencies: '@craco/craco': ^5.5.0 @@ -25333,14 +25177,14 @@ packages: '@craco/craco': 7.1.0_s2wp4rvkjiflatocn7n5b6ts7m antd: 4.24.8_sfoxds7t5ydpegc3knd667wn6m babel-plugin-import: 1.13.6 - craco-less: 1.17.0_ir2lcvgs2do7bdjsff3lxwyn5m + craco-less: 1.17.0_3scu73ucad4ffp3ycq2in6zsua less-vars-to-js: 1.3.0 react-scripts: 5.0.1_2xn6hao67zmtzyfac7qsgeyxpu transitivePeerDependencies: - webpack dev: true - /craco-less/1.17.0_ir2lcvgs2do7bdjsff3lxwyn5m: + /craco-less/1.17.0_3scu73ucad4ffp3ycq2in6zsua: resolution: {integrity: sha512-G+GPEKPPKiSvYDsnQWuj1C4CIuaY8w+iHvULHkNf5QWLE0LkPfSRf3frhRDJjFxtkThpLPSLjWndD9kx8bCWzw==} peerDependencies: '@craco/craco': ^5.5.0 @@ -25348,7 +25192,7 @@ packages: dependencies: '@craco/craco': 7.1.0_s2wp4rvkjiflatocn7n5b6ts7m less: 3.13.1 - less-loader: 6.2.0_webpack@5.88.2 + less-loader: 6.2.0_webpack@5.78.0 react-scripts: 5.0.1_2xn6hao67zmtzyfac7qsgeyxpu transitivePeerDependencies: - webpack @@ -25778,22 +25622,6 @@ packages: semver: 7.5.4 webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau - /css-loader/6.7.3_webpack@5.88.2: - resolution: {integrity: sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==} - engines: {node: '>= 12.13.0'} - peerDependencies: - webpack: ^5.0.0 - dependencies: - icss-utils: 5.1.0_postcss@8.4.29 - postcss: 8.4.29 - postcss-modules-extract-imports: 3.0.0_postcss@8.4.29 - postcss-modules-local-by-default: 4.0.0_postcss@8.4.29 - postcss-modules-scope: 3.0.0_postcss@8.4.29 - postcss-modules-values: 4.0.0_postcss@8.4.29 - postcss-value-parser: 4.2.0 - semver: 7.5.4 - webpack: 5.88.2 - /css-minimizer-webpack-plugin/3.4.1_rw5du4nyxcvxj5knuew24gpv6a: resolution: {integrity: sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==} engines: {node: '>= 12.13.0'} @@ -25822,7 +25650,7 @@ packages: source-map: 0.6.1 webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau - /css-minimizer-webpack-plugin/3.4.1_webpack@5.88.2: + /css-minimizer-webpack-plugin/3.4.1_webpack@5.78.0: resolution: {integrity: sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==} engines: {node: '>= 12.13.0'} peerDependencies: @@ -25847,7 +25675,7 @@ packages: schema-utils: 4.0.0 serialize-javascript: 6.0.1 source-map: 0.6.1 - webpack: 5.88.2 + webpack: 5.78.0 /css-prefers-color-scheme/6.0.3_postcss@8.4.21: resolution: {integrity: sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==} @@ -27221,6 +27049,7 @@ packages: /es-module-lexer/1.2.1: resolution: {integrity: sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==} + dev: true /es-set-tostringtag/2.0.1: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} @@ -27495,7 +27324,7 @@ packages: transitivePeerDependencies: - supports-color - /eslint-import-resolver-webpack/0.13.2_re6elnmjmviqdg6e3dtzej3oae: + /eslint-import-resolver-webpack/0.13.2_2shkfpyvap4zatu4dplbemujdy: resolution: {integrity: sha512-XodIPyg1OgE2h5BDErz3WJoK7lawxKTJNhgPNafRST6csC/MZC+L5P6kKqsZGRInpbgc02s/WZMrb4uGJzcuRg==} engines: {node: '>= 6'} peerDependencies: @@ -27514,7 +27343,7 @@ packages: lodash: 4.17.21 resolve: 1.22.2 semver: 5.7.1 - webpack: 5.88.2_@swc+core@1.3.49 + webpack: 5.78.0_@swc+core@1.3.49 transitivePeerDependencies: - supports-color dev: true @@ -27572,7 +27401,7 @@ packages: debug: 3.2.7 eslint: 8.38.0 eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-webpack: 0.13.2_re6elnmjmviqdg6e3dtzej3oae + eslint-import-resolver-webpack: 0.13.2_2shkfpyvap4zatu4dplbemujdy transitivePeerDependencies: - supports-color dev: true @@ -27985,21 +27814,6 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - /eslint-webpack-plugin/3.2.0_3hr2pwtplkwedgf6lcivtacw3q: - resolution: {integrity: sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==} - engines: {node: '>= 12.13.0'} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - webpack: ^5.0.0 - dependencies: - '@types/eslint': 8.44.2 - eslint: 8.48.0 - jest-worker: 28.1.3 - micromatch: 4.0.5 - normalize-path: 3.0.0 - schema-utils: 4.0.0 - webpack: 5.88.2 - /eslint-webpack-plugin/3.2.0_hzv2vgrkwrkjb5sk6efnemby4e: resolution: {integrity: sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==} engines: {node: '>= 12.13.0'} @@ -28769,19 +28583,9 @@ packages: webpack: ^4.0.0 || ^5.0.0 dependencies: loader-utils: 2.0.4 - schema-utils: 3.1.2 + schema-utils: 3.3.0 webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau - /file-loader/6.2.0_webpack@5.88.2: - resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==} - engines: {node: '>= 10.13.0'} - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - dependencies: - loader-utils: 2.0.4 - schema-utils: 3.1.2 - webpack: 5.88.2 - /file-selector/0.6.0: resolution: {integrity: sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==} engines: {node: '>= 12'} @@ -29089,37 +28893,6 @@ packages: /forever-agent/0.6.1: resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} - /fork-ts-checker-webpack-plugin/6.5.3_rjadthzu3mzu3rpkl7r437txge: - resolution: {integrity: sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==} - engines: {node: '>=10', yarn: '>=1.0.0'} - peerDependencies: - eslint: '>= 6' - typescript: '>= 2.7' - vue-template-compiler: '*' - webpack: '>= 4' - peerDependenciesMeta: - eslint: - optional: true - vue-template-compiler: - optional: true - dependencies: - '@babel/code-frame': 7.22.13 - '@types/json-schema': 7.0.12 - chalk: 4.1.2 - chokidar: 3.5.3 - cosmiconfig: 6.0.0 - deepmerge: 4.3.1 - eslint: 8.48.0 - fs-extra: 9.1.0 - glob: 7.2.3 - memfs: 3.5.0 - minimatch: 3.1.2 - schema-utils: 2.7.0 - semver: 7.5.4 - tapable: 1.1.3 - typescript: 4.9.5 - webpack: 5.88.2 - /fork-ts-checker-webpack-plugin/6.5.3_vq6t4wvflba3b6dvvfvomzl76u: resolution: {integrity: sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==} engines: {node: '>=10', yarn: '>=1.0.0'} @@ -29151,7 +28924,7 @@ packages: typescript: 4.9.5 webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau - /fork-ts-checker-webpack-plugin/8.0.0_rggdtlzfqxxwxudp3onsqdyocm: + /fork-ts-checker-webpack-plugin/8.0.0_fejcc7gjbwtmwzggoernzojija: resolution: {integrity: sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==} engines: {node: '>=12.13.0', yarn: '>=1.0.0'} peerDependencies: @@ -29171,7 +28944,7 @@ packages: semver: 7.5.4 tapable: 2.2.1 typescript: 4.9.5 - webpack: 5.88.2_u5c4qderjagc6tepfyiby6xhau + webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau dev: true /fork-ts-checker-webpack-plugin/8.0.0_wlox7xpecxj4rvkt6b6o7frtlu: @@ -30620,19 +30393,6 @@ packages: tapable: 2.2.1 webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau - /html-webpack-plugin/5.5.3_webpack@5.88.2: - resolution: {integrity: sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==} - engines: {node: '>=10.13.0'} - peerDependencies: - webpack: ^5.20.0 - dependencies: - '@types/html-minifier-terser': 6.1.0 - html-minifier-terser: 6.1.0 - lodash: 4.17.21 - pretty-error: 4.0.0 - tapable: 2.2.1 - webpack: 5.88.2 - /htmlencode/0.0.4: resolution: {integrity: sha512-0uDvNVpzj/E2TfvLLyyXhKBRvF1y84aZsyRxRXFsQobnHaL4pcaXk+Y9cnFlvnxrBLeXDNq/VJBD+ngdBgQG1w==} dev: false @@ -30789,6 +30549,16 @@ packages: - supports-color dev: true + /https-proxy-agent/5.0.0: + resolution: {integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /https-proxy-agent/5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -33942,21 +33712,7 @@ packages: webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau dev: true - /less-loader/4.1.0_less@4.1.3+webpack@5.88.2: - resolution: {integrity: sha512-KNTsgCE9tMOM70+ddxp9yyt9iHqgmSs0yTZc5XH5Wo+g80RWRIYNqE58QJKm/yMud5wZEvz50ugRDuzVIkyahg==} - engines: {node: '>= 4.8 < 5.0.0 || >= 5.10'} - peerDependencies: - less: ^2.3.1 || ^3.0.0 - webpack: ^2.0.0 || ^3.0.0 || ^4.0.0 - dependencies: - clone: 2.1.2 - less: 4.1.3 - loader-utils: 1.4.2 - pify: 3.0.0 - webpack: 5.88.2 - dev: true - - /less-loader/6.2.0_webpack@5.88.2: + /less-loader/6.2.0_webpack@5.78.0: resolution: {integrity: sha512-Cl5h95/Pz/PWub/tCBgT1oNMFeH1WTD33piG80jn5jr12T4XbxZcjThwNXDQ7AG649WEynuIzO4b0+2Tn9Qolg==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -33966,7 +33722,7 @@ packages: less: 3.13.1 loader-utils: 2.0.4 schema-utils: 2.7.1 - webpack: 5.88.2 + webpack: 5.78.0 dev: true /less-vars-to-js/1.3.0: @@ -35548,15 +35304,6 @@ packages: schema-utils: 4.0.0 webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau - /mini-css-extract-plugin/2.7.5_webpack@5.88.2: - resolution: {integrity: sha512-9HaR++0mlgom81s95vvNjxkg52n2b5s//3ZTI1EtzFb98awsLSivs2LMsVqnQ3ay0PVhqWcGNyDaTE961FOcjQ==} - engines: {node: '>= 12.13.0'} - peerDependencies: - webpack: ^5.0.0 - dependencies: - schema-utils: 4.0.0 - webpack: 5.88.2 - /minimalistic-assert/1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -35755,6 +35502,15 @@ packages: is-extendable: 1.0.1 dev: true + /mixpanel/0.17.0: + resolution: {integrity: sha512-DY5WeOy/hmkPrNiiZugJpWR0iMuOwuj1a3u0bgwB2eUFRV6oIew/pIahhpawdbNjb+Bye4a8ID3gefeNPvL81g==} + engines: {node: '>=10.0'} + dependencies: + https-proxy-agent: 5.0.0 + transitivePeerDependencies: + - supports-color + dev: false + /mkdirp-classic/0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} dev: true @@ -37903,27 +37659,6 @@ packages: passport-oauth2: 1.7.0 dev: false - /passport-google-oauth/2.0.0: - resolution: {integrity: sha512-JKxZpBx6wBQXX1/a1s7VmdBgwOugohH+IxCy84aPTZNq/iIPX6u7Mqov1zY7MKRz3niFPol0KJz8zPLBoHKtYA==} - engines: {node: '>= 0.4.0'} - dependencies: - passport-google-oauth1: 1.0.0 - passport-google-oauth20: 2.0.0 - dev: false - - /passport-google-oauth1/1.0.0: - resolution: {integrity: sha512-qpCEhuflJgYrdg5zZIpAq/K3gTqa1CtHjbubsEsidIdpBPLkEVq6tB1I8kBNcH89RdSiYbnKpCBXAZXX/dtx1Q==} - dependencies: - passport-oauth1: 1.3.0 - dev: false - - /passport-google-oauth20/2.0.0: - resolution: {integrity: sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==} - engines: {node: '>= 0.4.0'} - dependencies: - passport-oauth2: 1.7.0 - dev: false - /passport-jwt/4.0.1: resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} dependencies: @@ -37931,15 +37666,6 @@ packages: passport-strategy: 1.0.0 dev: false - /passport-oauth1/1.3.0: - resolution: {integrity: sha512-8T/nX4gwKTw0PjxP1xfD0QhrydQNakzeOpZ6M5Uqdgz9/a/Ag62RmJxnZQ4LkbdXGrRehQHIAHNAu11rCP46Sw==} - engines: {node: '>= 0.4.0'} - dependencies: - oauth: 0.9.15 - passport-strategy: 1.0.0 - utils-merge: 1.0.1 - dev: false - /passport-oauth2/1.7.0: resolution: {integrity: sha512-j2gf34szdTF2Onw3+76alNnaAExlUmHvkc7cL+cmaS5NzHzDP/BvFHJruueQ9XAeNOdpI+CH+PWid8RA7KCwAQ==} engines: {node: '>= 0.4.0'} @@ -38641,19 +38367,6 @@ packages: semver: 7.5.4 webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau - /postcss-loader/6.2.1_spab46aedetttbaa4fvapwm3fq: - resolution: {integrity: sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==} - engines: {node: '>= 12.13.0'} - peerDependencies: - postcss: ^7.0.0 || ^8.0.1 - webpack: ^5.0.0 - dependencies: - cosmiconfig: 7.1.0 - klona: 2.0.6 - postcss: 8.4.21 - semver: 7.5.4 - webpack: 5.88.2 - /postcss-loader/7.0.2_mquw4qchulb5tpkmg3p2j6qala: resolution: {integrity: sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg==} engines: {node: '>= 14.15.0'} @@ -40260,47 +39973,6 @@ packages: react-dom: 17.0.2_react@17.0.2 dev: false - /react-dev-utils/12.0.1_rjadthzu3mzu3rpkl7r437txge: - resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=2.7' - webpack: '>=4' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@babel/code-frame': 7.21.4 - address: 1.2.2 - browserslist: 4.21.10 - chalk: 4.1.2 - cross-spawn: 7.0.3 - detect-port-alt: 1.1.6 - escape-string-regexp: 4.0.0 - filesize: 8.0.7 - find-up: 5.0.0 - fork-ts-checker-webpack-plugin: 6.5.3_rjadthzu3mzu3rpkl7r437txge - global-modules: 2.0.0 - globby: 11.1.0 - gzip-size: 6.0.0 - immer: 9.0.21 - is-root: 2.1.0 - loader-utils: 3.2.1 - open: 8.4.2 - pkg-up: 3.1.0 - prompts: 2.4.2 - react-error-overlay: 6.0.11 - recursive-readdir: 2.2.3 - shell-quote: 1.8.1 - strip-ansi: 6.0.1 - text-table: 0.2.0 - typescript: 4.9.5 - webpack: 5.88.2 - transitivePeerDependencies: - - eslint - - supports-color - - vue-template-compiler - /react-dev-utils/12.0.1_vq6t4wvflba3b6dvvfvomzl76u: resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==} engines: {node: '>=14'} @@ -40698,54 +40370,54 @@ packages: optional: true dependencies: '@babel/core': 7.21.4 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.10_aaw3q2lthy6zceet2ugelootve + '@pmmmwh/react-refresh-webpack-plugin': 0.5.10_kwexxzmt7sjpqjleraytwi4jvu '@svgr/webpack': 5.5.0 babel-jest: 27.5.1_@babel+core@7.21.4 - babel-loader: 8.3.0_vymqytky47ah5a633vjyq2m3sy + babel-loader: 8.3.0_2bpkfvz2mezbew2j5yjox7n6pu babel-plugin-named-asset-import: 0.3.8_@babel+core@7.21.4 babel-preset-react-app: 10.0.1 bfj: 7.0.2 browserslist: 4.21.5 camelcase: 6.3.0 case-sensitive-paths-webpack-plugin: 2.4.0 - css-loader: 6.7.3_webpack@5.88.2 - css-minimizer-webpack-plugin: 3.4.1_webpack@5.88.2 + css-loader: 6.7.3_webpack@5.78.0 + css-minimizer-webpack-plugin: 3.4.1_webpack@5.78.0 dotenv: 10.0.0 dotenv-expand: 5.1.0 eslint: 8.48.0 eslint-config-react-app: 7.0.1_sbdixes4cky6hqsocdpcwctiue - eslint-webpack-plugin: 3.2.0_3hr2pwtplkwedgf6lcivtacw3q - file-loader: 6.2.0_webpack@5.88.2 + eslint-webpack-plugin: 3.2.0_hzv2vgrkwrkjb5sk6efnemby4e + file-loader: 6.2.0_webpack@5.78.0 fs-extra: 10.1.0 - html-webpack-plugin: 5.5.3_webpack@5.88.2 + html-webpack-plugin: 5.5.3_webpack@5.78.0 identity-obj-proxy: 3.0.0 jest: 27.5.1 jest-resolve: 27.5.1 jest-watch-typeahead: 1.1.0_jest@27.5.1 - mini-css-extract-plugin: 2.7.5_webpack@5.88.2 + mini-css-extract-plugin: 2.7.5_webpack@5.78.0 postcss: 8.4.21 postcss-flexbugs-fixes: 5.0.2_postcss@8.4.21 - postcss-loader: 6.2.1_spab46aedetttbaa4fvapwm3fq + postcss-loader: 6.2.1_2izhiogyhzv3k6gmxpzxzwhblu postcss-normalize: 10.0.1_jrpp4geoaqu5dz2gragkckznb4 postcss-preset-env: 7.8.3_postcss@8.4.21 prompts: 2.4.2 react: 17.0.2 react-app-polyfill: 3.0.0 - react-dev-utils: 12.0.1_rjadthzu3mzu3rpkl7r437txge + react-dev-utils: 12.0.1_vq6t4wvflba3b6dvvfvomzl76u react-refresh: 0.11.0 resolve: 1.22.2 resolve-url-loader: 4.0.0 - sass-loader: 12.6.0_webpack@5.88.2 + sass-loader: 12.6.0_webpack@5.78.0 semver: 7.4.0 - source-map-loader: 3.0.2_webpack@5.88.2 - style-loader: 3.3.2_webpack@5.88.2 + source-map-loader: 3.0.2_webpack@5.78.0 + style-loader: 3.3.2_webpack@5.78.0 tailwindcss: 3.3.1_postcss@8.4.21 - terser-webpack-plugin: 5.3.7_webpack@5.88.2 + terser-webpack-plugin: 5.3.7_webpack@5.78.0 typescript: 4.9.5 - webpack: 5.88.2 - webpack-dev-server: 4.11.1_webpack@5.88.2 - webpack-manifest-plugin: 4.1.1_webpack@5.88.2 - workbox-webpack-plugin: 6.5.4_webpack@5.88.2 + webpack: 5.78.0 + webpack-dev-server: 4.11.1_webpack@5.78.0 + webpack-manifest-plugin: 4.1.1_webpack@5.78.0 + workbox-webpack-plugin: 6.5.4_webpack@5.78.0 optionalDependencies: fsevents: 2.3.3 transitivePeerDependencies: @@ -41857,7 +41529,7 @@ packages: jest-worker: 26.6.2 rollup: 2.79.1 serialize-javascript: 4.0.0 - terser: 5.16.9 + terser: 5.19.3 /rollup-plugin-terser/7.0.2_rollup@3.20.2: resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} @@ -42063,29 +41735,6 @@ packages: neo-async: 2.6.2 webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau - /sass-loader/12.6.0_webpack@5.88.2: - resolution: {integrity: sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==} - engines: {node: '>= 12.13.0'} - peerDependencies: - fibers: '>= 3.1.0' - node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - sass: ^1.3.0 - sass-embedded: '*' - webpack: ^5.0.0 - peerDependenciesMeta: - fibers: - optional: true - node-sass: - optional: true - sass: - optional: true - sass-embedded: - optional: true - dependencies: - klona: 2.0.6 - neo-async: 2.6.2 - webpack: 5.88.2 - /sass-loader/13.2.0_sass@1.58.1+webpack@5.76.1: resolution: {integrity: sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==} engines: {node: '>= 14.15.0'} @@ -42186,6 +41835,7 @@ packages: '@types/json-schema': 7.0.12 ajv: 6.12.6 ajv-keywords: 3.5.2_ajv@6.12.6 + dev: true /schema-utils/3.3.0: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} @@ -42852,17 +42502,6 @@ packages: source-map-js: 1.0.2 webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau - /source-map-loader/3.0.2_webpack@5.88.2: - resolution: {integrity: sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==} - engines: {node: '>= 12.13.0'} - peerDependencies: - webpack: ^5.0.0 - dependencies: - abab: 2.0.6 - iconv-lite: 0.6.3 - source-map-js: 1.0.2 - webpack: 5.88.2 - /source-map-loader/4.0.1_webpack@5.76.1: resolution: {integrity: sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==} engines: {node: '>= 14.15.0'} @@ -43539,14 +43178,6 @@ packages: dependencies: webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau - /style-loader/3.3.2_webpack@5.88.2: - resolution: {integrity: sha512-RHs/vcrKdQK8wZliteNK4NKzxvLBzpuHMqYmUVWeKa6MkaIQ97ZTOS0b+zapZhy6GcrgWnvWYCMHRirC3FsUmw==} - engines: {node: '>= 12.13.0'} - peerDependencies: - webpack: ^5.0.0 - dependencies: - webpack: 5.88.2 - /stylehacks/5.1.1_postcss@8.4.25: resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} engines: {node: ^10 || ^12 || >=14.0} @@ -43790,14 +43421,14 @@ packages: upper-case: 1.1.3 dev: true - /swc-loader/0.2.3_zpsjxul5gtyjq5vu5uxru46xsq: + /swc-loader/0.2.3_wvz5dn57l37py5yhasbcqqk6hi: resolution: {integrity: sha512-D1p6XXURfSPleZZA/Lipb3A8pZ17fP4NObZvFCDjK/OKljroqDpPmsBdTraWhVBqUNpcWBQY1imWdoPScRlQ7A==} peerDependencies: '@swc/core': ^1.2.147 webpack: '>=2' dependencies: '@swc/core': 1.3.49 - webpack: 5.88.2_u5c4qderjagc6tepfyiby6xhau + webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau dev: true /symbol-observable/1.2.0: @@ -44060,7 +43691,7 @@ packages: terser: 5.19.3 webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau - /terser-webpack-plugin/5.3.7_webpack@5.88.2: + /terser-webpack-plugin/5.3.7_webpack@5.78.0: resolution: {integrity: sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -44081,9 +43712,9 @@ packages: schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.19.3 - webpack: 5.88.2 + webpack: 5.78.0 - /terser-webpack-plugin/5.3.9_2pue2hesc4kyarjonhtsco4zxm: + /terser-webpack-plugin/5.3.9_aka7ue4sjkoeo6uo4dlqntmpgy: resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -44099,17 +43730,17 @@ packages: uglify-js: optional: true dependencies: - '@jridgewell/trace-mapping': 0.3.19 + '@jridgewell/trace-mapping': 0.3.18 '@swc/core': 1.3.49 esbuild: 0.18.20 jest-worker: 27.5.1 schema-utils: 3.1.2 serialize-javascript: 6.0.1 - terser: 5.19.3 - webpack: 5.88.2_u5c4qderjagc6tepfyiby6xhau + terser: 5.16.9 + webpack: 5.82.1_w67ycjwq2niq3jlxgktvf5aow4 dev: true - /terser-webpack-plugin/5.3.9_aka7ue4sjkoeo6uo4dlqntmpgy: + /terser-webpack-plugin/5.3.9_dnqqsr3phzjhopay4d6e5ziqz4: resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -44125,17 +43756,16 @@ packages: uglify-js: optional: true dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.19 '@swc/core': 1.3.49 esbuild: 0.18.20 jest-worker: 27.5.1 - schema-utils: 3.1.2 + schema-utils: 3.3.0 serialize-javascript: 6.0.1 - terser: 5.16.9 - webpack: 5.82.1_w67ycjwq2niq3jlxgktvf5aow4 - dev: true + terser: 5.19.3 + webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau - /terser-webpack-plugin/5.3.9_dnqqsr3phzjhopay4d6e5ziqz4: + /terser-webpack-plugin/5.3.9_webpack@5.76.1: resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -44152,15 +43782,14 @@ packages: optional: true dependencies: '@jridgewell/trace-mapping': 0.3.19 - '@swc/core': 1.3.49 - esbuild: 0.18.20 jest-worker: 27.5.1 - schema-utils: 3.1.2 + schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.19.3 - webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau + webpack: 5.76.1 + dev: true - /terser-webpack-plugin/5.3.9_webpack@5.76.1: + /terser-webpack-plugin/5.3.9_webpack@5.78.0: resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -44178,11 +43807,10 @@ packages: dependencies: '@jridgewell/trace-mapping': 0.3.19 jest-worker: 27.5.1 - schema-utils: 3.1.2 + schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.19.3 - webpack: 5.76.1 - dev: true + webpack: 5.78.0 /terser-webpack-plugin/5.3.9_webpack@5.88.2: resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} @@ -44202,12 +43830,13 @@ packages: dependencies: '@jridgewell/trace-mapping': 0.3.19 jest-worker: 27.5.1 - schema-utils: 3.1.2 + schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.19.3 webpack: 5.88.2 + dev: true - /terser-webpack-plugin/5.3.9_zpsjxul5gtyjq5vu5uxru46xsq: + /terser-webpack-plugin/5.3.9_wvz5dn57l37py5yhasbcqqk6hi: resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -44226,10 +43855,10 @@ packages: '@jridgewell/trace-mapping': 0.3.19 '@swc/core': 1.3.49 jest-worker: 27.5.1 - schema-utils: 3.1.2 + schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.19.3 - webpack: 5.88.2_@swc+core@1.3.49 + webpack: 5.78.0_@swc+core@1.3.49 dev: true /terser/4.8.1: @@ -44263,6 +43892,7 @@ packages: acorn: 8.10.0 commander: 2.20.3 source-map-support: 0.5.21 + dev: true /terser/5.19.3: resolution: {integrity: sha512-pQzJ9UJzM0IgmT4FAtYI6+VqFf0lj/to58AV0Xfgg0Up37RyPG7Al+1cepC6/BVuAxR9oNb41/DL4DEoHJvTdg==} @@ -44796,7 +44426,7 @@ packages: yargs-parser: 21.1.1 dev: true - /ts-loader/9.4.2_hybrf64lftnf5l2xwgg7goi2iu: + /ts-loader/9.4.2_fejcc7gjbwtmwzggoernzojija: resolution: {integrity: sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==} engines: {node: '>=12.0.0'} peerDependencies: @@ -44808,10 +44438,10 @@ packages: micromatch: 4.0.5 semver: 7.5.2 typescript: 4.9.5 - webpack: 5.82.1_w67ycjwq2niq3jlxgktvf5aow4 + webpack: 5.78.0 dev: true - /ts-loader/9.4.2_rggdtlzfqxxwxudp3onsqdyocm: + /ts-loader/9.4.2_hybrf64lftnf5l2xwgg7goi2iu: resolution: {integrity: sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==} engines: {node: '>=12.0.0'} peerDependencies: @@ -44823,7 +44453,7 @@ packages: micromatch: 4.0.5 semver: 7.5.2 typescript: 4.9.5 - webpack: 5.88.2 + webpack: 5.82.1_w67ycjwq2niq3jlxgktvf5aow4 dev: true /ts-node/10.9.1_3oc4l4vkwjasz4xtxrjz3zw4u4: @@ -46217,20 +45847,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.0.0 - webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau - - /webpack-dev-middleware/5.3.3_webpack@5.88.2: - resolution: {integrity: sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==} - engines: {node: '>= 12.13.0'} - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - dependencies: - colorette: 2.0.19 - memfs: 3.5.0 - mime-types: 2.1.35 - range-parser: 1.2.1 - schema-utils: 4.0.0 - webpack: 5.88.2 + webpack: 5.78.0 /webpack-dev-middleware/6.0.1_webpack@5.76.1: resolution: {integrity: sha512-PZPZ6jFinmqVPJZbisfggDiC+2EeGZ1ZByyMP5sOFJcPPWSexalISz+cvm+j+oYPT7FIJyxT76esjnw9DhE5sw==} @@ -46246,7 +45863,7 @@ packages: webpack: 5.76.1 dev: true - /webpack-dev-middleware/6.1.1_webpack@5.88.2: + /webpack-dev-middleware/6.1.1_webpack@5.78.0: resolution: {integrity: sha512-y51HrHaFeeWir0YO4f0g+9GwZawuigzcAdRNon6jErXy/SqV/+O6eaVAzDqE6t3e3NpGeR5CS+cCDaTC+V3yEQ==} engines: {node: '>= 14.15.0'} peerDependencies: @@ -46260,7 +45877,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.0.0 - webpack: 5.88.2_u5c4qderjagc6tepfyiby6xhau + webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau dev: true /webpack-dev-server/4.11.1_webpack@5.76.1: @@ -46358,53 +45975,6 @@ packages: - supports-color - utf-8-validate - /webpack-dev-server/4.11.1_webpack@5.88.2: - resolution: {integrity: sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==} - engines: {node: '>= 12.13.0'} - hasBin: true - peerDependencies: - webpack: ^4.37.0 || ^5.0.0 - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - dependencies: - '@types/bonjour': 3.5.10 - '@types/connect-history-api-fallback': 1.3.5 - '@types/express': 4.17.17 - '@types/serve-index': 1.9.1 - '@types/serve-static': 1.15.1 - '@types/sockjs': 0.3.33 - '@types/ws': 8.5.4 - ansi-html-community: 0.0.8 - bonjour-service: 1.1.1 - chokidar: 3.5.3 - colorette: 2.0.19 - compression: 1.7.4 - connect-history-api-fallback: 2.0.0 - default-gateway: 6.0.3 - express: 4.18.2 - graceful-fs: 4.2.11 - html-entities: 2.3.3 - http-proxy-middleware: 2.0.6_@types+express@4.17.17 - ipaddr.js: 2.0.1 - open: 8.4.2 - p-retry: 4.6.2 - rimraf: 3.0.2 - schema-utils: 4.0.0 - selfsigned: 2.1.1 - serve-index: 1.9.1 - sockjs: 0.3.24 - spdy: 4.0.2 - webpack: 5.88.2 - webpack-dev-middleware: 5.3.3_webpack@5.88.2 - ws: 8.13.0 - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - utf-8-validate - /webpack-hot-middleware/2.25.3: resolution: {integrity: sha512-IK/0WAHs7MTu1tzLTjio73LjS3Ov+VvBKQmE8WPlJutgG5zT6Urgq/BbAdRrHTRpyzK0dvAvFh1Qg98akxgZpA==} dependencies: @@ -46423,16 +45993,6 @@ packages: webpack: 5.78.0_u5c4qderjagc6tepfyiby6xhau webpack-sources: 2.3.1 - /webpack-manifest-plugin/4.1.1_webpack@5.88.2: - resolution: {integrity: sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==} - engines: {node: '>=12.22.0'} - peerDependencies: - webpack: ^4.44.2 || ^5.47.0 - dependencies: - tapable: 2.2.1 - webpack: 5.88.2 - webpack-sources: 2.3.1 - /webpack-merge/5.8.0: resolution: {integrity: sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==} engines: {node: '>=10.0.0'} @@ -46519,7 +46079,7 @@ packages: - uglify-js dev: true - /webpack/5.78.0_u5c4qderjagc6tepfyiby6xhau: + /webpack/5.78.0: resolution: {integrity: sha512-gT5DP72KInmE/3azEaQrISjTvLYlSM0j1Ezhht/KLVkrqtv10JoP/RXhwmX/frrutOPuSq3o5Vq0ehR/4Vmd1g==} engines: {node: '>=10.13.0'} hasBin: true @@ -46536,7 +46096,7 @@ packages: '@webassemblyjs/wasm-parser': 1.11.1 acorn: 8.10.0 acorn-import-assertions: 1.9.0_acorn@8.10.0 - browserslist: 4.21.5 + browserslist: 4.21.10 chrome-trace-event: 1.0.3 enhanced-resolve: 5.15.0 es-module-lexer: 0.9.3 @@ -46550,7 +46110,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9_dnqqsr3phzjhopay4d6e5ziqz4 + terser-webpack-plugin: 5.3.9_webpack@5.78.0 watchpack: 2.4.0 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -46558,8 +46118,8 @@ packages: - esbuild - uglify-js - /webpack/5.82.1_w67ycjwq2niq3jlxgktvf5aow4: - resolution: {integrity: sha512-C6uiGQJ+Gt4RyHXXYt+v9f+SN1v83x68URwgxNQ98cvH8kxiuywWGP4XeNZ1paOzZ63aY3cTciCEQJNFUljlLw==} + /webpack/5.78.0_@swc+core@1.3.49: + resolution: {integrity: sha512-gT5DP72KInmE/3azEaQrISjTvLYlSM0j1Ezhht/KLVkrqtv10JoP/RXhwmX/frrutOPuSq3o5Vq0ehR/4Vmd1g==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -46569,16 +46129,16 @@ packages: optional: true dependencies: '@types/eslint-scope': 3.7.4 - '@types/estree': 1.0.0 - '@webassemblyjs/ast': 1.11.5 - '@webassemblyjs/wasm-edit': 1.11.5 - '@webassemblyjs/wasm-parser': 1.11.5 - acorn: 8.8.2 - acorn-import-assertions: 1.8.0_acorn@8.8.2 - browserslist: 4.21.5 + '@types/estree': 0.0.51 + '@webassemblyjs/ast': 1.11.1 + '@webassemblyjs/wasm-edit': 1.11.1 + '@webassemblyjs/wasm-parser': 1.11.1 + acorn: 8.10.0 + acorn-import-assertions: 1.9.0_acorn@8.10.0 + browserslist: 4.21.10 chrome-trace-event: 1.0.3 enhanced-resolve: 5.15.0 - es-module-lexer: 1.2.1 + es-module-lexer: 0.9.3 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -46587,11 +46147,10 @@ packages: loader-runner: 4.3.0 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 3.1.2 + schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9_aka7ue4sjkoeo6uo4dlqntmpgy + terser-webpack-plugin: 5.3.9_wvz5dn57l37py5yhasbcqqk6hi watchpack: 2.4.0 - webpack-cli: 5.1.4_jwpra7xq62zw3tehur2jwszlsq webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' @@ -46599,8 +46158,8 @@ packages: - uglify-js dev: true - /webpack/5.88.2: - resolution: {integrity: sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==} + /webpack/5.78.0_u5c4qderjagc6tepfyiby6xhau: + resolution: {integrity: sha512-gT5DP72KInmE/3azEaQrISjTvLYlSM0j1Ezhht/KLVkrqtv10JoP/RXhwmX/frrutOPuSq3o5Vq0ehR/4Vmd1g==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -46610,16 +46169,16 @@ packages: optional: true dependencies: '@types/eslint-scope': 3.7.4 - '@types/estree': 1.0.1 - '@webassemblyjs/ast': 1.11.5 - '@webassemblyjs/wasm-edit': 1.11.5 - '@webassemblyjs/wasm-parser': 1.11.5 + '@types/estree': 0.0.51 + '@webassemblyjs/ast': 1.11.1 + '@webassemblyjs/wasm-edit': 1.11.1 + '@webassemblyjs/wasm-parser': 1.11.1 acorn: 8.10.0 acorn-import-assertions: 1.9.0_acorn@8.10.0 browserslist: 4.21.10 chrome-trace-event: 1.0.3 enhanced-resolve: 5.15.0 - es-module-lexer: 1.2.1 + es-module-lexer: 0.9.3 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -46630,7 +46189,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9_webpack@5.88.2 + terser-webpack-plugin: 5.3.9_dnqqsr3phzjhopay4d6e5ziqz4 watchpack: 2.4.0 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -46638,8 +46197,8 @@ packages: - esbuild - uglify-js - /webpack/5.88.2_@swc+core@1.3.49: - resolution: {integrity: sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==} + /webpack/5.78.0_w67ycjwq2niq3jlxgktvf5aow4: + resolution: {integrity: sha512-gT5DP72KInmE/3azEaQrISjTvLYlSM0j1Ezhht/KLVkrqtv10JoP/RXhwmX/frrutOPuSq3o5Vq0ehR/4Vmd1g==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -46649,16 +46208,16 @@ packages: optional: true dependencies: '@types/eslint-scope': 3.7.4 - '@types/estree': 1.0.1 - '@webassemblyjs/ast': 1.11.5 - '@webassemblyjs/wasm-edit': 1.11.5 - '@webassemblyjs/wasm-parser': 1.11.5 + '@types/estree': 0.0.51 + '@webassemblyjs/ast': 1.11.1 + '@webassemblyjs/wasm-edit': 1.11.1 + '@webassemblyjs/wasm-parser': 1.11.1 acorn: 8.10.0 acorn-import-assertions: 1.9.0_acorn@8.10.0 browserslist: 4.21.10 chrome-trace-event: 1.0.3 enhanced-resolve: 5.15.0 - es-module-lexer: 1.2.1 + es-module-lexer: 0.9.3 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -46669,8 +46228,9 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9_zpsjxul5gtyjq5vu5uxru46xsq + terser-webpack-plugin: 5.3.9_dnqqsr3phzjhopay4d6e5ziqz4 watchpack: 2.4.0 + webpack-cli: 5.1.4_jwpra7xq62zw3tehur2jwszlsq webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' @@ -46678,8 +46238,8 @@ packages: - uglify-js dev: true - /webpack/5.88.2_u5c4qderjagc6tepfyiby6xhau: - resolution: {integrity: sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==} + /webpack/5.82.1_w67ycjwq2niq3jlxgktvf5aow4: + resolution: {integrity: sha512-C6uiGQJ+Gt4RyHXXYt+v9f+SN1v83x68URwgxNQ98cvH8kxiuywWGP4XeNZ1paOzZ63aY3cTciCEQJNFUljlLw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -46689,13 +46249,13 @@ packages: optional: true dependencies: '@types/eslint-scope': 3.7.4 - '@types/estree': 1.0.1 + '@types/estree': 1.0.0 '@webassemblyjs/ast': 1.11.5 '@webassemblyjs/wasm-edit': 1.11.5 '@webassemblyjs/wasm-parser': 1.11.5 - acorn: 8.10.0 - acorn-import-assertions: 1.9.0_acorn@8.10.0 - browserslist: 4.21.10 + acorn: 8.8.2 + acorn-import-assertions: 1.8.0_acorn@8.8.2 + browserslist: 4.21.5 chrome-trace-event: 1.0.3 enhanced-resolve: 5.15.0 es-module-lexer: 1.2.1 @@ -46707,10 +46267,11 @@ packages: loader-runner: 4.3.0 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 3.3.0 + schema-utils: 3.1.2 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9_2pue2hesc4kyarjonhtsco4zxm + terser-webpack-plugin: 5.3.9_aka7ue4sjkoeo6uo4dlqntmpgy watchpack: 2.4.0 + webpack-cli: 5.1.4_jwpra7xq62zw3tehur2jwszlsq webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' @@ -46718,7 +46279,7 @@ packages: - uglify-js dev: true - /webpack/5.88.2_w67ycjwq2niq3jlxgktvf5aow4: + /webpack/5.88.2: resolution: {integrity: sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==} engines: {node: '>=10.13.0'} hasBin: true @@ -46749,9 +46310,8 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9_2pue2hesc4kyarjonhtsco4zxm + terser-webpack-plugin: 5.3.9_webpack@5.88.2 watchpack: 2.4.0 - webpack-cli: 5.1.4_jwpra7xq62zw3tehur2jwszlsq webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' @@ -47102,22 +46662,6 @@ packages: - '@types/babel__core' - supports-color - /workbox-webpack-plugin/6.5.4_webpack@5.88.2: - resolution: {integrity: sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==} - engines: {node: '>=10.0.0'} - peerDependencies: - webpack: ^4.4.0 || ^5.9.0 - dependencies: - fast-json-stable-stringify: 2.1.0 - pretty-bytes: 5.6.0 - upath: 1.2.0 - webpack: 5.88.2 - webpack-sources: 1.4.3 - workbox-build: 6.5.4 - transitivePeerDependencies: - - '@types/babel__core' - - supports-color - /workbox-window/6.5.4: resolution: {integrity: sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==} dependencies: diff --git a/providers/africas-talking/package.json b/providers/africas-talking/package.json index 1d309105880..5bf268e0251 100644 --- a/providers/africas-talking/package.json +++ b/providers/africas-talking/package.json @@ -1,6 +1,6 @@ { "name": "@novu/africas-talking", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "An Africa's Talking wrapper for Novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -34,7 +34,7 @@ "pnpm": "^7.26.0" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "africastalking": "^0.6.2" }, "devDependencies": { diff --git a/providers/apns/package.json b/providers/apns/package.json index 322e2f381f2..0d539293d49 100644 --- a/providers/apns/package.json +++ b/providers/apns/package.json @@ -1,6 +1,6 @@ { "name": "@novu/apns", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A apns wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -36,7 +36,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "@parse/node-apn": "^5.2.3" }, "devDependencies": { diff --git a/providers/burst-sms/package.json b/providers/burst-sms/package.json index 370f87c8a95..d52eafa0e13 100644 --- a/providers/burst-sms/package.json +++ b/providers/burst-sms/package.json @@ -1,6 +1,6 @@ { "name": "@novu/burst-sms", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A burstSms wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -37,7 +37,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "axios": "^1.3.3", "qs": "^6.11.0" }, diff --git a/providers/clickatell/package.json b/providers/clickatell/package.json index 9b8b1861d4e..d11e3e17845 100644 --- a/providers/clickatell/package.json +++ b/providers/clickatell/package.json @@ -1,6 +1,6 @@ { "name": "@novu/clickatell", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A clickatell SMS provider wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -31,7 +31,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "axios": "^1.3.3" }, "devDependencies": { diff --git a/providers/discord/package.json b/providers/discord/package.json index 15a6bdc4b39..e3eda6bb0fd 100644 --- a/providers/discord/package.json +++ b/providers/discord/package.json @@ -1,6 +1,6 @@ { "name": "@novu/discord", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A discord wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "axios": "^1.3.3" }, "devDependencies": { diff --git a/providers/email-webhook/package.json b/providers/email-webhook/package.json index acad833538d..c7e428f9e81 100644 --- a/providers/email-webhook/package.json +++ b/providers/email-webhook/package.json @@ -1,6 +1,6 @@ { "name": "@novu/email-webhook", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "An email channel webhook provider wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", diff --git a/providers/emailjs/package.json b/providers/emailjs/package.json index e86b805827d..7e0027b2979 100644 --- a/providers/emailjs/package.json +++ b/providers/emailjs/package.json @@ -1,6 +1,6 @@ { "name": "@novu/emailjs", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "An emailjs provider for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -45,7 +45,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "emailjs": "^3.6.0" }, "devDependencies": { diff --git a/providers/expo/package.json b/providers/expo/package.json index 70bb32c8ded..08a2eb1c395 100644 --- a/providers/expo/package.json +++ b/providers/expo/package.json @@ -1,6 +1,6 @@ { "name": "@novu/expo", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A expo wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "expo-server-sdk": "^3.6.0" }, "devDependencies": { diff --git a/providers/fcm/package.json b/providers/fcm/package.json index 41d8fecacd5..4e124097fb9 100644 --- a/providers/fcm/package.json +++ b/providers/fcm/package.json @@ -1,6 +1,6 @@ { "name": "@novu/fcm", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A fcm wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "firebase-admin": "^11.10.1" }, "devDependencies": { diff --git a/providers/firetext/package.json b/providers/firetext/package.json index 56c85340d56..4e987a77b7e 100644 --- a/providers/firetext/package.json +++ b/providers/firetext/package.json @@ -1,6 +1,6 @@ { "name": "@novu/firetext", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A firetext wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "node-fetch": "^3.2.10" }, "devDependencies": { diff --git a/providers/forty-six-elks/package.json b/providers/forty-six-elks/package.json index a9a4db2c215..cb024285c0c 100644 --- a/providers/forty-six-elks/package.json +++ b/providers/forty-six-elks/package.json @@ -1,6 +1,6 @@ { "name": "@novu/forty-six-elks", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A 46elks wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -33,7 +33,7 @@ "pnpm": "^7.26.0" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "axios": "^1.3.4" }, "devDependencies": { diff --git a/providers/gupshup/package.json b/providers/gupshup/package.json index fb1cf1eab7f..16de2e73239 100644 --- a/providers/gupshup/package.json +++ b/providers/gupshup/package.json @@ -1,6 +1,6 @@ { "name": "@novu/gupshup", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A gupshup wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "node-fetch": "^3.2.10" }, "devDependencies": { diff --git a/providers/infobip/package.json b/providers/infobip/package.json index 3c4705daf6c..d50878c4d9c 100644 --- a/providers/infobip/package.json +++ b/providers/infobip/package.json @@ -1,6 +1,6 @@ { "name": "@novu/infobip", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A infobip wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -33,7 +33,7 @@ }, "dependencies": { "@infobip-api/sdk": "^0.2.0", - "@novu/stateless": "^0.19.0" + "@novu/stateless": "^0.20.0-alpha.0" }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.1", diff --git a/providers/kannel/package.json b/providers/kannel/package.json index 091f11e2d70..32e5bd09e32 100644 --- a/providers/kannel/package.json +++ b/providers/kannel/package.json @@ -1,6 +1,6 @@ { "name": "@novu/kannel", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A kannel wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "axios": "^0.27.2" }, "devDependencies": { diff --git a/providers/mailersend/package.json b/providers/mailersend/package.json index a8043ac137a..87967d95e79 100644 --- a/providers/mailersend/package.json +++ b/providers/mailersend/package.json @@ -1,6 +1,6 @@ { "name": "@novu/mailersend", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A mailersend wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "mailersend": "^1.3.1" }, "devDependencies": { diff --git a/providers/mailgun/package.json b/providers/mailgun/package.json index 04121153fb7..00972d34bc6 100644 --- a/providers/mailgun/package.json +++ b/providers/mailgun/package.json @@ -1,6 +1,6 @@ { "name": "@novu/mailgun", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A mailgun wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -44,7 +44,7 @@ "access": "public" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "form-data": "^4.0.0", "mailgun.js": "^8.0.1", "nock": "^13.1.3" diff --git a/providers/mailjet/package.json b/providers/mailjet/package.json index 59270cf5aa1..3488d17060c 100644 --- a/providers/mailjet/package.json +++ b/providers/mailjet/package.json @@ -1,6 +1,6 @@ { "name": "@novu/mailjet", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A mailjet wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "node-mailjet": "^6.0.4" }, "devDependencies": { diff --git a/providers/mandrill/package.json b/providers/mandrill/package.json index 31d252461f9..46fbcc2fd6b 100644 --- a/providers/mandrill/package.json +++ b/providers/mandrill/package.json @@ -1,6 +1,6 @@ { "name": "@novu/mandrill", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A mandrill wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -42,7 +42,7 @@ }, "dependencies": { "@mailchimp/mailchimp_transactional": "^1.0.46", - "@novu/stateless": "^0.19.0" + "@novu/stateless": "^0.20.0-alpha.0" }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "1.0.2", diff --git a/providers/maqsam/package.json b/providers/maqsam/package.json index 87fce89928b..939fc363a42 100644 --- a/providers/maqsam/package.json +++ b/providers/maqsam/package.json @@ -1,6 +1,6 @@ { "name": "@novu/maqsam", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A maqsam wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -33,7 +33,7 @@ "pnpm": "^7.26.0" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "axios": "^1.3.4", "date-fns": "2.29.3", "moment": "^2.29.4" diff --git a/providers/mattermost/package.json b/providers/mattermost/package.json index 72184969a98..bb072b30ca3 100644 --- a/providers/mattermost/package.json +++ b/providers/mattermost/package.json @@ -1,6 +1,6 @@ { "name": "@novu/mattermost", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A mattermost wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -33,7 +33,7 @@ "pnpm": "^7.26.0" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "axios": "^1.3.3" }, "devDependencies": { diff --git a/providers/ms-teams/package.json b/providers/ms-teams/package.json index a743ce7c003..d0f5b932248 100644 --- a/providers/ms-teams/package.json +++ b/providers/ms-teams/package.json @@ -1,6 +1,6 @@ { "name": "@novu/ms-teams", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A MS-Teams wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", diff --git a/providers/netcore/package.json b/providers/netcore/package.json index 128ca8c8eae..8d6a6d08d71 100644 --- a/providers/netcore/package.json +++ b/providers/netcore/package.json @@ -1,6 +1,6 @@ { "name": "@novu/netcore", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A netcore wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -45,7 +45,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "pepipost": "^5.0.0" }, "devDependencies": { diff --git a/providers/nexmo/package.json b/providers/nexmo/package.json index 7e81bb67369..b06c49cbfa0 100644 --- a/providers/nexmo/package.json +++ b/providers/nexmo/package.json @@ -1,6 +1,6 @@ { "name": "@novu/nexmo", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A nexmo wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "@vonage/server-sdk": "^2.10.10" }, "devDependencies": { diff --git a/providers/nodemailer/package.json b/providers/nodemailer/package.json index 2021ca2c435..216785c8e0b 100644 --- a/providers/nodemailer/package.json +++ b/providers/nodemailer/package.json @@ -1,6 +1,6 @@ { "name": "@novu/nodemailer", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A nodemailer wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -45,7 +45,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "nodemailer": "^6.6.5" }, "devDependencies": { diff --git a/providers/nodemailer/src/lib/nodemailer.provider.ts b/providers/nodemailer/src/lib/nodemailer.provider.ts index 7c52a221827..0d929257159 100644 --- a/providers/nodemailer/src/lib/nodemailer.provider.ts +++ b/providers/nodemailer/src/lib/nodemailer.provider.ts @@ -44,6 +44,7 @@ export class NodemailerProvider implements IEmailProvider { const tls: ConnectionOptions = this.getTlsOptions(); const smtpTransportOptions: SMTPTransport.Options = { + name: this.config.host, host: this.config.host, port: this.config.port, secure: this.config.secure, diff --git a/providers/one-signal/package.json b/providers/one-signal/package.json index 81a38f9a2cf..5399541be66 100644 --- a/providers/one-signal/package.json +++ b/providers/one-signal/package.json @@ -1,6 +1,6 @@ { "name": "@novu/one-signal", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A OneSignal wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -34,7 +34,7 @@ "pnpm": "^7.26.0" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "onesignal-node": "^3.4.0" }, "devDependencies": { diff --git a/providers/outlook365/package.json b/providers/outlook365/package.json index a3cfd739b8e..f85640809c9 100644 --- a/providers/outlook365/package.json +++ b/providers/outlook365/package.json @@ -1,6 +1,6 @@ { "name": "@novu/outlook365", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A outlook365 wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "nodemailer": "^6.6.5" }, "devDependencies": { diff --git a/providers/plivo/package.json b/providers/plivo/package.json index ecdcc4ed78d..ff7d82ce7cd 100644 --- a/providers/plivo/package.json +++ b/providers/plivo/package.json @@ -1,6 +1,6 @@ { "name": "@novu/plivo", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A plivo wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -44,7 +44,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "plivo": "^4.22.4" }, "devDependencies": { diff --git a/providers/plunk/package.json b/providers/plunk/package.json index 0cbf2a42b90..1a3493fa17c 100644 --- a/providers/plunk/package.json +++ b/providers/plunk/package.json @@ -1,6 +1,6 @@ { "name": "@novu/plunk", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A plunk wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -33,7 +33,7 @@ "pnpm": "^7.26.0" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "@plunk/node": "2.0.0" }, "devDependencies": { diff --git a/providers/postmark/package.json b/providers/postmark/package.json index e5d6a2f31f2..03a3aec4268 100644 --- a/providers/postmark/package.json +++ b/providers/postmark/package.json @@ -1,6 +1,6 @@ { "name": "@novu/postmark", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A postmark wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -45,7 +45,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "axios": "^1.3.6", "postmark": "^2.7.8" }, diff --git a/providers/push-webhook/package.json b/providers/push-webhook/package.json index 626d75b315b..5278eb91677 100644 --- a/providers/push-webhook/package.json +++ b/providers/push-webhook/package.json @@ -1,6 +1,6 @@ { "name": "@novu/push-webhook", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A push-webhook wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -34,7 +34,7 @@ "pnpm": "^7.26.0" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "axios": "^1.3.5" }, "devDependencies": { diff --git a/providers/resend/package.json b/providers/resend/package.json index 7686b1838e4..8f2a56c7730 100644 --- a/providers/resend/package.json +++ b/providers/resend/package.json @@ -1,6 +1,6 @@ { "name": "@novu/resend", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A resend wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "resend": "^0.11.0" }, "devDependencies": { diff --git a/providers/sendchamp/package.json b/providers/sendchamp/package.json index ca84ca8fdfc..e506a48789b 100644 --- a/providers/sendchamp/package.json +++ b/providers/sendchamp/package.json @@ -1,6 +1,6 @@ { "name": "@novu/sendchamp", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A sendchamp wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -33,7 +33,7 @@ "pnpm": "^7.26.0" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "axios": "^1.4.0" }, "devDependencies": { diff --git a/providers/sendgrid/package.json b/providers/sendgrid/package.json index 1f5da967e6b..3c7b8a6c6c8 100644 --- a/providers/sendgrid/package.json +++ b/providers/sendgrid/package.json @@ -1,6 +1,6 @@ { "name": "@novu/sendgrid", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A sendgrid wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -45,7 +45,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "@sendgrid/mail": "^7.4.6" }, "devDependencies": { diff --git a/providers/sendgrid/src/lib/sendgrid.provider.spec.ts b/providers/sendgrid/src/lib/sendgrid.provider.spec.ts index ebad0ec3edd..0b60953acb5 100644 --- a/providers/sendgrid/src/lib/sendgrid.provider.spec.ts +++ b/providers/sendgrid/src/lib/sendgrid.provider.spec.ts @@ -58,6 +58,19 @@ test('should trigger sendgrid correctly', async () => { novuTransactionId: undefined, novuWorkflowIdentifier: undefined, }, + personalizations: [ + { + to: [ + { + email: mockNovuMessage.to[0], + }, + ], + cc: undefined, + bcc: undefined, + dynamicTemplateData: undefined, + }, + ], + templateId: undefined, }); }); diff --git a/providers/sendgrid/src/lib/sendgrid.provider.ts b/providers/sendgrid/src/lib/sendgrid.provider.ts index f74203f12d4..acaa71ec49d 100644 --- a/providers/sendgrid/src/lib/sendgrid.provider.ts +++ b/providers/sendgrid/src/lib/sendgrid.provider.ts @@ -65,6 +65,15 @@ export class SendgridEmailProvider implements IEmailProvider { } private createMailData(options: IEmailOptions) { + const dynamicTemplateData = options.customData?.dynamicTemplateData; + const templateId = options.customData?.templateId as unknown as string; + /* + * deleted below values from customData to avoid passing them + * in customArgs because customArgs has max limit of 10,000 bytes + */ + delete options.customData?.dynamicTemplateData; + delete options.customData?.templateId; + const mailData: Partial = { from: { email: options.from || this.config.from, @@ -93,6 +102,15 @@ export class SendgridEmailProvider implements IEmailProvider { type: attachment.mime, }; }), + personalizations: [ + { + to: options.to.map((email) => ({ email })), + cc: options.cc?.map((ccItem) => ({ email: ccItem })), + bcc: options.bcc?.map((bccItem) => ({ email: bccItem })), + dynamicTemplateData: dynamicTemplateData, + }, + ], + templateId: templateId, }; if (options.replyTo) { diff --git a/providers/sendinblue/package.json b/providers/sendinblue/package.json index 74c64a7073f..07c7b8bf375 100644 --- a/providers/sendinblue/package.json +++ b/providers/sendinblue/package.json @@ -1,6 +1,6 @@ { "name": "@novu/sendinblue", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A sendinblue wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "@sendinblue/client": "^3.0.1" }, "devDependencies": { diff --git a/providers/ses/package.json b/providers/ses/package.json index d976f97fe80..a5bbc189caf 100644 --- a/providers/ses/package.json +++ b/providers/ses/package.json @@ -1,6 +1,6 @@ { "name": "@novu/ses", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A ses wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -33,7 +33,7 @@ }, "dependencies": { "@aws-sdk/client-ses": "3.382.0", - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "nodemailer": "^6.6.5" }, "devDependencies": { diff --git a/providers/slack/package.json b/providers/slack/package.json index f6f4965b7ed..d734d58a109 100644 --- a/providers/slack/package.json +++ b/providers/slack/package.json @@ -1,6 +1,6 @@ { "name": "@novu/slack", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A slack wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "axios": "^1.3.3" }, "devDependencies": { diff --git a/providers/sms-central/package.json b/providers/sms-central/package.json index fb9e80dd2d1..c89b91e28e0 100644 --- a/providers/sms-central/package.json +++ b/providers/sms-central/package.json @@ -1,6 +1,6 @@ { "name": "@novu/sms-central", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A sms-central wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", diff --git a/providers/sms77/package.json b/providers/sms77/package.json index 1c189002aa4..a1b81954906 100644 --- a/providers/sms77/package.json +++ b/providers/sms77/package.json @@ -1,6 +1,6 @@ { "name": "@novu/sms77", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A sms77 wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "node-fetch": "^2.6.7", "sms77-client": "^2.14.0" }, diff --git a/providers/sns/package.json b/providers/sns/package.json index f6be0962da7..bec123b17de 100644 --- a/providers/sns/package.json +++ b/providers/sns/package.json @@ -1,6 +1,6 @@ { "name": "@novu/sns", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A sns wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -33,7 +33,7 @@ }, "dependencies": { "@aws-sdk/client-sns": "^3.382.0", - "@novu/stateless": "^0.19.0" + "@novu/stateless": "^0.20.0-alpha.0" }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.1", diff --git a/providers/sparkpost/package.json b/providers/sparkpost/package.json index 01729ec4dee..35d82a46fbf 100644 --- a/providers/sparkpost/package.json +++ b/providers/sparkpost/package.json @@ -1,6 +1,6 @@ { "name": "@novu/sparkpost", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A sparkpost wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "sparkpost": "^2.1.4" }, "devDependencies": { diff --git a/providers/telnyx/package.json b/providers/telnyx/package.json index c14e580093b..b95b2429956 100644 --- a/providers/telnyx/package.json +++ b/providers/telnyx/package.json @@ -1,6 +1,6 @@ { "name": "@novu/telnyx", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A telnyx wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "telnyx": "^1.23.0" }, "devDependencies": { diff --git a/providers/termii/package.json b/providers/termii/package.json index 6b73d023051..938d45fd752 100644 --- a/providers/termii/package.json +++ b/providers/termii/package.json @@ -1,6 +1,6 @@ { "name": "@novu/termii", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A termii wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -32,7 +32,7 @@ "node": ">=10" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "node-fetch": "^3.2.10" }, "devDependencies": { diff --git a/providers/twilio/package.json b/providers/twilio/package.json index 789dc690783..4bbc18c5f28 100644 --- a/providers/twilio/package.json +++ b/providers/twilio/package.json @@ -1,6 +1,6 @@ { "name": "@novu/twilio", - "version": "0.19.0", + "version": "0.20.0-alpha.0", "description": "A twilio wrapper for novu", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -44,7 +44,7 @@ "access": "public" }, "dependencies": { - "@novu/stateless": "^0.19.0", + "@novu/stateless": "^0.20.0-alpha.0", "twilio": "^4.14.1" }, "devDependencies": {