mod activity;
mod api_metrics;
mod local;
mod security;
use chrono::Utc;
use log::info;
use octocrab::{Octocrab, models::Repository};
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use crate::github::search_repositories::cache::SearchCache;
use crate::github::search_repositories::config::SearchConfig;
use crate::github::search_repositories::rate_limiter::RateLimiter;
use crate::github::search_repositories::types::{
QualityMetrics, RepositoryResult, SearchError, SearchResult, WikiInfo,
};
pub(crate) use activity::compute_activity;
pub(crate) use api_metrics::compute_api_metrics;
pub(crate) use local::{LocalAnalysisContext, local_analysis};
pub(crate) use security::calculate_security_score;
pub(crate) async fn analyze_repo(
octocrab: Arc<Octocrab>,
cache: Arc<Mutex<SearchCache>>,
repo: Repository,
_token: String,
config: SearchConfig,
_rate_limiter: Arc<RwLock<RateLimiter>>,
) -> SearchResult<RepositoryResult> {
let repo_name = repo.full_name.as_deref().unwrap_or("unknown");
let url = repo.clone_url.as_ref().map_or("", reqwest::Url::as_str);
let stars = repo.stargazers_count.unwrap_or(0);
let owner_login = repo
.owner
.as_ref()
.ok_or_else(|| SearchError::InvalidQuery {
details: format!(
"Repository '{repo_name}' has no owner information - cannot fetch commits"
),
})?
.login
.as_str();
let commits_resp = octocrab
.repos(owner_login, &repo.name)
.list_commits()
.per_page(1)
.send()
.await
.map_err(|e| SearchError::ApiError(e.to_string()))?;
let latest_sha = commits_resp
.items
.first()
.map(|c| c.sha.clone())
.unwrap_or_default();
{
let mut c = cache.lock().await;
if let Some(found) = c.get_if_valid(repo_name, &latest_sha) {
info!("Cache hit for {repo_name}");
return Ok(found);
}
}
let activity = compute_activity(&commits_resp.items, &octocrab, owner_login, &repo.name).await;
let (api_score, ()) = compute_api_metrics(&repo, &activity);
let wiki_info = WikiInfo {
has_wiki: repo.has_wiki.unwrap_or(false),
clone_url: url.to_string(),
};
let context = LocalAnalysisContext {
repo_name,
url,
owner: owner_login,
repo_name_str: &repo.name,
wiki_info,
};
let local_scores = local_analysis(context, &repo, octocrab.clone(), &config).await?;
let overall_score = 0.7 * api_score + 0.3 * local_scores.overall_local;
let result = RepositoryResult {
name: repo.name.clone(),
full_name: repo_name.to_string(),
url: repo
.html_url
.as_ref()
.map_or("", reqwest::Url::as_str)
.to_string(),
clone_url: url.to_string(),
description: repo.description,
stars,
forks: repo.forks_count.unwrap_or(0),
watchers: repo.watchers_count.unwrap_or(0),
language: repo
.language
.as_ref()
.and_then(|v| v.as_str())
.map(std::string::ToString::to_string),
topics: repo.topics.unwrap_or_default(),
license: repo.license.map(|l| l.name),
created_at: repo.created_at.unwrap_or_else(Utc::now),
updated_at: repo.updated_at.unwrap_or_else(Utc::now),
pushed_at: repo.pushed_at.unwrap_or_else(Utc::now),
size_kb: repo.size.unwrap_or(0),
quality_metrics: QualityMetrics {
overall_score,
api_score,
local_score: local_scores.overall_local,
popularity_score: local_scores.readme_score,
maintenance_score: local_scores.coverage_score,
documentation_score: local_scores.readme_score,
security_score: local_scores
.metrics
.as_ref()
.map_or(0.5, |m| calculate_security_score(&m.security_metrics)),
},
activity_metrics: activity,
local_metrics: local_scores.metrics,
errors: vec![],
};
Ok(result)
}