From a77e7b8ff34cb5c0bbd60f5febbe2962f7fee9b3 Mon Sep 17 00:00:00 2001 From: Iris Date: Fri, 27 Oct 2023 10:09:11 +0200 Subject: [PATCH 1/2] fix: add deployed timestamp in db, create endpoint & update seniority achievement endpoint --- src/common/has_deployed_time.rs | 57 ++++++++++++++++ src/common/mod.rs | 1 + .../achievements/verify_seniority.rs | 67 ++++++------------- src/endpoints/get_deployed_time.rs | 29 ++++++++ src/endpoints/mod.rs | 1 + src/main.rs | 4 ++ src/models.rs | 5 ++ src/utils.rs | 29 ++++++++ 8 files changed, 148 insertions(+), 45 deletions(-) create mode 100644 src/common/has_deployed_time.rs create mode 100644 src/endpoints/get_deployed_time.rs diff --git a/src/common/has_deployed_time.rs b/src/common/has_deployed_time.rs new file mode 100644 index 00000000..2d5a57d4 --- /dev/null +++ b/src/common/has_deployed_time.rs @@ -0,0 +1,57 @@ +use std::sync::Arc; + +use crate::utils::DeployedTimesTrait; +use crate::{ + models::{AppState, DeployedTime}, + utils::to_hex, +}; +use mongodb::{bson::doc, Collection}; +use starknet::core::types::FieldElement; + +pub async fn execute_has_deployed_time( + state: Arc, + addr: &FieldElement, +) -> Result { + // Check if we have already a result in the db + let deployed_times_collection: Collection = state.db.collection("deployed_times"); + let filter = doc! { "addr": addr.to_string() }; + if let Ok(Some(document)) = deployed_times_collection.find_one(filter, None).await { + println!("Found deployed time in db: {:?}", document); + return Ok(document.timestamp); + } + + // If not we fetch it from the API and store it in the db + let url = format!( + "https://api.starkscan.co/api/v0/transactions?from_block=1&limit=1&contract_address={}&order_by=asc", + to_hex(*addr) + ); + let client = reqwest::Client::new(); + match client + .get(&url) + .header("accept", "application/json") + .header("x-api-key", state.conf.starkscan.api_key.clone()) + .send() + .await + { + Ok(response) => match response.json::().await { + Ok(json) => { + if let Some(timestamp) = json["data"][0]["timestamp"].as_i64() { + match state + .upsert_deployed_timestamp(*addr, timestamp as u32) + .await + { + Ok(_) => Ok(timestamp as u32), + Err(e) => Err(format!("{}", e)), + } + } else { + Err("Wallet not deployed.".to_string()) + } + } + Err(e) => Err(format!( + "Failed to get JSON response while fetching user transaction data: {}", + e + )), + }, + Err(e) => Err(format!("Failed to fetch user transactions from API: {}", e)), + } +} diff --git a/src/common/mod.rs b/src/common/mod.rs index 120a70ab..2273e8d6 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,3 +1,4 @@ +pub mod has_deployed_time; pub mod verify_has_nft; pub mod verify_has_root_domain; pub mod verify_has_root_or_braavos_domain; diff --git a/src/endpoints/achievements/verify_seniority.rs b/src/endpoints/achievements/verify_seniority.rs index 07908722..89194275 100644 --- a/src/endpoints/achievements/verify_seniority.rs +++ b/src/endpoints/achievements/verify_seniority.rs @@ -1,8 +1,9 @@ use std::sync::Arc; use crate::{ + common::has_deployed_time::execute_has_deployed_time, models::{AppState, VerifyAchievementQuery}, - utils::{get_error, to_hex, AchievementsTrait}, + utils::{get_error, AchievementsTrait}, }; use axum::{ extract::{Query, State}, @@ -28,51 +29,27 @@ pub async fn handler( return get_error("Invalid achievement id".to_string()); } - let url = format!( - "https://api.starkscan.co/api/v0/transactions?from_block=1&limit=1&contract_address={}&order_by=asc", - to_hex(addr) - ); - let client = reqwest::Client::new(); - match client - .get(&url) - .header("accept", "application/json") - .header("x-api-key", state.conf.starkscan.api_key.clone()) - .send() - .await - { - Ok(response) => match response.json::().await { - Ok(json) => { - if let Some(timestamp) = json["data"][0]["timestamp"].as_i64() { - let dt = NaiveDateTime::from_timestamp_opt(timestamp, 0).unwrap(); - let current_time = Utc::now().naive_utc(); - let duration = current_time - dt; - let days_passed = duration.num_days(); - - if (achievement_id == 14 && days_passed >= 90) - || (achievement_id == 15 && days_passed >= 180) - || (achievement_id == 16 && days_passed >= 365) - { - match state - .upsert_completed_achievement(addr, achievement_id) - .await - { - Ok(_) => { - (StatusCode::OK, Json(json!({"achieved": true}))).into_response() - } - Err(e) => get_error(format!("{}", e)), - } - } else { - get_error("Your wallet is too recent".to_string()) - } - } else { - get_error("No value found for this address".to_string()) + match execute_has_deployed_time(state.clone(), &query.addr).await { + Ok(timestamp) => { + let dt = NaiveDateTime::from_timestamp_opt(timestamp as i64, 0).unwrap(); + let current_time = Utc::now().naive_utc(); + let duration = current_time - dt; + let days_passed = duration.num_days(); + if (achievement_id == 14 && days_passed >= 90) + || (achievement_id == 15 && days_passed >= 180) + || (achievement_id == 16 && days_passed >= 365) + { + match state + .upsert_completed_achievement(addr, achievement_id) + .await + { + Ok(_) => (StatusCode::OK, Json(json!({"achieved": true}))).into_response(), + Err(e) => get_error(format!("{}", e)), } + } else { + get_error("Your wallet is too recent".to_string()) } - Err(e) => get_error(format!( - "Failed to get JSON response from Starkscan api: {}", - e - )), - }, - Err(e) => get_error(format!("Failed to fetch Starkscan api: {}", e)), + } + Err(e) => get_error(e), } } diff --git a/src/endpoints/get_deployed_time.rs b/src/endpoints/get_deployed_time.rs new file mode 100644 index 00000000..7dd9624b --- /dev/null +++ b/src/endpoints/get_deployed_time.rs @@ -0,0 +1,29 @@ +use std::sync::Arc; + +use crate::models::AppState; +use crate::{common::has_deployed_time::execute_has_deployed_time, utils::get_error}; +use axum::{ + extract::{Query, State}, + http::StatusCode, + response::IntoResponse, + Json, +}; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use starknet::core::types::FieldElement; + +#[derive(Debug, Serialize, Deserialize)] + +pub struct GetDeployedTimeQuery { + addr: FieldElement, +} + +pub async fn handler( + State(state): State>, + Query(query): Query, +) -> impl IntoResponse { + match execute_has_deployed_time(state, &query.addr).await { + Ok(timestamp) => (StatusCode::OK, Json(json!({ "timestamp": timestamp }))).into_response(), + Err(e) => get_error(e), + } +} diff --git a/src/endpoints/mod.rs b/src/endpoints/mod.rs index 0e452d55..5a9389ea 100644 --- a/src/endpoints/mod.rs +++ b/src/endpoints/mod.rs @@ -1,5 +1,6 @@ pub mod achievements; pub mod get_completed_quests; +pub mod get_deployed_time; pub mod get_quest; pub mod get_quest_participants; pub mod get_quests; diff --git a/src/main.rs b/src/main.rs index d9a70fdb..07de8f15 100644 --- a/src/main.rs +++ b/src/main.rs @@ -70,6 +70,10 @@ async fn main() { get(endpoints::get_trending_quests::handler), ) .route("/get_tasks", get(endpoints::get_tasks::handler)) + .route( + "/get_deployed_time", + get(endpoints::get_deployed_time::handler), + ) .route( "/quests/verify_quiz", post(endpoints::quests::verify_quiz::handler), diff --git a/src/models.rs b/src/models.rs index 97513324..f9e3c0af 100644 --- a/src/models.rs +++ b/src/models.rs @@ -175,3 +175,8 @@ pub_struct!(Debug, Deserialize, Serialize; BuildingDocument { level: u32, img_url: String, }); + +pub_struct!(Deserialize, Debug; DeployedTime { + addr: String, + timestamp: u32, +}); diff --git a/src/utils.rs b/src/utils.rs index c2b4f057..e7cab5d3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -179,3 +179,32 @@ impl AchievementsTrait for AppState { result } } + +#[async_trait] +pub trait DeployedTimesTrait { + async fn upsert_deployed_timestamp( + &self, + addr: FieldElement, + timestamp: u32, + ) -> Result; +} + +#[async_trait] +impl DeployedTimesTrait for AppState { + async fn upsert_deployed_timestamp( + &self, + addr: FieldElement, + timestamp: u32, + ) -> Result { + let deployed_times_collection: Collection = + self.db.collection("deployed_times"); + let filter = doc! { "addr": addr.to_string() }; + let update = doc! { "$setOnInsert": { "addr": addr.to_string(), "timestamp": timestamp } }; + let options = UpdateOptions::builder().upsert(true).build(); + + let result = deployed_times_collection + .update_one(filter, update, options) + .await; + result + } +} From 6e5d171d409069489d20238fcafd4fac3a1334e9 Mon Sep 17 00:00:00 2001 From: Iris Date: Fri, 27 Oct 2023 10:54:57 +0200 Subject: [PATCH 2/2] fix: use to_hex for addresses --- src/common/has_deployed_time.rs | 3 +-- src/utils.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/common/has_deployed_time.rs b/src/common/has_deployed_time.rs index 2d5a57d4..2f4b4d04 100644 --- a/src/common/has_deployed_time.rs +++ b/src/common/has_deployed_time.rs @@ -14,9 +14,8 @@ pub async fn execute_has_deployed_time( ) -> Result { // Check if we have already a result in the db let deployed_times_collection: Collection = state.db.collection("deployed_times"); - let filter = doc! { "addr": addr.to_string() }; + let filter = doc! { "addr": to_hex(*addr) }; if let Ok(Some(document)) = deployed_times_collection.find_one(filter, None).await { - println!("Found deployed time in db: {:?}", document); return Ok(document.timestamp); } diff --git a/src/utils.rs b/src/utils.rs index e7cab5d3..0d2df5df 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -199,7 +199,7 @@ impl DeployedTimesTrait for AppState { let deployed_times_collection: Collection = self.db.collection("deployed_times"); let filter = doc! { "addr": addr.to_string() }; - let update = doc! { "$setOnInsert": { "addr": addr.to_string(), "timestamp": timestamp } }; + let update = doc! { "$setOnInsert": { "addr": to_hex(addr), "timestamp": timestamp } }; let options = UpdateOptions::builder().upsert(true).build(); let result = deployed_times_collection