use rusqlite::{params, Connection}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct DeployInfo { pub id: String, pub project_id: String, pub server_host: Option, pub server_port: Option, pub username: Option, pub password: Option, pub ssh_key_path: Option, pub quick_links: Option, // JSON: [{label, url}] pub notes: Option, pub updated_at: Option, } #[derive(Debug, Deserialize)] pub struct SaveDeployInfoInput { pub server_host: Option, pub server_port: Option, pub username: Option, pub password: Option, pub ssh_key_path: Option, pub quick_links: Option, pub notes: Option, } // ── 业务函数 ────────────────────────────────────────────────────────────────── pub fn get_info(conn: &Connection, project_id: &str) -> Option { conn.query_row( "SELECT id, project_id, server_host, server_port, username, password, ssh_key_path, quick_links, notes, updated_at FROM project_deploy_info WHERE project_id = ?1", params![project_id], |row| { Ok(DeployInfo { id: row.get(0)?, project_id: row.get(1)?, server_host: row.get(2)?, server_port: row.get(3)?, username: row.get(4)?, password: row.get(5)?, ssh_key_path: row.get(6)?, quick_links: row.get(7)?, notes: row.get(8)?, updated_at: row.get(9)?, }) }, ) .ok() } pub fn save_info( conn: &Connection, project_id: &str, input: &SaveDeployInfoInput, ) -> Result { let existing = get_info(conn, project_id); let id = existing .as_ref() .map(|e| e.id.clone()) .unwrap_or_else(|| format!("deploy-{}", uuid::Uuid::new_v4())); conn.execute( "INSERT INTO project_deploy_info (id, project_id, server_host, server_port, username, password, ssh_key_path, quick_links, notes, updated_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, datetime('now')) ON CONFLICT(project_id) DO UPDATE SET server_host = ?3, server_port = ?4, username = ?5, password = ?6, ssh_key_path = ?7, quick_links = ?8, notes = ?9, updated_at = datetime('now')", params![ id, project_id, input.server_host, input.server_port, input.username, input.password, input.ssh_key_path, input.quick_links, input.notes, ], ) .map_err(|e| e.to_string())?; get_info(conn, project_id).ok_or_else(|| "保存后查询失败".to_string()) } // ── Tauri command 薄包装层 ──────────────────────────────────────────────────── #[tauri::command] pub fn get_deploy_info(project_id: String) -> Result, String> { let conn = crate::db::pool().get().map_err(|e| e.to_string())?; Ok(get_info(&conn, &project_id)) } #[tauri::command] pub fn save_deploy_info( project_id: String, input: SaveDeployInfoInput, ) -> Result { let conn = crate::db::pool().get().map_err(|e| e.to_string())?; save_info(&conn, &project_id, &input) } // ── 单元测试 ────────────────────────────────────────────────────────────────── #[cfg(test)] mod tests { use super::*; use rusqlite::Connection; fn setup() -> Connection { let conn = Connection::open_in_memory().unwrap(); conn.execute_batch( "CREATE TABLE project_deploy_info ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL UNIQUE, server_host TEXT, server_port TEXT DEFAULT '22', username TEXT, password TEXT, ssh_key_path TEXT, quick_links TEXT, notes TEXT, updated_at TEXT DEFAULT (datetime('now')) );", ) .unwrap(); conn } #[test] fn test_get_empty() { let conn = setup(); assert!(get_info(&conn, "proj-1").is_none()); } #[test] fn test_save_and_get() { let conn = setup(); let input = SaveDeployInfoInput { server_host: Some("192.168.1.100".to_string()), server_port: Some("22".to_string()), username: Some("root".to_string()), password: Some("secret".to_string()), ssh_key_path: None, quick_links: Some(r#"[{"label":"控制台","url":"https://example.com"}]"#.to_string()), notes: Some("测试服务器".to_string()), }; let info = save_info(&conn, "proj-1", &input).unwrap(); assert_eq!(info.server_host.as_deref(), Some("192.168.1.100")); assert_eq!(info.username.as_deref(), Some("root")); let fetched = get_info(&conn, "proj-1").unwrap(); assert_eq!(fetched.password.as_deref(), Some("secret")); } #[test] fn test_upsert_overwrites() { let conn = setup(); let input1 = SaveDeployInfoInput { server_host: Some("1.1.1.1".to_string()), server_port: Some("22".to_string()), username: Some("admin".to_string()), password: None, ssh_key_path: None, quick_links: None, notes: None, }; save_info(&conn, "proj-1", &input1).unwrap(); let input2 = SaveDeployInfoInput { server_host: Some("2.2.2.2".to_string()), server_port: Some("2222".to_string()), username: Some("deploy".to_string()), password: Some("new-pass".to_string()), ssh_key_path: None, quick_links: None, notes: None, }; let info = save_info(&conn, "proj-1", &input2).unwrap(); assert_eq!(info.server_host.as_deref(), Some("2.2.2.2")); assert_eq!(info.username.as_deref(), Some("deploy")); } }