use rusqlite::{params, Connection}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct HostApp { pub id: String, pub server_id: String, pub name: String, pub deploy_type: String, pub service_name: Option, pub binary_path: Option, pub config_path: Option, pub log_path: Option, pub port: Option, pub start_cmd: Option, pub stop_cmd: Option, pub restart_cmd: Option, pub status_cmd: Option, pub notes: Option, pub sort_order: Option, pub created_at: Option, pub updated_at: Option, } #[derive(Debug, Deserialize)] pub struct SaveHostAppInput { pub name: String, pub deploy_type: String, pub service_name: Option, pub binary_path: Option, pub config_path: Option, pub log_path: Option, pub port: Option, pub start_cmd: Option, pub stop_cmd: Option, pub restart_cmd: Option, pub status_cmd: Option, pub notes: Option, pub sort_order: Option, } const COLS: &str = "id, server_id, name, deploy_type, service_name, binary_path, config_path, log_path, port, start_cmd, stop_cmd, restart_cmd, status_cmd, notes, sort_order, created_at, updated_at"; fn row_to_app(row: &rusqlite::Row<'_>) -> rusqlite::Result { Ok(HostApp { id: row.get(0)?, server_id: row.get(1)?, name: row.get(2)?, deploy_type: row.get(3)?, service_name: row.get(4)?, binary_path: row.get(5)?, config_path: row.get(6)?, log_path: row.get(7)?, port: row.get(8)?, start_cmd: row.get(9)?, stop_cmd: row.get(10)?, restart_cmd: row.get(11)?, status_cmd: row.get(12)?, notes: row.get(13)?, sort_order: row.get(14)?, created_at: row.get(15)?, updated_at: row.get(16)?, }) } pub fn list_host_apps(conn: &Connection, server_id: &str) -> Vec { let sql = format!("SELECT {COLS} FROM server_host_apps WHERE server_id = ?1 ORDER BY sort_order ASC, created_at ASC"); let mut stmt = match conn.prepare(&sql) { Ok(s) => s, Err(_) => return vec![], }; stmt.query_map(params![server_id], row_to_app) .map(|iter| iter.filter_map(|r| r.ok()).collect()) .unwrap_or_default() } pub fn get_host_app(conn: &Connection, id: &str) -> Option { let sql = format!("SELECT {COLS} FROM server_host_apps WHERE id = ?1"); conn.query_row(&sql, params![id], row_to_app).ok() } pub fn create_host_app( conn: &Connection, server_id: &str, input: &SaveHostAppInput, ) -> Result { let id = format!("ha-{}", uuid::Uuid::new_v4()); conn.execute( "INSERT INTO server_host_apps (id, server_id, name, deploy_type, service_name, binary_path, config_path, log_path, port, start_cmd, stop_cmd, restart_cmd, status_cmd, notes, sort_order) VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15)", params![id, server_id, input.name, input.deploy_type, input.service_name, input.binary_path, input.config_path, input.log_path, input.port, input.start_cmd, input.stop_cmd, input.restart_cmd, input.status_cmd, input.notes, input.sort_order.unwrap_or(0)], ).map_err(|e| e.to_string())?; get_host_app(conn, &id).ok_or_else(|| "创建后查询失败".into()) } pub fn update_host_app( conn: &Connection, id: &str, input: &SaveHostAppInput, ) -> Result { let rows = conn .execute( "UPDATE server_host_apps SET name=?1, deploy_type=?2, service_name=?3, binary_path=?4, config_path=?5, log_path=?6, port=?7, start_cmd=?8, stop_cmd=?9, restart_cmd=?10, status_cmd=?11, notes=?12, sort_order=?13, updated_at=datetime('now') WHERE id=?14", params![ input.name, input.deploy_type, input.service_name, input.binary_path, input.config_path, input.log_path, input.port, input.start_cmd, input.stop_cmd, input.restart_cmd, input.status_cmd, input.notes, input.sort_order.unwrap_or(0), id ], ) .map_err(|e| e.to_string())?; if rows == 0 { return Err(format!("应用 {} 不存在", id)); } get_host_app(conn, id).ok_or_else(|| "更新后查询失败".into()) } pub fn delete_host_app_fn(conn: &Connection, id: &str) -> Result<(), String> { conn.execute("DELETE FROM server_host_apps WHERE id=?1", params![id]) .map_err(|e| e.to_string())?; Ok(()) } pub fn delete_apps_by_server(conn: &Connection, server_id: &str) -> Result<(), String> { conn.execute( "DELETE FROM server_host_apps WHERE server_id=?1", params![server_id], ) .map_err(|e| e.to_string())?; Ok(()) } // ── Tauri commands ─────────────────────────────────────────────────────────── #[tauri::command] pub fn get_host_apps(server_id: String) -> Result, String> { let conn = crate::db::pool().get().map_err(|e| e.to_string())?; Ok(list_host_apps(&conn, &server_id)) } #[tauri::command] pub fn add_host_app(server_id: String, input: SaveHostAppInput) -> Result { let conn = crate::db::pool().get().map_err(|e| e.to_string())?; create_host_app(&conn, &server_id, &input) } #[tauri::command] pub fn edit_host_app(id: String, input: SaveHostAppInput) -> Result { let conn = crate::db::pool().get().map_err(|e| e.to_string())?; update_host_app(&conn, &id, &input) } #[tauri::command] pub fn delete_host_app(id: String) -> Result<(), String> { let conn = crate::db::pool().get().map_err(|e| e.to_string())?; delete_host_app_fn(&conn, &id) }