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).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}