-
Notifications
You must be signed in to change notification settings - Fork 20
Added cp leaderboard queries and mutations #97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
|
Your commit messages could use some work; refer https://www.conventionalcommits.org/en/v1.0.0/ and https://www.simplethread.com/what-makes-a-good-git-commit/. |
|
Rewrite the commits and fix the failing workflow and you're good to go. |
|
@ivinjabraham ok thanks👍 |
02b2cd0 to
9ca8e7e
Compare
|
@ivinjabraham I have updated the PR with the requested changes |
|
|
||
| CREATE TABLE IF NOT EXISTS codeforces_stats ( | ||
| id SERIAL PRIMARY KEY, | ||
| member_id INT NOT NULL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unique
package-lock.json
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
L
src/daily_task/mod.rs
Outdated
| Ok(members) => { | ||
| for member in members { | ||
| // Fetch LeetCode username | ||
| let leetcode_username = sqlx::query_as::<_, LeetCodeStats>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change the variable name, the SQL will return an entire row from the leetcode table
src/daily_task/mod.rs
Outdated
| } | ||
|
|
||
| // Fetch Codeforces username | ||
| let codeforces_username = sqlx::query_as::<_, CodeforcesStats>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change the variable name, the SQL will return an entire row from the codeforces table
d691046 to
8770b04
Compare
|
@hrideshmg I hope this fixes all that you mentioned |
|
That's a lotta commits😳 Will take a look later this week |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Adds competitive programming (CP) leaderboard functionality: data models, migrations, external API integration (LeetCode/Codeforces), GraphQL queries/mutations, daily task updates, and seed data to populate and compute scores.
- Introduces new tables and models for leaderboard, LeetCode, and Codeforces stats.
- Adds GraphQL queries/mutations and background task to fetch/update stats and recompute unified scores.
- Implements external API calls and scoring logic but introduces several mapping, ordering, and robustness issues.
Reviewed Changes
Copilot reviewed 15 out of 20 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| src/models/mod.rs | Exposes new leaderboard module. |
| src/models/leaderboard.rs | Defines leaderboard and CP stats GraphQL/DB models. |
| src/graphql/queries/mod.rs | Registers leaderboard queries. |
| src/graphql/queries/leaderboard_queries.rs | Adds queries for unified leaderboard and platform-specific stats. |
| src/graphql/mutations/mod.rs | Registers new fetch mutations for CP stats. |
| src/graphql/mutations/leetcode_status.rs | Adds mutation to fetch/update LeetCode stats. |
| src/graphql/mutations/leaderboard_mutation.rs | Adds mutation for adding/updating CP handles (not wired into schema). |
| src/graphql/mutations/codeforces_status.rs | Adds mutation to fetch/update Codeforces stats. |
| src/graphql/mod.rs | Integrates leaderboard queries and fetch mutations into schema. |
| src/graphql/api/mod.rs | Exposes leaderboard API helper functions. |
| src/graphql/api/leaderboard_api.rs | Implements external API integration and scoring logic. |
| src/database_seeder/seed.sql | Adds seed data and initial leaderboard computations. |
| src/daily_task/mod.rs | Extends daily task to refresh CP stats and leaderboard. |
| package.json | Adds client-side GraphQL tooling dependencies. |
| migrations/20250312124630_add_leaderboard_tables.sql | Creates leaderboard and stats tables. |
Files not reviewed (4)
- .sqlx/query-00eed700b05fd13ddbbe88daed394e408acdc61d36618361f302bf25ca3f15a4.json: Language not supported
- .sqlx/query-1697cc6c5888d46b40bde260b125576e3b31d93275b267b5f2c8f39e4b7fe2ba.json: Language not supported
- .sqlx/query-8fe3e023fde759a78e0c4071f658cc4ed4cf29158360ce3f17b87b403dcf9f15.json: Language not supported
- .sqlx/query-d1a8d44f4608a7ca73e7e96d88678b0afdd72f2b91332a47f7f41627230dd989.json: Language not supported
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| let leaderboard = sqlx::query_as::<_, LeaderboardWithMember>( | ||
| "SELECT l.*, m.name AS member_name | ||
| FROM leaderboard l | ||
| JOIN member m ON l.member_id = m.member_id | ||
| ORDER BY unified_score DESC", |
Copilot
AI
Oct 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The struct LeaderboardWithMember places member_name as the third field, but this query selects l.* (which yields member_name last) and appends member_name at the end, causing column-to-field misalignment and a runtime row decoding error. Explicitly list columns in the correct order, e.g. SELECT l.id, l.member_id, m.name AS member_name, l.leetcode_score, l.codeforces_score, l.unified_score, l.last_updated ....
| let leetcode_stats = sqlx::query_as::<_, LeetCodeStatsWithName>( | ||
| "SELECT l.*, m.name AS member_name | ||
| FROM leetcode_stats l | ||
| JOIN member m ON l.member_id = m.member_id | ||
| ORDER BY best_rank", | ||
| ) |
Copilot
AI
Oct 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LeetCodeStatsWithName expects member_name as the third field but l.* places leetcode_username third; this will mis-map fields and likely fail decoding. Replace l.* with an ordered column list including m.name in the third position.
| let codeforces_stats = sqlx::query_as::<_, CodeforcesStatsWithName>( | ||
| "SELECT c.*, m.name AS member_name | ||
| FROM codeforces_stats c | ||
| JOIN member m ON c.member_id = m.member_id | ||
| ORDER BY max_rating DESC", | ||
| ) |
Copilot
AI
Oct 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c.* includes the id column (first) which is not present in CodeforcesStatsWithName and also places member_name last, causing column/field mismatch; decoding will fail. Select only the needed columns in correct order: c.member_id, m.name AS member_name, c.codeforces_handle, c.codeforces_rating, c.max_rating, c.contests_participated.
| let rating_val = rating.max(&0); | ||
| let max_rating_val = max_rating.max(&0); | ||
| let contests_val = contests.max(&0); | ||
|
|
||
| // Codeforces scoring | ||
| let rating_points = (*rating_val as f64 * 0.8).round() as i32; |
Copilot
AI
Oct 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rating_val, max_rating_val, and contests_val are &i32 references (from .max(&0)); subtracting references (max_rating_val - rating_val) does not compile. Convert them to owned i32 values (e.g. let rating_val = (*rating).max(0);) and adjust subsequent arithmetic.
| let rating_val = rating.max(&0); | |
| let max_rating_val = max_rating.max(&0); | |
| let contests_val = contests.max(&0); | |
| // Codeforces scoring | |
| let rating_points = (*rating_val as f64 * 0.8).round() as i32; | |
| let rating_val = *rating.max(&0); | |
| let max_rating_val = *max_rating.max(&0); | |
| let contests_val = *contests.max(&0); | |
| // Codeforces scoring | |
| let rating_points = (rating_val as f64 * 0.8).round() as i32; |
| @@ -0,0 +1,303 @@ | |||
| use reqwest; | |||
Copilot
AI
Oct 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This top-level use brings the crate name into scope redundantly since you already refer to reqwest::get and create Client via reqwest::Client; the crate name is available without a use statement. Remove this unused import to reduce noise.
| use reqwest; |
| if let Ok(Some(leetcode_stats)) = sqlx::query_as::<_, LeetCodeStats>( | ||
| "SELECT leetcode_username FROM leetcode_stats WHERE member_id = $1 AND leetcode_username IS NOT NULL AND leetcode_username != ''", | ||
| ) | ||
| .bind(member.member_id) | ||
| .fetch_optional(pool.as_ref()) | ||
| .await { |
Copilot
AI
Oct 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The query returns a single column (leetcode_username) but LeetCodeStats expects all columns defined in the model; this will cause a decode error. Use a lightweight struct with only leetcode_username or select all required columns matching LeetCodeStats.
| if let Ok(Some(codeforces_stats)) = sqlx::query_as::<_, CodeforcesStats>( | ||
| "SELECT codeforces_handle FROM codeforces_stats WHERE member_id = $1 AND codeforces_handle IS NOT NULL AND codeforces_handle != ''", | ||
| ) | ||
| .bind(member.member_id) | ||
| .fetch_optional(pool.as_ref()) | ||
| .await { |
Copilot
AI
Oct 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the LeetCode query, only codeforces_handle is selected while CodeforcesStats expects multiple columns; this will fail row decoding. Either select all columns or map to a minimal handle-only struct.
| for member in &members { | ||
| // Update LeetCode stats | ||
| if let Ok(Some(leetcode_stats)) = sqlx::query_as::<_, LeetCodeStats>( | ||
| "SELECT leetcode_username FROM leetcode_stats WHERE member_id = $1 AND leetcode_username IS NOT NULL AND leetcode_username != ''", | ||
| ) | ||
| .bind(member.member_id) | ||
| .fetch_optional(pool.as_ref()) | ||
| .await { | ||
| let username = leetcode_stats.leetcode_username.clone(); | ||
|
|
||
| match fetch_and_update_leetcode(pool.clone(), member.member_id, &username).await { | ||
| Ok(_) => debug!("LeetCode stats updated for member ID: {}", member.member_id), | ||
| Err(e) => error!("Failed to update LeetCode stats for member ID {}: {:?}", member.member_id, e), | ||
| } | ||
| } | ||
|
|
||
| if let Ok(Some(codeforces_stats)) = sqlx::query_as::<_, CodeforcesStats>( | ||
| "SELECT codeforces_handle FROM codeforces_stats WHERE member_id = $1 AND codeforces_handle IS NOT NULL AND codeforces_handle != ''", | ||
| ) | ||
| .bind(member.member_id) | ||
| .fetch_optional(pool.as_ref()) | ||
| .await { | ||
| let username = codeforces_stats.codeforces_handle.clone(); | ||
|
|
||
| match fetch_and_update_codeforces_stats(pool.clone(), member.member_id, &username).await { | ||
| Ok(_) => debug!("Codeforces stats updated for member ID: {}", member.member_id), | ||
| Err(e) => error!("Failed to update Codeforces stats for member ID {}: {:?}", member.member_id, e), | ||
| } | ||
| } | ||
| } |
Copilot
AI
Oct 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This performs up to two separate queries per member (N+1 pattern) and sequential external API calls, which will not scale; batch fetch existing usernames/handles first and parallelize external calls with bounded concurrency (e.g. FuturesUnordered + semaphore).
| let url = format!("https://codeforces.com/api/user.rating?handle={username}"); | ||
| let response = reqwest::get(&url).await?.text().await?; | ||
| let data: Value = serde_json::from_str(&response)?; |
Copilot
AI
Oct 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
External HTTP call lacks an explicit timeout; a hanging upstream request could stall the task—configure a Client with a timeout (e.g. Client::builder().timeout(...).build()) and propagate errors.
| let response = client | ||
| .post(url) | ||
| .header("Content-Type", "application/json") | ||
| .json(&serde_json::json!({ | ||
| "query": query, | ||
| "variables": { "username": username } | ||
| })) | ||
| .send() | ||
| .await?; |
Copilot
AI
Oct 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LeetCode request also lacks a timeout; set a client-level timeout to avoid indefinite waits and consider retry/backoff for transient failures.
|
Don't know if this is still planned, if it is please create an issue for it too @AadarshM07. Thanks. |
It is, waiting for @AadarshM07 to resolve the copilot reviews and rebase with the refactored version. |
This adds the necessary queries and mutations required for the cp leaderboard . It includes:
All functionalities have been tested using the GraphiQL playground