Skip to content

Commit

Permalink
Merge pull request #69 from starknet-id/feat/implement_achievements
Browse files Browse the repository at this point in the history
feat: achievements features
  • Loading branch information
Th0rgal authored Aug 18, 2023
2 parents 2f134cd + fc031c9 commit 6aa3a75
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 4 deletions.
112 changes: 112 additions & 0 deletions src/endpoints/achievements/fetch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use std::sync::Arc;

use crate::{
models::{AchievementCategoryDocument, AchievementQuery, AppState, UserAchievements},
utils::get_error,
};
use axum::{
extract::{Query, State},
http::StatusCode,
response::IntoResponse,
Json,
};
use futures::stream::StreamExt;
use mongodb::bson::{doc, from_document};
use starknet::core::types::FieldElement;

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<AchievementQuery>,
) -> impl IntoResponse {
let addr = FieldElement::to_string(&query.addr);
let achievement_categories = state
.db
.collection::<AchievementCategoryDocument>("achievement_categories");
let pipeline = vec![
doc! {
"$lookup": {
"from": "achievements",
"localField": "id",
"foreignField": "category_id",
"as": "achievement"
}
},
doc! {"$unwind": "$achievement" },
doc! {
"$lookup": {
"from": "achieved",
"let": { "achievement_id": "$achievement.id" },
"pipeline": [
{ "$match": {
"$expr": {
"$and": [
{ "$eq": ["$achievement_id", "$$achievement_id"] },
{ "$eq": ["$addr", addr] }
]
}
} }
],
"as": "achieved"
}
},
doc! {
"$project": {
"_id": 0,
"category_name": "$name",
"category_desc": "$desc",
"achievements": {
"name": "$achievement.name",
"short_desc": "$achievement.short_desc",
"title": {
"$cond": [
{ "$eq": [{ "$size": "$achieved" }, 0] },
"$achievement.todo_title",
"$achievement.done_title"
]
},
"desc": {
"$cond": [
{ "$eq": [{ "$size": "$achieved" }, 0] },
"$achievement.todo_desc",
"$achievement.done_desc"
]
},
"completed": { "$ne": [{ "$size": "$achieved" }, 0] },
"verify_type": "$achievement.verify_type"
}
}
},
doc! {
"$group": {
"_id": { "category_name": "$category_name", "category_desc": "$category_desc" },
"achievements": { "$push": "$achievements" }
}
},
doc! {
"$project": {
"category_name": "$_id.category_name",
"category_desc": "$_id.category_desc",
"achievements": 1,
"_id": 0
}
},
];

match achievement_categories.aggregate(pipeline, None).await {
Ok(mut cursor) => {
let mut achievements: Vec<UserAchievements> = Vec::new();
while let Some(result) = cursor.next().await {
match result {
Ok(document) => {
if let Ok(achievement) = from_document::<UserAchievements>(document) {
achievements.push(achievement);
}
}
_ => continue,
}
}
(StatusCode::OK, Json(achievements)).into_response()
}
Err(e) => get_error(format!("Error fetching user achievements: {}", e)),
}
}
2 changes: 2 additions & 0 deletions src/endpoints/achievements/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod fetch;
pub mod verify_default;
49 changes: 49 additions & 0 deletions src/endpoints/achievements/verify_default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::sync::Arc;

use crate::{
models::{AchievedDocument, AppState, VerifyAchievementQuery},
utils::{get_error, AchievementsTrait},
};
use axum::{
extract::{Query, State},
http::StatusCode,
response::IntoResponse,
Json,
};
use mongodb::bson::doc;
use serde_json::json;
use starknet::core::types::FieldElement;

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<VerifyAchievementQuery>,
) -> impl IntoResponse {
let addr = query.addr;
if addr == FieldElement::ZERO {
return get_error("Please connect your wallet first".to_string());
}
let achievement_id = query.id;
let achieved_collection = state.db.collection::<AchievedDocument>("achieved");
let filter = doc! {
"addr": FieldElement::to_string(&addr),
"achievement_id": achievement_id
};
match achieved_collection.find_one(filter, None).await {
Ok(Some(_)) => (StatusCode::OK, Json(json!({"achieved": true}))).into_response(),
Ok(None) => match state.get_achievement(achievement_id).await {
Ok(Some(achievement)) => {
// todo: add verifying logic here
match state
.upsert_completed_achievement(addr, achievement_id)
.await
{
Ok(_) => (StatusCode::OK, Json(json!({"achieved": true}))).into_response(),
Err(e) => get_error(format!("{}", e)),
}
}
Ok(None) => get_error("Achievement not found".to_string()),
Err(e) => get_error(format!("Error querying achievement : {}", e)),
},
Err(e) => get_error(format!("Error querying user achievement : {}", e)),
}
}
7 changes: 4 additions & 3 deletions src/endpoints/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod quests;
pub mod get_quiz;
pub mod achievements;
pub mod get_quest;
pub mod get_quests;
pub mod get_tasks;
pub mod get_quiz;
pub mod get_tasks;
pub mod quests;
8 changes: 8 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,14 @@ async fn main() {
"/quests/example/verify_quiz",
post(endpoints::quests::example::verify_quiz::handler),
)
.route(
"/achievements/verify_default",
get(endpoints::achievements::verify_default::handler),
)
.route(
"/achievements/fetch",
get(endpoints::achievements::fetch::handler),
)
.with_state(shared_state)
.layer(cors);

Expand Down
49 changes: 49 additions & 0 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,52 @@ pub_struct!(Deserialize; VerifyQuizQuery {
quiz_name: String,
user_answers_list: Vec<Vec<String>>,
});

pub_struct!(Deserialize; AchievementQuery {
addr: FieldElement,
});

pub_struct!(Deserialize; VerifyAchievementQuery {
addr: FieldElement,
id: u32,
});

pub_struct!(Debug, Serialize, Deserialize; AchievedDocument {
addr: String,
achievement_id: u32,
});

pub_struct!(Debug, Serialize, Deserialize; AchievementDocument {
id: u32,
category_id: u32,
name: String,
img_url: String,
short_desc: String,
todo_title: String,
todo_desc: String,
done_title: String,
done_desc: String,
verify_type: String,
verify_endpoint: String,
});

pub_struct!(Debug, Serialize, Deserialize; AchievementCategoryDocument {
id: u32,
name: String,
desc: String,
});

pub_struct!(Debug, Serialize, Deserialize; UserAchievements {
category_name: String,
category_desc: String,
achievements: Vec<UserAchievement>,
});

pub_struct!(Debug, Serialize, Deserialize; UserAchievement {
name: String,
short_desc: String,
title: String,
desc: String,
completed: bool,
verify_type: String,
});
52 changes: 51 additions & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::models::{AppState, CompletedTasks};
use crate::{
endpoints::achievements,
models::{AchievementDocument, AppState, CompletedTasks},
};
use async_trait::async_trait;
use axum::{
http::StatusCode,
Expand Down Expand Up @@ -90,3 +93,50 @@ pub fn to_hex(felt: FieldElement) -> String {
}
result
}

#[async_trait]
pub trait AchievementsTrait {
async fn upsert_completed_achievement(
&self,
addr: FieldElement,
achievement_id: u32,
) -> Result<UpdateResult, mongodb::error::Error>;

async fn get_achievement(
&self,
achievement_id: u32,
) -> Result<Option<AchievementDocument>, mongodb::error::Error>;
}

#[async_trait]
impl AchievementsTrait for AppState {
async fn upsert_completed_achievement(
&self,
addr: FieldElement,
achievement_id: u32,
) -> Result<UpdateResult, mongodb::error::Error> {
let achieved_collection: Collection<CompletedTasks> = self.db.collection("achieved");
let filter = doc! { "addr": addr.to_string(), "achievement_id": achievement_id };
let update =
doc! { "$setOnInsert": { "addr": addr.to_string(), "achievement_id": achievement_id } };
let options = UpdateOptions::builder().upsert(true).build();

let result = achieved_collection
.update_one(filter, update, options)
.await;
result
}

async fn get_achievement(
&self,
achievement_id: u32,
) -> Result<Option<AchievementDocument>, mongodb::error::Error> {
let achievements_collection: Collection<AchievementDocument> =
self.db.collection("achievements");
let query = doc! {
"id": achievement_id
};
let result = achievements_collection.find_one(query, None).await;
result
}
}

0 comments on commit 6aa3a75

Please sign in to comment.