Skip to content

Commit 9796bb0

Browse files
committed
Merge remote-tracking branch 'origin/DIAL-33-Add-API-for-botflow' into DIAL-32-ui-chat-bot
2 parents 8890879 + 386fefa commit 9796bb0

29 files changed

+1684
-342
lines changed

server/src/channels/base.channel.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { BOT_ENDPOINT, PUBLIC_DOMAIN } from "@/config";
2+
import { logger } from "@/utils/logger";
23
import axios from "axios";
34

45
export class BaseChannel {
@@ -39,14 +40,10 @@ export class BaseChannel {
3940
},
4041
})
4142
if (postMsg.data.success) {
42-
console.log(
43-
`[${this.channelType} - ${this.contactName} ${this.contactId}] - [Conversation ID: ${uid}] - [Send message to bot - Message: ${message}] - [Data: ${data}]`
44-
)
43+
logger.info(`[${this.channelType}] User ${userId} send message to Bot - Message: ${message} - Data: ${data}`)
4544
}
4645
} catch (error) {
47-
console.log(
48-
`[${this.channelType} - ${this.contactName} ${this.contactId}] - [Conversation ID: ${uid}] - [Can not send message to bot - Message: ${message}] - [Error: ${error.message}]`
49-
);
46+
logger.info(`[${this.channelType}] User ${userId} can not send message to Bot - Message: ${message} - Error: ${error.message}`);
5047
}
5148
}
5249

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { PUBLIC_DOMAIN } from "@/config";
2+
import { logger } from "@/utils/logger";
3+
import axios from "axios";
4+
import { Request, Response } from "express";
5+
import { BaseChannel } from "./base.channel";
6+
7+
export class LineChannel extends BaseChannel {
8+
pageToken: string;
9+
linePostURL: string;
10+
credentials: string;
11+
12+
constructor(id: string, contactId: string, contactName: string, channelType: string, credentials: string) {
13+
super(id, contactId, contactName, channelType);
14+
15+
let parseCredentials: LineChannel;
16+
17+
this.credentials = credentials;
18+
19+
if (credentials && typeof credentials == 'string') parseCredentials = JSON.parse(credentials);
20+
21+
if (parseCredentials) {
22+
this.pageToken = parseCredentials.pageToken;
23+
}
24+
25+
this.channelType = channelType;
26+
this.linePostURL = `https://api.line.me/v2/bot`;
27+
}
28+
29+
public async registerWebhook() {
30+
try {
31+
await axios({
32+
method: 'PUT',
33+
url: this.linePostURL + '/channel/webhook/endpoint',
34+
data: { endpoint: PUBLIC_DOMAIN + '/webhook/' + this.contactId },
35+
headers: {
36+
Authorization: `Bearer ${this.pageToken}`,
37+
'Content-Type': 'application/json',
38+
},
39+
});
40+
logger.info(`[LIN] Registered webhook for ${this.channelType} - ${this.contactName} ${this.contactId}`);
41+
} catch (e) {
42+
logger.info(`[LIN] Can not register webhook for ${this.channelType} - ${this.contactName} ${this.contactId}`);
43+
44+
}
45+
}
46+
47+
async getLineUserID() {
48+
try {
49+
const { data } = await axios({
50+
method: 'GET',
51+
url: this.linePostURL + '/info',
52+
headers: {
53+
Authorization: `Bearer ${this.pageToken}`,
54+
},
55+
});
56+
if (!data || !data.userId) throw new Error();
57+
58+
return data.userId;
59+
} catch (e) {
60+
logger.info(`[LIN] Can not get user ID for ${this.channelType} - ${this.contactName} ${this.contactId}`);
61+
}
62+
}
63+
64+
async prepareMessage(req: Request, res: Response) {
65+
try {
66+
const { destination, events } = req.body;
67+
68+
if (!(events && events[0] && events[0].type == 'message')) return;
69+
70+
const lineUserId = await this.getLineUserID();
71+
72+
if (destination == lineUserId) {
73+
const { message, source } = events[0];
74+
75+
await this.postMessageToBot({ userId: source.userId, message: message.text, data: null });
76+
}
77+
} catch (e) { }
78+
}
79+
80+
public async sendMessageToUser({ userId, text }) {
81+
const lineUserId = await this.getLineUserID();
82+
try {
83+
if (!text) return;
84+
85+
await axios({
86+
method: 'POST',
87+
url: this.linePostURL + '/message/push',
88+
data: {
89+
to: userId,
90+
messages: [{ type: 'text', text }],
91+
},
92+
headers: {
93+
Authorization: 'Bearer ' + this.pageToken,
94+
},
95+
})
96+
97+
logger.info(`[LIN] Bot send message to User ${lineUserId} - Message: ${text}`);
98+
} catch (e) {
99+
logger.info(`[LIN] Bot send message to User ${lineUserId} failed - Error: ${e.message}`);
100+
}
101+
}
102+
}

server/src/channels/messenger.channel.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { Helper } from "@/utils/helper";
2+
import { logger } from "@/utils/logger";
3+
import axios from "axios";
24
import { Request, Response } from "express";
35
import { BaseChannel } from "./base.channel";
46

@@ -32,10 +34,10 @@ export class MessengerChannel extends BaseChannel {
3234
let challenge = req.query['hub.challenge'];
3335

3436
if (mode === 'subscribe' && this.webhookSecret == token) {
35-
console.log(`channel ${this.channelType} - ${this.contactName} ${this.contactId} webhook verified!`);
37+
logger.info(`[MSG] channel ${this.channelType} - ${this.contactName} ${this.contactId} webhook verified!`);
3638
return challenge;
3739
} else {
38-
console.error(`Verification channel ${this.channelType} - ${this.contactName} ${this.contactId} failed!`);
40+
console.error(`[MSG] Verification channel ${this.channelType} - ${this.contactName} ${this.contactId} failed!`);
3941
return;
4042
}
4143
}
@@ -72,4 +74,50 @@ export class MessengerChannel extends BaseChannel {
7274
sendAddressToBot({ userId, address }) {
7375
return this.postMessageToBot({ userId, message: 'ADDRESS', data: { USER_INFORMATION: Helper.arrayToObj(address) } });
7476
}
77+
78+
public async sendMessageToUser({ userId, text }) {
79+
if (!text) return;
80+
81+
try {
82+
await axios({
83+
method: 'POST',
84+
url: this.messengerPostURL + this.pageToken,
85+
data: {
86+
messaging_type: 'RESPONSE',
87+
recipient: {
88+
id: userId,
89+
},
90+
message: { text },
91+
},
92+
});
93+
94+
logger.info(`[MSG] Bot Sent message to User ${userId} - Message: ${text}`);
95+
} catch (e) {
96+
logger.info(`[MSG] Bot Sent message to User ${userId} failed - Error: ${e.message}`);
97+
}
98+
}
99+
100+
public async sendActionToUser({ userId, type }) {
101+
const types = {
102+
typing: 'TYPING_ON',
103+
};
104+
105+
if (!types[type]) return;
106+
107+
try {
108+
await axios({
109+
method: 'POST',
110+
url: this.messengerPostURL + this.pageToken,
111+
data: {
112+
messaging_type: 'RESPONSE',
113+
recipient: {
114+
id: userId,
115+
},
116+
sender_action: types[type],
117+
},
118+
});
119+
} catch (e) {
120+
logger.info(`[MSG] Messenger can not send action to User - Error: ${e.message}`);
121+
}
122+
}
75123
}

server/src/constants/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ export const ENDPOINTS = {
4646
VERIFY: '/webhook/:contactId',
4747
INCOMING_MSG: '/webhook/:contactId',
4848
},
49+
CONVERSATION: {
50+
INDEX: '/v3/conversations'
51+
},
52+
FLOW: {
53+
INDEX: '/flow',
54+
GET_ONE: '/flow/get-one',
55+
PUBLISH: '/flow/publish-flow',
56+
ADD_CHANNELS: '/flow/add-channels',
57+
SELECT_FLOWS_FOR_CHANNEL: '/flow/select'
58+
}
4959
};
5060

5161
export const LOCALE_KEY = 'lang';
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { LOCALE_KEY } from "@/constants";
2+
import { LocaleService } from "@/i18n/ctx";
3+
import { ConversationService } from "@/services/conversation.service";
4+
import { catchAsync } from "@/utils/catch-async";
5+
import { StatusCodes } from "http-status-codes";
6+
import Container from "typedi";
7+
8+
export class ConversationController {
9+
public conversationService = Container.get(ConversationService);
10+
public localeService = Container.get<LocaleService>(LOCALE_KEY);
11+
12+
public handleIncomingMessage = catchAsync(async (req, res) => {
13+
res.status(StatusCodes.OK).end();
14+
return await this.conversationService.handleIncomingMessage(req);
15+
});
16+
17+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { LOCALE_KEY } from '@/constants';
2+
import { PagingDTO } from '@/dtos/paging.dto';
3+
import { LocaleService } from '@/i18n/ctx';
4+
import { RequestWithUser } from '@/interfaces/auth.interface';
5+
import { FlowService } from '@/services/flows.service';
6+
import { catchAsync } from '@/utils/catch-async';
7+
import { plainToClass } from 'class-transformer';
8+
import { StatusCodes } from 'http-status-codes';
9+
import Container from 'typedi';
10+
11+
export class FlowController {
12+
public flowService = Container.get(FlowService);
13+
public localeService = Container.get<LocaleService>(LOCALE_KEY);
14+
15+
public createFlow = catchAsync(async (req: RequestWithUser, res) => {
16+
req.body.userId = req.user?.id;
17+
await this.flowService.create(req.body);
18+
res.status(StatusCodes.OK).json({
19+
message: this.localeService.i18n().FLOW.CREATE_SUCCESS(),
20+
});
21+
});
22+
23+
public updateFlow = catchAsync(async (req: RequestWithUser, res) => {
24+
req.body.userId = req.user?.id;
25+
await this.flowService.updateFlowById(req.params.id, req.body);
26+
res.status(StatusCodes.OK).json({
27+
message: this.localeService.i18n().FLOW.UPDATE_SUCCESS(),
28+
});
29+
});
30+
31+
public deleteFlow = catchAsync(async (req: RequestWithUser, res) => {
32+
await this.flowService.deleteById(
33+
req.params.id,
34+
req.user?.id as string
35+
);
36+
res.status(StatusCodes.OK).json({
37+
message: this.localeService.i18n().FLOW.DELETE_FLOW_SUCCESS(),
38+
});
39+
});
40+
41+
public publishFlow = catchAsync(async (req: RequestWithUser, res) => {
42+
await this.flowService.publishFlow(
43+
req.params.id,
44+
req.user?.id as string
45+
)
46+
res.status(StatusCodes.OK).json({
47+
message: this.localeService.i18n().FLOW.PUBLISH_FLOW_SUCCESS(),
48+
});
49+
});
50+
51+
public getFlowById = catchAsync(async (req: RequestWithUser, res) => {
52+
const data = await this.flowService.getFlowById(
53+
req.params.id,
54+
req.user?.id as string
55+
)
56+
res.status(StatusCodes.OK).json({ data });
57+
});
58+
59+
public getAllFlows = catchAsync(async (req: RequestWithUser, res) => {
60+
const paging = plainToClass(PagingDTO, req.query);
61+
62+
const data = await this.flowService.getAllFlows(
63+
paging,
64+
req.user?.id as string
65+
);
66+
res.status(StatusCodes.OK).json({ data });
67+
});
68+
69+
public addMultipleChannels = catchAsync(async (req: RequestWithUser, res) => {
70+
await this.flowService.addMultipleChannels(
71+
req.body.channelIds,
72+
req.body.flowId,
73+
req.user?.id as string
74+
);
75+
res.status(StatusCodes.OK).json({
76+
message: this.localeService.i18n().FLOW.ADD_MULTIPLE_CHANNELS_FLOW__SUCCESS(),
77+
});
78+
});
79+
80+
public selectFlowsForChannel = catchAsync(async (req: RequestWithUser, res) => {
81+
const data = await this.flowService.selectFlowsForChannel(
82+
req.user?.id as string
83+
);
84+
res.status(StatusCodes.OK).json({ data });
85+
});
86+
}

server/src/controllers/webhook.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ export class WebhookController {
1717
});
1818

1919
public handleIncomingMessage = catchAsync(async (req, res) => {
20-
res.status(StatusCodes.OK);
2120

22-
return await this.webhookService.handleIncomingMessage(req.params.contactId, req, res);
21+
await this.webhookService.handleIncomingMessage(req.params.contactId, req, res);
22+
res.status(StatusCodes.OK).end();
2323
});
2424
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
CREATE TABLE IF NOT EXISTS "flows" (
2+
"id" varchar(36) PRIMARY KEY NOT NULL,
3+
"name" text NOT NULL,
4+
"user_id" varchar(36) NOT NULL,
5+
"deleted" boolean DEFAULT false,
6+
"updated_at" timestamp,
7+
"created_at" timestamp DEFAULT now(),
8+
"diagrams" json DEFAULT '[]'::json,
9+
"edges" json DEFAULT '[]'::json,
10+
"nodes" json DEFAULT '[]'::json,
11+
"settings" json DEFAULT '[]'::json,
12+
"variables" json DEFAULT '{}'::json,
13+
"flows" json DEFAULT '[]'::json,
14+
"publish_at" timestamp
15+
);
16+
--> statement-breakpoint
17+
ALTER TABLE "channels" ADD COLUMN "flow_id" text;--> statement-breakpoint
18+
DO $$ BEGIN
19+
ALTER TABLE "channels" ADD CONSTRAINT "channels_channel_type_id_channel_types_id_fk" FOREIGN KEY ("channel_type_id") REFERENCES "channel_types"("id") ON DELETE no action ON UPDATE no action;
20+
EXCEPTION
21+
WHEN duplicate_object THEN null;
22+
END $$;
23+
--> statement-breakpoint
24+
DO $$ BEGIN
25+
ALTER TABLE "channels" ADD CONSTRAINT "channels_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action;
26+
EXCEPTION
27+
WHEN duplicate_object THEN null;
28+
END $$;
29+
--> statement-breakpoint
30+
DO $$ BEGIN
31+
ALTER TABLE "channels" ADD CONSTRAINT "channels_flow_id_flows_id_fk" FOREIGN KEY ("flow_id") REFERENCES "flows"("id") ON DELETE no action ON UPDATE no action;
32+
EXCEPTION
33+
WHEN duplicate_object THEN null;
34+
END $$;
35+
--> statement-breakpoint
36+
DO $$ BEGIN
37+
ALTER TABLE "flows" ADD CONSTRAINT "flows_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE no action ON UPDATE no action;
38+
EXCEPTION
39+
WHEN duplicate_object THEN null;
40+
END $$;

0 commit comments

Comments
 (0)