289 lines
9.2 KiB
C++
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
|