mirror of
https://github.com/cheveguerra/whaticket-community.git
synced 2026-04-17 19:37:02 +00:00
@@ -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",
|
||||
|
||||
92
backend/src/controllers/ApiController.ts
Normal file
92
backend/src/controllers/ApiController.ts
Normal 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();
|
||||
};
|
||||
@@ -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", {});
|
||||
}
|
||||
};
|
||||
39
backend/src/middleware/isAuthApi.ts
Normal file
39
backend/src/middleware/isAuthApi.ts
Normal 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;
|
||||
14
backend/src/routes/apiRoutes.ts
Normal file
14
backend/src/routes/apiRoutes.ts
Normal 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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user