diff --git a/src/endpoints/admin/quest/get_quest_participants.rs b/src/endpoints/admin/quest/get_quest_participants.rs new file mode 100644 index 00000000..23735cb1 --- /dev/null +++ b/src/endpoints/admin/quest/get_quest_participants.rs @@ -0,0 +1,101 @@ +use std::sync::Arc; + +use axum::{ + extract::{Extension, Query, State}, + http::StatusCode, + response::{IntoResponse, Json}, +}; +use axum_auto_routes::route; +use futures::StreamExt; +use mongodb::bson::doc; +use serde::Deserialize; +use serde_json::json; +use starknet::core::types::FieldElement; + +use crate::{middleware::auth::auth_middleware, utils::to_hex}; +use crate::{ + models::{AppState, CompletedTaskDocument, QuestTaskDocument}, + utils::get_error, +}; + +pub_struct!(Deserialize; GetQuestParticipantsParams { + quest_id: i64, +}); + +#[route(get, "/admin/quests/get_quest_participants", auth_middleware)] +pub async fn get_quest_participants_handler( + State(state): State>, + Extension(_sub): Extension, + Query(params): Query, +) -> impl IntoResponse { + let tasks_collection = state.db.collection::("tasks"); + let completed_tasks_collection = state + .db + .collection::("completed_tasks"); + + // Fetch all task IDs for the given quest_id + let task_filter = doc! { "quest_id": params.quest_id }; + let task_ids: Vec = match tasks_collection.find(task_filter, None).await { + Ok(mut cursor) => { + let mut ids = Vec::new(); + while let Some(doc) = cursor.next().await { + match doc { + Ok(task) => ids.push(task.id), + Err(e) => return get_error(format!("Error processing tasks: {}", e)), + } + } + ids + } + Err(e) => return get_error(format!("Error fetching tasks: {}", e)), + }; + + if task_ids.is_empty() { + return get_error(format!("No tasks found for quest_id {}", params.quest_id)); + } + + // Use aggregation pipeline to fetch completed tasks and group by address + let pipeline = vec![ + doc! { "$match": { "task_id": { "$in": &task_ids } } }, + doc! { "$group": { + "_id": "$address", + "task_ids": { "$addToSet": "$task_id" } + }}, + doc! { "$project": { + "address": "$_id", + "tasks_completed_count": { "$size": "$task_ids" } + }}, + ]; + + let mut cursor = match completed_tasks_collection.aggregate(pipeline, None).await { + Ok(cursor) => cursor, + Err(e) => return get_error(format!("Error aggregating completed tasks: {}", e)), + }; + + let total_tasks = task_ids.len(); + let mut participants = Vec::new(); + + while let Some(doc) = cursor.next().await { + match doc { + Ok(doc) => { + // Get the decimal address and convert it to a hex string + let address: String = match doc.get_str("address") { + Ok(addr) => to_hex(FieldElement::from_dec_str(addr).unwrap()), + Err(_) => continue, // Skip invalid documents + }; + + let tasks_completed_count: usize = match doc.get_i32("tasks_completed_count") { + Ok(count) => count as usize, + Err(_) => continue, // Skip invalid documents + }; + + if tasks_completed_count == total_tasks { + participants.push(address); + } + } + Err(e) => return get_error(format!("Error processing aggregation results: {}", e)), + } + } + + let participants_json = json!({ "participants": participants }); + (StatusCode::OK, Json(participants_json)).into_response() +} diff --git a/src/endpoints/admin/quest/mod.rs b/src/endpoints/admin/quest/mod.rs index 7d39ec14..514940a8 100644 --- a/src/endpoints/admin/quest/mod.rs +++ b/src/endpoints/admin/quest/mod.rs @@ -1,5 +1,6 @@ pub mod create_quest; mod get_quest; +pub mod get_quest_participants; pub mod get_quests; pub mod get_tasks; pub mod update_quest; diff --git a/src/endpoints/admin/quiz/create_question.rs b/src/endpoints/admin/quiz/create_question.rs index a279985a..d103d1df 100644 --- a/src/endpoints/admin/quiz/create_question.rs +++ b/src/endpoints/admin/quiz/create_question.rs @@ -65,7 +65,8 @@ pub async fn handler( let mut state_last_id = state.last_question_id.lock().await; - let next_quiz_question_id = get_next_question_id(&quiz_questions_collection, state_last_id.clone()).await; + let next_quiz_question_id = + get_next_question_id(&quiz_questions_collection, state_last_id.clone()).await; *state_last_id = next_quiz_question_id; diff --git a/src/endpoints/admin/quiz/delete_question.rs b/src/endpoints/admin/quiz/delete_question.rs index 7039f600..4e4a3f4a 100644 --- a/src/endpoints/admin/quiz/delete_question.rs +++ b/src/endpoints/admin/quiz/delete_question.rs @@ -65,7 +65,10 @@ pub async fn handler( "id": &body.id, }; - return match quiz_questions_collection.delete_one(question_filter, None).await { + return match quiz_questions_collection + .delete_one(question_filter, None) + .await + { Ok(_) => ( StatusCode::OK, Json(json!({"message": "deleted successfully"})), @@ -73,4 +76,4 @@ pub async fn handler( .into_response(), Err(_e) => get_error("error deleting question".to_string()), }; -} \ No newline at end of file +} diff --git a/src/endpoints/admin/quiz/mod.rs b/src/endpoints/admin/quiz/mod.rs index df1f8777..27fcc69c 100644 --- a/src/endpoints/admin/quiz/mod.rs +++ b/src/endpoints/admin/quiz/mod.rs @@ -1,6 +1,6 @@ pub mod create_question; pub mod create_quiz; +pub mod delete_question; pub mod get_quiz; pub mod update_question; pub mod update_quiz; -pub mod delete_question; \ No newline at end of file diff --git a/src/endpoints/defi/mod.rs b/src/endpoints/defi/mod.rs index bad87da0..a5977669 100644 --- a/src/endpoints/defi/mod.rs +++ b/src/endpoints/defi/mod.rs @@ -1 +1 @@ -pub mod rewards; \ No newline at end of file +pub mod rewards; diff --git a/src/endpoints/get_boosted_quests.rs b/src/endpoints/get_boosted_quests.rs index 74b04cc4..fd292426 100644 --- a/src/endpoints/get_boosted_quests.rs +++ b/src/endpoints/get_boosted_quests.rs @@ -1,10 +1,10 @@ use crate::{models::AppState, utils::get_error}; use axum::{extract::State, response::IntoResponse, Json}; +use axum::http::StatusCode; use axum_auto_routes::route; use futures::TryStreamExt; use mongodb::bson::{doc, Document}; -use axum::http::StatusCode; use std::sync::Arc; #[route(get, "/get_boosted_quests")] diff --git a/src/endpoints/get_completed_quests.rs b/src/endpoints/get_completed_quests.rs index 2e39cac7..7a0e32fd 100644 --- a/src/endpoints/get_completed_quests.rs +++ b/src/endpoints/get_completed_quests.rs @@ -5,10 +5,10 @@ use axum::{ Json, }; +use axum::http::StatusCode; use axum_auto_routes::route; use futures::TryStreamExt; use mongodb::bson::{doc, Document}; -use axum::http::StatusCode; use serde::{Deserialize, Serialize}; use starknet::core::types::FieldElement; use std::sync::Arc; diff --git a/src/endpoints/get_quest_participants.rs b/src/endpoints/get_quest_participants.rs index 2e11b2e3..4ae8f29c 100644 --- a/src/endpoints/get_quest_participants.rs +++ b/src/endpoints/get_quest_participants.rs @@ -5,10 +5,10 @@ use axum::{ Json, }; +use axum::http::StatusCode; use axum_auto_routes::route; use futures::StreamExt; use mongodb::bson::{doc, Document}; -use axum::http::StatusCode; use serde::{Deserialize, Serialize}; use std::sync::Arc; diff --git a/src/endpoints/get_trending_quests.rs b/src/endpoints/get_trending_quests.rs index 6095702c..706a0583 100644 --- a/src/endpoints/get_trending_quests.rs +++ b/src/endpoints/get_trending_quests.rs @@ -2,6 +2,7 @@ use crate::{ models::{AppState, QuestDocument}, utils::get_error, }; +use axum::http::StatusCode; use axum::{ extract::{Query, State}, response::IntoResponse, @@ -10,7 +11,6 @@ use axum::{ use axum_auto_routes::route; use futures::StreamExt; use mongodb::bson::{doc, from_document}; -use axum::http::StatusCode; use serde::{Deserialize, Serialize}; use starknet::core::types::FieldElement; use std::sync::Arc; diff --git a/src/endpoints/has_completed_quest.rs b/src/endpoints/has_completed_quest.rs index e2beff87..9f6c3c4d 100644 --- a/src/endpoints/has_completed_quest.rs +++ b/src/endpoints/has_completed_quest.rs @@ -5,10 +5,10 @@ use axum::{ Json, }; +use axum::http::StatusCode; use axum_auto_routes::route; use futures::TryStreamExt; use mongodb::bson::{doc, Document}; -use axum::http::StatusCode; use serde::{Deserialize, Serialize}; use starknet::core::types::FieldElement; use std::sync::Arc; diff --git a/src/endpoints/leaderboard/get_ranking.rs b/src/endpoints/leaderboard/get_ranking.rs index 2c0ea9d9..b66abe45 100644 --- a/src/endpoints/leaderboard/get_ranking.rs +++ b/src/endpoints/leaderboard/get_ranking.rs @@ -41,12 +41,12 @@ use axum::{ use axum_auto_routes::route; use crate::utils::get_timestamp_from_days; +use axum::http::StatusCode; use axum::http::{header, Response}; use chrono::Utc; use futures::TryStreamExt; use mongodb::bson::{doc, Document}; use mongodb::Collection; -use axum::http::StatusCode; use serde::{Deserialize, Serialize}; use std::sync::Arc; diff --git a/src/endpoints/leaderboard/get_static_info.rs b/src/endpoints/leaderboard/get_static_info.rs index 0a04f7f8..ea95bc7b 100644 --- a/src/endpoints/leaderboard/get_static_info.rs +++ b/src/endpoints/leaderboard/get_static_info.rs @@ -16,11 +16,11 @@ use axum_auto_routes::route; use crate::utils::get_timestamp_from_days; use axum::http::header; +use axum::http::StatusCode; use axum::response::Response; use chrono::Utc; use futures::TryStreamExt; use mongodb::bson::{doc, Document}; -use axum::http::StatusCode; use serde::{Deserialize, Serialize}; use std::sync::Arc; diff --git a/src/endpoints/mod.rs b/src/endpoints/mod.rs index 58c8d42d..7bcf59be 100644 --- a/src/endpoints/mod.rs +++ b/src/endpoints/mod.rs @@ -1,6 +1,7 @@ pub mod achievements; pub mod admin; pub mod analytics; +pub mod defi; pub mod discover; pub mod get_boosted_quests; pub mod get_completed_quests; @@ -17,4 +18,3 @@ pub mod leaderboard; pub mod quest_boost; pub mod quests; pub mod unique_page_visit; -pub mod defi; diff --git a/src/endpoints/quest_boost/get_claim_params.rs b/src/endpoints/quest_boost/get_claim_params.rs index 937aef26..e15e98e6 100644 --- a/src/endpoints/quest_boost/get_claim_params.rs +++ b/src/endpoints/quest_boost/get_claim_params.rs @@ -8,8 +8,8 @@ use axum_auto_routes::route; use std::str::FromStr; use crate::utils::to_hex; -use mongodb::bson::{doc, Bson, Document}; use axum::http::StatusCode; +use mongodb::bson::{doc, Bson, Document}; use serde::{Deserialize, Serialize}; use serde_json::json; use starknet::core::crypto::ecdsa_sign; diff --git a/src/endpoints/quest_boost/get_completed_boosts.rs b/src/endpoints/quest_boost/get_completed_boosts.rs index 1c3b8daf..bc69bfc1 100644 --- a/src/endpoints/quest_boost/get_completed_boosts.rs +++ b/src/endpoints/quest_boost/get_completed_boosts.rs @@ -5,10 +5,10 @@ use axum::{ Json, }; +use axum::http::StatusCode; use axum_auto_routes::route; use futures::TryStreamExt; use mongodb::bson::{doc, Document}; -use axum::http::StatusCode; use serde::{Deserialize, Serialize}; use starknet::core::types::FieldElement; use std::sync::Arc; diff --git a/src/endpoints/quests/claimable.rs b/src/endpoints/quests/claimable.rs index 9059342c..f2c81650 100644 --- a/src/endpoints/quests/claimable.rs +++ b/src/endpoints/quests/claimable.rs @@ -7,10 +7,10 @@ use axum::{ use crate::models::{Reward, RewardResponse}; use crate::utils::get_nft; +use axum::http::StatusCode; use axum_auto_routes::route; use futures::TryStreamExt; use mongodb::bson::{doc, Document}; -use axum::http::StatusCode; use serde::{Deserialize, Serialize}; use starknet::core::types::FieldElement; use starknet::signers::{LocalWallet, SigningKey}; diff --git a/src/logger.rs b/src/logger.rs index 973609f8..b1bb4409 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,5 +1,4 @@ use chrono::Utc; -use reqwest; use serde_derive::Serialize; use std::borrow::Cow; use std::sync::Arc; diff --git a/src/models.rs b/src/models.rs index dc86f03e..58402947 100644 --- a/src/models.rs +++ b/src/models.rs @@ -105,9 +105,9 @@ pub_struct!(Deserialize; CompletedTasks { #[derive(Debug, Serialize, Deserialize)] pub struct CompletedTaskDocument { - address: String, - task_id: u32, - timestamp: i64, + pub address: String, + pub task_id: u32, + pub timestamp: i64, } impl CompletedTaskDocument { @@ -522,3 +522,7 @@ pub struct DefiReward { pub amount: FieldElement, pub token_symbol: String, } + +pub_struct!(Deserialize; GetQuestParticipantsParams { + quest_id: i64, +});