From e404f08d866f5c2fdd737b831620500b0f7a8173 Mon Sep 17 00:00:00 2001 From: watawada Date: Tue, 7 Jan 2025 10:18:08 -0800 Subject: [PATCH 1/4] Implemented messaging storing and loading Added code in backend to store messages in mongodb whenever a message is sent. Added code in frontend to retrieve messages by conversation id and load them whenever you click on a user. --- client/src/app/messages/Chatlog.tsx | 34 ++++++++++++++++++++-- client/src/app/messages/RecentMessages.tsx | 2 +- server/bin/www.js | 21 +++++-------- server/bin/www.ts | 29 +++++++----------- server/models/Message.js | 24 +++++---------- server/models/Post.js | 24 +++++---------- server/routes/post.js | 4 +-- server/routes/user.js | 5 ++-- server/routes/user.ts | 3 +- 9 files changed, 69 insertions(+), 77 deletions(-) diff --git a/client/src/app/messages/Chatlog.tsx b/client/src/app/messages/Chatlog.tsx index deb3ea5..413583e 100644 --- a/client/src/app/messages/Chatlog.tsx +++ b/client/src/app/messages/Chatlog.tsx @@ -12,11 +12,13 @@ const Chatlog = () => { const { selectedUserId } = useUserContext(); // Access the selectedUserId from context const { username } = useUserContext(); const [currentUserId, setCurrentUserId] = useState(""); + const [conversationId, setConversationId] = useState(""); + const [loading, setLoading] = useState(false); const chatContainerRef = useRef(null); // Ref to the chat container const fetchUserData = async () => { try { - const response = await backendConnection.get("users/self", { withCredentials: true }) + const response = await backendConnection.get("user/self", { withCredentials: true }) .then((res) => res.data) .then((data) => { setCurrentUserId(data); @@ -26,11 +28,36 @@ const Chatlog = () => { } }; + const loadMessages = async (conversationId: string) => { + try { + setLoading(true); + const response = await backendConnection.get(`/api/messages/${conversationId}`); + const messages = response.data.map((msg:any) => ({ + sender: msg.user_id === currentUserId ? "user" : "other", + text: msg.message, + timestamp: new Date(msg.timestamp).toLocaleTimeString, + })); + setMessages(messages); + } catch (error){ + console.error("Error loading messages", error); + } finally{ + setLoading(false); + } + } + useEffect(() => { if (selectedUserId) { + const newConversationId = currentUserId > selectedUserId + ? `${currentUserId}${selectedUserId}` + : `${selectedUserId}${currentUserId}`; + + setConversationId(newConversationId); + // Emit 'join_chat' event to the server socket.emit("join_chat", currentUserId, selectedUserId); + loadMessages(newConversationId); + // Listen for new messages socket.on("receive_message", (data) => { if (data.user_id !== currentUserId) { @@ -48,9 +75,12 @@ const Chatlog = () => { socket.off("receive_message"); }; } - fetchUserData(); }, [selectedUserId, currentUserId]); + + useEffect(() => { + fetchUserData(); + }, []); // Scroll to the newest message when `messages` updates useEffect(() => { if (chatContainerRef.current) { diff --git a/client/src/app/messages/RecentMessages.tsx b/client/src/app/messages/RecentMessages.tsx index 52e872a..8d05135 100644 --- a/client/src/app/messages/RecentMessages.tsx +++ b/client/src/app/messages/RecentMessages.tsx @@ -11,7 +11,7 @@ const RecentMessages: React.FC = () => { // For now the "recent messages" are jsut going to be all users until we can estabilsh saving messages in database const fetchRecentMessages = async () => { - const response = await backendConnection.get('users/all', { withCredentials: true }) + const response = await backendConnection.get('user/all', { withCredentials: true }) .then((res) => res.data) .then((data) => { console.log("users") diff --git a/server/bin/www.js b/server/bin/www.js index ffba41e..69a661f 100644 --- a/server/bin/www.js +++ b/server/bin/www.js @@ -21,6 +21,7 @@ const app_1 = __importDefault(require("../app")); const debug_1 = __importDefault(require("debug")); const http_1 = __importDefault(require("http")); const socket_io_1 = require("socket.io"); +const Message_1 = __importDefault(require("../models/Message")); /** * Get port from environment and store in Express. */ @@ -31,19 +32,6 @@ app_1.default.set('port', port); */ const server = http_1.default.createServer(app_1.default); exports.server = server; -/** - * Connect to MongoDB - */ -// const mongoURI = process.env.MONGO_URI; -// if (!mongoURI) { -// console.error('MONGO_URI is not defined in the environment variables.'); -// process.exit(1); // Exit the process with a failure -// } -// mongoose.connect(mongoURI).then(() => { -// console.log('Connected to MongoDB'); -// }).catch((err) => { -// console.error('Error connecting to MongoDB:', err); -// }); /** * Create SocketIO server */ @@ -72,11 +60,18 @@ io.on('connection', (socket) => { }); socket.on('send_message', (data) => __awaiter(void 0, void 0, void 0, function* () { const { message, user_id } = data; + const newMessage = new Message_1.default({ + message: message, + user_id: user_id, + timestamp: new Date(), + conversation_id: conversation_id + }); try { // Broadcast the message to the unique chat room io.to(conversation_id).emit('receive_message', { message, user_id }); // Optionally, acknowledge the sender that the message was sent socket.emit('message_sent', { success: true, message }); + yield newMessage.save(); } catch (error) { console.error('Error sending message:', error); diff --git a/server/bin/www.ts b/server/bin/www.ts index f55c375..2b7c839 100644 --- a/server/bin/www.ts +++ b/server/bin/www.ts @@ -9,7 +9,7 @@ import debug from 'debug'; import http from 'http'; import { Socket, Server} from 'socket.io'; import mongoose from 'mongoose'; -import { Message } from '../models/messageSchema'; +import Message from '../models/Message'; import { v4 as uuidv4} from 'uuid'; /** @@ -24,22 +24,6 @@ app.set('port', port); */ const server = http.createServer(app); -/** - * Connect to MongoDB - */ - -// const mongoURI = process.env.MONGO_URI; - -// if (!mongoURI) { -// console.error('MONGO_URI is not defined in the environment variables.'); -// process.exit(1); // Exit the process with a failure -// } - -// mongoose.connect(mongoURI).then(() => { -// console.log('Connected to MongoDB'); -// }).catch((err) => { -// console.error('Error connecting to MongoDB:', err); -// }); /** * Create SocketIO server @@ -77,13 +61,20 @@ io.on('connection', (socket: Socket) => { socket.on('send_message', async (data) => { const { message, user_id } = data; - + const newMessage = new Message({ + message: message, + user_id: user_id, + timestamp: new Date(), + conversation_id: conversation_id + }) try { - // Broadcast the message to the unique chat room + // Broadcast the message to the unique chat room io.to(conversation_id).emit('receive_message', { message, user_id }); // Optionally, acknowledge the sender that the message was sent socket.emit('message_sent', { success: true, message }); + + await newMessage.save(); } catch (error) { console.error('Error sending message:', error); socket.emit('error_message', { error: 'Failed to send message' }); diff --git a/server/models/Message.js b/server/models/Message.js index 1579a00..7827b77 100644 --- a/server/models/Message.js +++ b/server/models/Message.js @@ -15,23 +15,13 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; Object.defineProperty(exports, "__esModule", { value: true }); const mongoose_1 = __importStar(require("mongoose")); const messageSchema = new mongoose_1.Schema({ diff --git a/server/models/Post.js b/server/models/Post.js index e4c6734..fef3f8a 100644 --- a/server/models/Post.js +++ b/server/models/Post.js @@ -15,23 +15,13 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; Object.defineProperty(exports, "__esModule", { value: true }); const mongoose_1 = __importStar(require("mongoose")); const postSchema = new mongoose_1.Schema({ diff --git a/server/routes/post.js b/server/routes/post.js index 5a41663..34e3587 100644 --- a/server/routes/post.js +++ b/server/routes/post.js @@ -16,8 +16,6 @@ const express_1 = __importDefault(require("express")); const Post_1 = __importDefault(require("../models/Post")); const User_1 = require("../models/User"); const mongoose_1 = __importDefault(require("mongoose")); -const User_1 = require("../models/User"); -const mongoose_1 = __importDefault(require("mongoose")); const router = express_1.default.Router(); /** * @route POST / @@ -59,7 +57,6 @@ router.post("/", (req, res) => __awaiter(void 0, void 0, void 0, function* () { cost, numStores, author: req.user.user_id, - author: req.user.user_id, available_stores, image, tags, @@ -316,4 +313,5 @@ router.patch("/like", (req, res) => __awaiter(void 0, void 0, void 0, function* res.status(500).json({ error: "Error liking post" }); } })); +// Export the router exports.default = router; diff --git a/server/routes/user.js b/server/routes/user.js index a955b41..82b29ab 100644 --- a/server/routes/user.js +++ b/server/routes/user.js @@ -128,7 +128,6 @@ router.post('/follow', (req, res) => __awaiter(void 0, void 0, void 0, function* res.status(500).json({ message: 'Following error occurred:', err }); } })); - /** * @route PATCH /profile * @desc Edit profile @@ -146,6 +145,7 @@ router.post('/follow', (req, res) => __awaiter(void 0, void 0, void 0, function* * * Responses: * - 200: The profile was updated successfully. + * - 400: Username was taken. * - 401: Unauthorized. * - 404: The user was not found. * - 500: Internal server error. @@ -212,8 +212,7 @@ router.patch('/profile', (req, res) => __awaiter(void 0, void 0, void 0, functio */ router.get('/self', (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { if (req.user) { - const user = yield User_1.User.findOne({ user_id: req.user.user_id }); - res.status(200).json(user); + res.status(200).json(req.user.user_id); } else { res.status(401).send('Unauthorized'); diff --git a/server/routes/user.ts b/server/routes/user.ts index 5901e79..1e854f6 100644 --- a/server/routes/user.ts +++ b/server/routes/user.ts @@ -215,8 +215,7 @@ router.patch('/profile', async (req, res) => { router.get('/self', async (req: Request, res: Response, next: NextFunction) => { if (req.user) { - const user = await User.findOne({ user_id: (req.user as IUser).user_id}); - res.status(200).json(user); + res.status(200).json((req.user as IUser).user_id); } else { res.status(401).send('Unauthorized'); } From 61a51007aeecf7bcbeea2dc26714cd90e9fb1685 Mon Sep 17 00:00:00 2001 From: watawada Date: Tue, 7 Jan 2025 10:37:09 -0800 Subject: [PATCH 2/4] Update Chatlog.tsx --- client/src/app/messages/Chatlog.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/app/messages/Chatlog.tsx b/client/src/app/messages/Chatlog.tsx index 413583e..ff36692 100644 --- a/client/src/app/messages/Chatlog.tsx +++ b/client/src/app/messages/Chatlog.tsx @@ -13,7 +13,6 @@ const Chatlog = () => { const { username } = useUserContext(); const [currentUserId, setCurrentUserId] = useState(""); const [conversationId, setConversationId] = useState(""); - const [loading, setLoading] = useState(false); const chatContainerRef = useRef(null); // Ref to the chat container const fetchUserData = async () => { @@ -28,10 +27,13 @@ const Chatlog = () => { } }; + // Load messages based on conversation ID const loadMessages = async (conversationId: string) => { try { - setLoading(true); + // Get conversation's messages from backend const response = await backendConnection.get(`/api/messages/${conversationId}`); + + // Create array of the retrieved messages const messages = response.data.map((msg:any) => ({ sender: msg.user_id === currentUserId ? "user" : "other", text: msg.message, @@ -40,9 +42,7 @@ const Chatlog = () => { setMessages(messages); } catch (error){ console.error("Error loading messages", error); - } finally{ - setLoading(false); - } + } } useEffect(() => { From 2d92c2b749494a2d06c013a2f1e8aab87b8d1578 Mon Sep 17 00:00:00 2001 From: watawada Date: Tue, 7 Jan 2025 10:53:14 -0800 Subject: [PATCH 3/4] Update Chatlog.tsx --- client/src/app/messages/Chatlog.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/app/messages/Chatlog.tsx b/client/src/app/messages/Chatlog.tsx index ff36692..c0a2787 100644 --- a/client/src/app/messages/Chatlog.tsx +++ b/client/src/app/messages/Chatlog.tsx @@ -47,6 +47,7 @@ const Chatlog = () => { useEffect(() => { if (selectedUserId) { + // Create the conversation ID (same logic as backend) const newConversationId = currentUserId > selectedUserId ? `${currentUserId}${selectedUserId}` : `${selectedUserId}${currentUserId}`; @@ -56,6 +57,7 @@ const Chatlog = () => { // Emit 'join_chat' event to the server socket.emit("join_chat", currentUserId, selectedUserId); + // Call load messages to load the messages associated with the conversation ID loadMessages(newConversationId); // Listen for new messages From db0b80741291e56434946697af0f73f27d91f4e6 Mon Sep 17 00:00:00 2001 From: watawada Date: Thu, 9 Jan 2025 14:28:32 -0800 Subject: [PATCH 4/4] Updated editing tags in profile --- server/routes/user.js | 16 ++++++---------- server/routes/user.ts | 19 ++++++++++--------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/server/routes/user.js b/server/routes/user.js index 82b29ab..61a7691 100644 --- a/server/routes/user.js +++ b/server/routes/user.js @@ -170,16 +170,11 @@ router.patch('/profile', (req, res) => __awaiter(void 0, void 0, void 0, functio return; } if (tags) { - if (user.tags.includes(tags)) { - yield User_1.User.findOneAndUpdate({ user_id: user_id }, { - $pull: { tags: tags } - }, { new: true }); - } - else { - yield User_1.User.findOneAndUpdate({ user_id: user_id }, { - $push: { tags: { $each: [tags] } } - }, { new: true }); + if (!Array.isArray(tags)) { + res.status(400).json({ message: 'Tags must be an array' }); + return; } + yield User_1.User.findOneAndUpdate({ user_id: user_id }, { $set: { tags: tags } }, { new: true }); } const updatedUser = yield User_1.User.findOneAndUpdate({ user_id: user_id }, { $set: { @@ -212,7 +207,8 @@ router.patch('/profile', (req, res) => __awaiter(void 0, void 0, void 0, functio */ router.get('/self', (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { if (req.user) { - res.status(200).json(req.user.user_id); + const user = req.user.user_id; + res.status(200).json(user); } else { res.status(401).send('Unauthorized'); diff --git a/server/routes/user.ts b/server/routes/user.ts index 1e854f6..b3f7767 100644 --- a/server/routes/user.ts +++ b/server/routes/user.ts @@ -172,15 +172,15 @@ router.patch('/profile', async (req, res) => { } if (tags){ - if(user.tags.includes(tags)){ - await User.findOneAndUpdate({user_id: user_id},{ - $pull: {tags: tags} - }, {new: true}); - } else{ - await User.findOneAndUpdate({user_id: user_id},{ - $push: {tags: {$each: [tags]}} - }, {new: true}) + if (!Array.isArray(tags)) { + res.status(400).json({message: 'Tags must be an array'}); + return; } + await User.findOneAndUpdate( + {user_id: user_id}, + { $set: { tags: tags }}, + {new: true} + ); } const updatedUser = await User.findOneAndUpdate({user_id: user_id},{ $set: { @@ -215,7 +215,8 @@ router.patch('/profile', async (req, res) => { router.get('/self', async (req: Request, res: Response, next: NextFunction) => { if (req.user) { - res.status(200).json((req.user as IUser).user_id); + const user = (req.user as IUser).user_id; + res.status(200).json(user); } else { res.status(401).send('Unauthorized'); }