387 lines
13 KiB
C++
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
|
||
|
|
|