Statistics with a chart at the end of a game, 64 achievements kept locally and on the server
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
193b199875
commit
9931ca9b54
37 files changed
+1833
-4
No files matched your search
@@ -562,6 +562,43 @@ async fn list_campaigns(State(st): State<AppState>, headers: HeaderMap) -> Respo
|
||||
Json(campaign_infos(&st.campaign_dir(&u.name)).await).into_response()
|
||||
}
|
||||
|
||||
// the player's profile: lifetime statistics and achievements (an opaque JSON object; the client
|
||||
// merges it with what it kept locally)
|
||||
const MAX_PROFILE: usize = 64 * 1024;
|
||||
|
||||
async fn get_profile(State(st): State<AppState>, headers: HeaderMap) -> Response {
|
||||
let u = match st.auth(&headers).await {
|
||||
Ok(u) => u,
|
||||
Err(r) => return r,
|
||||
};
|
||||
match tokio::fs::read(st.user_dir(&u.name).join("profile.json")).await {
|
||||
Ok(b) => ([("content-type", "application/json")], b).into_response(),
|
||||
Err(_) => ([("content-type", "application/json")], b"{}".to_vec()).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn put_profile(State(st): State<AppState>, headers: HeaderMap, body: Bytes) -> Response {
|
||||
let u = match st.auth(&headers).await {
|
||||
Ok(u) => u,
|
||||
Err(r) => return r,
|
||||
};
|
||||
if body.len() > MAX_PROFILE {
|
||||
return err(StatusCode::PAYLOAD_TOO_LARGE, "profile too large");
|
||||
}
|
||||
if limited(&st.writes, WRITES_PER_MINUTE).await {
|
||||
return err(StatusCode::TOO_MANY_REQUESTS, "too many requests");
|
||||
}
|
||||
if !serde_json::from_slice::<serde_json::Value>(&body).map_or(false, |v| v.is_object()) {
|
||||
return err(StatusCode::BAD_REQUEST, "not a JSON object");
|
||||
}
|
||||
let dir = st.user_dir(&u.name);
|
||||
let _ = tokio::fs::create_dir_all(&dir).await;
|
||||
match tokio::fs::write(dir.join("profile.json"), &body).await {
|
||||
Ok(_) => StatusCode::NO_CONTENT.into_response(),
|
||||
Err(_) => err(StatusCode::INTERNAL_SERVER_ERROR, "write failed"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_campaign(State(st): State<AppState>, headers: HeaderMap, Path(id): Path<String>) -> Response {
|
||||
let u = match st.auth(&headers).await {
|
||||
Ok(u) => u,
|
||||
@@ -754,6 +791,7 @@ fn router(state: AppState) -> Router {
|
||||
.route("/api/users", get(list_users).post(create_user))
|
||||
.route("/api/users/{name}", delete(delete_user))
|
||||
.route("/api/netplay/ws", get(netplay::netplay_ws))
|
||||
.route("/api/profile", get(get_profile).put(put_profile))
|
||||
.route("/api/saves", get(list).post(create))
|
||||
.route("/api/saves/{id}", get(get_save).put(overwrite).delete(delete_save))
|
||||
.route("/api/campaigns", get(list_campaigns).post(create_campaign).layer(campaign_limit()))
|
||||
|
||||
Reference in new issue
Block a user