Skip to content

Commit 47d4693

Browse files
authored
Merge pull request #15 from Dialogue-Bot/DIAL-18-Integrate-webhook-for-channel-Messenger
DIAL-18-Integrate-webhook-for-channel-Messenger
2 parents fa60e68 + 13a2c1d commit 47d4693

20 files changed

+1205
-5344
lines changed

server/package-lock.json

Lines changed: 839 additions & 5272 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@paralleldrive/cuid2": "^2.2.2",
3232
"@react-email/components": "^0.0.15",
3333
"@react-email/render": "0.0.7",
34+
"axios": "^1.6.7",
3435
"bcrypt": "^5.0.1",
3536
"bullmq": "^5.1.5",
3637
"class-transformer": "^0.5.1",
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { BOT_URL } from "@/config";
2+
import axios from "axios";
3+
4+
export class BaseChannel {
5+
id: string;
6+
contactId: string;
7+
contactName: string;
8+
channelType: string;
9+
10+
constructor(id: string, contactId: string, contactName: string, channelType: string,) {
11+
this.id = id;
12+
this.contactId = contactId;
13+
this.contactName = contactName;
14+
this.channelType = channelType;
15+
16+
if (channelType && contactName && contactId) {
17+
console.log(`Init channel ${channelType} - ${contactName} ${contactId}`);
18+
}
19+
}
20+
21+
public async postMessageToBot({ userId, data }) {
22+
try {
23+
const uId = this.initConversationId(userId);
24+
await axios({
25+
method: 'POST',
26+
url: BOT_URL,
27+
data: {
28+
conversation: {
29+
id: uId,
30+
},
31+
from: {
32+
id: userId,
33+
},
34+
recipient: {
35+
id: this.contactId,
36+
},
37+
type: 'event',
38+
name: 'payload',
39+
data,
40+
id: uId,
41+
channelId: this.channelType,
42+
serviceUrl: 'http://localhost:3000/api',
43+
},
44+
})
45+
} catch (error) {
46+
console.log(`Can not send data to bot!`, userId);
47+
console.log(error.message);
48+
}
49+
}
50+
51+
initConversationId(useId: string) {
52+
return this.contactId + '-' + useId
53+
}
54+
55+
// public async verifyWebhook(req: Request, res: Response) {
56+
// };
57+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Request, Response } from "express";
2+
import { BaseChannel } from "./base.channel";
3+
4+
export class MessengerChannel extends BaseChannel {
5+
pageToken: string;
6+
webhookSecret: string;
7+
messengerPostURL: string;
8+
credentials: string;
9+
10+
11+
constructor(id: string, contactId: string, contactName: string, channelType: string, credentials: string) {
12+
super(id, contactId, contactName, channelType);
13+
14+
let parseCredentials: MessengerChannel;
15+
16+
this.credentials = credentials;
17+
18+
if (credentials && typeof credentials == 'string') parseCredentials = JSON.parse(credentials);
19+
20+
if (parseCredentials) {
21+
this.pageToken = parseCredentials.pageToken;
22+
this.webhookSecret = parseCredentials.webhookSecret;
23+
}
24+
25+
this.channelType = channelType;
26+
this.messengerPostURL = `https://graph.facebook.com/v18.0/me/messages?access_token=`;
27+
}
28+
29+
public verifyWebhook(req: Request, res: Response) {
30+
let mode = req.query['hub.mode'];
31+
let token = req.query['hub.verify_token'];
32+
let challenge = req.query['hub.challenge'];
33+
34+
if (mode === 'subscribe' && this.webhookSecret == token) {
35+
console.log(`channel ${this.channelType} - ${this.contactName} ${this.contactId} webhook verified!`);
36+
return challenge;
37+
} else {
38+
console.error(`Verification channel ${this.channelType} - ${this.contactName} ${this.contactId} failed!`);
39+
return null;
40+
}
41+
}
42+
}

server/src/config/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ export const {
2727
FIREBASE_CLIENT_X509_CERT_URL,
2828
FIREBASE_UNIVERSE_DOMAIN,
2929
FIREBASE_DATABASE_URL,
30+
PUBLIC_DOMAIN,
31+
BOT_URL,
3032
} = process.env;

server/src/constants/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ export const ENDPOINTS = {
3131
INDEX: '/channel',
3232
DELETE: '/channel/delete',
3333
DELETES: '/channel/deletes',
34+
},
35+
WEBHOOK: {
36+
INDEX: '/webhook',
37+
VERIFY: '/webhook/:contactId',
3438
}
3539
};
3640

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { LOCALE_KEY } from "@/constants";
22
import { PagingDTO } from "@/dtos/paging.dto";
33
import { LocaleService } from "@/i18n/ctx";
4+
import { RequestWithUser } from "@/interfaces/auth.interface";
45
import { ChannelService } from "@/services/channels.service";
56
import { catchAsync } from "@/utils/catch-async";
67
import { plainToClass } from "class-transformer";
@@ -11,38 +12,40 @@ export class ChannelController {
1112
public channelService = Container.get(ChannelService);
1213
public localeService = Container.get<LocaleService>(LOCALE_KEY);
1314

14-
public createChannel = catchAsync(async (req, res) => {
15+
public createChannel = catchAsync(async (req: RequestWithUser, res) => {
16+
req.body.userId = req.user?.id;
1517
await this.channelService.create(req.body);
1618
res.status(StatusCodes.OK).json({
1719
message: this.localeService.i18n().CHANNEL.CREATE_SUCCESS(),
1820
});
1921
});
2022

21-
public updateChannel = catchAsync(async (req, res) => {
23+
public updateChannel = catchAsync(async (req: RequestWithUser, res) => {
24+
req.body.userId = req.user?.id;
2225
await this.channelService.updateById(req.params.id, req.body);
2326
res.status(StatusCodes.OK).json({
2427
message: this.localeService.i18n().CHANNEL.UPDATE_SUCCESS(),
2528
});
2629
})
2730

28-
public deleteChannel = catchAsync(async (req, res) => {
29-
await this.channelService.deleteById(req.params.id);
31+
public deleteChannel = catchAsync(async (req: RequestWithUser, res) => {
32+
await this.channelService.deleteById(req.params.id, req.user?.id as string);
3033
res.status(StatusCodes.OK).json({
3134
message: this.localeService.i18n().CHANNEL.DELETE_CHANNEL_SUCCESS(),
3235
});
3336
})
3437

35-
public deleteMultipleChannel = catchAsync(async (req, res) => {
36-
await this.channelService.deleteByIds(req.body.id);
38+
public deleteMultipleChannel = catchAsync(async (req: RequestWithUser, res) => {
39+
await this.channelService.deleteByIds(req.body.id, req.user?.id as string);
3740
res.status(StatusCodes.OK).json({
3841
message: this.localeService.i18n().CHANNEL.DELETE_MULTIPLE_CHANNELS_SUCCESS(),
3942
});
4043
})
4144

42-
public getChannelsPaging = catchAsync(async (req, res) => {
45+
public getAllChannels = catchAsync(async (req: RequestWithUser, res) => {
4346
const paging = plainToClass(PagingDTO, req.query);
4447

45-
const data = await this.channelService.getChannelsPaging(paging);
48+
const data = await this.channelService.getAllChannels(paging, req.user?.id as string);
4649
res.status(StatusCodes.OK).json({ data });
4750
})
4851
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { LOCALE_KEY } from "@/constants";
2+
import { LocaleService } from "@/i18n/ctx";
3+
import { WebhookService } from "@/services/webhook.service";
4+
import { catchAsync } from "@/utils/catch-async";
5+
import { StatusCodes } from "http-status-codes";
6+
import Container from "typedi";
7+
8+
export class WebhookController {
9+
public webhookService = Container.get(WebhookService);
10+
public localeService = Container.get<LocaleService>(LOCALE_KEY);
11+
12+
13+
public verifyWebhook = catchAsync(async (req, res) => {
14+
const data = await this.webhookService.verifyWebhook(req.params.contactId as string, req, res);
15+
16+
res.status(StatusCodes.OK).send(data);
17+
});
18+
}

server/src/database/schema.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export const channels = pgTable('channels', {
5959
active: boolean('active'),
6060
deleted: boolean('deleted').default(false),
6161
channelTypeId: text('channel_type_id').notNull(),
62+
userId: text('user_id').notNull(),
6263
createdAt: timestamp('created_at').defaultNow(),
6364
updatedAt: timestamp('updated_at'),
6465
});
@@ -67,9 +68,19 @@ export const channelTypesRelations = relations(channelTypes, ({ many }) => ({
6768
channels: many(channels),
6869
}));
6970

71+
export const usersRelations = relations(users, ({ many }) => ({
72+
channels: many(channels),
73+
}));
74+
7075
export const channelsRelations = relations(channels, ({ one }) => ({
7176
channelType: one(channelTypes, {
7277
fields: [channels.channelTypeId],
7378
references: [channelTypes.id],
7479
}),
80+
User: one(users, {
81+
fields: [channels.userId],
82+
references: [users.id]
83+
}),
7584
}));
85+
86+

server/src/database/seed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ async function seedChannelTypes() {
1919
async function seedDefaultAccount() {
2020
try {
2121
const hashedPassword = await bcrypt.hash("Hello@123", 10);
22-
await db.insert(users).values({email: "[email protected]", password: hashedPassword, name: "admin",});
22+
await db.insert(users).values({ email: "[email protected]", password: hashedPassword, name: "admin", });
2323
} catch (error) {
2424
console.error(`Can't create default account`);
2525
}

0 commit comments

Comments
 (0)