Skip to content

Commit 0881cba

Browse files
authored
feat: emailRemindersFrequency field (#486)
1 parent b9d81c8 commit 0881cba

19 files changed

+113
-24
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ To generate a new migration
186186
yarn migration:generate
187187
```
188188

189+
Add the new migration import into [typeorm.config.ts](./src/typeorm.config.ts)
190+
189191
To run (apply) migrations
190192

191193
```bash

src/api/crisp/crisp-api.interfaces.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import { EMAIL_REMINDERS_FREQUENCY } from '../../utils/constants';
2+
13
export interface CrispProfileCustomFields {
24
signed_up_at?: string;
35
last_active_at?: string;
6+
email_reminders_frequency?: EMAIL_REMINDERS_FREQUENCY;
47
language?: string;
58
marketing_permission?: boolean;
69
service_emails_permission?: boolean;

src/api/mailchimp/mailchimp-api.interfaces.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { EMAIL_REMINDERS_FREQUENCY } from '../../utils/constants';
2+
13
export enum MAILCHIMP_MERGE_FIELD_TYPES {
24
TEXT = 'text',
35
NUMBER = 'number',
@@ -16,6 +18,7 @@ export interface ListMemberCustomFields {
1618
NAME?: string;
1719
SIGNUPD?: string;
1820
LACTIVED?: string;
21+
REMINDFREQ?: EMAIL_REMINDERS_FREQUENCY;
1922
PARTNERS?: string;
2023
FEATTHER?: string;
2124
FEATCHAT?: string;

src/api/mailchimp/mailchimp-api.ts

-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ export const batchCreateMailchimpProfiles = async (users: UserEntity[]) => {
5959
console.log('Mailchimp batch response:', batchResponse);
6060
}, 120000);
6161
} catch (error) {
62-
console.log(error);
6362
throw new Error(`Batch create mailchimp profiles API call failed: ${error}`);
6463
}
6564
};

src/entities/user.entity.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Column, Entity, Generated, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
22
import { PartnerAccessEntity } from '../entities/partner-access.entity';
33
import { PartnerAdminEntity } from '../entities/partner-admin.entity';
4+
import { EMAIL_REMINDERS_FREQUENCY } from '../utils/constants';
45
import { BaseBloomEntity } from './base.entity';
56
import { CourseUserEntity } from './course-user.entity';
67
import { EventLogEntity } from './event-log.entity';
@@ -30,6 +31,9 @@ export class UserEntity extends BaseBloomEntity {
3031
@Column({ default: true })
3132
serviceEmailsPermission: boolean; // service emails consent - mapped to mailchimp status field
3233

34+
@Column({ default: EMAIL_REMINDERS_FREQUENCY.NEVER })
35+
emailRemindersFrequency: EMAIL_REMINDERS_FREQUENCY;
36+
3337
@Column({ type: Boolean, default: false })
3438
isSuperAdmin: boolean;
3539

src/logger/logger.ts

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export class Logger extends ConsoleLogger {
3737
accessToken: rollbarToken,
3838
captureUncaught: true,
3939
captureUnhandledRejections: true,
40+
captureIp: 'anonymize',
4041
ignoredMessages: [...Object.values(FIREBASE_ERRORS)],
4142
});
4243
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
3+
export class BloomBackend1718728423454 implements MigrationInterface {
4+
name = 'BloomBackend1718728423454'
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(`ALTER TABLE "user" ADD "emailRemindersFrequency" character varying NOT NULL DEFAULT 'NEVER'`);
8+
}
9+
10+
public async down(queryRunner: QueryRunner): Promise<void> {
11+
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "emailRemindersFrequency"`);
12+
}
13+
14+
}

src/partner-admin/partner-admin-auth.guard.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { DecodedIdToken } from 'firebase-admin/lib/auth/token-verifier';
44
import { AuthService } from 'src/auth/auth.service';
55
import { PartnerAdminEntity } from 'src/entities/partner-admin.entity';
66
import { UserEntity } from 'src/entities/user.entity';
7+
import { EMAIL_REMINDERS_FREQUENCY } from 'src/utils/constants';
78
import { Repository } from 'typeorm';
89
import { createQueryBuilderMock } from '../../test/utils/mockUtils';
910
import { PartnerAdminAuthGuard } from './partner-admin-auth.guard';
@@ -17,6 +18,7 @@ const userEntity: UserEntity = {
1718
name: 'name',
1819
contactPermission: false,
1920
serviceEmailsPermission: true,
21+
emailRemindersFrequency: EMAIL_REMINDERS_FREQUENCY.TWO_MONTHS,
2022
isSuperAdmin: false,
2123
crispTokenId: '123',
2224
partnerAccess: [],

src/typeorm.config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { bloomBackend1697818259254 } from './migrations/1697818259254-bloom-back
4545
import { bloomBackend1698136145516 } from './migrations/1698136145516-bloom-backend';
4646
import { bloomBackend1706174260018 } from './migrations/1706174260018-bloom-backend';
4747
import { BloomBackend1718300621138 } from './migrations/1718300621138-bloom-backend';
48+
import { BloomBackend1718728423454 } from './migrations/1718728423454-bloom-backend';
4849

4950
config();
5051
const configService = new ConfigService();
@@ -110,6 +111,7 @@ export const dataSourceOptions = {
110111
bloomBackend1698136145516,
111112
bloomBackend1706174260018,
112113
BloomBackend1718300621138,
114+
BloomBackend1718728423454,
113115
],
114116
subscribers: [],
115117
ssl: isProduction,

src/user/dtos/create-user.dto.ts

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ApiProperty } from '@nestjs/swagger';
22
import { IsBoolean, IsDefined, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
3+
import { EMAIL_REMINDERS_FREQUENCY } from '../../utils/constants';
34

45
export class CreateUserDto {
56
@IsString()
@@ -40,6 +41,11 @@ export class CreateUserDto {
4041
@ApiProperty({ type: Boolean })
4142
serviceEmailsPermission: boolean;
4243

44+
@IsOptional()
45+
@IsString()
46+
@ApiProperty({ type: String })
47+
emailRemindersFrequency: EMAIL_REMINDERS_FREQUENCY;
48+
4349
@IsOptional()
4450
@IsString()
4551
@ApiProperty({ type: String })

src/user/dtos/update-user.dto.ts

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ApiProperty } from '@nestjs/swagger';
22
import { IsBoolean, IsDate, IsOptional, IsString } from 'class-validator';
3+
import { EMAIL_REMINDERS_FREQUENCY } from '../../utils/constants';
34

45
export class UpdateUserDto {
56
@IsString()
@@ -17,6 +18,11 @@ export class UpdateUserDto {
1718
@ApiProperty({ type: Boolean })
1819
serviceEmailsPermission: boolean;
1920

21+
@IsOptional()
22+
@IsString()
23+
@ApiProperty({ type: String })
24+
emailRemindersFrequency: EMAIL_REMINDERS_FREQUENCY;
25+
2026
@IsString()
2127
@IsOptional()
2228
@ApiProperty({ type: String })

src/user/user.interface.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { EMAIL_REMINDERS_FREQUENCY } from '../utils/constants';
2+
13
export interface IUser {
24
id: string;
35
createdAt: Date | string;
@@ -10,4 +12,5 @@ export interface IUser {
1012
crispTokenId: string;
1113
isSuperAdmin: boolean;
1214
signUpLanguage: string;
15+
emailRemindersFrequency: EMAIL_REMINDERS_FREQUENCY;
1316
}

src/user/user.service.spec.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { PartnerAccessEntity } from 'src/entities/partner-access.entity';
99
import { PartnerEntity } from 'src/entities/partner.entity';
1010
import { SubscriptionUserService } from 'src/subscription-user/subscription-user.service';
1111
import { TherapySessionService } from 'src/therapy-session/therapy-session.service';
12-
import { PartnerAccessCodeStatusEnum } from 'src/utils/constants';
12+
import { EMAIL_REMINDERS_FREQUENCY, PartnerAccessCodeStatusEnum } from 'src/utils/constants';
1313
import {
1414
mockIFirebaseUser,
1515
mockPartnerAccessEntity,
@@ -39,6 +39,7 @@ const createUserDto: CreateUserDto = {
3939
name: 'name',
4040
contactPermission: false,
4141
serviceEmailsPermission: true,
42+
emailRemindersFrequency: EMAIL_REMINDERS_FREQUENCY.TWO_MONTHS,
4243
signUpLanguage: 'en',
4344
};
4445

@@ -168,6 +169,7 @@ describe('UserService', () => {
168169
last_active_at: (user.user.lastActiveAt as Date).toISOString(),
169170
marketing_permission: true,
170171
service_emails_permission: true,
172+
email_reminders_frequency: EMAIL_REMINDERS_FREQUENCY.TWO_MONTHS,
171173
partners: 'bumble',
172174
feature_live_chat: true,
173175
feature_therapy: true,

src/user/user.service.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -197,16 +197,15 @@ export class UserService {
197197
if (!user) {
198198
throw new HttpException('USER NOT FOUND', HttpStatus.NOT_FOUND);
199199
}
200-
201200
const newUserData: UserEntity = {
202201
...user,
203202
...updateUserDto,
204203
};
205204
const updatedUser = await this.userRepository.save(newUserData);
206205

207206
const isCrispBaseUpdateRequired =
208-
(user.signUpLanguage !== updateUserDto.signUpLanguage && user.name !== updateUserDto.name) ||
209-
user.lastActiveAt !== updateUserDto.lastActiveAt;
207+
user.signUpLanguage !== updateUserDto.signUpLanguage && user.name !== updateUserDto.name;
208+
210209
updateServiceUserProfilesUser(user, isCrispBaseUpdateRequired, user.email);
211210

212211
return updatedUser;
@@ -332,7 +331,6 @@ export class UserService {
332331
});
333332
const usersWithCourseUsers = users.filter((user) => user.courseUser.length > 0);
334333

335-
console.log(usersWithCourseUsers);
336334
await batchCreateMailchimpProfiles(usersWithCourseUsers);
337335
this.logger.log(
338336
`Created batch mailchimp profiles for ${usersWithCourseUsers.length} users, created before ${filterStartDate}`,

src/utils/constants.ts

+19-12
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,31 @@
11
import dotenv from 'dotenv';
22
dotenv.config();
33

4+
export enum ENVIRONMENTS {
5+
DEVELOPMENT = 'development',
6+
STAGING = 'staging',
7+
PRODUCTION = 'production',
8+
TEST = 'test',
9+
}
10+
411
export enum SIGNUP_TYPE {
512
PUBLIC_USER = 'PUBLIC_USER',
613
PARTNER_USER_WITH_CODE = 'PARTNER_USER_WITH_CODE',
714
PARTNER_USER_WITHOUT_CODE = 'PARTNER_USER_WITHOUT_CODE',
815
}
916

17+
export enum LANGUAGE_DEFAULT {
18+
EN = 'en',
19+
ES = 'es',
20+
}
21+
22+
export enum EMAIL_REMINDERS_FREQUENCY {
23+
TWO_WEEKS = 'TWO_WEEKS',
24+
ONE_MONTH = 'ONE_MONTH',
25+
TWO_MONTHS = 'TWO_MONTHS',
26+
NEVER = 'NEVER',
27+
}
28+
1029
export enum FEATURES {
1130
AUTOMATIC_ACCESS_CODE = 'AUTOMATIC_ACCESS_CODE',
1231
}
@@ -30,11 +49,6 @@ export enum STORYBLOK_STORY_STATUS_ENUM {
3049
DELETED = 'deleted',
3150
}
3251

33-
export enum LANGUAGE_DEFAULT {
34-
EN = 'en',
35-
ES = 'es',
36-
}
37-
3852
export enum PartnerAccessCodeStatusEnum {
3953
VALID = 'VALID',
4054
INVALID_CODE = 'INVALID_CODE',
@@ -53,13 +67,6 @@ export enum COMMUNICATION_SERVICE {
5367
MAILCHIMP = 'MAILCHIMP',
5468
}
5569

56-
export enum ENVIRONMENTS {
57-
DEVELOPMENT = 'development',
58-
STAGING = 'staging',
59-
PRODUCTION = 'production',
60-
TEST = 'test',
61-
}
62-
6370
const getEnv = (env: string, envName: string): string => {
6471
try {
6572
if (!env) throw `Unable to get environment variable ${envName}`;

src/utils/serialize.ts

+2
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export const formatUserObject = (userObject: UserEntity): GetUserDto => {
9191
crispTokenId: userObject.crispTokenId,
9292
isSuperAdmin: userObject.isSuperAdmin,
9393
signUpLanguage: userObject.signUpLanguage,
94+
emailRemindersFrequency: userObject.emailRemindersFrequency,
9495
},
9596
partnerAccesses: userObject.partnerAccess
9697
? formatPartnerAccessObjects(userObject.partnerAccess)
@@ -127,6 +128,7 @@ export const formatGetUsersObject = (userObject: UserEntity): GetUserDto => {
127128
crispTokenId: userObject.crispTokenId,
128129
isSuperAdmin: userObject.isSuperAdmin,
129130
signUpLanguage: userObject.signUpLanguage,
131+
emailRemindersFrequency: userObject.emailRemindersFrequency,
130132
},
131133
...(userObject.partnerAccess
132134
? {

src/utils/serviceUserProfiles.spec.ts

+21-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ import {
1616
mockPartnerEntity,
1717
mockUserEntity,
1818
} from 'test/utils/mockData';
19-
import { SIMPLYBOOK_ACTION_ENUM, mailchimpMarketingPermissionId } from './constants';
19+
import {
20+
EMAIL_REMINDERS_FREQUENCY,
21+
SIMPLYBOOK_ACTION_ENUM,
22+
mailchimpMarketingPermissionId,
23+
} from './constants';
2024
import {
2125
createMailchimpCourseMergeField,
2226
createServiceUserProfiles,
@@ -52,6 +56,7 @@ describe('Service user profiles', () => {
5256
{
5357
marketing_permission: mockUserEntity.contactPermission,
5458
service_emails_permission: mockUserEntity.serviceEmailsPermission,
59+
email_reminders_frequency: EMAIL_REMINDERS_FREQUENCY.TWO_MONTHS,
5560
signed_up_at: createdAt,
5661
last_active_at: lastActiveAt,
5762
feature_live_chat: true,
@@ -83,6 +88,7 @@ describe('Service user profiles', () => {
8388
PARTNERS: '',
8489
THERREMAIN: 0,
8590
THERREDEEM: 0,
91+
REMINDFREQ: EMAIL_REMINDERS_FREQUENCY.TWO_MONTHS,
8692
},
8793
});
8894
});
@@ -105,6 +111,7 @@ describe('Service user profiles', () => {
105111
signed_up_at: createdAt,
106112
marketing_permission: mockUserEntity.contactPermission,
107113
service_emails_permission: mockUserEntity.serviceEmailsPermission,
114+
email_reminders_frequency: EMAIL_REMINDERS_FREQUENCY.TWO_MONTHS,
108115
partners: partnerName,
109116
last_active_at: lastActiveAt,
110117
feature_live_chat: mockPartnerAccessEntity.featureLiveChat,
@@ -135,6 +142,7 @@ describe('Service user profiles', () => {
135142
FEATTHER: String(mockPartnerAccessEntity.featureTherapy),
136143
THERREMAIN: mockPartnerAccessEntity.therapySessionsRemaining,
137144
THERREDEEM: mockPartnerAccessEntity.therapySessionsRedeemed,
145+
REMINDFREQ: EMAIL_REMINDERS_FREQUENCY.TWO_MONTHS,
138146
},
139147
});
140148
});
@@ -157,6 +165,7 @@ describe('Service user profiles', () => {
157165
{
158166
marketing_permission: mockUserEntity.contactPermission,
159167
service_emails_permission: mockUserEntity.serviceEmailsPermission,
168+
email_reminders_frequency: EMAIL_REMINDERS_FREQUENCY.TWO_MONTHS,
160169
last_active_at: lastActiveAt,
161170
},
162171
mockUserEntity.email,
@@ -173,7 +182,11 @@ describe('Service user profiles', () => {
173182
enabled: mockUserEntity.contactPermission,
174183
},
175184
],
176-
merge_fields: { NAME: mockUserEntity.name, LACTIVED: lastActiveAt },
185+
merge_fields: {
186+
NAME: mockUserEntity.name,
187+
LACTIVED: lastActiveAt,
188+
REMINDFREQ: EMAIL_REMINDERS_FREQUENCY.TWO_MONTHS,
189+
},
177190
},
178191
mockUserEntity.email,
179192
);
@@ -194,6 +207,7 @@ describe('Service user profiles', () => {
194207
marketing_permission: false,
195208
service_emails_permission: false,
196209
last_active_at: lastActiveAt,
210+
email_reminders_frequency: EMAIL_REMINDERS_FREQUENCY.TWO_MONTHS,
197211
},
198212
mockUser.email,
199213
);
@@ -209,7 +223,11 @@ describe('Service user profiles', () => {
209223
enabled: false,
210224
},
211225
],
212-
merge_fields: { NAME: mockUser.name, LACTIVED: lastActiveAt },
226+
merge_fields: {
227+
NAME: mockUser.name,
228+
LACTIVED: lastActiveAt,
229+
REMINDFREQ: EMAIL_REMINDERS_FREQUENCY.TWO_MONTHS,
230+
},
213231
},
214232
mockUser.email,
215233
);

0 commit comments

Comments
 (0)