Skip to content

Commit 24aba66

Browse files
Merge pull request #393 from SkyWarrior123/testnet
Fix: Add CSV export for quest users
2 parents 8610c25 + 1797cd6 commit 24aba66

3 files changed

Lines changed: 174 additions & 0 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ ctor = "0.2.6"
3333
axum-client-ip = "0.4.0"
3434
jsonwebtoken = "9"
3535
tower = "0.4.13"
36+
csv = "1.3.1"
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
use crate::middleware::auth::auth_middleware;
2+
use crate::models::{AppState, CompletedTaskDocument, QuestDocument, QuestTaskDocument};
3+
use crate::utils::{get_error, to_hex, verify_quest_auth};
4+
use axum::{
5+
extract::{Extension, Query, State},
6+
http::{header, HeaderMap, HeaderValue, StatusCode},
7+
response::IntoResponse,
8+
};
9+
use axum_auto_routes::route;
10+
use csv::WriterBuilder;
11+
use futures::TryStreamExt;
12+
use mongodb::bson::doc;
13+
use serde::Deserialize;
14+
use starknet::core::types::FieldElement;
15+
use std::collections::HashSet;
16+
use std::sync::Arc;
17+
18+
pub_struct!(Deserialize; GetQuestUsersParams {
19+
quest_id: i64,
20+
});
21+
22+
#[route(get, "/admin/quests/get_quest_users_csv", auth_middleware)]
23+
pub async fn get_quest_users_csv_handler(
24+
State(state): State<Arc<AppState>>,
25+
Extension(_sub): Extension<String>,
26+
Query(params): Query<GetQuestUsersParams>,
27+
) -> impl IntoResponse {
28+
let tasks_collection = state.db.collection::<QuestTaskDocument>("tasks");
29+
let completed_tasks_collection = state
30+
.db
31+
.collection::<CompletedTaskDocument>("completed_tasks");
32+
let quests_collection = state.db.collection::<QuestDocument>("quests");
33+
34+
// Verify quest authorization
35+
let res = verify_quest_auth(_sub, &quests_collection, &params.quest_id).await;
36+
if !res {
37+
return get_error("User is not authorized to view this quest's users.".to_string());
38+
}
39+
40+
// Fetch all task IDs for the given quest_id
41+
let task_filter = doc! { "quest_id": params.quest_id };
42+
let task_cursor = match tasks_collection.find(task_filter, None).await {
43+
Ok(cursor) => cursor,
44+
Err(e) => return get_error(format!("Error fetching tasks: {}", e)),
45+
};
46+
47+
let task_ids_result: Result<Vec<u32>, _> =
48+
task_cursor.map_ok(|doc| doc.id as u32).try_collect().await;
49+
50+
let task_ids = match task_ids_result {
51+
Ok(ids) => ids,
52+
Err(e) => return get_error(format!("Error processing tasks: {}", e)),
53+
};
54+
55+
if task_ids.is_empty() {
56+
return get_error(format!("No tasks found for quest_id {}", params.quest_id));
57+
}
58+
59+
// Fetch all completed tasks for these task IDs
60+
let completed_task_filter = doc! { "task_id": { "$in": &task_ids } };
61+
let completed_task_cursor = match completed_tasks_collection
62+
.find(completed_task_filter, None)
63+
.await
64+
{
65+
Ok(cursor) => cursor,
66+
Err(e) => return get_error(format!("Error fetching completed tasks: {}", e)),
67+
};
68+
69+
let completed_tasks_result: Result<Vec<CompletedTaskDocument>, _> =
70+
completed_task_cursor.try_collect().await;
71+
72+
let users: Vec<String> = match completed_tasks_result {
73+
Ok(completed_tasks) => {
74+
let user_set: HashSet<String> = completed_tasks
75+
.into_iter()
76+
.map(|task| task.address().to_string()) // address() returns FieldElement, .to_string() gives decimal
77+
.collect();
78+
79+
user_set
80+
.into_iter()
81+
.filter_map(|addr_dec_str| {
82+
FieldElement::from_dec_str(&addr_dec_str).ok().map(to_hex)
83+
})
84+
.collect()
85+
}
86+
Err(e) => return get_error(format!("Error processing completed tasks: {}", e)),
87+
};
88+
89+
// Convert data to CSV
90+
let mut wtr = WriterBuilder::new().has_headers(true).from_writer(vec![]);
91+
92+
if let Err(e) = wtr.write_record(&["user_address"]) {
93+
eprintln!("Error writing CSV header: {}", e);
94+
return (
95+
StatusCode::INTERNAL_SERVER_ERROR,
96+
[(
97+
header::CONTENT_TYPE,
98+
HeaderValue::from_static("text/plain; charset=utf-8"),
99+
)],
100+
"Error generating CSV data".to_string(),
101+
)
102+
.into_response();
103+
}
104+
105+
for user_address in users {
106+
if let Err(e) = wtr.write_record(&[&user_address]) {
107+
eprintln!("Error writing CSV record: {}", e);
108+
return (
109+
StatusCode::INTERNAL_SERVER_ERROR,
110+
[(
111+
header::CONTENT_TYPE,
112+
HeaderValue::from_static("text/plain; charset=utf-8"),
113+
)],
114+
"Error generating CSV data".to_string(),
115+
)
116+
.into_response();
117+
}
118+
}
119+
120+
let csv_data = match wtr.into_inner() {
121+
Ok(data) => data,
122+
Err(e) => {
123+
eprintln!("Error finalizing CSV data: {}", e);
124+
return (
125+
StatusCode::INTERNAL_SERVER_ERROR,
126+
[(
127+
header::CONTENT_TYPE,
128+
HeaderValue::from_static("text/plain; charset=utf-8"),
129+
)],
130+
"Error generating CSV data".to_string(),
131+
)
132+
.into_response();
133+
}
134+
};
135+
136+
let csv_string = match String::from_utf8(csv_data) {
137+
Ok(s) => s,
138+
Err(e) => {
139+
eprintln!("Error converting CSV data to UTF-8 string: {}", e);
140+
return (
141+
StatusCode::INTERNAL_SERVER_ERROR,
142+
[(
143+
header::CONTENT_TYPE,
144+
HeaderValue::from_static("text/plain; charset=utf-8"),
145+
)],
146+
"Error generating CSV data".to_string(),
147+
)
148+
.into_response();
149+
}
150+
};
151+
152+
let mut headers = HeaderMap::new();
153+
headers.insert(
154+
header::CONTENT_TYPE,
155+
HeaderValue::from_static("text/csv; charset=utf-8"),
156+
);
157+
let filename = format!("quest_users_{}.csv", params.quest_id);
158+
match HeaderValue::from_str(&format!("attachment; filename=\"{}\"", filename)) {
159+
Ok(val) => {
160+
headers.insert(header::CONTENT_DISPOSITION, val);
161+
}
162+
Err(e) => {
163+
eprintln!("Error creating Content-Disposition header value: {}", e);
164+
headers.insert(
165+
header::CONTENT_DISPOSITION,
166+
HeaderValue::from_static("attachment; filename=\"quest_users.csv\""),
167+
);
168+
}
169+
}
170+
171+
(StatusCode::OK, headers, csv_string).into_response()
172+
}

src/endpoints/admin/quest/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ pub mod get_quest_users;
55
pub mod get_quests;
66
pub mod get_tasks;
77
pub mod update_quest;
8+
pub mod get_quest_users_csv; // added csv

0 commit comments

Comments
 (0)