scar-chat7/dbmanager/db_manager.cpp
2025-12-07 12:00:44 -07:00

289 lines
9.2 KiB
C++

#include "db_manager.h"
#include "../shared/crypto/argon2_wrapper.h"
#include <iostream>
#include <fstream>
#include <filesystem>
#include <iomanip>
#include <ctime>
namespace scar {
DBManager::DBManager(const std::string& db_path) {
db_ = std::make_unique<Database>(db_path);
if (!db_->initialize()) {
throw std::runtime_error("Failed to initialize database: " + db_path);
}
}
bool DBManager::addUser(const std::string& username, const std::string& password,
const std::string& avatar) {
// Check if user already exists
auto existing_user = db_->getUserByUsername(username);
if (existing_user) {
std::cerr << "Error: User '" << username << "' already exists" << std::endl;
return false;
}
// Generate random salt
std::string salt = Argon2Wrapper::generateSalt();
// Hash password with salt
std::string password_hash = Argon2Wrapper::hashPassword(password, salt);
// Create user
if (!db_->createUser(username, password_hash, salt)) {
std::cerr << "Error: Failed to create user" << std::endl;
return false;
}
// Set avatar if provided
if (!avatar.empty()) {
modifyAvatar(username, avatar);
}
std::cout << "User '" << username << "' created successfully" << std::endl;
std::cout << " Salt: " << salt << std::endl;
std::cout << " Hash: " << password_hash.substr(0, 32) << "..." << std::endl;
return true;
}
bool DBManager::deleteUser(const std::string& username) {
auto user = db_->getUserByUsername(username);
if (!user) {
std::cerr << "Error: User '" << username << "' not found" << std::endl;
return false;
}
// Execute delete
if (!db_->deleteUser(username)) {
std::cerr << "Error: Failed to delete user" << std::endl;
return false;
}
std::cout << "User '" << username << "' deleted successfully" << std::endl;
return true;
}
bool DBManager::modifyPassword(const std::string& username, const std::string& new_password) {
auto user = db_->getUserByUsername(username);
if (!user) {
std::cerr << "Error: User '" << username << "' not found" << std::endl;
return false;
}
// Generate new salt
std::string salt = Argon2Wrapper::generateSalt();
// Hash new password
std::string password_hash = Argon2Wrapper::hashPassword(new_password, salt);
// Update database
if (!db_->updateUserPassword(username, password_hash, salt)) {
std::cerr << "Error: Failed to update password" << std::endl;
return false;
}
std::cout << "Password updated for user '" << username << "'" << std::endl;
return true;
}
bool DBManager::modifyAvatar(const std::string& username, const std::string& avatar_path) {
auto user = db_->getUserByUsername(username);
if (!user) {
std::cerr << "Error: User '" << username << "' not found" << std::endl;
return false;
}
// Read avatar file
auto avatar_data = readAvatarFile(avatar_path);
if (avatar_data.empty()) {
std::cerr << "Error: Failed to read avatar file" << std::endl;
return false;
}
// Update database
if (!db_->updateUserAvatar(username, avatar_data)) {
std::cerr << "Error: Failed to update avatar" << std::endl;
return false;
}
std::cout << "Avatar updated for user '" << username << "' ("
<< avatar_data.size() << " bytes)" << std::endl;
return true;
}
bool DBManager::modifyEmail(const std::string& username, const std::string& email) {
auto user = db_->getUserByUsername(username);
if (!user) {
std::cerr << "Error: User '" << username << "' not found" << std::endl;
return false;
}
if (!db_->updateUserEmail(username, email)) {
std::cerr << "Error: Failed to update email" << std::endl;
return false;
}
std::cout << "Email updated for user '" << username << "'" << std::endl;
return true;
}
bool DBManager::modifyRole(const std::string& username, const std::string& role) {
auto user = db_->getUserByUsername(username);
if (!user) {
std::cerr << "Error: User '" << username << "' not found" << std::endl;
return false;
}
if (!db_->updateUserRole(username, role)) {
std::cerr << "Error: Failed to update role" << std::endl;
return false;
}
std::cout << "Role updated for user '" << username << "'" << std::endl;
return true;
}
void DBManager::fetchUser(const std::string& username) {
auto user = db_->getUserByUsername(username);
if (!user) {
std::cerr << "User '" << username << "' not found" << std::endl;
return;
}
printUserDetails(*user);
}
void DBManager::searchUsers(const std::string& field, const std::string& value) {
auto users = db_->searchUsers(field, value);
if (users.empty()) {
std::cout << "No users found matching " << field << "='" << value << "'" << std::endl;
return;
}
std::cout << "Found " << users.size() << " user(s):" << std::endl;
std::cout << std::string(80, '-') << std::endl;
for (const auto& user : users) {
printUserDetails(user);
std::cout << std::string(80, '-') << std::endl;
}
}
void DBManager::listAllUsers() {
auto users = db_->getAllUsers();
if (users.empty()) {
std::cout << "No users in database" << std::endl;
return;
}
std::cout << "Total users: " << users.size() << std::endl;
std::cout << std::string(100, '=') << std::endl;
// Table header
std::cout << std::left
<< std::setw(20) << "Username"
<< std::setw(10) << "Status"
<< std::setw(25) << "Email"
<< std::setw(15) << "Role"
<< std::setw(20) << "Last Login"
<< std::endl;
std::cout << std::string(100, '-') << std::endl;
for (const auto& user : users) {
std::string status = (user.status == UserStatus::ONLINE) ? "Online" : "Offline";
std::string last_login = "Never";
if (user.last_login > 0) {
std::time_t t = static_cast<std::time_t>(user.last_login);
std::tm* tm = std::localtime(&t);
char buffer[32];
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm);
last_login = buffer;
}
std::cout << std::left
<< std::setw(20) << user.username
<< std::setw(10) << status
<< std::setw(25) << user.email
<< std::setw(15) << user.role
<< std::setw(20) << last_login
<< std::endl;
}
}
std::string DBManager::findDatabase(const std::string& override_path) {
// If path provided, use it
if (!override_path.empty()) {
if (std::filesystem::exists(override_path)) {
return override_path;
}
std::cerr << "Warning: Specified database not found: " << override_path << std::endl;
}
// Check current directory
if (std::filesystem::exists("scarchat.db")) {
return "scarchat.db";
}
// Check install path (would be set by CMake install)
std::string install_path = "/usr/local/share/scarchat/scarchat.db";
if (std::filesystem::exists(install_path)) {
return install_path;
}
// Check user's home directory
const char* home = std::getenv("HOME");
if (home) {
std::string home_path = std::string(home) + "/.local/share/scarchat/scarchat.db";
if (std::filesystem::exists(home_path)) {
return home_path;
}
}
// Default to current directory (will be created if doesn't exist)
return "scarchat.db";
}
void DBManager::printUserDetails(const UserRecord& user) {
std::cout << "User ID: " << user.id << std::endl;
std::cout << "Username: " << user.username << std::endl;
std::cout << "Status: " << (user.status == UserStatus::ONLINE ? "Online" : "Offline") << std::endl;
std::cout << "Email: " << (user.email.empty() ? "(not set)" : user.email) << std::endl;
std::cout << "Role: " << (user.role.empty() ? "(not set)" : user.role) << std::endl;
if (user.last_login > 0) {
std::time_t t = static_cast<std::time_t>(user.last_login);
std::cout << "Last Login: " << std::ctime(&t);
} else {
std::cout << "Last Login: Never" << std::endl;
}
std::cout << "Has Avatar: " << (user.avatar_pic.empty() ? "No" : "Yes") << std::endl;
std::cout << "Token: " << (user.token.empty() ? "(none)" : user.token.substr(0, 20) + "...") << std::endl;
std::cout << "Salt: " << user.salt << std::endl;
std::cout << "Password Hash: " << user.password_hash.substr(0, 32) << "..." << std::endl;
}
std::vector<uint8_t> DBManager::readAvatarFile(const std::string& path) {
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
return {};
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> buffer(size);
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
return {};
}
return buffer;
}
} // namespace scar