const { PrismaClient: MSGClient } = require("../../prisma/clients/msg"); const logger = require("../services/logger.services.js"); const { successResponse, errorResponse, notFoundResponse, badRequestResponse } = require("../res/responses.js"); const fileService = require("../services/file.services.js"); const msgPrisma = new MSGClient(); class MessagesController { async getConversations(req, res) { try { const { userId } = req.query; const { userType = 'user' } = req.query; const { page = 1, limit = 20 } = req.query; const skip = (parseInt(page) - 1) * parseInt(limit); let whereCondition; if (userType === 'admin') { whereCondition = { adminId: userId }; } else { whereCondition = { userId: userId }; } const conversations = await msgPrisma.conversations.findMany({ where: whereCondition, include: { messages: { orderBy: { createdAt: 'desc' }, take: 1, select: { content: true, senderType: true, senderName: true, createdAt: true, messageType: true } }, _count: { select: { messages: true } } }, orderBy: { lastMessageAt: 'desc' }, skip: skip, take: parseInt(limit) }); const totalCount = await msgPrisma.conversations.count({ where: whereCondition }); const formattedConversations = conversations.map(conv => ({ id: conv.id, subject: conv.subject, status: conv.status, priority: conv.priority, userId: conv.userId, adminId: conv.adminId, createdAt: conv.createdAt, updatedAt: conv.updatedAt, lastMessageAt: conv.lastMessageAt, messageCount: conv._count.messages, lastMessage: conv.messages[0] || null })); successResponse(res, 'Conversations retrieved successfully', { conversations: formattedConversations, pagination: { page: parseInt(page), limit: parseInt(limit), total: totalCount, pages: Math.ceil(totalCount / parseInt(limit)) } }); } catch (error) { logger.error('Error fetching conversations:', error); errorResponse(res, 'Failed to fetch conversations'); } } async getMessages(req, res) { try { const { conversationId } = req.params; const { limit = 50, offset = 0 } = req.query; const conversation = await msgPrisma.conversations.findUnique({ where: { id: conversationId } }); if (!conversation) { return notFoundResponse(res, 'Conversation not found'); } const messages = await msgPrisma.messages.findMany({ where: { conversationId: conversationId }, orderBy: { createdAt: 'asc' }, skip: parseInt(offset), take: parseInt(limit), }); const totalCount = await msgPrisma.messages.count({ where: { conversationId: conversationId } }); const messageIds = messages.map(m => m.id); let attachmentsByMessage = {}; if (messageIds.length > 0) { const attachments = await msgPrisma.messageAttachments.findMany({ where: { messageId: { in: messageIds } } }); attachmentsByMessage = attachments.reduce((acc, att) => { if (!acc[att.messageId]) acc[att.messageId] = []; acc[att.messageId].push({ id: att.id, fileName: att.fileName, fileSize: att.fileSize, mimeType: att.mimeType, fileUrl: att.fileUrl, createdAt: att.createdAt }); return acc; }, {}); } const formattedMessages = messages.map(msg => ({ id: msg.id, conversationId: msg.conversationId, content: msg.content, messageType: msg.messageType, senderId: msg.senderId, senderType: msg.senderType, senderName: msg.senderName, status: msg.status, readAt: msg.readAt, deliveredAt: msg.deliveredAt, createdAt: msg.createdAt, updatedAt: msg.updatedAt, metadata: msg.metadata, attachments: attachmentsByMessage[msg.id] || [] })); successResponse(res, 'Messages retrieved successfully', { messages: formattedMessages, pagination: { limit: parseInt(limit), offset: parseInt(offset), total: totalCount, hasMore: parseInt(offset) + parseInt(limit) < totalCount } }); } catch (error) { logger.error('Error fetching messages:', error); errorResponse(res, 'Failed to fetch messages'); } } async createConversation(req, res) { try { const { userId, adminId, subject, initialMessage, priority = 'normal' } = req.body; if (!userId) { return badRequestResponse(res, 'userId is required'); } const conversation = await msgPrisma.conversations.create({ data: { userId, adminId, subject: subject || 'New Conversation', priority, status: 'active' } }); let initialMessageData = null; if (initialMessage) { const message = await msgPrisma.messages.create({ data: { conversationId: conversation.id, content: initialMessage.content || initialMessage, messageType: initialMessage.messageType || 'text', senderId: initialMessage.senderId || userId, senderType: initialMessage.senderType || 'user', senderName: initialMessage.senderName, status: 'sent' } }); initialMessageData = { id: message.id, content: message.content, senderId: message.senderId, senderType: message.senderType, createdAt: message.createdAt }; await msgPrisma.conversations.update({ where: { id: conversation.id }, data: { lastMessageAt: new Date() } }); } successResponse(res, 'Conversation created successfully', { ...conversation, initialMessage: initialMessageData }); } catch (error) { logger.error('Error creating conversation:', error); errorResponse(res, 'Failed to create conversation'); } } async sendMessage(req, res) { try { const { conversationId, message, senderId, senderType, messageType = 'text', senderName, metadata } = req.body; if (!conversationId || !message || !senderId || !senderType) { return badRequestResponse(res, 'conversationId, message, senderId, and senderType are required'); } const conversation = await msgPrisma.conversations.findUnique({ where: { id: conversationId } }); if (!conversation) { return notFoundResponse(res, 'Conversation not found'); } const savedMessage = await msgPrisma.messages.create({ data: { conversationId, content: message, messageType, senderId, senderType, senderName, status: 'sent', metadata: metadata || {} } }); let attachments = []; if (req.files && req.files.length > 0) { const bucketName = 'cifosuperapps'; const folderName = 'messages/attachments'; for (const file of req.files) { try { const [fileUrl, fileName] = await fileService.upload(bucketName, folderName, file.mimetype, [file]); const attachment = await msgPrisma.messageAttachments.create({ data: { messageId: savedMessage.id, fileName: file.originalname, fileSize: file.size, mimeType: file.mimetype, fileUrl: fileUrl, metadata: { originalName: file.originalname, uploadedName: fileName } } }); attachments.push({ id: attachment.id, fileName: attachment.fileName, fileSize: attachment.fileSize, mimeType: attachment.mimeType, fileUrl: attachment.fileUrl, createdAt: attachment.createdAt }); } catch (uploadError) { logger.error('Error uploading attachment:', uploadError); } } } await msgPrisma.conversations.update({ where: { id: conversationId }, data: { lastMessageAt: new Date() } }); const responseMessage = { id: savedMessage.id, conversationId: savedMessage.conversationId, content: savedMessage.content, messageType: savedMessage.messageType, senderId: savedMessage.senderId, senderType: savedMessage.senderType, senderName: savedMessage.senderName, status: savedMessage.status, createdAt: savedMessage.createdAt, updatedAt: savedMessage.updatedAt, metadata: savedMessage.metadata, attachments: attachments }; successResponse(res, 'Message sent successfully', responseMessage); } catch (error) { logger.error('Error sending message:', error); errorResponse(res, 'Failed to send message'); } } async markMessagesAsRead(req, res) { try { const { conversationId } = req.params; const { userId, userType } = req.body; if (!userId || !userType) { return badRequestResponse(res, 'userId and userType are required'); } const conversation = await msgPrisma.conversations.findUnique({ where: { id: conversationId } }); if (!conversation) { return notFoundResponse(res, 'Conversation not found'); } const result = await msgPrisma.messages.updateMany({ where: { conversationId: conversationId, senderId: { not: userId }, readAt: null }, data: { readAt: new Date(), status: 'read' } }); successResponse(res, `${result.count} messages marked as read`, { conversationId, messagesMarkedAsRead: result.count }); } catch (error) { logger.error('Error marking messages as read:', error); errorResponse(res, 'Failed to mark messages as read'); } } async deleteAttachment(req, res) { try { const { attachmentId } = req.params; if (!attachmentId) { return badRequestResponse(res, 'attachmentId is required'); } const attachment = await msgPrisma.messageAttachments.findUnique({ where: { id: attachmentId } }); if (!attachment) { return notFoundResponse(res, 'Attachment not found'); } // Delete file from storage try { const bucketName = 'messages'; const folderName = 'attachments'; const fileName = attachment.metadata?.uploadedName || attachment.fileName; await fileService.delete(bucketName, folderName, fileName); } catch (fileError) { logger.error('Error deleting file from storage:', fileError); // Continue with database deletion even if file deletion fails } // Delete attachment record from database await msgPrisma.messageAttachments.delete({ where: { id: attachmentId } }); successResponse(res, 'Attachment deleted successfully'); } catch (error) { logger.error('Error deleting attachment:', error); errorResponse(res, 'Failed to delete attachment'); } } async getStats(req, res) { try { const { userId } = req.params; const { userType = 'user' } = req.query; let whereCondition; if (userType === 'admin') { whereCondition = { adminId: userId }; } else { whereCondition = { userId: userId }; } const totalConversations = await msgPrisma.conversations.count({ where: whereCondition }); const activeConversations = await msgPrisma.conversations.count({ where: { ...whereCondition, status: 'active' } }); let conversationIds = []; if (userType === 'admin') { const conversations = await msgPrisma.conversations.findMany({ where: { adminId: userId }, select: { id: true } }); conversationIds = conversations.map(c => c.id); } else { const conversations = await msgPrisma.conversations.findMany({ where: { userId: userId }, select: { id: true } }); conversationIds = conversations.map(c => c.id); } let totalMessages = 0; let unreadMessages = 0; if (conversationIds.length > 0) { totalMessages = await msgPrisma.messages.count({ where: { conversationId: { in: conversationIds } } }); unreadMessages = await msgPrisma.messages.count({ where: { conversationId: { in: conversationIds }, senderId: { not: userId }, readAt: null } }); } const stats = { totalConversations, activeConversations, unreadMessages, totalMessages }; successResponse(res, 'Statistics retrieved successfully', stats); } catch (error) { logger.error('Error fetching stats:', error); errorResponse(res, 'Failed to fetch statistics'); } } } module.exports = new MessagesController();