csa-backend-test/app/controllers/messages.controller.js

455 lines
16 KiB
JavaScript

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();