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