Skip to content
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

CP leaderboard implementation #18

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

swayam-agrahari
Copy link

  • Added scheduled tasks to update CP ratings and insert attendance records daily at midnight.
  • Implemented API calls to fetch ratings from Codeforces and LeetCode.
  • Updated update_cp_ratings function to handle dynamic ratings based on platform.
  • Created placeholders for test configuration to avoid hardcoding sensitive data.
  • Added tests to validate rating updates for members with Codeforces and LeetCode profiles.

@@ -14,4 +13,7 @@ pub struct Member {
pub sex: String,
pub year: i32,
pub macaddress: String,
pub leaderboard_id: String,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this used for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the username for their respective cp platform

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah gotcha, but they should be free to do both like i said not just 1 platform

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alright so to implement this, we might need to create another leaderboard struct as this is one to many relation with foreign key as the member's id (primary key)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep that's right

@@ -14,4 +13,7 @@ pub struct Member {
pub sex: String,
pub year: i32,
pub macaddress: String,
pub leaderboard_id: String,
pub cp_platform: String,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aren't members restricted to only one platform if we do it like this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, they will be. I thought we were only allowed to choose one and given the current scenario, no one is trying for both.

Comment on lines +227 to +269
mod tests {
use super::*;

// Mocking the PgPool for testing purposes (if necessary)
use sqlx::PgPool;

#[tokio::test]
// Update these variables with the actual values before running the test

async fn test_fetch_codeforces_rating() {
let codeforces_username = ""; // Add your Codeforces username here
let result = fetch_codeforces_rating(codeforces_username).await;
assert!(result.is_ok());
let rating = result.unwrap();
assert!(rating.is_some());
}

#[tokio::test]
async fn test_fetch_leetcode_ranking() {
let leetcode_username = ""; // Add your LeetCode username here
let result = fetch_leetcode_ranking(leetcode_username).await;
assert!(result.is_ok());
let ranking = result.unwrap();
assert!(ranking.is_some());
}

#[tokio::test]
async fn test_scheduled_task() {
let database_url = ""; // Add your database URL here
let pool = Arc::new(PgPool::connect_lazy(database_url).unwrap());

scheduled_task(pool).await;
}

// Test for update_cp_ratings
#[tokio::test]
async fn test_update_cp_ratings() {
let database_url = ""; // Add your database URL here
let pool = Arc::new(PgPool::connect(database_url).await.unwrap());
update_cp_ratings(pool).await;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good to add tests but should be in a separate directory not in main.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah sure will do it. I also thought about creating more tests for seeing members for dev purpose so this function can lie there.

Comment on lines +135 to +225
username: &str,
) -> Result<Option<i32>, Box<dyn std::error::Error>> {
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)?;

if data["status"] == "OK" {
if let Some(results) = data["result"].as_array() {
if let Some(last_contest) = results.last() {
let new_rating = last_contest["newRating"].as_i64().unwrap_or_default() as i32;
return Ok(Some(new_rating));
}
}
}
Ok(None)
}

// Function to fetch LeetCode ranking
async fn fetch_leetcode_ranking(username: &str) -> Result<Option<i32>, Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let url = "https://leetcode.com/graphql";
let query = r#"
query userPublicProfile($username: String!) {
matchedUser(username: $username) {
profile {
ranking
}
}
}
"#;

let response = client
.post(url)
.header("Content-Type", "application/json")
.json(&serde_json::json!({
"query": query,
"variables": {
"username": username
}
}))
.send()
.await?;

let data: Value = response.json().await?;
let ranking = data["data"]["matchedUser"]["profile"]["ranking"]
.as_i64()
.map(|v| v as i32);

Ok(ranking)
}

// Fetch and update CP ratings for all members
async fn update_cp_ratings(pool: Arc<PgPool>) {
let members: Result<Vec<Member>, sqlx::Error> =
sqlx::query_as::<_, Member>("SELECT * FROM Member")
.fetch_all(pool.as_ref())
.await;

match members {
Ok(members) => {
for member in members {
let rating = match member.cp_platform.as_str() {
"Codeforces" => fetch_codeforces_rating(&member.leaderboard_id)
.await
.ok()
.flatten(),
"LeetCode" => fetch_leetcode_ranking(&member.leaderboard_id)
.await
.ok()
.flatten(),
_ => None,
};

if let Some(rating) = rating {
let update_result = sqlx::query("UPDATE Member SET rating = $1 WHERE id = $2")
.bind(rating)
.bind(member.id)
.execute(pool.as_ref())
.await;

match update_result {
Ok(_) => println!("Updated rating for {}: {}", member.name, rating),
Err(e) => eprintln!("Failed to update rating for {}: {:?}", member.name, e),
}
}
}
}
Err(e) => eprintln!("Failed to fetch members: {:?}", e),
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Single Leaderboard with Weighted Scores:
Combines scores from LeetCode and Codeforces into a unified ranking.

  • Pros: Simple, gives a balanced view, and avoids splitting attention.
    
  • Cons: Needs agreement on weightage and a formula to merge scores fairly.
    

Separate Leaderboards:
Maintains distinct rankings for each platform.

  • Pros: Highlights specific performance on each platform.
    
  • Cons: Can feel redundant or less competitive if interest skews heavily toward one platform
    

can you ask in discord which is preferred? make sure to highlite these pros and cons

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes done : )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants