diff --git a/backend/src/controllers/QueueController.ts b/backend/src/controllers/QueueController.ts index 412840f..de489c7 100644 --- a/backend/src/controllers/QueueController.ts +++ b/backend/src/controllers/QueueController.ts @@ -11,9 +11,9 @@ export const index = async (req: Request, res: Response): Promise => { }; export const store = async (req: Request, res: Response): Promise => { - const { name, color } = req.body; + const { name, color, greetingMessage } = req.body; - const queue = await Queue.create({ name, color }); + const queue = await Queue.create({ name, color, greetingMessage }); return res.status(200).json(queue); }; diff --git a/backend/src/controllers/WhatsAppController.ts b/backend/src/controllers/WhatsAppController.ts index 2ccad1f..5759109 100644 --- a/backend/src/controllers/WhatsAppController.ts +++ b/backend/src/controllers/WhatsAppController.ts @@ -9,9 +9,14 @@ import ListWhatsAppsService from "../services/WhatsappService/ListWhatsAppsServi import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService"; import UpdateWhatsAppService from "../services/WhatsappService/UpdateWhatsAppService"; +interface QueueData { + id: number; + optionNumber: number; +} interface WhatsappData { name: string; - queueIds: number[]; + queuesData: QueueData[]; + greetingMessage?: string; status?: string; isDefault?: boolean; } @@ -23,13 +28,20 @@ export const index = async (req: Request, res: Response): Promise => { }; export const store = async (req: Request, res: Response): Promise => { - const { name, status, isDefault, queueIds }: WhatsappData = req.body; + const { + name, + status, + isDefault, + greetingMessage, + queuesData + }: WhatsappData = req.body; const { whatsapp, oldDefaultWhatsapp } = await CreateWhatsAppService({ name, status, isDefault, - queueIds + greetingMessage, + queuesData }); // StartWhatsAppSession(whatsapp); diff --git a/backend/src/database/migrations/20210108164404-create-queues.ts b/backend/src/database/migrations/20210108164404-create-queues.ts index 41681d5..4a404d6 100644 --- a/backend/src/database/migrations/20210108164404-create-queues.ts +++ b/backend/src/database/migrations/20210108164404-create-queues.ts @@ -19,6 +19,9 @@ module.exports = { allowNull: false, unique: true }, + greetingMessage: { + type: DataTypes.TEXT + }, createdAt: { type: DataTypes.DATE, allowNull: false diff --git a/backend/src/database/migrations/20210108174594-associate-whatsapp-queue.ts b/backend/src/database/migrations/20210108174594-associate-whatsapp-queue.ts index 0e08f71..0ea50f0 100644 --- a/backend/src/database/migrations/20210108174594-associate-whatsapp-queue.ts +++ b/backend/src/database/migrations/20210108174594-associate-whatsapp-queue.ts @@ -3,6 +3,9 @@ import { QueryInterface, DataTypes } from "sequelize"; module.exports = { up: (queryInterface: QueryInterface) => { return queryInterface.createTable("WhatsappQueues", { + optionNumber: { + type: DataTypes.INTEGER + }, whatsappId: { type: DataTypes.INTEGER, primaryKey: true diff --git a/backend/src/database/migrations/20210109192513-add-greetingMessage-to-whatsapp.ts b/backend/src/database/migrations/20210109192513-add-greetingMessage-to-whatsapp.ts new file mode 100644 index 0000000..6d3c3be --- /dev/null +++ b/backend/src/database/migrations/20210109192513-add-greetingMessage-to-whatsapp.ts @@ -0,0 +1,13 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.addColumn("Whatsapps", "greetingMessage", { + type: DataTypes.TEXT + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.removeColumn("Whatsapps", "greetingMessage"); + } +}; diff --git a/backend/src/models/Queue.ts b/backend/src/models/Queue.ts index 65d3548..334abb2 100644 --- a/backend/src/models/Queue.ts +++ b/backend/src/models/Queue.ts @@ -8,7 +8,8 @@ import { AutoIncrement, AllowNull, Unique, - BelongsToMany + BelongsToMany, + HasMany } from "sequelize-typescript"; import User from "./User"; import UserQueue from "./UserQueue"; @@ -33,6 +34,9 @@ class Queue extends Model { @Column color: string; + @Column + greetingMessage: string; + @CreatedAt createdAt: Date; diff --git a/backend/src/models/Whatsapp.ts b/backend/src/models/Whatsapp.ts index ba6febb..3c76a0c 100644 --- a/backend/src/models/Whatsapp.ts +++ b/backend/src/models/Whatsapp.ts @@ -47,6 +47,9 @@ class Whatsapp extends Model { @Column retries: number; + @Column(DataType.TEXT) + greetingMessage: string; + @Default(false) @AllowNull @Column @@ -63,6 +66,9 @@ class Whatsapp extends Model { @BelongsToMany(() => Queue, () => WhatsappQueue) queues: Array; + + @HasMany(() => WhatsappQueue) + whatsappQueues: WhatsappQueue[]; } export default Whatsapp; diff --git a/backend/src/models/WhatsappQueue.ts b/backend/src/models/WhatsappQueue.ts index 17479cc..7886618 100644 --- a/backend/src/models/WhatsappQueue.ts +++ b/backend/src/models/WhatsappQueue.ts @@ -4,13 +4,17 @@ import { CreatedAt, UpdatedAt, Model, - ForeignKey + ForeignKey, + BelongsTo } from "sequelize-typescript"; import Queue from "./Queue"; import Whatsapp from "./Whatsapp"; @Table class WhatsappQueue extends Model { + @Column + optionNumber: number; + @ForeignKey(() => Whatsapp) @Column whatsappId: number; @@ -24,6 +28,9 @@ class WhatsappQueue extends Model { @UpdatedAt updatedAt: Date; + + @BelongsTo(() => Queue) + queue: Queue; } export default WhatsappQueue; diff --git a/backend/src/services/QueueService/AssociateWhatsappQueue.ts b/backend/src/services/QueueService/AssociateWhatsappQueue.ts new file mode 100644 index 0000000..45a5a84 --- /dev/null +++ b/backend/src/services/QueueService/AssociateWhatsappQueue.ts @@ -0,0 +1,32 @@ +import Whatsapp from "../../models/Whatsapp"; +import WhatsappQueue from "../../models/WhatsappQueue"; + +interface QueueData { + id: number; + optionNumber: number; +} + +const AssociateWhatsappQueue = async ( + whatsapp: Whatsapp, + queuesData: QueueData[] +): Promise => { + const queueIds = queuesData.map(({ id }) => id); + + await whatsapp.$set("queues", queueIds); + + /* eslint-disable no-restricted-syntax */ + /* eslint-disable no-await-in-loop */ + for (const queueData of queuesData) { + await WhatsappQueue.update( + { optionNumber: queueData.optionNumber }, + { + where: { + whatsappId: whatsapp.id, + queueId: queueData.id + } + } + ); + } +}; + +export default AssociateWhatsappQueue; diff --git a/backend/src/services/TicketServices/FindOrCreateTicketService.ts b/backend/src/services/TicketServices/FindOrCreateTicketService.ts index 9d0ba89..bf4c2b0 100644 --- a/backend/src/services/TicketServices/FindOrCreateTicketService.ts +++ b/backend/src/services/TicketServices/FindOrCreateTicketService.ts @@ -16,23 +16,19 @@ const FindOrCreateTicketService = async ( [Op.or]: ["open", "pending"] }, contactId: groupContact ? groupContact.id : contact.id - }, - include: ["contact"] + } }); if (ticket) { await ticket.update({ unreadMessages }); - - return ticket; } - if (groupContact) { + if (!ticket && groupContact) { ticket = await Ticket.findOne({ where: { contactId: groupContact.id }, - order: [["updatedAt", "DESC"]], - include: ["contact"] + order: [["updatedAt", "DESC"]] }); if (ticket) { @@ -41,10 +37,10 @@ const FindOrCreateTicketService = async ( userId: null, unreadMessages }); - - return ticket; } - } else { + } + + if (!ticket && !groupContact) { ticket = await Ticket.findOne({ where: { updatedAt: { @@ -52,8 +48,7 @@ const FindOrCreateTicketService = async ( }, contactId: contact.id }, - order: [["updatedAt", "DESC"]], - include: ["contact"] + order: [["updatedAt", "DESC"]] }); if (ticket) { @@ -62,20 +57,20 @@ const FindOrCreateTicketService = async ( userId: null, unreadMessages }); - - return ticket; } } - const { id } = await Ticket.create({ - contactId: groupContact ? groupContact.id : contact.id, - status: "pending", - isGroup: !!groupContact, - unreadMessages, - whatsappId - }); + if (!ticket) { + ticket = await Ticket.create({ + contactId: groupContact ? groupContact.id : contact.id, + status: "pending", + isGroup: !!groupContact, + unreadMessages, + whatsappId + }); + } - ticket = await ShowTicketService(id); + ticket = await ShowTicketService(ticket.id); return ticket; }; diff --git a/backend/src/services/TicketServices/ShowTicketService.ts b/backend/src/services/TicketServices/ShowTicketService.ts index dd0cc30..5efab0c 100644 --- a/backend/src/services/TicketServices/ShowTicketService.ts +++ b/backend/src/services/TicketServices/ShowTicketService.ts @@ -2,6 +2,7 @@ import Ticket from "../../models/Ticket"; import AppError from "../../errors/AppError"; import Contact from "../../models/Contact"; import User from "../../models/User"; +import Queue from "../../models/Queue"; const ShowTicketService = async (id: string | number): Promise => { const ticket = await Ticket.findByPk(id, { @@ -16,6 +17,11 @@ const ShowTicketService = async (id: string | number): Promise => { model: User, as: "user", attributes: ["id", "name"] + }, + { + model: Queue, + as: "queue", + attributes: ["id", "name", "color"] } ] }); diff --git a/backend/src/services/WbotServices/StartAllWhatsAppsSessions.ts b/backend/src/services/WbotServices/StartAllWhatsAppsSessions.ts index a472f9f..9e5e935 100644 --- a/backend/src/services/WbotServices/StartAllWhatsAppsSessions.ts +++ b/backend/src/services/WbotServices/StartAllWhatsAppsSessions.ts @@ -1,8 +1,8 @@ -import Whatsapp from "../../models/Whatsapp"; +import ListWhatsAppsService from "../WhatsappService/ListWhatsAppsService"; import { StartWhatsAppSession } from "./StartWhatsAppSession"; export const StartAllWhatsAppsSessions = async (): Promise => { - const whatsapps = await Whatsapp.findAll(); + const whatsapps = await ListWhatsAppsService(); if (whatsapps.length > 0) { whatsapps.forEach(whatsapp => { StartWhatsAppSession(whatsapp); diff --git a/backend/src/services/WbotServices/wbotMessageListener.ts b/backend/src/services/WbotServices/wbotMessageListener.ts index ac687e4..76f8904 100644 --- a/backend/src/services/WbotServices/wbotMessageListener.ts +++ b/backend/src/services/WbotServices/wbotMessageListener.ts @@ -19,6 +19,7 @@ import CreateMessageService from "../MessageServices/CreateMessageService"; import { logger } from "../../utils/logger"; import CreateOrUpdateContactService from "../ContactServices/CreateOrUpdateContactService"; import FindOrCreateTicketService from "../TicketServices/FindOrCreateTicketService"; +import ShowWhatsAppService from "../WhatsappService/ShowWhatsAppService"; interface Session extends Client { id?: number; @@ -126,6 +127,54 @@ const verifyMessage = async ( await CreateMessageService({ messageData }); }; +const verifyQueue = async ( + wbot: Session, + msg: WbotMessage, + ticket: Ticket, + contact: Contact +) => { + const { whatsappQueues, greetingMessage } = await ShowWhatsAppService( + wbot.id! + ); + + if (whatsappQueues.length === 1) { + await ticket.$set("queue", whatsappQueues[0].queue); + // TODO sendTicketQueueUpdate to frontend + + return; + } + + const selectedOption = msg.body[0]; + + const validOption = whatsappQueues.find( + q => q.optionNumber === +selectedOption + ); + + if (validOption) { + await ticket.$set("queue", validOption.queue); + + const body = `\u200e ${validOption.queue.greetingMessage}`; + + const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body); + + await verifyMessage(sentMessage, ticket, contact); + + // TODO sendTicketQueueUpdate to frontend + } else { + let options = ""; + + whatsappQueues.forEach(whatsQueue => { + options += `*${whatsQueue.optionNumber}* - ${whatsQueue.queue.name}\n`; + }); + + const body = `\u200e ${greetingMessage}\n\n${options}`; + + const sentMessage = await wbot.sendMessage(`${contact.number}@c.us`, body); + + await verifyMessage(sentMessage, ticket, contact); + } +}; + const isValidMsg = (msg: WbotMessage): boolean => { if (msg.from === "status@broadcast") return false; if ( @@ -146,64 +195,64 @@ const handleMessage = async ( msg: WbotMessage, wbot: Session ): Promise => { - return new Promise((resolve, reject) => { - (async () => { - if (!isValidMsg(msg)) { - return; + if (!isValidMsg(msg)) { + return; + } + + try { + let msgContact: WbotContact; + let groupContact: Contact | undefined; + + if (msg.fromMe) { + // messages sent automatically by wbot have a special character in front of it + // if so, this message was already been stored in database; + if (/\u200e/.test(msg.body[0])) return; + + // media messages sent from me from cell phone, first comes with "hasMedia = false" and type = "image/ptt/etc" + // in this case, return and let this message be handled by "media_uploaded" event, when it will have "hasMedia = true" + + if (!msg.hasMedia && msg.type !== "chat" && msg.type !== "vcard") return; + + msgContact = await wbot.getContactById(msg.to); + } else { + msgContact = await msg.getContact(); + } + + const chat = await msg.getChat(); + + if (chat.isGroup) { + let msgGroupContact; + + if (msg.fromMe) { + msgGroupContact = await wbot.getContactById(msg.to); + } else { + msgGroupContact = await wbot.getContactById(msg.from); } - try { - let msgContact: WbotContact; - let groupContact: Contact | undefined; + groupContact = await verifyContact(msgGroupContact); + } - if (msg.fromMe) { - // media messages sent from me from cell phone, first comes with "hasMedia = false" and type = "image/ptt/etc" - // in this case, return and let this message be handled by "media_uploaded" event, when it will have "hasMedia = true" + const contact = await verifyContact(msgContact); + const ticket = await FindOrCreateTicketService( + contact, + wbot.id!, + chat.unreadCount, + groupContact + ); - if (!msg.hasMedia && msg.type !== "chat" && msg.type !== "vcard") - return; + if (msg.hasMedia) { + await verifyMediaMessage(msg, ticket, contact); + } else { + await verifyMessage(msg, ticket, contact); + } - msgContact = await wbot.getContactById(msg.to); - } else { - msgContact = await msg.getContact(); - } - - const chat = await msg.getChat(); - - if (chat.isGroup) { - let msgGroupContact; - - if (msg.fromMe) { - msgGroupContact = await wbot.getContactById(msg.to); - } else { - msgGroupContact = await wbot.getContactById(msg.from); - } - - groupContact = await verifyContact(msgGroupContact); - } - - const contact = await verifyContact(msgContact); - const ticket = await FindOrCreateTicketService( - contact, - wbot.id!, - chat.unreadCount, - groupContact - ); - - if (msg.hasMedia) { - await verifyMediaMessage(msg, ticket, contact); - resolve(); - } else { - await verifyMessage(msg, ticket, contact); - resolve(); - } - } catch (err) { - Sentry.captureException(err); - logger.error(`Error handling whatsapp message: Err: ${err}`); - reject(err); - } - })(); - }); + if (!ticket.queue && !chat.isGroup && !msg.fromMe) { + await verifyQueue(wbot, msg, ticket, contact); + } + } catch (err) { + Sentry.captureException(err); + logger.error(`Error handling whatsapp message: Err: ${err}`); + } }; const handleMsgAck = async (msg: WbotMessage, ack: MessageAck) => { diff --git a/backend/src/services/WhatsappService/CreateWhatsAppService.ts b/backend/src/services/WhatsappService/CreateWhatsAppService.ts index 1c2883b..f1375e5 100644 --- a/backend/src/services/WhatsappService/CreateWhatsAppService.ts +++ b/backend/src/services/WhatsappService/CreateWhatsAppService.ts @@ -2,10 +2,16 @@ import * as Yup from "yup"; import AppError from "../../errors/AppError"; import Whatsapp from "../../models/Whatsapp"; +import AssociateWhatsappQueue from "../QueueService/AssociateWhatsappQueue"; +interface QueueData { + id: number; + optionNumber: number; +} interface Request { name: string; - queueIds: number[]; + queuesData: QueueData[]; + greetingMessage?: string; status?: string; isDefault?: boolean; } @@ -18,7 +24,8 @@ interface Response { const CreateWhatsAppService = async ({ name, status = "OPENING", - queueIds = [], + queuesData = [], + greetingMessage, isDefault = false }: Request): Promise => { const schema = Yup.object().shape({ @@ -68,12 +75,13 @@ const CreateWhatsAppService = async ({ { name, status, + greetingMessage, isDefault }, { include: ["queues"] } ); - await whatsapp.$set("queues", queueIds); + await AssociateWhatsappQueue(whatsapp, queuesData); await whatsapp.reload(); diff --git a/backend/src/services/WhatsappService/ListWhatsAppsService.ts b/backend/src/services/WhatsappService/ListWhatsAppsService.ts index 5a1817a..0ac127c 100644 --- a/backend/src/services/WhatsappService/ListWhatsAppsService.ts +++ b/backend/src/services/WhatsappService/ListWhatsAppsService.ts @@ -1,11 +1,24 @@ import Queue from "../../models/Queue"; import Whatsapp from "../../models/Whatsapp"; +import WhatsappQueue from "../../models/WhatsappQueue"; const ListWhatsAppsService = async (): Promise => { const whatsapps = await Whatsapp.findAll({ include: [ - { model: Queue, as: "queues", attributes: ["id", "name", "color"] } - ] + { + model: WhatsappQueue, + as: "whatsappQueues", + attributes: ["optionNumber"], + include: [ + { + model: Queue, + as: "queue", + attributes: ["id", "name", "color", "greetingMessage"] + } + ] + } + ], + order: [["whatsappQueues", "optionNumber", "ASC"]] }); return whatsapps; diff --git a/backend/src/services/WhatsappService/ShowWhatsAppService.ts b/backend/src/services/WhatsappService/ShowWhatsAppService.ts index ea4a83b..5643032 100644 --- a/backend/src/services/WhatsappService/ShowWhatsAppService.ts +++ b/backend/src/services/WhatsappService/ShowWhatsAppService.ts @@ -1,12 +1,25 @@ import Whatsapp from "../../models/Whatsapp"; import AppError from "../../errors/AppError"; import Queue from "../../models/Queue"; +import WhatsappQueue from "../../models/WhatsappQueue"; const ShowWhatsAppService = async (id: string | number): Promise => { const whatsapp = await Whatsapp.findByPk(id, { include: [ - { model: Queue, as: "queues", attributes: ["id", "name", "color"] } - ] + { + model: WhatsappQueue, + as: "whatsappQueues", + attributes: ["optionNumber"], + include: [ + { + model: Queue, + as: "queue", + attributes: ["id", "name", "color", "greetingMessage"] + } + ] + } + ], + order: [["whatsappQueues", "optionNumber", "ASC"]] }); if (!whatsapp) { diff --git a/backend/src/services/WhatsappService/UpdateWhatsAppService.ts b/backend/src/services/WhatsappService/UpdateWhatsAppService.ts index f24fbe1..417d8b2 100644 --- a/backend/src/services/WhatsappService/UpdateWhatsAppService.ts +++ b/backend/src/services/WhatsappService/UpdateWhatsAppService.ts @@ -4,13 +4,19 @@ import { Op } from "sequelize"; import AppError from "../../errors/AppError"; import Whatsapp from "../../models/Whatsapp"; import ShowWhatsAppService from "./ShowWhatsAppService"; +import AssociateWhatsappQueue from "../QueueService/AssociateWhatsappQueue"; +interface QueueData { + id: number; + optionNumber: number; +} interface WhatsappData { name?: string; status?: string; session?: string; isDefault?: boolean; - queueIds?: number[]; + greetingMessage?: string; + queuesData?: QueueData[]; } interface Request { @@ -32,7 +38,14 @@ const UpdateWhatsAppService = async ({ isDefault: Yup.boolean() }); - const { name, status, isDefault, session, queueIds = [] } = whatsappData; + const { + name, + status, + isDefault, + session, + greetingMessage, + queuesData = [] + } = whatsappData; try { await schema.validate({ name, status, isDefault }); @@ -57,10 +70,11 @@ const UpdateWhatsAppService = async ({ name, status, session, + greetingMessage, isDefault }); - await whatsapp.$set("queues", queueIds); + await AssociateWhatsappQueue(whatsapp, queuesData); await whatsapp.reload();