From f47ab15a3b9b0f68a8968737c69150ff33123d4a Mon Sep 17 00:00:00 2001 From: soyombo <40427263+soyombo-baterdene@users.noreply.github.com> Date: Wed, 29 Jul 2020 11:14:19 +0800 Subject: [PATCH 1/6] productBoard integration --- src/debuggers.ts | 1 + src/index.ts | 4 ++++ src/productBoard/controller.ts | 40 ++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 src/productBoard/controller.ts diff --git a/src/debuggers.ts b/src/debuggers.ts index ccdd2492..c5cb7911 100644 --- a/src/debuggers.ts +++ b/src/debuggers.ts @@ -15,6 +15,7 @@ export const debugWhatsapp = debug('erxes-integrations:whatsapp'); export const debugExternalRequests = debug('erxes-integrations:external-requests'); export const debugDaily = debug('erxes-integrations:daily'); export const debugSmooch = debug('erxes-integrations:smooch'); +export const debugProductBoard = debug('erxes-integrations:productBoard'); export const debugRequest = (debugInstance, req) => debugInstance(` diff --git a/src/index.ts b/src/index.ts index c620188a..6de81aef 100755 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ import { initConsumer, rabbitMQStatus } from './messageBroker'; import Accounts from './models/Accounts'; import Configs from './models/Configs'; import { initNylas } from './nylas/controller'; +import initProductBoard from './productBoard/controller'; import { initRedis, redisStatus } from './redisClient'; import initSmooch from './smooch/controller'; import { init } from './startup'; @@ -149,6 +150,9 @@ initDaily(app); // init smooch initSmooch(app); +// init product board +initProductBoard(app); + // Error handling middleware app.use((error, _req, res, _next) => { console.error(error.stack); diff --git a/src/productBoard/controller.ts b/src/productBoard/controller.ts new file mode 100644 index 00000000..ddc632f7 --- /dev/null +++ b/src/productBoard/controller.ts @@ -0,0 +1,40 @@ +import { debugProductBoard, debugRequest } from '../debuggers'; +import { getConfig, getEnv, sendRequest } from '../utils'; + +const init = async app => { + app.post('/productBoard/create-note', async (req, res, next) => { + debugRequest(debugProductBoard, req); + + const PRODUCT_BOARD_TOKEN = await getConfig('PRODUCT_BOARD_TOKEN'); + const MAIN_APP_DOMAIN = getEnv({ name: 'MAIN_APP_DOMAIN' }); + + const { customer, content, tags } = req.body; + + try { + await sendRequest({ + url: 'https://api.productboard.com/notes', + method: 'POST', + headerParams: { + authorization: `Bearer ${PRODUCT_BOARD_TOKEN}`, + }, + body: { + title: `Erxes message from ${customer.firstName}`, + content, + customer_email: customer.email, + display_url: `${MAIN_APP_DOMAIN}/inbox/?_id=${customer._id}`, + source: { + origin: 'erxes inbox', + customerId: customer._id, + }, + tags, + }, + }); + } catch (e) { + next(e); + } + + return res.json({ status: 'ok' }); + }); +}; + +export default init; From bd795a469235390f45189fad4b26a1ac5e762524 Mon Sep 17 00:00:00 2001 From: soyombo <40427263+soyombo-baterdene@users.noreply.github.com> Date: Wed, 29 Jul 2020 20:05:45 +0800 Subject: [PATCH 2/6] Update controller.ts --- src/productBoard/controller.ts | 43 ++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/productBoard/controller.ts b/src/productBoard/controller.ts index ddc632f7..b3a2a2c9 100644 --- a/src/productBoard/controller.ts +++ b/src/productBoard/controller.ts @@ -8,32 +8,55 @@ const init = async app => { const PRODUCT_BOARD_TOKEN = await getConfig('PRODUCT_BOARD_TOKEN'); const MAIN_APP_DOMAIN = getEnv({ name: 'MAIN_APP_DOMAIN' }); - const { customer, content, tags } = req.body; + const { customer, messages, tags, erxesApiConversationId, user } = req.body; + + let content = ''; + + for (const message of messages) { + const messageDate = new Date(message.createdAt).toUTCString(); + + if (message.customerId) { + content = content.concat(`${customer.primaryEmail} ${messageDate}

${message.content}


`); + } else { + content = content.concat(`${user.details.fullName} ${messageDate}

${message.content}


`); + } + + if (message.attachments) { + for (const attachment of message.attachments) { + content = content.concat(`${attachment.name}
`); + } + } + } + + const origin = messages[messages.length - 1].content; try { - await sendRequest({ + const result = await sendRequest({ url: 'https://api.productboard.com/notes', method: 'POST', headerParams: { authorization: `Bearer ${PRODUCT_BOARD_TOKEN}`, }, body: { - title: `Erxes message from ${customer.firstName}`, + title: messages[0].content, content, - customer_email: customer.email, - display_url: `${MAIN_APP_DOMAIN}/inbox/?_id=${customer._id}`, + customer_email: customer.primaryEmail, + display_url: `${MAIN_APP_DOMAIN}/inbox/?_id=${erxesApiConversationId}`, source: { - origin: 'erxes inbox', - customerId: customer._id, + origin, + record_id: erxesApiConversationId, }, - tags, + tags: tags.map(tag => tag.name), }, }); + + return res.send(result.links.html); } catch (e) { + if (e.statusCode === 422) { + next(new Error('already exists')); + } next(e); } - - return res.json({ status: 'ok' }); }); }; From 4b0b3614580d590677783d06f791f73e31e3a5e5 Mon Sep 17 00:00:00 2001 From: soyombo <40427263+soyombo-baterdene@users.noreply.github.com> Date: Thu, 30 Jul 2020 03:13:48 +0800 Subject: [PATCH 3/6] fix --- src/productBoard/controller.ts | 18 +++++++++++++ src/productBoard/models.ts | 47 ++++++++++++++++++++++++++++++++++ src/whatsapp/store.ts | 1 - 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/productBoard/models.ts diff --git a/src/productBoard/controller.ts b/src/productBoard/controller.ts index b3a2a2c9..c54ef8e2 100644 --- a/src/productBoard/controller.ts +++ b/src/productBoard/controller.ts @@ -1,7 +1,19 @@ import { debugProductBoard, debugRequest } from '../debuggers'; import { getConfig, getEnv, sendRequest } from '../utils'; +import { Conversations } from './models'; const init = async app => { + app.get('/productBoard/note', async (req, res) => { + const { erxesApiId } = req.query; + try { + const conversation = await Conversations.findOne({ erxesApiId }); + + return res.send(conversation.productBoardLink); + } catch (e) { + return res.send(''); + } + }); + app.post('/productBoard/create-note', async (req, res, next) => { debugRequest(debugProductBoard, req); @@ -50,6 +62,12 @@ const init = async app => { }, }); + await Conversations.create({ + timestamp: new Date(), + erxesApiId: erxesApiConversationId, + productBoardLink: result.links.html, + }); + return res.send(result.links.html); } catch (e) { if (e.statusCode === 422) { diff --git a/src/productBoard/models.ts b/src/productBoard/models.ts new file mode 100644 index 00000000..aa0afeb0 --- /dev/null +++ b/src/productBoard/models.ts @@ -0,0 +1,47 @@ +import { Document, Model, model, Schema } from 'mongoose'; +import { field } from '../models/utils'; + +export interface IConversation { + erxesApiId?: string; + timestamp: Date; + productBoardLink?: string; +} + +export interface IConversationDocument extends IConversation, Document {} + +export const conversationSchema = new Schema({ + _id: field({ pkey: true }), + erxesApiId: String, + timestamp: Date, + productBoardLink: String, +}); + +// conversationSchema.index({ instanceId: 1, recipientId: 1 }, { unique: true }); + +export interface IConversationModel extends Model { + getConversation(selector): Promise; +} + +export const loadConversationClass = () => { + class Conversation { + public static async getConversation(selector) { + const conversation = await Conversations.findOne(selector); + + if (!conversation) { + throw new Error('Conversation not found'); + } + + return conversation; + } + } + + conversationSchema.loadClass(Conversation); + + return conversationSchema; +}; + +// tslint:disable-next-line:variable-name +export const Conversations = model( + 'conversations_productboard', + conversationSchema, +); diff --git a/src/whatsapp/store.ts b/src/whatsapp/store.ts index 04f5294f..2cf006d2 100644 --- a/src/whatsapp/store.ts +++ b/src/whatsapp/store.ts @@ -122,7 +122,6 @@ export const createMessage = async (message, conversationIds) => { let attachments = []; if (message.type !== 'chat') { - console.log('body: ', message.body); attachments = [{ type: message.type, url: message.body }]; message.body = ''; } From 2770818335bbf6a77b4e631c33ab9a47e3b916e7 Mon Sep 17 00:00:00 2001 From: soyombo <40427263+soyombo-baterdene@users.noreply.github.com> Date: Thu, 30 Jul 2020 09:56:04 +0800 Subject: [PATCH 4/6] Update controller.ts --- src/productBoard/controller.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/productBoard/controller.ts b/src/productBoard/controller.ts index c54ef8e2..3a89077e 100644 --- a/src/productBoard/controller.ts +++ b/src/productBoard/controller.ts @@ -72,6 +72,8 @@ const init = async app => { } catch (e) { if (e.statusCode === 422) { next(new Error('already exists')); + } else if (e.statusCode === 401) { + next(new Error('Please enter the product board access token in system config.')); } next(e); } From a83b59d16901c0e0ea919cc533891046094e569c Mon Sep 17 00:00:00 2001 From: soyombo <40427263+soyombo-baterdene@users.noreply.github.com> Date: Fri, 7 Aug 2020 15:44:15 +0800 Subject: [PATCH 5/6] Update controller.ts --- src/productBoard/controller.ts | 47 +++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/productBoard/controller.ts b/src/productBoard/controller.ts index 3a89077e..5d86c6d4 100644 --- a/src/productBoard/controller.ts +++ b/src/productBoard/controller.ts @@ -1,8 +1,11 @@ import { debugProductBoard, debugRequest } from '../debuggers'; +import { Integrations } from '../models'; import { getConfig, getEnv, sendRequest } from '../utils'; -import { Conversations } from './models'; +import { ALL_MODELS, Conversations } from './models'; const init = async app => { + const mailIntegrations = ['office365', 'yahoo', 'outlook', 'imap', 'exchange', 'gmail']; + app.get('/productBoard/note', async (req, res) => { const { erxesApiId } = req.query; try { @@ -20,11 +23,39 @@ const init = async app => { const PRODUCT_BOARD_TOKEN = await getConfig('PRODUCT_BOARD_TOKEN'); const MAIN_APP_DOMAIN = getEnv({ name: 'MAIN_APP_DOMAIN' }); - const { customer, messages, tags, erxesApiConversationId, user } = req.body; - + const { customer, tags, erxesApiConversationId, user, integrationId } = req.body; + let { messages } = req.body; let content = ''; + let title = ''; + + const integration = await Integrations.findOne({ erxesApiId: integrationId }).lean(); + + let origin = messages[messages.length - 1].content; + + if (integration) { + if (integration.kind !== 'facebook-post') { + const conversations = ALL_MODELS[integration.kind].conversations; + const conversation = await conversations.findOne({ erxesApiId: erxesApiConversationId }).lean(); + + const conversationMessages = ALL_MODELS[integration.kind].conversationMessages; + messages = await conversationMessages.find({ conversationId: conversation._id }); + } else { + const post = await ALL_MODELS['facebook-post'].posts.findOne({ erxesApiId: integrationId }).lean(); + + const comments = await ALL_MODELS['facebook-post'].comments.find({ postId: post._id }); + + for (const comment of comments) { + const commentDate = new Date(comment.timestamp).toUTCString(); + content = content.concat(`${commentDate}

${comment.content}


`); + } + origin = post.postId; + title = post.content; + } + } for (const message of messages) { + title = messages[0].content; + const messageDate = new Date(message.createdAt).toUTCString(); if (message.customerId) { @@ -38,9 +69,13 @@ const init = async app => { content = content.concat(`${attachment.name}
`); } } - } - const origin = messages[messages.length - 1].content; + if (mailIntegrations.some(i => integration.kind === i)) { + title = messages[0].subject; + content = content.concat(`${message.from[0]} ${messageDate}

${message.body}


`); + origin = messages[messages.length - 1].subject; + } + } try { const result = await sendRequest({ @@ -50,7 +85,7 @@ const init = async app => { authorization: `Bearer ${PRODUCT_BOARD_TOKEN}`, }, body: { - title: messages[0].content, + title, content, customer_email: customer.primaryEmail, display_url: `${MAIN_APP_DOMAIN}/inbox/?_id=${erxesApiConversationId}`, From 646c0cd0b49d3205649c097c68bcc952494d58cc Mon Sep 17 00:00:00 2001 From: soyombo <40427263+soyombo-baterdene@users.noreply.github.com> Date: Fri, 7 Aug 2020 15:44:24 +0800 Subject: [PATCH 6/6] Update models.ts --- src/productBoard/models.ts | 124 ++++++++++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/src/productBoard/models.ts b/src/productBoard/models.ts index aa0afeb0..73cec3b8 100644 --- a/src/productBoard/models.ts +++ b/src/productBoard/models.ts @@ -1,6 +1,56 @@ import { Document, Model, model, Schema } from 'mongoose'; +import { + Comments, + ConversationMessages as FacebookMessengerConversationMessages, + Conversations as FacebookMessengerConversations, + Customers as FacebookMessengerCustomers, + Posts, +} from '../facebook/models'; import { field } from '../models/utils'; - +import { + NylasExchangeConversationMessages, + NylasExchangeConversations, + NylasExchangeCustomers, + NylasGmailConversationMessages, + NylasGmailConversations, + NylasGmailCustomers, + NylasImapConversationMessages, + NylasImapConversations, + NylasImapCustomers, + NylasOffice365ConversationMessages, + NylasOffice365Conversations, + NylasOffice365Customers, + NylasOutlookConversationMessages, + NylasOutlookConversations, + NylasOutlookCustomers, + NylasYahooConversationMessages, + NylasYahooConversations, + NylasYahooCustomers, +} from '../nylas/models'; +import { + SmoochLineConversationMessages, + SmoochLineConversations, + SmoochLineCustomers, + SmoochTelegramConversationMessages, + SmoochTelegramConversations, + SmoochTelegramCustomers, + SmoochTwilioConversationMessages, + SmoochTwilioConversations, + SmoochTwilioCustomers, + SmoochViberConversationMessages, + SmoochViberConversations, + SmoochViberCustomers, +} from '../smooch/models'; +import { + ConversationMessages as TwitterConversationMessages, + Conversations as TwitterConversations, + Customers as TwitterCustomers, +} from '../twitter/models'; +import { + ConversationMessages as WhatsAppConversationMessages, + Conversations as WhatsAppConversations, + Customers as WhatsAppCustomers, +} from '../whatsapp/models'; export interface IConversation { erxesApiId?: string; timestamp: Date; @@ -45,3 +95,75 @@ export const Conversations = model( 'conversations_productboard', conversationSchema, ); + +export const ALL_MODELS = { + gmail: { + customers: NylasGmailCustomers, + conversations: NylasGmailConversations, + conversationMessages: NylasGmailConversationMessages, + }, + exchange: { + customers: NylasExchangeCustomers, + conversations: NylasExchangeConversations, + conversationMessages: NylasExchangeConversationMessages, + }, + imap: { + customers: NylasImapCustomers, + conversations: NylasImapConversations, + conversationMessages: NylasImapConversationMessages, + }, + outlook: { + customers: NylasOutlookCustomers, + conversations: NylasOutlookConversations, + conversationMessages: NylasOutlookConversationMessages, + }, + yahoo: { + customers: NylasYahooCustomers, + conversations: NylasYahooConversations, + conversationMessages: NylasYahooConversationMessages, + }, + office365: { + customers: NylasOffice365Customers, + conversations: NylasOffice365Conversations, + conversationMessages: NylasOffice365ConversationMessages, + }, + telegram: { + customers: SmoochTelegramCustomers, + conversations: SmoochTelegramConversations, + conversationMessages: SmoochTelegramConversationMessages, + }, + viber: { + customers: SmoochViberCustomers, + conversations: SmoochViberConversations, + conversationMessages: SmoochViberConversationMessages, + }, + line: { + customers: SmoochLineCustomers, + conversations: SmoochLineConversations, + conversationMessages: SmoochLineConversationMessages, + }, + twilio: { + customers: SmoochTwilioCustomers, + conversations: SmoochTwilioConversations, + conversationMessages: SmoochTwilioConversationMessages, + }, + whatsapp: { + customers: WhatsAppCustomers, + conversations: WhatsAppConversations, + conversationMessages: WhatsAppConversationMessages, + }, + twitter: { + customers: TwitterCustomers, + conversations: TwitterConversations, + conversationMessages: TwitterConversationMessages, + }, + 'facebook-messenger': { + customers: FacebookMessengerCustomers, + conversations: FacebookMessengerConversations, + conversationMessages: FacebookMessengerConversationMessages, + }, + 'facebook-post': { + posts: Posts, + comments: Comments, + }, +};