UNPKG

powr-sdk-api

Version:

Shared API core library for PowrStack projects. Zero dependencies - works with Express, Next.js API routes, and other frameworks. All features are optional and install only what you need.

463 lines (438 loc) 13.3 kB
"use strict"; const express = require('express'); const router = express.Router(); const { ObjectId } = require('mongodb'); const { getDb } = require('../services/mongo'); // Get all conversations for a user router.get('/conversations', async (req, res) => { try { const userId = req.user.powrId; const userAccess = req.user.access; const projectId = req.projectId; console.log('Current user ID:', userId, 'Access:', userAccess, 'Project:', projectId); const db = await getDb(); // Show all conversations user is in (1:1 DMs and groups, any project) const conversations = await db.collection('conversations').find({ participants: userId }).toArray(); // Build response for each conversation const conversationsWithUsers = await Promise.all(conversations.map(async conv => { const isGroup = conv.type === 'group'; // Get last message const lastMessage = await db.collection('messages').findOne({ conversationId: conv._id.toString() }, { sort: { createdAt: -1 } }); // Get unread count const unreadCount = await db.collection('messages').countDocuments({ conversationId: conv._id.toString(), senderId: { $ne: userId }, read: false }); let displayName, avatar; if (isGroup) { displayName = conv.name || 'Group'; avatar = null; // Groups typically use icon, not user avatar } else { const otherUserId = conv.participants.find(p => String(p) !== String(userId)); const otherUser = otherUserId ? await db.collection('users').findOne({ _id: new ObjectId(otherUserId) }, { projection: { fullName: 1, avatar: 1 } }) : null; displayName = (otherUser === null || otherUser === void 0 ? void 0 : otherUser.fullName) || 'Unknown User'; avatar = otherUser === null || otherUser === void 0 ? void 0 : otherUser.avatar; } return { id: conv._id.toString(), type: isGroup ? 'group' : 'dm', name: displayName, avatar, lastMessage: (lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.content) || '', lastMessageTime: (lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.createdAt) || conv.createdAt, unreadCount, isOnline: false }; })); return res.json({ success: true, data: conversationsWithUsers }); } catch (error) { console.error('Error fetching conversations:', error); return res.status(500).json({ success: false, message: "Failed to fetch conversations." }); } }); // Get messages for a conversation router.get('/conversations/:conversationId/messages', async (req, res) => { try { const { conversationId } = req.params; const userId = req.user.powrId; const projectId = req.projectId; const db = await getDb(); // Verify user is part of conversation const conversation = await db.collection('conversations').findOne({ _id: new ObjectId(conversationId), participants: userId }); if (!conversation) { return res.status(404).json({ success: false, message: 'Conversation not found' }); } const messages = await db.collection('messages').find({ conversationId: conversationId }).sort({ createdAt: 1 }).toArray(); // Mark messages as read await db.collection('messages').updateMany({ conversationId: conversationId, senderId: { $ne: userId }, read: false }, { $set: { read: true } }); const messagesWithOwnership = messages.map(msg => ({ id: msg._id.toString(), sender: msg.senderName, message: msg.content, timestamp: msg.createdAt, isOwn: msg.senderId === userId })); return res.json({ success: true, data: messagesWithOwnership }); } catch (error) { console.error('Error fetching messages:', error); return res.status(500).json({ success: false, message: "Failed to fetch messages." }); } }); // Send a message router.post('/conversations/:conversationId/messages', async (req, res) => { try { const { conversationId } = req.params; const { content, projectId: bodyProjectId } = req.body; const userId = req.user.powrId; const projectId = bodyProjectId || req.projectId; if (!content || content.trim() === '') { return res.status(400).json({ success: false, message: "Message content is required" }); } const db = await getDb(); // Verify user is part of conversation const conversation = await db.collection('conversations').findOne({ _id: new ObjectId(conversationId), participants: userId }); if (!conversation) { return res.status(404).json({ success: false, message: "Conversation not found" }); } // Get current user's name from database const currentUser = await db.collection('users').findOne({ _id: new ObjectId(userId) }, { projection: { fullName: 1 } }); const message = { conversationId: conversationId, senderId: userId, senderName: (currentUser === null || currentUser === void 0 ? void 0 : currentUser.fullName) || 'Unknown User', content: content.trim(), createdAt: new Date(), read: false, projectId: projectId }; const result = await db.collection('messages').insertOne(message); // Update conversation last message await db.collection('conversations').updateOne({ _id: new ObjectId(conversationId) }, { $set: { updatedAt: new Date() } }); const responseMessage = { id: result.insertedId.toString(), sender: message.senderName, message: message.content, timestamp: message.createdAt, isOwn: true }; return res.status(201).json({ success: true, data: responseMessage }); } catch (error) { console.error('Error sending message:', error); return res.status(500).json({ success: false, message: "Failed to send message." }); } }); // Create a new conversation (DM or group) router.post('/conversations', async (req, res) => { try { const { participantId, participantIds, type, name, projectId: bodyProjectId } = req.body; const userId = req.user.powrId; const userAccess = req.user.access; const projectId = bodyProjectId || req.projectId; const db = await getDb(); // --- GROUP --- if (type === 'group') { if (!participantIds || !Array.isArray(participantIds) || participantIds.length === 0) { return res.status(400).json({ success: false, message: "participantIds (array) is required for group" }); } if (!name || typeof name !== 'string' || !name.trim()) { return res.status(400).json({ success: false, message: "name is required for group" }); } // Normalize participant IDs (ensure strings, dedupe, include creator) const normalizedIds = [...new Set([userId, ...participantIds.map(id => String(id))])]; // Check if group with same projectId + name already exists const existingGroup = await db.collection('conversations').findOne({ type: 'group', projectId: projectId, name: name.trim() }); if (existingGroup) { // Return existing if creator is a participant const isParticipant = existingGroup.participants.some(p => String(p) === String(userId)); if (isParticipant) { return res.json({ success: true, data: { id: existingGroup._id.toString(), message: 'Group already exists' } }); } } const conversation = { type: 'group', participants: normalizedIds, name: name.trim(), projectId: projectId, createdBy: userId, createdAt: new Date(), updatedAt: new Date() }; const result = await db.collection('conversations').insertOne(conversation); return res.status(201).json({ success: true, data: { id: result.insertedId.toString(), message: 'Group created successfully' } }); } // --- DM (default) --- if (!participantId) { return res.status(400).json({ success: false, message: "Participant ID is required for DM (or use type: 'group' with participantIds)" }); } // Check if user has permission to chat with this participant const participant = await db.collection('users').findOne({ _id: new ObjectId(participantId) }, { projection: { access: 1 } }); if (!participant) { return res.status(404).json({ success: false, message: "Participant not found" }); } // Validate access permissions let hasPermission = false; if (userAccess === 100) { hasPermission = true; } else if (userAccess === 900) { hasPermission = participant.access === 100; } else { hasPermission = participant.access === 100 || participant.access === null; } if (!hasPermission) { return res.status(403).json({ success: false, message: "You do not have permission to chat with this user" }); } // Check if conversation already exists const existingConversation = await db.collection('conversations').findOne({ type: { $ne: 'group' }, participants: { $all: [userId, participantId] }, projectId: projectId }); if (existingConversation) { return res.json({ success: true, data: { id: existingConversation._id.toString(), message: 'Conversation already exists' } }); } const conversation = { type: 'dm', participants: [userId, participantId], projectId: projectId, createdAt: new Date(), updatedAt: new Date() }; const result = await db.collection('conversations').insertOne(conversation); return res.status(201).json({ success: true, data: { id: result.insertedId.toString(), message: 'Conversation created successfully' } }); } catch (error) { console.error('Error creating conversation:', error); return res.status(500).json({ success: false, message: "Failed to create conversation." }); } }); // Get users for starting new conversations router.get('/users', async (req, res) => { try { const userId = req.user.powrId; const userAccess = req.user.access; const projectId = req.projectId; console.log('Current user access level:', userAccess, 'Project:', projectId); const db = await getDb(); // Get users from the current project const projectUsers = await db.collection('profiles').find({ projectId: projectId }).toArray(); console.log('Project users found:', projectUsers.length, 'for projectId:', projectId); const userIds = projectUsers.map(profile => profile.userId); console.log('User IDs extracted:', userIds); // If no users found for this project, return empty array if (userIds.length === 0) { console.log('No users found for project:', projectId); return res.json({ success: true, data: [] }); } let userQuery = { _id: { $in: userIds.map(id => new ObjectId(id)) }, _id: { $ne: new ObjectId(userId) } }; // Apply role-based filtering if (userAccess === 100) { // Admin can chat with everyone console.log('Admin access - can chat with everyone'); } else if (userAccess === 900) { // Client can only chat with admins userQuery.access = 100; console.log('Client access - can only chat with admins'); } else { // Employee (access null) can chat with employees and admins userQuery.$or = [{ access: 100 }, // Admins { access: null } // Employees ]; console.log('Employee access - can chat with employees and admins'); } // Get filtered users const users = await db.collection('users').find(userQuery, { projection: { fullName: 1, username: 1, avatar: 1, _id: 1, access: 1 } }).toArray(); const formattedUsers = users.map(user => ({ id: user._id.toString(), name: user.fullName || 'Unknown User', username: user.username, avatar: user.avatar, access: user.access })); console.log('Available users for chat:', formattedUsers); return res.json({ success: true, data: formattedUsers }); } catch (error) { console.error('Error fetching users:', error); return res.status(500).json({ success: false, message: "Failed to fetch users." }); } }); module.exports = router;