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 { let local_results = self.db.search(query, limit as i64)?; let total_cached = self.db.count()?; let mut all_repos: HashMap = 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 = all_repos.into_values().collect(); results.sort_by(|a, b| b.stars.cmp(&a.stars)); results.truncate(limit); let search_results: Vec = 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 { 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 { 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) } }