[Refactor] Abstract database logic with Repository classes.

This commit is contained in:
Ritchie Cunningham 2025-10-10 20:17:20 +01:00
parent e156eb6391
commit 2a50d937ea
12 changed files with 260 additions and 105 deletions

View File

@ -1,8 +1,13 @@
#include "database_manager.h" #include "database_manager.h"
#include <memory>
#include "vfs.h" #include "vfs.h"
DatabaseManager::DatabaseManager(const std::string& db_path) : _db(db_path) { DatabaseManager::DatabaseManager(const std::string& db_path) :
/* db is opened in the construtor's init list. */ _db(db_path) {
_player_repository = std::make_unique<PlayerRepository>(_db);
_machine_repository = std::make_unique<MachineRepository>(_db);
_service_repository = std::make_unique<ServiceRepository>(_db);
_vfs_repository = std::make_unique<VFSRepository>(_db);
} }
DatabaseManager::~DatabaseManager(void) { DatabaseManager::~DatabaseManager(void) {
@ -49,51 +54,33 @@ bool DatabaseManager::create_player(const std::string& username, const std::stri
try { try {
_db << "BEGIN;"; _db << "BEGIN;";
/* Create the player. */ player_id = _player_repository->create(username, password, hostname);
_db << "INSERT INTO players (username, password, hostname) VALUES (?, ?, ?);"
<< username
<< password
<< hostname;
player_id = _db.last_insert_rowid();
/* Create the home machine. */ /* Create the home machine. */
/* TODO: Implement real IP allication. */ /* TODO: Implement real IP allication. */
std::string ip_address = "192.168.1." + std::to_string(player_id); std::string ip_address = "192.168.1." + std::to_string(player_id);
_db << "INSERT INTO machines (owner_id, hostname, ip_address) VALUES (?, ?, ?);" machine_id = _machine_repository->create(player_id, hostname, ip_address);
<< player_id << hostname << ip_address;
machine_id = _db.last_insert_rowid();
/* Link player to their new machine. */ _player_repository->set_home_machine_id(player_id, machine_id);
_db << "UPDATE players SET home_machine_id = ? WHERE id = ?;"
<< machine_id << player_id;
/* Create the root dir for the new machine's VFS. */ /* Create the root dir for the new machine's VFS. */
_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type) VALUES(?, NULL, ?, 1);" long long root_id = _vfs_repository->create_node(machine_id, nullptr, "/", DIR_NODE);
<< machine_id << "/";
long long root_id = _db.last_insert_rowid();
/* Create default subdirs. */ /* Create default subdirs. */
_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type) VALUES (?, ?, ?, 1);" _vfs_repository->create_node(machine_id, &root_id, "home", DIR_NODE);
<< machine_id << root_id << "home"; _vfs_repository->create_node(machine_id, &root_id, "etc", DIR_NODE);
_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type) VALUES (?, ?, ?, 1);"
<< machine_id << root_id << "etc";
/* Create /bing and get it's ID */ /* Create /bin and get it's ID */
_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type) VALUES(?, ?, ?, 1);" long long bin_id = _vfs_repository->create_node(machine_id, &root_id, "bin", DIR_NODE);
<< machine_id << root_id << "bin";
long long bin_id = _db.last_insert_rowid();
/* Copy scripts from template into new machine's /bin */ /* Copy scripts from template into new machine's /bin */
vfs_node* template_bin = vfs_template->children["bin"]; vfs_node* template_bin = vfs_template->children["bin"];
for(auto const& [name, node] : template_bin->children) { for(auto const& [name, node] : template_bin->children) {
_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type, content) " _vfs_repository->create_node(machine_id, &bin_id, name, FILE_NODE, node->content);
"VALUES (?, ?, ?, ?, ?);"
<< machine_id << bin_id << name << FILE_NODE <<node->content;
} }
/* Add default SSH service. */ /* Add default SSH service. */
_db << "INSERT INTO services (machine_id, port, name) VALUES (?, ?, ?);" _service_repository->create(machine_id, 22, "SSH");
<< machine_id << 22 << "SSH";
_db << "COMMIT"; _db << "COMMIT";
} catch(const std::exception& e) { } catch(const std::exception& e) {
@ -102,21 +89,3 @@ bool DatabaseManager::create_player(const std::string& username, const std::stri
} }
return true; return true;
} }
bool DatabaseManager::auth_player(const std::string& username, const std::string& password) {
bool authed = false;
_db << "SELECT id FROM players WHERE username = ? AND password = ?;"
<< username
<< password
>> [&](long long id) { authed = true; };
return authed;
}
long long DatabaseManager::get_player_home_machine_id(const std::string& username) {
long long machine_id = -1; /* Return -1 if not found. */
_db << "SELECT home_machine_id FROM players WHERE username = ?;"
<< username
>> machine_id;
return machine_id;
}

View File

@ -1,9 +1,14 @@
#pragma once #pragma once
#include <string> #include <string>
#include <memory>
#include "service_repository.h"
#include "player_repository.h"
#include "machine_repository.h"
#include "vfs_repository.h"
#include "sqlite_modern_cpp.h"
#include "vfs.h" #include "vfs.h"
#include "db.h"
class DatabaseManager { class DatabaseManager {
public: public:
@ -16,10 +21,16 @@ public:
bool create_player(const std::string& username, const std::string& password, bool create_player(const std::string& username, const std::string& password,
const std::string& hostname, vfs_node* vfs_template); const std::string& hostname, vfs_node* vfs_template);
/* Return true if creds are valid. */ PlayerRepository& players(void) { return *_player_repository; }
bool auth_player(const std::string& username, const std::string& password); MachineRepository& machines(void) { return *_machine_repository; }
ServiceRepository& services(void) { return *_service_repository; }
long long get_player_home_machine_id(const std::string& username); VFSRepository& vfs(void) { return *_vfs_repository; }
sqlite::database _db; sqlite::database _db;
private:
std::unique_ptr<PlayerRepository> _player_repository;
std::unique_ptr<MachineRepository> _machine_repository;
std::unique_ptr<ServiceRepository> _service_repository;
std::unique_ptr<VFSRepository> _vfs_repository;
}; };

View File

@ -0,0 +1,32 @@
#include "machine_repository.h"
#include "sqlite_modern_cpp.h"
MachineRepository::MachineRepository(sqlite::database& db) : _db(db) {}
long long MachineRepository::create(std::optional<long long> owner_id,
const std::string& hostname,
const std::string& ip_address) {
if(owner_id.has_value()) {
_db << "INSERT INTO machines (owner_id, hostname, ip_address) VALUES(?, ?, ?);"
<< owner_id.value() << hostname << ip_address;
} else {
_db << "INSERT INTO machines (owner_id, hostname, ip_address) VALUES (NULL, ?, ?);"
<< hostname << ip_address;
}
return _db.last_insert_rowid();
}
int MachineRepository::get_npc_count(void) {
int count = 0;
_db << "SELECT count(*) FROM machines WHERE owner_id IS NULL;" >> count;
return count;
}
std::vector<MachineData> MachineRepository::get_all_npcs(void) {
std::vector<MachineData> machines;
_db << "SELECT id, ip_address FROM machines WHERE owner_id IS NULL;"
>> [&](long long id, std::string ip_address) {
machines.push_back({id, ip_address});
};
return machines;
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <string>
#include <vector>
#include <optional>
#include "sqlite_modern_cpp.h"
/* Struct to hold machine data. */
struct MachineData {
long long id;
std::string ip_address;
};
class MachineRepository {
public:
MachineRepository(sqlite::database& db);
long long create(std::optional<long long> owner_id, const std::string& hostname,
const std::string& ip_address);
int get_npc_count(void);
std::vector<MachineData> get_all_npcs(void);
private:
sqlite::database& _db;
};

View File

@ -0,0 +1,34 @@
#include "player_repository.h"
PlayerRepository::PlayerRepository(sqlite::database& db) : _db(db) {}
long long PlayerRepository::create(const std::string& username, const std::string& password,
const std::string& hostname) {
_db << "INSERT INTO players (username, password, hostname) VALUES (?, ?, ?);"
<< username
<< password
<< hostname;
return _db.last_insert_rowid();
}
bool PlayerRepository::authenticate(const std::string& username, const std::string& password) {
bool authed = false;
_db << "SELECT id FROM players WHERE username = ? AND password = ?;"
<< username
<< password
>> [&](long long id) {authed = true;};
return authed;
}
long long PlayerRepository::get_home_machine_id(const std::string& username) {
long long machine_id = -1;
_db << "SELECT home_machine_id FROM players WHERE username = ?;"
<< username
>> machine_id;
return machine_id;
}
void PlayerRepository::set_home_machine_id(long long player_id, long long machine_id) {
_db << "UPDATE players SET home_machine_id = ? WHERE id = ?;"
<< machine_id << player_id;
}

View File

@ -0,0 +1,19 @@
#pragma once
#include <string>
#include "sqlite_modern_cpp.h"
class PlayerRepository {
public:
PlayerRepository(sqlite::database& db);
long long create(const std::string& username, const std::string& password,
const std::string& hostname);
bool authenticate(const std::string& username, const std::string& password);
long long get_home_machine_id(const std::string& username);
void set_home_machine_id(long long player_id, long long machine_id);
private:
sqlite::database& _db;
};

View File

@ -0,0 +1,19 @@
#include "service_repository.h"
#include "sqlite_modern_cpp.h"
ServiceRepository::ServiceRepository(sqlite::database& db) : _db(db) {}
void ServiceRepository::create(long long machine_id, int port, const std::string& name) {
_db << "INSERT INTO services (machine_id, port, name) VALUES (?, ?, ?);"
<< machine_id << port << name;
}
std::map<int, std::string> ServiceRepository::get_for_machine(long long machine_id) {
std::map<int, std::string> services;
_db << "SELECT port, name FROM services WHERE machine_id = ?;"
<< machine_id
>> [&](int port, std::string name) {
services[port] = name;
};
return services;
}

View File

@ -0,0 +1,17 @@
#pragma once
#include <string>
#include <map>
#include "sqlite_modern_cpp.h"
class ServiceRepository {
public:
ServiceRepository(sqlite::database& db);
void create(long long machine_id, int port, const std::string& name);
std::map<int, std::string> get_for_machine(long long machine_id);
private:
sqlite::database& _db;
};

View File

@ -0,0 +1,35 @@
#include "vfs_repository.h"
VFSRepository::VFSRepository(sqlite::database& db) : _db(db) {}
long long VFSRepository::create_node(long long machine_id, long long* parent_id,
const std::string& name, vfs_node_type type,
const std::string& content) {
if(parent_id) {
_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type, content) "
"VALUES (?, ?, ?, ?, ?);"
<< machine_id << *parent_id << name << type << content;
} else {
_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type, content) "
"VALUES (?, NULL, ?, ?, ?);"
<< machine_id << name << type << content;
}
return _db.last_insert_rowid();
}
std::vector<vfs_node*> VFSRepository::get_nodes_for_machine(long long machine_id) {
std::vector<vfs_node*> nodes;
_db << "SELECT id, parent_id, name, type, content FROM vfs_nodes WHERE machine_id = ?;"
<< machine_id
>> [&](long long id, long long parent_id, std::string name, int type, std::string content) {
vfs_node* node = new vfs_node();
node->id = id;
node->parent_id = parent_id;
node->name = name;
node->type = (vfs_node_type) type;
node->content = content;
nodes.push_back(node);
};
return nodes;
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <string>
#include <vector>
#include "vfs.h"
#include "sqlite_modern_cpp.h"
class VFSRepository {
public:
VFSRepository(sqlite::database& db);
long long create_node(long long machine_id, long long* parent_id,
const std::string& name, vfs_node_type type,
const std::string& content = "");
std::vector<vfs_node*> get_nodes_for_machine(long long machine_id);
private:
sqlite::database& _db;
};

View File

@ -107,32 +107,21 @@ Machine* MachineManager::load_machine(long long machine_id, DatabaseManager* db_
Machine* machine = new Machine(machine_id, hostname); Machine* machine = new Machine(machine_id, hostname);
/* Load all VFS nodes for this machine from the database. */ /* Load all VFS nodes for this machine from the database. */
std::map<long long, vfs_node*> nodes; std::map<long long, vfs_node*> node_map;
vfs_node* root = nullptr; vfs_node* root = nullptr;
db_manager->_db << "SELECT id, parent_id, name, type, content FROM vfs_nodes WHERE machine_id = ?;" auto nodes = db_manager->vfs().get_nodes_for_machine(machine_id);
<< machine_id for(vfs_node* node : nodes) {
>> [&](long long id, long long parent_id, std::string name, int type, std::string content) { node_map[node->id] = node;
vfs_node* node = new vfs_node(); if(node->name == "/") {
node->id = id;
node->parent_id = parent_id; /* Store temp id for tree building. */
node->name = name;
node->type = (vfs_node_type)type;
node->content = content;
nodes[id] = node;
if(name == "/") {
root = node; root = node;
} }
}; }
db_manager->_db << "SELECT port, name FROM services WHERE machine_id = ?;" machine->services = _db_manager->services().get_for_machine(machine_id);
<< machine_id
>> [&](int port, std::string name) {
machine->services[port] = name;
};
if(root) { if(root) {
build_tree(root, nodes); build_tree(root, node_map);
machine->vfs_root = root; machine->vfs_root = root;
} }
return machine; return machine;

View File

@ -23,10 +23,11 @@ NetworkManager::NetworkManager(const std::string& db_path) :
_seed_npc_machines(); _seed_npc_machines();
/* Load NPC machines from the database. */ /* Load NPC machines from the database. */
_db_manager->_db << "SELECT id, ip_address FROM machines WHERE owner_id IS NULL;" auto npc_machines = _db_manager->machines().get_all_npcs();
>> [&](long long id, std::string ip_address) { for(const auto& machine_data : npc_machines) {
_world_machines[ip_address] = _machine_manager.load_machine(id, _db_manager.get()); _world_machines[machine_data.ip_address] =
}; _machine_manager.load_machine(machine_data.id, _db_manager.get());
}
fprintf(stderr, "Created world with %zu networks\n", _world_machines.size()); fprintf(stderr, "Created world with %zu networks\n", _world_machines.size());
} }
@ -133,7 +134,7 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
if(parts.size() == 3) { if(parts.size() == 3) {
if(_db_manager->create_player(parts[0], parts[1], parts[2], if(_db_manager->create_player(parts[0], parts[1], parts[2],
_machine_manager.get_vfs_template())) { _machine_manager.get_vfs_template())) {
long long machine_id = _db_manager->get_player_home_machine_id(parts[0]); long long machine_id = _db_manager->players().get_home_machine_id(parts[0]);
Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get()); Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get());
delete player->cmd_processor; /* Delete old command processor. */ delete player->cmd_processor; /* Delete old command processor. */
player->cmd_processor = new CommandProcessor(home_machine, _world_machines, player->cmd_processor = new CommandProcessor(home_machine, _world_machines,
@ -152,8 +153,8 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
std::string payload = message.substr(7); std::string payload = message.substr(7);
auto parts = split_message(payload, "::"); auto parts = split_message(payload, "::");
if(parts.size() == 2) { if(parts.size() == 2) {
if(_db_manager->auth_player(parts[0], parts[1])) { if(_db_manager->players().authenticate(parts[0], parts[1])) {
long long machine_id = _db_manager->get_player_home_machine_id(parts[0]); long long machine_id = _db_manager->players().get_home_machine_id(parts[0]);
printf("DEBUG: Loading machine %lld for player %s\n", machine_id, parts[0].c_str()); printf("DEBUG: Loading machine %lld for player %s\n", machine_id, parts[0].c_str());
Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get()); Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get());
delete player->cmd_processor; /* Delete old command processor. */ delete player->cmd_processor; /* Delete old command processor. */
@ -226,8 +227,7 @@ void NetworkManager::on_disconnect(std::shared_ptr<net::TcpConnection> connectio
} }
void NetworkManager::_seed_npc_machines(void) { void NetworkManager::_seed_npc_machines(void) {
int npc_count = 0; int npc_count = _db_manager->machines().get_npc_count();
_db_manager->_db << "SELECT count(*) FROM machines WHERE owner_id IS NULL;" >> npc_count;
if(npc_count > 0) { if(npc_count > 0) {
return; /* NPC already in db. */ return; /* NPC already in db. */
@ -248,38 +248,22 @@ void NetworkManager::_seed_npc_machines(void) {
try { try {
for(const auto& def : npc_defs) { for(const auto& def : npc_defs) {
_db_manager->_db << "INSERT INTO machines (hostname, ip_address) " long long machine_id = _db_manager->machines().create({}, def.hostname, def.ip);
"VALUES(?, ?);"
<< def.hostname << def.ip;
long long machine_id = _db_manager->_db.last_insert_rowid();
/* Create a basic VFS for the NPC machines. */ /* Create a basic VFS for the NPC machines. */
_db_manager->_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type) " long long root_id = _db_manager->vfs().create_node(machine_id, nullptr, "/", DIR_NODE);
"VALUES (?, NULL, ?, 1);"
<< machine_id << "/";
long long root_id = _db_manager->_db.last_insert_rowid();
_db_manager->_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type) " _db_manager->vfs().create_node(machine_id, &root_id, "etc", DIR_NODE);
"VALUES (?, ?, ?, 1);"
<< machine_id << root_id << "etc";
long long etc_id = _db_manager->_db.last_insert_rowid();
_db_manager->_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type) " long long bin_id = _db_manager->vfs().create_node(machine_id, &root_id, "bin", DIR_NODE);
"VALUES (?, ?, ?, 1);"
<< machine_id << root_id << "bin";
long long bin_id = _db_manager->_db.last_insert_rowid();
vfs_node* template_bin = _machine_manager.get_vfs_template()->children["bin"]; vfs_node* template_bin = _machine_manager.get_vfs_template()->children["bin"];
for(auto const& [name, node] : template_bin->children) { for(auto const& [name, node] : template_bin->children) {
_db_manager->_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type, content) " _db_manager->vfs().create_node(machine_id, &bin_id, name, FILE_NODE, node->content);
"VALUES(?, ?, ?, ?, ?);"
<< machine_id << bin_id << name << FILE_NODE << node->content;
} }
for(const auto& service : def.services) { for(const auto& service : def.services) {
_db_manager->_db << "INSERT INTO services (machine_id, port, name) " _db_manager->services().create(machine_id, service.first, service.second);
"VALUES (?, ?, ?);"
<< machine_id << service.first << service.second;
} }
} }
} catch(const std::exception& e) { } catch(const std::exception& e) {