mod client;
mod detector;
pub mod config;
pub mod keyring;
use async_trait::async_trait;
use gitlab_tracker_core::{Activity, LinkedTicket, TimeEntry, TimeEntryRequest, TrackerProvider};
pub use config::{load_or_create_config, RedmineConfig};
pub use keyring::get_or_prompt_token;
pub struct RedmineProvider {
config: RedmineConfig,
token: String,
http: reqwest::Client,
}
impl RedmineProvider {
pub fn new(config: RedmineConfig, token: String) -> Self {
Self {
config,
token,
http: reqwest::Client::new(),
}
}
}
#[async_trait]
impl TrackerProvider for RedmineProvider {
fn name(&self) -> &'static str {
"Redmine"
}
fn detect_ticket_id(&self, title: &str, description: &str) -> Option<String> {
detector::detect_ticket_id(title, description, &self.config.ticket_patterns)
}
async fn fetch_ticket(&self, ticket_id: &str) -> Option<LinkedTicket> {
let issue =
client::fetch_issue(&self.http, &self.config.redmine_url, &self.token, ticket_id)
.await?;
Some(LinkedTicket {
id: issue.id.to_string(),
subject: issue.subject,
status: issue.status.name,
url: self.ticket_url(ticket_id),
author: issue.author.map(|u| u.name),
assignee: issue.assigned_to.map(|u| u.name),
time_estimate: issue.estimated_hours.map(|h| (h * 3600.0).round() as u32),
time_spent: issue.spent_hours.map(|h| (h * 3600.0).round() as u32),
})
}
fn ticket_url(&self, ticket_id: &str) -> String {
format!(
"{}/issues/{}",
self.config.redmine_url.trim_end_matches('/'),
ticket_id
)
}
async fn fetch_activities(&self) -> Vec<Activity> {
client::fetch_activities(&self.http, &self.config.redmine_url, &self.token).await
}
async fn fetch_time_entries(&self, ticket_id: &str) -> Vec<TimeEntry> {
client::fetch_time_entries(&self.http, &self.config.redmine_url, &self.token, ticket_id)
.await
}
async fn log_time(&self, ticket_id: &str, entry: TimeEntryRequest) -> Result<(), String> {
client::log_time(
&self.http,
&self.config.redmine_url,
&self.token,
ticket_id,
entry,
)
.await
}
}