521 lines
16 KiB
JavaScript
521 lines
16 KiB
JavaScript
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" });
|
|
}
|
|
};
|