diff --git a/config.template.toml b/config.template.toml index f16c6dc3..abb1d4aa 100644 --- a/config.template.toml +++ b/config.template.toml @@ -35,6 +35,7 @@ api_endpoint="XXXXXXXXXXXXXXXXX" api_key="XXXXXXXXXXXXXXXXX" [quests] +utils_contract = "0x012" [quests.sithswap] utils_contract = "0xXXXXXXXXXXXX" pairs = ["0xXXXXXXXXXXXX"] diff --git a/src/config.rs b/src/config.rs index e3e9a288..7841d83a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -72,6 +72,7 @@ pub_struct!(Clone, Deserialize; ZkLend { }); pub_struct!(Clone, Deserialize; Quests { + utils_contract: FieldElement, sithswap: Pairs, zklend: ZkLend, jediswap: Pairs, @@ -112,8 +113,8 @@ pub enum QuizQuestionType { impl<'de> Deserialize<'de> for QuizQuestionType { fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, + where + D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; match s.to_lowercase().as_str() { diff --git a/src/endpoints/admin/balance/create_balance.rs b/src/endpoints/admin/balance/create_balance.rs new file mode 100644 index 00000000..4f804fef --- /dev/null +++ b/src/endpoints/admin/balance/create_balance.rs @@ -0,0 +1,91 @@ +use crate::models::{JWTClaims, QuestDocument, QuestTaskDocument}; +use crate::utils::verify_quest_auth; +use crate::{models::AppState, utils::get_error}; +use axum::http::HeaderMap; +use axum::{ + extract::State, + http::StatusCode, + response::{IntoResponse, Json}, +}; +use axum_auto_routes::route; +use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; +use mongodb::bson::doc; +use mongodb::options::FindOneOptions; +use serde::Deserialize; +use serde_json::json; +use starknet::core::types::FieldElement; +use std::str::FromStr; +use std::sync::Arc; + +pub_struct!(Deserialize; CreateBalance { + quest_id: i64, + name: String, + desc: String, + contracts: String, + href: String, + cta: String, +}); + +#[route( + post, + "/admin/tasks/balance/create", + crate::endpoints::admin::balance::create_balance +)] +pub async fn handler( + State(state): State>, + headers: HeaderMap, + body: Json, +) -> impl IntoResponse { + let user = check_authorization!(headers, &state.conf.auth.secret_key.as_ref()) as String; + let collection = state.db.collection::("tasks"); + // Get the last id in increasing order + let last_id_filter = doc! {}; + let options = FindOneOptions::builder().sort(doc! {"id": -1}).build(); + let last_doc = &collection.find_one(last_id_filter, options).await.unwrap(); + + let quests_collection = state.db.collection::("quests"); + + let res = verify_quest_auth(user, &quests_collection, &(body.quest_id as i64)).await; + if !res { + return get_error("Error creating task".to_string()); + }; + + let mut next_id = 1; + if let Some(doc) = last_doc { + let last_id = doc.id; + next_id = last_id + 1; + } + + // Build a vector of FieldElement from the comma separated contracts string + let parsed_contracts: Vec = body + .contracts + .split(",") + .map(|x| FieldElement::from_str(&x).unwrap()) + .collect(); + + let new_document = QuestTaskDocument { + name: body.name.clone(), + desc: body.desc.clone(), + verify_redirect: None, + href: body.href.clone(), + quest_id: body.quest_id, + id: next_id, + cta: body.cta.clone(), + verify_endpoint: "/quests/verify_balance".to_string(), + verify_endpoint_type: "default".to_string(), + task_type: Some("balance".to_string()), + discord_guild_id: None, + quiz_name: None, + contracts: Some(parsed_contracts), + }; + + // insert document to boost collection + return match collection.insert_one(new_document, None).await { + Ok(_) => ( + StatusCode::OK, + Json(json!({"message": "Task created successfully"})).into_response(), + ) + .into_response(), + Err(_e) => get_error("Error creating tasks".to_string()), + }; +} diff --git a/src/endpoints/admin/balance/mod.rs b/src/endpoints/admin/balance/mod.rs new file mode 100644 index 00000000..dd7f7830 --- /dev/null +++ b/src/endpoints/admin/balance/mod.rs @@ -0,0 +1,2 @@ +pub mod create_balance; +pub mod update_balance; diff --git a/src/endpoints/admin/balance/update_balance.rs b/src/endpoints/admin/balance/update_balance.rs new file mode 100644 index 00000000..b109fe98 --- /dev/null +++ b/src/endpoints/admin/balance/update_balance.rs @@ -0,0 +1,94 @@ +use crate::models::{JWTClaims, QuestTaskDocument}; +use crate::utils::verify_task_auth; +use crate::{models::AppState, utils::get_error}; +use axum::http::HeaderMap; +use axum::{ + extract::State, + http::StatusCode, + response::{IntoResponse, Json}, +}; +use axum_auto_routes::route; +use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; +use mongodb::bson::doc; +use serde::Deserialize; +use serde_json::json; +use starknet::core::types::FieldElement; +use std::str::FromStr; +use std::sync::Arc; + +pub_struct!(Deserialize; CreateBalance { + id: i64, + name: Option, + desc: Option, + contracts: Option, + href: Option, + cta: Option, +}); + +// Helper function to convert FieldElement to Bson +fn field_element_to_bson(fe: &FieldElement) -> mongodb::bson::Bson { + mongodb::bson::Bson::String(fe.to_string()) +} + +#[route( + post, + "/admin/tasks/balance/update", + crate::endpoints::admin::balance::update_balance +)] +pub async fn handler( + State(state): State>, + headers: HeaderMap, + body: Json, +) -> impl IntoResponse { + let user = check_authorization!(headers, &state.conf.auth.secret_key.as_ref()) as String; + let collection = state.db.collection::("tasks"); + + let res = verify_task_auth(user, &collection, &(body.id as i32)).await; + if !res { + return get_error("Error updating tasks".to_string()); + } + + // filter to get existing quest + let filter = doc! { + "id": &body.id, + }; + + let mut update_doc = doc! {}; + + if let Some(name) = &body.name { + update_doc.insert("name", name); + } + if let Some(desc) = &body.desc { + update_doc.insert("desc", desc); + } + if let Some(href) = &body.href { + update_doc.insert("href", href); + } + if let Some(cta) = &body.cta { + update_doc.insert("cta", cta); + } + if let Some(contracts) = &body.contracts { + let parsed_contracts: Vec = contracts + .split(",") + .map(|x| FieldElement::from_str(&x).unwrap()) + .collect(); + let contracts_bson: Vec = + parsed_contracts.iter().map(field_element_to_bson).collect(); + update_doc.insert("contracts", contracts_bson); + } + + // update quest query + let update = doc! { + "$set": update_doc + }; + + // insert document to boost collection + return match collection.find_one_and_update(filter, update, None).await { + Ok(_) => ( + StatusCode::OK, + Json(json!({"message": "Task updated successfully"})).into_response(), + ) + .into_response(), + Err(_e) => get_error("Error updating tasks".to_string()), + }; +} diff --git a/src/endpoints/admin/custom/create_custom.rs b/src/endpoints/admin/custom/create_custom.rs index 97517a32..01e37da1 100644 --- a/src/endpoints/admin/custom/create_custom.rs +++ b/src/endpoints/admin/custom/create_custom.rs @@ -1,20 +1,19 @@ -use crate::models::{QuestDocument, QuestTaskDocument,JWTClaims}; +use crate::models::{JWTClaims, QuestDocument, QuestTaskDocument}; +use crate::utils::verify_quest_auth; use crate::{models::AppState, utils::get_error}; +use axum::http::HeaderMap; use axum::{ extract::State, http::StatusCode, response::{IntoResponse, Json}, }; use axum_auto_routes::route; -use mongodb::bson::{doc}; +use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; +use mongodb::bson::doc; use mongodb::options::FindOneOptions; use serde::Deserialize; use serde_json::json; use std::sync::Arc; -use crate::utils::verify_quest_auth; -use axum::http::HeaderMap; -use jsonwebtoken::{Validation,Algorithm,decode,DecodingKey}; - pub_struct!(Deserialize; CreateCustom { quest_id: i64, @@ -25,7 +24,11 @@ pub_struct!(Deserialize; CreateCustom { api: String, }); -#[route(post, "/admin/tasks/custom/create", crate::endpoints::admin::custom::create_custom)] +#[route( + post, + "/admin/tasks/custom/create", + crate::endpoints::admin::custom::create_custom +)] pub async fn handler( State(state): State>, headers: HeaderMap, @@ -40,8 +43,7 @@ pub async fn handler( let quests_collection = state.db.collection::("quests"); - - let res= verify_quest_auth(user, &quests_collection, &(body.quest_id as i64)).await; + let res = verify_quest_auth(user, &quests_collection, &(body.quest_id as i64)).await; if !res { return get_error("Error creating task".to_string()); }; @@ -57,7 +59,7 @@ pub async fn handler( desc: body.desc.clone(), verify_redirect: Some(body.href.clone()), href: body.href.clone(), - quest_id : body.quest_id, + quest_id: body.quest_id, id: next_id, cta: body.cta.clone(), verify_endpoint: body.api.clone(), @@ -65,15 +67,11 @@ pub async fn handler( task_type: Some("custom".to_string()), discord_guild_id: None, quiz_name: None, + contracts: None, }; // insert document to boost collection - return match collection - .insert_one(new_document, - None, - ) - .await - { + return match collection.insert_one(new_document, None).await { Ok(_) => ( StatusCode::OK, Json(json!({"message": "Task created successfully"})).into_response(), diff --git a/src/endpoints/admin/discord/create_discord.rs b/src/endpoints/admin/discord/create_discord.rs index 1f7e97f8..d57e85d1 100644 --- a/src/endpoints/admin/discord/create_discord.rs +++ b/src/endpoints/admin/discord/create_discord.rs @@ -1,21 +1,19 @@ -use crate::models::{QuestDocument, QuestTaskDocument,JWTClaims}; +use crate::models::{JWTClaims, QuestDocument, QuestTaskDocument}; +use crate::utils::verify_quest_auth; use crate::{models::AppState, utils::get_error}; +use axum::http::HeaderMap; use axum::{ extract::State, http::StatusCode, response::{IntoResponse, Json}, }; use axum_auto_routes::route; -use mongodb::bson::{doc}; +use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; +use mongodb::bson::doc; use mongodb::options::FindOneOptions; use serde::Deserialize; use serde_json::json; use std::sync::Arc; -use axum::http::HeaderMap; -use crate::utils::verify_quest_auth; -use jsonwebtoken::{Validation,Algorithm,decode,DecodingKey}; - - pub_struct!(Deserialize; CreateCustom { quest_id: i64, @@ -25,13 +23,17 @@ pub_struct!(Deserialize; CreateCustom { guild_id: String, }); -#[route(post, "/admin/tasks/discord/create", crate::endpoints::admin::discord::create_discord)] +#[route( + post, + "/admin/tasks/discord/create", + crate::endpoints::admin::discord::create_discord +)] pub async fn handler( State(state): State>, headers: HeaderMap, body: Json, ) -> impl IntoResponse { - let user = check_authorization!(headers, &state.conf.auth.secret_key.as_ref()) as String; + let user = check_authorization!(headers, &state.conf.auth.secret_key.as_ref()) as String; let collection = state.db.collection::("tasks"); // Get the last id in increasing order let last_id_filter = doc! {}; @@ -40,8 +42,7 @@ pub async fn handler( let quests_collection = state.db.collection::("quests"); - - let res= verify_quest_auth(user, &quests_collection, &(body.quest_id as i64)).await; + let res = verify_quest_auth(user, &quests_collection, &(body.quest_id as i64)).await; if !res { return get_error("Error creating task".to_string()); }; @@ -65,15 +66,11 @@ pub async fn handler( discord_guild_id: Some(body.guild_id.clone()), quiz_name: None, verify_redirect: None, + contracts: None, }; // insert document to boost collection - return match collection - .insert_one(new_document, - None, - ) - .await - { + return match collection.insert_one(new_document, None).await { Ok(_) => ( StatusCode::OK, Json(json!({"message": "Task created successfully"})).into_response(), diff --git a/src/endpoints/admin/domain/create_domain.rs b/src/endpoints/admin/domain/create_domain.rs index 99a0995a..83d32be6 100644 --- a/src/endpoints/admin/domain/create_domain.rs +++ b/src/endpoints/admin/domain/create_domain.rs @@ -1,21 +1,19 @@ +use crate::models::{JWTClaims, QuestDocument, QuestTaskDocument}; +use crate::utils::verify_quest_auth; use crate::{models::AppState, utils::get_error}; +use axum::http::HeaderMap; use axum::{ extract::State, http::StatusCode, response::{IntoResponse, Json}, }; use axum_auto_routes::route; -use mongodb::bson::{doc}; -use mongodb::options::{FindOneOptions}; +use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; +use mongodb::bson::doc; +use mongodb::options::FindOneOptions; +use serde::Deserialize; use serde_json::json; use std::sync::Arc; -use serde::Deserialize; -use crate::models::{QuestDocument, QuestTaskDocument,JWTClaims}; -use axum::http::HeaderMap; -use crate::utils::verify_quest_auth; -use jsonwebtoken::{Validation,Algorithm,decode,DecodingKey}; - - pub_struct!(Deserialize; CreateTwitterFw { name: String, @@ -23,18 +21,21 @@ pub_struct!(Deserialize; CreateTwitterFw { quest_id: i64, }); -#[route(post, "/admin/tasks/domain/create", crate::endpoints::admin::domain::create_domain)] +#[route( + post, + "/admin/tasks/domain/create", + crate::endpoints::admin::domain::create_domain +)] pub async fn handler( State(state): State>, headers: HeaderMap, body: Json, ) -> impl IntoResponse { - let user = check_authorization!(headers, &state.conf.auth.secret_key.as_ref()) as String; + let user = check_authorization!(headers, &state.conf.auth.secret_key.as_ref()) as String; let collection = state.db.collection::("tasks"); let quests_collection = state.db.collection::("quests"); - - let res= verify_quest_auth(user, &quests_collection, &body.quest_id).await; + let res = verify_quest_auth(user, &quests_collection, &body.quest_id).await; if !res { return get_error("Error creating task".to_string()); }; @@ -62,13 +63,11 @@ pub async fn handler( discord_guild_id: None, quiz_name: None, verify_redirect: None, + contracts: None, }; // insert document to boost collection - return match collection - .insert_one(new_document, None) - .await - { + return match collection.insert_one(new_document, None).await { Ok(_) => ( StatusCode::OK, Json(json!({"message": "Task created successfully"})).into_response(), diff --git a/src/endpoints/admin/login.rs b/src/endpoints/admin/login.rs index c2fee9bc..8d581eab 100644 --- a/src/endpoints/admin/login.rs +++ b/src/endpoints/admin/login.rs @@ -1,22 +1,19 @@ -use crate::{ - models::{AppState}, - utils::get_error, -}; +use crate::models::{JWTClaims, LoginDetails}; +use crate::utils::calculate_hash; +use crate::{models::AppState, utils::get_error}; use axum::{ extract::{Query, State}, http::StatusCode, response::{IntoResponse, Json}, }; use axum_auto_routes::route; +use chrono::Utc; use futures::StreamExt; -use mongodb::bson::{doc,from_document}; +use jsonwebtoken::{encode, EncodingKey, Header}; +use mongodb::bson::{doc, from_document}; use serde::Deserialize; -use std::sync::Arc; -use chrono::Utc; use serde_json::json; -use crate::models::{JWTClaims, LoginDetails}; -use jsonwebtoken::{encode, Header, EncodingKey}; -use crate::utils::calculate_hash; +use std::sync::Arc; #[derive(Deserialize)] pub struct GetQuestsQuery { @@ -30,14 +27,11 @@ pub async fn handler( ) -> impl IntoResponse { let collection = state.db.collection::("login_details"); let hashed_code = calculate_hash(&query.code); - let pipeline = [ - doc! { - "$match": { - "code": hashed_code.to_string(), - } - }, - ]; - + let pipeline = [doc! { + "$match": { + "code": hashed_code.to_string(), + } + }]; match collection.aggregate(pipeline, None).await { Ok(mut cursor) => { @@ -46,12 +40,19 @@ pub async fn handler( Ok(document) => { let secret_key = &state.conf.auth.secret_key; if let Ok(login) = from_document::(document) { - let new_exp = (Utc::now().timestamp_millis() + &state.conf.auth.expiry_duration) as usize; + let new_exp = (Utc::now().timestamp_millis() + + &state.conf.auth.expiry_duration) + as usize; let user_claims = JWTClaims { sub: login.user.parse().unwrap(), exp: new_exp, }; - let token = encode(&Header::default(), &user_claims, &EncodingKey::from_secret(&secret_key.as_ref())).unwrap(); + let token = encode( + &Header::default(), + &user_claims, + &EncodingKey::from_secret(&secret_key.as_ref()), + ) + .unwrap(); return (StatusCode::OK, Json(json!({"token":token}))).into_response(); } } diff --git a/src/endpoints/admin/mod.rs b/src/endpoints/admin/mod.rs index 1d0a1014..da45ab39 100644 --- a/src/endpoints/admin/mod.rs +++ b/src/endpoints/admin/mod.rs @@ -1,11 +1,12 @@ -pub mod login; -pub mod delete_task; -pub mod quiz; -pub mod twitter; -pub mod quest_boost; -pub mod quest; +pub mod balance; pub mod custom; +pub mod delete_task; pub mod discord; -pub mod nft_uri; pub mod domain; -pub mod user; \ No newline at end of file +pub mod login; +pub mod nft_uri; +pub mod quest; +pub mod quest_boost; +pub mod quiz; +pub mod twitter; +pub mod user; diff --git a/src/endpoints/admin/quiz/create_quiz.rs b/src/endpoints/admin/quiz/create_quiz.rs index 05c0ca12..d588ac0e 100644 --- a/src/endpoints/admin/quiz/create_quiz.rs +++ b/src/endpoints/admin/quiz/create_quiz.rs @@ -1,21 +1,19 @@ +use crate::models::{JWTClaims, QuestDocument, QuestTaskDocument, QuizInsertDocument}; +use crate::utils::verify_quest_auth; use crate::{models::AppState, utils::get_error}; +use axum::http::HeaderMap; use axum::{ extract::State, http::StatusCode, response::{IntoResponse, Json}, }; use axum_auto_routes::route; -use mongodb::bson::{doc}; -use mongodb::options::{FindOneOptions}; +use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; +use mongodb::bson::doc; +use mongodb::options::FindOneOptions; +use serde::Deserialize; use serde_json::json; use std::sync::Arc; -use serde::Deserialize; -use crate::models::{QuestDocument, QuestTaskDocument, QuizInsertDocument,JWTClaims}; -use crate::utils::verify_quest_auth; -use axum::http::HeaderMap; -use jsonwebtoken::{Validation,Algorithm,decode,DecodingKey}; - - pub_struct!(Deserialize; CreateQuiz { name: String, @@ -26,20 +24,23 @@ pub_struct!(Deserialize; CreateQuiz { quest_id: i64, }); -#[route(post, "/admin/tasks/quiz/create", crate::endpoints::admin::quiz::create_quiz)] +#[route( + post, + "/admin/tasks/quiz/create", + crate::endpoints::admin::quiz::create_quiz +)] pub async fn handler( State(state): State>, headers: HeaderMap, body: Json, ) -> impl IntoResponse { - let user = check_authorization!(headers, &state.conf.auth.secret_key.as_ref()) as String; + let user = check_authorization!(headers, &state.conf.auth.secret_key.as_ref()) as String; let tasks_collection = state.db.collection::("tasks"); let quiz_collection = state.db.collection::("quizzes"); let quests_collection = state.db.collection::("quests"); - - let res= verify_quest_auth(user, &quests_collection, &body.quest_id).await; + let res = verify_quest_auth(user, &quests_collection, &body.quest_id).await; if !res { return get_error("Error creating task".to_string()); }; @@ -47,7 +48,10 @@ pub async fn handler( // Get the last id in increasing order let last_id_filter = doc! {}; let options = FindOneOptions::builder().sort(doc! {"id": -1}).build(); - let last_quiz_doc = &quiz_collection.find_one(last_id_filter.clone(), options.clone()).await.unwrap(); + let last_quiz_doc = &quiz_collection + .find_one(last_id_filter.clone(), options.clone()) + .await + .unwrap(); let mut next_quiz_id = 1; if let Some(doc) = last_quiz_doc { @@ -62,16 +66,15 @@ pub async fn handler( intro: body.intro.clone(), }; - match quiz_collection - .insert_one(new_quiz_document, None) - .await - { + match quiz_collection.insert_one(new_quiz_document, None).await { Ok(res) => res, Err(_e) => return get_error("Error creating quiz".to_string()), }; - - let last_task_doc = &tasks_collection.find_one(last_id_filter.clone(), options.clone()).await.unwrap(); + let last_task_doc = &tasks_collection + .find_one(last_id_filter.clone(), options.clone()) + .await + .unwrap(); let mut next_id = 1; if let Some(doc) = last_task_doc { let last_id = doc.id; @@ -91,12 +94,10 @@ pub async fn handler( task_type: Some("quiz".to_string()), discord_guild_id: None, verify_redirect: None, + contracts: None, }; - return match tasks_collection - .insert_one(new_document, None) - .await - { + return match tasks_collection.insert_one(new_document, None).await { Ok(_) => ( StatusCode::OK, Json(json!({"id": &next_quiz_id })).into_response(), diff --git a/src/endpoints/admin/twitter/create_twitter_fw.rs b/src/endpoints/admin/twitter/create_twitter_fw.rs index 133cc1b9..e30ee439 100644 --- a/src/endpoints/admin/twitter/create_twitter_fw.rs +++ b/src/endpoints/admin/twitter/create_twitter_fw.rs @@ -1,20 +1,19 @@ +use crate::models::{JWTClaims, QuestDocument, QuestTaskDocument}; +use crate::utils::verify_quest_auth; use crate::{models::AppState, utils::get_error}; +use axum::http::HeaderMap; use axum::{ extract::State, http::StatusCode, response::{IntoResponse, Json}, }; use axum_auto_routes::route; -use mongodb::bson::{doc}; -use mongodb::options::{FindOneOptions}; +use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; +use mongodb::bson::doc; +use mongodb::options::FindOneOptions; +use serde::Deserialize; use serde_json::json; use std::sync::Arc; -use serde::Deserialize; -use crate::models::{QuestDocument, QuestTaskDocument,JWTClaims}; -use crate::utils::{verify_quest_auth}; -use axum::http::HeaderMap; -use jsonwebtoken::{Validation,Algorithm,decode,DecodingKey}; - pub_struct!(Deserialize; CreateTwitterFw { name: String, @@ -23,7 +22,11 @@ pub_struct!(Deserialize; CreateTwitterFw { quest_id: i64, }); -#[route(post, "/admin/tasks/twitter_fw/create", crate::endpoints::admin::twitter::create_twitter_fw)] +#[route( + post, + "/admin/tasks/twitter_fw/create", + crate::endpoints::admin::twitter::create_twitter_fw +)] pub async fn handler( State(state): State>, headers: HeaderMap, @@ -37,13 +40,11 @@ pub async fn handler( let last_doc = &collection.find_one(last_id_filter, options).await.unwrap(); let quests_collection = state.db.collection::("quests"); - - let res= verify_quest_auth(user, &quests_collection, &body.quest_id).await; + let res = verify_quest_auth(user, &quests_collection, &body.quest_id).await; if !res { return get_error("Error creating task".to_string()); }; - let mut next_id = 1; if let Some(doc) = last_doc { let last_id = doc.id; @@ -53,7 +54,10 @@ pub async fn handler( let new_document = QuestTaskDocument { name: body.name.clone(), desc: body.desc.clone(), - verify_redirect: Some(format!("https://twitter.com/intent/user?screen_name={}", body.username.clone())), + verify_redirect: Some(format!( + "https://twitter.com/intent/user?screen_name={}", + body.username.clone() + )), href: format!("https://twitter.com/{}", body.username.clone()), quest_id: body.quest_id.clone(), id: next_id, @@ -63,13 +67,11 @@ pub async fn handler( cta: "Follow".to_string(), discord_guild_id: None, quiz_name: None, + contracts: None, }; // insert document to boost collection - return match collection - .insert_one(new_document, None) - .await - { + return match collection.insert_one(new_document, None).await { Ok(_) => ( StatusCode::OK, Json(json!({"message": "Task created successfully"})).into_response(), diff --git a/src/endpoints/admin/twitter/create_twitter_rw.rs b/src/endpoints/admin/twitter/create_twitter_rw.rs index 77bdb98e..420db5e6 100644 --- a/src/endpoints/admin/twitter/create_twitter_rw.rs +++ b/src/endpoints/admin/twitter/create_twitter_rw.rs @@ -1,20 +1,19 @@ +use crate::models::{JWTClaims, QuestDocument, QuestTaskDocument}; +use crate::utils::verify_quest_auth; use crate::{models::AppState, utils::get_error}; +use axum::http::HeaderMap; use axum::{ extract::State, http::StatusCode, response::{IntoResponse, Json}, }; use axum_auto_routes::route; -use mongodb::bson::{doc}; -use mongodb::options::{FindOneOptions}; +use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; +use mongodb::bson::doc; +use mongodb::options::FindOneOptions; +use serde::Deserialize; use serde_json::json; use std::sync::Arc; -use serde::Deserialize; -use crate::models::{QuestDocument, QuestTaskDocument,JWTClaims}; -use axum::http::HeaderMap; -use crate::utils::verify_quest_auth; -use jsonwebtoken::{Validation,Algorithm,decode,DecodingKey}; - pub_struct!(Deserialize; CreateTwitterRw { name: String, @@ -23,7 +22,11 @@ pub_struct!(Deserialize; CreateTwitterRw { quest_id: i64, }); -#[route(post, "/admin/tasks/twitter_rw/create", crate::endpoints::admin::twitter::create_twitter_rw)] +#[route( + post, + "/admin/tasks/twitter_rw/create", + crate::endpoints::admin::twitter::create_twitter_rw +)] pub async fn handler( State(state): State>, headers: HeaderMap, @@ -38,8 +41,7 @@ pub async fn handler( let quests_collection = state.db.collection::("quests"); - - let res= verify_quest_auth(user, &quests_collection, &body.quest_id).await; + let res = verify_quest_auth(user, &quests_collection, &body.quest_id).await; if !res { return get_error("Error creating task".to_string()); }; @@ -63,13 +65,11 @@ pub async fn handler( cta: "Retweet".to_string(), discord_guild_id: None, quiz_name: None, + contracts: None, }; // insert document to boost collection - return match collection - .insert_one(new_document, None) - .await - { + return match collection.insert_one(new_document, None).await { Ok(_) => ( StatusCode::OK, Json(json!({"message": "task created successfully"})).into_response(), diff --git a/src/endpoints/quests/mod.rs b/src/endpoints/quests/mod.rs index 20db86bb..d16a997f 100644 --- a/src/endpoints/quests/mod.rs +++ b/src/endpoints/quests/mod.rs @@ -1,30 +1,31 @@ pub mod avnu; +pub mod bountive; pub mod braavos; pub mod carmine; +pub mod claimable; pub mod contract_uri; +pub mod discord_fw_callback; pub mod ekubo; pub mod element; pub mod focustree; +pub mod haiko; +pub mod hashstack; +pub mod influence; pub mod jediswap; pub mod morphine; pub mod myswap; +pub mod nimbora; pub mod nostra; pub mod orbiter; +pub mod rango; +pub mod rhino; +pub mod sithswap; pub mod starknet; pub mod starknetid; pub mod tribe; pub mod uri; +pub mod verify_balance; pub mod verify_quiz; -pub mod zklend; -pub mod rhino; -pub mod rango; -pub mod nimbora; -pub mod hashstack; -pub mod haiko; -pub mod claimable; -pub mod discord_fw_callback; -pub mod verify_twitter_rw; pub mod verify_twitter_fw; -pub mod bountive; -pub mod sithswap; -pub mod influence; +pub mod verify_twitter_rw; +pub mod zklend; diff --git a/src/endpoints/quests/nimbora/mod.rs b/src/endpoints/quests/nimbora/mod.rs index 5f9929f5..2b7bfe06 100644 --- a/src/endpoints/quests/nimbora/mod.rs +++ b/src/endpoints/quests/nimbora/mod.rs @@ -1,4 +1,4 @@ -pub mod verify_twitter_rt; -pub mod discord_fw_callback; +pub mod check_balance; pub mod claimable; -pub mod check_balance; \ No newline at end of file +pub mod discord_fw_callback; +pub mod verify_twitter_rt; diff --git a/src/endpoints/quests/verify_balance.rs b/src/endpoints/quests/verify_balance.rs new file mode 100644 index 00000000..8df6d9a1 --- /dev/null +++ b/src/endpoints/quests/verify_balance.rs @@ -0,0 +1,82 @@ +use std::sync::Arc; + +use crate::{ + models::{AppState, QuestTaskDocument}, + utils::{get_error, CompletedTasksTrait}, +}; +use axum::{ + extract::{Query, State}, + http::StatusCode, + response::IntoResponse, + Json, +}; +use axum_auto_routes::route; +use mongodb::bson::doc; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use starknet::{ + core::types::{BlockId, BlockTag, FieldElement, FunctionCall}, + macros::selector, + providers::Provider, +}; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct VerifyBalanceQuery { + pub addr: FieldElement, + pub task_id: u32, +} + +#[route( + get, + "/quests/verify_balance", + crate::endpoints::quests::verify_balance +)] +pub async fn handler( + State(state): State>, + Query(query): Query, +) -> impl IntoResponse { + let task_id = query.task_id; + // Get task in db + let task_collection = state.db.collection("tasks"); + let task: QuestTaskDocument = task_collection + .find_one(doc! {"id": task_id}, None) + .await + .unwrap() + .unwrap(); + + if task.task_type != Some("balance".to_string()) { + return get_error("Invalid task type.".to_string()); + } + + let addr = &query.addr; + let utils_contract = state.conf.quests.utils_contract; + + let mut calldata = vec![addr.clone(), task.contracts.clone().unwrap().len().into()]; + calldata.append(&mut task.contracts.unwrap().clone()); + + let call_result = state + .provider + .call( + FunctionCall { + contract_address: utils_contract, + entry_point_selector: selector!("sum_balances"), + calldata, + }, + BlockId::Tag(BlockTag::Latest), + ) + .await; + + match call_result { + Ok(result) => { + if result[0] < FieldElement::from_dec_str("4000000000000000").unwrap() { + get_error("You didn't invest on nimbora.".to_string()) + } else { + match state.upsert_completed_task(query.addr, task_id).await { + Ok(_) => (StatusCode::OK, Json(json!({"res": true}))).into_response(), + Err(e) => get_error(format!("{}", e)), + } + } + } + Err(e) => get_error(format!("{}", e)), + } +} diff --git a/src/models.rs b/src/models.rs index 1ea901a6..0f20632e 100644 --- a/src/models.rs +++ b/src/models.rs @@ -124,6 +124,8 @@ pub struct QuestTaskDocument { pub task_type: Option, #[serde(default)] pub(crate) discord_guild_id: Option, + #[serde(default)] + pub(crate) contracts: Option>, } pub_struct!(Serialize; Reward { @@ -158,6 +160,11 @@ pub_struct!(Deserialize; VerifyQuizQuery { user_answers_list: Vec>, }); +pub_struct!(Deserialize; VerifyBalanceQuery { + addr: FieldElement, + task_id: u32, +}); + pub_struct!(Deserialize; UniquePageVisit { viewer_ip: String, viewed_page_id: String, diff --git a/src/utils.rs b/src/utils.rs index aae0c87f..2a1f230b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,7 @@ -use crate::models::{AchievementDocument, AppState, BoostTable, CompletedTasks, LeaderboardTable, QuestDocument, QuestTaskDocument, UserExperience}; +use crate::models::{ + AchievementDocument, AppState, BoostTable, CompletedTasks, LeaderboardTable, QuestDocument, + QuestTaskDocument, UserExperience, +}; use async_trait::async_trait; use axum::{ body::Body, @@ -798,7 +801,6 @@ pub async fn verify_quest_auth( Some(_) => true, None => false, } - } pub async fn make_api_request(endpoint: &str, addr: &str, api_key: Option<&str>) -> bool { let client = reqwest::Client::new();