use reqwest; use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize, Clone)] pub struct RedmineIssue { pub id: u32, pub subject: String, pub description: Option, pub status: Option, pub priority: Option, pub assigned_to: Option, pub author: Option, pub project: Option, pub created_on: Option, pub updated_on: Option, pub start_date: Option, pub due_date: Option, pub done_ratio: Option, pub estimated_hours: Option, pub spent_hours: Option, pub journals: Option>, pub attachments: Option>, pub custom_fields: Option>, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct RedmineStatus { pub id: u32, pub name: String, pub is_closed: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct RedminePriority { pub id: u32, pub name: String, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct RedmineUser { pub id: u32, pub name: String, pub mail: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct RedmineProject { pub id: u32, pub name: String, pub identifier: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct RedmineJournal { pub id: u32, pub user: Option, pub notes: Option, pub created_on: Option, pub details: Option>, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct RedmineJournalDetail { pub property: Option, pub name: Option, pub old_value: Option, pub new_value: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct RedmineAttachment { pub id: u32, pub filename: String, pub filesize: Option, pub content_type: Option, pub content_url: Option, pub digest: Option, pub downloads: Option, pub author: Option, pub created_on: Option, pub description: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct RedmineCustomField { pub id: u32, pub name: String, pub value: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] struct RedmineIssueResponse { issue: RedmineIssue, } #[derive(Debug, Deserialize, Serialize, Clone)] struct RedmineIssuesResponse { issues: Vec, } pub async fn fetch_user_issues( api_key: &str, url: &str, user_id: u32, ) -> Result, Box> { let client = reqwest::Client::new(); let response = client .get(format!("{}/issues.json", url.trim_end_matches('/'))) .query(&[ ("assigned_to_id", user_id.to_string()), ("key", api_key.to_string()), ("include", "attachments,journals,relations".to_string()), ("limit", "100".to_string()), ]) .send() .await?; if !response.status().is_success() { return Err(format!("Redmine API error: {}", response.status()).into()); } let body: RedmineIssuesResponse = response.json().await?; Ok(body.issues) } pub async fn fetch_issue_full( api_key: &str, url: &str, issue_id: u32, ) -> Result> { let client = reqwest::Client::new(); let response = client .get(format!( "{}/issues/{}.json", url.trim_end_matches('/'), issue_id )) .query(&[ ("key", api_key.to_string()), ( "include", "attachments,journals,relations,watchers".to_string(), ), ]) .send() .await?; if !response.status().is_success() { return Err(format!("Redmine API error: {}", response.status()).into()); } let body: RedmineIssueResponse = response.json().await?; Ok(body.issue) } pub async fn test_connection( api_key: &str, url: &str, user_id: u32, ) -> Result> { let issues = fetch_user_issues(api_key, url, user_id).await?; Ok(issues.len()) }