stmo_cli/commands/
archive.rs1#![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}