source/gitea: implement
This commit is contained in:
@@ -8,7 +8,9 @@ use std::collections::HashSet;
|
||||
|
||||
pub trait KagamiSource {
|
||||
const NAME: &'static str;
|
||||
const DEFAULT_URI_PREFIX: &'static str;
|
||||
const DEFAULT_HOST: &'static str;
|
||||
|
||||
fn api_base(&self) -> String;
|
||||
|
||||
// Get Repository
|
||||
// - Use native API if available
|
||||
@@ -47,28 +49,47 @@ pub trait KagamiSource {
|
||||
) -> impl std::future::Future<Output = anyhow::Result<HashSet<RepositoryInfo>>> + Send;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum RepositoryVisibility {
|
||||
Public,
|
||||
Internal,
|
||||
Private,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum RegexFilter {
|
||||
Include(regex::Regex),
|
||||
Exclude(regex::Regex),
|
||||
Include(String),
|
||||
Exclude(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct RepositoryFilter {
|
||||
pub filter_regex: Vec<RegexFilter>,
|
||||
pub include_forks: bool,
|
||||
pub include_archived: bool,
|
||||
pub visibility: HashSet<RepositoryVisibility>,
|
||||
pub min_stars: Option<u32>,
|
||||
pub max_stars: Option<u32>,
|
||||
pub include_public: bool,
|
||||
pub include_internal: bool,
|
||||
pub include_private: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
impl Default for RepositoryFilter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
filter_regex: Vec::new(),
|
||||
include_forks: true,
|
||||
include_archived: true,
|
||||
min_stars: None,
|
||||
max_stars: None,
|
||||
include_public: true,
|
||||
include_internal: true,
|
||||
include_private: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct RepositoryInfo {
|
||||
pub name: String,
|
||||
pub full_name: String,
|
||||
|
||||
@@ -1,11 +1,236 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::sources::{KagamiSource, RepositoryFilter, RepositoryInfo, RepositoryVisibility};
|
||||
|
||||
use gitea_sdk;
|
||||
use gitea_sdk::Auth::Token;
|
||||
use gitea_sdk::Client as GiteaClient;
|
||||
|
||||
pub struct KagamiSourceGitea {
|
||||
host: String,
|
||||
host: Option<String>,
|
||||
}
|
||||
|
||||
impl KagamiSourceGitea {
|
||||
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 KagamiSourceGitea {
|
||||
const NAME: &'static str = "gitea";
|
||||
const DEFAULT_HOST: &'static str = "gitea.com";
|
||||
|
||||
fn api_base(&self) -> String {
|
||||
format!("https://{}", self.host.as_deref().unwrap_or(Self::DEFAULT_HOST))
|
||||
}
|
||||
|
||||
async fn get_repositories_by_user(
|
||||
&self,
|
||||
owner: &str,
|
||||
token: Option<&str>,
|
||||
_filter: &RepositoryFilter,
|
||||
) -> anyhow::Result<HashSet<RepositoryInfo>> {
|
||||
let base = self.api_base();
|
||||
|
||||
let client: GiteaClient = match token {
|
||||
Some(tok) if !tok.is_empty() => GiteaClient::new(&base, Token(tok.to_string())),
|
||||
_ => GiteaClient::new(&base, gitea_sdk::Auth::<String>::None),
|
||||
};
|
||||
|
||||
let mut page = 1i64;
|
||||
let mut set = HashSet::new();
|
||||
|
||||
loop {
|
||||
let repos_resp = client
|
||||
.users(owner)
|
||||
.list_repos()
|
||||
.limit(100)
|
||||
.page(page)
|
||||
.send(&client)
|
||||
.await
|
||||
.context("failed to list gitea user repos")?;
|
||||
|
||||
if repos_resp.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
for r in repos_resp.iter() {
|
||||
let info = RepositoryInfo {
|
||||
name: r.name.clone(),
|
||||
full_name: r.full_name.clone(),
|
||||
url: r.html_url.clone(),
|
||||
icon_url: if r.avatar_url.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(r.avatar_url.clone())
|
||||
},
|
||||
is_fork: r.fork,
|
||||
is_archived: r.archived,
|
||||
visibility: if r.private {
|
||||
RepositoryVisibility::Private
|
||||
} else if r.internal {
|
||||
RepositoryVisibility::Internal
|
||||
} else {
|
||||
RepositoryVisibility::Public
|
||||
},
|
||||
stars: r.stars_count as u32,
|
||||
description: if r.description.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(r.description.clone())
|
||||
},
|
||||
};
|
||||
set.insert(info);
|
||||
}
|
||||
|
||||
if repos_resp.len() < 100 {
|
||||
break;
|
||||
}
|
||||
page += 1;
|
||||
}
|
||||
|
||||
Ok(set)
|
||||
}
|
||||
|
||||
async fn get_repositories_by_organization(
|
||||
&self,
|
||||
owner: &str,
|
||||
token: Option<&str>,
|
||||
// NOTE: Gitea does not have nested organizations/groups like GitLab,
|
||||
_recurse_groups: bool,
|
||||
_filter: &RepositoryFilter,
|
||||
) -> anyhow::Result<HashSet<RepositoryInfo>> {
|
||||
let base = self.api_base();
|
||||
|
||||
let client: GiteaClient = match token {
|
||||
Some(tok) if !tok.is_empty() => GiteaClient::new(&base, Token(tok.to_string())),
|
||||
_ => GiteaClient::new(&base, gitea_sdk::Auth::<String>::None),
|
||||
};
|
||||
|
||||
let mut page = 1i64;
|
||||
let mut set = HashSet::new();
|
||||
|
||||
loop {
|
||||
let repos_resp = client
|
||||
.orgs(owner)
|
||||
.list_repos()
|
||||
.limit(100)
|
||||
.page(page)
|
||||
.send(&client)
|
||||
.await
|
||||
.context("failed to list gitea org repos")?;
|
||||
|
||||
if repos_resp.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
for r in repos_resp.iter() {
|
||||
let info = RepositoryInfo {
|
||||
name: r.name.clone(),
|
||||
full_name: r.full_name.clone(),
|
||||
url: r.html_url.clone(),
|
||||
icon_url: if r.avatar_url.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(r.avatar_url.clone())
|
||||
},
|
||||
is_fork: r.fork,
|
||||
is_archived: r.archived,
|
||||
visibility: if r.private {
|
||||
RepositoryVisibility::Private
|
||||
} else if r.internal {
|
||||
RepositoryVisibility::Internal
|
||||
} else {
|
||||
RepositoryVisibility::Public
|
||||
},
|
||||
stars: r.stars_count as u32,
|
||||
description: if r.description.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(r.description.clone())
|
||||
},
|
||||
};
|
||||
set.insert(info);
|
||||
}
|
||||
|
||||
if repos_resp.len() < 100 {
|
||||
break;
|
||||
}
|
||||
page += 1;
|
||||
}
|
||||
|
||||
Ok(set)
|
||||
}
|
||||
|
||||
async fn get_repositories_by_stars(
|
||||
&self,
|
||||
owner: &str,
|
||||
token: Option<&str>,
|
||||
_filter: &RepositoryFilter,
|
||||
) -> anyhow::Result<HashSet<RepositoryInfo>> {
|
||||
let base = self.api_base();
|
||||
|
||||
let client: GiteaClient = match token {
|
||||
Some(tok) if !tok.is_empty() => GiteaClient::new(&base, Token(tok.to_string())),
|
||||
_ => GiteaClient::new(&base, gitea_sdk::Auth::<String>::None),
|
||||
};
|
||||
|
||||
let mut page = 1u64;
|
||||
let mut set = HashSet::new();
|
||||
|
||||
loop {
|
||||
let repos_resp = client
|
||||
.users(owner)
|
||||
.list_starred()
|
||||
.limit(100u64)
|
||||
.page(page)
|
||||
.send(&client)
|
||||
.await
|
||||
.context("failed to list gitea starred repos")?;
|
||||
|
||||
if repos_resp.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
for r in repos_resp.iter() {
|
||||
let info = RepositoryInfo {
|
||||
name: r.name.clone(),
|
||||
full_name: r.full_name.clone(),
|
||||
url: r.html_url.clone(),
|
||||
icon_url: if r.avatar_url.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(r.avatar_url.clone())
|
||||
},
|
||||
is_fork: r.fork,
|
||||
is_archived: r.archived,
|
||||
visibility: if r.private {
|
||||
RepositoryVisibility::Private
|
||||
} else if r.internal {
|
||||
RepositoryVisibility::Internal
|
||||
} else {
|
||||
RepositoryVisibility::Public
|
||||
},
|
||||
stars: r.stars_count as u32,
|
||||
description: if r.description.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(r.description.clone())
|
||||
},
|
||||
};
|
||||
set.insert(info);
|
||||
}
|
||||
|
||||
if repos_resp.len() < 100 {
|
||||
break;
|
||||
}
|
||||
page += 1;
|
||||
}
|
||||
|
||||
Ok(set)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user