diff --git a/Cargo.lock b/Cargo.lock index 9f19c24..3cac3c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -939,8 +939,8 @@ dependencies = [ "crossterm", "directories", "gray_matter", - "libjirac", "open", + "reqwest", "serde", "serde_json", "tabwriter", @@ -976,19 +976,6 @@ version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" -[[package]] -name = "libjirac" -version = "0.1.0" -dependencies = [ - "chrono", - "http", - "reqwest", - "serde", - "serde_json", - "thiserror", - "url", -] - [[package]] name = "libredox" version = "0.1.3" diff --git a/Cargo.toml b/Cargo.toml index 467e0c7..545ff29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,6 @@ resolver = "2" [workspace.dependencies] clap = { version = "4.4", features = ["derive"] } reqwest = { version = "0.12", features = ["json"] } -http = "1" -url = "2" tokio = { version = "1.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -18,5 +16,4 @@ gray_matter = { version = "0.2", default-features = false, features = ["toml"] } open = "5.2" chrono = { version = "0.4", features = ["serde"] } tabwriter = "1.4" -crossterm = "0.28" -thiserror = "2" \ No newline at end of file +crossterm = "0.28" \ No newline at end of file diff --git a/crates/jirac/Cargo.toml b/crates/jirac/Cargo.toml index 667f2d1..b38327d 100644 --- a/crates/jirac/Cargo.toml +++ b/crates/jirac/Cargo.toml @@ -4,9 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -libjirac = { path = "../libjirac" } - clap = { workspace = true } +reqwest = { workspace = true } tokio = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/jirac/src/cmd/create.rs b/crates/jirac/src/cmd/create.rs index 2861e27..b4f7264 100644 --- a/crates/jirac/src/cmd/create.rs +++ b/crates/jirac/src/cmd/create.rs @@ -1,11 +1,7 @@ use crate::jira_config::JiraConfig; use gray_matter::engine::TOML; use gray_matter::Matter; -use libjirac::client::commands::{ - IssueCreateCommand, IssueTransitionsCommand, IssueTransitionsUpdateCommand, -}; -use libjirac::client::JiraClient; -use libjirac::entities::issue_request::*; +use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; @@ -27,6 +23,84 @@ struct IssueMetadata { extra: HashMap, } +#[derive(Debug, Serialize)] +struct JiraIssueRequest { + fields: JiraIssueFields, + update: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +struct JiraIssueFields { + project: Project, + summary: String, + description: String, + issuetype: IssueType, + #[serde(skip_serializing_if = "Option::is_none")] + assignee: Option, + #[serde(skip_serializing_if = "Option::is_none")] + status: Option, +} + +#[derive(Debug, Serialize)] +struct JiraIssueUpdate { + #[serde(skip_serializing_if = "Option::is_none")] + parent: Option>, +} + +#[derive(Debug, Serialize)] +struct ParentUpdate { + add: Parent, +} + +#[derive(Debug, Serialize)] +struct Parent { + key: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Assignee { + name: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Project { + key: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct IssueType { + name: String, +} + +#[derive(Deserialize)] +struct JiraResponse { + key: String, +} + +#[derive(Deserialize)] +struct JiraSearchResponse { + issues: Vec, + total: u32, +} + +#[derive(Deserialize)] +struct JiraIssue { + key: String, + fields: JiraIssueResponseFields, +} + +#[derive(Deserialize)] +struct JiraIssueResponseFields { + summary: String, + status: Status, + created: String, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Status { + name: String, +} + fn get_issue_template( issue_metadata: &IssueMetadata, ) -> Result> { @@ -142,27 +216,37 @@ async fn create_jira_issue( title: &str, description: &str, metadata: &IssueMetadata, -) -> Result> { - let client = JiraClient::from(config); +) -> Result> { + let client = reqwest::Client::new(); - let fields = IssueFields { + let mut headers = HeaderMap::new(); + headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + + let mut fields = JiraIssueFields { project: Project { key: project_key.to_string(), }, summary: title.to_string(), description: description.to_string(), - issue_type: IssueType { + issuetype: IssueType { name: "Task".to_string(), }, assignee: None, status: None, }; + // Add assignee if specified + if let Some(assignee) = &metadata.assignee { + fields.assignee = Some(Assignee { + name: assignee.clone(), + }); + } + let mut update = None; // Add parent if specified if let Some(parent_key) = &metadata.parent { - update = Some(IssueUpdate { + update = Some(JiraIssueUpdate { parent: Some(vec![ParentUpdate { add: Parent { key: parent_key.clone(), @@ -171,24 +255,33 @@ async fn create_jira_issue( }); } - let issue = IssueCreateRequest { fields, update }; + let issue = JiraIssueRequest { fields, update }; - let create_cmd = IssueCreateCommand::new(issue); - let issue_response = client.exec(create_cmd).await?; + // println!("{:#?}", issue); + // Ok(JiraResponse { + // key: format!("{}-1234", project_key), + // }) + + let response = client + .post(format!("{}/rest/api/2/issue", config.url)) + .basic_auth(&config.email, Some(&config.api_token)) + .headers(headers) + .json(&issue) + .send() + .await?; + + if !response.status().is_success() { + let error_text = response.text().await?; + return Err(format!("Failed to create issue: {}", error_text).into()); + } + + let issue_response = response.json::().await?; // Update status if specified (requires a separate API call) if let Some(status) = &metadata.status { update_issue_status(config, &issue_response.key, status).await?; } - // Update assignee if specified - if let Some(_assignee) = &metadata.assignee { - // Need to look up users by username, because the api wants an account id - // fields.assignee = Some(Assignee { - // id: assignee.clone(), - // }); - } - Ok(issue_response) } @@ -197,29 +290,52 @@ async fn update_issue_status( issue_key: &str, status: &str, ) -> Result<(), Box> { - let client = JiraClient::from(config); + let client = reqwest::Client::new(); - let transitions_cmd = IssueTransitionsCommand::new(issue_key); - let transitions = client.exec(transitions_cmd).await?; + let transitions = client + .get(format!( + "{}/rest/api/2/issue/{}/transitions", + config.url, issue_key + )) + .basic_auth(&config.email, Some(&config.api_token)) + .send() + .await? + .json::() + .await?; // Find the transition ID for the desired status - let transition_id = transitions - .transitions - .iter() - .find_map(|t| { - if t.to.name.eq_ignore_ascii_case(status) { - Some(t.id.clone()) - } else { - None - } + let transition_id = transitions["transitions"] + .as_array() + .and_then(|t| { + t.iter().find(|t| { + t["to"]["name"] + .as_str() + .map_or(false, |s| s.eq_ignore_ascii_case(status)) + }) }) + .and_then(|t| t["id"].as_str()) .ok_or_else(|| format!("No transition found for status: {}", status))?; - let transition_cmd = IssueTransitionsUpdateCommand::new(issue_key, &transition_id); - match client.exec(transition_cmd).await { - Ok(x) => Ok(x), - Err(reason) => Err(reason.into()), + // Perform the transition + let transition_payload = serde_json::json!({ + "transition": { "id": transition_id } + }); + + let response = client + .post(format!( + "{}/rest/api/2/issue/{}/transitions", + config.url, issue_key + )) + .basic_auth(&config.email, Some(&config.api_token)) + .json(&transition_payload) + .send() + .await?; + + if !response.status().is_success() { + return Err(format!("Failed to update status: {}", response.text().await?).into()); } + + Ok(()) } pub async fn create( diff --git a/crates/jirac/src/cmd/search.rs b/crates/jirac/src/cmd/search.rs index e678582..e2dd050 100644 --- a/crates/jirac/src/cmd/search.rs +++ b/crates/jirac/src/cmd/search.rs @@ -1,81 +1,6 @@ use crate::cli::FormatMode; use crate::jira_config::JiraConfig; -use crate::term::hyperlink; -use crossterm::style::{Color, Stylize}; -use libjirac::client::commands::SearchCommand; -use libjirac::client::JiraClient; -use libjirac::entities::issue::JiraIssue; -use std::io::Write; - -pub fn display_issues_pretty(issues: &[JiraIssue]) -> Result<(), Box> { - println!("Found {} issues:", issues.len()); - println!("{:-<80}", ""); - - for issue in issues { - let mut tw = tabwriter::TabWriter::new(vec![]); - writeln!(tw, "{}:\t{}", "Key".blue(), issue.key)?; - writeln!(tw, "{}:\t{}", "Summary".blue(), issue.fields.summary)?; - writeln!(tw, "{}:\t{}", "Status".blue(), issue.fields.status.name)?; - writeln!( - tw, - "{}:\t{}", - "Created".blue(), - issue.fields.created.with_timezone(&chrono::Local) - )?; - writeln!( - tw, - "{}:\t{}", - "Due Date".blue(), - match issue.fields.due_date { - None => "None".to_string(), - Some(x) => x.to_string(), - } - )?; - writeln!( - tw, - "{}", - &hyperlink(&issue.href, &"Open Issue".green().to_string()) - )?; - - tw.flush().unwrap(); - - let written = String::from_utf8(tw.into_inner().unwrap()).unwrap(); - print!("{}", written); - println!("{:-<80}", ""); - } - - Ok(()) -} - -pub fn display_issues_compact(issues: &[JiraIssue]) -> Result<(), Box> { - println!("Found {} issues:", issues.len()); - println!("{:-<80}", ""); - - let mut tw = tabwriter::TabWriter::new(vec![]); - for issue in issues { - writeln!( - tw, - "{}:\t{}", - hyperlink( - &issue.href, - &issue.key.clone().blue().underline(Color::Blue).to_string() - ), - issue.fields.summary.clone().green() - )?; - } - - tw.flush().unwrap(); - let written = String::from_utf8(tw.into_inner().unwrap()).unwrap(); - print!("{}", written); - - Ok(()) -} - -pub fn display_issues_json(issues: &[JiraIssue]) -> Result<(), Box> { - let j = serde_json::to_string_pretty(issues)?; - println!("{}", j); - Ok(()) -} +use crate::types::issue::{display_issues_compact, display_issues_json, display_issues_pretty}; pub async fn exec(output: FormatMode, jql: &str) -> Result<(), Box> { let config = JiraConfig::load().map_err(|e| format!("Configuration error: {}", e))?; @@ -83,10 +8,13 @@ pub async fn exec(output: FormatMode, jql: &str) -> Result<(), Box x, + Err(reason) => { + eprintln!("Error fetching issues: {}", reason); + std::process::exit(1); + } + }; match (output, result.issues.is_empty()) { (FormatMode::Pretty, false) => { diff --git a/crates/jirac/src/cmd/view.rs b/crates/jirac/src/cmd/view.rs index d8d1213..752fe5d 100644 --- a/crates/jirac/src/cmd/view.rs +++ b/crates/jirac/src/cmd/view.rs @@ -1,12 +1,32 @@ use crate::cli::FormatMode; use crate::jira_config::JiraConfig; use crate::term::hyperlink; +use crate::types::issue::JiraIssue; use crossterm::style::{Color, Stylize}; -use libjirac::client::commands::{SearchCommand, SelfCommand}; -use libjirac::client::JiraClient; -use libjirac::entities::issue::JiraIssue; use std::io::Write; +async fn fetch_issue( + config: &JiraConfig, + href: &str, +) -> Result> { + let client = reqwest::Client::new(); + + let response = client + .get(href) + .basic_auth(&config.email, Some(&config.api_token)) + .send() + .await?; + + if !response.status().is_success() { + let error_text = response.text().await?; + return Err(format!("Failed to fetch issue: {}", error_text).into()); + } + + let issue_response = response.json::().await?; + + Ok(issue_response) +} + fn pretty_print(issue: &JiraIssue) -> Result<(), Box> { println!("\n== Title {:=<71}", ""); println!( @@ -104,10 +124,13 @@ pub async fn exec(output: FormatMode, issue_key: &str) -> Result<(), Box x, + Err(reason) => { + eprintln!("Error fetching issue: {}", reason); + std::process::exit(1); + } + }; let matched_issue = match matched_issues.issues.first() { Some(x) => x, @@ -117,8 +140,7 @@ pub async fn exec(output: FormatMode, issue_key: &str) -> Result<(), Box::new(&matched_issue.href); - let fetched_issue = client.exec(self_cmd).await?; + let fetched_issue = fetch_issue(&config, &matched_issue.href).await?; match output { FormatMode::Pretty => pretty_print(&fetched_issue)?, diff --git a/crates/jirac/src/jira_config.rs b/crates/jirac/src/jira_config.rs index 778e22b..d2be3df 100644 --- a/crates/jirac/src/jira_config.rs +++ b/crates/jirac/src/jira_config.rs @@ -1,6 +1,5 @@ use config::{Config, ConfigError, File}; use directories::ProjectDirs; -use libjirac::client::JiraClient; use serde::{Deserialize, Serialize}; use std::fs; use std::path::PathBuf; @@ -67,9 +66,3 @@ impl JiraConfig { Ok(()) } } - -impl From<&JiraConfig> for JiraClient { - fn from(value: &JiraConfig) -> Self { - Self::new(&value.url, &value.email, &value.api_token) - } -} diff --git a/crates/jirac/src/jql.rs b/crates/jirac/src/jql.rs new file mode 100644 index 0000000..a281f3d --- /dev/null +++ b/crates/jirac/src/jql.rs @@ -0,0 +1,37 @@ +use crate::jira_config::JiraConfig; +use crate::types::issue::JiraIssue; +use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE}; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct JiraSearchResponse { + pub issues: Vec, + pub total: u32, +} + +pub async fn run( + config: &JiraConfig, + jql: &str, +) -> Result> { + let client = reqwest::Client::new(); + + let mut headers = HeaderMap::new(); + headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + + let query = [("jql", jql)]; + + let response = client + .get(format!("{}/rest/api/2/search", config.url)) + .basic_auth(&config.email, Some(&config.api_token)) + .headers(headers) + .query(&query) + .send() + .await?; + + if !response.status().is_success() { + let error_text = response.text().await?; + return Err(format!("Failed to fetch issues: {}", error_text).into()); + } + + Ok(response.json::().await?) +} diff --git a/crates/jirac/src/main.rs b/crates/jirac/src/main.rs index d01c6a9..199f533 100644 --- a/crates/jirac/src/main.rs +++ b/crates/jirac/src/main.rs @@ -1,7 +1,9 @@ mod cli; mod cmd; mod jira_config; +mod jql; mod term; +mod types; use clap::Parser; use cli::{Cli, Commands}; diff --git a/crates/jirac/src/types.rs b/crates/jirac/src/types.rs new file mode 100644 index 0000000..d93d369 --- /dev/null +++ b/crates/jirac/src/types.rs @@ -0,0 +1 @@ +pub mod issue; diff --git a/crates/jirac/src/types/issue.rs b/crates/jirac/src/types/issue.rs new file mode 100644 index 0000000..ac009a8 --- /dev/null +++ b/crates/jirac/src/types/issue.rs @@ -0,0 +1,153 @@ +use crate::term::hyperlink; +use crossterm::style::{Color, Stylize}; +use serde::{Deserialize, Serialize}; +use std::io::Write; + +#[derive(Debug, Deserialize, Serialize)] +pub struct JiraIssue { + pub key: String, + #[serde(rename = "self")] + pub href: String, + pub fields: JiraIssueResponseFields, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct JiraIssueResponseFields { + pub summary: String, + pub description: Option, + pub status: Status, + pub created: chrono::DateTime, + pub priority: Priority, + pub assignee: Person, + pub reporter: Person, + pub creator: Person, + #[serde(rename = "duedate")] + pub due_date: Option, + pub comment: Option, + pub votes: Votes, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Status { + pub name: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Priority { + pub name: String, + pub id: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Person { + #[serde(rename = "self")] + pub href: String, + #[serde(rename = "displayName")] + pub display_name: String, + #[serde(rename = "accountId")] + pub account_id: String, + #[serde(rename = "emailAddress")] + pub email_address: Option, +} + +#[derive(Debug, Default, Clone, Deserialize, Serialize)] +pub struct Comments { + pub total: u32, + #[serde(rename = "maxResults")] + pub max_results: u32, + #[serde(rename = "startAt")] + pub start_at: u32, + pub comments: Vec, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Comment { + #[serde(rename = "self")] + pub href: String, + pub id: String, + pub author: Person, + pub body: String, + #[serde(rename = "updateAuthor")] + pub update_author: Person, + pub created: chrono::DateTime, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Votes { + #[serde(rename = "self")] + pub href: String, + #[serde(rename = "votes")] + pub count: i32, + #[serde(rename = "hasVoted")] + pub has_voted: bool, +} + +pub fn display_issues_pretty(issues: &[JiraIssue]) -> Result<(), Box> { + println!("Found {} issues:", issues.len()); + println!("{:-<80}", ""); + + for issue in issues { + let mut tw = tabwriter::TabWriter::new(vec![]); + writeln!(tw, "{}:\t{}", "Key".blue(), issue.key)?; + writeln!(tw, "{}:\t{}", "Summary".blue(), issue.fields.summary)?; + writeln!(tw, "{}:\t{}", "Status".blue(), issue.fields.status.name)?; + writeln!( + tw, + "{}:\t{}", + "Created".blue(), + issue.fields.created.with_timezone(&chrono::Local) + )?; + writeln!( + tw, + "{}:\t{}", + "Due Date".blue(), + match issue.fields.due_date { + None => "None".to_string(), + Some(x) => x.to_string(), + } + )?; + writeln!( + tw, + "{}", + &hyperlink(&issue.href, &"Open Issue".green().to_string()) + )?; + + tw.flush().unwrap(); + + let written = String::from_utf8(tw.into_inner().unwrap()).unwrap(); + print!("{}", written); + println!("{:-<80}", ""); + } + + Ok(()) +} + +pub fn display_issues_compact(issues: &[JiraIssue]) -> Result<(), Box> { + println!("Found {} issues:", issues.len()); + println!("{:-<80}", ""); + + let mut tw = tabwriter::TabWriter::new(vec![]); + for issue in issues { + writeln!( + tw, + "{}:\t{}", + hyperlink( + &issue.href, + &issue.key.clone().blue().underline(Color::Blue).to_string() + ), + issue.fields.summary.clone().green() + )?; + } + + tw.flush().unwrap(); + let written = String::from_utf8(tw.into_inner().unwrap()).unwrap(); + print!("{}", written); + + Ok(()) +} + +pub fn display_issues_json(issues: &[JiraIssue]) -> Result<(), Box> { + let j = serde_json::to_string_pretty(issues)?; + println!("{}", j); + Ok(()) +} diff --git a/crates/libjirac/Cargo.toml b/crates/libjirac/Cargo.toml deleted file mode 100644 index e83b8d5..0000000 --- a/crates/libjirac/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "libjirac" -version = "0.1.0" -edition = "2021" - -[dependencies] -serde = { workspace = true } -serde_json = { workspace = true } -chrono = { workspace = true } -reqwest = { workspace = true } -http = { workspace = true } -url = { workspace = true } -thiserror = { workspace = true } \ No newline at end of file diff --git a/crates/libjirac/src/client.rs b/crates/libjirac/src/client.rs deleted file mode 100644 index 5824646..0000000 --- a/crates/libjirac/src/client.rs +++ /dev/null @@ -1,155 +0,0 @@ -pub mod commands; - -use http::header::CONTENT_TYPE; -use http::{HeaderMap, HeaderValue}; -use serde::de::DeserializeOwned; -use serde::Serialize; -use std::fmt::Debug; -use thiserror::Error; -use url::{ParseError, Url}; - -pub struct JiraClient { - pub url: String, - pub email: String, - pub api_token: String, -} - -pub trait JiraCommand { - type TResponse: DeserializeOwned + Clone; - type TPayload: Serialize; - const REQUEST_TYPE: JiraRequestType; - fn endpoint(&self) -> String; - fn request_body(&self) -> Option<&Self::TPayload>; - fn query_params(&self) -> Option>; -} - -pub enum JiraRequestType { - Read, - Create, - Update, - Delete, -} - -impl From for http::Method { - fn from(value: JiraRequestType) -> Self { - match value { - JiraRequestType::Read => Self::GET, - JiraRequestType::Create => Self::POST, - JiraRequestType::Update => Self::PATCH, - JiraRequestType::Delete => Self::DELETE, - } - } -} - -#[derive(Debug, Error)] -pub enum JiraClientError { - #[error(transparent)] - UrlParseError(#[from] ParseError), - #[error(transparent)] - ReqwestError(#[from] reqwest::Error), - #[error(transparent)] - SerdeError(#[from] serde_json::Error), - #[error("API Error: {0}")] - ApiError(String), -} - -impl JiraClient { - pub fn new(url: &str, email: &str, api_token: &str) -> Self { - Self { - url: url.to_string(), - email: email.to_string(), - api_token: api_token.to_string(), - } - } - - pub async fn exec( - &self, - cmd: TCommand, - ) -> Result - where - TCommand: JiraCommand + Debug, - { - let client = reqwest::Client::new(); - - let mut headers = HeaderMap::new(); - headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); - - let url = self.construct_url(&cmd.endpoint())?; - - let mut request_builder = client - .request(TCommand::REQUEST_TYPE.into(), url) - .basic_auth(&self.email, Some(&self.api_token)) - .headers(headers); - - if let Some(params) = cmd.query_params() { - request_builder = request_builder.query(¶ms); - } - - if let Some(body) = cmd.request_body() { - request_builder = request_builder.json(body); - } - - let response = match request_builder.send().await { - Ok(x) => x, - Err(reason) => return Err(JiraClientError::ReqwestError(reason)), - }; - - if !response.status().is_success() { - let error_text = response.text().await?; - return Err(JiraClientError::ApiError(error_text)); - } - - let plain = match response.bytes().await { - Ok(x) => x, - Err(reason) => return Err(JiraClientError::ReqwestError(reason)), - }; - - let value = match is_unit::() { - true => b"null", - false => plain.as_ref(), - }; - - match serde_json::from_slice::(value) { - Ok(x) => Ok(x), - Err(reason) => Err(JiraClientError::SerdeError(reason)), - } - } - - fn construct_url(&self, endpoint: &str) -> Result { - let mut url = match Url::parse(&self.url) { - Ok(x) => x, - Err(reason) => { - return Err(JiraClientError::UrlParseError(reason)); - } - }; - - url.set_path(endpoint); - - Ok(url.to_string()) - } -} - -const fn is_unit() -> bool { - size_of::() == 0 && align_of::() == 1 -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_is_unit_type() { - assert!(is_unit::<()>()); - assert!(!is_unit::()); - } - - #[test] - fn deserialize_unit_type() { - let value = match is_unit::<()>() { - true => b"null".as_ref(), - false => b"{}".as_ref(), - }; - - serde_json::from_slice::<()>(value).unwrap(); - } -} diff --git a/crates/libjirac/src/client/commands.rs b/crates/libjirac/src/client/commands.rs deleted file mode 100644 index 6f5d4f0..0000000 --- a/crates/libjirac/src/client/commands.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod issue_create_command; -mod issue_transitions_command; -mod issue_transitions_update_command; -mod search_command; -mod self_command; - -pub use issue_create_command::IssueCreateCommand; -pub use issue_transitions_command::IssueTransitionsCommand; -pub use issue_transitions_update_command::IssueTransitionsUpdateCommand; -pub use search_command::SearchCommand; -pub use self_command::SelfCommand; diff --git a/crates/libjirac/src/client/commands/issue_create_command.rs b/crates/libjirac/src/client/commands/issue_create_command.rs deleted file mode 100644 index ce19ae3..0000000 --- a/crates/libjirac/src/client/commands/issue_create_command.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::client::{JiraCommand, JiraRequestType}; -use crate::entities::issue_request::{IssueCreateRequest, IssueCreateResponse}; - -#[derive(Debug)] -pub struct IssueCreateCommand { - issue: IssueCreateRequest, -} - -impl IssueCreateCommand { - pub fn new(issue: IssueCreateRequest) -> Self { - Self { issue } - } -} - -impl JiraCommand for IssueCreateCommand { - type TResponse = IssueCreateResponse; - type TPayload = IssueCreateRequest; - const REQUEST_TYPE: JiraRequestType = JiraRequestType::Create; - - fn endpoint(&self) -> String { - "/rest/api/2/issue".to_string() - } - - fn request_body(&self) -> Option<&Self::TPayload> { - Some(&self.issue) - } - - fn query_params(&self) -> Option> { - None - } -} diff --git a/crates/libjirac/src/client/commands/issue_transitions_command.rs b/crates/libjirac/src/client/commands/issue_transitions_command.rs deleted file mode 100644 index 1f57c00..0000000 --- a/crates/libjirac/src/client/commands/issue_transitions_command.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::client::{JiraCommand, JiraRequestType}; -use crate::entities::transitions::Transitions; - -#[derive(Debug)] -pub struct IssueTransitionsCommand { - issue_key: String, -} - -impl IssueTransitionsCommand { - pub fn new(issue_key: &str) -> Self { - Self { - issue_key: issue_key.to_string(), - } - } -} - -impl JiraCommand for IssueTransitionsCommand { - type TResponse = Transitions; - type TPayload = (); - const REQUEST_TYPE: JiraRequestType = JiraRequestType::Read; - - fn endpoint(&self) -> String { - format!("/rest/api/3/issue/{}/transitions", self.issue_key) - } - - fn request_body(&self) -> Option<&Self::TPayload> { - None - } - - fn query_params(&self) -> Option> { - None - } -} diff --git a/crates/libjirac/src/client/commands/issue_transitions_update_command.rs b/crates/libjirac/src/client/commands/issue_transitions_update_command.rs deleted file mode 100644 index b68ec21..0000000 --- a/crates/libjirac/src/client/commands/issue_transitions_update_command.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::client::{JiraCommand, JiraRequestType}; -use crate::entities::transitions::{IssueTransitionUpdatePayload, IssueTransitionUpdatePayloadId}; - -#[derive(Debug)] -pub struct IssueTransitionsUpdateCommand { - pub issue_key: String, - pub transition: IssueTransitionUpdatePayload, -} - -impl IssueTransitionsUpdateCommand { - pub fn new(issue_key: &str, transition_id: &str) -> Self { - Self { - issue_key: issue_key.to_string(), - transition: IssueTransitionUpdatePayload { - transition: IssueTransitionUpdatePayloadId { - id: transition_id.to_string(), - }, - }, - } - } -} - -impl JiraCommand for IssueTransitionsUpdateCommand { - type TResponse = (); - type TPayload = IssueTransitionUpdatePayload; - const REQUEST_TYPE: JiraRequestType = JiraRequestType::Create; - - fn endpoint(&self) -> String { - format!("/rest/api/3/issue/{}/transitions", self.issue_key) - } - - fn request_body(&self) -> Option<&Self::TPayload> { - Some(&self.transition) - } - - fn query_params(&self) -> Option> { - None - } -} diff --git a/crates/libjirac/src/client/commands/search_command.rs b/crates/libjirac/src/client/commands/search_command.rs deleted file mode 100644 index b44e4b8..0000000 --- a/crates/libjirac/src/client/commands/search_command.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::client::{JiraCommand, JiraRequestType}; -use crate::entities::search::JiraSearchResponse; - -#[derive(Debug)] -pub struct SearchCommand { - jql: String, -} - -impl SearchCommand { - pub fn new(jql: &str) -> Self { - Self { - jql: jql.to_string(), - } - } -} - -impl JiraCommand for SearchCommand { - type TResponse = JiraSearchResponse; - type TPayload = (); - const REQUEST_TYPE: JiraRequestType = JiraRequestType::Read; - - fn endpoint(&self) -> String { - "/rest/api/2/search".to_string() - } - - fn request_body(&self) -> Option<&Self::TPayload> { - None - } - - fn query_params(&self) -> Option> { - let params = vec![("jql".to_string(), self.jql.clone())]; - - Some(params) - } -} diff --git a/crates/libjirac/src/client/commands/self_command.rs b/crates/libjirac/src/client/commands/self_command.rs deleted file mode 100644 index bfee0f9..0000000 --- a/crates/libjirac/src/client/commands/self_command.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::client::{JiraCommand, JiraRequestType}; -use serde::de::DeserializeOwned; -use url::Url; - -#[derive(Debug)] -pub struct SelfCommand -where - T: DeserializeOwned + Clone, -{ - href: String, - _marker: std::marker::PhantomData, -} - -impl SelfCommand -where - T: DeserializeOwned + Clone, -{ - pub fn new(href: &str) -> Self { - Self { - href: href.to_owned(), - _marker: std::marker::PhantomData, - } - } -} - -impl JiraCommand for SelfCommand -where - T: DeserializeOwned + Clone, -{ - type TResponse = T; - type TPayload = (); - const REQUEST_TYPE: JiraRequestType = JiraRequestType::Read; - - fn endpoint(&self) -> String { - let url = Url::parse(&self.href).unwrap(); - url.path().to_string() - } - - fn request_body(&self) -> Option<&Self::TPayload> { - None - } - - fn query_params(&self) -> Option> { - None - } -} diff --git a/crates/libjirac/src/entities.rs b/crates/libjirac/src/entities.rs deleted file mode 100644 index 1f415ef..0000000 --- a/crates/libjirac/src/entities.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod issue; -pub mod issue_request; -pub mod search; -pub mod transitions; diff --git a/crates/libjirac/src/entities/issue.rs b/crates/libjirac/src/entities/issue.rs deleted file mode 100644 index a576a7b..0000000 --- a/crates/libjirac/src/entities/issue.rs +++ /dev/null @@ -1,80 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct JiraIssue { - pub key: String, - #[serde(rename = "self")] - pub href: String, - pub fields: JiraIssueResponseFields, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct JiraIssueResponseFields { - pub summary: String, - pub description: Option, - pub status: Status, - pub created: chrono::DateTime, - pub priority: Priority, - pub assignee: Person, - pub reporter: Person, - pub creator: Person, - #[serde(rename = "duedate")] - pub due_date: Option, - pub comment: Option, - pub votes: Votes, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Status { - pub name: String, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Priority { - pub name: String, - pub id: String, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Person { - #[serde(rename = "self")] - pub href: String, - #[serde(rename = "displayName")] - pub display_name: String, - #[serde(rename = "accountId")] - pub account_id: String, - #[serde(rename = "emailAddress")] - pub email_address: Option, -} - -#[derive(Debug, Default, Clone, Deserialize, Serialize)] -pub struct Comments { - pub total: u32, - #[serde(rename = "maxResults")] - pub max_results: u32, - #[serde(rename = "startAt")] - pub start_at: u32, - pub comments: Vec, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Comment { - #[serde(rename = "self")] - pub href: String, - pub id: String, - pub author: Person, - pub body: String, - #[serde(rename = "updateAuthor")] - pub update_author: Person, - pub created: chrono::DateTime, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Votes { - #[serde(rename = "self")] - pub href: String, - #[serde(rename = "votes")] - pub count: i32, - #[serde(rename = "hasVoted")] - pub has_voted: bool, -} diff --git a/crates/libjirac/src/entities/issue_request.rs b/crates/libjirac/src/entities/issue_request.rs deleted file mode 100644 index 16699c0..0000000 --- a/crates/libjirac/src/entities/issue_request.rs +++ /dev/null @@ -1,61 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Deserialize)] -pub struct IssueCreateResponse { - pub key: String, -} - -#[derive(Debug, Clone, Serialize)] -pub struct IssueCreateRequest { - pub fields: IssueFields, - pub update: Option, -} - -#[derive(Debug, Clone, Serialize)] -pub struct IssueUpdate { - #[serde(skip_serializing_if = "Option::is_none")] - pub parent: Option>, -} - -#[derive(Debug, Clone, Serialize)] -pub struct ParentUpdate { - pub add: Parent, -} - -#[derive(Debug, Clone, Serialize)] -pub struct Parent { - pub key: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct IssueFields { - pub project: Project, - pub summary: String, - pub description: String, - #[serde(rename = "issuetype")] - pub issue_type: IssueType, - #[serde(skip_serializing_if = "Option::is_none")] - pub assignee: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub status: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Assignee { - pub id: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Project { - pub key: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct IssueType { - pub name: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Status { - pub name: String, -} diff --git a/crates/libjirac/src/entities/search.rs b/crates/libjirac/src/entities/search.rs deleted file mode 100644 index 9ae48a3..0000000 --- a/crates/libjirac/src/entities/search.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::entities::issue::JiraIssue; -use serde::Deserialize; - -#[derive(Debug, Clone, Deserialize)] -pub struct JiraSearchResponse { - pub issues: Vec, - pub total: u32, -} diff --git a/crates/libjirac/src/entities/transitions.rs b/crates/libjirac/src/entities/transitions.rs deleted file mode 100644 index 03282e8..0000000 --- a/crates/libjirac/src/entities/transitions.rs +++ /dev/null @@ -1,29 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Deserialize)] -pub struct Transitions { - pub transitions: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Transition { - pub id: String, - pub to: TransitionTarget, - pub name: String, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct TransitionTarget { - pub id: String, - pub name: String, -} - -#[derive(Debug, Serialize)] -pub struct IssueTransitionUpdatePayload { - pub transition: IssueTransitionUpdatePayloadId, -} - -#[derive(Debug, Serialize)] -pub struct IssueTransitionUpdatePayloadId { - pub id: String, -} diff --git a/crates/libjirac/src/lib.rs b/crates/libjirac/src/lib.rs deleted file mode 100644 index 5ea289c..0000000 --- a/crates/libjirac/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod client; -pub mod entities;