Skip to content

Commit f5b1d16

Browse files
authored
WIP: Crisp mailchimp messaging service (#673)
1 parent 636b56c commit f5b1d16

36 files changed

+821
-336
lines changed

docs/key-concepts.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ User authentication is handled by [Firebase Auth](https://firebase.google.com/do
1818

1919
Crisp is the messaging platform used to message the Chayn team in relation to bloom course content or other queries and support. For public users, this 1-1 chat feature is available on _live_ courses only. For partner users, This 1-1 chat feature is available to users with a `PartnerAccess` that has 1-1 chat enabled.
2020

21-
Users who have access to 1-1 chat also have a profile on Crisp that reflects data from our database regarding their partners, access and course progress. See [crisp-api.ts](src/api/crisp/crisp-api.ts)
21+
Users who have access to 1-1 chat also have a profile on Crisp that reflects data from our database regarding their partners, access and course progress. See [crisp.service.ts](src/crisp/crisp.service.ts)
2222

2323
### Reporting
2424

2525
Google Data Studio is an online tool for converting data into customizable informative reports and dashboards.
2626

2727
The reports are generated by writing custom sql queries that return actionable data to Data Studio. Filters are applied in Data Studio allowing
28-
data to be segregated into different time periods based on the data createdAt date
28+
data to be segregated into different time periods based on the data createdAt date

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"axios": "^1.7.7",
4242
"class-transformer": "^0.5.1",
4343
"class-validator": "^0.14.1",
44+
"crisp-api": "^9.2.0",
4445
"date-fns": "^3.6.0",
4546
"dotenv": "^16.4.5",
4647
"firebase": "^10.10.0",

src/api/crisp/crisp-api.ts

-127
This file was deleted.

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

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export enum MAILCHIMP_MERGE_FIELD_TYPES {
1414
ZIP = 'zip',
1515
}
1616

17+
export enum MAILCHIMP_CUSTOM_EVENTS {
18+
CRISP_MESSAGE_RECEIVED = 'CRISP_MESSAGE_RECEIVED',
19+
}
20+
1721
export interface ListMemberCustomFields {
1822
NAME?: string;
1923
SIGNUPD?: string;

src/api/mailchimp/mailchimp-api.ts

+11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Logger } from '../../logger/logger';
55
import {
66
ListMember,
77
ListMemberPartial,
8+
MAILCHIMP_CUSTOM_EVENTS,
89
MAILCHIMP_MERGE_FIELD_TYPES,
910
UpdateListMemberRequest,
1011
} from './mailchimp-api.interfaces';
@@ -128,3 +129,13 @@ export const deleteCypressMailchimpProfiles = async () => {
128129
throw new Error(`Delete cypress mailchimp profiles API call failed: ${error}`);
129130
}
130131
};
132+
133+
export const sendMailchimpUserEvent = async (email: string, event: MAILCHIMP_CUSTOM_EVENTS) => {
134+
try {
135+
await mailchimp.lists.createListMemberEvent(mailchimpAudienceId, getEmailMD5Hash(email), {
136+
name: event,
137+
});
138+
} catch (error) {
139+
throw new Error(`Send mailchimp user event failed: ${error}`);
140+
}
141+
};

src/app.module.ts

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { AuthModule } from './auth/auth.module';
66
import { CoursePartnerModule } from './course-partner/course-partner.module';
77
import { CourseUserModule } from './course-user/course-user.module';
88
import { CourseModule } from './course/course.module';
9+
import { CrispListenerModule } from './crisp-listener/crisp-listener.module';
10+
import { CrispModule } from './crisp/crisp.module';
911
import { EventLoggerModule } from './event-logger/event-logger.module';
1012
import { FeatureModule } from './feature/feature.module';
1113
import { HealthModule } from './health/health.module';
@@ -43,6 +45,8 @@ import { WebhooksModule } from './webhooks/webhooks.module';
4345
PartnerFeatureModule,
4446
EventLoggerModule,
4547
HealthModule,
48+
CrispModule,
49+
CrispListenerModule,
4650
],
4751
})
4852
export class AppModule {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Module } from '@nestjs/common';
2+
import { TypeOrmModule } from '@nestjs/typeorm';
3+
import { CrispService } from 'src/crisp/crisp.service';
4+
import { EventLogEntity } from 'src/entities/event-log.entity';
5+
import { UserEntity } from 'src/entities/user.entity';
6+
import { EventLoggerService } from 'src/event-logger/event-logger.service';
7+
import { CrispListenerService } from './crisp-listener.service';
8+
9+
@Module({
10+
imports: [TypeOrmModule.forFeature([EventLogEntity, UserEntity])],
11+
providers: [CrispService, CrispListenerService, EventLoggerService],
12+
})
13+
export class CrispListenerModule {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
2+
import Crisp from 'crisp-api';
3+
import { EVENT_NAME } from 'src/crisp/crisp.interface';
4+
import { CrispService } from 'src/crisp/crisp.service';
5+
import { CrispEventDto } from 'src/crisp/dtos/crisp.dto';
6+
import { crispPluginId, crispPluginKey } from 'src/utils/constants';
7+
const CrispClient = new Crisp();
8+
const logger = new Logger('CrispLogger');
9+
10+
// This service is split from CrispService due to CrispService being imported/initiated multiple times
11+
// To avoid creating duplicate listeners and events, this CrispListenerService was decoupled
12+
@Injectable()
13+
export class CrispListenerService implements OnModuleInit {
14+
constructor(private crispService: CrispService) {
15+
CrispClient.authenticateTier('plugin', crispPluginId, crispPluginKey);
16+
}
17+
18+
onModuleInit() {
19+
logger.log(`Crisp service initiated`);
20+
21+
try {
22+
const handleCrispEvent = async (message, eventName) =>
23+
await this.crispService.handleCrispEvent(message, eventName);
24+
25+
CrispClient.on('message:send', async function (message: CrispEventDto) {
26+
handleCrispEvent(message, EVENT_NAME.CHAT_MESSAGE_SENT);
27+
})
28+
.then(function () {
29+
logger.log('Crisp service listening to sent messages');
30+
})
31+
.catch(function (error) {
32+
logger.error('Crisp service failed listening to sent messages:', error);
33+
});
34+
35+
CrispClient.on('message:received', function (message: CrispEventDto) {
36+
handleCrispEvent(message, EVENT_NAME.CHAT_MESSAGE_RECEIVED);
37+
})
38+
.then(function () {
39+
logger.log('Crisp service listening to received messages');
40+
})
41+
.catch(function (error) {
42+
logger.error('Crisp service failed listening to sent messages:', error);
43+
});
44+
} catch (error) {
45+
logger.error('Crisp service failed to initiate:', error);
46+
}
47+
}
48+
}

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

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
1-
import { EMAIL_REMINDERS_FREQUENCY } from '../../utils/constants';
1+
import { EMAIL_REMINDERS_FREQUENCY } from 'src/utils/constants';
2+
3+
export enum EVENT_NAME {
4+
CHAT_MESSAGE_SENT = 'CHAT_MESSAGE_SENT',
5+
CHAT_MESSAGE_RECEIVED = 'CHAT_MESSAGE_RECEIVED',
6+
LOGGED_IN = 'LOGGED_IN',
7+
LOGGED_OUT = 'LOGGED_OUT',
8+
}
9+
10+
export interface ICreateEventLog {
11+
date: Date | string;
12+
event: EVENT_NAME;
13+
userId: string;
14+
}
215

316
export interface CrispProfileCustomFields {
417
signed_up_at?: string;
@@ -32,8 +45,8 @@ export interface CrispProfileCustomFields {
3245
export interface CrispProfileBase {
3346
email?: string;
3447
person?: {
35-
nickname: string;
36-
locales: string[];
48+
nickname?: string;
49+
locales?: string[];
3750
};
3851
segments?: string[];
3952
notepad?: string;

src/crisp/crisp.module.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Module } from '@nestjs/common';
2+
import { TypeOrmModule } from '@nestjs/typeorm';
3+
import { EventLogEntity } from 'src/entities/event-log.entity';
4+
import { UserEntity } from 'src/entities/user.entity';
5+
import { EventLoggerService } from 'src/event-logger/event-logger.service';
6+
import { CrispService } from './crisp.service';
7+
8+
@Module({
9+
imports: [TypeOrmModule.forFeature([EventLogEntity, UserEntity])],
10+
providers: [CrispService, EventLoggerService],
11+
})
12+
export class CrispModule {}

0 commit comments

Comments
 (0)