integrate-gitea #2
							
								
								
									
										10
									
								
								src/debug.rs
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/debug.rs
									
									
									
									
									
								
							| @@ -1,17 +1,21 @@ | ||||
| use url::Url; | ||||
| use crate::config::Config; | ||||
| use crate::error::GeneralError; | ||||
| use crate::gitea::issue::Issue; | ||||
| use crate::openproject::client::Client; | ||||
| use crate::openproject::root::RootClient; | ||||
| use crate::openproject::user::GetMe; | ||||
| use url::Url; | ||||
|  | ||||
| pub(crate) async fn debug(config: Config) -> Result<(), GeneralError> { | ||||
|     println!("test"); | ||||
|  | ||||
|  | ||||
|     let gitea_client = crate::gitea::client::Client::from_config(config.gitea.first().unwrap()); | ||||
|     let issue: Issue = gitea_client.get(Url::parse("https://gitea.champs-libres.be/api/v1/repos/julienfastre/test/issues/6").unwrap()).await?; | ||||
|     let issue: Issue = gitea_client | ||||
|         .get( | ||||
|             Url::parse("https://gitea.champs-libres.be/api/v1/repos/julienfastre/test/issues/6") | ||||
|                 .unwrap(), | ||||
|         ) | ||||
|         .await?; | ||||
|  | ||||
|     println!("issue: {:?}", issue); | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| use crate::config::{Config, GiteaConfig, GitlabConfig}; | ||||
| use crate::error::GeneralError; | ||||
| use reqwest::header::{HeaderMap, AUTHORIZATION, ACCEPT}; | ||||
| use gitlab::AsyncGitlab; | ||||
| use reqwest::header::{HeaderMap, ACCEPT, AUTHORIZATION}; | ||||
| use reqwest::{ClientBuilder, StatusCode}; | ||||
| use serde::de::DeserializeOwned; | ||||
| use url::Url; | ||||
| use crate::config::GiteaConfig; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct Client { | ||||
| @@ -12,7 +13,6 @@ pub struct Client { | ||||
| } | ||||
|  | ||||
| impl Client { | ||||
|  | ||||
|     pub fn from_config(config: &GiteaConfig) -> Self { | ||||
|         Self::new(&config.token, &config.domain) | ||||
|     } | ||||
| @@ -40,7 +40,7 @@ impl Client { | ||||
|             StatusCode::OK => { | ||||
|                 let result: T = response.json().await?; | ||||
|  | ||||
|                 return Ok(result); | ||||
|                 Ok(result) | ||||
|             } | ||||
|             _ => Err(GeneralError { | ||||
|                 description: format!( | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| use crate::gitea::client::Client; | ||||
| use serde::Deserialize; | ||||
| use crate::gitea::repository::Repository; | ||||
| use serde::Deserialize; | ||||
|  | ||||
| #[derive(Debug, Deserialize)] | ||||
| pub struct Issue { | ||||
| @@ -8,7 +8,7 @@ pub struct Issue { | ||||
|     number: u64, | ||||
|     title: String, | ||||
|     body: String, | ||||
|     repository: Repository | ||||
|     repository: Repository, | ||||
| } | ||||
|  | ||||
| pub trait IssueClient { | ||||
|   | ||||
							
								
								
									
										139
									
								
								src/gitlab/action.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								src/gitlab/action.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| use crate::cli::Issue2Work; | ||||
| use crate::config::Config; | ||||
| use crate::error::GeneralError; | ||||
| use crate::gitlab::client::{client_for_url, has_client_for_url}; | ||||
| use crate::gitlab::issue::IssueBundle; | ||||
| use crate::openproject::client::Client; | ||||
| use crate::openproject::user::{GetMe, User}; | ||||
| use crate::openproject::work::WorkPackageWriterAssignee; | ||||
| use crate::planning::Issue2WorkActionTrait; | ||||
| use gitlab::api::{issues, projects, AsyncQuery}; | ||||
| use gitlab::{Issue, Project}; | ||||
| use url::Url; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct IssueInfo { | ||||
|     project: String, | ||||
|     iid: u64, | ||||
| } | ||||
|  | ||||
| /// details on how to create a work package from various informations | ||||
| #[derive(Debug)] | ||||
| struct Issue2WorkPackageDTO { | ||||
|     pub issue: IssueBundle, | ||||
|     pub assign_to: Option<User>, | ||||
| } | ||||
|  | ||||
| pub(crate) struct GitlabAction {} | ||||
|  | ||||
| impl Issue2WorkActionTrait for GitlabAction { | ||||
|     async fn run(&self, url: &Url, config: &Config, args: &Issue2Work) -> Result<(), GeneralError> { | ||||
|         let client = client_for_url(&url, &config).await?; | ||||
|         let data = extract_issue_info(&url).unwrap(); | ||||
|  | ||||
|         let endpoint = issues::ProjectIssues::builder() | ||||
|             .iid(data.iid) | ||||
|             .project(String::from(data.project)) | ||||
|             .build() | ||||
|             .unwrap(); | ||||
|  | ||||
|         let issues: Vec<Issue> = endpoint.query_async(&client).await.unwrap(); | ||||
|         let issue = issues.first().unwrap(); | ||||
|  | ||||
|         let project_endpoint = projects::Project::builder() | ||||
|             .project(issue.project_id.value()) | ||||
|             .build() | ||||
|             .unwrap(); | ||||
|  | ||||
|         let project: Project = project_endpoint.query_async(&client).await.unwrap(); | ||||
|  | ||||
|         let issue_bundle = IssueBundle::new(&issue, &project); | ||||
|  | ||||
|         let open_project_client = Client::from_config(&config.openproject); | ||||
|  | ||||
|         let dto = Issue2WorkPackageDTO { | ||||
|             issue: issue_bundle, | ||||
|             assign_to: 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(&(&dto).into(), &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 extract_issue_info(url: &Url) -> Option<IssueInfo> { | ||||
|     let parts = url | ||||
|         .path_segments() | ||||
|         .expect("Could not parse path segment of given url"); | ||||
|     let mut project_url: Vec<String> = Vec::with_capacity(3); | ||||
|     let mut iid: Option<String> = None; | ||||
|  | ||||
|     let mut project_found = false; | ||||
|  | ||||
|     for el in parts { | ||||
|         if el == "-" { | ||||
|             project_found = true; | ||||
|             continue; | ||||
|         } | ||||
|         if el == "issues" { | ||||
|             continue; | ||||
|         } | ||||
|         if !project_found { | ||||
|             project_url.push(String::from(el)); | ||||
|         } else { | ||||
|             // must be the id | ||||
|             iid = Some(String::from(el)); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Some(IssueInfo { | ||||
|         project: project_url.join("/"), | ||||
|         iid: iid | ||||
|             .expect("iid of the issue not found") | ||||
|             .parse() | ||||
|             .expect("could not transform issue id to u64"), | ||||
|     }) | ||||
| } | ||||
|  | ||||
| impl From<&Issue2WorkPackageDTO> for crate::openproject::work::WorkPackageWriter { | ||||
|     fn from(value: &Issue2WorkPackageDTO) -> Self { | ||||
|         crate::openproject::work::WorkPackageWriter { | ||||
|             subject: format!( | ||||
|                 "{} ({}/{})", | ||||
|                 value.issue.issue.title, | ||||
|                 value.issue.project.name_with_namespace, | ||||
|                 value.issue.issue.iid | ||||
|             ), | ||||
|             work_type: "TASK".into(), | ||||
|             description: crate::openproject::work::DescriptionWriter { | ||||
|                 format: "markdown".into(), | ||||
|                 raw: format!("From gitlab: {}", value.issue.issue.web_url), | ||||
|             }, | ||||
|             assignee: WorkPackageWriterAssignee { | ||||
|                 href: match &value.assign_to { | ||||
|                     None => None, | ||||
|                     Some(w) => Some(w.clone().d_links.d_self.href), | ||||
|                 }, | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,35 +1,48 @@ | ||||
| use crate::config::Config; | ||||
| use crate::config::{Config, GitlabConfig}; | ||||
| use crate::error::GeneralError; | ||||
| use gitlab::AsyncGitlab; | ||||
| use gitlab::GitlabBuilder; | ||||
| use gitlab::{AsyncGitlab, GitlabBuilder}; | ||||
| use url::Url; | ||||
|  | ||||
| pub trait ClientProviderTrait { | ||||
|     async fn client_for_url(url: &Url, config: &Config) -> Result<AsyncGitlab, GeneralError>; | ||||
| } | ||||
|  | ||||
| pub struct ClientProvider {} | ||||
|  | ||||
| impl ClientProviderTrait for ClientProvider { | ||||
|     async fn client_for_url(url: &Url, config: &Config) -> Result<AsyncGitlab, GeneralError> { | ||||
|         for c in &config.gitlab { | ||||
|             if url.domain() == Some(c.domain.as_str()) { | ||||
|                 let client = GitlabBuilder::new("gitlab.com", c.token.clone()) | ||||
|                     .build_async() | ||||
|                     .await; | ||||
| impl ClientProvider {} | ||||
|  | ||||
|                 return match client { | ||||
|                     Ok(new_client) => Ok(new_client), | ||||
|                     Err(e) => { | ||||
|                         let new_error = e.into(); | ||||
|                         Err(new_error) | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         Err(GeneralError { | ||||
|             description: format!("No client available for this domain: {:?}", url.domain()), | ||||
|         }) | ||||
| fn is_client_for_url(url: &Url, config: &GitlabConfig) -> bool { | ||||
|     if url.domain() == Some(config.domain.as_str()) { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     false | ||||
| } | ||||
|  | ||||
| pub async fn client_for_url(url: &Url, config: &Config) -> Result<AsyncGitlab, GeneralError> { | ||||
|     for c in &config.gitlab { | ||||
|         if is_client_for_url(url, c) { | ||||
|             let client = GitlabBuilder::new("gitlab.com", c.token.clone()) | ||||
|                 .build_async() | ||||
|                 .await; | ||||
|  | ||||
|             return match client { | ||||
|                 Ok(new_client) => Ok(new_client), | ||||
|                 Err(e) => { | ||||
|                     let new_error = e.into(); | ||||
|                     Err(new_error) | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     Err(GeneralError { | ||||
|         description: format!("No client available for this domain: {:?}", url.domain()), | ||||
|     }) | ||||
| } | ||||
|  | ||||
| pub fn has_client_for_url(url: &Url, config: &Config) -> bool { | ||||
|     for c in &config.gitlab { | ||||
|         if (is_client_for_url(url, c)) { | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     false | ||||
| } | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| pub mod action; | ||||
| pub mod client; | ||||
| pub mod issue; | ||||
|  | ||||
|   | ||||
| @@ -1,132 +1,37 @@ | ||||
| use crate::cli::Issue2Work; | ||||
| use crate::config::Config; | ||||
| use crate::error::GeneralError; | ||||
| use crate::gitlab::client::{ClientProvider, ClientProviderTrait}; | ||||
| use crate::gitlab::issue::IssueBundle; | ||||
| use crate::openproject::client::Client; | ||||
| use crate::openproject::user::{GetMe, User}; | ||||
| use crate::openproject::work::WorkPackageWriterAssignee; | ||||
| use gitlab::api::{issues, projects, AsyncQuery}; | ||||
| use gitlab::{Issue, Project}; | ||||
| use crate::gitlab::action::GitlabAction; | ||||
| use crate::planning::Issue2WorkActionTrait; | ||||
| use url::Url; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct IssueInfo { | ||||
|     project: String, | ||||
|     iid: u64, | ||||
| struct App { | ||||
|     gitlab_issue2work_action: GitlabAction, | ||||
| } | ||||
|  | ||||
| /// details on how to create a work package from various informations | ||||
| #[derive(Debug)] | ||||
| struct Issue2WorkPackageDTO { | ||||
|     pub issue: IssueBundle, | ||||
|     pub assign_to: Option<User>, | ||||
| } | ||||
|  | ||||
| impl From<&Issue2WorkPackageDTO> for crate::openproject::work::WorkPackageWriter { | ||||
|     fn from(value: &Issue2WorkPackageDTO) -> Self { | ||||
|         crate::openproject::work::WorkPackageWriter { | ||||
|             subject: format!( | ||||
|                 "{} ({}/{})", | ||||
|                 value.issue.issue.title, | ||||
|                 value.issue.project.name_with_namespace, | ||||
|                 value.issue.issue.iid | ||||
|             ), | ||||
|             work_type: "TASK".into(), | ||||
|             description: crate::openproject::work::DescriptionWriter { | ||||
|                 format: "markdown".into(), | ||||
|                 raw: format!("From gitlab: {}", value.issue.issue.web_url), | ||||
|             }, | ||||
|             assignee: WorkPackageWriterAssignee { | ||||
|                 href: match &value.assign_to { | ||||
|                     None => None, | ||||
|                     Some(w) => Some(w.clone().d_links.d_self.href), | ||||
|                 }, | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| trait Issue2WorkPackageAction { | ||||
|     fn issue2work_package_action( | ||||
|         &self, | ||||
|         config: &Config, | ||||
|         args: &Issue2Work, | ||||
|     ) -> Result<(), GeneralError>; | ||||
| } | ||||
|  | ||||
| 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 data = extract_issue_info(&url).unwrap(); | ||||
|  | ||||
|     let client = ClientProvider::client_for_url(&url, &config).await?; | ||||
|  | ||||
|     let endpoint = issues::ProjectIssues::builder() | ||||
|         .iid(data.iid) | ||||
|         .project(String::from(data.project)) | ||||
|         .build() | ||||
|         .unwrap(); | ||||
|  | ||||
|     let issues: Vec<Issue> = endpoint.query_async(&client).await.unwrap(); | ||||
|     let issue = issues.first().unwrap(); | ||||
|  | ||||
|     let project_endpoint = projects::Project::builder() | ||||
|         .project(issue.project_id.value()) | ||||
|         .build() | ||||
|         .unwrap(); | ||||
|  | ||||
|     let project: Project = project_endpoint.query_async(&client).await.unwrap(); | ||||
|  | ||||
|     let issue_bundle = IssueBundle::new(&issue, &project); | ||||
|  | ||||
|     let open_project_client = Client::from_config(&config.openproject); | ||||
|  | ||||
|     let dto = Issue2WorkPackageDTO { | ||||
|         issue: issue_bundle, | ||||
|         assign_to: match args.assign_to_me { | ||||
|             true => { | ||||
|                 let u = open_project_client.me().await?; | ||||
|                 Some(u) | ||||
|             } | ||||
|             false => None, | ||||
|         }, | ||||
|     let app = App { | ||||
|         gitlab_issue2work_action: GitlabAction {}, | ||||
|     }; | ||||
|  | ||||
|     let work_package = open_project_client | ||||
|         .create_work_package(&(&dto).into(), &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 extract_issue_info(url: &Url) -> Option<IssueInfo> { | ||||
|     let parts = url | ||||
|         .path_segments() | ||||
|         .expect("Could not parse path segment of given url"); | ||||
|     let mut project_url: Vec<String> = Vec::with_capacity(3); | ||||
|     let mut iid: Option<String> = None; | ||||
|  | ||||
|     let mut project_found = false; | ||||
|  | ||||
|     for el in parts { | ||||
|         if el == "-" { | ||||
|             project_found = true; | ||||
|             continue; | ||||
|         } | ||||
|         if el == "issues" { | ||||
|             continue; | ||||
|         } | ||||
|         if !project_found { | ||||
|             project_url.push(String::from(el)); | ||||
|         } else { | ||||
|             // must be the id | ||||
|             iid = Some(String::from(el)); | ||||
|             break; | ||||
|         } | ||||
|     if (app | ||||
|         .gitlab_issue2work_action | ||||
|         .supports(&url, &config, args) | ||||
|         ) | ||||
|     { | ||||
|         app.gitlab_issue2work_action.run(&url, &config, args).await | ||||
|     } else { | ||||
|         Err(GeneralError { | ||||
|             description: format!("This action is not supported for this url: {}", url), | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     Some(IssueInfo { | ||||
|         project: project_url.join("/"), | ||||
|         iid: iid | ||||
|             .expect("iid of the issue not found") | ||||
|             .parse() | ||||
|             .expect("could not transform issue id to u64"), | ||||
|     }) | ||||
| } | ||||
|   | ||||
| @@ -1 +1,14 @@ | ||||
| use crate::cli::Issue2Work; | ||||
| use crate::config::Config; | ||||
| use crate::error::GeneralError; | ||||
| use crate::gitlab::client::ClientProvider; | ||||
| use gitlab::{AsyncGitlab, GitlabBuilder}; | ||||
| use url::Url; | ||||
|  | ||||
| pub(crate) mod issue2work; | ||||
|  | ||||
| pub trait Issue2WorkActionTrait { | ||||
|     async fn run(&self, url: &Url, config: &Config, args: &Issue2Work) -> Result<(), GeneralError>; | ||||
|  | ||||
|     fn supports(&self, url: &Url, config: &Config, args: &Issue2Work) -> bool; | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user