bitmine/src/services/redmine.rs
2026-03-18 22:05:02 +03:00

166 lines
4.3 KiB
Rust

use reqwest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct RedmineIssue {
pub id: u32,
pub subject: String,
pub description: Option<String>,
pub status: Option<RedmineStatus>,
pub priority: Option<RedminePriority>,
pub assigned_to: Option<RedmineUser>,
pub author: Option<RedmineUser>,
pub project: Option<RedmineProject>,
pub created_on: Option<String>,
pub updated_on: Option<String>,
pub start_date: Option<String>,
pub due_date: Option<String>,
pub done_ratio: Option<u32>,
pub estimated_hours: Option<f32>,
pub spent_hours: Option<f32>,
pub journals: Option<Vec<RedmineJournal>>,
pub attachments: Option<Vec<RedmineAttachment>>,
pub custom_fields: Option<Vec<RedmineCustomField>>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct RedmineStatus {
pub id: u32,
pub name: String,
pub is_closed: Option<bool>,
}
#[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<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct RedmineProject {
pub id: u32,
pub name: String,
pub identifier: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct RedmineJournal {
pub id: u32,
pub user: Option<RedmineUser>,
pub notes: Option<String>,
pub created_on: Option<String>,
pub details: Option<Vec<RedmineJournalDetail>>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct RedmineJournalDetail {
pub property: Option<String>,
pub name: Option<String>,
pub old_value: Option<String>,
pub new_value: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct RedmineAttachment {
pub id: u32,
pub filename: String,
pub filesize: Option<u32>,
pub content_type: Option<String>,
pub content_url: Option<String>,
pub digest: Option<String>,
pub downloads: Option<u32>,
pub author: Option<RedmineUser>,
pub created_on: Option<String>,
pub description: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct RedmineCustomField {
pub id: u32,
pub name: String,
pub value: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
struct RedmineIssueResponse {
issue: RedmineIssue,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
struct RedmineIssuesResponse {
issues: Vec<RedmineIssue>,
}
pub async fn fetch_user_issues(
api_key: &str,
url: &str,
user_id: u32,
) -> Result<Vec<RedmineIssue>, Box<dyn std::error::Error + Send + Sync>> {
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<RedmineIssue, Box<dyn std::error::Error + Send + Sync>> {
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<usize, Box<dyn std::error::Error + Send + Sync>> {
let issues = fetch_user_issues(api_key, url, user_id).await?;
Ok(issues.len())
}