Save messages to database
This commit is contained in:
@@ -66,7 +66,7 @@ async function fetchUsername(id) {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch("/userinfo/" + id);
|
const res = await fetch("/userinfo/" + id);
|
||||||
if (!res.ok) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
const usernameHeader = res.headers.get("X-Username");
|
const usernameHeader = res.headers.get("X-Username");
|
||||||
@@ -77,6 +77,27 @@ async function fetchUsername(id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} id
|
||||||
|
* @returns {Array<Message>}
|
||||||
|
*/
|
||||||
|
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() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
fetchSelfId();
|
fetchSelfId();
|
||||||
@@ -97,7 +118,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}, 5000);
|
}, 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.onmessage = function(event) {
|
socket.onmessage = async function(event) {
|
||||||
console.log("recieved data", event.data);
|
console.log("recieved data", event.data);
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
@@ -113,7 +134,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
console.log("message from", data.userId, ":", data.content);
|
console.log("message from", data.userId, ":", data.content);
|
||||||
// update the messages variable
|
// update the messages variable
|
||||||
if (recievedMessages.get(data.userId) === undefined) {
|
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));
|
recievedMessages.get(data.userId).push(new Message(data.userId, data.content));
|
||||||
if (currentUser === data.userId) {
|
if (currentUser === data.userId) {
|
||||||
@@ -210,16 +232,23 @@ function updateMessages() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @param {number} user */
|
/** @param {number} user */
|
||||||
function openUser(user) {
|
async function openUser(user) {
|
||||||
currentUser = user;
|
currentUser = user;
|
||||||
|
|
||||||
const messagesList = document.getElementById("friends-messages");
|
const messagesList = document.getElementById("friends-messages");
|
||||||
messagesList.innerHTML = "";
|
messagesList.innerHTML = "";
|
||||||
|
|
||||||
const messages = recievedMessages.get(user);
|
if (recievedMessages.get(user) === undefined) {
|
||||||
if (messages === 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const messages = recievedMessages.get(user);
|
||||||
for (const message of messages) {
|
for (const message of messages) {
|
||||||
renderMessage(message);
|
renderMessage(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "db.h"
|
#include "db.h"
|
||||||
|
#include <cstdint>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -60,6 +61,14 @@ Database::Database(const std::string& path) {
|
|||||||
postid INTEGER NOT NULL,
|
postid INTEGER NOT NULL,
|
||||||
userid 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;
|
char* errmsg = nullptr;
|
||||||
@@ -448,3 +457,69 @@ void Database::invalidateUserSessions(const User& user) {
|
|||||||
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Message> 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<Message> 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<const char*>(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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
#include <sys/types.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "post.h"
|
#include "post.h"
|
||||||
@@ -19,13 +20,33 @@ struct User {
|
|||||||
std::string passwordHash = "";
|
std::string passwordHash = "";
|
||||||
std::string bio = "";
|
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) {
|
id(id), passwordHash(passwordHash), bio(bio) {
|
||||||
sanitize(namein);
|
sanitize(namein);
|
||||||
name = 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 {
|
class Database {
|
||||||
|
|
||||||
sqlite3* db;
|
sqlite3* db;
|
||||||
@@ -52,6 +73,10 @@ class Database {
|
|||||||
void updateUser(const User& user);
|
void updateUser(const User& user);
|
||||||
void invalidateUserSessions(const User& user);
|
void invalidateUserSessions(const User& user);
|
||||||
|
|
||||||
|
std::vector<Message> getMessages(uint64_t userA, uint64_t userB, uint64_t amount);
|
||||||
|
// modifies the message to have it's id
|
||||||
|
void addMessage(Message& message);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <ctime>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
@@ -171,7 +172,7 @@ int main() {
|
|||||||
response.set_content("<p>hey you can't have an empty username!!!!1!!!1! >:(</p>", "text/html");
|
response.set_content("<p>hey you can't have an empty username!!!!1!!!1! >:(</p>", "text/html");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
User newUser{0, username, bcrypt::generateHash(password), ""};
|
User newUser{0, username, bcrypt::generateHash(password), "", 1};
|
||||||
database.addUser(newUser);
|
database.addUser(newUser);
|
||||||
user = newUser;
|
user = newUser;
|
||||||
}
|
}
|
||||||
@@ -483,15 +484,15 @@ int main() {
|
|||||||
Json returnData;
|
Json returnData;
|
||||||
returnData["type"] = "ok";
|
returnData["type"] = "ok";
|
||||||
ws.send(returnData.dump());
|
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<std::mutex> lock(data_mutex);
|
||||||
|
Message message{0, user->id, userId, content, std::time(nullptr), 1};
|
||||||
|
database.addMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - Store in databse
|
|
||||||
// - Verify users are friends
|
// - Verify users are friends
|
||||||
|
|
||||||
} else if (type == 1) { // friend request
|
} 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<std::mutex> lock(data_mutex);
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::optional<User> 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<Message> 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("<p>there was an error :( it is: " + std::string(e.what()) + "</p>", "text/html");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
response.status = 500;
|
||||||
|
response.set_content("<p>there was an error :( it is: " + std::string(e.what()) + "</p>", "text/html");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// settings endpoints
|
// settings endpoints
|
||||||
svr.Post("/settings/changePassword", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) {
|
svr.Post("/settings/changePassword", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) {
|
||||||
|
|||||||
Reference in New Issue
Block a user