From 766af64e8c730ae991b48eb7040ce3512d272ecc Mon Sep 17 00:00:00 2001 From: nicolasito1411 <60229704+Marchand-Nicolas@users.noreply.github.com> Date: Sat, 13 Jul 2024 15:33:34 +0200 Subject: [PATCH 1/6] feat: nimbora quest 2 --- config.template.toml | 2 + src/config.rs | 5 +- src/endpoints/quests/nimbora/mod.rs | 7 ++- .../quests/nimbora/quest2/check_balance.rs | 58 +++++++++++++++++++ src/endpoints/quests/nimbora/quest2/mod.rs | 1 + 5 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 src/endpoints/quests/nimbora/quest2/check_balance.rs create mode 100644 src/endpoints/quests/nimbora/quest2/mod.rs diff --git a/config.template.toml b/config.template.toml index f16c6dc3..30666764 100644 --- a/config.template.toml +++ b/config.template.toml @@ -71,6 +71,8 @@ contract = "0xXXXXXXXXXXXX" contract = "0xXXXXXXXXXXXX" [quests.sithswap_2] api_endpoint = "xxxxxxx" +[quests.nimbora_2] +contract = "xxxxx" [rhino] api_endpoint = "xxxxx" diff --git a/src/config.rs b/src/config.rs index e3e9a288..4c031db5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -86,6 +86,7 @@ pub_struct!(Clone, Deserialize; Quests { nimbora: Contract, bountive: Contract, sithswap_2: ApiEndpoint, + nimbora_2: Contract, }); pub_struct!(Clone, Deserialize; Twitter { @@ -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/quests/nimbora/mod.rs b/src/endpoints/quests/nimbora/mod.rs index 5f9929f5..eb15c9a8 100644 --- a/src/endpoints/quests/nimbora/mod.rs +++ b/src/endpoints/quests/nimbora/mod.rs @@ -1,4 +1,5 @@ -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 quest2; +pub mod verify_twitter_rt; diff --git a/src/endpoints/quests/nimbora/quest2/check_balance.rs b/src/endpoints/quests/nimbora/quest2/check_balance.rs new file mode 100644 index 00000000..27b49a76 --- /dev/null +++ b/src/endpoints/quests/nimbora/quest2/check_balance.rs @@ -0,0 +1,58 @@ +use std::sync::Arc; + +use crate::{ + models::{AppState, VerifyQuery}, + utils::{get_error, CompletedTasksTrait}, +}; +use axum::{ + extract::{Query, State}, + http::StatusCode, + response::IntoResponse, + Json, +}; +use axum_auto_routes::route; +use serde_json::json; +use starknet::{ + core::types::{BlockId, BlockTag, FieldElement, FunctionCall}, + macros::selector, + providers::Provider, +}; + +#[route( + get, + "/quests/nimbora2/check_balance", + crate::endpoints::quests::nimbora::quest2::check_balance +)] +pub async fn handler( + State(state): State>, + Query(query): Query, +) -> impl IntoResponse { + let task_id = 144; + let addr = &query.addr; + let calldata = vec![*addr]; + let call_result = state + .provider + .call( + FunctionCall { + contract_address: state.conf.quests.nimbora_2.contract, + entry_point_selector: selector!("balance_of"), + 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/endpoints/quests/nimbora/quest2/mod.rs b/src/endpoints/quests/nimbora/quest2/mod.rs new file mode 100644 index 00000000..efaa97b9 --- /dev/null +++ b/src/endpoints/quests/nimbora/quest2/mod.rs @@ -0,0 +1 @@ +pub mod check_balance; From 3591652045529c8fb601fa463380c504afa2180c Mon Sep 17 00:00:00 2001 From: nicolasito1411 <60229704+Marchand-Nicolas@users.noreply.github.com> Date: Sun, 14 Jul 2024 15:01:27 +0200 Subject: [PATCH 2/6] feat: adding balance task type --- config.template.toml | 1 + src/config.rs | 1 + src/endpoints/admin/balance/create_balance.rs | 83 +++++++++++++++++ src/endpoints/admin/balance/mod.rs | 2 + src/endpoints/admin/balance/update_balance.rs | 89 +++++++++++++++++++ src/endpoints/admin/custom/create_custom.rs | 30 +++---- src/endpoints/admin/discord/create_discord.rs | 31 +++---- src/endpoints/admin/domain/create_domain.rs | 33 ++++--- src/endpoints/admin/login.rs | 41 ++++----- src/endpoints/admin/mod.rs | 17 ++-- src/endpoints/admin/quiz/create_quiz.rs | 49 +++++----- .../admin/twitter/create_twitter_fw.rs | 36 ++++---- .../admin/twitter/create_twitter_rw.rs | 30 +++---- src/endpoints/quests/mod.rs | 25 +++--- src/endpoints/quests/nimbora/mod.rs | 1 - src/endpoints/quests/nimbora/quest2/mod.rs | 1 - .../check_balance.rs => verify_balance.rs} | 44 +++++++-- src/models.rs | 7 ++ 18 files changed, 365 insertions(+), 156 deletions(-) create mode 100644 src/endpoints/admin/balance/create_balance.rs create mode 100644 src/endpoints/admin/balance/mod.rs create mode 100644 src/endpoints/admin/balance/update_balance.rs delete mode 100644 src/endpoints/quests/nimbora/quest2/mod.rs rename src/endpoints/quests/{nimbora/quest2/check_balance.rs => verify_balance.rs} (53%) diff --git a/config.template.toml b/config.template.toml index 30666764..c2d9d219 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 4c031db5..143370f5 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, diff --git a/src/endpoints/admin/balance/create_balance.rs b/src/endpoints/admin/balance/create_balance.rs new file mode 100644 index 00000000..7c856c34 --- /dev/null +++ b/src/endpoints/admin/balance/create_balance.rs @@ -0,0 +1,83 @@ +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::sync::Arc; + +pub_struct!(Deserialize; CreateBalance { + quest_id: i64, + name: String, + desc: String, + contracts: Vec, + 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; + } + + 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("custom".to_string()), + discord_guild_id: None, + quiz_name: None, + contracts: Some(body.contracts.clone()), + }; + + // 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..7dbef9a1 --- /dev/null +++ b/src/endpoints/admin/balance/update_balance.rs @@ -0,0 +1,89 @@ +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::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 contracts_bson: Vec = + 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 eb15c9a8..2b7bfe06 100644 --- a/src/endpoints/quests/nimbora/mod.rs +++ b/src/endpoints/quests/nimbora/mod.rs @@ -1,5 +1,4 @@ pub mod check_balance; pub mod claimable; pub mod discord_fw_callback; -pub mod quest2; pub mod verify_twitter_rt; diff --git a/src/endpoints/quests/nimbora/quest2/mod.rs b/src/endpoints/quests/nimbora/quest2/mod.rs deleted file mode 100644 index efaa97b9..00000000 --- a/src/endpoints/quests/nimbora/quest2/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod check_balance; diff --git a/src/endpoints/quests/nimbora/quest2/check_balance.rs b/src/endpoints/quests/verify_balance.rs similarity index 53% rename from src/endpoints/quests/nimbora/quest2/check_balance.rs rename to src/endpoints/quests/verify_balance.rs index 27b49a76..82a1c87d 100644 --- a/src/endpoints/quests/nimbora/quest2/check_balance.rs +++ b/src/endpoints/quests/verify_balance.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use crate::{ - models::{AppState, VerifyQuery}, + models::{AppState, QuestTaskDocument}, utils::{get_error, CompletedTasksTrait}, }; use axum::{ @@ -11,6 +11,8 @@ use axum::{ 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}, @@ -18,24 +20,50 @@ use starknet::{ providers::Provider, }; +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct VerifyBalanceQuery { + pub addr: FieldElement, + pub task_id: u32, +} + #[route( get, - "/quests/nimbora2/check_balance", - crate::endpoints::quests::nimbora::quest2::check_balance + "/quests/verify_balance", + crate::endpoints::quests::verify_balance )] pub async fn handler( State(state): State>, - Query(query): Query, + Query(query): Query, ) -> impl IntoResponse { - let task_id = 144; + 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 calldata = vec![*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: state.conf.quests.nimbora_2.contract, - entry_point_selector: selector!("balance_of"), + contract_address: utils_contract, + entry_point_selector: selector!("sum_balances"), calldata, }, BlockId::Tag(BlockTag::Latest), 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, From 37c2d3d14b0bd9b260a95f5195594b8929e9c45c Mon Sep 17 00:00:00 2001 From: nicolasito1411 <60229704+Marchand-Nicolas@users.noreply.github.com> Date: Sun, 14 Jul 2024 15:59:34 +0200 Subject: [PATCH 3/6] fix contract deserialization --- src/endpoints/admin/balance/create_balance.rs | 12 ++++++++++-- src/endpoints/admin/balance/update_balance.rs | 9 +++++++-- src/utils.rs | 10 +++++++--- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/endpoints/admin/balance/create_balance.rs b/src/endpoints/admin/balance/create_balance.rs index 7c856c34..016a0575 100644 --- a/src/endpoints/admin/balance/create_balance.rs +++ b/src/endpoints/admin/balance/create_balance.rs @@ -14,13 +14,14 @@ 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: Vec, + contracts: String, href: String, cta: String, }); @@ -55,6 +56,13 @@ pub async fn handler( 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(), @@ -68,7 +76,7 @@ pub async fn handler( task_type: Some("custom".to_string()), discord_guild_id: None, quiz_name: None, - contracts: Some(body.contracts.clone()), + contracts: Some(parsed_contracts), }; // insert document to boost collection diff --git a/src/endpoints/admin/balance/update_balance.rs b/src/endpoints/admin/balance/update_balance.rs index 7dbef9a1..b109fe98 100644 --- a/src/endpoints/admin/balance/update_balance.rs +++ b/src/endpoints/admin/balance/update_balance.rs @@ -13,13 +13,14 @@ 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>, + contracts: Option, href: Option, cta: Option, }); @@ -67,8 +68,12 @@ pub async fn handler( 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 = - contracts.iter().map(field_element_to_bson).collect(); + parsed_contracts.iter().map(field_element_to_bson).collect(); update_doc.insert("contracts", contracts_bson); } diff --git a/src/utils.rs b/src/utils.rs index aae0c87f..4a615881 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, @@ -63,7 +66,9 @@ macro_rules! check_authorization { } } } - None => return get_error("missing auth header".to_string()), + //None => return get_error("missing auth header".to_string()), + // Temp: return as everyone is super user + None => "super_user".to_string(), } }; } @@ -798,7 +803,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(); From d95fb57b3e4307cef2a45e200d22e18d8f85b82e Mon Sep 17 00:00:00 2001 From: nicolasito1411 <60229704+Marchand-Nicolas@users.noreply.github.com> Date: Sun, 14 Jul 2024 16:00:07 +0200 Subject: [PATCH 4/6] removing temp fix --- src/utils.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 4a615881..2a1f230b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -66,9 +66,7 @@ macro_rules! check_authorization { } } } - //None => return get_error("missing auth header".to_string()), - // Temp: return as everyone is super user - None => "super_user".to_string(), + None => return get_error("missing auth header".to_string()), } }; } From 58db63ee02b773925bfb9772b37fb23113bf3a00 Mon Sep 17 00:00:00 2001 From: nicolasito1411 <60229704+Marchand-Nicolas@users.noreply.github.com> Date: Sun, 14 Jul 2024 16:32:31 +0200 Subject: [PATCH 5/6] fix: task type --- src/endpoints/admin/balance/create_balance.rs | 2 +- src/endpoints/quests/verify_balance.rs | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/endpoints/admin/balance/create_balance.rs b/src/endpoints/admin/balance/create_balance.rs index 016a0575..4f804fef 100644 --- a/src/endpoints/admin/balance/create_balance.rs +++ b/src/endpoints/admin/balance/create_balance.rs @@ -73,7 +73,7 @@ pub async fn handler( cta: body.cta.clone(), verify_endpoint: "/quests/verify_balance".to_string(), verify_endpoint_type: "default".to_string(), - task_type: Some("custom".to_string()), + task_type: Some("balance".to_string()), discord_guild_id: None, quiz_name: None, contracts: Some(parsed_contracts), diff --git a/src/endpoints/quests/verify_balance.rs b/src/endpoints/quests/verify_balance.rs index 82a1c87d..8df6d9a1 100644 --- a/src/endpoints/quests/verify_balance.rs +++ b/src/endpoints/quests/verify_balance.rs @@ -39,11 +39,7 @@ pub async fn handler( // Get task in db let task_collection = state.db.collection("tasks"); let task: QuestTaskDocument = task_collection - .find_one( - doc! {"id - ": task_id}, - None, - ) + .find_one(doc! {"id": task_id}, None) .await .unwrap() .unwrap(); From 39468b4544649dd2ada28474c7d894941148edbb Mon Sep 17 00:00:00 2001 From: nicolasito1411 <60229704+Marchand-Nicolas@users.noreply.github.com> Date: Sun, 14 Jul 2024 16:51:49 +0200 Subject: [PATCH 6/6] removing nimbora_2 from config --- config.template.toml | 2 -- src/config.rs | 1 - 2 files changed, 3 deletions(-) diff --git a/config.template.toml b/config.template.toml index c2d9d219..abb1d4aa 100644 --- a/config.template.toml +++ b/config.template.toml @@ -72,8 +72,6 @@ contract = "0xXXXXXXXXXXXX" contract = "0xXXXXXXXXXXXX" [quests.sithswap_2] api_endpoint = "xxxxxxx" -[quests.nimbora_2] -contract = "xxxxx" [rhino] api_endpoint = "xxxxx" diff --git a/src/config.rs b/src/config.rs index 143370f5..7841d83a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -87,7 +87,6 @@ pub_struct!(Clone, Deserialize; Quests { nimbora: Contract, bountive: Contract, sithswap_2: ApiEndpoint, - nimbora_2: Contract, }); pub_struct!(Clone, Deserialize; Twitter {