1use super::{lock_conn, to_json_string, Database};
6use crate::app_config::MultiAppConfig;
7use crate::error::AppError;
8use rusqlite::{params, Connection};
9
10impl Database {
11 pub fn migrate_from_json(&self, config: &MultiAppConfig) -> Result<(), AppError> {
13 let mut conn = lock_conn!(self.conn);
14 let tx = conn
15 .transaction()
16 .map_err(|e| AppError::Database(e.to_string()))?;
17
18 Self::migrate_from_json_tx(&tx, config)?;
19
20 tx.commit()
21 .map_err(|e| AppError::Database(format!("Commit migration failed: {e}")))?;
22 Ok(())
23 }
24
25 pub fn migrate_from_json_dry_run(config: &MultiAppConfig) -> Result<(), AppError> {
29 let mut conn =
30 Connection::open_in_memory().map_err(|e| AppError::Database(e.to_string()))?;
31 Self::create_tables_on_conn(&conn)?;
32 Self::apply_schema_migrations_on_conn(&conn)?;
33
34 let tx = conn
35 .transaction()
36 .map_err(|e| AppError::Database(e.to_string()))?;
37 Self::migrate_from_json_tx(&tx, config)?;
38
39 drop(tx);
41 Ok(())
42 }
43
44 fn migrate_from_json_tx(
46 tx: &rusqlite::Transaction<'_>,
47 config: &MultiAppConfig,
48 ) -> Result<(), AppError> {
49 Self::migrate_providers(tx, config)?;
51
52 Self::migrate_mcp_servers(tx, config)?;
54
55 Self::migrate_prompts(tx, config)?;
57
58 Self::migrate_skills(tx, config)?;
60
61 Self::migrate_common_config(tx, config)?;
63
64 Ok(())
65 }
66
67 fn migrate_providers(
69 tx: &rusqlite::Transaction<'_>,
70 config: &MultiAppConfig,
71 ) -> Result<(), AppError> {
72 for (app_key, manager) in &config.apps {
73 let app_type = app_key;
74 let current_id = &manager.current;
75
76 for (id, provider) in &manager.providers {
77 let is_current = if id == current_id { 1 } else { 0 };
78
79 let mut meta_clone = provider.meta.clone().unwrap_or_default();
81 let endpoints = std::mem::take(&mut meta_clone.custom_endpoints);
82
83 tx.execute(
84 "INSERT OR REPLACE INTO providers (
85 id, app_type, name, settings_config, website_url, category,
86 created_at, sort_index, notes, icon, icon_color, meta, is_current
87 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
88 params![
89 id,
90 app_type,
91 provider.name,
92 to_json_string(&provider.settings_config)?,
93 provider.website_url,
94 provider.category,
95 provider.created_at,
96 provider.sort_index,
97 provider.notes,
98 provider.icon,
99 provider.icon_color,
100 to_json_string(&meta_clone)?,
101 is_current,
102 ],
103 )
104 .map_err(|e| AppError::Database(format!("Migrate provider failed: {e}")))?;
105
106 for (url, endpoint) in endpoints {
108 tx.execute(
109 "INSERT INTO provider_endpoints (provider_id, app_type, url, added_at)
110 VALUES (?1, ?2, ?3, ?4)",
111 params![id, app_type, url, endpoint.added_at],
112 )
113 .map_err(|e| AppError::Database(format!("Migrate endpoint failed: {e}")))?;
114 }
115 }
116 }
117 Ok(())
118 }
119
120 fn migrate_mcp_servers(
122 tx: &rusqlite::Transaction<'_>,
123 config: &MultiAppConfig,
124 ) -> Result<(), AppError> {
125 if let Some(servers) = &config.mcp.servers {
126 for (id, server) in servers {
127 tx.execute(
128 "INSERT OR REPLACE INTO mcp_servers (
129 id, name, server_config, description, homepage, docs, tags,
130 enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, enabled_openclaw, enabled_hermes
131 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
132 params![
133 id,
134 server.name,
135 to_json_string(&server.server)?,
136 server.description,
137 server.homepage,
138 server.docs,
139 to_json_string(&server.tags)?,
140 server.apps.claude,
141 server.apps.codex,
142 server.apps.gemini,
143 server.apps.opencode,
144 server.apps.openclaw,
145 server.apps.hermes,
146 ],
147 )
148 .map_err(|e| AppError::Database(format!("Migrate mcp server failed: {e}")))?;
149 }
150 }
151 Ok(())
152 }
153
154 fn migrate_prompts(
156 tx: &rusqlite::Transaction<'_>,
157 config: &MultiAppConfig,
158 ) -> Result<(), AppError> {
159 let migrate_app_prompts = |prompts_map: &std::collections::HashMap<
160 String,
161 crate::prompt::Prompt,
162 >,
163 app_type: &str|
164 -> Result<(), AppError> {
165 for (id, prompt) in prompts_map {
166 tx.execute(
167 "INSERT OR REPLACE INTO prompts (
168 id, app_type, name, content, description, enabled, created_at, updated_at
169 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
170 params![
171 id,
172 app_type,
173 prompt.name,
174 prompt.content,
175 prompt.description,
176 prompt.enabled,
177 prompt.created_at,
178 prompt.updated_at,
179 ],
180 )
181 .map_err(|e| AppError::Database(format!("Migrate prompt failed: {e}")))?;
182 }
183 Ok(())
184 };
185
186 migrate_app_prompts(&config.prompts.claude.prompts, "claude")?;
187 migrate_app_prompts(&config.prompts.codex.prompts, "codex")?;
188 migrate_app_prompts(&config.prompts.gemini.prompts, "gemini")?;
189
190 Ok(())
191 }
192
193 fn migrate_skills(
195 tx: &rusqlite::Transaction<'_>,
196 config: &MultiAppConfig,
197 ) -> Result<(), AppError> {
198 for repo in &config.skills.repos {
210 tx.execute(
211 "INSERT OR REPLACE INTO skill_repos (owner, name, branch, enabled) VALUES (?1, ?2, ?3, ?4)",
212 params![repo.owner, repo.name, repo.branch, repo.enabled],
213 ).map_err(|e| AppError::Database(format!("Migrate skill repo failed: {e}")))?;
214 }
215
216 Ok(())
217 }
218
219 fn migrate_common_config(
221 tx: &rusqlite::Transaction<'_>,
222 config: &MultiAppConfig,
223 ) -> Result<(), AppError> {
224 if let Some(snippet) = &config.common_config_snippets.claude {
225 tx.execute(
226 "INSERT OR REPLACE INTO settings (key, value) VALUES (?1, ?2)",
227 params!["common_config_claude", snippet],
228 )
229 .map_err(|e| AppError::Database(format!("Migrate settings failed: {e}")))?;
230 }
231 if let Some(snippet) = &config.common_config_snippets.codex {
232 tx.execute(
233 "INSERT OR REPLACE INTO settings (key, value) VALUES (?1, ?2)",
234 params!["common_config_codex", snippet],
235 )
236 .map_err(|e| AppError::Database(format!("Migrate settings failed: {e}")))?;
237 }
238 if let Some(snippet) = &config.common_config_snippets.gemini {
239 tx.execute(
240 "INSERT OR REPLACE INTO settings (key, value) VALUES (?1, ?2)",
241 params!["common_config_gemini", snippet],
242 )
243 .map_err(|e| AppError::Database(format!("Migrate settings failed: {e}")))?;
244 }
245
246 Ok(())
247 }
248}