133 lines
4.1 KiB
Rust
133 lines
4.1 KiB
Rust
use crate::db::Database;
|
|
use crate::github::GitHubClient;
|
|
use crate::models::{RateLimitInfo, Repo, SearchResponse, SearchResult};
|
|
use anyhow::Result;
|
|
use std::collections::HashMap;
|
|
|
|
pub struct SearchEngine {
|
|
db: Database,
|
|
github: GitHubClient,
|
|
}
|
|
|
|
impl SearchEngine {
|
|
pub fn new(db: Database, github: GitHubClient) -> Self {
|
|
Self { db, github }
|
|
}
|
|
|
|
pub async fn search(
|
|
&self,
|
|
query: &str,
|
|
limit: usize,
|
|
) -> Result<SearchResponse> {
|
|
let local_results = self.db.search(query, limit as i64)?;
|
|
let total_cached = self.db.count()?;
|
|
|
|
let mut all_repos: HashMap<String, Repo> = HashMap::new();
|
|
|
|
for repo in &local_results {
|
|
all_repos.insert(repo.full_name.clone(), repo.clone());
|
|
}
|
|
|
|
let mut rate_limit = RateLimitInfo {
|
|
remaining: 10,
|
|
limit: 10,
|
|
};
|
|
|
|
if local_results.len() < limit {
|
|
match self.github.search_repositories(query, limit).await {
|
|
Ok((api_repos, api_rate_limit)) => {
|
|
rate_limit = api_rate_limit;
|
|
|
|
for gh_repo in api_repos {
|
|
let repo: Repo = gh_repo.into();
|
|
self.db.insert_or_update(&repo)?;
|
|
all_repos.insert(repo.full_name.clone(), repo);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Warning: Failed to fetch from GitHub API: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut results: Vec<Repo> = all_repos.into_values().collect();
|
|
results.sort_by(|a, b| b.stars.cmp(&a.stars));
|
|
results.truncate(limit);
|
|
|
|
let search_results: Vec<SearchResult> = results
|
|
.into_iter()
|
|
.enumerate()
|
|
.map(|(idx, repo)| SearchResult {
|
|
rank: idx + 1,
|
|
repo,
|
|
})
|
|
.collect();
|
|
|
|
Ok(SearchResponse {
|
|
query: query.to_string(),
|
|
limit,
|
|
api_remaining: rate_limit.remaining,
|
|
api_limit: rate_limit.limit,
|
|
total_cached,
|
|
results: search_results,
|
|
})
|
|
}
|
|
|
|
pub async fn update_all(&self) -> Result<RateLimitInfo> {
|
|
let repos = self.db.get_all()?;
|
|
let total = repos.len();
|
|
|
|
let mut last_rate_limit = RateLimitInfo {
|
|
remaining: 10,
|
|
limit: 10,
|
|
};
|
|
|
|
println!("Updating {} repositories...", total);
|
|
|
|
for (idx, repo) in repos.iter().enumerate() {
|
|
let parts: Vec<&str> = repo.full_name.split('/').collect();
|
|
if parts.len() != 2 {
|
|
continue;
|
|
}
|
|
|
|
print!("\rUpdating {}/{}: {}", idx + 1, total, repo.full_name);
|
|
|
|
match self.github.get_repository(parts[0], parts[1]).await {
|
|
Ok((gh_repo, rate_limit)) => {
|
|
last_rate_limit = rate_limit;
|
|
self.db.update_stars(&repo.full_name, gh_repo.stargazers_count)?;
|
|
}
|
|
Err(e) => {
|
|
eprintln!("\nWarning: Failed to update {}: {}", repo.full_name, e);
|
|
}
|
|
}
|
|
|
|
if last_rate_limit.remaining < 3 {
|
|
println!("\nWarning: Rate limit running low ({} remaining)", last_rate_limit.remaining);
|
|
}
|
|
}
|
|
|
|
println!("\n✓ Updated {} repositories", total);
|
|
Ok(last_rate_limit)
|
|
}
|
|
|
|
pub async fn update_single(&self, full_name: &str) -> Result<RateLimitInfo> {
|
|
let parts: Vec<&str> = full_name.split('/').collect();
|
|
if parts.len() != 2 {
|
|
anyhow::bail!("Invalid repository format. Use 'owner/repo'");
|
|
}
|
|
|
|
let (owner, name) = (parts[0], parts[1]);
|
|
|
|
let (gh_repo, rate_limit) = self.github.get_repository(owner, name).await?;
|
|
let repo: Repo = gh_repo.into();
|
|
|
|
self.db.insert_or_update(&repo)?;
|
|
|
|
println!("✓ Updated {}", full_name);
|
|
println!(" Stars: {}", repo.stars);
|
|
|
|
Ok(rate_limit)
|
|
}
|
|
}
|