Skip to content

Commit ac146ee

Browse files
AadarshAadarsh
Aadarsh
authored and
Aadarsh
committed
Refactored the mutations required for leaderboard
1 parent 2e3f8a7 commit ac146ee

File tree

4 files changed

+276
-162
lines changed

4 files changed

+276
-162
lines changed
+6-62
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use async_graphql::{Context, Object, Result};
2-
use reqwest;
3-
use serde_json::Value;
42
use sqlx::PgPool;
53
use std::sync::Arc;
4+
use super::leaderboard_api::fetch_and_update_codeforces_stats;
65

76
#[derive(Default)]
87
pub struct FetchCodeForces;
@@ -11,67 +10,12 @@ pub struct FetchCodeForces;
1110
impl FetchCodeForces {
1211
pub async fn fetch_codeforces_stats(
1312
&self,
14-
ctx: &Context<'_>, // Retrieve the database pool from context
13+
ctx: &Context<'_>,
1514
member_id: i32,
1615
username: String,
17-
) -> Result<String> {
18-
let pool = ctx.data::<Arc<PgPool>>()?; // Get the PgPool from context
19-
20-
let url = format!("https://codeforces.com/api/user.rating?handle={}", username);
21-
let response = reqwest::get(&url).await?.text().await?;
22-
let data: Value = serde_json::from_str(&response)?;
23-
24-
if data["status"] == "OK" {
25-
if let Some(results) = data["result"].as_array() {
26-
let contests_participated = results.len() as i32;
27-
28-
let mut max_rating = 0;
29-
let mut codeforces_rating = 0;
30-
31-
for contest in results {
32-
if let Some(new_rating) = contest["newRating"].as_i64() {
33-
codeforces_rating = new_rating as i32;
34-
max_rating = max_rating.max(codeforces_rating);
35-
}
36-
}
37-
38-
let update_result = sqlx::query!(
39-
r#"
40-
INSERT INTO codeforces_stats (
41-
member_id, codeforces_handle, codeforces_rating, max_rating, contests_participated
42-
)
43-
VALUES ($1, $2, $3, $4, $5)
44-
ON CONFLICT (member_id) DO UPDATE SET
45-
codeforces_handle = EXCLUDED.codeforces_handle,
46-
codeforces_rating = EXCLUDED.codeforces_rating,
47-
max_rating = EXCLUDED.max_rating,
48-
contests_participated = EXCLUDED.contests_participated
49-
"#,
50-
member_id,
51-
username,
52-
codeforces_rating,
53-
max_rating,
54-
contests_participated
55-
)
56-
.execute(pool.as_ref())
57-
.await;
58-
59-
match update_result {
60-
Ok(_) => Ok(format!(
61-
"Codeforces stats updated successfully for member ID: {}",
62-
member_id
63-
)),
64-
Err(e) => Err(format!(
65-
"Failed to update Codeforces stats for member ID {}: {:?}",
66-
member_id, e
67-
)
68-
.into()),
69-
}
70-
} else {
71-
Err("Invalid response from Codeforces API".into())
72-
}
73-
} else {
74-
Err("Codeforces API returned an error".into())
75-
}
16+
) -> Result<bool> {
17+
let pool = ctx.data::<Arc<PgPool>>()?;
18+
fetch_and_update_codeforces_stats(pool.clone(), member_id, &username).await;
19+
Ok(true)
7620
}
7721
}
+264
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
use reqwest;
2+
use serde_json::Value;
3+
use sqlx::PgPool;
4+
use std::sync::Arc;
5+
use std::collections::HashMap;
6+
use reqwest::Client;
7+
8+
9+
pub async fn fetch_and_update_codeforces_stats(
10+
pool: Arc<PgPool>,
11+
member_id: i32,
12+
username: &str,
13+
) -> Result<(), Box<dyn std::error::Error>> {
14+
let url = format!("https://codeforces.com/api/user.rating?handle={}", username);
15+
let response = reqwest::get(&url).await?.text().await?;
16+
let data: Value = serde_json::from_str(&response)?;
17+
18+
if data["status"] == "OK" {
19+
if let Some(results) = data["result"].as_array() {
20+
let contests_participated = results.len() as i32;
21+
22+
// Calculate the user's current and max ratings
23+
let mut max_rating = 0;
24+
let mut codeforces_rating = 0;
25+
26+
for contest in results {
27+
if let Some(new_rating) = contest["newRating"].as_i64() {
28+
codeforces_rating = new_rating as i32;
29+
max_rating = max_rating.max(codeforces_rating);
30+
}
31+
}
32+
33+
let update_result = sqlx::query(
34+
r#"
35+
INSERT INTO codeforces_stats (
36+
member_id, codeforces_handle, codeforces_rating, max_rating, contests_participated
37+
)
38+
VALUES ($1, $2, $3, $4, $5)
39+
ON CONFLICT (member_id) DO UPDATE SET
40+
codeforces_handle = EXCLUDED.codeforces_handle,
41+
codeforces_rating = EXCLUDED.codeforces_rating,
42+
max_rating = EXCLUDED.max_rating,
43+
contests_participated = EXCLUDED.contests_participated
44+
"#,
45+
)
46+
.bind(member_id)
47+
.bind(username)
48+
.bind(codeforces_rating)
49+
.bind(max_rating)
50+
.bind(contests_participated)
51+
.execute(pool.as_ref())
52+
.await;
53+
54+
match update_result {
55+
Ok(_) => println!("Codeforces stats updated for member ID: {}", member_id),
56+
Err(e) => eprintln!(
57+
"Failed to update Codeforces stats for member ID {}: {:?}",
58+
member_id, e
59+
),
60+
}
61+
62+
return Ok(());
63+
}
64+
}
65+
66+
Err(format!("Failed to fetch stats for Codeforces handle: {}", username).into())
67+
}
68+
69+
pub async fn update_leaderboard_scores(pool: Arc<PgPool>) -> Result<(), sqlx::Error> {
70+
let leetcode_stats = sqlx::query!(
71+
"SELECT member_id, problems_solved, easy_solved, medium_solved, hard_solved,
72+
contests_participated, best_rank
73+
FROM leetcode_stats"
74+
)
75+
.fetch_all(pool.as_ref())
76+
.await?;
77+
78+
let codeforces_stats = sqlx::query!(
79+
"SELECT member_id, codeforces_rating, max_rating, contests_participated
80+
FROM codeforces_stats"
81+
)
82+
.fetch_all(pool.as_ref())
83+
.await?;
84+
85+
let cf_lookup: HashMap<i32, (i32, i32, i32)> = codeforces_stats
86+
.iter()
87+
.map(|row| {
88+
(
89+
row.member_id,
90+
(row.codeforces_rating, row.max_rating, row.contests_participated),
91+
)
92+
})
93+
.collect();
94+
95+
for row in &leetcode_stats {
96+
let leetcode_score = (5 * row.easy_solved)
97+
+ (10 * row.medium_solved)
98+
+ (20 * row.hard_solved)
99+
+ (2 * row.contests_participated)
100+
+ (100 - row.best_rank / 10).max(0);
101+
102+
let (codeforces_score, unified_score) = cf_lookup.get(&row.member_id)
103+
.map(|(rating, max_rating, contests)| {
104+
let cf_score = (rating / 10) + (max_rating / 20) + (5 * contests);
105+
(cf_score, leetcode_score + cf_score)
106+
})
107+
.unwrap_or((0, leetcode_score));
108+
109+
let result = sqlx::query!(
110+
"INSERT INTO leaderboard (member_id, leetcode_score, codeforces_score, unified_score, last_updated)
111+
VALUES ($1, $2, $3, $4, NOW())
112+
ON CONFLICT (member_id) DO UPDATE SET
113+
leetcode_score = EXCLUDED.leetcode_score,
114+
codeforces_score = EXCLUDED.codeforces_score,
115+
unified_score = EXCLUDED.unified_score,
116+
last_updated = NOW()",
117+
row.member_id,
118+
leetcode_score,
119+
codeforces_score,
120+
unified_score
121+
)
122+
.execute(pool.as_ref())
123+
.await;
124+
125+
if let Err(e) = result {
126+
eprintln!("Failed to update leaderboard for member ID {}: {:?}", row.member_id, e);
127+
}
128+
}
129+
130+
for row in &codeforces_stats {
131+
if leetcode_stats.iter().any(|lc| lc.member_id == row.member_id) {
132+
continue;
133+
}
134+
135+
let codeforces_score = (row.codeforces_rating / 10)
136+
+ (row.max_rating / 20)
137+
+ (5 * row.contests_participated);
138+
139+
let unified_score = codeforces_score;
140+
141+
let result = sqlx::query!(
142+
"INSERT INTO leaderboard (member_id, leetcode_score, codeforces_score, unified_score, last_updated)
143+
VALUES ($1, $2, $3, $4, NOW())
144+
ON CONFLICT (member_id) DO UPDATE SET
145+
leetcode_score = EXCLUDED.leetcode_score,
146+
codeforces_score = EXCLUDED.codeforces_score,
147+
unified_score = EXCLUDED.unified_score,
148+
last_updated = NOW()",
149+
row.member_id,
150+
0,
151+
codeforces_score,
152+
unified_score
153+
)
154+
.execute(pool.as_ref())
155+
.await;
156+
157+
if let Err(e) = result {
158+
eprintln!("Failed to update leaderboard for Codeforces-only member ID {}: {:?}", row.member_id, e);
159+
}
160+
}
161+
162+
Ok(())
163+
}
164+
165+
166+
167+
pub async fn fetch_and_update_leetcode(
168+
pool: Arc<PgPool>,
169+
member_id: i32,
170+
username: &str,
171+
) -> Result<(), Box<dyn std::error::Error>> {
172+
let client = Client::new();
173+
let url = "https://leetcode.com/graphql";
174+
let query = r#"
175+
query userProfile($username: String!) {
176+
userContestRanking(username: $username) {
177+
attendedContestsCount
178+
}
179+
matchedUser(username: $username) {
180+
profile {
181+
ranking
182+
}
183+
submitStats {
184+
acSubmissionNum {
185+
difficulty
186+
count
187+
}
188+
}
189+
}
190+
}
191+
"#;
192+
193+
let response = client
194+
.post(url)
195+
.header("Content-Type", "application/json")
196+
.json(&serde_json::json!({
197+
"query": query,
198+
"variables": { "username": username }
199+
}))
200+
.send()
201+
.await?;
202+
203+
let data: Value = response.json().await?;
204+
205+
let empty_vec = vec![];
206+
let submissions = data["data"]["matchedUser"]["submitStats"]["acSubmissionNum"]
207+
.as_array()
208+
.unwrap_or(&empty_vec);
209+
210+
let mut problems_solved = 0;
211+
let mut easy_solved = 0;
212+
let mut medium_solved = 0;
213+
let mut hard_solved = 0;
214+
215+
for stat in submissions {
216+
let count = stat["count"].as_i64().unwrap_or(0) as i32;
217+
match stat["difficulty"].as_str().unwrap_or("") {
218+
"Easy" => easy_solved = count,
219+
"Medium" => medium_solved = count,
220+
"Hard" => hard_solved = count,
221+
"All" => problems_solved = count,
222+
_ => {}
223+
}
224+
}
225+
226+
let contests_participated = data["data"]["userContestRanking"]["attendedContestsCount"]
227+
.as_i64()
228+
.unwrap_or(0) as i32;
229+
let rank = data["data"]["matchedUser"]["profile"]["ranking"]
230+
.as_i64()
231+
.unwrap_or(0) as i32;
232+
233+
sqlx::query!(
234+
r#"
235+
INSERT INTO leetcode_stats (
236+
member_id, leetcode_username, problems_solved, easy_solved, medium_solved,
237+
hard_solved, contests_participated, best_rank, total_contests
238+
)
239+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
240+
ON CONFLICT (member_id) DO UPDATE SET
241+
leetcode_username = EXCLUDED.leetcode_username,
242+
problems_solved = EXCLUDED.problems_solved,
243+
easy_solved = EXCLUDED.easy_solved,
244+
medium_solved = EXCLUDED.medium_solved,
245+
hard_solved = EXCLUDED.hard_solved,
246+
contests_participated = EXCLUDED.contests_participated,
247+
best_rank = EXCLUDED.best_rank,
248+
total_contests = EXCLUDED.total_contests
249+
"#,
250+
member_id,
251+
username,
252+
problems_solved,
253+
easy_solved,
254+
medium_solved,
255+
hard_solved,
256+
contests_participated,
257+
rank,
258+
contests_participated
259+
)
260+
.execute(pool.as_ref())
261+
.await?;
262+
263+
Ok(())
264+
}

0 commit comments

Comments
 (0)