Skip to main content

stmo_cli/commands/
archive.rs

1#![allow(clippy::missing_errors_doc)]
2
3use anyhow::{Context, Result};
4use std::fs;
5use std::path::Path;
6
7use crate::api::RedashClient;
8
9fn find_query_files(query_id: u64) -> Result<Option<(String, String)>> {
10    let queries_dir = Path::new("queries");
11
12    if !queries_dir.exists() {
13        return Ok(None);
14    }
15
16    let mut sql_path = None;
17    let mut yaml_path = None;
18
19    for entry in fs::read_dir(queries_dir).context("Failed to read queries directory")? {
20        let entry = entry.context("Failed to read directory entry")?;
21        let path = entry.path();
22
23        if let Some(filename) = path.file_name().and_then(|f| f.to_str())
24            && let Some(id_str) = filename.split('-').next()
25            && let Ok(id) = id_str.parse::<u64>()
26            && id == query_id
27        {
28            if path.extension().is_some_and(|ext| ext == "sql") {
29                sql_path = Some(path.to_string_lossy().to_string());
30            } else if path.extension().is_some_and(|ext| ext == "yaml") {
31                yaml_path = Some(path.to_string_lossy().to_string());
32            }
33        }
34    }
35
36    match (sql_path, yaml_path) {
37        (Some(sql), Some(yaml)) => Ok(Some((sql, yaml))),
38        _ => Ok(None),
39    }
40}
41
42fn delete_query_files(sql_path: &str, yaml_path: &str) -> Result<()> {
43    fs::remove_file(sql_path)
44        .context(format!("Failed to delete {sql_path}"))?;
45    fs::remove_file(yaml_path)
46        .context(format!("Failed to delete {yaml_path}"))?;
47    Ok(())
48}
49
50pub async fn archive(client: &RedashClient, query_ids: Vec<u64>) -> Result<()> {
51    let mut errors = Vec::new();
52    let mut archived_count = 0;
53
54    println!("Archiving {} queries...\n", query_ids.len());
55
56    for query_id in &query_ids {
57        match client.archive_query(*query_id).await {
58            Ok(query) => {
59                println!("  ✓ Archived query {query_id} - {}", query.name);
60
61                if let Ok(Some((sql_path, yaml_path))) = find_query_files(*query_id) {
62                    if let Err(e) = delete_query_files(&sql_path, &yaml_path) {
63                        eprintln!("  ⚠ Failed to delete local files for query {query_id}: {e}");
64                    } else {
65                        println!("    Deleted local files");
66                    }
67                } else {
68                    println!("    No local files found");
69                }
70
71                archived_count += 1;
72            }
73            Err(e) => {
74                eprintln!("  ✗ Failed to archive query {query_id}: {e}");
75                errors.push((*query_id, e));
76            }
77        }
78    }
79
80    println!("\n✓ Archived {archived_count}/{} queries", query_ids.len());
81
82    if !errors.is_empty() {
83        anyhow::bail!("Failed to archive {} queries", errors.len());
84    }
85
86    Ok(())
87}
88
89pub async fn cleanup(client: &RedashClient) -> Result<()> {
90    let queries_dir = Path::new("queries");
91
92    if !queries_dir.exists() {
93        println!("No queries directory found");
94        return Ok(());
95    }
96
97    let mut query_ids = Vec::new();
98
99    for entry in fs::read_dir(queries_dir).context("Failed to read queries directory")? {
100        let entry = entry.context("Failed to read directory entry")?;
101        let path = entry.path();
102
103        if path.extension().is_some_and(|ext| ext == "yaml")
104            && let Some(filename) = path.file_name().and_then(|f| f.to_str())
105            && let Some(id_str) = filename.split('-').next()
106            && let Ok(id) = id_str.parse::<u64>()
107        {
108            query_ids.push(id);
109        }
110    }
111
112    query_ids.sort_unstable();
113    query_ids.dedup();
114
115    if query_ids.is_empty() {
116        println!("No queries found in queries/ directory");
117        return Ok(());
118    }
119
120    println!("Checking {} queries for archive status...\n", query_ids.len());
121
122    let mut cleaned_count = 0;
123    let mut errors = Vec::new();
124
125    for query_id in &query_ids {
126        match client.get_query(*query_id).await {
127            Ok(query) => {
128                if query.is_archived {
129                    println!("  Found archived query {query_id} - {}", query.name);
130
131                    if let Ok(Some((sql_path, yaml_path))) = find_query_files(*query_id) {
132                        if let Err(e) = delete_query_files(&sql_path, &yaml_path) {
133                            eprintln!("    ✗ Failed to delete files: {e}");
134                            errors.push((*query_id, e));
135                        } else {
136                            println!("    ✓ Deleted local files");
137                            cleaned_count += 1;
138                        }
139                    }
140                }
141            }
142            Err(e) => {
143                eprintln!("  ⚠ Failed to check query {query_id}: {e}");
144            }
145        }
146    }
147
148    if cleaned_count > 0 {
149        println!("\n✓ Cleaned up {cleaned_count} archived queries");
150    } else {
151        println!("\n✓ No archived queries with local files found");
152    }
153
154    if !errors.is_empty() {
155        anyhow::bail!("Failed to clean up {} queries", errors.len());
156    }
157
158    Ok(())
159}
160
161pub async fn unarchive(client: &RedashClient, query_ids: Vec<u64>) -> Result<()> {
162    let mut errors = Vec::new();
163    let mut unarchived_count = 0;
164
165    println!("Unarchiving {} queries...\n", query_ids.len());
166
167    for query_id in &query_ids {
168        match client.unarchive_query(*query_id).await {
169            Ok(query) => {
170                println!("  ✓ Unarchived query {query_id} - {}", query.name);
171                unarchived_count += 1;
172            }
173            Err(e) => {
174                let error_msg = e.to_string();
175                if error_msg.contains("403") || error_msg.contains("Permission") {
176                    eprintln!("  ✗ Permission denied to unarchive query {query_id}");
177                } else {
178                    eprintln!("  ✗ Failed to unarchive query {query_id}: {e}");
179                }
180                errors.push((*query_id, e));
181            }
182        }
183    }
184
185    println!("\n✓ Unarchived {unarchived_count}/{} queries", query_ids.len());
186
187    if !errors.is_empty() {
188        anyhow::bail!("Failed to unarchive {} queries", errors.len());
189    }
190
191    Ok(())
192}