import Chat from "../models/Chat.model.js"; import Store from "../models/Store.model.js"; import Vendor from "../models/Vendor.model.js"; import { encryptWithPGP } from "../utils/encryptPgp.js"; import logger from "../utils/logger.js"; import { sendTelegramMessage } from "../utils/telegramUtils.js"; import axios from "axios"; // Get all chats for a vendor export const getVendorChats = async (req, res) => { try { const { vendorId } = req.params; // Check if vendor exists and requester has access if (req.user._id.toString() !== vendorId) { return res.status(403).json({ error: "Not authorized to access these chats" }); } const chats = await Chat.find({ vendorId }) .sort({ lastUpdated: -1 }) .select("-messages") // Don't include messages in the list view .lean(); return res.status(200).json(chats); } catch (error) { logger.error("Error getting vendor chats", { error: error.message, stack: error.stack }); return res.status(500).json({ error: "Server error getting chats" }); } }; // Get all messages in a specific chat export const getChatMessages = async (req, res) => { try { const { chatId } = req.params; const { markAsRead = "true" } = req.query; // Default to true for backward compatibility const shouldMarkAsRead = markAsRead === "true"; const chat = await Chat.findById(chatId).lean(); if (!chat) { return res.status(404).json({ error: "Chat not found" }); } // Check if user has access to this chat if (req.user && chat.vendorId.toString() !== req.user._id.toString()) { return res.status(403).json({ error: "Not authorized to access this chat" }); } // Only mark messages as read if markAsRead parameter is true if (shouldMarkAsRead) { // Mark all vendor messages as read if the request is from the buyer if (req.telegramUser && chat.buyerId === req.telegramUser.telegramId) { await Chat.updateMany( { _id: chatId, "messages.sender": "vendor", "messages.read": false }, { $set: { "messages.$[elem].read": true } }, { arrayFilters: [{ "elem.sender": "vendor", "elem.read": false }] } ); } // Mark all buyer messages as read if the request is from the vendor if (req.user && chat.vendorId.toString() === req.user._id.toString()) { await Chat.updateMany( { _id: chatId, "messages.sender": "buyer", "messages.read": false }, { $set: { "messages.$[elem].read": true } }, { arrayFilters: [{ "elem.sender": "buyer", "elem.read": false }] } ); } } return res.status(200).json(chat); } catch (error) { logger.error("Error getting chat messages", { error: error.message, stack: error.stack }); return res.status(500).json({ error: "Server error getting chat messages" }); } }; // Explicitly mark messages as read export const markMessagesAsRead = async (req, res) => { try { const { chatId } = req.params; const chat = await Chat.findById(chatId); if (!chat) { return res.status(404).json({ error: "Chat not found" }); } // Check if user has access to this chat if (req.user && chat.vendorId.toString() !== req.user._id.toString()) { return res.status(403).json({ error: "Not authorized to access this chat" }); } // Mark all buyer messages as read if the request is from the vendor if (req.user && chat.vendorId.toString() === req.user._id.toString()) { await Chat.updateMany( { _id: chatId, "messages.sender": "buyer", "messages.read": false }, { $set: { "messages.$[elem].read": true } }, { arrayFilters: [{ "elem.sender": "buyer", "elem.read": false }] } ); logger.info("Marked all buyer messages as read", { chatId }); } return res.status(200).json({ success: true }); } catch (error) { logger.error("Error marking messages as read", { error: error.message, stack: error.stack }); return res.status(500).json({ error: "Server error marking messages as read" }); } }; // Send a message from vendor to buyer export const sendVendorMessage = async (req, res) => { try { const { chatId } = req.params; const { content, attachments } = req.body; if (!content && (!attachments || attachments.length === 0)) { return res.status(400).json({ error: "Message content or attachments required" }); } const chat = await Chat.findById(chatId); if (!chat) { return res.status(404).json({ error: "Chat not found" }); } // Check vendor authorization if (chat.vendorId.toString() !== req.user._id.toString()) { return res.status(403).json({ error: "Not authorized to send messages in this chat" }); } // Create the new message const newMessage = { sender: "vendor", buyerId: chat.buyerId, vendorId: chat.vendorId, content: content || "", attachments: attachments || [], read: false, createdAt: new Date() }; // Add message to chat chat.messages.push(newMessage); chat.lastUpdated = new Date(); await chat.save(); // Send notification to Telegram client if configured try { const store = await Store.findById(chat.storeId); if (!store) { logger.warn("Store not found for chat notification", { chatId, storeId: chat.storeId }); } else { // Get store name for the notification const storeName = store.storeName || "Vendor"; // Check if store has telegram token if (store.telegramToken) { logger.info("Sending Telegram notification", { buyerId: chat.buyerId, storeId: store._id }); // Format the message with emoji and preview const notificationMessage = `📬 New message from ${storeName}: ${content.substring(0, 50)}${content.length > 50 ? '...' : ''}`; // Use the sendTelegramMessage utility instead of axios const success = await sendTelegramMessage( store.telegramToken, chat.buyerId, notificationMessage ); if (success) { logger.info("Telegram notification sent successfully", { chatId, buyerId: chat.buyerId }); } else { logger.error("Failed to send Telegram notification", { chatId, buyerId: chat.buyerId }); } } else { logger.warn("Store missing telegramToken", { storeId: store._id }); } } } catch (notifyError) { // Log but don't fail if notification fails logger.error("Notification system error", { error: notifyError.message, stack: notifyError.stack, chatId }); } return res.status(201).json(newMessage); } catch (error) { logger.error("Error sending vendor message", { error: error.message, stack: error.stack }); return res.status(500).json({ error: "Server error sending message" }); } }; // Process an incoming message from the Telegram client export const processTelegramMessage = async (req, res) => { try { const { buyerId, storeId, content, attachments } = req.body; if (!buyerId || !storeId) { return res.status(400).json({ error: "Buyer ID and Store ID are required" }); } if (!content && (!attachments || attachments.length === 0)) { return res.status(400).json({ error: "Message content or attachments required" }); } // Find the store const store = await Store.findById(storeId); if (!store) { logger.error("Store not found for Telegram message", { storeId }); return res.status(404).json({ error: "Store not found" }); } // Check if vendorId field exists in store if (!store.vendorId) { logger.error("Store missing vendorId field", { storeId, store }); return res.status(500).json({ error: "Store data is invalid (missing vendorId)" }); } // Find or create a chat let chat = await Chat.findOne({ buyerId, storeId }); if (!chat) { // Create a new chat chat = new Chat({ buyerId, vendorId: store.vendorId, storeId, messages: [], }); } // Create the new message const newMessage = { sender: "buyer", buyerId, vendorId: store.vendorId, content: content || "", attachments: attachments || [], read: false, createdAt: new Date() }; // Add message to chat chat.messages.push(newMessage); chat.lastUpdated = new Date(); await chat.save(); return res.status(201).json({ success: true, chatId: chat._id, message: newMessage }); } catch (error) { logger.error("Error processing Telegram message", { error: error.message, stack: error.stack }); return res.status(500).json({ error: "Server error processing message" }); } }; // Get unread message counts for a vendor export const getVendorUnreadCounts = async (req, res) => { try { const { vendorId } = req.params; // Check if vendor is authorized if (req.user._id.toString() !== vendorId) { return res.status(403).json({ error: "Not authorized" }); } // Find all chats for this vendor const chats = await Chat.find({ vendorId }); // Calculate unread counts const result = { totalUnread: 0, chatCounts: {} }; chats.forEach(chat => { const unreadCount = chat.messages.filter( msg => msg.sender === "buyer" && !msg.read ).length; if (unreadCount > 0) { result.totalUnread += unreadCount; result.chatCounts[chat._id] = unreadCount; } }); return res.status(200).json(result); } catch (error) { logger.error("Error getting unread counts", { error: error.message, stack: error.stack }); return res.status(500).json({ error: "Server error getting unread counts" }); } }; // Create a new chat directly (for customer service purposes) export const createChat = async (req, res) => { try { const { buyerId, storeId, initialMessage } = req.body; if (!buyerId || !storeId) { return res.status(400).json({ error: "Buyer ID and Store ID are required" }); } // Find the store and vendor const store = await Store.findById(storeId); console.log("Store data:", JSON.stringify(store, null, 2)); console.log("Authenticated user:", JSON.stringify(req.user, null, 2)); if (!store) { return res.status(404).json({ error: "Store not found" }); } // Check if vendorId field exists in store if (!store.vendorId) { logger.error("Store missing vendorId field", { storeId, store }); return res.status(500).json({ error: "Store data is invalid (missing vendorId)" }); } // Check if requester is authorized for this store if (req.user._id.toString() !== store.vendorId.toString()) { return res.status(403).json({ error: "Not authorized to create chats for this store" }); } // Check if chat already exists const existingChat = await Chat.findOne({ buyerId, storeId }); if (existingChat) { return res.status(409).json({ error: "Chat already exists", chatId: existingChat._id }); } // Create a new chat const chat = new Chat({ buyerId, vendorId: store.vendorId, storeId, messages: [], }); // Add initial message if provided if (initialMessage) { chat.messages.push({ sender: "vendor", buyerId, vendorId: store.vendorId, content: initialMessage, read: false, createdAt: new Date() }); } chat.lastUpdated = new Date(); await chat.save(); // Send notification to Telegram if configured try { if (initialMessage) { // Get store name for the notification const storeName = store.storeName || "Vendor"; // Check if store has telegram token if (store.telegramToken) { logger.info("Sending Telegram notification for new chat", { buyerId, storeId: store._id }); // Format the message with emoji and preview const notificationMessage = `📬 New chat created by ${storeName}: ${initialMessage.substring(0, 50)}${initialMessage.length > 50 ? '...' : ''}`; // Use the sendTelegramMessage utility instead of axios const success = await sendTelegramMessage( store.telegramToken, buyerId, notificationMessage ); if (success) { logger.info("Telegram notification for new chat sent successfully", { chatId: chat._id, buyerId }); } else { logger.error("Failed to send Telegram notification for new chat", { chatId: chat._id, buyerId }); } } else { logger.warn("Store missing telegramToken for new chat notification", { storeId: store._id }); } } } catch (notifyError) { logger.error("Notification system error for new chat", { error: notifyError.message, stack: notifyError.stack, chatId: chat._id, buyerId, storeId }); } return res.status(201).json({ success: true, chatId: chat._id }); } catch (error) { logger.error("Error creating chat", { error: error.message, stack: error.stack }); return res.status(500).json({ error: "Server error creating chat" }); } }; // Create a chat from Telegram client export const createTelegramChat = async (req, res) => { try { const { buyerId, storeId, initialMessage } = req.body; if (!buyerId || !storeId) { return res.status(400).json({ error: "Buyer ID and Store ID are required" }); } // Find the store const store = await Store.findById(storeId); if (!store) { logger.error("Store not found for Telegram chat creation", { storeId }); return res.status(404).json({ error: "Store not found" }); } // Check if vendorId field exists in store if (!store.vendorId) { logger.error("Store missing vendorId field", { storeId, store }); return res.status(500).json({ error: "Store data is invalid (missing vendorId)" }); } // Check if chat already exists const existingChat = await Chat.findOne({ buyerId, storeId }); // If chat exists, just return it if (existingChat) { logger.info("Chat already exists, returning existing chat", { chatId: existingChat._id, buyerId, storeId }); // Add initial message to existing chat if provided if (initialMessage) { existingChat.messages.push({ sender: "buyer", buyerId, vendorId: store.vendorId, content: initialMessage, read: false, createdAt: new Date() }); existingChat.lastUpdated = new Date(); await existingChat.save(); // Notify vendor about the new message // This would typically be done through a notification system logger.info("Added message to existing chat", { chatId: existingChat._id, buyerId, message: initialMessage.substring(0, 50) }); } return res.status(200).json({ message: "Using existing chat", chatId: existingChat._id }); } // Create a new chat const chat = new Chat({ buyerId, vendorId: store.vendorId, storeId, messages: [], }); // Add initial message if provided if (initialMessage) { chat.messages.push({ sender: "buyer", buyerId, vendorId: store.vendorId, content: initialMessage, read: false, createdAt: new Date() }); } chat.lastUpdated = new Date(); await chat.save(); logger.info("New chat created from Telegram", { chatId: chat._id, buyerId, storeId }); return res.status(201).json({ message: "Chat created successfully", chatId: chat._id }); } catch (error) { logger.error("Error creating chat from Telegram", { error: error.message, stack: error.stack, buyerId: req.body.buyerId, storeId: req.body.storeId }); return res.status(500).json({ error: "Server error creating chat" }); } };