diff --git a/src/gitea/action.rs b/src/gitea/action.rs new file mode 100644 index 0000000..c93a473 --- /dev/null +++ b/src/gitea/action.rs @@ -0,0 +1,63 @@ +use url::Url; +use crate::cli::Issue2Work; +use crate::config::Config; +use crate::error::GeneralError; +use crate::gitea::issue::{issue_html_url_to_api, Issue}; +use crate::gitea::client::has_client_for_url; +use crate::openproject::user::{GetMe, User}; +use crate::openproject::work::{WorkPackage, WorkPackageWriter, WorkPackageWriterAssignee}; +use crate::planning::Issue2WorkActionTrait; + +pub(crate) struct GiteaAction {} + +impl Issue2WorkActionTrait for GiteaAction { + async fn run(&self, url: &Url, config: &Config, args: &Issue2Work) -> Result<(), GeneralError> { + let gitea_client = crate::gitea::client::Client::from_config(config.gitea.first().unwrap()); + let issue: Issue = gitea_client + .get(issue_html_url_to_api(url)?) + .await?; + let open_project_client = crate::openproject::client::Client::from_config(&config.openproject); + + let work_package = create_work_package_from_issue( + &issue, + match args.assign_to_me { + true => { + let u = open_project_client.me().await?; + Some(u) + } + false => None, + } + ); + + let work_package = open_project_client + .create_work_package(&work_package, &args.project_id) + .await?; + + println!( + "new work package created: {:?}, edit at {}/projects/{}/work_packages/{}", + work_package.subject, config.openproject.base_url, args.project_id, work_package.id + ); + + Ok(()) + } + + fn supports(&self, url: &Url, config: &Config, _args: &Issue2Work) -> bool { + has_client_for_url(&url, &config) + } +} + +fn create_work_package_from_issue(issue: &Issue, assignee: Option) -> WorkPackageWriter { + WorkPackageWriter { + subject: format!( + "{} ({})", + issue.title, + issue.repository.full_name + ), + work_type: "TASK".into(), + description: crate::openproject::work::DescriptionWriter { + format: "markdown".into(), + raw: format!("From Gitea issue: {} \n\n{}", issue.html_url, issue.body), + }, + assignee: assignee.into() + } +} \ No newline at end of file diff --git a/src/gitea/client.rs b/src/gitea/client.rs index cfce202..65dc5f9 100644 --- a/src/gitea/client.rs +++ b/src/gitea/client.rs @@ -1,6 +1,5 @@ use crate::config::{Config, GiteaConfig, GitlabConfig}; use crate::error::GeneralError; -use gitlab::AsyncGitlab; use reqwest::header::{HeaderMap, ACCEPT, AUTHORIZATION}; use reqwest::{ClientBuilder, StatusCode}; use serde::de::DeserializeOwned; @@ -12,6 +11,35 @@ pub struct Client { base_uri: String, } +fn is_client_for_url(url: &Url, config: &GiteaConfig) -> bool { + if url.domain() == Some(config.domain.as_str()) { + return true; + } + false +} + +pub(crate) fn has_client_for_url(url: &Url, config: &Config) -> bool { + for c in &config.gitea { + if is_client_for_url(url, c) { + return true; + } + } + + false +} + +fn client_for_url(url: &Url, config: &Config) -> Result { + for c in &config.gitea { + if is_client_for_url(url, c) { + return Ok(Client::from_config(&c)); + } + } + + Err(GeneralError { + description: format!("No gitea client found for url {}", url), + }) +} + impl Client { pub fn from_config(config: &GiteaConfig) -> Self { Self::new(&config.token, &config.domain) @@ -52,3 +80,19 @@ impl Client { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_is_client_for_url() { + let config = GiteaConfig { + domain: "gitea.champs-libres.be".into(), + token: "".into(), + }; + + assert_eq!(is_client_for_url(&Url::parse("https://gitea.champs-libres.be/something/somewhere").unwrap(), &config), true); + assert_eq!(is_client_for_url(&Url::parse("https://somewhere.else/something/somewhere").unwrap(), &config), false); + } +} diff --git a/src/gitea/issue.rs b/src/gitea/issue.rs index 416365c..e087049 100644 --- a/src/gitea/issue.rs +++ b/src/gitea/issue.rs @@ -1,14 +1,17 @@ use crate::gitea::client::Client; use crate::gitea::repository::Repository; use serde::Deserialize; +use url::Url; +use crate::error::GeneralError; #[derive(Debug, Deserialize)] pub struct Issue { - id: u64, + pub id: u64, number: u64, - title: String, - body: String, - repository: Repository, + pub title: String, + pub body: String, + pub repository: Repository, + pub html_url: String, } pub trait IssueClient { @@ -16,7 +19,38 @@ pub trait IssueClient { } impl IssueClient for Client { - fn get_issue(owner_or_organisation: &String, repo: &String, number: &u64) -> Option { + fn get_issue(_owner_or_organisation: &String, _repo: &String, number: &u64) -> Option { todo!() } } + +pub fn issue_html_url_to_api(url: &Url) -> Result { + let mut parts = url.path_segments().unwrap(); + let domain = parts.next().unwrap(); + let repo = parts.next().unwrap(); + let issue = parts.next().unwrap(); + let iid = parts.next().unwrap(); + + if (!issue.eq("issues")) { + return Err(GeneralError { + description: format!("Issue url is not valid: {}", url), + }); + } + + let url = Url::parse(format!("{}://{}/api/v1/repos/{}/{}/issues/{}", url.scheme(), url.host().unwrap(), domain, repo, iid).as_str()).unwrap(); + + Ok(url) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_issue_html_url_to_api() { + let url = Url::parse("https://gitea.champs-libres.be/champs-libres/test/issues/1").unwrap(); + let result = issue_html_url_to_api(&url).unwrap(); + + assert_eq!(result.as_str(), "https://gitea.champs-libres.be/api/v1/repos/champs-libres/test/issues/1"); + } +} diff --git a/src/gitea/mod.rs b/src/gitea/mod.rs index c47c4ac..32c6c76 100644 --- a/src/gitea/mod.rs +++ b/src/gitea/mod.rs @@ -1,3 +1,4 @@ pub mod client; pub mod issue; pub mod repository; +pub(crate) mod action; diff --git a/src/gitea/repository.rs b/src/gitea/repository.rs index 0e0ba74..80d50db 100644 --- a/src/gitea/repository.rs +++ b/src/gitea/repository.rs @@ -5,5 +5,5 @@ pub struct Repository { id: u64, name: String, owner: String, - full_name: String, + pub full_name: String, } diff --git a/src/gitlab/action.rs b/src/gitlab/action.rs index 1b9822f..ef3c4d7 100644 --- a/src/gitlab/action.rs +++ b/src/gitlab/action.rs @@ -74,7 +74,7 @@ impl Issue2WorkActionTrait for GitlabAction { Ok(()) } - fn supports(&self, url: &Url, config: &Config, args: &Issue2Work) -> bool { + fn supports(&self, url: &Url, config: &Config, _args: &Issue2Work) -> bool { has_client_for_url(&url, &config) } } diff --git a/src/gitlab/client.rs b/src/gitlab/client.rs index 11a3fd0..4b3d2a6 100644 --- a/src/gitlab/client.rs +++ b/src/gitlab/client.rs @@ -39,7 +39,7 @@ pub async fn client_for_url(url: &Url, config: &Config) -> Result bool { for c in &config.gitlab { - if (is_client_for_url(url, c)) { + if is_client_for_url(url, c) { return true; } } diff --git a/src/openproject/user.rs b/src/openproject/user.rs index 3e29a3f..54965a1 100644 --- a/src/openproject/user.rs +++ b/src/openproject/user.rs @@ -11,8 +11,11 @@ pub struct UserLink { #[derive(Deserialize, Debug, Clone)] pub struct User { #[serde(rename = "_type")] + #[allow(unused_variables)] pub d_type: String, + #[allow(unused_variables)] pub id: u64, + #[allow(unused_variables)] pub name: String, #[serde(rename = "_links")] pub d_links: UserLink, diff --git a/src/openproject/work.rs b/src/openproject/work.rs index d9aca46..5520698 100644 --- a/src/openproject/work.rs +++ b/src/openproject/work.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use crate::openproject::user::User; #[derive(Serialize, Debug)] pub struct WorkPackageWriterAssignee { @@ -24,3 +25,14 @@ pub struct WorkPackage { pub id: u64, pub subject: String, } + +impl From> for WorkPackageWriterAssignee { + fn from(value: Option) -> Self { + WorkPackageWriterAssignee { + href: match value { + None => None, + Some(w) => Some(w.clone().d_links.d_self.href), + }, + } + } +} diff --git a/src/planning/issue2work.rs b/src/planning/issue2work.rs index 695728c..2e1189b 100644 --- a/src/planning/issue2work.rs +++ b/src/planning/issue2work.rs @@ -1,34 +1,27 @@ use crate::cli::Issue2Work; use crate::config::Config; use crate::error::GeneralError; +use crate::gitea::action::GiteaAction; use crate::gitlab::action::GitlabAction; use crate::planning::Issue2WorkActionTrait; use url::Url; struct App { gitlab_issue2work_action: GitlabAction, -} - -trait Issue2WorkPackageAction { - fn issue2work_package_action( - &self, - config: &Config, - args: &Issue2Work, - ) -> Result<(), GeneralError>; + gitea_issue2work_action: GiteaAction, } pub(crate) async fn issue2work(config: Config, args: &Issue2Work) -> Result<(), GeneralError> { let url = Url::parse(&*args.issue_url).expect("issue_url is not valid"); let app = App { gitlab_issue2work_action: GitlabAction {}, + gitea_issue2work_action: GiteaAction {}, }; - if (app - .gitlab_issue2work_action - .supports(&url, &config, args) - ) - { + if app.gitlab_issue2work_action.supports(&url, &config, args) { app.gitlab_issue2work_action.run(&url, &config, args).await + } else if app.gitea_issue2work_action.supports(&url, &config, args) { + app.gitea_issue2work_action.run(&url, &config, args).await } else { Err(GeneralError { description: format!("This action is not supported for this url: {}", url),