feat: add github fetch prs refactoring
All checks were successful
continuous-integration/drone/push Build is passing

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
2024-09-14 15:10:46 +02:00
parent f1b9a373d5
commit 6a0900e190
8 changed files with 720 additions and 55 deletions

View File

@@ -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"

View File

@@ -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 {

View File

@@ -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![

View File

@@ -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;

View File

@@ -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();

View File

@@ -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(&current_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)
}
}

View File

@@ -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 {