source/gitea: implement

This commit is contained in:
2026-01-16 13:03:17 +09:00
parent 0b9a7debd3
commit 6ca2e8b2f8
2 changed files with 259 additions and 13 deletions

View File

@@ -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)
}
}
}