Skip to content

Commit

Permalink
Merge branch 'master' into feat/expired-quests
Browse files Browse the repository at this point in the history
  • Loading branch information
Marchand-Nicolas committed Nov 1, 2023
2 parents 7336dbf + 26b7970 commit 1c4582d
Show file tree
Hide file tree
Showing 14 changed files with 320 additions and 46 deletions.
20 changes: 14 additions & 6 deletions src/endpoints/get_quests.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
use crate::models::QuestDocument;
use crate::{models::AppState, utils::get_error};
use axum::response::{IntoResponse, Json};
use axum::{extract::State, http::StatusCode};
use futures::stream::StreamExt;
use mongodb::bson::{doc, from_document};
use crate::{
models::{AppState, QuestDocument},
utils::get_error,
};
use axum::{
extract::State,
http::StatusCode,
response::{IntoResponse, Json},
};
use chrono::Utc;
use futures::StreamExt;
use futures::TryStreamExt;
use mongodb::bson;
use mongodb::bson::doc;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
Expand Down
85 changes: 48 additions & 37 deletions src/endpoints/get_trending_quests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ use axum::{
http::StatusCode,
response::{IntoResponse, Json},
};
use chrono::Utc;
use futures::StreamExt;
use futures::TryStreamExt;
use mongodb::bson;
use mongodb::bson::doc;
use mongodb::bson::from_document;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

Expand All @@ -20,46 +22,55 @@ pub struct NFTItem {
}

pub async fn handler(State(state): State<Arc<AppState>>) -> impl IntoResponse {
let pipeline = vec![
doc! {
"$match": {
"disabled": false,
"hidden": false,
"is_trending": true,
}
},
doc! {
"$addFields": {
"expired": {
"$cond": [
{
"$and": [
{ "$gte": ["$expiry", 0] },
{ "$lt": ["$expiry", "$$NOW"] },
]
},
true,
false
]
}
}
},
];
let collection = state.db.collection::<QuestDocument>("quests");
match collection.aggregate(pipeline, None).await {
Ok(mut cursor) => {
let mut quests: Vec<QuestDocument> = Vec::new();
while let Some(result) = cursor.next().await {
match result {
Ok(document) => {
if let Ok(quest) = from_document::<QuestDocument>(document) {
quests.push(quest);
}
let current_timestamp = bson::DateTime::from_millis(Utc::now().timestamp_millis());
let filter = doc! {
"$and": [
{
"$or": [
{
"expiry": {
"$exists": false
}
},
{
"expiry": {
"$gt": current_timestamp
}
_ => continue,
}
]
},
{
"disabled": false
},
{
"hidden": false
},
{
"is_trending": true
}
]
};
match collection.find(Some(filter), None).await {
Ok(cursor) => {
let quests: Vec<QuestDocument> = cursor
.map(|result| {
result.map(|mut quest: QuestDocument| {
if let Some(expiry) = &quest.expiry {
let timestamp = expiry.timestamp_millis().to_string();
quest.expiry_timestamp = Some(timestamp);
}
quest
})
})
.try_collect()
.await
.unwrap_or_else(|_| vec![]);
if quests.is_empty() {
get_error("No quests found".to_string())
} else {
(StatusCode::OK, Json(quests)).into_response()
}
(StatusCode::OK, Json(quests)).into_response()
}
Err(_) => get_error("Error querying quests".to_string()),
}
Expand Down
1 change: 1 addition & 0 deletions src/endpoints/quests/braavos/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod myswap;
pub mod starknetid;
101 changes: 101 additions & 0 deletions src/endpoints/quests/braavos/myswap/claimable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use crate::models::{AppState, CompletedTaskDocument, Reward, RewardResponse};
use crate::utils::{get_error, get_nft};
use axum::{
extract::{Query, State},
http::StatusCode,
response::IntoResponse,
Json,
};
use futures::StreamExt;
use mongodb::bson::doc;
use serde::Deserialize;
use starknet::{
core::types::FieldElement,
signers::{LocalWallet, SigningKey},
};
use std::sync::Arc;

const QUEST_ID: u32 = 101;
const TASK_IDS: &[u32] = &[75, 76, 77, 78];
const LAST_TASK: u32 = TASK_IDS[3];
const NFT_LEVEL: u32 = 26;

#[derive(Deserialize)]
pub struct ClaimableQuery {
addr: FieldElement,
}

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<ClaimableQuery>,
) -> impl IntoResponse {
let collection = state
.db
.collection::<CompletedTaskDocument>("completed_tasks");

let pipeline = vec![
doc! {
"$match": {
"address": &query.addr.to_string(),
"task_id": { "$in": TASK_IDS },
},
},
doc! {
"$lookup": {
"from": "tasks",
"localField": "task_id",
"foreignField": "id",
"as": "task",
},
},
doc! {
"$match": {
"task.quest_id": QUEST_ID,
},
},
doc! {
"$group": {
"_id": "$address",
"completed_tasks": { "$push": "$task_id" },
},
},
doc! {
"$match": {
"completed_tasks": { "$all": TASK_IDS },
},
},
];

let completed_tasks = collection.aggregate(pipeline, None).await;
match completed_tasks {
Ok(mut tasks_cursor) => {
if tasks_cursor.next().await.is_none() {
return get_error("User hasn't completed all tasks".into());
}

let signer = LocalWallet::from(SigningKey::from_secret_scalar(
state.conf.nft_contract.private_key,
));

let mut rewards = vec![];

let Ok((token_id, sig)) = get_nft(QUEST_ID, LAST_TASK, &query.addr, NFT_LEVEL, &signer).await else {
return get_error("Signature failed".into());
};

rewards.push(Reward {
task_id: LAST_TASK,
nft_contract: state.conf.nft_contract.address.clone(),
token_id: token_id.to_string(),
sig: (sig.r, sig.s),
});

if rewards.is_empty() {
get_error("No rewards found for this user".into())
} else {
(StatusCode::OK, Json(RewardResponse { rewards })).into_response()
}
}
Err(_) => get_error("Error querying rewards".into()),
}
}
5 changes: 5 additions & 0 deletions src/endpoints/quests/braavos/myswap/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod claimable;
pub mod verify_added_liquidity;
pub mod verify_has_domain;
pub mod verify_twitter_fw_braavos;
pub mod verify_twitter_fw_myswap;
53 changes: 53 additions & 0 deletions src/endpoints/quests/braavos/myswap/verify_added_liquidity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use std::sync::Arc;

use crate::{
models::{AppState, VerifyQuery},
utils::{get_error, CompletedTasksTrait},
};
use axum::{
extract::{Query, State},
http::StatusCode,
response::IntoResponse,
Json,
};
use serde_json::json;
use starknet::{
core::types::{BlockId, BlockTag, FieldElement, FunctionCall},
macros::selector,
providers::Provider,
};

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<VerifyQuery>,
) -> impl IntoResponse {
let task_id = 76;
let addr = &query.addr;

// check if user has provider liquidity
let call_result = state
.provider
.call(
FunctionCall {
contract_address: state.conf.quests.myswap.contract,
entry_point_selector: selector!("balance_of"),
calldata: vec![*addr],
},
BlockId::Tag(BlockTag::Latest),
)
.await;

match call_result {
Ok(result) => {
if result[0] == FieldElement::ZERO && result[1] == FieldElement::ZERO {
get_error("You haven't provided any liquidity on MySwap.".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)),
}
}
16 changes: 16 additions & 0 deletions src/endpoints/quests/braavos/myswap/verify_has_domain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use crate::{
common::verify_has_root_or_braavos_domain::verify_has_root_or_braavos_domain,
models::{AppState, VerifyQuery},
};
use axum::{
extract::{Query, State},
response::IntoResponse,
};
use std::sync::Arc;

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<VerifyQuery>,
) -> impl IntoResponse {
verify_has_root_or_braavos_domain(state, &query.addr, 75).await
}
24 changes: 24 additions & 0 deletions src/endpoints/quests/braavos/myswap/verify_twitter_fw_braavos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use std::sync::Arc;

use crate::{
models::{AppState, VerifyQuery},
utils::{get_error, CompletedTasksTrait},
};
use axum::{
extract::{Query, State},
http::StatusCode,
response::IntoResponse,
Json,
};
use serde_json::json;

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<VerifyQuery>,
) -> impl IntoResponse {
let task_id = 78;
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)),
}
}
24 changes: 24 additions & 0 deletions src/endpoints/quests/braavos/myswap/verify_twitter_fw_myswap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use std::sync::Arc;

use crate::{
models::{AppState, VerifyQuery},
utils::{get_error, CompletedTasksTrait},
};
use axum::{
extract::{Query, State},
http::StatusCode,
response::IntoResponse,
Json,
};
use serde_json::json;

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<VerifyQuery>,
) -> impl IntoResponse {
let task_id = 77;
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)),
}
}
3 changes: 2 additions & 1 deletion src/endpoints/quests/element/layerswap/verify_has_bridged.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub async fn handler(
"https://bridge-api.layerswap.io/api/explorer/{}",
to_hex(query.addr)
);

let client = reqwest::Client::new();
let response_result = client.get(url).send().await;
match response_result {
Expand All @@ -47,7 +48,7 @@ pub async fn handler(
return get_error(format!("Received error from Layerswap: {}", err.message));
}

// Check if there is data and if any entry has "completed" status
// Check if there is data and if any entry has "completed" status & was made in the last 3 months
if res
.data
.as_ref()
Expand Down
2 changes: 1 addition & 1 deletion src/endpoints/quests/myswap/verify_added_liquidity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub async fn handler(
match call_result {
Ok(result) => {
if result[0] == FieldElement::ZERO {
get_error("You didn't use defi pooling on MySwap.".to_string())
get_error("You haven't provided any liquidity on MySwap.".to_string())
} else {
match state.upsert_completed_task(query.addr, task_id).await {
Ok(_) => (StatusCode::OK, Json(json!({"res": true}))).into_response(),
Expand Down
Loading

0 comments on commit 1c4582d

Please sign in to comment.