457 lines
15 KiB
TypeScript
457 lines
15 KiB
TypeScript
import type { Express, Request, Response } from "express";
|
|
import multer from "multer";
|
|
import path from "path";
|
|
import fs from "fs";
|
|
import { whatsappService } from "./service";
|
|
|
|
const uploadDir = path.join(process.cwd(), "uploads", "whatsapp");
|
|
if (!fs.existsSync(uploadDir)) {
|
|
fs.mkdirSync(uploadDir, { recursive: true });
|
|
}
|
|
|
|
const storage = multer.diskStorage({
|
|
destination: (req, file, cb) => cb(null, uploadDir),
|
|
filename: (req, file, cb) => {
|
|
const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
|
|
cb(null, uniqueSuffix + path.extname(file.originalname));
|
|
},
|
|
});
|
|
|
|
const upload = multer({
|
|
storage,
|
|
limits: { fileSize: 64 * 1024 * 1024 }, // 64MB limit
|
|
});
|
|
|
|
export function registerWhatsappRoutes(app: Express): void {
|
|
app.post("/api/whatsapp/connect", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const result = await whatsappService.connect(userId);
|
|
res.json(result);
|
|
} catch (error) {
|
|
console.error("WhatsApp connect error:", error);
|
|
res.status(500).json({ error: "Failed to connect to WhatsApp" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/whatsapp/disconnect", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
await whatsappService.disconnect(userId);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error("WhatsApp disconnect error:", error);
|
|
res.status(500).json({ error: "Failed to disconnect from WhatsApp" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/whatsapp/status", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const session = whatsappService.getStatus(userId);
|
|
|
|
if (!session) {
|
|
return res.json({ status: "disconnected", qrCode: null, phoneNumber: null });
|
|
}
|
|
|
|
res.json({
|
|
status: session.status,
|
|
qrCode: session.qrCode,
|
|
phoneNumber: session.phoneNumber,
|
|
});
|
|
} catch (error) {
|
|
console.error("WhatsApp status error:", error);
|
|
res.status(500).json({ error: "Failed to get WhatsApp status" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/whatsapp/send", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const { to, message } = req.body;
|
|
|
|
if (!to || !message) {
|
|
return res.status(400).json({ error: "to and message are required" });
|
|
}
|
|
|
|
await whatsappService.sendMessage(userId, to, message);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error("WhatsApp send error:", error);
|
|
res.status(500).json({ error: "Failed to send message" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/whatsapp/tickets", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const status = req.query.status as string | undefined;
|
|
const tickets = await whatsappService.getTickets(userId, status);
|
|
res.json(tickets);
|
|
} catch (error) {
|
|
console.error("WhatsApp tickets error:", error);
|
|
res.status(500).json({ error: "Failed to get tickets" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/whatsapp/tickets/:id/messages", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const ticketId = parseInt(req.params.id);
|
|
const messages = await whatsappService.getTicketMessages(userId, ticketId);
|
|
res.json(messages);
|
|
} catch (error) {
|
|
console.error("WhatsApp ticket messages error:", error);
|
|
res.status(500).json({ error: "Failed to get messages" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/whatsapp/tickets/:id/read", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const ticketId = parseInt(req.params.id);
|
|
await whatsappService.markTicketAsRead(userId, ticketId);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error("WhatsApp mark read error:", error);
|
|
res.status(500).json({ error: "Failed to mark as read" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/whatsapp/tickets/:id/close", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const ticketId = parseInt(req.params.id);
|
|
await whatsappService.closeTicket(userId, ticketId);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error("WhatsApp close ticket error:", error);
|
|
res.status(500).json({ error: "Failed to close ticket" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/whatsapp/tickets/:id/send", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const ticketId = parseInt(req.params.id);
|
|
const { message } = req.body;
|
|
|
|
if (!message) {
|
|
return res.status(400).json({ error: "message is required" });
|
|
}
|
|
|
|
const tickets = await whatsappService.getTickets(userId);
|
|
const ticket = tickets.find(t => t.id === ticketId);
|
|
|
|
if (!ticket || !ticket.contact) {
|
|
return res.status(404).json({ error: "Ticket not found" });
|
|
}
|
|
|
|
await whatsappService.sendMessage(userId, ticket.contact.whatsappId, message);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error("WhatsApp send to ticket error:", error);
|
|
res.status(500).json({ error: "Failed to send message" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/whatsapp/contacts", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const contacts = await whatsappService.getContacts(userId);
|
|
res.json(contacts);
|
|
} catch (error) {
|
|
console.error("WhatsApp contacts error:", error);
|
|
res.status(500).json({ error: "Failed to get contacts" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/whatsapp/auto-reply/config", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const config = whatsappService.getAutoReplyConfig(userId);
|
|
res.json(config);
|
|
} catch (error) {
|
|
console.error("WhatsApp auto-reply config error:", error);
|
|
res.status(500).json({ error: "Failed to get auto-reply config" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/whatsapp/auto-reply/config", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const config = req.body;
|
|
whatsappService.setAutoReplyConfig(userId, config);
|
|
res.json({ success: true, config: whatsappService.getAutoReplyConfig(userId) });
|
|
} catch (error) {
|
|
console.error("WhatsApp set auto-reply config error:", error);
|
|
res.status(500).json({ error: "Failed to set auto-reply config" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/whatsapp/analytics", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const analytics = await whatsappService.getAnalytics(userId);
|
|
res.json(analytics);
|
|
} catch (error) {
|
|
console.error("WhatsApp analytics error:", error);
|
|
res.status(500).json({ error: "Failed to get analytics" });
|
|
}
|
|
});
|
|
|
|
app.delete("/api/whatsapp/messages/:messageId", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const { messageId } = req.params;
|
|
await whatsappService.deleteMessage(userId, messageId);
|
|
res.json({ success: true });
|
|
} catch (error: any) {
|
|
console.error("WhatsApp delete message error:", error);
|
|
res.status(500).json({ error: error.message || "Failed to delete message" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/whatsapp/messages/:messageId/reply", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const { messageId } = req.params;
|
|
const { text } = req.body;
|
|
|
|
if (!text) {
|
|
return res.status(400).json({ error: "text is required" });
|
|
}
|
|
|
|
await whatsappService.replyToMessage(userId, messageId, text);
|
|
res.json({ success: true });
|
|
} catch (error: any) {
|
|
console.error("WhatsApp reply message error:", error);
|
|
res.status(500).json({ error: error.message || "Failed to reply to message" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/whatsapp/messages/:messageId/forward", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const { messageId } = req.params;
|
|
const { targetJids } = req.body;
|
|
|
|
if (!targetJids || !Array.isArray(targetJids) || targetJids.length === 0) {
|
|
return res.status(400).json({ error: "targetJids array is required" });
|
|
}
|
|
|
|
await whatsappService.forwardMessage(userId, messageId, targetJids);
|
|
res.json({ success: true });
|
|
} catch (error: any) {
|
|
console.error("WhatsApp forward message error:", error);
|
|
res.status(500).json({ error: error.message || "Failed to forward message" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/whatsapp/contacts/sync", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const count = await whatsappService.syncContacts(userId);
|
|
res.json({ success: true, syncedCount: count });
|
|
} catch (error: any) {
|
|
console.error("WhatsApp sync contacts error:", error);
|
|
res.status(500).json({ error: error.message || "Failed to sync contacts" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/whatsapp/contacts", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const { name, phone, email, company, isLead, leadSource } = req.body;
|
|
|
|
if (!name || !phone) {
|
|
return res.status(400).json({ error: "name and phone are required" });
|
|
}
|
|
|
|
const result = await whatsappService.createContact(userId, {
|
|
name,
|
|
phone,
|
|
email,
|
|
company,
|
|
isLead: isLead === true,
|
|
leadSource: leadSource || "whatsapp",
|
|
});
|
|
|
|
res.json(result);
|
|
} catch (error: any) {
|
|
console.error("WhatsApp create contact error:", error);
|
|
res.status(500).json({ error: error.message || "Failed to create contact" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/whatsapp/contacts/search", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const query = req.query.q as string || "";
|
|
|
|
if (!query) {
|
|
const contacts = await whatsappService.getContacts(userId);
|
|
return res.json(contacts);
|
|
}
|
|
|
|
const contacts = await whatsappService.searchContacts(userId, query);
|
|
res.json(contacts);
|
|
} catch (error: any) {
|
|
console.error("WhatsApp search contacts error:", error);
|
|
res.status(500).json({ error: error.message || "Failed to search contacts" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/whatsapp/tickets/:id/transfer", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const ticketId = parseInt(req.params.id);
|
|
const { targetUserId, note } = req.body;
|
|
|
|
if (!targetUserId) {
|
|
return res.status(400).json({ error: "targetUserId is required" });
|
|
}
|
|
|
|
await whatsappService.transferTicket(userId, ticketId, targetUserId, note);
|
|
res.json({ success: true });
|
|
} catch (error: any) {
|
|
console.error("WhatsApp transfer ticket error:", error);
|
|
res.status(500).json({ error: error.message || "Failed to transfer ticket" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/whatsapp/tickets/:id/forward-to-chat", async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const ticketId = parseInt(req.params.id);
|
|
const { targetUserIds, contextMessage } = req.body;
|
|
|
|
if (!targetUserIds || !Array.isArray(targetUserIds) || targetUserIds.length === 0) {
|
|
return res.status(400).json({ error: "targetUserIds array is required" });
|
|
}
|
|
|
|
const result = await whatsappService.forwardTicketToChat(userId, ticketId, targetUserIds, contextMessage);
|
|
res.json({ success: true, threadId: result.threadId });
|
|
} catch (error: any) {
|
|
console.error("WhatsApp forward to chat error:", error);
|
|
res.status(500).json({ error: error.message || "Failed to forward to chat" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/whatsapp/tickets/:id/send-media", upload.single("file"), async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.isAuthenticated()) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
|
|
const userId = req.user!.id;
|
|
const ticketId = parseInt(req.params.id);
|
|
const caption = req.body.caption as string | undefined;
|
|
|
|
if (!req.file) {
|
|
return res.status(400).json({ error: "File is required" });
|
|
}
|
|
|
|
const filePath = req.file.path;
|
|
const fileName = req.file.originalname;
|
|
const mimeType = req.file.mimetype;
|
|
|
|
await whatsappService.sendMedia(userId, ticketId, filePath, fileName, mimeType, caption);
|
|
|
|
fs.unlinkSync(filePath);
|
|
|
|
res.json({ success: true });
|
|
} catch (error: any) {
|
|
console.error("WhatsApp send media error:", error);
|
|
if (req.file && fs.existsSync(req.file.path)) {
|
|
fs.unlinkSync(req.file.path);
|
|
}
|
|
res.status(500).json({ error: error.message || "Failed to send media" });
|
|
}
|
|
});
|
|
}
|