Merge branch 'master' into refresh-tokens

This commit is contained in:
canove
2020-09-30 19:30:43 -03:00
16 changed files with 150 additions and 78 deletions

View File

@@ -1,10 +1,8 @@
import { Request, Response } from "express";
import { Message as WbotMessage } from "whatsapp-web.js";
import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead";
import { getIO } from "../libs/socket";
import CreateMessageService from "../services/MessageServices/CreateMessageService";
import ListMessagesService from "../services/MessageServices/ListMessagesService";
import ShowTicketService from "../services/TicketServices/ShowTicketService";
import DeleteWhatsAppMessage from "../services/WbotServices/DeleteWhatsAppMessage";
@@ -39,44 +37,20 @@ export const index = async (req: Request, res: Response): Promise<Response> => {
export const store = async (req: Request, res: Response): Promise<Response> => {
const { ticketId } = req.params;
const { body, fromMe, read }: MessageData = req.body;
const { body }: MessageData = req.body;
const media = req.file;
const ticket = await ShowTicketService(ticketId);
let sentMessage: WbotMessage;
if (media) {
sentMessage = await SendWhatsAppMedia({ media, ticket });
await SendWhatsAppMedia({ media, ticket });
} else {
sentMessage = await SendWhatsAppMessage({ body, ticket });
await SendWhatsAppMessage({ body, ticket });
}
const newMessage = {
id: sentMessage.id.id,
body,
fromMe,
read,
mediaType: sentMessage.type,
mediaUrl: media?.filename
};
const message = await CreateMessageService({
messageData: newMessage,
ticketId: ticket.id
});
const io = getIO();
io.to(ticketId).to("notification").to(ticket.status).emit("appMessage", {
action: "create",
message,
ticket,
contact: ticket.contact
});
await SetTicketMessagesAsRead(ticket);
return res.status(200).json(message);
return res.send();
};
export const remove = async (
@@ -93,5 +67,5 @@ export const remove = async (
message
});
return res.json({ ok: true });
return res.send();
};

View File

@@ -0,0 +1,15 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("Tickets", "isGroup", {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("Tickets", "isGroup");
}
};

View File

@@ -0,0 +1,15 @@
import { QueryInterface, DataTypes } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.addColumn("Contacts", "isGroup", {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
});
},
down: (queryInterface: QueryInterface) => {
return queryInterface.removeColumn("Contacts", "isGroup");
}
};

View File

@@ -9,7 +9,9 @@ export const GetWbotMessage = async (
): Promise<WbotMessage> => {
const wbot = await GetTicketWbot(ticket);
const wbotChat = await wbot.getChatById(`${ticket.contact.number}@c.us`);
const wbotChat = await wbot.getChatById(
`${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`
);
try {
const chatMessages = await wbotChat.fetchMessages({ limit: 20 });

View File

@@ -37,6 +37,10 @@ class Contact extends Model<Contact> {
@Column
profilePicUrl: string;
@Default(false)
@Column
isGroup: boolean;
@CreatedAt
createdAt: Date;

View File

@@ -11,7 +11,8 @@ import {
HasMany,
AutoIncrement,
AfterFind,
BeforeUpdate
BeforeUpdate,
Default
} from "sequelize-typescript";
import Contact from "./Contact";
@@ -35,6 +36,10 @@ class Ticket extends Model<Ticket> {
@Column
lastMessage: string;
@Default(false)
@Column
isGroup: boolean;
@CreatedAt
createdAt: Date;

View File

@@ -2,13 +2,13 @@ import Contact from "../../models/Contact";
import AppError from "../../errors/AppError";
const ShowContactService = async (id: string | number): Promise<Contact> => {
const user = await Contact.findByPk(id, { include: ["extraInfo"] });
const contact = await Contact.findByPk(id, { include: ["extraInfo"] });
if (!user) {
if (!contact) {
throw new AppError("No contact found with this ID.", 404);
}
return user;
return contact;
};
export default ShowContactService;

View File

@@ -2,6 +2,7 @@ import AppError from "../../errors/AppError";
import CheckContactOpenTickets from "../../helpers/CheckContactOpenTickets";
import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp";
import Ticket from "../../models/Ticket";
import ShowContactService from "../ContactServices/ShowContactService";
interface Request {
contactId: number;
@@ -22,9 +23,12 @@ const CreateTicketService = async ({
await CheckContactOpenTickets(contactId);
const { isGroup } = await ShowContactService(contactId);
const { id }: Ticket = await defaultWhatsapp.$create("ticket", {
contactId,
status,
isGroup,
userId
});

View File

@@ -1,3 +1,4 @@
import fs from "fs";
import { MessageMedia, Message as WbotMessage } from "whatsapp-web.js";
import AppError from "../../errors/AppError";
import GetTicketWbot from "../../helpers/GetTicketWbot";
@@ -18,11 +19,14 @@ const SendWhatsAppMedia = async ({
const newMedia = MessageMedia.fromFilePath(media.path);
const sentMessage = await wbot.sendMessage(
`${ticket.contact.number}@c.us`,
`${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`,
newMedia
);
await ticket.update({ lastMessage: media.filename });
fs.unlinkSync(media.path);
return sentMessage;
} catch (err) {
console.log(err);

View File

@@ -16,7 +16,7 @@ const SendWhatsAppMessage = async ({
const wbot = await GetTicketWbot(ticket);
const sentMessage = await wbot.sendMessage(
`${ticket.contact.number}@c.us`,
`${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`,
body
);

View File

@@ -2,7 +2,7 @@ import path from "path";
import fs from "fs";
import { Op } from "sequelize";
import { subHours } from "date-fns";
import Sentry from "@sentry/node";
import * as Sentry from "@sentry/node";
import {
Contact as WbotContact,
@@ -24,43 +24,88 @@ const verifyContact = async (
profilePicUrl: string
): Promise<Contact> => {
let contact = await Contact.findOne({
where: { number: msgContact.number }
where: { number: msgContact.id.user }
});
if (contact) {
await contact.update({ profilePicUrl });
} else {
contact = await Contact.create({
name: msgContact.pushname || msgContact.number.toString(),
number: msgContact.number,
name: msgContact.name || msgContact.pushname || msgContact.id.user,
number: msgContact.id.user,
profilePicUrl
});
const io = getIO();
io.emit("contact", {
action: "create",
contact
});
}
return contact;
};
const verifyGroup = async (msgGroupContact: WbotContact) => {
const profilePicUrl = await msgGroupContact.getProfilePicUrl();
let groupContact = await Contact.findOne({
where: { number: msgGroupContact.id.user }
});
if (groupContact) {
await groupContact.update({ profilePicUrl });
} else {
groupContact = await Contact.create({
name: msgGroupContact.name,
number: msgGroupContact.id.user,
isGroup: msgGroupContact.isGroup,
profilePicUrl
});
const io = getIO();
io.emit("contact", {
action: "create",
contact: groupContact
});
}
return groupContact;
};
const verifyTicket = async (
contact: Contact,
whatsappId: number
whatsappId: number,
groupContact?: Contact
): Promise<Ticket> => {
let ticket = await Ticket.findOne({
where: {
status: {
[Op.or]: ["open", "pending"]
},
contactId: contact.id
contactId: groupContact ? groupContact.id : contact.id
},
include: ["contact"]
});
if (!ticket && groupContact) {
ticket = await Ticket.findOne({
where: {
contactId: groupContact.id
},
order: [["createdAt", "DESC"]],
include: ["contact"]
});
if (ticket) {
await ticket.update({ status: "pending", userId: null });
}
}
if (!ticket) {
ticket = await Ticket.findOne({
where: {
createdAt: {
[Op.between]: [+subHours(new Date(), 2), +new Date()]
},
contactId: contact.id
contactId: groupContact ? groupContact.id : contact.id
},
order: [["createdAt", "DESC"]],
include: ["contact"]
@@ -73,8 +118,9 @@ const verifyTicket = async (
if (!ticket) {
const { id } = await Ticket.create({
contactId: contact.id,
contactId: groupContact ? groupContact.id : contact.id,
status: "pending",
isGroup: !!groupContact,
whatsappId
});
@@ -86,7 +132,8 @@ const verifyTicket = async (
const handlMedia = async (
msg: WbotMessage,
ticket: Ticket
ticket: Ticket,
contact: Contact
): Promise<Message> => {
const media = await msg.downloadMedia();
@@ -110,7 +157,9 @@ const handlMedia = async (
const newMessage: Message = await ticket.$create("message", {
id: msg.id.id,
body: msg.body || media.filename,
body: msg.fromMe
? msg.body
: `${contact.name}: ${msg.body ? msg.body : media.filename}`,
fromMe: msg.fromMe,
mediaUrl: media.filename,
mediaType: media.mimetype.split("/")[0]
@@ -127,12 +176,13 @@ const handleMessage = async (
let newMessage: Message;
if (msg.hasMedia) {
newMessage = await handlMedia(msg, ticket);
newMessage = await handlMedia(msg, ticket, contact);
} else {
newMessage = await ticket.$create("message", {
id: msg.id.id,
body: msg.body,
fromMe: msg.fromMe
body: msg.fromMe ? msg.body : `${contact.name}: ${msg.body}`,
fromMe: msg.fromMe,
read: msg.fromMe
});
await ticket.update({ lastMessage: msg.body });
}
@@ -150,7 +200,6 @@ const handleMessage = async (
};
const isValidMsg = (msg: WbotMessage): boolean => {
if (msg.author) return false;
if (
msg.type === "chat" ||
msg.type === "audio" ||
@@ -176,28 +225,22 @@ const wbotMessageListener = (whatsapp: Whatsapp): void => {
try {
let msgContact: WbotContact;
let msgGroupContact: WbotContact | null = null;
let groupContact: Contact | undefined;
if (msg.fromMe) {
msgContact = await wbot.getContactById(msg.to);
} else {
msgContact = await msg.getContact();
}
if (msg.author) {
msgGroupContact = await wbot.getContactById(msg.from);
groupContact = await verifyGroup(msgGroupContact);
}
const profilePicUrl = await msgContact.getProfilePicUrl();
const contact = await verifyContact(msgContact, profilePicUrl);
const ticket = await verifyTicket(contact, whatsappId);
// return if message was already created by messageController
if (msg.fromMe) {
const alreadyExists = await Message.findOne({
where: { id: msg.id.id }
});
if (alreadyExists) {
return;
}
}
const ticket = await verifyTicket(contact, whatsappId, groupContact);
await handleMessage(msg, ticket, contact);
} catch (err) {

View File

@@ -88,6 +88,7 @@ const NotificationsPopOver = () => {
socket.on("appMessage", data => {
if (
data.action === "create" &&
!data.message.read &&
(data.ticket.userId === userId || !data.ticket.userId)
) {
setNotifications(prevState => {
@@ -103,7 +104,8 @@ const NotificationsPopOver = () => {
(ticketIdRef.current &&
data.message.ticketId === ticketIdRef.current &&
document.visibilityState === "visible") ||
(data.ticket.userId && data.ticket.userId !== userId)
(data.ticket.userId && data.ticket.userId !== userId) ||
data.ticket.isGroup
)
return;
else {

View File

@@ -103,7 +103,7 @@ const TicketActionButtons = ({ ticket }) => {
color="primary"
onClick={e => handleUpdateTicketStatus(e, "open", userId)}
>
ACCEPT
{i18n.t("messagesList.header.buttons.accept")}
</Button>
)}
</div>

View File

@@ -9,6 +9,7 @@ import TicketListItem from "../TicketListItem";
import TicketsListSkeleton from "../TicketsListSkeleton";
import useTickets from "../../hooks/useTickets";
import { i18n } from "../../translate/i18n";
import { ListSubheader } from "@material-ui/core";
const useStyles = makeStyles(theme => ({
ticketsListWrapper: {
@@ -29,13 +30,14 @@ const useStyles = makeStyles(theme => ({
},
ticketsListHeader: {
display: "flex",
alignItems: "center",
fontWeight: 500,
fontSize: "16px",
height: "56px",
// display: "flex",
// alignItems: "center",
// fontWeight: 500,
// fontSize: "16px",
// height: "56px",
color: "rgb(67, 83, 105)",
padding: "0px 12px",
// padding: "0px 12px",
backgroundColor: "white",
borderBottom: "1px solid rgba(0, 0, 0, 0.12)",
},
@@ -255,16 +257,16 @@ const TicketsList = ({ status, searchParam, showAll }) => {
>
<List style={{ paddingTop: 0 }}>
{status === "open" && (
<div className={classes.ticketsListHeader}>
<ListSubheader className={classes.ticketsListHeader}>
{i18n.t("ticketsList.assignedHeader")}
<span className={classes.ticketsCount}>{ticketsList.length}</span>
</div>
</ListSubheader>
)}
{status === "pending" && (
<div className={classes.ticketsListHeader}>
<ListSubheader className={classes.ticketsListHeader}>
{i18n.t("ticketsList.pendingHeader")}
<span className={classes.ticketsCount}>{ticketsList.length}</span>
</div>
</ListSubheader>
)}
{ticketsList.length === 0 && !loading ? (
<div className={classes.noTicketsDiv}>

View File

@@ -243,7 +243,8 @@ const messages = {
buttons: {
return: "Return",
resolve: "Resolve",
reopen: "Reopen",
reopen: "Reopen",
accept: "Accept"
},
},
},

View File

@@ -245,6 +245,7 @@ const messages = {
return: "Retornar",
resolve: "Resolver",
reopen: "Reabrir",
accept: "Aceitar",
},
},
},