mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-26 06:24:32 +08:00
0f959112b1
Remove the skillsPath field from SkillRepo and Skill structs since recursive scanning now automatically discovers skills in all directories. Simplify the UI by removing the path input field.
243 lines
8.9 KiB
Rust
243 lines
8.9 KiB
Rust
//! JSON → SQLite 数据迁移
|
|
//!
|
|
//! 将旧版 config.json (MultiAppConfig) 数据迁移到 SQLite 数据库。
|
|
|
|
use super::{lock_conn, to_json_string, Database};
|
|
use crate::app_config::MultiAppConfig;
|
|
use crate::error::AppError;
|
|
use rusqlite::{params, Connection};
|
|
|
|
impl Database {
|
|
/// 从 MultiAppConfig 迁移数据到数据库
|
|
pub fn migrate_from_json(&self, config: &MultiAppConfig) -> Result<(), AppError> {
|
|
let mut conn = lock_conn!(self.conn);
|
|
let tx = conn
|
|
.transaction()
|
|
.map_err(|e| AppError::Database(e.to_string()))?;
|
|
|
|
Self::migrate_from_json_tx(&tx, config)?;
|
|
|
|
tx.commit()
|
|
.map_err(|e| AppError::Database(format!("Commit migration failed: {e}")))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// 运行迁移的 dry-run 模式(在内存数据库中验证,不写入磁盘)
|
|
///
|
|
/// 用于部署前验证迁移逻辑是否正确。
|
|
pub fn migrate_from_json_dry_run(config: &MultiAppConfig) -> Result<(), AppError> {
|
|
let mut conn =
|
|
Connection::open_in_memory().map_err(|e| AppError::Database(e.to_string()))?;
|
|
Self::create_tables_on_conn(&conn)?;
|
|
Self::apply_schema_migrations_on_conn(&conn)?;
|
|
|
|
let tx = conn
|
|
.transaction()
|
|
.map_err(|e| AppError::Database(e.to_string()))?;
|
|
Self::migrate_from_json_tx(&tx, config)?;
|
|
|
|
// 显式 drop transaction 而不提交(内存数据库会被丢弃)
|
|
drop(tx);
|
|
Ok(())
|
|
}
|
|
|
|
/// 在事务中执行迁移
|
|
fn migrate_from_json_tx(
|
|
tx: &rusqlite::Transaction<'_>,
|
|
config: &MultiAppConfig,
|
|
) -> Result<(), AppError> {
|
|
// 1. 迁移 Providers
|
|
Self::migrate_providers(tx, config)?;
|
|
|
|
// 2. 迁移 MCP Servers
|
|
Self::migrate_mcp_servers(tx, config)?;
|
|
|
|
// 3. 迁移 Prompts
|
|
Self::migrate_prompts(tx, config)?;
|
|
|
|
// 4. 迁移 Skills
|
|
Self::migrate_skills(tx, config)?;
|
|
|
|
// 5. 迁移 Common Config
|
|
Self::migrate_common_config(tx, config)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 迁移供应商数据
|
|
fn migrate_providers(
|
|
tx: &rusqlite::Transaction<'_>,
|
|
config: &MultiAppConfig,
|
|
) -> Result<(), AppError> {
|
|
for (app_key, manager) in &config.apps {
|
|
let app_type = app_key;
|
|
let current_id = &manager.current;
|
|
|
|
for (id, provider) in &manager.providers {
|
|
let is_current = if id == current_id { 1 } else { 0 };
|
|
|
|
// 处理 meta 和 endpoints
|
|
let mut meta_clone = provider.meta.clone().unwrap_or_default();
|
|
let endpoints = std::mem::take(&mut meta_clone.custom_endpoints);
|
|
|
|
tx.execute(
|
|
"INSERT OR REPLACE INTO providers (
|
|
id, app_type, name, settings_config, website_url, category,
|
|
created_at, sort_index, notes, icon, icon_color, meta, is_current
|
|
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
|
|
params![
|
|
id,
|
|
app_type,
|
|
provider.name,
|
|
to_json_string(&provider.settings_config)?,
|
|
provider.website_url,
|
|
provider.category,
|
|
provider.created_at,
|
|
provider.sort_index,
|
|
provider.notes,
|
|
provider.icon,
|
|
provider.icon_color,
|
|
to_json_string(&meta_clone)?,
|
|
is_current,
|
|
],
|
|
)
|
|
.map_err(|e| AppError::Database(format!("Migrate provider failed: {e}")))?;
|
|
|
|
// 迁移 Endpoints
|
|
for (url, endpoint) in endpoints {
|
|
tx.execute(
|
|
"INSERT INTO provider_endpoints (provider_id, app_type, url, added_at)
|
|
VALUES (?1, ?2, ?3, ?4)",
|
|
params![id, app_type, url, endpoint.added_at],
|
|
)
|
|
.map_err(|e| AppError::Database(format!("Migrate endpoint failed: {e}")))?;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// 迁移 MCP 服务器数据
|
|
fn migrate_mcp_servers(
|
|
tx: &rusqlite::Transaction<'_>,
|
|
config: &MultiAppConfig,
|
|
) -> Result<(), AppError> {
|
|
if let Some(servers) = &config.mcp.servers {
|
|
for (id, server) in servers {
|
|
tx.execute(
|
|
"INSERT OR REPLACE INTO mcp_servers (
|
|
id, name, server_config, description, homepage, docs, tags,
|
|
enabled_claude, enabled_codex, enabled_gemini
|
|
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
|
|
params![
|
|
id,
|
|
server.name,
|
|
to_json_string(&server.server)?,
|
|
server.description,
|
|
server.homepage,
|
|
server.docs,
|
|
to_json_string(&server.tags)?,
|
|
server.apps.claude,
|
|
server.apps.codex,
|
|
server.apps.gemini,
|
|
],
|
|
)
|
|
.map_err(|e| AppError::Database(format!("Migrate mcp server failed: {e}")))?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// 迁移提示词数据
|
|
fn migrate_prompts(
|
|
tx: &rusqlite::Transaction<'_>,
|
|
config: &MultiAppConfig,
|
|
) -> Result<(), AppError> {
|
|
let migrate_app_prompts = |prompts_map: &std::collections::HashMap<
|
|
String,
|
|
crate::prompt::Prompt,
|
|
>,
|
|
app_type: &str|
|
|
-> Result<(), AppError> {
|
|
for (id, prompt) in prompts_map {
|
|
tx.execute(
|
|
"INSERT OR REPLACE INTO prompts (
|
|
id, app_type, name, content, description, enabled, created_at, updated_at
|
|
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
|
|
params![
|
|
id,
|
|
app_type,
|
|
prompt.name,
|
|
prompt.content,
|
|
prompt.description,
|
|
prompt.enabled,
|
|
prompt.created_at,
|
|
prompt.updated_at,
|
|
],
|
|
)
|
|
.map_err(|e| AppError::Database(format!("Migrate prompt failed: {e}")))?;
|
|
}
|
|
Ok(())
|
|
};
|
|
|
|
migrate_app_prompts(&config.prompts.claude.prompts, "claude")?;
|
|
migrate_app_prompts(&config.prompts.codex.prompts, "codex")?;
|
|
migrate_app_prompts(&config.prompts.gemini.prompts, "gemini")?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 迁移 Skills 数据
|
|
fn migrate_skills(
|
|
tx: &rusqlite::Transaction<'_>,
|
|
config: &MultiAppConfig,
|
|
) -> Result<(), AppError> {
|
|
for (key, state) in &config.skills.skills {
|
|
tx.execute(
|
|
"INSERT OR REPLACE INTO skills (key, installed, installed_at) VALUES (?1, ?2, ?3)",
|
|
params![key, state.installed, state.installed_at.timestamp()],
|
|
)
|
|
.map_err(|e| AppError::Database(format!("Migrate skill failed: {e}")))?;
|
|
}
|
|
|
|
for repo in &config.skills.repos {
|
|
tx.execute(
|
|
"INSERT OR REPLACE INTO skill_repos (owner, name, branch, enabled) VALUES (?1, ?2, ?3, ?4)",
|
|
params![repo.owner, repo.name, repo.branch, repo.enabled],
|
|
).map_err(|e| AppError::Database(format!("Migrate skill repo failed: {e}")))?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 迁移通用配置片段
|
|
fn migrate_common_config(
|
|
tx: &rusqlite::Transaction<'_>,
|
|
config: &MultiAppConfig,
|
|
) -> Result<(), AppError> {
|
|
if let Some(snippet) = &config.common_config_snippets.claude {
|
|
tx.execute(
|
|
"INSERT OR REPLACE INTO settings (key, value) VALUES (?1, ?2)",
|
|
params!["common_config_claude", snippet],
|
|
)
|
|
.map_err(|e| AppError::Database(format!("Migrate settings failed: {e}")))?;
|
|
}
|
|
if let Some(snippet) = &config.common_config_snippets.codex {
|
|
tx.execute(
|
|
"INSERT OR REPLACE INTO settings (key, value) VALUES (?1, ?2)",
|
|
params!["common_config_codex", snippet],
|
|
)
|
|
.map_err(|e| AppError::Database(format!("Migrate settings failed: {e}")))?;
|
|
}
|
|
if let Some(snippet) = &config.common_config_snippets.gemini {
|
|
tx.execute(
|
|
"INSERT OR REPLACE INTO settings (key, value) VALUES (?1, ?2)",
|
|
params!["common_config_gemini", snippet],
|
|
)
|
|
.map_err(|e| AppError::Database(format!("Migrate settings failed: {e}")))?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|