Skip to content

Commit

Permalink
Merge pull request #23 from starknet-id/feat/add_stats_endpoints
Browse files Browse the repository at this point in the history
feat: add stats, renewal & starkscan endpoints
  • Loading branch information
Th0rgal authored Sep 19, 2023
2 parents d739fe5 + a1e678e commit 07b5d3a
Show file tree
Hide file tree
Showing 17 changed files with 787 additions and 12 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ tokio = { version = "1.28.1", features = ["macros", "rt-multi-thread"] }
toml = "0.7.4"
tower-http = { version = "0.4.0", features = ["cors"] }
chrono = "0.4.24"
reqwest = "0.11.20"
17 changes: 12 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,21 @@ COPY Cargo.toml Cargo.lock config.toml ./
# Copy the source code
COPY src ./src

# Build the application in release mode
RUN cargo build --release
# Accept a build argument for the build mode
ARG BUILD_MODE=release

# Expose the port your application uses (replace 8083 with your app's port)
# Build the application based on the build mode
RUN if [ "$BUILD_MODE" = "debug" ]; then \
cargo build; \
else \
cargo build --release; \
fi

# Expose the port your application uses
EXPOSE 8080

# Set the unbuffered environment variable
ENV RUST_BACKTRACE "1"

# Run the binary
CMD ["./target/release/starknetid_server"]
# Run the binary conditionally based on the build mode
CMD if [ "$BUILD_MODE" = "debug" ]; then ./target/debug/starknetid_server; else ./target/release/starknetid_server; fi
4 changes: 4 additions & 0 deletions config.template.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ naming = "0xXXXXXXXXXXXX"
verifier = "0xXXXXXXXXXXXX"
old_verifier = "0xXXXXXXXXXXXX"
pop_verifier = "0xXXXXXXXXXXXX"

[starkscan]
api_url = "https://api-testnet..starkscan.co/api/v0/"
api_key="xxxxxx"
20 changes: 13 additions & 7 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,44 @@ use std::env;
use std::fs;

macro_rules! pub_struct {
($name:ident {$($field:ident: $t:ty,)*}) => {
#[derive(Clone, Deserialize)]
($($derive:path),*; $name:ident {$($field:ident: $t:ty),* $(,)?}) => {
#[derive($($derive),*)]
pub struct $name {
$(pub $field: $t),*
}
}
}

pub_struct!(Server { port: u16, });
pub_struct!(Clone, Deserialize; Server { port: u16 });

pub_struct!(Databases {
pub_struct!(Clone, Deserialize; Databases {
starknetid: Database,
sales: Database,
});

pub_struct!(Database {
pub_struct!(Clone, Deserialize; Database {
name: String,
connection_string: String,
});

pub_struct!(Contracts {
pub_struct!(Clone, Deserialize; Contracts {
starknetid: FieldElement,
naming: FieldElement,
verifier: FieldElement,
old_verifier: FieldElement,
pop_verifier: FieldElement,
});

pub_struct!(Config {
pub_struct!(Clone, Deserialize; Starkscan {
api_url: String,
api_key: String,
});

pub_struct!(Clone, Deserialize; Config {
server: Server,
databases: Databases,
contracts: Contracts,
starkscan: Starkscan,
});

pub fn load() -> Config {
Expand Down
3 changes: 3 additions & 0 deletions src/endpoints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ pub mod domain_to_data;
pub mod galxe;
pub mod id_to_data;
pub mod referral;
pub mod renewal;
pub mod starkscan;
pub mod stats;
pub mod uri;
67 changes: 67 additions & 0 deletions src/endpoints/renewal/get_renewal_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use crate::{
models::AppState,
utils::{get_error, to_hex},
};
use axum::{
extract::{Query, State},
http::{HeaderMap, HeaderValue, StatusCode},
response::{IntoResponse, Json},
};
use futures::StreamExt;
use mongodb::bson::doc;
use serde::{Deserialize, Serialize};
use starknet::core::types::FieldElement;
use std::sync::Arc;

#[derive(Serialize)]
pub struct StarknetIdData {
starknet_id: String,
}

#[derive(Deserialize)]
pub struct StarknetIdQuery {
addr: FieldElement,
domain: String,
}

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<StarknetIdQuery>,
) -> impl IntoResponse {
let renew_collection = state
.starknetid_db
.collection::<mongodb::bson::Document>("auto_renew_flows");

let documents = renew_collection
.find(
doc! {
"renewer_address": to_hex(&query.addr),
"domain": query.domain,
"_cursor.to": null,
},
None,
)
.await;

match documents {
Ok(mut cursor) => {
let mut headers = HeaderMap::new();
headers.insert("Cache-Control", HeaderValue::from_static("max-age=30"));

if let Some(result) = cursor.next().await {
match result {
Ok(res) => {
let mut res = res;
res.remove("_id");
res.remove("_cursor");
(StatusCode::OK, headers, Json(res)).into_response()
}
Err(e) => get_error(format!("Error while processing the document: {:?}", e)),
}
} else {
get_error("no results founds".to_string())
}
}
Err(_) => get_error("Error while fetching from database".to_string()),
}
}
1 change: 1 addition & 0 deletions src/endpoints/renewal/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod get_renewal_data;
84 changes: 84 additions & 0 deletions src/endpoints/starkscan/fetch_nfts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use crate::{
models::AppState,
utils::{get_error, to_hex},
};
use axum::{
extract::{Query, State},
http::StatusCode,
response::{IntoResponse, Json},
};
use mongodb::bson::doc;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use starknet::core::types::FieldElement;
use std::sync::Arc;

#[derive(Deserialize)]
pub struct FetchNftsQuery {
addr: FieldElement,
next_url: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct StarkscanApiResult {
data: Vec<StarkscanNftProps>,
next_url: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct StarkscanNftProps {
animation_url: Option<String>,
attributes: Option<Value>,
contract_address: String,
description: Option<String>,
external_url: Option<String>,
image_url: Option<String>,
image_medium_url: Option<String>,
image_small_url: Option<String>,
minted_at_transaction_hash: Option<String>,
minted_by_address: Option<String>,
token_id: String,
name: Option<String>,
nft_id: Option<String>,
token_uri: Option<String>,
minted_at_timestamp: i64,
}

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<FetchNftsQuery>,
) -> impl IntoResponse {
let url = if let Some(next_url) = &query.next_url {
next_url.clone()
} else {
format!(
"{}nfts?owner_address={}",
state.conf.starkscan.api_url,
to_hex(&query.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.text().await {
Ok(text) => match serde_json::from_str::<StarkscanApiResult>(&text) {
Ok(res) => (StatusCode::OK, Json(res)).into_response(),
Err(e) => get_error(format!(
"Failed to deserialize result from Starkscan API: {} for response: {}",
e, text
)),
},
Err(e) => get_error(format!(
"Failed to get JSON response while fetching user NFT data: {}",
e
)),
},
Err(e) => get_error(format!("Failed to fetch user NFTs from API: {}", e)),
}
}
1 change: 1 addition & 0 deletions src/endpoints/starkscan/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod fetch_nfts;
61 changes: 61 additions & 0 deletions src/endpoints/stats/count_addrs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate::{models::AppState, utils::get_error};
use axum::{
extract::{Query, State},
http::{HeaderMap, HeaderValue, StatusCode},
response::IntoResponse,
Json,
};
use futures::StreamExt;
use mongodb::bson::doc;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

#[derive(Serialize)]
pub struct CountAddrsData {
count: i32,
}

#[derive(Deserialize)]
pub struct CountAddrsQuery {
since: i64,
}

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<CountAddrsQuery>,
) -> impl IntoResponse {
let mut headers = HeaderMap::new();
headers.insert("Cache-Control", HeaderValue::from_static("max-age=60"));

let domain_collection = state
.starknetid_db
.collection::<mongodb::bson::Document>("domains");
let aggregate_cursor = domain_collection
.aggregate(
vec![
doc! { "$match": { "_cursor.to": null, "creation_date": { "$gte": query.since } }},
doc! { "$group": { "_id": "$legacy_address" }},
doc! { "$count": "total" },
],
None,
)
.await;

match aggregate_cursor {
Ok(mut cursor) => {
if let Some(result) = cursor.next().await {
match result {
Ok(doc_) => {
let count = doc_.get_i32("total").unwrap_or(0);
let response_data = CountAddrsData { count };
(StatusCode::OK, headers, Json(response_data)).into_response()
}
Err(e) => get_error(format!("Error while processing the document: {:?}", e)),
}
} else {
get_error("No documents found".to_string())
}
}
Err(e) => get_error(format!("Error while fetching from database: {:?}", e)),
}
}
Loading

0 comments on commit 07b5d3a

Please sign in to comment.