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

View File

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