feat(pixelle): 连接凭证 service(settings 读写 + config.yaml 同步)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
boos
2026-06-18 11:51:47 +08:00
parent 96b8d2c399
commit 55188e51ff
2 changed files with 101 additions and 0 deletions
+1
View File
@@ -14,3 +14,4 @@ pub mod runtime_logs;
pub mod shared;
pub mod workspace;
pub mod comfy_cloud;
pub mod pixelle_connection;
+100
View File
@@ -0,0 +1,100 @@
use crate::error::{AppError, AppResult};
use serde::{Deserialize, Serialize};
use sqlx::SqlitePool;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PixelleConnection {
pub runninghub_api_key: Option<String>,
pub comfyui_url: Option<String>,
pub comfyui_api_key: Option<String>,
}
const KEY_RH: &str = "pixelle.runninghub_api_key";
const KEY_URL: &str = "pixelle.comfyui_url";
const KEY_CK: &str = "pixelle.comfyui_api_key";
pub async fn read_connection(db: &SqlitePool) -> AppResult<PixelleConnection> {
async fn get(db: &SqlitePool, k: &str) -> AppResult<Option<String>> {
Ok(sqlx::query_scalar("SELECT value FROM settings WHERE key = ?")
.bind(k).fetch_optional(db).await?)
}
Ok(PixelleConnection {
runninghub_api_key: get(db, KEY_RH).await?,
comfyui_url: get(db, KEY_URL).await?,
comfyui_api_key: get(db, KEY_CK).await?,
})
}
pub async fn save_connection(db: &SqlitePool, conn: &PixelleConnection) -> AppResult<()> {
async fn put(db: &SqlitePool, k: &str, v: &Option<String>) -> AppResult<()> {
if let Some(val) = v {
sqlx::query("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)")
.bind(k).bind(val).execute(db).await?;
}
Ok(())
}
put(db, KEY_RH, &conn.runninghub_api_key).await?;
put(db, KEY_URL, &conn.comfyui_url).await?;
put(db, KEY_CK, &conn.comfyui_api_key).await?;
Ok(())
}
/// 改 config.yaml 的 comfyui.{runninghub_api_key,comfyui_url,comfyui_api_key} 三键,其余保留。
/// 改前备份到 <path>.bak-<ts>。返回备份路径。
pub fn sync_to_pixelle(config_path: &str, conn: &PixelleConnection) -> AppResult<String> {
let content = std::fs::read_to_string(config_path)
.map_err(|e| AppError::ServiceUnavailable(format!("读取 Pixelle config 失败: {e}")))?;
let mut doc: serde_yaml::Value = serde_yaml::from_str(&content)
.map_err(|e| AppError::ServiceUnavailable(format!("解析 config.yaml 失败: {e}")))?;
let comfyui = doc.get_mut("comfyui")
.ok_or_else(|| AppError::ServiceUnavailable("config.yaml 缺少 comfyui 段".into()))?;
let map = comfyui.as_mapping_mut()
.ok_or_else(|| AppError::ServiceUnavailable("comfyui 段不是映射".into()))?;
if let Some(v) = &conn.runninghub_api_key {
map.insert("runninghub_api_key".into(), serde_yaml::Value::String(v.clone()));
}
if let Some(v) = &conn.comfyui_url {
map.insert("comfyui_url".into(), serde_yaml::Value::String(v.clone()));
}
if let Some(v) = &conn.comfyui_api_key {
map.insert("comfyui_api_key".into(), serde_yaml::Value::String(v.clone()));
}
let ts = chrono::Utc::now().format("%Y%m%d%H%M%S").to_string();
let backup = format!("{config_path}.bak-{ts}");
std::fs::copy(config_path, &backup)
.map_err(|e| AppError::ServiceUnavailable(format!("备份失败: {e}")))?;
let out = serde_yaml::to_string(&doc)
.map_err(|e| AppError::Internal(anyhow::anyhow!(e)))?;
std::fs::write(config_path, out)
.map_err(|e| AppError::ServiceUnavailable(format!("写入 config 失败: {e}")))?;
Ok(backup)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sync_updates_three_keys_preserves_rest() {
let dir = std::env::temp_dir().join(format!("pxtest_{}", uuid::Uuid::new_v4().simple()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("config.yaml");
std::fs::write(&path,
"project_name: Pixelle-Video\ncomfyui:\n comfyui_url: https://old\n runninghub_api_key: oldkey\n comfyui_api_key: oldck\n runninghub_concurrent_limit: 1\n").unwrap();
let conn = PixelleConnection {
runninghub_api_key: Some("newkey".into()),
comfyui_url: Some("http://new".into()),
comfyui_api_key: Some("newck".into()),
};
let backup = sync_to_pixelle(path.to_str().unwrap(), &conn).unwrap();
assert!(std::path::Path::new(&backup).exists());
let after = std::fs::read_to_string(&path).unwrap();
assert!(after.contains("newkey"));
assert!(after.contains("http://new"));
assert!(after.contains("newck"));
assert!(after.contains("runninghub_concurrent_limit"));
assert!(after.contains("project_name"));
std::fs::remove_dir_all(&dir).ok();
}
}