|
|
@@ -0,0 +1,362 @@
|
|
|
+import * as Yup from "yup";
|
|
|
+import { Request, Response } from "express";
|
|
|
+import { getIO } from "../libs/socket";
|
|
|
+import { head } from "lodash";
|
|
|
+import fs from "fs";
|
|
|
+import path from "path";
|
|
|
+
|
|
|
+import ListService from "../services/CampaignService/ListService";
|
|
|
+import CreateService from "../services/CampaignService/CreateService";
|
|
|
+import ShowService from "../services/CampaignService/ShowService";
|
|
|
+import UpdateService from "../services/CampaignService/UpdateService";
|
|
|
+import DeleteService from "../services/CampaignService/DeleteService";
|
|
|
+import FindService from "../services/CampaignService/FindService";
|
|
|
+
|
|
|
+import Campaign from "../models/Campaign";
|
|
|
+
|
|
|
+import AppError from "../errors/AppError";
|
|
|
+import { CancelService } from "../services/CampaignService/CancelService";
|
|
|
+import { RestartService } from "../services/CampaignService/RestartService";
|
|
|
+import TicketTag from "../models/TicketTag";
|
|
|
+import Ticket from "../models/Ticket";
|
|
|
+import Contact from "../models/Contact";
|
|
|
+import ContactList from "../models/ContactList";
|
|
|
+import ContactListItem from "../models/ContactListItem";
|
|
|
+
|
|
|
+type IndexQuery = {
|
|
|
+ searchParam: string;
|
|
|
+ pageNumber: string;
|
|
|
+ companyId: string | number;
|
|
|
+};
|
|
|
+
|
|
|
+type StoreData = {
|
|
|
+ name: string;
|
|
|
+ status: string;
|
|
|
+ scheduledAt: string;
|
|
|
+ companyId: number;
|
|
|
+ contactListId: number;
|
|
|
+ tagListId: number | string;
|
|
|
+ fileListId: number;
|
|
|
+};
|
|
|
+
|
|
|
+type FindParams = {
|
|
|
+ companyId: string;
|
|
|
+};
|
|
|
+
|
|
|
+export const index = async (req: Request, res: Response): Promise<Response> => {
|
|
|
+ const { searchParam, pageNumber } = req.query as IndexQuery;
|
|
|
+ const { companyId } = req.user;
|
|
|
+
|
|
|
+ const { records, count, hasMore } = await ListService({
|
|
|
+ searchParam,
|
|
|
+ pageNumber,
|
|
|
+ companyId
|
|
|
+ });
|
|
|
+
|
|
|
+ return res.json({ records, count, hasMore });
|
|
|
+};
|
|
|
+
|
|
|
+export const store = async (req: Request, res: Response): Promise<Response> => {
|
|
|
+ const { companyId } = req.user;
|
|
|
+ const data = req.body as StoreData;
|
|
|
+
|
|
|
+ const schema = Yup.object().shape({
|
|
|
+ name: Yup.string().required()
|
|
|
+ });
|
|
|
+
|
|
|
+ try {
|
|
|
+ await schema.validate(data);
|
|
|
+ } catch (err: any) {
|
|
|
+ throw new AppError(err.message);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof data.tagListId === 'number' && typeof data.contactListId !== 'number') {
|
|
|
+ const tagId = data.tagListId;
|
|
|
+ const campanhaNome = data.name;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const contactListId = await createContactListFromTag(tagId, companyId, campanhaNome);
|
|
|
+
|
|
|
+ const record = await CreateService({
|
|
|
+ ...data,
|
|
|
+ tagId: Number(data.tagListId),
|
|
|
+ companyId,
|
|
|
+ contactListId: contactListId,
|
|
|
+ });
|
|
|
+ const io = getIO();
|
|
|
+ io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-campaign`, {
|
|
|
+ action: "create",
|
|
|
+ record
|
|
|
+ });
|
|
|
+ return res.status(200).json(record);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error:', error);
|
|
|
+ return res.status(500).json({ error: 'Error creating contact list' });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof data.tagListId === 'number' && typeof data.contactListId === 'number') {
|
|
|
+ const tagId = data.tagListId;
|
|
|
+ const campanhaNome = data.name;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const contactListId = await createContactListFromTagAndContactList(tagId, data.contactListId, companyId, campanhaNome);
|
|
|
+
|
|
|
+ const record = await CreateService({
|
|
|
+ ...data,
|
|
|
+ tagId: Number(data.tagListId),
|
|
|
+ companyId,
|
|
|
+ contactListId: contactListId,
|
|
|
+ });
|
|
|
+
|
|
|
+ const io = getIO();
|
|
|
+
|
|
|
+ io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-campaign`, {
|
|
|
+ action: "create",
|
|
|
+ record
|
|
|
+ });
|
|
|
+
|
|
|
+ return res.status(200).json(record);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error:', error);
|
|
|
+ return res.status(500).json({ error: 'Error creating contact list' });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const record = await CreateService({
|
|
|
+ ...data,
|
|
|
+ tagId: null,
|
|
|
+ companyId
|
|
|
+ });
|
|
|
+
|
|
|
+ const io = getIO();
|
|
|
+ io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-campaign`, {
|
|
|
+ action: "create",
|
|
|
+ record
|
|
|
+ });
|
|
|
+
|
|
|
+ return res.status(200).json(record);
|
|
|
+};
|
|
|
+
|
|
|
+export const show = async (req: Request, res: Response): Promise<Response> => {
|
|
|
+ const { id } = req.params;
|
|
|
+
|
|
|
+ const record = await ShowService(id);
|
|
|
+
|
|
|
+ return res.status(200).json(record);
|
|
|
+};
|
|
|
+
|
|
|
+export const update = async (
|
|
|
+ req: Request,
|
|
|
+ res: Response
|
|
|
+): Promise<Response> => {
|
|
|
+ const data = req.body as StoreData;
|
|
|
+ const { companyId } = req.user;
|
|
|
+
|
|
|
+ const schema = Yup.object().shape({
|
|
|
+ name: Yup.string().required()
|
|
|
+ });
|
|
|
+
|
|
|
+ try {
|
|
|
+ await schema.validate(data);
|
|
|
+ } catch (err: any) {
|
|
|
+ throw new AppError(err.message);
|
|
|
+ }
|
|
|
+
|
|
|
+ const { id } = req.params;
|
|
|
+
|
|
|
+ const record = await UpdateService({
|
|
|
+ ...data,
|
|
|
+ tagId: typeof data.tagListId === 'number' ? Number(data.tagListId) : null,
|
|
|
+ id
|
|
|
+ }, companyId);
|
|
|
+
|
|
|
+ const io = getIO();
|
|
|
+ io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-campaign`, {
|
|
|
+ action: "update",
|
|
|
+ record
|
|
|
+ });
|
|
|
+
|
|
|
+ return res.status(200).json(record);
|
|
|
+};
|
|
|
+
|
|
|
+export const cancel = async (
|
|
|
+ req: Request,
|
|
|
+ res: Response
|
|
|
+): Promise<Response> => {
|
|
|
+ const { id } = req.params;
|
|
|
+
|
|
|
+ await CancelService(+id);
|
|
|
+
|
|
|
+ return res.status(204).json({ message: "Cancelamento realizado" });
|
|
|
+};
|
|
|
+
|
|
|
+export const restart = async (
|
|
|
+ req: Request,
|
|
|
+ res: Response
|
|
|
+): Promise<Response> => {
|
|
|
+ const { id } = req.params;
|
|
|
+
|
|
|
+ await RestartService(+id);
|
|
|
+
|
|
|
+ return res.status(204).json({ message: "Reinício dos disparos" });
|
|
|
+};
|
|
|
+
|
|
|
+export const remove = async (
|
|
|
+ req: Request,
|
|
|
+ res: Response
|
|
|
+): Promise<Response> => {
|
|
|
+ const { id } = req.params;
|
|
|
+ const { companyId } = req.user;
|
|
|
+
|
|
|
+ await DeleteService(id);
|
|
|
+
|
|
|
+ const io = getIO();
|
|
|
+ io.to(`company-${companyId}-mainchannel`).emit(`company-${companyId}-campaign`, {
|
|
|
+ action: "delete",
|
|
|
+ id
|
|
|
+ });
|
|
|
+
|
|
|
+ return res.status(200).json({ message: "Campaign deleted" });
|
|
|
+};
|
|
|
+
|
|
|
+export const findList = async (
|
|
|
+ req: Request,
|
|
|
+ res: Response
|
|
|
+): Promise<Response> => {
|
|
|
+ const params = req.query as FindParams;
|
|
|
+ const records: Campaign[] = await FindService(params);
|
|
|
+
|
|
|
+ return res.status(200).json(records);
|
|
|
+};
|
|
|
+
|
|
|
+export const mediaUpload = async (
|
|
|
+ req: Request,
|
|
|
+ res: Response
|
|
|
+): Promise<Response> => {
|
|
|
+ const { id } = req.params;
|
|
|
+ const files = req.files as Express.Multer.File[];
|
|
|
+ const file = head(files);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const campaign = await Campaign.findByPk(id);
|
|
|
+ campaign.mediaPath = file.filename;
|
|
|
+ campaign.mediaName = file.originalname;
|
|
|
+ await campaign.save();
|
|
|
+ return res.send({ mensagem: "Mensagem enviada" });
|
|
|
+ } catch (err: any) {
|
|
|
+ throw new AppError(err.message);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+export const deleteMedia = async (
|
|
|
+ req: Request,
|
|
|
+ res: Response
|
|
|
+): Promise<Response> => {
|
|
|
+ const { id } = req.params;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const campaign = await Campaign.findByPk(id);
|
|
|
+ const filePath = path.resolve("public", campaign.mediaPath);
|
|
|
+ const fileExists = fs.existsSync(filePath);
|
|
|
+ if (fileExists) {
|
|
|
+ fs.unlinkSync(filePath);
|
|
|
+ }
|
|
|
+
|
|
|
+ campaign.mediaPath = null;
|
|
|
+ campaign.mediaName = null;
|
|
|
+ await campaign.save();
|
|
|
+ return res.send({ mensagem: "Arquivo excluído" });
|
|
|
+ } catch (err: any) {
|
|
|
+ throw new AppError(err.message);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+export async function createContactListFromTag(tagId: number, companyId: number, campanhaNome: string) : Promise<number> {
|
|
|
+ const currentDate = new Date();
|
|
|
+ const formattedDate = currentDate.toISOString();
|
|
|
+
|
|
|
+ try {
|
|
|
+ const ticketTags = await TicketTag.findAll({ where: { tagId } });
|
|
|
+ const ticketIds = ticketTags.map((ticketTag) => ticketTag.ticketId);
|
|
|
+
|
|
|
+ const tickets = await Ticket.findAll({ where: { id: ticketIds } });
|
|
|
+ const contactIds = tickets.map((ticket) => ticket.contactId);
|
|
|
+
|
|
|
+ const selectedContacts = await Contact.findAll({ where: { id: contactIds } });
|
|
|
+
|
|
|
+ const randomName = `${campanhaNome} | TAG: ${tagId} - ${formattedDate}`
|
|
|
+ const contactList = await ContactList.create({ name: randomName, companyId: companyId });
|
|
|
+
|
|
|
+ const { id: contactListId } = contactList;
|
|
|
+
|
|
|
+ const setContacts = new Set(selectedContacts);
|
|
|
+ const contacts = Array.from(setContacts);
|
|
|
+
|
|
|
+ const contactListItems = contacts.map((contact) => ({
|
|
|
+ name: contact.name,
|
|
|
+ number: contact.number,
|
|
|
+ email: contact.email,
|
|
|
+ contactListId,
|
|
|
+ companyId,
|
|
|
+ isWhatsappValid: true,
|
|
|
+ }));
|
|
|
+
|
|
|
+ await ContactListItem.bulkCreate(contactListItems);
|
|
|
+
|
|
|
+ return contactListId;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error creating contact list:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export async function createContactListFromTagAndContactList(tagId: number, contactListId: number, companyId: number, campanhaNome: string) : Promise<number> {
|
|
|
+ const currentDate = new Date();
|
|
|
+ const formattedDate = currentDate.toISOString();
|
|
|
+
|
|
|
+ try {
|
|
|
+ const ticketTags = await TicketTag.findAll({ where: { tagId } });
|
|
|
+ const ticketIds = ticketTags.map((ticketTag) => ticketTag.ticketId);
|
|
|
+
|
|
|
+ const tickets = await Ticket.findAll({ where: { id: ticketIds } });
|
|
|
+ const contactIds = tickets.map((ticket) => ticket.contactId);
|
|
|
+
|
|
|
+ const selectedContactListItems = await ContactListItem.findAll({ where: { contactListId } })
|
|
|
+ const ticketContacts = await Contact.findAll({ where: { id: contactIds } });
|
|
|
+
|
|
|
+ const contactMap = new Map<string, {email: string, name: string, number: string}>();
|
|
|
+
|
|
|
+ selectedContactListItems.forEach(contact => {
|
|
|
+ contactMap.set(contact.number, {email: contact.email, name: contact.name, number: contact.number});
|
|
|
+ });
|
|
|
+
|
|
|
+ ticketContacts.forEach(contact => {
|
|
|
+ contactMap.set(contact.number, {email: contact.email, name: contact.name, number: contact.number});
|
|
|
+ });
|
|
|
+
|
|
|
+ const mergedContacts = Array.from(contactMap.values());
|
|
|
+
|
|
|
+ const randomName = `${campanhaNome} | TAG: ${tagId} - ${formattedDate}`
|
|
|
+ const contactList = await ContactList.create({ name: randomName, companyId: companyId });
|
|
|
+
|
|
|
+ const { id } = contactList;
|
|
|
+
|
|
|
+ const contactLists = mergedContacts.map((contact) => ({
|
|
|
+ name: contact.name,
|
|
|
+ number: contact.number,
|
|
|
+ email: contact.email,
|
|
|
+ contactListId: id,
|
|
|
+ companyId,
|
|
|
+ isWhatsappValid: true,
|
|
|
+
|
|
|
+ }));
|
|
|
+
|
|
|
+ await ContactListItem.bulkCreate(contactLists);
|
|
|
+
|
|
|
+ return id;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error creating contact list:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+}
|