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:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 23:36:19 +01:00
1 parent 193b199875
commit 9931ca9b54
37 files changed
+1833 -4

No files matched your search

+38
View File
@@ -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()))