Skip to content

Commit ba2cc2f

Browse files
Merge branch 'next' into self-hosted-auth
2 parents 4899262 + 71c6acb commit ba2cc2f

File tree

71 files changed

+1477
-481
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+1477
-481
lines changed

.source

apps/api/src/app/environments-v1/usecases/create-environment/create-environment.usecase.ts

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export class CreateEnvironment {
108108
environmentId: environment._id,
109109
organizationId: environment._organizationId,
110110
userId: command.userId,
111+
name: environment.name,
111112
})
112113
);
113114

apps/api/src/app/environments-v1/usecases/output-renderers/email-output-renderer.usecase.ts

+60-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,17 @@ import { EnvironmentEntity } from '@novu/dal';
88
import { FullPayloadForRender, RenderCommand } from './render-command';
99
import { MailyAttrsEnum } from '../../../shared/helpers/maily.types';
1010
import { parseLiquid } from '../../../shared/helpers/liquid';
11-
import { hasShow, isRepeatNode, isVariableNode, wrapMailyInLiquid } from '../../../shared/helpers/maily-utils';
11+
import {
12+
hasShow,
13+
isButtonNode,
14+
isImageNode,
15+
isLinkNode,
16+
isRepeatNode,
17+
isVariableNode,
18+
wrapMailyInLiquid,
19+
} from '../../../shared/helpers/maily-utils';
20+
21+
type MailyJSONMarks = NonNullable<MailyJSONContent['marks']>[number];
1222

1323
export class EmailOutputRendererCommand extends RenderCommand {
1424
environmentId: string;
@@ -249,7 +259,11 @@ export class EmailOutputRendererUsecase {
249259
}
250260
}
251261

252-
private processForEachNodes(nodes: MailyJSONContent[], iterablePath: string, index: number): MailyJSONContent[] {
262+
private processForEachNodes(
263+
nodes: MailyJSONContent[],
264+
iterablePath: string,
265+
index: number
266+
): Array<MailyJSONContent | MailyJSONMarks> {
253267
return nodes.map((node) => {
254268
const processedNode = { ...node };
255269

@@ -262,10 +276,54 @@ export class EmailOutputRendererUsecase {
262276
return processedNode;
263277
}
264278

279+
if (isButtonNode(processedNode)) {
280+
if (processedNode.attrs?.text) {
281+
processedNode.attrs.text = this.addIndexToLiquidExpression(processedNode.attrs.text, iterablePath, index);
282+
}
283+
284+
if (processedNode.attrs?.url) {
285+
processedNode.attrs.url = this.addIndexToLiquidExpression(processedNode.attrs.url, iterablePath, index);
286+
}
287+
288+
return processedNode;
289+
}
290+
291+
if (isImageNode(processedNode)) {
292+
if (processedNode.attrs?.src) {
293+
processedNode.attrs.src = this.addIndexToLiquidExpression(processedNode.attrs.src, iterablePath, index);
294+
}
295+
296+
if (processedNode.attrs?.externalLink) {
297+
processedNode.attrs.externalLink = this.addIndexToLiquidExpression(
298+
processedNode.attrs.externalLink,
299+
iterablePath,
300+
index
301+
);
302+
}
303+
304+
return processedNode;
305+
}
306+
307+
if (isLinkNode(processedNode)) {
308+
if (processedNode.attrs?.href) {
309+
processedNode.attrs.href = this.addIndexToLiquidExpression(processedNode.attrs.href, iterablePath, index);
310+
}
311+
312+
return processedNode;
313+
}
314+
265315
if (processedNode.content?.length) {
266316
processedNode.content = this.processForEachNodes(processedNode.content, iterablePath, index);
267317
}
268318

319+
if (processedNode.marks?.length) {
320+
processedNode.marks = this.processForEachNodes(
321+
processedNode.marks,
322+
iterablePath,
323+
index
324+
) as Array<MailyJSONMarks>;
325+
}
326+
269327
return processedNode;
270328
});
271329
}

apps/api/src/app/events/events.module.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { TerminusModule } from '@nestjs/terminus';
33

44
import { GetNovuProviderCredentials, StorageHelperService } from '@novu/application-generic';
55

6-
import { CommunityOrganizationRepository } from '@novu/dal';
6+
import { CommunityOrganizationRepository, CommunityUserRepository } from '@novu/dal';
77
import { EventsController } from './events.controller';
88
import { USE_CASES } from './usecases';
99

@@ -37,6 +37,6 @@ const PROVIDERS = [GetNovuProviderCredentials, StorageHelperService, CommunityOr
3737
BridgeModule,
3838
],
3939
controllers: [EventsController],
40-
providers: [...PROVIDERS, ...USE_CASES],
40+
providers: [...PROVIDERS, ...USE_CASES, CommunityUserRepository],
4141
})
4242
export class EventsModule {}

apps/api/src/app/inbox/dtos/subscriber-session-response.dto.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ export class SubscriberSessionResponseDto {
22
readonly token: string;
33
readonly totalUnreadCount: number;
44
readonly removeNovuBranding: boolean;
5-
readonly isSnoozeEnabled: boolean;
5+
readonly maxSnoozeDurationHours: number;
66
readonly isDevelopmentMode: boolean;
77
}

apps/api/src/app/inbox/usecases/session/session.spec.ts

+11-21
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@ import sinon from 'sinon';
22
import { expect } from 'chai';
33
import { NotFoundException, BadRequestException } from '@nestjs/common';
44
import { CommunityOrganizationRepository, EnvironmentRepository, IntegrationRepository } from '@novu/dal';
5-
import {
6-
AnalyticsService,
7-
CreateOrUpdateSubscriberUseCase,
8-
FeatureFlagsService,
9-
SelectIntegration,
10-
} from '@novu/application-generic';
5+
import { AnalyticsService, CreateOrUpdateSubscriberUseCase, SelectIntegration } from '@novu/application-generic';
116
import { ApiServiceLevelEnum, ChannelTypeEnum, InAppProviderIdEnum } from '@novu/shared';
127
import { AuthService } from '../../../auth/services/auth.service';
138
import { Session } from './session.usecase';
@@ -45,7 +40,6 @@ describe('Session', () => {
4540
let notificationsCount: sinon.SinonStubbedInstance<NotificationsCount>;
4641
let integrationRepository: sinon.SinonStubbedInstance<IntegrationRepository>;
4742
let organizationRepository: sinon.SinonStubbedInstance<CommunityOrganizationRepository>;
48-
let featureFlagsService: sinon.SinonStubbedInstance<FeatureFlagsService>;
4943

5044
beforeEach(() => {
5145
environmentRepository = sinon.createStubInstance(EnvironmentRepository);
@@ -56,7 +50,6 @@ describe('Session', () => {
5650
notificationsCount = sinon.createStubInstance(NotificationsCount);
5751
integrationRepository = sinon.createStubInstance(IntegrationRepository);
5852
organizationRepository = sinon.createStubInstance(CommunityOrganizationRepository);
59-
featureFlagsService = sinon.createStubInstance(FeatureFlagsService);
6053

6154
session = new Session(
6255
environmentRepository as any,
@@ -66,8 +59,7 @@ describe('Session', () => {
6659
analyticsService as any,
6760
notificationsCount as any,
6861
integrationRepository as any,
69-
organizationRepository as any,
70-
featureFlagsService as any
62+
organizationRepository as any
7163
);
7264
});
7365

@@ -226,9 +218,7 @@ describe('Session', () => {
226218
).to.be.true;
227219
});
228220

229-
it('should return the correct isSnoozeEnabled value for different service levels', async () => {
230-
featureFlagsService.getFlag.resolves(true);
231-
221+
it('should return the correct maxSnoozeDurationHours value for different service levels', async () => {
232222
const command: SessionCommand = {
233223
applicationIdentifier: 'app-id',
234224
subscriber: { subscriberId: 'subscriber-id' },
@@ -247,24 +237,24 @@ describe('Session', () => {
247237
notificationsCount.execute.resolves(notificationCount);
248238
authService.getSubscriberWidgetToken.resolves(token);
249239

250-
// FREE plan should have snooze disabled
240+
// FREE plan should have 24 hours max snooze duration
251241
organizationRepository.findOne.resolves({ apiServiceLevel: ApiServiceLevelEnum.FREE } as any);
252242
const freeResponse: SubscriberSessionResponseDto = await session.execute(command);
253-
expect(freeResponse.isSnoozeEnabled).to.equal(false);
243+
expect(freeResponse.maxSnoozeDurationHours).to.equal(24);
254244

255-
// PRO plan should have snooze enabled
245+
// PRO plan should have 90 days max snooze duration
256246
organizationRepository.findOne.resolves({ apiServiceLevel: ApiServiceLevelEnum.PRO } as any);
257247
const proResponse: SubscriberSessionResponseDto = await session.execute(command);
258-
expect(proResponse.isSnoozeEnabled).to.equal(true);
248+
expect(proResponse.maxSnoozeDurationHours).to.equal(90 * 24);
259249

260-
// BUSINESS/TEAM plan should have snooze enabled
250+
// BUSINESS/TEAM plan should have 90 days max snooze duration
261251
organizationRepository.findOne.resolves({ apiServiceLevel: ApiServiceLevelEnum.BUSINESS } as any);
262252
const businessResponse: SubscriberSessionResponseDto = await session.execute(command);
263-
expect(businessResponse.isSnoozeEnabled).to.equal(true);
253+
expect(businessResponse.maxSnoozeDurationHours).to.equal(90 * 24);
264254

265-
// ENTERPRISE plan should have snooze enabled
255+
// ENTERPRISE plan should have 90 days max snooze duration
266256
organizationRepository.findOne.resolves({ apiServiceLevel: ApiServiceLevelEnum.ENTERPRISE } as any);
267257
const enterpriseResponse: SubscriberSessionResponseDto = await session.execute(command);
268-
expect(enterpriseResponse.isSnoozeEnabled).to.equal(true);
258+
expect(enterpriseResponse.maxSnoozeDurationHours).to.equal(90 * 24);
269259
});
270260
});

apps/api/src/app/inbox/usecases/session/session.usecase.ts

+6-27
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,16 @@ import {
66
LogDecorator,
77
SelectIntegration,
88
SelectIntegrationCommand,
9-
FeatureFlagsService,
109
} from '@novu/application-generic';
1110
import {
1211
CommunityOrganizationRepository,
1312
EnvironmentEntity,
1413
EnvironmentRepository,
1514
IntegrationRepository,
16-
OrganizationEntity,
17-
UserEntity,
1815
} from '@novu/dal';
1916
import {
2017
ApiServiceLevelEnum,
2118
ChannelTypeEnum,
22-
FeatureFlagsKeysEnum,
2319
FeatureNameEnum,
2420
getFeatureForTierAsNumber,
2521
InAppProviderIdEnum,
@@ -46,8 +42,7 @@ export class Session {
4642
private analyticsService: AnalyticsService,
4743
private notificationsCount: NotificationsCount,
4844
private integrationRepository: IntegrationRepository,
49-
private organizationRepository: CommunityOrganizationRepository,
50-
private featureFlagsService: FeatureFlagsService
45+
private organizationRepository: CommunityOrganizationRepository
5146
) {}
5247

5348
@LogDecorator()
@@ -116,11 +111,7 @@ export class Session {
116111
const token = await this.authService.getSubscriberWidgetToken(subscriber);
117112

118113
const removeNovuBranding = inAppIntegration.removeNovuBranding || false;
119-
const isSnoozeEnabled = await this.isSnoozeEnabled(
120-
environment._organizationId,
121-
environment._id,
122-
command.subscriber.subscriberId
123-
);
114+
const maxSnoozeDurationHours = await this.getMaxSnoozeDurationHours(environment);
124115

125116
/**
126117
* We want to prevent the playground inbox demo from marking the integration as connected
@@ -151,34 +142,22 @@ export class Session {
151142
token,
152143
totalUnreadCount,
153144
removeNovuBranding,
154-
isSnoozeEnabled,
145+
maxSnoozeDurationHours,
155146
isDevelopmentMode: environment.name.toLowerCase() !== 'production',
156147
};
157148
}
158149

159-
private async isSnoozeEnabled(organizationId: string, environmentId: string, subscriberId: string) {
150+
private async getMaxSnoozeDurationHours(environment: EnvironmentEntity) {
160151
const organization = await this.organizationRepository.findOne({
161-
_id: organizationId,
162-
});
163-
164-
const isSnoozeEnabled = await this.featureFlagsService.getFlag({
165-
key: FeatureFlagsKeysEnum.IS_SNOOZE_ENABLED,
166-
defaultValue: false,
167-
organization: { _id: organizationId } as OrganizationEntity,
168-
environment: { _id: environmentId } as EnvironmentEntity,
169-
user: { _id: subscriberId } as UserEntity,
152+
_id: environment._organizationId,
170153
});
171154

172-
if (!isSnoozeEnabled) {
173-
return false;
174-
}
175-
176155
const tierLimitMs = getFeatureForTierAsNumber(
177156
FeatureNameEnum.PLATFORM_MAX_SNOOZE_DURATION,
178157
organization?.apiServiceLevel || ApiServiceLevelEnum.FREE,
179158
true
180159
);
181160

182-
return tierLimitMs > 0;
161+
return tierLimitMs / 1000 / 60 / 60;
183162
}
184163
}

0 commit comments

Comments
 (0)