Initial commit
This commit is contained in:
132
src/search.rs
Normal file
132
src/search.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user