Initial commit

This commit is contained in:
2026-03-23 17:30:16 +02:00
commit bbfb77b4e7
17 changed files with 4030 additions and 0 deletions

132
src/search.rs Normal file
View 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)
}
}