Merge pull request #329 from w3nder/master

Feat. Api send
This commit is contained in:
Cassio Santos
2022-01-11 06:40:20 -03:00
committed by GitHub
9 changed files with 221 additions and 5 deletions

View File

@@ -37,6 +37,7 @@
"sequelize-cli": "^5.5.1",
"sequelize-typescript": "^1.1.0",
"socket.io": "^3.0.5",
"uuid": "^8.3.2",
"whatsapp-web.js": "^1.15.3",
"yup": "^0.32.8"
},
@@ -53,6 +54,7 @@
"@types/multer": "^1.4.4",
"@types/node": "^14.11.8",
"@types/supertest": "^2.0.10",
"@types/uuid": "^8.3.3",
"@types/validator": "^13.1.0",
"@types/yup": "^0.29.8",
"@typescript-eslint/eslint-plugin": "^4.4.0",

View File

@@ -0,0 +1,92 @@
import { Request, Response } from "express";
import * as Yup from "yup";
import AppError from "../errors/AppError";
import GetDefaultWhatsApp from "../helpers/GetDefaultWhatsApp";
import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead";
import Message from "../models/Message";
import CreateOrUpdateContactService from "../services/ContactServices/CreateOrUpdateContactService";
import FindOrCreateTicketService from "../services/TicketServices/FindOrCreateTicketService";
import ShowTicketService from "../services/TicketServices/ShowTicketService";
import CheckIsValidContact from "../services/WbotServices/CheckIsValidContact";
import CheckContactNumber from "../services/WbotServices/CheckNumber";
import GetProfilePicUrl from "../services/WbotServices/GetProfilePicUrl";
import SendWhatsAppMedia from "../services/WbotServices/SendWhatsAppMedia";
import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage";
type MessageData = {
body: string;
fromMe: boolean;
read: boolean;
quotedMsg?: Message;
};
interface ContactData {
number: string;
}
const createContact = async (newContact: string) => {
await CheckIsValidContact(newContact);
const validNumber: any = await CheckContactNumber(newContact);
const profilePicUrl = await GetProfilePicUrl(validNumber);
const number = validNumber;
const contactData = {
name: `${number}`,
number,
profilePicUrl,
isGroup: false
};
const contact = await CreateOrUpdateContactService(contactData);
const defaultWhatsapp = await GetDefaultWhatsApp();
const createTicket = await FindOrCreateTicketService(
contact,
defaultWhatsapp.id,
1
);
const ticket = await ShowTicketService(createTicket.id);
SetTicketMessagesAsRead(ticket);
return ticket;
};
export const index = async (req: Request, res: Response): Promise<Response> => {
const newContact: ContactData = req.body;
const { body, quotedMsg }: MessageData = req.body;
const medias = req.files as Express.Multer.File[];
newContact.number = newContact.number.replace("-", "").replace(" ", "");
const schema = Yup.object().shape({
number: Yup.string()
.required()
.matches(/^\d+$/, "Invalid number format. Only numbers is allowed.")
});
try {
await schema.validate(newContact);
} catch (err: any) {
throw new AppError(err.message);
}
const contactAndTicket = await createContact(newContact.number);
if (medias) {
await Promise.all(
medias.map(async (media: Express.Multer.File) => {
await SendWhatsAppMedia({ body, media, ticket: contactAndTicket });
})
);
} else {
await SendWhatsAppMessage({ body, ticket: contactAndTicket, quotedMsg });
}
return res.send();
};

View File

@@ -0,0 +1,23 @@
import { QueryInterface } from "sequelize";
import { v4 as uuidv4 } from "uuid";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.bulkInsert(
"Settings",
[
{
key: "userApiToken",
value: uuidv4(),
createdAt: new Date(),
updatedAt: new Date()
}
],
{}
);
},
down: (queryInterface: QueryInterface) => {
return queryInterface.bulkDelete("Settings", {});
}
};

View File

@@ -0,0 +1,39 @@
import { Request, Response, NextFunction } from "express";
import AppError from "../errors/AppError";
import ListSettingByValueService from "../services/SettingServices/ListSettingByValueService";
const isAuthApi = async (
req: Request,
res: Response,
next: NextFunction
): Promise<void> => {
const authHeader = req.headers.authorization;
if (!authHeader) {
throw new AppError("ERR_SESSION_EXPIRED", 401);
}
const [, token] = authHeader.split(" ");
try {
const getToken = await ListSettingByValueService(token);
if (!getToken) {
throw new AppError("ERR_SESSION_EXPIRED", 401);
}
if (getToken.value !== token) {
throw new AppError("ERR_SESSION_EXPIRED", 401);
}
} catch (err) {
console.log(err);
throw new AppError(
"Invalid token. We'll try to assign a new one on next request",
403
);
}
return next();
};
export default isAuthApi;

View File

@@ -0,0 +1,14 @@
import express from "express";
import multer from "multer";
import uploadConfig from "../config/upload";
import * as ApiController from "../controllers/ApiController";
import isAuthApi from "../middleware/isAuthApi";
const upload = multer(uploadConfig);
const ApiRoutes = express.Router();
ApiRoutes.post("/send", isAuthApi, upload.array("medias"), ApiController.index);
export default ApiRoutes;

View File

@@ -10,6 +10,7 @@ import messageRoutes from "./messageRoutes";
import whatsappSessionRoutes from "./whatsappSessionRoutes";
import queueRoutes from "./queueRoutes";
import quickAnswerRoutes from "./quickAnswerRoutes";
import apiRoutes from "./apiRoutes";
const routes = Router();
@@ -23,5 +24,6 @@ routes.use(messageRoutes);
routes.use(whatsappSessionRoutes);
routes.use(queueRoutes);
routes.use(quickAnswerRoutes);
routes.use("/api/messages", apiRoutes);
export default routes;

View File

@@ -0,0 +1,22 @@
import AppError from "../../errors/AppError";
import Setting from "../../models/Setting";
interface Response {
key: string;
value: string;
}
const ListSettingByKeyService = async (
value: string
): Promise<Response | undefined> => {
const settings = await Setting.findOne({
where: { value }
});
if (!settings) {
throw new AppError("ERR_NO_API_TOKEN_FOUND", 404);
}
return { key: settings.key, value: settings.value };
};
export default ListSettingByKeyService;

View File

@@ -7,24 +7,28 @@ import Ticket from "../../models/Ticket";
interface Request {
media: Express.Multer.File;
ticket: Ticket;
body?: string;
}
const SendWhatsAppMedia = async ({
media,
ticket
ticket,
body
}: 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 }
{
caption: body,
sendAudioAsVoice: true
}
);
await ticket.update({ lastMessage: media.filename });
await ticket.update({ lastMessage: body || media.filename });
fs.unlinkSync(media.path);

View File

@@ -6,6 +6,7 @@ import Paper from "@material-ui/core/Paper";
import Typography from "@material-ui/core/Typography";
import Container from "@material-ui/core/Container";
import Select from "@material-ui/core/Select";
import TextField from "@material-ui/core/TextField";
import { toast } from "react-toastify";
import api from "../../services/api";
@@ -16,13 +17,15 @@ const useStyles = makeStyles(theme => ({
root: {
display: "flex",
alignItems: "center",
padding: theme.spacing(4),
padding: theme.spacing(8, 8, 3),
},
paper: {
padding: theme.spacing(2),
display: "flex",
alignItems: "center",
marginBottom: 12,
},
settingOption: {
@@ -31,6 +34,7 @@ const useStyles = makeStyles(theme => ({
margin: {
margin: theme.spacing(1),
},
}));
const Settings = () => {
@@ -117,7 +121,21 @@ const Settings = () => {
{i18n.t("settings.settings.userCreation.options.disabled")}
</option>
</Select>
</Paper>
<Paper className={classes.paper}>
<TextField
id="api-token-setting"
readonly
label="Token Api"
margin="dense"
variant="outlined"
fullWidth
value={settings && settings.length > 0 && getSettingValue("userApiToken")}
/>
</Paper>
</Container>
</div>
);