feat: add github fetch prs refactoring
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
@@ -19,3 +19,4 @@ toml = "0.8.19"
|
||||
|
||||
gitea-rs = { git = "https://git.front.kjuulh.io/kjuulh/gitea-rs", version = "1.22.1" }
|
||||
url = "2.5.2"
|
||||
octocrab = "0.39.0"
|
||||
|
@@ -1,10 +1,4 @@
|
||||
use crate::{
|
||||
app::App,
|
||||
git_provider::{
|
||||
gitea::GiteaProviderApp, github::GitHubProviderApp, GitProvider, VecRepositoryExt,
|
||||
},
|
||||
projects_list::ProjectsListApp,
|
||||
};
|
||||
use crate::{ app::App, projects_list::ProjectsListApp};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RootCommand {
|
||||
|
@@ -19,6 +19,14 @@ pub struct Providers {
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct GitHub {
|
||||
#[serde(default)]
|
||||
pub url: Option<String>,
|
||||
|
||||
pub access_token: GitHubAccessToken,
|
||||
|
||||
#[serde(default)]
|
||||
pub current_user: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub users: Vec<GitHubUser>,
|
||||
#[serde(default)]
|
||||
@@ -28,12 +36,38 @@ pub struct GitHub {
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct GitHubUser(String);
|
||||
|
||||
impl From<GitHubUser> for String {
|
||||
fn from(value: GitHubUser) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a GitHubUser> for &'a str {
|
||||
fn from(value: &'a GitHubUser) -> Self {
|
||||
value.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct GitHubOrganisation(String);
|
||||
|
||||
impl From<GitHubOrganisation> for String {
|
||||
fn from(value: GitHubOrganisation) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a GitHubOrganisation> for &'a str {
|
||||
fn from(value: &'a GitHubOrganisation) -> Self {
|
||||
value.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Gitea {
|
||||
pub url: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub access_token: Option<GiteaAccessToken>,
|
||||
|
||||
#[serde(default)]
|
||||
@@ -52,6 +86,13 @@ pub enum GiteaAccessToken {
|
||||
Env { env: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum GitHubAccessToken {
|
||||
Direct(String),
|
||||
Env { env: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct GiteaUser(String);
|
||||
|
||||
@@ -138,11 +179,19 @@ mod test {
|
||||
github: vec![
|
||||
GitHub {
|
||||
users: vec![GitHubUser("kjuulh".into())],
|
||||
organisations: vec![GitHubOrganisation("lunarway".into())]
|
||||
organisations: vec![GitHubOrganisation("lunarway".into())],
|
||||
url: None,
|
||||
access_token: GitHubAccessToken::Direct("some-token".into()),
|
||||
current_user: Some("kjuulh".into())
|
||||
},
|
||||
GitHub {
|
||||
users: vec![GitHubUser("other".into())],
|
||||
organisations: vec![GitHubOrganisation("org".into())]
|
||||
organisations: vec![GitHubOrganisation("org".into())],
|
||||
url: None,
|
||||
access_token: GitHubAccessToken::Env {
|
||||
env: "something".into()
|
||||
},
|
||||
current_user: None
|
||||
}
|
||||
],
|
||||
gitea: vec![
|
||||
|
@@ -1,6 +1,4 @@
|
||||
use std::{collections::HashMap, path::PathBuf, str::FromStr};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd)]
|
||||
pub struct Repository {
|
||||
@@ -31,14 +29,5 @@ impl VecRepositoryExt for Vec<Repository> {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait GitProvider {
|
||||
async fn list_repositories_for_user(&self, user: &str) -> anyhow::Result<Vec<Repository>>;
|
||||
async fn list_repositories_for_organisation(
|
||||
&self,
|
||||
organisation: &str,
|
||||
) -> anyhow::Result<Vec<Repository>>;
|
||||
}
|
||||
|
||||
pub mod gitea;
|
||||
pub mod github;
|
||||
|
@@ -1,11 +1,12 @@
|
||||
use anyhow::Context;
|
||||
use gitea_rs::apis::configuration::{ApiKey, Configuration};
|
||||
use gitea_rs::apis::configuration::Configuration;
|
||||
use url::Url;
|
||||
|
||||
use crate::{app::App, config::GiteaAccessToken};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GiteaProvider {
|
||||
#[allow(dead_code)]
|
||||
app: &'static App,
|
||||
}
|
||||
|
||||
@@ -14,14 +15,12 @@ impl GiteaProvider {
|
||||
GiteaProvider { app }
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn list_repositories_for_current_user(
|
||||
&self,
|
||||
user: &str,
|
||||
api: &str,
|
||||
access_token: Option<&GiteaAccessToken>,
|
||||
) -> anyhow::Result<Vec<super::Repository>> {
|
||||
tracing::debug!("fetching gitea repositories for user");
|
||||
tracing::debug!("fetching gitea repositories for current user");
|
||||
|
||||
let config = self.get_config(api, access_token)?;
|
||||
|
||||
@@ -29,7 +28,7 @@ impl GiteaProvider {
|
||||
let mut page = 1;
|
||||
loop {
|
||||
let mut repos = self
|
||||
.list_repositories_for_current_user_with_page(user, &config, page)
|
||||
.list_repositories_for_current_user_with_page(&config, page)
|
||||
.await?;
|
||||
|
||||
if repos.is_empty() {
|
||||
@@ -65,10 +64,8 @@ impl GiteaProvider {
|
||||
Ok(provider.into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn list_repositories_for_current_user_with_page(
|
||||
&self,
|
||||
user: &str,
|
||||
config: &Configuration,
|
||||
page: usize,
|
||||
) -> anyhow::Result<Vec<gitea_rs::models::Repository>> {
|
||||
@@ -80,14 +77,13 @@ impl GiteaProvider {
|
||||
Ok(repos)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn list_repositories_for_user(
|
||||
&self,
|
||||
user: &str,
|
||||
api: &str,
|
||||
access_token: Option<&GiteaAccessToken>,
|
||||
) -> anyhow::Result<Vec<super::Repository>> {
|
||||
tracing::debug!("fetching gitea repositories for user");
|
||||
tracing::debug!(user = user, "fetching gitea repositories for user");
|
||||
|
||||
let config = self.get_config(api, access_token)?;
|
||||
|
||||
@@ -146,6 +142,10 @@ impl GiteaProvider {
|
||||
api: &str,
|
||||
access_token: Option<&GiteaAccessToken>,
|
||||
) -> anyhow::Result<Vec<super::Repository>> {
|
||||
tracing::debug!(
|
||||
organisation = organisation,
|
||||
"fetching gitea repositories for organisation"
|
||||
);
|
||||
let config = self.get_config(api, access_token)?;
|
||||
|
||||
let mut repositories = Vec::new();
|
||||
|
@@ -1,10 +1,15 @@
|
||||
use async_trait::async_trait;
|
||||
use anyhow::Context;
|
||||
use octocrab::{
|
||||
auth::Auth,
|
||||
models::{hooks::Config, Repository},
|
||||
params::repos::Sort,
|
||||
NoSvc, Octocrab, Page,
|
||||
};
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
use super::GitProvider;
|
||||
use crate::{app::App, config::GitHubAccessToken};
|
||||
|
||||
pub struct GitHubProvider {
|
||||
#[allow(dead_code)]
|
||||
app: &'static App,
|
||||
}
|
||||
|
||||
@@ -12,22 +17,159 @@ impl GitHubProvider {
|
||||
pub fn new(app: &'static App) -> GitHubProvider {
|
||||
GitHubProvider { app }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl GitProvider for GitHubProvider {
|
||||
async fn list_repositories_for_user(
|
||||
pub async fn list_repositories_for_current_user(
|
||||
&self,
|
||||
user: &str,
|
||||
url: Option<&String>,
|
||||
access_token: &GitHubAccessToken,
|
||||
) -> anyhow::Result<Vec<super::Repository>> {
|
||||
todo!()
|
||||
tracing::debug!("fetching github repositories for current user");
|
||||
|
||||
let client = self.get_client(url, access_token)?;
|
||||
|
||||
let current_page = client
|
||||
.current()
|
||||
.list_repos_for_authenticated_user()
|
||||
.type_("all")
|
||||
.per_page(100)
|
||||
.sort("full_name")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let repos = self.unfold_pages(client, current_page).await?;
|
||||
|
||||
Ok(repos
|
||||
.into_iter()
|
||||
.filter_map(|repo| {
|
||||
Some(super::Repository {
|
||||
provider: self.get_url(url),
|
||||
owner: repo.owner.map(|su| su.login)?,
|
||||
repo_name: repo.name,
|
||||
ssh_url: repo.ssh_url?,
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn list_repositories_for_organisation(
|
||||
pub async fn list_repositories_for_user(
|
||||
&self,
|
||||
user: &str,
|
||||
url: Option<&String>,
|
||||
access_token: &GitHubAccessToken,
|
||||
) -> anyhow::Result<Vec<super::Repository>> {
|
||||
tracing::debug!(user = user, "fetching github repositories for user");
|
||||
|
||||
let client = self.get_client(url, access_token)?;
|
||||
|
||||
let current_page = client
|
||||
.users(user)
|
||||
.repos()
|
||||
.r#type(octocrab::params::users::repos::Type::All)
|
||||
.sort(Sort::FullName)
|
||||
.per_page(100)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let repos = self.unfold_pages(client, current_page).await?;
|
||||
|
||||
Ok(repos
|
||||
.into_iter()
|
||||
.filter_map(|repo| {
|
||||
Some(super::Repository {
|
||||
provider: self.get_url(url),
|
||||
owner: repo.owner.map(|su| su.login)?,
|
||||
repo_name: repo.name,
|
||||
ssh_url: repo.ssh_url?,
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn list_repositories_for_organisation(
|
||||
&self,
|
||||
organisation: &str,
|
||||
url: Option<&String>,
|
||||
access_token: &GitHubAccessToken,
|
||||
) -> anyhow::Result<Vec<super::Repository>> {
|
||||
todo!()
|
||||
tracing::debug!(
|
||||
organisation = organisation,
|
||||
"fetching github repositories for organisation"
|
||||
);
|
||||
|
||||
let client = self.get_client(url, access_token)?;
|
||||
|
||||
let current_page = client
|
||||
.orgs(organisation)
|
||||
.list_repos()
|
||||
.repo_type(Some(octocrab::params::repos::Type::All))
|
||||
.sort(Sort::FullName)
|
||||
.per_page(100)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let repos = self.unfold_pages(client, current_page).await?;
|
||||
|
||||
Ok(repos
|
||||
.into_iter()
|
||||
.filter_map(|repo| {
|
||||
Some(super::Repository {
|
||||
provider: self.get_url(url),
|
||||
owner: repo.owner.map(|su| su.login)?,
|
||||
repo_name: repo.name,
|
||||
ssh_url: repo.ssh_url?,
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn unfold_pages(
|
||||
&self,
|
||||
client: octocrab::Octocrab,
|
||||
page: Page<Repository>,
|
||||
) -> anyhow::Result<Vec<Repository>> {
|
||||
let mut current_page = page;
|
||||
|
||||
let mut repos = current_page.take_items();
|
||||
while let Ok(Some(mut new_page)) = client.get_page(¤t_page.next).await {
|
||||
repos.extend(new_page.take_items());
|
||||
|
||||
current_page = new_page;
|
||||
}
|
||||
|
||||
Ok(repos)
|
||||
}
|
||||
|
||||
fn get_url(&self, url: Option<&String>) -> String {
|
||||
let default_domain = "github.com".to_string();
|
||||
|
||||
if let Some(url) = url {
|
||||
let Some(url) = url::Url::parse(url).ok() else {
|
||||
return default_domain;
|
||||
};
|
||||
|
||||
let Some(domain) = url.domain().map(|d| d.to_string()) else {
|
||||
return default_domain;
|
||||
};
|
||||
|
||||
domain
|
||||
} else {
|
||||
default_domain
|
||||
}
|
||||
}
|
||||
|
||||
fn get_client(
|
||||
&self,
|
||||
url: Option<&String>,
|
||||
access_token: &GitHubAccessToken,
|
||||
) -> anyhow::Result<Octocrab> {
|
||||
let client = octocrab::Octocrab::builder()
|
||||
.personal_token(match access_token {
|
||||
GitHubAccessToken::Direct(token) => token.to_owned(),
|
||||
GitHubAccessToken::Env { env } => std::env::var(env)?,
|
||||
})
|
||||
.build()?;
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,8 @@
|
||||
use crate::{
|
||||
app::App,
|
||||
git_provider::{gitea::GiteaProviderApp, Repository, VecRepositoryExt},
|
||||
git_provider::{
|
||||
gitea::GiteaProviderApp, github::GitHubProviderApp, Repository, VecRepositoryExt,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct ProjectsList {
|
||||
@@ -16,6 +18,7 @@ impl ProjectsList {
|
||||
let mut repositories = Vec::new();
|
||||
|
||||
repositories.extend(self.get_gitea_projects().await?);
|
||||
repositories.extend(self.get_github_projects().await?);
|
||||
|
||||
repositories.collect_unique();
|
||||
|
||||
@@ -27,13 +30,9 @@ impl ProjectsList {
|
||||
|
||||
let mut repositories = Vec::new();
|
||||
for gitea in self.app.config.providers.gitea.iter() {
|
||||
if let Some(user) = &gitea.current_user {
|
||||
if let Some(_user) = &gitea.current_user {
|
||||
let mut repos = gitea_provider
|
||||
.list_repositories_for_current_user(
|
||||
user,
|
||||
&gitea.url,
|
||||
gitea.access_token.as_ref(),
|
||||
)
|
||||
.list_repositories_for_current_user(&gitea.url, gitea.access_token.as_ref())
|
||||
.await?;
|
||||
|
||||
repositories.append(&mut repos);
|
||||
@@ -66,6 +65,47 @@ impl ProjectsList {
|
||||
|
||||
Ok(repositories)
|
||||
}
|
||||
|
||||
async fn get_github_projects(&self) -> anyhow::Result<Vec<Repository>> {
|
||||
let github_provider = self.app.github_provider();
|
||||
|
||||
let mut repositories = Vec::new();
|
||||
for github in self.app.config.providers.github.iter() {
|
||||
if let Some(_user) = &github.current_user {
|
||||
let mut repos = github_provider
|
||||
.list_repositories_for_current_user(github.url.as_ref(), &github.access_token)
|
||||
.await?;
|
||||
|
||||
repositories.append(&mut repos);
|
||||
}
|
||||
|
||||
for github_user in github.users.iter() {
|
||||
let mut repos = github_provider
|
||||
.list_repositories_for_user(
|
||||
github_user.into(),
|
||||
github.url.as_ref(),
|
||||
&github.access_token,
|
||||
)
|
||||
.await?;
|
||||
|
||||
repositories.append(&mut repos);
|
||||
}
|
||||
|
||||
for github_org in github.organisations.iter() {
|
||||
let mut repos = github_provider
|
||||
.list_repositories_for_organisation(
|
||||
github_org.into(),
|
||||
github.url.as_ref(),
|
||||
&github.access_token,
|
||||
)
|
||||
.await?;
|
||||
|
||||
repositories.append(&mut repos);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(repositories)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ProjectsListApp {
|
||||
|
Reference in New Issue
Block a user