#![allow(dead_code)]
use anyhow::Result;
use chrono::{DateTime, Utc};
use octocrab::{Octocrab, Page};
use std::collections::HashMap;
use tracing::info;
pub mod scanner;
pub mod types;
pub use scanner::GitHubScanner;
pub use types::{Repository, Branch, Commit, PullRequest, LanguageStats};
pub struct GitHubClient {
client: Octocrab,
username: String,
}
impl GitHubClient {
pub fn new(token: Option<String>) -> Result<Self> {
let client = if let Some(token) = token {
Octocrab::builder()
.personal_token(token)
.build()?
} else {
Octocrab::builder().build()?
};
Ok(Self {
client,
username: String::new(),
})
}
pub async fn authenticate(&mut self) -> Result<String> {
let user = self.client.current().user().await?;
self.username = user.login.clone();
info!("Authenticated as GitHub user: {}", self.username);
Ok(self.username.clone())
}
pub async fn get_all_repos(&self) -> Result<Vec<Repository>> {
info!("Fetching all repositories for user: {}", self.username);
let mut repos = Vec::new();
let mut page: Page<octocrab::models::Repository> = self.client
.current()
.list_repos_for_authenticated_user()
.per_page(100)
.send()
.await?;
loop {
for repo in &page {
repos.push(Repository::from_octocrab(repo));
}
match self.client.get_page(&page.next).await? {
Some(next_page) => page = next_page,
None => break,
}
}
info!("Found {} repositories", repos.len());
Ok(repos)
}
pub async fn get_branches(&self, owner: &str, repo: &str) -> Result<Vec<Branch>> {
let mut branches = Vec::new();
let mut page = self.client
.repos(owner, repo)
.list_branches()
.per_page(100)
.send()
.await?;
loop {
for branch in &page {
branches.push(Branch::from_octocrab(branch));
}
match self.client.get_page(&page.next).await? {
Some(next_page) => page = next_page,
None => break,
}
}
Ok(branches)
}
pub async fn get_recent_commits(
&self,
owner: &str,
repo: &str,
branch: &str,
since: Option<DateTime<Utc>>,
) -> Result<Vec<Commit>> {
let mut commits = Vec::new();
let repos = self.client.repos(owner, repo);
let mut builder = repos.list_commits().sha(branch).per_page(100);
if let Some(since_date) = since {
builder = builder.since(since_date);
}
let mut page = builder.send().await?;
loop {
for commit in &page {
commits.push(Commit::from_octocrab(commit));
}
if commits.len() >= 100 {
break;
}
match self.client.get_page(&page.next).await? {
Some(next_page) => page = next_page,
None => break,
}
}
Ok(commits)
}
pub async fn get_language_stats(&self, owner: &str, repo: &str) -> Result<LanguageStats> {
let languages: HashMap<String, i64> = self.client
.get(format!("/repos/{}/{}/languages", owner, repo), None::<&()>)
.await?;
let mut stats = HashMap::new();
let total: i64 = languages.values().sum();
for (lang, bytes) in languages {
let percentage = if total > 0 {
(bytes as f64 / total as f64) * 100.0
} else {
0.0
};
stats.insert(lang, (bytes, percentage));
}
Ok(LanguageStats {
languages: stats,
total_bytes: total,
})
}
pub async fn get_recent_prs(
&self,
owner: &str,
repo: &str,
state: &str,
) -> Result<Vec<PullRequest>> {
use octocrab::params::State;
let state_filter = match state {
"open" => State::Open,
"closed" => State::Closed,
_ => State::All,
};
let mut prs = Vec::new();
let mut page = self.client
.pulls(owner, repo)
.list()
.state(state_filter)
.per_page(50)
.send()
.await?;
loop {
for pr in &page {
prs.push(PullRequest::from_octocrab(pr));
}
match self.client.get_page(&page.next).await? {
Some(next_page) => page = next_page,
None => break,
}
}
Ok(prs)
}
pub async fn get_topics(&self, owner: &str, repo: &str) -> Result<Vec<String>> {
let repo_data = self.client
.repos(owner, repo)
.get()
.await?;
Ok(repo_data.topics.unwrap_or_default())
}
pub fn username(&self) -> &str {
&self.username
}
}