Skip to content

Commit

Permalink
Merge branch 'master' into feat/optimize_leaderboard
Browse files Browse the repository at this point in the history
  • Loading branch information
ayushtom authored Dec 1, 2023
2 parents b4acf61 + c31a131 commit 9aafe0c
Show file tree
Hide file tree
Showing 21 changed files with 656 additions and 38 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ futures = "0.3.28"
reqwest = { version = "0.11.17", features = ["json"] }
rand = "0.8.5"
async-trait = "0.1.68"
percent-encoding = "2.3.0"
percent-encoding = "2.3.1"
chrono = "0.4.19"
lazy_static = "1.4.0"
regex = "1.10.0"
4 changes: 4 additions & 0 deletions config.template.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ contract = "0x01b22f7a9d18754c994ae0ee9adb4628d414232e3ebd748c386ac286f86c3066"
[achievements.carbonable]
contract = "0x0541b5dd5fae206ceccaf4eeb0642e4c04d456c5bc296eab047c9414bdad4f09"

[quest_boost]
private_key = "xxxxxx"
update_interval = 600

[quizzes]

[quizzes.ekubo]
Expand Down
15 changes: 13 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,19 @@ pub_struct!(Clone, Deserialize; Quests {
braavos: Braavos,
element: Element,
nostra: Pairs,
focustree: Api ,
});

pub_struct!(Clone, Deserialize; Twitter {
oauth2_clientid: String,
oauth2_secret: String,
});

pub_struct!(Clone, Deserialize; QuestBoost{
private_key: FieldElement,
update_interval: u64,
});

pub_struct!(Clone, Deserialize; Discord {
oauth2_clientid: String,
oauth2_secret: String,
Expand All @@ -84,8 +90,8 @@ pub enum QuizQuestionType {

impl<'de> Deserialize<'de> for QuizQuestionType {
fn deserialize<D>(deserializer: D) -> Result<QuizQuestionType, D::Error>
where
D: Deserializer<'de>,
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.to_lowercase().as_str() {
Expand Down Expand Up @@ -117,6 +123,10 @@ pub_struct!(Clone, Deserialize; Starkscan {
api_key: String,
});

pub_struct!(Clone, Deserialize; Api {
api_endpoint: String,
});

pub_struct!(Clone, Deserialize; Achievement {
contract: FieldElement,
});
Expand All @@ -139,6 +149,7 @@ pub_struct!(Clone, Deserialize; Config {
quizzes: HashMap<String, Quiz>,
starkscan: Starkscan,
achievements: Achievements,
quest_boost: QuestBoost,
});

pub fn load() -> Config {
Expand Down
14 changes: 2 additions & 12 deletions src/endpoints/achievements/verify_briq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use axum::{
use mongodb::bson::doc;
use serde_json::json;
use starknet::core::types::FieldElement;
use crate::utils::fetch_json_from_url;

pub async fn handler(
State(state): State<Arc<AppState>>,
Expand Down Expand Up @@ -46,7 +47,7 @@ pub async fn handler(
Ok(response) => {
if let Some(sets) = response.get("sets") {
match sets {
serde_json::Value::Array(sets_array) => {
serde_json::Value::Array(sets_array ) => {
for set in sets_array.iter() {
if let serde_json::Value::String(set_str) = set {
let url = format!(
Expand Down Expand Up @@ -104,17 +105,6 @@ pub async fn handler(
}
}

pub async fn fetch_json_from_url(url: String) -> Result<serde_json::Value, String> {
let client = reqwest::Client::new();
match client.get(url).send().await {
Ok(response) => match response.json::<serde_json::Value>().await {
Ok(json) => Ok(json),
Err(e) => Err(format!("Failed to get JSON response: {}", e)),
},
Err(e) => Err(format!("Failed to send request: {}", e)),
}
}

pub async fn check_for_ducks(properties: &serde_json::Value) -> bool {
if let Some(serde_json::Value::Array(value_arr)) =
properties.get("collections").and_then(|c| c.get("value"))
Expand Down
37 changes: 37 additions & 0 deletions src/endpoints/get_quest_category.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use crate::{
models::{AppState, QuestCategoryDocument},
utils::get_error,
};
use axum::{
extract::{Query, State},
http::StatusCode,
response::{IntoResponse, Json},
};
use mongodb::bson::doc;
use serde::Deserialize;
use std::sync::Arc;

#[derive(Deserialize)]
pub struct GetQuestsQuery {
name: String,
}

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<GetQuestsQuery>,
) -> impl IntoResponse {
let collection = state
.db
.collection::<QuestCategoryDocument>("quest_categories");
let filter = doc! {
"name": &query.name
};

match collection.find_one(filter, None).await {
Ok(option) => match option {
Some(category) => (StatusCode::OK, Json(category)).into_response(),
None => get_error("Category not found".to_string()),
},
Err(_) => get_error("Error querying quest category data".to_string()),
}
}
106 changes: 106 additions & 0 deletions src/endpoints/has_completed_quest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use crate::{models::AppState, utils::get_error};
use axum::{
extract::{Query, State},
response::IntoResponse,
Json,
};

use futures::TryStreamExt;
use mongodb::bson::{doc, Document};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use starknet::core::types::FieldElement;
use std::sync::Arc;

#[derive(Debug, Serialize, Deserialize)]
pub struct HasCompletedQuestsQuery {
addr: FieldElement,
quest_id: u32,
}

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<HasCompletedQuestsQuery>,
) -> impl IntoResponse {
let address = query.addr.to_string();
let quest_id = query.quest_id;
let pipeline = vec![
doc! {
"$match": doc! {
"address": address,
}
},
doc! {
"$lookup": doc! {
"from": "tasks",
"localField": "task_id",
"foreignField": "id",
"as": "associatedTask"
}
},
doc! {
"$unwind": "$associatedTask"
},
doc! {
"$project": doc! {
"_id": 0,
"address": 1,
"task_id": 1,
"quest_id": "$associatedTask.quest_id"
}
},
doc! {
"$group": doc! {
"_id": "$quest_id",
"done": doc! {
"$sum": 1
}
}
},
doc! {
"$match": doc! {
"_id": quest_id,
}
},
doc! {
"$lookup": doc! {
"from": "tasks",
"localField": "_id",
"foreignField": "quest_id",
"as": "tasks"
}
},
doc! {
"$project": doc! {
"_id": 0,
"result": doc! {
"$cond": doc! {
"if": doc! {
"$eq": [
doc! {
"$size": "$tasks"
},
"$done"
]
},
"then": true,
"else": false
}
}
}
},
];
let tasks_collection = state.db.collection::<Document>("completed_tasks");
match tasks_collection.aggregate(pipeline, None).await {
Ok(cursor) => {
let mut cursor = cursor;
let mut result = false;
while let Some(doc) = cursor.try_next().await.unwrap() {
result = doc.get("result").unwrap().as_bool().unwrap();
}
let response = serde_json::json!({ "completed": result });
(StatusCode::OK, Json(response)).into_response()
}
Err(_) => get_error("Error querying status".to_string()),
}
}
5 changes: 4 additions & 1 deletion src/endpoints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ pub mod achievements;
pub mod get_completed_quests;
pub mod get_deployed_time;
pub mod get_quest;
pub mod get_quest_category;
pub mod get_quest_participants;
pub mod get_quests;
pub mod get_quiz;
pub mod get_tasks;
pub mod get_trending_quests;
pub mod quests;
pub mod has_completed_quest;
pub mod leaderboard;
pub mod quest_boost;
pub mod quests;
62 changes: 62 additions & 0 deletions src/endpoints/quest_boost/get_claim_params.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use crate::{models::AppState, utils::get_error};
use axum::{
extract::{Query, State},
response::IntoResponse,
Json,
};
use std::str::FromStr;

use mongodb::bson::{doc, Document};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use serde_json::json;
use starknet::core::crypto::ecdsa_sign;
use starknet::core::{crypto::pedersen_hash, types::FieldElement};
use std::sync::Arc;

#[derive(Debug, Serialize, Deserialize)]
pub struct GetClaimBoostQuery {
boost_id: u32,
}

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<GetClaimBoostQuery>,
) -> impl IntoResponse {
let boost_id = query.boost_id;
let collection = state.db.collection::<Document>("boosts");
let res=collection.find_one(doc! {"id":boost_id},None).await.unwrap();

// if no boost found with the requested id
if res.is_none() {
return get_error(format!("Boost with id {} not found", boost_id));
}

let boost: Document = res.unwrap();
let amount = boost.get("amount").unwrap().as_i32().unwrap() as u32;
let token = boost.get("token").unwrap().as_str().unwrap();
let address = boost.get("winner").unwrap().as_str().unwrap();

let hashed = pedersen_hash(
&FieldElement::from(boost_id),
&pedersen_hash(
&FieldElement::from(amount),
&pedersen_hash(
&FieldElement::from(0 as u32),
&pedersen_hash(
&FieldElement::from_str(token).unwrap(),
&FieldElement::from_str(address).unwrap(),
),
),
),
);

match ecdsa_sign(&state.conf.quest_boost.private_key, &hashed) {
Ok(signature) => (
StatusCode::OK,
Json(json!({"address": address, "r": signature.r, "s": signature.s})),
)
.into_response(),
Err(e) => get_error(format!("Error while generating signature: {}", e)),
}
}
1 change: 1 addition & 0 deletions src/endpoints/quest_boost/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod get_claim_params;
12 changes: 1 addition & 11 deletions src/endpoints/quests/element/briq/verify_own_briq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use axum::{
};
use serde_json::json;
use starknet::core::types::FieldElement;
use crate::utils::fetch_json_from_url;

pub async fn handler(
State(state): State<Arc<AppState>>,
Expand Down Expand Up @@ -74,14 +75,3 @@ pub async fn handler(
Err(e) => get_error(e),
}
}

pub async fn fetch_json_from_url(url: String) -> Result<serde_json::Value, String> {
let client = reqwest::Client::new();
match client.get(url).send().await {
Ok(response) => match response.json::<serde_json::Value>().await {
Ok(json) => Ok(json),
Err(e) => Err(format!("Failed to get JSON response: {}", e)),
},
Err(e) => Err(format!("Failed to send request: {}", e)),
}
}
Loading

0 comments on commit 9aafe0c

Please sign in to comment.