Backend: Server connection/config/admins DB queries, server API routes with auth-gated endpoints (overview, config CRUD, admin management). Frontend: Server store wired to API, dashboard fetches server data on mount with live status indicators, uptime formatting, and server config display. Logout now redirects to /login. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
179 lines
5.3 KiB
Rust
179 lines
5.3 KiB
Rust
use sqlx::PgPool;
|
|
use uuid::Uuid;
|
|
use anyhow::Result;
|
|
|
|
use crate::models::server::{ServerConnection, ServerConfig, GameAdmin};
|
|
|
|
/// Get the server connection for a license.
|
|
pub async fn get_server_connection(pool: &PgPool, license_id: Uuid) -> Result<Option<ServerConnection>> {
|
|
let conn = sqlx::query_as::<_, ServerConnection>(
|
|
"SELECT id, license_id, connection_type, panel_api_endpoint, panel_api_key_encrypted, \
|
|
panel_server_identifier, companion_agent_token, companion_last_seen, plugin_last_seen, \
|
|
server_ip, server_port, game_port, connection_status, created_at, updated_at \
|
|
FROM server_connections WHERE license_id = $1",
|
|
)
|
|
.bind(license_id)
|
|
.fetch_optional(pool)
|
|
.await?;
|
|
|
|
Ok(conn)
|
|
}
|
|
|
|
/// Create a new server connection.
|
|
pub async fn create_server_connection(
|
|
pool: &PgPool,
|
|
license_id: Uuid,
|
|
connection_type: &str,
|
|
server_ip: Option<&str>,
|
|
server_port: Option<i32>,
|
|
game_port: Option<i32>,
|
|
) -> Result<Uuid> {
|
|
let row: (Uuid,) = sqlx::query_as(
|
|
"INSERT INTO server_connections (license_id, connection_type, server_ip, server_port, game_port) \
|
|
VALUES ($1, $2, $3, $4, $5) RETURNING id",
|
|
)
|
|
.bind(license_id)
|
|
.bind(connection_type)
|
|
.bind(server_ip)
|
|
.bind(server_port)
|
|
.bind(game_port)
|
|
.fetch_one(pool)
|
|
.await?;
|
|
|
|
Ok(row.0)
|
|
}
|
|
|
|
/// Update server connection status.
|
|
pub async fn update_connection_status(pool: &PgPool, license_id: Uuid, status: &str) -> Result<()> {
|
|
sqlx::query(
|
|
"UPDATE server_connections SET connection_status = $1, updated_at = NOW() WHERE license_id = $2",
|
|
)
|
|
.bind(status)
|
|
.bind(license_id)
|
|
.execute(pool)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Get the server config for a license.
|
|
pub async fn get_server_config(pool: &PgPool, license_id: Uuid) -> Result<Option<ServerConfig>> {
|
|
let config = sqlx::query_as::<_, ServerConfig>(
|
|
"SELECT id, license_id, server_name, max_players, world_size, current_seed, \
|
|
current_map_id, server_description, server_url, server_header_image, tags, \
|
|
auto_restart_enabled, auto_restart_cron, auto_restart_timezone, \
|
|
crash_recovery_enabled, crash_recovery_max_attempts, crash_recovery_cooldown_minutes, \
|
|
force_wipe_eligible, auto_update_on_force_wipe, config_overrides, \
|
|
created_at, updated_at \
|
|
FROM server_config WHERE license_id = $1",
|
|
)
|
|
.bind(license_id)
|
|
.fetch_optional(pool)
|
|
.await?;
|
|
|
|
Ok(config)
|
|
}
|
|
|
|
/// Create a default server config for a license.
|
|
pub async fn create_server_config(
|
|
pool: &PgPool,
|
|
license_id: Uuid,
|
|
server_name: &str,
|
|
) -> Result<Uuid> {
|
|
let row: (Uuid,) = sqlx::query_as(
|
|
"INSERT INTO server_config (license_id, server_name) VALUES ($1, $2) RETURNING id",
|
|
)
|
|
.bind(license_id)
|
|
.bind(server_name)
|
|
.fetch_one(pool)
|
|
.await?;
|
|
|
|
Ok(row.0)
|
|
}
|
|
|
|
/// Update server config fields.
|
|
pub async fn update_server_config(
|
|
pool: &PgPool,
|
|
license_id: Uuid,
|
|
server_name: Option<&str>,
|
|
max_players: Option<i32>,
|
|
world_size: Option<i32>,
|
|
current_seed: Option<i32>,
|
|
) -> Result<()> {
|
|
// Dynamic update — only modify provided fields
|
|
if let Some(name) = server_name {
|
|
sqlx::query("UPDATE server_config SET server_name = $1, updated_at = NOW() WHERE license_id = $2")
|
|
.bind(name)
|
|
.bind(license_id)
|
|
.execute(pool)
|
|
.await?;
|
|
}
|
|
if let Some(mp) = max_players {
|
|
sqlx::query("UPDATE server_config SET max_players = $1, updated_at = NOW() WHERE license_id = $2")
|
|
.bind(mp)
|
|
.bind(license_id)
|
|
.execute(pool)
|
|
.await?;
|
|
}
|
|
if let Some(ws) = world_size {
|
|
sqlx::query("UPDATE server_config SET world_size = $1, updated_at = NOW() WHERE license_id = $2")
|
|
.bind(ws)
|
|
.bind(license_id)
|
|
.execute(pool)
|
|
.await?;
|
|
}
|
|
if let Some(seed) = current_seed {
|
|
sqlx::query("UPDATE server_config SET current_seed = $1, updated_at = NOW() WHERE license_id = $2")
|
|
.bind(seed)
|
|
.bind(license_id)
|
|
.execute(pool)
|
|
.await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Get all game admins for a license.
|
|
pub async fn get_game_admins(pool: &PgPool, license_id: Uuid) -> Result<Vec<GameAdmin>> {
|
|
let admins = sqlx::query_as::<_, GameAdmin>(
|
|
"SELECT id, license_id, steam_id, display_name, admin_level, permissions, added_by, created_at \
|
|
FROM game_admins WHERE license_id = $1 ORDER BY created_at",
|
|
)
|
|
.bind(license_id)
|
|
.fetch_all(pool)
|
|
.await?;
|
|
|
|
Ok(admins)
|
|
}
|
|
|
|
/// Add a game admin.
|
|
pub async fn create_game_admin(
|
|
pool: &PgPool,
|
|
license_id: Uuid,
|
|
steam_id: &str,
|
|
display_name: &str,
|
|
admin_level: &str,
|
|
added_by: Uuid,
|
|
) -> Result<Uuid> {
|
|
let row: (Uuid,) = sqlx::query_as(
|
|
"INSERT INTO game_admins (license_id, steam_id, display_name, admin_level, added_by) \
|
|
VALUES ($1, $2, $3, $4, $5) RETURNING id",
|
|
)
|
|
.bind(license_id)
|
|
.bind(steam_id)
|
|
.bind(display_name)
|
|
.bind(admin_level)
|
|
.bind(added_by)
|
|
.fetch_one(pool)
|
|
.await?;
|
|
|
|
Ok(row.0)
|
|
}
|
|
|
|
/// Remove a game admin.
|
|
pub async fn delete_game_admin(pool: &PgPool, admin_id: Uuid) -> Result<()> {
|
|
sqlx::query("DELETE FROM game_admins WHERE id = $1")
|
|
.bind(admin_id)
|
|
.execute(pool)
|
|
.await?;
|
|
Ok(())
|
|
}
|