import express from "express"; import pool from "../db/pool.js"; import { getIO, getSocketId } from "../socket.js"; const router = express.Router(); // 1. POST /conversations (Create or Get existing) router.post("/conversations", async (req, res) => { try { // We expect explicit buyer/seller ID, or just two user IDs. // Based on schema, we have buyer_id and seller_id. // Ideally, the client sends which is which, or we just treat them as user1/user2 and check both permutations. const { buyer_id, seller_id } = req.body; if (!buyer_id || !seller_id) { return res.status(400).json({ error: "buyer_id and seller_id are required" }); } // Check if conversation exists (bidirectional check for robustness, although schema has specific columns) const queryCheck = ` SELECT * FROM conversations WHERE (buyer_id = $1 AND seller_id = $2) OR (buyer_id = $2 AND seller_id = $1) AND deleted = FALSE `; const checkResult = await pool.query(queryCheck, [buyer_id, seller_id]); if (checkResult.rows.length > 0) { return res.json(checkResult.rows[0]); } // Create new const queryInsert = ` INSERT INTO conversations (buyer_id, seller_id) VALUES ($1, $2) RETURNING * `; const insertResult = await pool.query(queryInsert, [buyer_id, seller_id]); res.status(201).json(insertResult.rows[0]); } catch (err) { console.error("Error creating/getting conversation:", err); res.status(500).json({ error: "Internal server error" }); } }); // 2. GET /conversations/user/:userId (Inbox) router.get("/conversations/user/:userId", async (req, res) => { try { const { userId } = req.params; // Fetch conversations where user is involved. // Also fetch the OTHER user's name/avatar. const queryText = ` SELECT c.*, CASE WHEN c.buyer_id = $1 THEN u_seller.name ELSE u_buyer.name END as other_user_name, CASE WHEN c.buyer_id = $1 THEN u_seller.avatar_url ELSE u_buyer.avatar_url END as other_user_avatar, CASE WHEN c.buyer_id = $1 THEN u_seller.id ELSE u_buyer.id END as other_user_id FROM conversations c JOIN users u_buyer ON c.buyer_id = u_buyer.id JOIN users u_seller ON c.seller_id = u_seller.id WHERE (c.buyer_id = $1 OR c.seller_id = $1) AND c.deleted = FALSE ORDER BY c.updated_at DESC `; const result = await pool.query(queryText, [userId]); res.json(result.rows); } catch (err) { console.error("Error fetching user conversations:", err); res.status(500).json({ error: "Internal server error" }); } }); // 3. GET /conversations/:conversationId/messages (History) router.get("/conversations/:conversationId/messages", async (req, res) => { try { const { conversationId } = req.params; const { limit = 50, offset = 0 } = req.query; const queryText = ` SELECT m.*, cm.media_url, cm.media_type as media_file_type, cm.thumbnail_url FROM messages m LEFT JOIN conversation_media cm ON m.media_id = cm.id WHERE m.conversation_id = $1 AND m.deleted = FALSE ORDER BY m.created_at DESC LIMIT $2 OFFSET $3 `; const result = await pool.query(queryText, [conversationId, limit, offset]); // Reverse for frontend if needed, but API usually sends standard order. // Sending newest first (DESC) is common for pagination. res.json(result.rows); } catch (err) { console.error("Error fetching messages:", err); res.status(500).json({ error: "Internal server error" }); } }); // 4. POST /messages (Send Message) router.post("/messages", async (req, res) => { const client = await pool.connect(); try { await client.query("BEGIN"); const { conversation_id, sender_id, receiver_id, content, message_type = 'text', media } = req.body; let media_id = null; // Insert Media if present and message type allows it if ((message_type === 'media' || message_type === 'both') && media) { const insertMediaQuery = ` INSERT INTO conversation_media (media_type, media_url, thumbnail_url) VALUES ($1, $2, $3) RETURNING id `; const mediaResult = await client.query(insertMediaQuery, [media.media_type, media.media_url, media.thumbnail_url]); media_id = mediaResult.rows[0].id; } // Insert Message const insertMessageQuery = ` INSERT INTO messages (conversation_id, sender_id, receiver_id, message_type, content, media_id) VALUES ($1, $2, $3, $4, $5, $6) RETURNING * `; const messageResult = await client.query(insertMessageQuery, [conversation_id, sender_id, receiver_id, message_type, content, media_id]); // Update conversation timestamp const updateConvQuery = `UPDATE conversations SET updated_at = NOW() WHERE id = $1`; await client.query(updateConvQuery, [conversation_id]); await client.query("COMMIT"); // Real-time update via Socket.io const receiverSocketId = getSocketId(receiver_id); if (receiverSocketId) { getIO().to(receiverSocketId).emit("receive_message", messageResult.rows[0]); } res.status(201).json(messageResult.rows[0]); } catch (err) { await client.query("ROLLBACK"); console.error("Error sending message:", err); res.status(500).json({ error: "Internal server error" }); } finally { client.release(); } }); // 5. PUT /messages/:messageId/read (Mark Read) router.put("/messages/:messageId/read", async (req, res) => { try { const { messageId } = req.params; const queryText = ` UPDATE messages SET is_read = TRUE, read_at = NOW() WHERE id = $1 RETURNING * `; const result = await pool.query(queryText, [messageId]); if (result.rows.length === 0) { return res.status(404).json({ error: "Message not found" }); } res.json(result.rows[0]); // Real-time update via Socket.io to the sender (so they know it's read) const updatedMessage = result.rows[0]; const senderId = updatedMessage.sender_id; const senderSocketId = getSocketId(senderId); if (senderSocketId) { getIO().to(senderSocketId).emit("message_read", updatedMessage); } } catch (err) { console.error("Error marking message as read:", err); res.status(500).json({ error: "Internal server error" }); } }); export default router;