diff --git a/backend/.gitignore b/backend/.gitignore index 36e4483..1c092bc 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -10,3 +10,7 @@ yarn.lock yarn-error.log /src/config/sentry.js + +# Ignore test-related files +/coverage.data +/coverage/ diff --git a/backend/coverage/lcov-report/AuthServices/RefreshTokenService.ts.html b/backend/coverage/lcov-report/AuthServices/RefreshTokenService.ts.html deleted file mode 100644 index 32112c2..0000000 --- a/backend/coverage/lcov-report/AuthServices/RefreshTokenService.ts.html +++ /dev/null @@ -1,203 +0,0 @@ - - - - -
-- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import { verify } from "jsonwebtoken"; -import AppError from "../../errors/AppError"; -import ShowUserService from "../UserServices/ShowUserService"; -import authConfig from "../../config/auth"; -import { - createAccessToken, - createRefreshToken -} from "../../helpers/CreateTokens"; - -interface RefreshTokenPayload { - id: string; - tokenVersion: number; -} - -interface Response { - newToken: string; - refreshToken: string; -} - -export const RefreshTokenService = async (token: string): Promise<Response> => { - let decoded; - - try { - decoded = verify(token, authConfig.refreshSecret); - } catch (err) { - throw new AppError("ERR_SESSION_EXPIRED", 401); - } - - const { id, tokenVersion } = decoded as RefreshTokenPayload; - - const user = await ShowUserService(id); - - if (user.tokenVersion !== tokenVersion) { - throw new AppError("ERR_SESSION_EXPIRED", 401); - } - - const newToken = createAccessToken(user); - const refreshToken = createRefreshToken(user); - - return { newToken, refreshToken }; -}; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
|---|---|---|---|---|---|---|---|---|---|
| RefreshTokenService.ts | -
-
- |
- 0% | -0/41 | -0% | -0/1 | -0% | -0/1 | -0% | -0/41 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import AppError from "../../errors/AppError"; -import Contact from "../../models/Contact"; - -interface ExtraInfo { - name: string; - value: string; -} - -interface Request { - name: string; - number: string; - email?: string; - profilePicUrl?: string; - extraInfo?: ExtraInfo[]; -} - -const CreateContactService = async ({ - name, - number, - email = "", - extraInfo = [] -}: Request): Promise<Contact> => { - const numberExists = await Contact.findOne({ - where: { number } - }); - - if (numberExists) { - throw new AppError("ERR_DUPLICATED_CONTACT"); - } - - const contact = await Contact.create( - { - name, - number, - email, - extraInfo - }, - { - include: ["extraInfo"] - } - ); - - return contact; -}; - -export default CreateContactService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 | - - - - - - - - - - - - - - - - | import Contact from "../../models/Contact"; -import AppError from "../../errors/AppError"; - -const DeleteContactService = async (id: string): Promise<void> => { - const contact = await Contact.findOne({ - where: { id } - }); - - if (!contact) { - throw new AppError("ERR_NO_CONTACT_FOUND", 404); - } - - await contact.destroy(); -}; - -export default DeleteContactService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import { Sequelize, Op } from "sequelize"; -import Contact from "../../models/Contact"; - -interface Request { - searchParam?: string; - pageNumber?: string; -} - -interface Response { - contacts: Contact[]; - count: number; - hasMore: boolean; -} - -const ListContactsService = async ({ - searchParam = "", - pageNumber = "1" -}: Request): Promise<Response> => { - const whereCondition = { - [Op.or]: [ - { - name: Sequelize.where( - Sequelize.fn("LOWER", Sequelize.col("name")), - "LIKE", - `%${searchParam.toLowerCase().trim()}%` - ) - }, - { number: { [Op.like]: `%${searchParam.toLowerCase().trim()}%` } } - ] - }; - const limit = 20; - const offset = limit * (+pageNumber - 1); - - const { count, rows: contacts } = await Contact.findAndCountAll({ - where: whereCondition, - limit, - offset, - order: [["name", "ASC"]] - }); - - const hasMore = count > offset + contacts.length; - - return { - contacts, - count, - hasMore - }; -}; - -export default ListContactsService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 | - - - - - - - - - - - - - - | import Contact from "../../models/Contact"; -import AppError from "../../errors/AppError"; - -const ShowContactService = async (id: string | number): Promise<Contact> => { - const contact = await Contact.findByPk(id, { include: ["extraInfo"] }); - - if (!contact) { - throw new AppError("ERR_NO_CONTACT_FOUND", 404); - } - - return contact; -}; - -export default ShowContactService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import AppError from "../../errors/AppError"; -import Contact from "../../models/Contact"; -import ContactCustomField from "../../models/ContactCustomField"; - -interface ExtraInfo { - id?: number; - name: string; - value: string; -} -interface ContactData { - email?: string; - number?: string; - name?: string; - extraInfo?: ExtraInfo[]; -} - -interface Request { - contactData: ContactData; - contactId: string; -} - -const UpdateContactService = async ({ - contactData, - contactId -}: Request): Promise<Contact> => { - const { email, name, number, extraInfo } = contactData; - - const contact = await Contact.findOne({ - where: { id: contactId }, - attributes: ["id", "name", "number", "email", "profilePicUrl"], - include: ["extraInfo"] - }); - - if (!contact) { - throw new AppError("ERR_NO_CONTACT_FOUND", 404); - } - - if (extraInfo) { - await Promise.all( - extraInfo.map(async info => { - await ContactCustomField.upsert({ ...info, contactId: contact.id }); - }) - ); - - await Promise.all( - contact.extraInfo.map(async oldInfo => { - const stillExists = extraInfo.findIndex(info => info.id === oldInfo.id); - - if (stillExists === -1) { - await ContactCustomField.destroy({ where: { id: oldInfo.id } }); - } - }) - ); - } - - await contact.update({ - name, - number, - email - }); - - await contact.reload({ - attributes: ["id", "name", "number", "email", "profilePicUrl"], - include: ["extraInfo"] - }); - - return contact; -}; - -export default UpdateContactService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
|---|---|---|---|---|---|---|---|---|---|
| CreateContactService.ts | -
-
- |
- 0% | -0/46 | -0% | -0/1 | -0% | -0/1 | -0% | -0/46 | -
| DeleteContactService.ts | -
-
- |
- 0% | -0/16 | -0% | -0/1 | -0% | -0/1 | -0% | -0/16 | -
| ListContactsService.ts | -
-
- |
- 0% | -0/50 | -0% | -0/1 | -0% | -0/1 | -0% | -0/50 | -
| ShowContactService.ts | -
-
- |
- 0% | -0/14 | -0% | -0/1 | -0% | -0/1 | -0% | -0/14 | -
| UpdateContactService.ts | -
-
- |
- 0% | -0/70 | -0% | -0/1 | -0% | -0/1 | -0% | -0/70 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import AppError from "../../errors/AppError"; -import Message from "../../models/Message"; -import ShowTicketService from "../TicketServices/ShowTicketService"; - -interface MessageData { - id: string; - ticketId: number; - body: string; - contactId?: number; - fromMe?: boolean; - read?: boolean; - mediaType?: string; - mediaUrl?: string; -} -interface Request { - messageData: MessageData; -} - -const CreateMessageService = async ({ - messageData -}: Request): Promise<Message> => { - const ticket = await ShowTicketService(messageData.ticketId); - - if (!ticket) { - throw new AppError("ERR_NO_TICKET_FOUND", 404); - } - - await Message.upsert(messageData); - - const message = await Message.findByPk(messageData.id, { - include: ["contact"] - }); - - if (!message) { - throw new AppError("ERR_CREATING_MESSAGE", 501); - } - - return message; -}; - -export default CreateMessageService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import { where, fn, col } from "sequelize"; -import AppError from "../../errors/AppError"; -import Message from "../../models/Message"; -import Ticket from "../../models/Ticket"; -import ShowTicketService from "../TicketServices/ShowTicketService"; - -interface Request { - ticketId: string; - searchParam?: string; - pageNumber?: string; -} - -interface Response { - messages: Message[]; - ticket: Ticket; - count: number; - hasMore: boolean; -} - -const ListMessagesService = async ({ - searchParam = "", - pageNumber = "1", - ticketId -}: Request): Promise<Response> => { - const ticket = await ShowTicketService(ticketId); - - if (!ticket) { - throw new AppError("ERR_NO_TICKET_FOUND", 404); - } - - const whereCondition = { - body: where( - fn("LOWER", col("body")), - "LIKE", - `%${searchParam.toLowerCase()}%` - ), - ticketId - }; - - // await setMessagesAsRead(ticket); - const limit = 20; - const offset = limit * (+pageNumber - 1); - - const { count, rows: messages } = await Message.findAndCountAll({ - where: whereCondition, - limit, - include: ["contact"], - offset, - order: [["createdAt", "DESC"]] - }); - - const hasMore = count > offset + messages.length; - - return { - messages: messages.reverse(), - ticket, - count, - hasMore - }; -}; - -export default ListMessagesService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
|---|---|---|---|---|---|---|---|---|---|
| CreateMessageService.ts | -
-
- |
- 0% | -0/41 | -0% | -0/1 | -0% | -0/1 | -0% | -0/41 | -
| ListMessagesService.ts | -
-
- |
- 0% | -0/62 | -0% | -0/1 | -0% | -0/1 | -0% | -0/62 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 | - - - - - - - - - | import Setting from "../../models/Setting"; - -const ListSettingsService = async (): Promise<Setting[] | undefined> => { - const settings = await Setting.findAll(); - - return settings; -}; - -export default ListSettingsService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 | - - - - - - - - - - - - - - - - - - - - - - - - - - | import AppError from "../../errors/AppError"; -import Setting from "../../models/Setting"; - -interface Request { - key: string; - value: string; -} - -const UpdateSettingService = async ({ - key, - value -}: Request): Promise<Setting | undefined> => { - const setting = await Setting.findOne({ - where: { key } - }); - - if (!setting) { - throw new AppError("ERR_NO_SETTING_FOUND", 404); - } - - await setting.update({ value }); - - return setting; -}; - -export default UpdateSettingService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
|---|---|---|---|---|---|---|---|---|---|
| ListSettingsService.ts | -
-
- |
- 0% | -0/9 | -0% | -0/1 | -0% | -0/1 | -0% | -0/9 | -
| UpdateSettingService.ts | -
-
- |
- 0% | -0/26 | -0% | -0/1 | -0% | -0/1 | -0% | -0/26 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | 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; - status: string; - userId: number; -} - -const CreateTicketService = async ({ - contactId, - status, - userId -}: Request): Promise<Ticket> => { - const defaultWhatsapp = await GetDefaultWhatsApp(); - - await CheckContactOpenTickets(contactId); - - const { isGroup } = await ShowContactService(contactId); - - const { id }: Ticket = await defaultWhatsapp.$create("ticket", { - contactId, - status, - isGroup, - userId - }); - - const ticket = await Ticket.findByPk(id, { include: ["contact"] }); - - if (!ticket) { - throw new AppError("ERR_CREATING_TICKET"); - } - - return ticket; -}; - -export default CreateTicketService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 | - - - - - - - - - - - - - - - - - - | import Ticket from "../../models/Ticket"; -import AppError from "../../errors/AppError"; - -const DeleteTicketService = async (id: string): Promise<Ticket> => { - const ticket = await Ticket.findOne({ - where: { id } - }); - - if (!ticket) { - throw new AppError("ERR_NO_TICKET_FOUND", 404); - } - - await ticket.destroy(); - - return ticket; -}; - -export default DeleteTicketService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import { Op, fn, where, col, Filterable, Includeable } from "sequelize"; -import { startOfDay, endOfDay, parseISO } from "date-fns"; - -import Ticket from "../../models/Ticket"; -import Contact from "../../models/Contact"; -import Message from "../../models/Message"; - -interface Request { - searchParam?: string; - pageNumber?: string; - status?: string; - date?: string; - showAll?: string; - userId: string; - withUnreadMessages?: string; -} - -interface Response { - tickets: Ticket[]; - count: number; - hasMore: boolean; -} - -const ListTicketsService = async ({ - searchParam = "", - pageNumber = "1", - status, - date, - showAll, - userId, - withUnreadMessages -}: Request): Promise<Response> => { - let whereCondition: Filterable["where"] = { - [Op.or]: [{ userId }, { status: "pending" }] - }; - let includeCondition: Includeable[]; - - includeCondition = [ - { - model: Contact, - as: "contact", - attributes: ["id", "name", "number", "profilePicUrl"] - } - ]; - - if (showAll === "true") { - whereCondition = {}; - } - - if (status) { - whereCondition = { - ...whereCondition, - status - }; - } - - if (searchParam) { - const sanitizedSearchParam = searchParam.toLocaleLowerCase().trim(); - - includeCondition = [ - ...includeCondition, - { - model: Message, - as: "messages", - attributes: ["id", "body"], - where: { - body: where( - fn("LOWER", col("body")), - "LIKE", - `%${sanitizedSearchParam}%` - ) - }, - required: false, - duplicating: false - } - ]; - - whereCondition = { - [Op.or]: [ - { - "$contact.name$": where( - fn("LOWER", col("name")), - "LIKE", - `%${sanitizedSearchParam}%` - ) - }, - { "$contact.number$": { [Op.like]: `%${sanitizedSearchParam}%` } }, - { - "$message.body$": where( - fn("LOWER", col("body")), - "LIKE", - `%${sanitizedSearchParam}%` - ) - } - ] - }; - } - - if (date) { - whereCondition = { - ...whereCondition, - createdAt: { - [Op.between]: [+startOfDay(parseISO(date)), +endOfDay(parseISO(date))] - } - }; - } - - if (withUnreadMessages === "true") { - includeCondition = [ - ...includeCondition, - { - model: Message, - as: "messages", - attributes: [], - where: { - read: false, - fromMe: false - } - } - ]; - } - - const limit = 20; - const offset = limit * (+pageNumber - 1); - - const { count, rows: tickets } = await Ticket.findAndCountAll({ - where: whereCondition, - include: includeCondition, - distinct: true, - limit, - offset, - order: [["updatedAt", "DESC"]] - }); - - const hasMore = count > offset + tickets.length; - - return { - tickets, - count, - hasMore - }; -}; - -export default ListTicketsService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import Ticket from "../../models/Ticket"; -import AppError from "../../errors/AppError"; -import Contact from "../../models/Contact"; -import User from "../../models/User"; - -const ShowTicketService = async (id: string | number): Promise<Ticket> => { - const ticket = await Ticket.findByPk(id, { - include: [ - { - model: Contact, - as: "contact", - attributes: ["id", "name", "number", "profilePicUrl"], - include: ["extraInfo"] - }, - { - model: User, - as: "user", - attributes: ["id", "name"] - } - ] - }); - - if (!ticket) { - throw new AppError("ERR_NO_TICKET_FOUND", 404); - } - - return ticket; -}; - -export default ShowTicketService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import AppError from "../../errors/AppError"; -import CheckContactOpenTickets from "../../helpers/CheckContactOpenTickets"; -import SetTicketMessagesAsRead from "../../helpers/SetTicketMessagesAsRead"; -import Contact from "../../models/Contact"; -import Ticket from "../../models/Ticket"; -import User from "../../models/User"; - -interface TicketData { - status?: string; - userId?: number; -} - -interface Request { - ticketData: TicketData; - ticketId: string; -} - -interface Response { - ticket: Ticket; - ticketUser: User | null; - oldStatus: string; -} - -const UpdateTicketService = async ({ - ticketData, - ticketId -}: Request): Promise<Response> => { - const { status, userId } = ticketData; - - const ticket = await Ticket.findOne({ - where: { id: ticketId }, - include: [ - { - model: Contact, - as: "contact", - attributes: ["id", "name", "number", "profilePicUrl"] - } - ] - }); - - if (!ticket) { - throw new AppError("ERR_NO_TICKET_FOUND", 404); - } - - await SetTicketMessagesAsRead(ticket); - - const oldStatus = ticket.status; - - if (oldStatus === "closed") { - await CheckContactOpenTickets(ticket.contact.id); - } - - await ticket.update({ - status, - userId - }); - const ticketUser = await ticket.$get("user", { attributes: ["id", "name"] }); - - return { ticket, oldStatus, ticketUser }; -}; - -export default UpdateTicketService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
|---|---|---|---|---|---|---|---|---|---|
| CreateTicketService.ts | -
-
- |
- 0% | -0/40 | -0% | -0/1 | -0% | -0/1 | -0% | -0/40 | -
| DeleteTicketService.ts | -
-
- |
- 0% | -0/18 | -0% | -0/1 | -0% | -0/1 | -0% | -0/18 | -
| ListTicketsService.ts | -
-
- |
- 0% | -0/144 | -0% | -0/1 | -0% | -0/1 | -0% | -0/144 | -
| ShowTicketService.ts | -
-
- |
- 0% | -0/30 | -0% | -0/1 | -0% | -0/1 | -0% | -0/30 | -
| UpdateTicketService.ts | -
-
- |
- 0% | -0/62 | -0% | -0/1 | -0% | -0/1 | -0% | -0/62 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import User from "../../models/User"; -import AppError from "../../errors/AppError"; -import { - createAccessToken, - createRefreshToken -} from "../../helpers/CreateTokens"; - -interface Request { - email: string; - password: string; -} - -interface Response { - user: User; - token: string; - refreshToken: string; -} - -const AuthUserService = async ({ - email, - password -}: Request): Promise<Response> => { - const user = await User.findOne({ - where: { email } - }); - - if (!user) { - throw new AppError("ERR_INVALID_CREDENTIALS", 401); - } - - if (!(await user.checkPassword(password))) { - throw new AppError("ERR_INVALID_CREDENTIALS", 401); - } - - const token = createAccessToken(user); - const refreshToken = createRefreshToken(user); - - return { - user, - token, - refreshToken - }; -}; - -export default AuthUserService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 | 1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -1x -5x -5x -5x -5x -5x -5x -5x -5x -5x -5x -5x -5x -5x -5x -5x -5x -5x -4x -4x -5x -5x -5x -5x -5x -5x -5x -1x -1x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -3x -1x -1x - | import * as Yup from "yup";
-
-import AppError from "../../errors/AppError";
-import User from "../../models/User";
-
-interface Request {
- email: string;
- password: string;
- name: string;
- profile?: string;
-}
-
-interface Response {
- email: string;
- name: string;
- id: number;
- profile: string;
-}
-
-const CreateUserService = async ({
- email,
- password,
- name,
- profile = "admin"
-}: Request): Promise<Response> => {
- const schema = Yup.object().shape({
- name: Yup.string().required().min(2),
- email: Yup.string()
- .email()
- .required()
- .test(
- "Check-email",
- "An user with this email already exists.",
- async value => {
- const emailExists = await User.findOne({
- where: { email: value! }
- });
- return !emailExists;
- }
- ),
- password: Yup.string().required().min(5)
- });
-
- try {
- await schema.validate({ email, password, name });
- } catch (err) {
- throw new AppError(err.message);
- }
-
- const user = await User.create({
- email,
- password,
- name,
- profile
- });
-
- const serializedUser = {
- id: user.id,
- name: user.name,
- email: user.email,
- profile: user.profile
- };
-
- return serializedUser;
-};
-
-export default CreateUserService;
- |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 | - - - - - - - - - - - - - - - - - - - - - - - - - - | import User from "../../models/User"; -import AppError from "../../errors/AppError"; -import Ticket from "../../models/Ticket"; -import UpdateDeletedUserOpenTicketsStatus from "../../helpers/UpdateDeletedUserOpenTicketsStatus"; - -const DeleteUserService = async (id: string): Promise<void> => { - const user = await User.findOne({ - where: { id } - }); - - if (!user) { - throw new AppError("ERR_NO_USER_FOUND", 404); - } - - const userOpenTickets: Ticket[] = await user.$get("tickets", { - where: { status: "open" } - }); - - if (userOpenTickets.length > 0) { - UpdateDeletedUserOpenTicketsStatus(userOpenTickets); - } - - await user.destroy(); -}; - -export default DeleteUserService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import { Sequelize, Op } from "sequelize"; -import User from "../../models/User"; - -interface Request { - searchParam?: string; - pageNumber?: string; -} - -interface Response { - users: User[]; - count: number; - hasMore: boolean; -} - -const ListUsersService = async ({ - searchParam = "", - pageNumber = "1" -}: Request): Promise<Response> => { - const whereCondition = { - [Op.or]: [ - { - name: Sequelize.where( - Sequelize.fn("LOWER", Sequelize.col("name")), - "LIKE", - `%${searchParam.toLowerCase()}%` - ) - }, - { email: { [Op.like]: `%${searchParam.toLowerCase()}%` } } - ] - }; - const limit = 20; - const offset = limit * (+pageNumber - 1); - - const { count, rows: users } = await User.findAndCountAll({ - where: whereCondition, - attributes: ["name", "id", "email", "profile"], - limit, - offset, - order: [["createdAt", "DESC"]] - }); - - const hasMore = count > offset + users.length; - - return { - users, - count, - hasMore - }; -}; - -export default ListUsersService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 | - - - - - - - - - - - - - - - - | import User from "../../models/User"; -import AppError from "../../errors/AppError"; - -const ShowUserService = async (id: string | number): Promise<User> => { - const user = await User.findByPk(id, { - attributes: ["name", "id", "email", "profile", "tokenVersion"] - }); - - if (!user) { - throw new AppError("ERR_NO_USER_FOUND", 404); - } - - return user; -}; - -export default ShowUserService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import * as Yup from "yup"; - -import AppError from "../../errors/AppError"; -import User from "../../models/User"; - -interface UserData { - email?: string; - password?: string; - name?: string; - profile?: string; -} - -interface Request { - userData: UserData; - userId: string; -} - -interface Response { - id: number; - name: string; - email: string; - profile: string; -} - -const UpdateUserService = async ({ - userData, - userId -}: Request): Promise<Response | undefined> => { - const user = await User.findOne({ - where: { id: userId }, - attributes: ["name", "id", "email", "profile"] - }); - - if (!user) { - throw new AppError("ERR_NO_USER_FOUND", 404); - } - - const schema = Yup.object().shape({ - name: Yup.string().min(2), - email: Yup.string().email(), - password: Yup.string() - }); - - const { email, password, name } = userData; - - try { - await schema.validate({ email, password, name }); - } catch (err) { - throw new AppError(err.message); - } - - await user.update({ - email, - password, - name - }); - - const serializedUser = { - id: user.id, - name: user.name, - email: user.email, - profile: user.profile - }; - - return serializedUser; -}; - -export default UpdateUserService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
|---|---|---|---|---|---|---|---|---|---|
| AuthUserSerice.ts | -
-
- |
- 0% | -0/45 | -0% | -0/1 | -0% | -0/1 | -0% | -0/45 | -
| CreateUserService.ts | -
-
- |
- 100% | -67/67 | -71.88% | -23/32 | -90% | -9/10 | -100% | -67/67 | -
| DeleteUserService.ts | -
-
- |
- 0% | -0/26 | -0% | -0/1 | -0% | -0/1 | -0% | -0/26 | -
| ListUsersService.ts | -
-
- |
- 0% | -0/51 | -0% | -0/1 | -0% | -0/1 | -0% | -0/51 | -
| ShowUserService.ts | -
-
- |
- 0% | -0/16 | -0% | -0/1 | -0% | -0/1 | -0% | -0/16 | -
| UpdateUserService.ts | -
-
- |
- 0% | -0/68 | -0% | -0/1 | -0% | -0/1 | -0% | -0/68 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 | - - - - - - - - - - - - - - - - - - - - - - - - | import AppError from "../../errors/AppError"; -import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; -import { getWbot } from "../../libs/wbot"; - -const CheckIsValidContact = async (number: string): Promise<void> => { - const defaultWhatsapp = await GetDefaultWhatsApp(); - - const wbot = getWbot(defaultWhatsapp.id); - - try { - const isValidNumber = await wbot.isRegisteredUser(`${number}@c.us`); - if (!isValidNumber) { - throw new AppError("invalidNumber"); - } - } catch (err) { - console.log(err); - if (err.message === "invalidNumber") { - throw new AppError("ERR_WAPP_INVALID_CONTACT"); - } - throw new AppError("ERR_WAPP_CHECK_CONTACT"); - } -}; - -export default CheckIsValidContact; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import AppError from "../../errors/AppError"; -import GetWbotMessage from "../../helpers/GetWbotMessage"; -import Message from "../../models/Message"; -import Ticket from "../../models/Ticket"; - -const DeleteWhatsAppMessage = async (messageId: string): Promise<Message> => { - const message = await Message.findByPk(messageId, { - include: [ - { - model: Ticket, - as: "ticket", - include: ["contact"] - } - ] - }); - - if (!message) { - throw new AppError("No message found with this ID."); - } - - const { ticket } = message; - - const messageToDelete = await GetWbotMessage(ticket, messageId); - - try { - await messageToDelete.delete(true); - } catch (err) { - throw new AppError("ERR_DELETE_WAPP_MSG"); - } - - await message.update({ isDeleted: true }); - - return message; -}; - -export default DeleteWhatsAppMessage; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 | - - - - - - - - - - - - - - | import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; -import { getWbot } from "../../libs/wbot"; - -const GetProfilePicUrl = async (number: string): Promise<string> => { - const defaultWhatsapp = await GetDefaultWhatsApp(); - - const wbot = getWbot(defaultWhatsapp.id); - - const profilePicUrl = await wbot.getProfilePicUrl(`${number}@c.us`); - - return profilePicUrl; -}; - -export default GetProfilePicUrl; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import GetDefaultWhatsApp from "../../helpers/GetDefaultWhatsApp"; -import { getWbot } from "../../libs/wbot"; -import Contact from "../../models/Contact"; - -const ImportContactsService = async (): Promise<void> => { - const defaultWhatsapp = await GetDefaultWhatsApp(); - - const wbot = getWbot(defaultWhatsapp.id); - - let phoneContacts; - - try { - phoneContacts = await wbot.getContacts(); - } catch (err) { - console.log( - "Could not get whatsapp contacts from phone. Check connection page.", - err - ); - } - - if (phoneContacts) { - await Promise.all( - phoneContacts.map(async ({ number, name }) => { - if (!number) { - return null; - } - if (!name) { - name = number; - } - - const numberExists = await Contact.findOne({ - where: { number } - }); - - if (numberExists) return null; - - return Contact.create({ number, name }); - }) - ); - } -}; - -export default ImportContactsService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import fs from "fs"; -import { MessageMedia, Message as WbotMessage } from "whatsapp-web.js"; -import AppError from "../../errors/AppError"; -import GetTicketWbot from "../../helpers/GetTicketWbot"; -import Ticket from "../../models/Ticket"; - -interface Request { - media: Express.Multer.File; - ticket: Ticket; -} - -const SendWhatsAppMedia = async ({ - media, - ticket -}: Request): Promise<WbotMessage> => { - try { - const wbot = await GetTicketWbot(ticket); - - const newMedia = MessageMedia.fromFilePath(media.path); - - const sentMessage = await wbot.sendMessage( - `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, - newMedia, - { sendAudioAsVoice: true } - ); - - await ticket.update({ lastMessage: media.filename }); - - fs.unlinkSync(media.path); - - return sentMessage; - } catch (err) { - console.log(err); - throw new AppError("ERR_SENDING_WAPP_MSG"); - } -}; - -export default SendWhatsAppMedia; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import { Message as WbotMessage } from "whatsapp-web.js"; -import AppError from "../../errors/AppError"; -import GetTicketWbot from "../../helpers/GetTicketWbot"; -import Ticket from "../../models/Ticket"; - -interface Request { - body: string; - ticket: Ticket; -} - -const SendWhatsAppMessage = async ({ - body, - ticket -}: Request): Promise<WbotMessage> => { - try { - const wbot = await GetTicketWbot(ticket); - - const sentMessage = await wbot.sendMessage( - `${ticket.contact.number}@${ticket.isGroup ? "g" : "c"}.us`, - body - ); - - await ticket.update({ lastMessage: body }); - return sentMessage; - } catch (err) { - console.log(err); - throw new AppError("ERR_SENDING_WAPP_MSG"); - } -}; - -export default SendWhatsAppMessage; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 | - - - - - - - - - - - - - - - - - - | import { initWbot } from "../../libs/wbot"; -import Whatsapp from "../../models/Whatsapp"; -import wbotMessageListener from "./wbotMessageListener"; -import wbotMonitor from "./wbotMonitor"; - -export const StartWhatsAppSessions = async (): Promise<void> => { - const whatsapps = await Whatsapp.findAll(); - if (whatsapps.length > 0) { - whatsapps.forEach(whatsapp => { - initWbot(whatsapp) - .then(() => { - wbotMessageListener(whatsapp); - wbotMonitor(whatsapp); - }) - .catch(err => console.log(err)); - }); - } -}; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
|---|---|---|---|---|---|---|---|---|---|
| CheckIsValidContact.ts | -
-
- |
- 0% | -0/24 | -0% | -0/1 | -0% | -0/1 | -0% | -0/24 | -
| DeleteWhatsAppMessage.ts | -
-
- |
- 0% | -0/36 | -0% | -0/1 | -0% | -0/1 | -0% | -0/36 | -
| GetProfilePicUrl.ts | -
-
- |
- 0% | -0/14 | -0% | -0/1 | -0% | -0/1 | -0% | -0/14 | -
| ImportContactsService.ts | -
-
- |
- 0% | -0/43 | -0% | -0/1 | -0% | -0/1 | -0% | -0/43 | -
| SendWhatsAppMedia.ts | -
-
- |
- 0% | -0/38 | -0% | -0/1 | -0% | -0/1 | -0% | -0/38 | -
| SendWhatsAppMessage.ts | -
-
- |
- 0% | -0/31 | -0% | -0/1 | -0% | -0/1 | -0% | -0/31 | -
| StartWhatsAppSessions.ts | -
-
- |
- 0% | -0/18 | -0% | -0/1 | -0% | -0/1 | -0% | -0/18 | -
| wbotMessageListener.ts | -
-
- |
- 0% | -0/333 | -0% | -0/1 | -0% | -0/1 | -0% | -0/333 | -
| wbotMonitor.ts | -
-
- |
- 0% | -0/87 | -0% | -0/1 | -0% | -0/1 | -0% | -0/87 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import { join } from "path"; -import { promisify } from "util"; -import { writeFile } from "fs"; -import { Op } from "sequelize"; -import { subHours } from "date-fns"; -import * as Sentry from "@sentry/node"; - -import { - Contact as WbotContact, - Message as WbotMessage -} from "whatsapp-web.js"; - -import Contact from "../../models/Contact"; -import Ticket from "../../models/Ticket"; -import Message from "../../models/Message"; -import Whatsapp from "../../models/Whatsapp"; - -import { getIO } from "../../libs/socket"; -import { getWbot } from "../../libs/wbot"; -import AppError from "../../errors/AppError"; -import ShowTicketService from "../TicketServices/ShowTicketService"; -import CreateMessageService from "../MessageServices/CreateMessageService"; - -const writeFileAsync = promisify(writeFile); - -const verifyContact = async ( - msgContact: WbotContact, - profilePicUrl: string -): Promise<Contact> => { - const io = getIO(); - - let contact = await Contact.findOne({ - where: { number: msgContact.id.user } - }); - - if (contact) { - await contact.update({ profilePicUrl }); - - io.emit("contact", { - action: "update", - contact - }); - } else { - contact = await Contact.create({ - name: msgContact.name || msgContact.pushname || msgContact.id.user, - number: msgContact.id.user, - profilePicUrl - }); - - 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, - groupContact?: Contact -): Promise<Ticket> => { - let ticket = await Ticket.findOne({ - where: { - status: { - [Op.or]: ["open", "pending"] - }, - 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: groupContact ? groupContact.id : contact.id - }, - order: [["updatedAt", "DESC"]], - include: ["contact"] - }); - - if (ticket) { - await ticket.update({ status: "pending", userId: null }); - } - } - - if (!ticket) { - const { id } = await Ticket.create({ - contactId: groupContact ? groupContact.id : contact.id, - status: "pending", - isGroup: !!groupContact, - whatsappId - }); - - ticket = await ShowTicketService(id); - } - - return ticket; -}; - -const handlMedia = async ( - msg: WbotMessage, - ticket: Ticket, - contact: Contact -): Promise<Message> => { - const media = await msg.downloadMedia(); - - if (!media) { - throw new AppError("ERR_WAPP_DOWNLOAD_MEDIA"); - } - - if (!media.filename) { - const ext = media.mimetype.split("/")[1].split(";")[0]; - media.filename = `${new Date().getTime()}.${ext}`; - } - - try { - await writeFileAsync( - join(__dirname, "..", "..", "..", "public", media.filename), - media.data, - "base64" - ); - } catch (err) { - console.log(err); - } - - const messageData = { - id: msg.id.id, - ticketId: ticket.id, - contactId: msg.fromMe ? undefined : contact.id, - body: msg.body || media.filename, - fromMe: msg.fromMe, - read: msg.fromMe, - mediaUrl: media.filename, - mediaType: media.mimetype.split("/")[0] - }; - - const newMessage = await CreateMessageService({ messageData }); - - await ticket.update({ lastMessage: msg.body || media.filename }); - return newMessage; -}; - -const handleMessage = async ( - msg: WbotMessage, - ticket: Ticket, - contact: Contact -) => { - let newMessage: Message | null; - - if (msg.hasMedia) { - newMessage = await handlMedia(msg, ticket, contact); - } else { - const messageData = { - id: msg.id.id, - ticketId: ticket.id, - contactId: msg.fromMe ? undefined : contact.id, - body: msg.body, - fromMe: msg.fromMe, - mediaType: msg.type, - read: msg.fromMe - }; - - newMessage = await CreateMessageService({ messageData }); - await ticket.update({ lastMessage: msg.body }); - } - - const io = getIO(); - io.to(ticket.id.toString()) - .to(ticket.status) - .to("notification") - .emit("appMessage", { - action: "create", - message: newMessage, - ticket, - contact - }); -}; - -const isValidMsg = (msg: WbotMessage): boolean => { - if (msg.from === "status@broadcast") return false; - if ( - msg.type === "chat" || - msg.type === "audio" || - msg.type === "ptt" || - msg.type === "video" || - msg.type === "image" || - msg.type === "document" || - msg.type === "vcard" || - msg.type === "sticker" - ) - return true; - return false; -}; - -const wbotMessageListener = (whatsapp: Whatsapp): void => { - const whatsappId = whatsapp.id; - const wbot = getWbot(whatsappId); - const io = getIO(); - - wbot.on("message_create", async msg => { - // console.log(msg); - if (!isValidMsg(msg)) { - return; - } - - try { - let msgContact: WbotContact; - let groupContact: Contact | undefined; - - if (msg.fromMe) { - msgContact = await wbot.getContactById(msg.to); - - // return if it's a media message, it will be handled by media_uploaded event - - if (!msg.hasMedia && msg.type !== "chat" && msg.type !== "vcard") - return; - } 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 verifyGroup(msgGroupContact); - } - - const profilePicUrl = await msgContact.getProfilePicUrl(); - const contact = await verifyContact(msgContact, profilePicUrl); - const ticket = await verifyTicket(contact, whatsappId, groupContact); - - await handleMessage(msg, ticket, contact); - } catch (err) { - Sentry.captureException(err); - console.log(err); - } - }); - - wbot.on("media_uploaded", async msg => { - try { - let groupContact: Contact | undefined; - const msgContact = await wbot.getContactById(msg.to); - if (msg.author) { - const 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, groupContact); - - await handleMessage(msg, ticket, contact); - } catch (err) { - Sentry.captureException(err); - console.log(err); - } - }); - - wbot.on("message_ack", async (msg, ack) => { - await new Promise(r => setTimeout(r, 500)); - - try { - const messageToUpdate = await Message.findByPk(msg.id.id, { - include: ["contact"] - }); - if (!messageToUpdate) { - return; - } - await messageToUpdate.update({ ack }); - - io.to(messageToUpdate.ticketId.toString()).emit("appMessage", { - action: "update", - message: messageToUpdate - }); - } catch (err) { - Sentry.captureException(err); - console.log(err); - } - }); -}; - -export default wbotMessageListener; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import * as Sentry from "@sentry/node"; - -import wbotMessageListener from "./wbotMessageListener"; - -import { getIO } from "../../libs/socket"; -import { getWbot, initWbot } from "../../libs/wbot"; -import Whatsapp from "../../models/Whatsapp"; - -const wbotMonitor = (whatsapp: Whatsapp): void => { - const io = getIO(); - const sessionName = whatsapp.name; - const wbot = getWbot(whatsapp.id); - - try { - wbot.on("change_state", async newState => { - console.log("Monitor session:", sessionName, newState); - try { - await whatsapp.update({ status: newState }); - } catch (err) { - Sentry.captureException(err); - console.log(err); - } - - io.emit("whatsappSession", { - action: "update", - session: whatsapp - }); - }); - - wbot.on("change_battery", async batteryInfo => { - const { battery, plugged } = batteryInfo; - console.log( - `Battery session: ${sessionName} ${battery}% - Charging? ${plugged}` - ); - - try { - await whatsapp.update({ battery, plugged }); - } catch (err) { - Sentry.captureException(err); - console.log(err); - } - - io.emit("whatsappSession", { - action: "update", - session: whatsapp - }); - }); - - wbot.on("disconnected", async reason => { - console.log("Disconnected session:", sessionName, reason); - try { - await whatsapp.update({ status: "disconnected" }); - } catch (err) { - Sentry.captureException(err); - console.log(err); - } - - io.emit("whatsappSession", { - action: "update", - session: whatsapp - }); - - setTimeout( - () => - initWbot(whatsapp) - .then(() => { - wbotMessageListener(whatsapp); - wbotMonitor(whatsapp); - }) - .catch(err => { - Sentry.captureException(err); - console.log(err); - }), - 2000 - ); - }); - - // setInterval(() => { - // wbot.resetState(); - // }, 20000); - } catch (err) { - Sentry.captureException(err); - console.log(err); - } -}; - -export default wbotMonitor; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import * as Yup from "yup"; - -import AppError from "../../errors/AppError"; -import Whatsapp from "../../models/Whatsapp"; - -interface Request { - name: string; - status?: string; - isDefault?: boolean; -} - -const CreateWhatsAppService = async ({ - name, - status = "INITIALIZING", - isDefault = false -}: Request): Promise<Whatsapp> => { - const schema = Yup.object().shape({ - name: Yup.string() - .required() - .min(2) - .test( - "Check-name", - "This whatsapp name is already used.", - async value => { - if (value) { - const whatsappFound = await Whatsapp.findOne({ - where: { name: value } - }); - return !whatsappFound; - } - return true; - } - ), - isDefault: Yup.boolean() - .required() - .test( - "Check-default", - "Only one default whatsapp is permitted", - async value => { - if (value === true) { - const whatsappFound = await Whatsapp.findOne({ - where: { isDefault: true } - }); - return !whatsappFound; - } - return true; - } - ) - }); - - try { - await schema.validate({ name, status, isDefault }); - } catch (err) { - throw new AppError(err.message); - } - - const whatsapp = await Whatsapp.create({ - name, - status, - isDefault - }); - - return whatsapp; -}; - -export default CreateWhatsAppService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 | - - - - - - - - - - - - - - - - | import Whatsapp from "../../models/Whatsapp"; -import AppError from "../../errors/AppError"; - -const DeleteWhatsApprService = async (id: string): Promise<void> => { - const whatsapp = await Whatsapp.findOne({ - where: { id } - }); - - if (!whatsapp) { - throw new AppError("ERR_NO_WAPP_FOUND", 404); - } - - await whatsapp.destroy(); -}; - -export default DeleteWhatsApprService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 | - - - - - - - - - | import Whatsapp from "../../models/Whatsapp"; - -const ListWhatsAppsService = async (): Promise<Whatsapp[]> => { - const whatsapps = await Whatsapp.findAll(); - - return whatsapps; -}; - -export default ListWhatsAppsService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 | - - - - - - - - - - - - - - - - | import Whatsapp from "../../models/Whatsapp"; -import AppError from "../../errors/AppError"; - -const ShowWhatsAppService = async ( - id: string | number -): Promise<Whatsapp | undefined> => { - const whatsapp = await Whatsapp.findByPk(id); - - if (!whatsapp) { - throw new AppError("ERR_NO_WAPP_FOUND", 404); - } - - return whatsapp; -}; - -export default ShowWhatsAppService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| 1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | import * as Yup from "yup"; - -import AppError from "../../errors/AppError"; -import Whatsapp from "../../models/Whatsapp"; - -interface WhatsappData { - name?: string; - status?: string; - isDefault?: boolean; -} - -interface Request { - whatsappData: WhatsappData; - whatsappId: string; -} - -const UpdateWhatsAppService = async ({ - whatsappData, - whatsappId -}: Request): Promise<Whatsapp> => { - const schema = Yup.object().shape({ - name: Yup.string().min(2), - default: Yup.boolean().test( - "Check-default", - "Only one default whatsapp is permited", - async value => { - if (value === true) { - const whatsappFound = await Whatsapp.findOne({ - where: { default: true } - }); - if (whatsappFound) { - return !(whatsappFound.id !== +whatsappId); - } - return true; - } - return true; - } - ) - }); - - const { name, status, isDefault } = whatsappData; - - try { - await schema.validate({ name, status, isDefault }); - } catch (err) { - throw new AppError(err.message); - } - - const whatsapp = await Whatsapp.findOne({ - where: { id: whatsappId } - }); - - if (!whatsapp) { - throw new AppError("ERR_NO_WAPP_FOUND", 404); - } - - await whatsapp.update({ - name, - status, - isDefault - }); - - return whatsapp; -}; - -export default UpdateWhatsAppService; - |
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
|---|---|---|---|---|---|---|---|---|---|
| CreateWhatsAppService.ts | -
-
- |
- 0% | -0/66 | -0% | -0/1 | -0% | -0/1 | -0% | -0/66 | -
| DeleteWhatsAppService.ts | -
-
- |
- 0% | -0/16 | -0% | -0/1 | -0% | -0/1 | -0% | -0/16 | -
| ListWhatsAppsService.ts | -
-
- |
- 0% | -0/9 | -0% | -0/1 | -0% | -0/1 | -0% | -0/9 | -
| ShowWhatsAppService.ts | -
-
- |
- 0% | -0/16 | -0% | -0/1 | -0% | -0/1 | -0% | -0/16 | -
| UpdateWhatsAppService.ts | -
-
- |
- 0% | -0/66 | -0% | -0/1 | -0% | -0/1 | -0% | -0/66 | -
- Press n or j to go to the next uncovered block, b, p or k for the previous block. -
-| File | -- | Statements | -- | Branches | -- | Functions | -- | Lines | -- |
|---|---|---|---|---|---|---|---|---|---|
| AuthServices | -
-
- |
- 0% | -0/41 | -0% | -0/1 | -0% | -0/1 | -0% | -0/41 | -
| ContactServices | -
-
- |
- 0% | -0/196 | -0% | -0/5 | -0% | -0/5 | -0% | -0/196 | -
| MessageServices | -
-
- |
- 0% | -0/103 | -0% | -0/2 | -0% | -0/2 | -0% | -0/103 | -
| SettingServices | -
-
- |
- 0% | -0/35 | -0% | -0/2 | -0% | -0/2 | -0% | -0/35 | -
| TicketServices | -
-
- |
- 0% | -0/294 | -0% | -0/5 | -0% | -0/5 | -0% | -0/294 | -
| UserServices | -
-
- |
- 24.54% | -67/273 | -62.16% | -23/37 | -60% | -9/15 | -24.54% | -67/273 | -
| WbotServices | -
-
- |
- 0% | -0/624 | -0% | -0/9 | -0% | -0/9 | -0% | -0/624 | -
| WhatsappService | -
-
- |
- 0% | -0/173 | -0% | -0/5 | -0% | -0/5 | -0% | -0/173 | -