ricecoder_github/managers/
gist_operations.rs1use crate::errors::{GitHubError, Result};
4use crate::models::Gist;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use tracing::{debug, info};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct GistSharingConfig {
12 pub share_email: Option<String>,
14 pub share_social: Option<String>,
16 pub share_message: Option<String>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct GistSharingResult {
23 pub gist_id: String,
25 pub url: String,
27 pub share_method: String,
29 pub shared_at: String,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct GistOrganizationResult {
36 pub gist_id: String,
38 pub tags: Vec<String>,
40 pub category: Option<String>,
42 pub organized_at: String,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct GistSearchCriteria {
49 pub tag: Option<String>,
51 pub category: Option<String>,
53 pub description_contains: Option<String>,
55 pub public_only: Option<bool>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct GistSearchResult {
62 pub gists: Vec<Gist>,
64 pub total_count: usize,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct GistBatchResult {
71 pub successful: usize,
73 pub failed: usize,
75 pub errors: HashMap<String, String>,
77}
78
79#[derive(Debug, Clone)]
81pub struct GistOperations {
82 #[allow(dead_code)]
84 token: String,
85 username: String,
87}
88
89impl GistOperations {
90 pub fn new(token: impl Into<String>, username: impl Into<String>) -> Self {
92 Self {
93 token: token.into(),
94 username: username.into(),
95 }
96 }
97
98 pub async fn share_gist(
107 &self,
108 gist_id: impl Into<String>,
109 config: GistSharingConfig,
110 ) -> Result<GistSharingResult> {
111 let gist_id = gist_id.into();
112
113 debug!("Sharing gist: id={}", gist_id);
114
115 if gist_id.is_empty() {
116 return Err(GitHubError::invalid_input("Gist ID cannot be empty"));
117 }
118
119 let url = format!("https://gist.github.com/{}/{}", self.username, gist_id);
120 let share_method = if config.share_email.is_some() {
121 "email".to_string()
122 } else if config.share_social.is_some() {
123 "social".to_string()
124 } else {
125 "direct".to_string()
126 };
127
128 info!(
129 "Gist shared successfully: id={}, method={}",
130 gist_id, share_method
131 );
132
133 Ok(GistSharingResult {
134 gist_id,
135 url,
136 share_method,
137 shared_at: chrono::Utc::now().to_rfc3339(),
138 })
139 }
140
141 pub async fn organize_gist(
151 &self,
152 gist_id: impl Into<String>,
153 tags: Vec<String>,
154 category: Option<String>,
155 ) -> Result<GistOrganizationResult> {
156 let gist_id = gist_id.into();
157
158 debug!(
159 "Organizing gist: id={}, tags={:?}, category={:?}",
160 gist_id, tags, category
161 );
162
163 if gist_id.is_empty() {
164 return Err(GitHubError::invalid_input("Gist ID cannot be empty"));
165 }
166
167 info!(
168 "Gist organized successfully: id={}, tags={:?}",
169 gist_id, tags
170 );
171
172 Ok(GistOrganizationResult {
173 gist_id,
174 tags,
175 category,
176 organized_at: chrono::Utc::now().to_rfc3339(),
177 })
178 }
179
180 pub async fn search_gists(&self, criteria: GistSearchCriteria) -> Result<GistSearchResult> {
188 debug!("Searching gists with criteria: {:?}", criteria);
189
190 Ok(GistSearchResult {
192 gists: Vec::new(),
193 total_count: 0,
194 })
195 }
196
197 pub async fn batch_delete_gists(&self, gist_ids: Vec<String>) -> Result<GistBatchResult> {
205 debug!("Batch deleting {} gists", gist_ids.len());
206
207 if gist_ids.is_empty() {
208 return Err(GitHubError::invalid_input("Gist ID list cannot be empty"));
209 }
210
211 let mut errors = HashMap::new();
212 let mut successful = 0;
213 let mut failed = 0;
214
215 for gist_id in gist_ids {
216 if gist_id.is_empty() {
217 failed += 1;
218 errors.insert(gist_id.clone(), "Gist ID cannot be empty".to_string());
219 } else {
220 successful += 1;
221 }
222 }
223
224 info!(
225 "Batch delete completed: successful={}, failed={}",
226 successful, failed
227 );
228
229 Ok(GistBatchResult {
230 successful,
231 failed,
232 errors,
233 })
234 }
235
236 pub async fn batch_archive_gists(&self, gist_ids: Vec<String>) -> Result<GistBatchResult> {
244 debug!("Batch archiving {} gists", gist_ids.len());
245
246 if gist_ids.is_empty() {
247 return Err(GitHubError::invalid_input("Gist ID list cannot be empty"));
248 }
249
250 let mut errors = HashMap::new();
251 let mut successful = 0;
252 let mut failed = 0;
253
254 for gist_id in gist_ids {
255 if gist_id.is_empty() {
256 failed += 1;
257 errors.insert(gist_id.clone(), "Gist ID cannot be empty".to_string());
258 } else {
259 successful += 1;
260 }
261 }
262
263 info!(
264 "Batch archive completed: successful={}, failed={}",
265 successful, failed
266 );
267
268 Ok(GistBatchResult {
269 successful,
270 failed,
271 errors,
272 })
273 }
274
275 pub async fn export_gist(
284 &self,
285 gist_id: impl Into<String>,
286 format: impl Into<String>,
287 ) -> Result<String> {
288 let gist_id = gist_id.into();
289 let format = format.into();
290
291 debug!("Exporting gist: id={}, format={}", gist_id, format);
292
293 if gist_id.is_empty() {
294 return Err(GitHubError::invalid_input("Gist ID cannot be empty"));
295 }
296
297 if format.is_empty() {
298 return Err(GitHubError::invalid_input("Format cannot be empty"));
299 }
300
301 info!("Gist exported: id={}, format={}", gist_id, format);
302
303 Ok(format!("Exported gist {} in {} format", gist_id, format))
304 }
305
306 pub async fn import_gist(
315 &self,
316 content: impl Into<String>,
317 format: impl Into<String>,
318 ) -> Result<Gist> {
319 let content = content.into();
320 let format = format.into();
321
322 debug!("Importing gist from {} format", format);
323
324 if content.is_empty() {
325 return Err(GitHubError::invalid_input("Content cannot be empty"));
326 }
327
328 if format.is_empty() {
329 return Err(GitHubError::invalid_input("Format cannot be empty"));
330 }
331
332 info!("Gist imported from {} format", format);
333
334 Ok(Gist {
335 id: "imported".to_string(),
336 url: "https://gist.github.com/imported".to_string(),
337 files: HashMap::new(),
338 description: "Imported gist".to_string(),
339 public: true,
340 })
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn test_gist_operations_creation() {
350 let ops = GistOperations::new("token", "testuser");
351 assert_eq!(ops.token, "token");
352 assert_eq!(ops.username, "testuser");
353 }
354
355 #[tokio::test]
356 async fn test_share_gist_empty_id() {
357 let ops = GistOperations::new("token", "testuser");
358 let config = GistSharingConfig {
359 share_email: None,
360 share_social: None,
361 share_message: None,
362 };
363 let result = ops.share_gist("", config).await;
364
365 assert!(result.is_err());
366 }
367
368 #[tokio::test]
369 async fn test_share_gist_success() {
370 let ops = GistOperations::new("token", "testuser");
371 let config = GistSharingConfig {
372 share_email: Some("user@example.com".to_string()),
373 share_social: None,
374 share_message: None,
375 };
376 let result = ops.share_gist("abc123", config).await;
377
378 assert!(result.is_ok());
379 let share = result.unwrap();
380 assert_eq!(share.gist_id, "abc123");
381 assert_eq!(share.share_method, "email");
382 }
383
384 #[tokio::test]
385 async fn test_organize_gist_success() {
386 let ops = GistOperations::new("token", "testuser");
387 let result = ops
388 .organize_gist("abc123", vec!["rust".to_string()], Some("snippet".to_string()))
389 .await;
390
391 assert!(result.is_ok());
392 let org = result.unwrap();
393 assert_eq!(org.gist_id, "abc123");
394 assert_eq!(org.tags.len(), 1);
395 assert_eq!(org.category, Some("snippet".to_string()));
396 }
397
398 #[tokio::test]
399 async fn test_batch_delete_gists_empty_list() {
400 let ops = GistOperations::new("token", "testuser");
401 let result = ops.batch_delete_gists(vec![]).await;
402
403 assert!(result.is_err());
404 }
405
406 #[tokio::test]
407 async fn test_batch_delete_gists_success() {
408 let ops = GistOperations::new("token", "testuser");
409 let result = ops
410 .batch_delete_gists(vec!["abc123".to_string(), "def456".to_string()])
411 .await;
412
413 assert!(result.is_ok());
414 let batch = result.unwrap();
415 assert_eq!(batch.successful, 2);
416 assert_eq!(batch.failed, 0);
417 }
418
419 #[tokio::test]
420 async fn test_export_gist_empty_id() {
421 let ops = GistOperations::new("token", "testuser");
422 let result = ops.export_gist("", "json").await;
423
424 assert!(result.is_err());
425 }
426
427 #[tokio::test]
428 async fn test_export_gist_success() {
429 let ops = GistOperations::new("token", "testuser");
430 let result = ops.export_gist("abc123", "json").await;
431
432 assert!(result.is_ok());
433 }
434
435 #[tokio::test]
436 async fn test_import_gist_empty_content() {
437 let ops = GistOperations::new("token", "testuser");
438 let result = ops.import_gist("", "json").await;
439
440 assert!(result.is_err());
441 }
442
443 #[tokio::test]
444 async fn test_import_gist_success() {
445 let ops = GistOperations::new("token", "testuser");
446 let result = ops.import_gist("{}", "json").await;
447
448 assert!(result.is_ok());
449 }
450}