455 lines
16 KiB
JavaScript
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(); |