gitfetch_rs/fetcher/
gitea.rs1use super::Fetcher;
2use anyhow::Result;
3use async_trait::async_trait;
4use serde_json::Value;
5
6pub struct GiteaFetcher {
7 client: reqwest::Client,
8 api_base: String,
9 token: Option<String>,
10}
11
12impl GiteaFetcher {
13 pub fn new(base_url: &str, token: Option<&str>) -> Result<Self> {
14 let base = base_url.trim_end_matches('/').to_string();
15 let api_base = format!("{}/api/v1", base);
16
17 Ok(Self {
18 client: reqwest::Client::new(),
19 api_base,
20 token: token.map(String::from),
21 })
22 }
23
24 fn api_request(&self, endpoint: &str) -> Result<Value> {
25 let url = format!("{}{}", self.api_base, endpoint);
26
27 let mut req = self.client.get(&url);
28
29 if let Some(token) = &self.token {
30 req = req.header("Authorization", format!("token {}", token));
31 }
32
33 let rt = tokio::runtime::Runtime::new()?;
34 let response =
35 rt.block_on(async { req.timeout(std::time::Duration::from_secs(30)).send().await })?;
36
37 if !response.status().is_success() {
38 return Err(anyhow::anyhow!(
39 "Gitea API request failed: {}",
40 response.status()
41 ));
42 }
43
44 let rt = tokio::runtime::Runtime::new()?;
45 let data = rt.block_on(async { response.json::<Value>().await })?;
46
47 Ok(data)
48 }
49}
50
51#[async_trait]
52impl Fetcher for GiteaFetcher {
53 async fn get_authenticated_user(&self) -> Result<String> {
54 if self.token.is_none() {
55 return Err(anyhow::anyhow!("Token required for Gitea authentication"));
56 }
57
58 let data = self.api_request("/user")?;
59 data["login"]
60 .as_str()
61 .map(String::from)
62 .ok_or_else(|| anyhow::anyhow!("Could not get authenticated user"))
63 }
64
65 async fn fetch_user_data(&self, username: &str) -> Result<Value> {
66 self.api_request(&format!("/users/{}", username))
67 }
68
69 async fn fetch_user_stats(&self, username: &str, user_data: Option<&Value>) -> Result<Value> {
70 let _user = if let Some(data) = user_data {
71 data.clone()
72 } else {
73 self.fetch_user_data(username).await?
74 };
75
76 let mut repos = Vec::new();
78 let mut page = 1;
79 let per_page = 50;
80
81 loop {
82 let endpoint = format!("/users/{}/repos?page={}&limit={}", username, page, per_page);
83 let data = self.api_request(&endpoint)?;
84
85 let data_array = match data.as_array() {
86 Some(arr) if !arr.is_empty() => arr,
87 _ => break,
88 };
89
90 repos.extend(data_array.clone());
91 page += 1;
92
93 if data_array.len() < per_page {
94 break;
95 }
96 }
97
98 let total_stars: i64 = repos.iter().filter_map(|r| r["stars_count"].as_i64()).sum();
100
101 let total_forks: i64 = repos.iter().filter_map(|r| r["forks_count"].as_i64()).sum();
102
103 let languages = self.calculate_language_stats(&repos);
105
106 Ok(serde_json::json!({
108 "total_stars": total_stars,
109 "total_forks": total_forks,
110 "total_repos": repos.len(),
111 "languages": languages,
112 "contribution_graph": [],
113 "current_streak": 0,
114 "longest_streak": 0,
115 "total_contributions": 0,
116 "pull_requests": {
117 "open": 0,
118 "awaiting_review": 0,
119 "mentions": 0
120 },
121 "issues": {
122 "assigned": 0,
123 "created": 0,
124 "mentions": 0
125 },
126 }))
127 }
128}
129
130impl GiteaFetcher {
131 fn calculate_language_stats(&self, repos: &[Value]) -> Value {
132 use std::collections::HashMap;
133
134 let mut language_counts: HashMap<String, i32> = HashMap::new();
135
136 for repo in repos {
137 if let Some(language) = repo["language"].as_str() {
138 if !language.is_empty() {
139 let normalized = language.to_lowercase();
140 *language_counts.entry(normalized).or_insert(0) += 1;
141 }
142 }
143 }
144
145 let total: i32 = language_counts.values().sum();
146 if total == 0 {
147 return serde_json::json!({});
148 }
149
150 let mut language_percentages: HashMap<String, f64> = HashMap::new();
151 for (lang, count) in language_counts {
152 let percentage = (count as f64 / total as f64) * 100.0;
153 let display_name = lang
154 .chars()
155 .enumerate()
156 .map(|(i, c)| {
157 if i == 0 {
158 c.to_uppercase().to_string()
159 } else {
160 c.to_string()
161 }
162 })
163 .collect::<String>();
164 language_percentages.insert(display_name, percentage);
165 }
166
167 serde_json::to_value(language_percentages).unwrap_or_else(|_| serde_json::json!({}))
168 }
169}