From ae7ef10b67737f08a86743f687bca30a12e9e17a Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 12 Jul 2026 19:38:03 +0200 Subject: first stable --- src/db.rs | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/db.rs (limited to 'src/db.rs') diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..7868e3e --- /dev/null +++ b/src/db.rs @@ -0,0 +1,107 @@ +use rusqlite::Connection; +use std::path::Path; +use std::sync::{Arc, Mutex}; + +#[derive(Clone)] +pub struct Database { + conn: Arc>, +} + +// Safe because all access to Connection goes through Mutex::lock() +unsafe impl Sync for Database {} + +impl Database { + pub fn open(path: &Path) -> Result { + let conn = Connection::open(path)?; + conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?; + let db = Self { + conn: Arc::new(Mutex::new(conn)), + }; + db.migrate()?; + Ok(db) + } + + fn migrate(&self) -> Result<(), rusqlite::Error> { + let conn = self.conn.lock().unwrap(); + conn.execute_batch( + " + CREATE TABLE IF NOT EXISTS accounts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + password TEXT NOT NULL, + is_superuser INTEGER NOT NULL DEFAULT 0, + password_change_required INTEGER NOT NULL DEFAULT 0 + ); + + CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + account_id INTEGER NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE + ); + + CREATE TABLE IF NOT EXISTS instances ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + version TEXT NOT NULL, + server_type TEXT NOT NULL CHECK(server_type IN ('VANILLA', 'FORGE', 'FABRIC')), + world_source TEXT NOT NULL CHECK(world_source IN ('generate', 'upload')), + world_seed TEXT, + level_type TEXT NOT NULL DEFAULT 'DEFAULT', + has_modpack INTEGER NOT NULL DEFAULT 0, + memory_mb INTEGER NOT NULL, + difficulty INTEGER NOT NULL DEFAULT 1, + gamemode INTEGER NOT NULL DEFAULT 0, + max_players INTEGER NOT NULL DEFAULT 20, + pvp INTEGER NOT NULL DEFAULT 1, + motd TEXT NOT NULL DEFAULT 'A Minecraft Server', + rcon_password TEXT NOT NULL, + is_active INTEGER NOT NULL DEFAULT 0, + container_state TEXT NOT NULL DEFAULT 'stopped', + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')) + ); + + CREATE TABLE IF NOT EXISTS backups ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + instance_id INTEGER NOT NULL, + filename TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE + ); + + CREATE TABLE IF NOT EXISTS whitelist ( + instance_id INTEGER NOT NULL, + username TEXT NOT NULL, + PRIMARY KEY (instance_id, username), + FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE + ); + + CREATE TABLE IF NOT EXISTS ops ( + instance_id INTEGER NOT NULL, + username TEXT NOT NULL, + PRIMARY KEY (instance_id, username), + FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE + ); + + CREATE TABLE IF NOT EXISTS bans ( + instance_id INTEGER NOT NULL, + username TEXT NOT NULL, + ban_type TEXT NOT NULL CHECK(ban_type IN ('user', 'ip')), + PRIMARY KEY (instance_id, username), + FOREIGN KEY (instance_id) REFERENCES instances(id) ON DELETE CASCADE + ); + ", + )?; + + // Migration: add container_state column if missing (for older DBs) + let _ = conn.execute_batch( + "ALTER TABLE instances ADD COLUMN container_state TEXT NOT NULL DEFAULT 'stopped'", + ); + Ok(()) + } + + pub fn conn(&self) -> std::sync::MutexGuard<'_, Connection> { + self.conn.lock().unwrap() + } +} -- cgit v1.2.3