2025-12-07 12:00:44 -07:00

387 lines
13 KiB
C++

#include "database.h"
#include <iostream>
#include <ctime>
namespace scar {
Database::Database(const std::string& db_path)
: db_(nullptr), db_path_(db_path) {}
Database::~Database() {
if (db_) {
sqlite3_close(db_);
}
}
bool Database::initialize() {
int rc = sqlite3_open(db_path_.c_str(), &db_);
if (rc != SQLITE_OK) {
std::cerr << "Cannot open database: " << sqlite3_errmsg(db_) << std::endl;
return false;
}
const char* create_table_sql = R"(
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
salt TEXT NOT NULL,
token TEXT,
status TEXT DEFAULT 'offline',
role TEXT,
email TEXT,
last_login INTEGER,
avatar_pic BLOB
);
CREATE INDEX IF NOT EXISTS idx_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_token ON users(token);
)";
return execute(create_table_sql);
}
bool Database::execute(const std::string& sql) {
char* err_msg = nullptr;
int rc = sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, &err_msg);
if (rc != SQLITE_OK) {
std::cerr << "SQL error: " << err_msg << std::endl;
sqlite3_free(err_msg);
return false;
}
return true;
}
bool Database::createUser(const std::string& username, const std::string& password_hash,
const std::string& salt) {
const char* sql = "INSERT INTO users (username, password, salt) VALUES (?, ?, ?)";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
return false;
}
sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, password_hash.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, salt.c_str(), -1, SQLITE_TRANSIENT);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
std::unique_ptr<UserRecord> Database::getUserByUsername(const std::string& username) {
const char* sql = "SELECT id, username, password, salt, token, status, role, email, last_login FROM users WHERE username = ?";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
return nullptr;
}
sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_TRANSIENT);
std::unique_ptr<UserRecord> user;
if (sqlite3_step(stmt) == SQLITE_ROW) {
user = std::make_unique<UserRecord>();
user->id = sqlite3_column_int(stmt, 0);
user->username = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
user->password_hash = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
user->salt = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
const char* token = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 4));
user->token = token ? token : "";
const char* status_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 5));
user->status = (status_str && std::string(status_str) == "online")
? UserStatus::ONLINE : UserStatus::OFFLINE;
const char* role = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 6));
user->role = role ? role : "";
const char* email = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 7));
user->email = email ? email : "";
user->last_login = sqlite3_column_int64(stmt, 8);
}
sqlite3_finalize(stmt);
return user;
}
bool Database::updateUserToken(const std::string& username, const std::string& token) {
const char* sql = "UPDATE users SET token = ? WHERE username = ?";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
return false;
}
sqlite3_bind_text(stmt, 1, token.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, username.c_str(), -1, SQLITE_TRANSIENT);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
bool Database::updateUserStatus(const std::string& username, UserStatus status) {
const char* status_str = (status == UserStatus::ONLINE) ? "online" : "offline";
const char* sql = "UPDATE users SET status = ? WHERE username = ?";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
return false;
}
sqlite3_bind_text(stmt, 1, status_str, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, username.c_str(), -1, SQLITE_TRANSIENT);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
bool Database::updateLastLogin(const std::string& username) {
int64_t now = std::time(nullptr);
const char* sql = "UPDATE users SET last_login = ? WHERE username = ?";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
return false;
}
sqlite3_bind_int64(stmt, 1, now);
sqlite3_bind_text(stmt, 2, username.c_str(), -1, SQLITE_TRANSIENT);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
bool Database::verifyCredentials(const std::string& username, const std::string& password_hash) {
auto user = getUserByUsername(username);
return user && user->password_hash == password_hash;
}
std::string Database::getUsernameByToken(const std::string& token) {
const char* sql = "SELECT username FROM users WHERE token = ?";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
return "";
}
sqlite3_bind_text(stmt, 1, token.c_str(), -1, SQLITE_TRANSIENT);
std::string username;
if (sqlite3_step(stmt) == SQLITE_ROW) {
username = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
}
sqlite3_finalize(stmt);
return username;
}
bool Database::deleteUser(const std::string& username) {
const char* sql = "DELETE FROM users WHERE username = ?";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
return false;
}
sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_TRANSIENT);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
bool Database::updateUserPassword(const std::string& username, const std::string& password_hash,
const std::string& salt) {
const char* sql = "UPDATE users SET password = ?, salt = ? WHERE username = ?";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
return false;
}
sqlite3_bind_text(stmt, 1, password_hash.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, salt.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, username.c_str(), -1, SQLITE_TRANSIENT);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
bool Database::updateUserAvatar(const std::string& username, const std::vector<uint8_t>& avatar_data) {
const char* sql = "UPDATE users SET avatar_pic = ? WHERE username = ?";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
return false;
}
sqlite3_bind_blob(stmt, 1, avatar_data.data(), avatar_data.size(), SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, username.c_str(), -1, SQLITE_TRANSIENT);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
bool Database::updateUserEmail(const std::string& username, const std::string& email) {
const char* sql = "UPDATE users SET email = ? WHERE username = ?";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
return false;
}
sqlite3_bind_text(stmt, 1, email.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, username.c_str(), -1, SQLITE_TRANSIENT);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
bool Database::updateUserRole(const std::string& username, const std::string& role) {
const char* sql = "UPDATE users SET role = ? WHERE username = ?";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
return false;
}
sqlite3_bind_text(stmt, 1, role.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, username.c_str(), -1, SQLITE_TRANSIENT);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
std::vector<UserRecord> Database::searchUsers(const std::string& field, const std::string& value) {
std::vector<UserRecord> results;
std::string sql = "SELECT id, username, password, salt, token, status, role, email, last_login, avatar_pic FROM users WHERE " + field + " LIKE ?";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
return results;
}
std::string search_pattern = "%" + value + "%";
sqlite3_bind_text(stmt, 1, search_pattern.c_str(), -1, SQLITE_TRANSIENT);
while (sqlite3_step(stmt) == SQLITE_ROW) {
UserRecord user;
user.id = sqlite3_column_int(stmt, 0);
user.username = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
user.password_hash = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
user.salt = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
const char* token = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 4));
user.token = token ? token : "";
const char* status_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 5));
user.status = (status_str && std::string(status_str) == "online")
? UserStatus::ONLINE : UserStatus::OFFLINE;
const char* role = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 6));
user.role = role ? role : "";
const char* email = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 7));
user.email = email ? email : "";
user.last_login = sqlite3_column_int64(stmt, 8);
const void* blob = sqlite3_column_blob(stmt, 9);
int blob_size = sqlite3_column_bytes(stmt, 9);
if (blob && blob_size > 0) {
const uint8_t* data = static_cast<const uint8_t*>(blob);
user.avatar_pic.assign(data, data + blob_size);
}
results.push_back(user);
}
sqlite3_finalize(stmt);
return results;
}
std::vector<UserRecord> Database::getAllUsers() {
std::vector<UserRecord> results;
const char* sql = "SELECT id, username, password, salt, token, status, role, email, last_login, avatar_pic FROM users ORDER BY username";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
return results;
}
while (sqlite3_step(stmt) == SQLITE_ROW) {
UserRecord user;
user.id = sqlite3_column_int(stmt, 0);
user.username = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
user.password_hash = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
user.salt = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
const char* token = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 4));
user.token = token ? token : "";
const char* status_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 5));
user.status = (status_str && std::string(status_str) == "online")
? UserStatus::ONLINE : UserStatus::OFFLINE;
const char* role = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 6));
user.role = role ? role : "";
const char* email = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 7));
user.email = email ? email : "";
user.last_login = sqlite3_column_int64(stmt, 8);
const void* blob = sqlite3_column_blob(stmt, 9);
int blob_size = sqlite3_column_bytes(stmt, 9);
if (blob && blob_size > 0) {
const uint8_t* data = static_cast<const uint8_t*>(blob);
user.avatar_pic.assign(data, data + blob_size);
}
results.push_back(user);
}
sqlite3_finalize(stmt);
return results;
}
} // namespace scar