#include "db.h" #include #include #include #include Database::Database(const std::string& path) { // init db if (sqlite3_open(path.c_str(), &db) != SQLITE_OK) { throw std::runtime_error("sqlite3 error: " + std::string(sqlite3_errmsg(db))); } // create tables in case they don't exist const char* sql = R"( CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, password TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY, userid INTEGER NOT NULL, content TEXT NOT NULL, timestamp INTEGER NOT NULL, likes INTEGER ); )"; char* errmsg = nullptr; if (sqlite3_exec(db, sql, nullptr, nullptr, &errmsg) != SQLITE_OK) { throw std::runtime_error("sqlite3 error: " + std::string(errmsg)); } } Database::~Database() { sqlite3_close(db); } std::optional Database::getPost(uint64_t id) { std::stringstream sql; sql << "SELECT * FROM posts WHERE id = " << id << ";"; std::string sqlstr = sql.str(); sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db, sqlstr.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { throw std::runtime_error("sqlite3 error: " + std::string(sqlite3_errmsg(db))); } // we only want the first one (there's probably only one) // if there is none, return nothing if (sqlite3_step(stmt) == SQLITE_DONE) { return {}; } int64_t userid = sqlite3_column_int64(stmt, 1); std::string content{reinterpret_cast(sqlite3_column_text(stmt, 2))}; int64_t timestamp = sqlite3_column_int64(stmt, 3); int64_t likes = sqlite3_column_int64(stmt, 4); sqlite3_finalize(stmt); // get user info from post std::optional user = getUser(userid); if (user.has_value()) { return Post(id, content, user->name, timestamp, likes); } else { throw std::runtime_error("post has invalid user ID attached (this is weird)"); } } std::vector Database::getUserPosts(uint64_t userId) { std::stringstream sql; sql << "SELECT * FROM posts WHERE userid = " << userId << ";"; std::string sqlstr = sql.str(); sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db, sqlstr.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { throw std::runtime_error("sqlite3 error: " + std::string(sqlite3_errmsg(db))); } // get user info from post std::optional user = getUser(userId); if (!user.has_value()) { throw std::runtime_error("user does not exist"); } std::vector posts = {}; while (sqlite3_step(stmt) != SQLITE_DONE) { int64_t postid = sqlite3_column_int64(stmt, 0); std::string content{reinterpret_cast(sqlite3_column_text(stmt, 2))}; int64_t timestamp = sqlite3_column_int64(stmt, 3); int64_t likes = sqlite3_column_int64(stmt, 4); posts.emplace_back(postid, content, user->name, timestamp, likes); } sqlite3_finalize(stmt); return posts; } std::vector Database::getTopPosts(uint64_t amount) { std::stringstream sql; sql << "SELECT * FROM posts ORDER BY timestamp DESC LIMIT " << amount << ";"; std::string sqlstr = sql.str(); sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db, sqlstr.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { throw std::runtime_error("sqlite3 error: " + std::string(sqlite3_errmsg(db))); } std::vector posts = {}; while (sqlite3_step(stmt) != SQLITE_DONE) { int64_t postid = sqlite3_column_int64(stmt, 0); int64_t userid = sqlite3_column_int64(stmt, 1); std::string content{reinterpret_cast(sqlite3_column_text(stmt, 2))}; int64_t timestamp = sqlite3_column_int64(stmt, 3); int64_t likes = sqlite3_column_int64(stmt, 4); // get username from id std::optional user = getUser(userid); if (!user.has_value()) { throw std::runtime_error("post has invalid user ID attached (this is weird)"); } posts.emplace_back(postid, content, user->name, timestamp, likes); } sqlite3_finalize(stmt); return posts; } void Database::addPost(const Post& post) { const char* sql = R"( INSERT INTO posts (userid, content, timestamp) 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, post.userId); sqlite3_bind_text(stmt, 2, post.content.c_str(), -1, SQLITE_STATIC); sqlite3_bind_int64(stmt, 3, post.time); if (sqlite3_step(stmt) != SQLITE_DONE) { throw std::runtime_error("sqlite3 error: " + std::string(sqlite3_errmsg(db))); } sqlite3_finalize(stmt); } void Database::addLike(uint64_t postId, uint64_t userId) { std::stringstream sql; sql << R"( UPDATE posts SET likes = likes + 1 WHERE id = )"; sql << postId << ";"; std::string sqlstr = sql.str(); char* errmsg = nullptr; if (sqlite3_exec(db, sqlstr.c_str(), nullptr, nullptr, &errmsg) != SQLITE_OK) { throw std::runtime_error("sqlite3 error: " + std::string(errmsg)); } } std::optional Database::getUser(uint64_t id) { std::stringstream sql; sql << "SELECT * FROM users WHERE id = " << id << ";"; std::string sqlstr = sql.str(); sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db, sqlstr.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { throw std::runtime_error("sqlite3 error: " + std::string(sqlite3_errmsg(db))); } // we only want the first one (there's probably only one) // if there is none, return nothing if (sqlite3_step(stmt) == SQLITE_DONE) { return {}; } std::string name{reinterpret_cast(sqlite3_column_text(stmt, 1))}; std::string password{reinterpret_cast(sqlite3_column_text(stmt, 2))}; sqlite3_finalize(stmt); return User(id, name, password); } std::optional Database::getUserByName(const std::string& name) { std::string sql = "SELECT * FROM users WHERE name = ?;"; sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { throw std::runtime_error("sqlite3 error: " + std::string(sqlite3_errmsg(db))); } sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_STATIC); // we only want the first one (there's probably only one) // if there is none, return nothing if (sqlite3_step(stmt) == SQLITE_DONE) { return {}; } uint64_t id = sqlite3_column_int64(stmt, 0); std::string password{reinterpret_cast(sqlite3_column_text(stmt, 2))}; sqlite3_finalize(stmt); return User(id, name, password); } void Database::addUser(User& user) { const char* sql = R"( INSERT INTO users (name, password) 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_text(stmt, 1, user.name.c_str(), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, user.passwordHash.c_str(), -1, SQLITE_STATIC); if (sqlite3_step(stmt) != SQLITE_DONE) { throw std::runtime_error("sqlite3 error: " + std::string(sqlite3_errmsg(db))); } user.id = sqlite3_last_insert_rowid(db); sqlite3_finalize(stmt); }