diff --git a/Cargo.toml b/Cargo.toml index 7554e289..35d5aae5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -starknet = "0.6.0" -starknet-id = { git = "https://github.com/starknet-id/starknet-id.rs.git", branch = "master" } +starknet = { git = "https://github.com/xJonathanLEI/starknet-rs" } +starknet-id = { git = "https://github.com/starknet-id/starknet-id.rs.git" } axum = "0.6.17" toml = "0.5.10" serde = { version = "1.0.152", features = ["derive"] } diff --git a/src/common/get_achievement.rs b/src/common/get_achievement.rs new file mode 100644 index 00000000..fa933a48 --- /dev/null +++ b/src/common/get_achievement.rs @@ -0,0 +1,88 @@ +use std::sync::Arc; + +use crate::models::AppState; +use crate::models::{AchievementCategoryDocument, UserAchievementsCategory}; +use futures::StreamExt; +use mongodb::bson::{doc, from_document}; +use starknet::core::types::FieldElement; + +pub async fn get_achievement( + state: &Arc, + addr: &FieldElement, + category_id: u32, +) -> Result { + let achievement_categories = state + .db + .collection::("achievement_categories"); + let pipeline = vec![ + doc! { + "$match": { + "id": category_id, + } + }, + doc! { + "$lookup": { + "from": "achievements", + "localField": "id", + "foreignField": "category_id", + "as": "achievement" + } + }, + doc! {"$unwind": "$achievement" }, + doc! { + "$lookup": { + "from": "achieved", + "let": { "achievement_id": "$achievement.id" }, + "pipeline": [ + { "$match": { + "$expr": { + "$and": [ + { "$eq": ["$achievement_id", "$$achievement_id"] }, + { "$eq": ["$addr", FieldElement::to_string(&addr)] } + ] + } + } } + ], + "as": "achieved" + } + }, + doc! { + "$project": { + "_id": 0, + "category_id": "$id", + "achievements": { + "id": "$achievement.id", + "completed": { "$ne": [{ "$size": "$achieved" }, 0] }, + "verify_type": "$achievement.verify_type", + } + } + }, + doc! { + "$group": { + "_id": { + "category_id": "$category_id", + }, + "achievements": { "$push": "$achievements" } + } + }, + doc! { + "$project": { + "category_id": "$_id.category_id", + "achievements": 1, + "_id": 0 + } + }, + ]; + + match achievement_categories.aggregate(pipeline, None).await { + Ok(mut cursor) => match cursor.next().await { + Some(Ok(document)) => match from_document::(document) { + Ok(achievement_category) => Ok(achievement_category), + Err(e) => Err(format!("Error deserializing document : {}", e)), + }, + Some(Err(e)) => Err(format!("No data found: {}", e)), + None => Err("No data found".to_string()), + }, + Err(e) => Err(format!("Error fetching user achievements: {}", e)), + } +} diff --git a/src/common/mod.rs b/src/common/mod.rs index 2273e8d6..e6ce6f50 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,3 +1,4 @@ +pub mod get_achievement; pub mod has_deployed_time; pub mod verify_has_nft; pub mod verify_has_root_domain; diff --git a/src/endpoints/achievements/batched/mod.rs b/src/endpoints/achievements/batched/mod.rs new file mode 100644 index 00000000..e45e819b --- /dev/null +++ b/src/endpoints/achievements/batched/mod.rs @@ -0,0 +1 @@ +pub mod verify_tvl_batched; diff --git a/src/endpoints/achievements/batched/verify_tvl_batched.rs b/src/endpoints/achievements/batched/verify_tvl_batched.rs new file mode 100644 index 00000000..0d865548 --- /dev/null +++ b/src/endpoints/achievements/batched/verify_tvl_batched.rs @@ -0,0 +1,74 @@ +use std::sync::Arc; + +use crate::{ + common::get_achievement::get_achievement, + models::{AppState, VerifyAchievementBatchedQuery}, + utils::{get_error, to_hex, AchievementsTrait}, +}; +use axum::{ + extract::{Query, State}, + http::StatusCode, + response::IntoResponse, + Json, +}; +use serde_json::json; +use starknet::core::types::FieldElement; + +pub async fn handler( + State(state): State>, + Query(query): Query, +) -> impl IntoResponse { + let addr = query.addr; + if addr == FieldElement::ZERO { + return get_error("Please connect your wallet first".to_string()); + } + + let url = format!( + "https://stack.starkendefi.xyz/public/aggregates/{}", + to_hex(addr) + ); + let client = reqwest::Client::new(); + match client.get(url).send().await { + Ok(response) => match response.json::().await { + Ok(json) => { + if let Some(total_tvl_dollars) = json["total_tvl_dollars"].as_f64() { + if total_tvl_dollars < 100.0 { + return get_error("Your TVL is too low".to_string()); + } + + match get_achievement(&state, &query.addr, query.category_id).await { + Ok(achievements) => { + let mut achieved: Vec = vec![]; + for achievement in achievements.achievements { + if !achievement.completed + && (achievement.id == 11 && total_tvl_dollars >= 100.0) + || (achievement.id == 12 && total_tvl_dollars >= 1000.0) + || (achievement.id == 13 && total_tvl_dollars >= 10000.0) + { + match state + .upsert_completed_achievement(addr, achievement.id) + .await + { + Ok(_) => { + achieved.push(achievement.id); + } + Err(e) => return get_error(format!("{}", e)), + } + } + } + (StatusCode::OK, Json(json!({ "achieved": achieved }))).into_response() + } + Err(e) => get_error(e), + } + } else { + get_error("total_tvl_dollars not found or not a float".to_string()) + } + } + Err(e) => get_error(format!( + "Failed to get JSON response from Starkendefi: {}", + e + )), + }, + Err(e) => get_error(format!("Failed to fetch Starkendefi: {}", e)), + } +} diff --git a/src/endpoints/achievements/fetch.rs b/src/endpoints/achievements/fetch.rs index b62483d2..b7a97b28 100644 --- a/src/endpoints/achievements/fetch.rs +++ b/src/endpoints/achievements/fetch.rs @@ -58,6 +58,7 @@ pub async fn handler( "category_img_url": "$img_url", "category_type": "$type", "category_disabled": "$disabled", + "category_override_verified_type": "$override_verified_type", "achievements": { "id": "$achievement.id", "name": "$achievement.name", @@ -91,6 +92,7 @@ pub async fn handler( "category_img_url": "$category_img_url", "category_type": "$category_type", "category_disabled": "$category_disabled", + "category_override_verified_type": "$category_override_verified_type", }, "achievements": { "$push": "$achievements" } } @@ -103,6 +105,7 @@ pub async fn handler( "category_img_url": "$_id.category_img_url", "category_type": "$_id.category_type", "category_disabled": "$_id.category_disabled", + "category_override_verified_type": "$_id.category_override_verified_type", "achievements": 1, "_id": 0 } diff --git a/src/endpoints/achievements/mod.rs b/src/endpoints/achievements/mod.rs index 22d318bd..0ea1cc4b 100644 --- a/src/endpoints/achievements/mod.rs +++ b/src/endpoints/achievements/mod.rs @@ -1,3 +1,4 @@ +pub mod batched; pub mod fetch; pub mod fetch_buildings; pub mod verify_achieved_quests; diff --git a/src/endpoints/achievements/verify_achieved_quests.rs b/src/endpoints/achievements/verify_achieved_quests.rs index 8ba9533e..5a5d6851 100644 --- a/src/endpoints/achievements/verify_achieved_quests.rs +++ b/src/endpoints/achievements/verify_achieved_quests.rs @@ -35,7 +35,6 @@ pub async fn handler( match client.get(&url).send().await { Ok(response) => match response.json::>().await { Ok(quests) => { - println!("quests: {:?}", quests); if quests.is_empty() { return get_error("You have not completed any quests.".to_string()); } diff --git a/src/main.rs b/src/main.rs index b8c41816..5451b0f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -354,6 +354,10 @@ async fn main() { "/achievements/verify_tvl", get(endpoints::achievements::verify_tvl::handler), ) + .route( + "/achievements/batched/verify_tvl_batched", + get(endpoints::achievements::batched::verify_tvl_batched::handler), + ) .route( "/achievements/verify_seniority", get(endpoints::achievements::verify_seniority::handler), diff --git a/src/models.rs b/src/models.rs index f9e3c0af..8281ea9d 100644 --- a/src/models.rs +++ b/src/models.rs @@ -115,6 +115,7 @@ pub struct UserAchievements { category_type: String, #[serde(default = "default_category_disabled")] pub category_disabled: bool, + pub category_override_verified_type: Option, achievements: Vec, } @@ -180,3 +181,19 @@ pub_struct!(Deserialize, Debug; DeployedTime { addr: String, timestamp: u32, }); + +pub_struct!(Deserialize; VerifyAchievementBatchedQuery { + addr: FieldElement, + category_id: u32, +}); + +pub_struct!(Deserialize, Serialize, Debug; UserAchievementsCategory { + category_id: u32, + achievements: Vec, +}); + +pub_struct!(Deserialize, Serialize, Debug; UserAchievementCategory { + id: u32, + completed: bool, + verify_type: String, +});