Added base chat routes & socket for communication
This commit is contained in:
parent
05d1dfe718
commit
2cf6cf1772
|
|
@ -12,7 +12,29 @@
|
|||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.1.0",
|
||||
"pg": "^8.16.3"
|
||||
"pg": "^8.16.3",
|
||||
"socket.io": "^4.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
|
||||
},
|
||||
"node_modules/@types/cors": {
|
||||
"version": "2.8.19",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
|
||||
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.2.tgz",
|
||||
"integrity": "sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA==",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
|
|
@ -27,6 +49,14 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/base64id": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
|
||||
"engines": {
|
||||
"node": "^4.5.0 || >= 5.9"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz",
|
||||
|
|
@ -194,6 +224,88 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io": {
|
||||
"version": "6.6.4",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
|
||||
"integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
|
||||
"dependencies": {
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/node": ">=10.0.0",
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "2.0.0",
|
||||
"cookie": "~0.7.2",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.17.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io/node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io/node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
|
|
@ -845,6 +957,131 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
|
||||
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io": "~6.6.0",
|
||||
"socket.io-adapter": "~2.5.2",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-adapter": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
|
||||
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
|
||||
"dependencies": {
|
||||
"debug": "~4.3.4",
|
||||
"ws": "~8.17.1"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-adapter/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io/node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io/node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/split2": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||
|
|
@ -882,6 +1119,11 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
|
|
@ -903,6 +1145,26 @@
|
|||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.1.0",
|
||||
"pg": "^8.16.3"
|
||||
"pg": "^8.16.3",
|
||||
"socket.io": "^4.8.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,196 @@
|
|||
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;
|
||||
10
server.js
10
server.js
|
|
@ -10,10 +10,18 @@ const PORT = process.env.PORT || 3200;
|
|||
// Add routes here
|
||||
import listingRoutes from "./routes/listingRoutes.js";
|
||||
import locationRoutes from "./routes/locationRoutes.js";
|
||||
import chatRoutes from "./routes/chatRoutes.js";
|
||||
|
||||
|
||||
app.use("/listings", listingRoutes);
|
||||
app.use("/locations", locationRoutes);
|
||||
app.use("/chat", chatRoutes);
|
||||
import http from "http";
|
||||
import { initSocket } from "./socket.js";
|
||||
|
||||
app.listen(PORT, () => {
|
||||
const server = http.createServer(app);
|
||||
initSocket(server);
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`BuySellService is running on port ${PORT}`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
import { Server } from "socket.io";
|
||||
|
||||
let io;
|
||||
const userSocketMap = new Map(); // userId -> socketId
|
||||
|
||||
// Initialize Socket.IO
|
||||
export const initSocket = (server) => {
|
||||
io = new Server(server, {
|
||||
cors: {
|
||||
origin: "*", // Adjust as needed for production
|
||||
methods: ["GET", "POST"],
|
||||
},
|
||||
});
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
console.log("A user connected:", socket.id);
|
||||
|
||||
// Event for user to register their ID
|
||||
socket.on("register", (userId) => {
|
||||
if (userId) {
|
||||
userSocketMap.set(userId, socket.id);
|
||||
console.log(`Registered user ${userId} to socket ${socket.id}`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
console.log("User disconnected:", socket.id);
|
||||
// Optional: Remove user from map. This is inefficient O(N).
|
||||
// Better: keep reverse map or just iterate. Map iteration is okay for small scale.
|
||||
for (const [userId, socketId] of userSocketMap.entries()) {
|
||||
if (socketId === socket.id) {
|
||||
userSocketMap.delete(userId);
|
||||
console.log(`Unregistered user ${userId}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return io;
|
||||
};
|
||||
|
||||
// Get the IO instance
|
||||
export const getIO = () => {
|
||||
if (!io) {
|
||||
throw new Error("Socket.io not initialized!");
|
||||
}
|
||||
return io;
|
||||
};
|
||||
|
||||
// Get socket ID for a user
|
||||
export const getSocketId = (userId) => {
|
||||
return userSocketMap.get(userId);
|
||||
};
|
||||
Loading…
Reference in New Issue