From 1159bdebde8aff3d997495197921f57a5b192e66 Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Thu, 23 Jul 2026 14:31:13 +1000 Subject: [PATCH] Save messages to database --- client/script_friends.js | 41 ++++++++++++++++++---- server/src/db.cpp | 75 ++++++++++++++++++++++++++++++++++++++++ server/src/db.h | 27 ++++++++++++++- server/src/main.cpp | 58 ++++++++++++++++++++++++++----- 4 files changed, 186 insertions(+), 15 deletions(-) diff --git a/client/script_friends.js b/client/script_friends.js index b5f91ce..5b595ae 100644 --- a/client/script_friends.js +++ b/client/script_friends.js @@ -66,7 +66,7 @@ async function fetchUsername(id) { try { const res = await fetch("/userinfo/" + id); if (!res.ok) { - console.error("Failed to fetch /userinfo/" + id + " :", res.status); + console.error("Failed to fetch /userinfo/" + id + " :", res.status, await res.text()); return; } const usernameHeader = res.headers.get("X-Username"); @@ -77,6 +77,27 @@ async function fetchUsername(id) { } } +/** + * + * @param {number} id + * @returns {Array} + */ +async function getMessageHistory(id) { + try { + const res = await fetch("/friends/chatHistory/" + id); + if (!res.ok) { + console.error("Failed to fetch /friends/chatHistory/" + id + " :", res.status, await res.text()); + return []; + } + const json = await res.json(); + // the server wraps the array in { "messages": [...] }, not a bare array + return json.messages.map(m => new Message(m.sender, m.content)); + } catch (err) { + console.error("Error fetching /me:", err); + return []; + } +} + document.addEventListener('DOMContentLoaded', function() { fetchSelfId(); @@ -97,7 +118,7 @@ document.addEventListener('DOMContentLoaded', function() { }, 5000); }; - socket.onmessage = function(event) { + socket.onmessage = async function(event) { console.log("recieved data", event.data); const data = JSON.parse(event.data); switch (data.type) { @@ -113,7 +134,8 @@ document.addEventListener('DOMContentLoaded', function() { console.log("message from", data.userId, ":", data.content); // update the messages variable if (recievedMessages.get(data.userId) === undefined) { - recievedMessages.set(data.userId, []); + // get previously recieved messages + recievedMessages.set(data.userId, await getMessageHistory(data.userId)); } recievedMessages.get(data.userId).push(new Message(data.userId, data.content)); if (currentUser === data.userId) { @@ -210,16 +232,23 @@ function updateMessages() { } /** @param {number} user */ -function openUser(user) { +async function openUser(user) { currentUser = user; const messagesList = document.getElementById("friends-messages"); messagesList.innerHTML = ""; - const messages = recievedMessages.get(user); - if (messages === undefined) { + if (recievedMessages.get(user) === undefined) { + recievedMessages.set(user, await getMessageHistory(user)); + } + + // another openUser() call may have run while we were awaiting, and + // switched the user again - bail out if we're no longer viewing this one + if (currentUser !== user) { return; } + + const messages = recievedMessages.get(user); for (const message of messages) { renderMessage(message); } diff --git a/server/src/db.cpp b/server/src/db.cpp index d53462d..6615cef 100644 --- a/server/src/db.cpp +++ b/server/src/db.cpp @@ -1,4 +1,5 @@ #include "db.h" +#include #include #include #include @@ -60,6 +61,14 @@ Database::Database(const std::string& path) { postid INTEGER NOT NULL, userid INTEGER NOT NULL ); + + CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY, + senderid INTEGER NOT NULL, + recieverid INTEGER NOT NULL, + timestamp INTEGER NOT NULL, + text TEXT NOT NULL + ); )"; char* errmsg = nullptr; @@ -448,3 +457,69 @@ void Database::invalidateUserSessions(const User& user) { sqlite3_finalize(stmt); } + + + +std::vector Database::getMessages(uint64_t userA, uint64_t userB, uint64_t amount) { + const char* sql = R"( + SELECT * FROM messages + WHERE (senderid = ? AND recieverid = ?) OR (senderid = ? AND recieverid = ?) + ORDER BY timestamp DESC + LIMIT ?; + )"; + + sqlite3_stmt* stmt; + if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) { + throw std::runtime_error("sqlite3 error: " + std::string(sqlite3_errmsg(db))); + } + + sqlite3_bind_int64(stmt, 1, userA); + sqlite3_bind_int64(stmt, 2, userB); + sqlite3_bind_int64(stmt, 3, userB); + sqlite3_bind_int64(stmt, 4, userA); + sqlite3_bind_int64(stmt, 5, amount); + + std::vector messages = {}; + while (sqlite3_step(stmt) == SQLITE_ROW) { + + uint64_t id = sqlite3_column_int64(stmt, 0); + uint64_t senderId = sqlite3_column_int64(stmt, 1); + uint64_t recieverId = sqlite3_column_int64(stmt, 2); + std::time_t timestamp = sqlite3_column_int64(stmt, 3); + const char* textptr = reinterpret_cast(sqlite3_column_text(stmt, 4)); + std::string text; + if (textptr == NULL) { + text = ""; + } else { + text = std::string(textptr); + } + + messages.emplace_back(id, senderId, recieverId, text, timestamp); + } + + sqlite3_finalize(stmt); + return messages; +} + +void Database::addMessage(Message& message) { + const char* sql = R"( + INSERT INTO messages (senderid, recieverid, timestamp, text) + VALUES (?, ?, ?, ?); + )"; + + sqlite3_stmt* stmt; + if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) { + throw std::runtime_error("sqlite3 error: " + std::string(sqlite3_errmsg(db))); + } + + sqlite3_bind_int64(stmt, 1, message.sender); + sqlite3_bind_int64(stmt, 2, message.reciever); + sqlite3_bind_int64(stmt, 3, message.timestamp); + sqlite3_bind_text(stmt, 4, message.content.c_str(), -1, SQLITE_STATIC); + + if (sqlite3_step(stmt) != SQLITE_DONE) { + throw std::runtime_error("sqlite3 error: " + std::string(sqlite3_errmsg(db))); + } + + sqlite3_finalize(stmt); +} diff --git a/server/src/db.h b/server/src/db.h index cb0b44f..814a178 100644 --- a/server/src/db.h +++ b/server/src/db.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "post.h" @@ -19,13 +20,33 @@ struct User { std::string passwordHash = ""; std::string bio = ""; - User(uint64_t id, std::string namein, const std::string& passwordHash, const std::string& bio) : + User(uint64_t id, const std::string& namein, const std::string& passwordHash, const std::string& bio) : + id(id), passwordHash(passwordHash), bio(bio), name(namein) {} + + User(uint64_t id, std::string namein, const std::string& passwordHash, const std::string& bio, int doSanitize) : id(id), passwordHash(passwordHash), bio(bio) { sanitize(namein); name = namein; } }; +struct Message { + uint64_t id = 0; + uint64_t sender = 0; + uint64_t reciever = 0; + std::time_t timestamp; + std::string content = ""; + + Message(uint64_t id, uint64_t sender, uint64_t reciever, const std::string& content, std::time_t timestamp) : + id(id), sender(sender), reciever(reciever), content(content), timestamp(timestamp) {} + + Message(uint64_t id, uint64_t sender, uint64_t reciever, std::string contentin, std::time_t timestamp, int doSanitize) : + id(id), sender(sender), reciever(reciever), timestamp(timestamp) { + sanitize(contentin); + content = contentin; + } +}; + class Database { sqlite3* db; @@ -52,6 +73,10 @@ class Database { void updateUser(const User& user); void invalidateUserSessions(const User& user); + std::vector getMessages(uint64_t userA, uint64_t userB, uint64_t amount); + // modifies the message to have it's id + void addMessage(Message& message); + }; #endif diff --git a/server/src/main.cpp b/server/src/main.cpp index dbd2dd4..0d04174 100644 --- a/server/src/main.cpp +++ b/server/src/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -171,7 +172,7 @@ int main() { response.set_content("

hey you can't have an empty username!!!!1!!!1! >:(

", "text/html"); return; } - User newUser{0, username, bcrypt::generateHash(password), ""}; + User newUser{0, username, bcrypt::generateHash(password), "", 1}; database.addUser(newUser); user = newUser; } @@ -483,15 +484,15 @@ int main() { Json returnData; returnData["type"] = "ok"; ws.send(returnData.dump()); - } else { - Json data; - data["type"] = "error"; - data["content"] = "friend not online"; - ws.send(data.dump()); - connectedFriends.erase(userId); } + // Store the message in database + { + std::lock_guard lock(data_mutex); + Message message{0, user->id, userId, content, std::time(nullptr), 1}; + database.addMessage(message); + } + // TODO: - // - Store in databse // - Verify users are friends } else if (type == 1) { // friend request @@ -545,6 +546,47 @@ int main() { } }); + svr.Get("/friends/chatHistory/:userid", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) { + std::string userIdStr = request.path_params.at("userid"); + std::lock_guard lock(data_mutex); + + try { + std::optional user = getLoggedInUser(request, database); + if (!user.has_value()) { + response.status = 401; + response.set_content("your session is invalid", "text/plain"); + return; + } + uint64_t friendUserId = std::stoull(userIdStr); + std::vector messages = database.getMessages(user->id, friendUserId, 100); + + // construct json for response + Json list; + for (auto it = messages.rbegin(); it != messages.rend(); ++it) { + Json message; + message["sender"] = it->sender; + message["reciever"] = it->reciever; + message["id"] = it->id; + message["timestamp"] = it->timestamp; + message["content"] = it->content; + list.push_back(message); + } + + Json data; + data["messages"] = list; + + response.set_content(data.dump(), "application/json"); + + } catch (const std::runtime_error& e) { + response.status = 500; + response.set_content("

there was an error :( it is: " + std::string(e.what()) + "

", "text/html"); + } catch (const std::exception& e) { + response.status = 500; + response.set_content("

there was an error :( it is: " + std::string(e.what()) + "

", "text/html"); + } + + }); + // settings endpoints svr.Post("/settings/changePassword", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) {