source/github: implement
This commit is contained in:
@@ -1,11 +1,215 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use anyhow::Context;
|
||||
use octocrab::{Octocrab, Page, models};
|
||||
|
||||
use crate::sources::{KagamiSource, RepositoryFilter, RepositoryInfo, RepositoryVisibility};
|
||||
|
||||
pub struct KagamiSourceGithub {
|
||||
host: String,
|
||||
host: Option<String>,
|
||||
}
|
||||
|
||||
impl KagamiSourceGithub {
|
||||
pub fn new(host: &str) -> Self {
|
||||
Self {
|
||||
host: host.to_string(),
|
||||
pub fn new(host: Option<&str>) -> Self {
|
||||
Self {
|
||||
host: host.map(|s| s.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KagamiSource for KagamiSourceGithub {
|
||||
const NAME: &'static str = "github";
|
||||
const DEFAULT_HOST: &'static str = "github.com";
|
||||
|
||||
fn api_base(&self) -> String {
|
||||
match self.host.as_deref() {
|
||||
Some(host) if host != "github.com" => format!("https://{}/api/v3", host),
|
||||
_ => "https://api.github.com".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_repositories_by_user(
|
||||
&self,
|
||||
owner: &str,
|
||||
token: Option<&str>,
|
||||
_filter: &RepositoryFilter,
|
||||
) -> anyhow::Result<HashSet<RepositoryInfo>> {
|
||||
let octocrab = {
|
||||
let mut builder = Octocrab::builder();
|
||||
if let Some(t) = token {
|
||||
builder = builder.personal_token(t.to_owned());
|
||||
}
|
||||
if self.host.is_some() {
|
||||
builder = builder
|
||||
.base_uri(self.api_base())
|
||||
.context("failed to set octocrab base_uri")?;
|
||||
}
|
||||
builder.build().context("failed to build octocrab client")?
|
||||
};
|
||||
|
||||
let mut page: Option<Page<models::Repository>> = None;
|
||||
let mut repos = HashSet::new();
|
||||
|
||||
loop {
|
||||
let current: Page<models::Repository> = match page {
|
||||
None => octocrab
|
||||
.get(
|
||||
&format!("/users/{}/repos", owner),
|
||||
Some(&[("per_page", "100")]),
|
||||
)
|
||||
.await
|
||||
.context("failed to fetch user repositories")?,
|
||||
Some(ref p) => {
|
||||
match octocrab.get_page(&p.next).await {
|
||||
Ok(Some(next_page)) => next_page,
|
||||
Ok(None) => break,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for r in ¤t.items {
|
||||
let ri = map_github_repo_to_info(r);
|
||||
repos.insert(ri);
|
||||
}
|
||||
|
||||
if current.next.is_none() {
|
||||
break;
|
||||
} else {
|
||||
page = Some(current);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(repos)
|
||||
}
|
||||
|
||||
async fn get_repositories_by_organization(
|
||||
&self,
|
||||
owner: &str,
|
||||
token: Option<&str>,
|
||||
// NOTE: GitHub nested teams doesn't remove the organization repos; this flag is unused.
|
||||
_recurse_groups: bool,
|
||||
_filter: &RepositoryFilter,
|
||||
) -> anyhow::Result<HashSet<RepositoryInfo>> {
|
||||
let octocrab = {
|
||||
let mut builder = Octocrab::builder();
|
||||
if let Some(t) = token {
|
||||
builder = builder.personal_token(t.to_owned());
|
||||
}
|
||||
if self.host.is_some() {
|
||||
builder = builder
|
||||
.base_uri(self.api_base())
|
||||
.context("failed to set octocrab base_uri")?;
|
||||
}
|
||||
builder.build().context("failed to build octocrab client")?
|
||||
};
|
||||
|
||||
let mut repos = HashSet::new();
|
||||
let mut page: Option<Page<models::Repository>> = None;
|
||||
|
||||
loop {
|
||||
let current: Page<models::Repository> = match page {
|
||||
None => octocrab
|
||||
.get(
|
||||
&format!("/orgs/{}/repos", owner),
|
||||
Some(&[("per_page", "100")]),
|
||||
)
|
||||
.await
|
||||
.context("failed to fetch organization repositories")?,
|
||||
Some(ref p) => match octocrab.get_page(&p.next).await {
|
||||
Ok(Some(next_page)) => next_page,
|
||||
Ok(None) => break,
|
||||
Err(_) => break,
|
||||
},
|
||||
};
|
||||
|
||||
for r in ¤t.items {
|
||||
let ri = map_github_repo_to_info(r);
|
||||
repos.insert(ri);
|
||||
}
|
||||
|
||||
if current.next.is_none() {
|
||||
break;
|
||||
} else {
|
||||
page = Some(current);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(repos)
|
||||
}
|
||||
|
||||
async fn get_repositories_by_stars(
|
||||
&self,
|
||||
owner: &str,
|
||||
token: Option<&str>,
|
||||
_filter: &RepositoryFilter,
|
||||
) -> anyhow::Result<HashSet<RepositoryInfo>> {
|
||||
let octocrab = {
|
||||
let mut builder = Octocrab::builder();
|
||||
if let Some(t) = token {
|
||||
builder = builder.personal_token(t.to_owned());
|
||||
}
|
||||
if self.host.is_some() {
|
||||
builder = builder
|
||||
.base_uri(self.api_base())
|
||||
.context("failed to set octocrab base_uri")?;
|
||||
}
|
||||
builder.build().context("failed to build octocrab client")?
|
||||
};
|
||||
|
||||
let mut page: Option<Page<models::Repository>> = None;
|
||||
let mut repos = HashSet::new();
|
||||
|
||||
loop {
|
||||
let current: Page<models::Repository> = match page {
|
||||
None => octocrab
|
||||
.get(
|
||||
&format!("/users/{}/starred", owner),
|
||||
Some(&[("per_page", "100")]),
|
||||
)
|
||||
.await
|
||||
.context("failed to fetch starred repositories")?,
|
||||
Some(ref p) => match octocrab.get_page(&p.next).await {
|
||||
Ok(Some(next_page)) => next_page,
|
||||
Ok(None) => break,
|
||||
Err(_) => break,
|
||||
},
|
||||
};
|
||||
|
||||
for r in ¤t.items {
|
||||
let ri = map_github_repo_to_info(r);
|
||||
repos.insert(ri);
|
||||
}
|
||||
|
||||
if current.next.is_none() {
|
||||
break;
|
||||
} else {
|
||||
page = Some(current);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(repos)
|
||||
}
|
||||
}
|
||||
|
||||
fn map_github_repo_to_info(r: &models::Repository) -> RepositoryInfo {
|
||||
RepositoryInfo {
|
||||
name: r.name.clone(),
|
||||
// TODO: don't unwrap with string default here
|
||||
url: r
|
||||
.html_url
|
||||
.clone()
|
||||
.map(|u| u.to_string())
|
||||
.unwrap_or_default(),
|
||||
icon_url: r.owner.as_ref().map(|o| o.avatar_url.to_string()),
|
||||
is_fork: r.fork.unwrap_or(false),
|
||||
is_archived: r.archived.unwrap_or(false),
|
||||
visibility: if r.private.unwrap_or(false) {
|
||||
RepositoryVisibility::Private
|
||||
} else {
|
||||
RepositoryVisibility::Public
|
||||
},
|
||||
stars: r.stargazers_count.unwrap_or(0),
|
||||
description: r.description.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user