Phase 17: profile level-up system
CI / Build, lint & test (push) Failing after 57s

XP thresholds (500→1200→2000→…→11000→+2500/level) drive automatic level
increases. add_xp_with_levelup() replaces bare add_xp() in match
processing: for each level gained it grants level×500 coins and milestone
packs (bronze@5, silver@10, gold@15, rare_gold@20, gold every 5 after).

GET /profile now returns computed level (recalculated from XP so it
stays consistent), xp_to_next_level, and xp_for_next_level so the
dashboard can render a progress bar without a second call.

POST /matches/result response gains level_ups array (empty when no
level-up occurred) with new_level, coins_granted, pack_granted per event.

Four new tests: profile level fields, level_for_xp boundary checks,
level-up event in match result, milestone pack unit test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-25 17:41:13 -07:00
parent 3d50da3589
commit d97695d414
6 changed files with 257 additions and 6 deletions
+28 -3
View File
@@ -1,9 +1,34 @@
use crate::{
app::AppState, error::AppResult, models::profile::Profile, services::profile as profile_svc,
app::AppState,
error::AppResult,
models::profile::{level_for_xp, XP_THRESHOLDS},
services::profile as profile_svc,
};
use axum::{extract::State, Json};
use serde_json::{json, Value};
pub async fn get_profile(State(state): State<AppState>) -> AppResult<Json<Profile>> {
pub async fn get_profile(State(state): State<AppState>) -> AppResult<Json<Value>> {
let profile = profile_svc::get_active_profile(&state.pool).await?;
Ok(Json(profile))
let computed_level = level_for_xp(profile.xp);
let next_level = computed_level + 1;
let xp_for_next = if next_level as usize <= XP_THRESHOLDS.len() {
XP_THRESHOLDS[next_level as usize - 1]
} else {
let overflow_base = XP_THRESHOLDS.last().copied().unwrap_or(0);
let levels_past = next_level - XP_THRESHOLDS.len() as i64;
overflow_base + levels_past * 2500
};
let xp_to_next = (xp_for_next - profile.xp).max(0);
Ok(Json(json!({
"id": profile.id,
"username": profile.username,
"level": computed_level,
"xp": profile.xp,
"xp_to_next_level": xp_to_next,
"xp_for_next_level": xp_for_next,
"created_at": profile.created_at,
"updated_at": profile.updated_at,
})))
}