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:
+42
-1
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
db::Pool,
|
||||
error::{AppError, AppResult},
|
||||
models::profile::Profile,
|
||||
models::profile::{coins_for_level, level_for_xp, pack_for_level, LevelUpEvent, Profile},
|
||||
};
|
||||
use chrono::Utc;
|
||||
|
||||
@@ -50,3 +50,44 @@ pub async fn add_xp(pool: &Pool, profile_id: &str, xp: i64) -> AppResult<()> {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add XP, check for level-ups, grant rewards, and return every level gained.
|
||||
/// Callers should use this instead of `add_xp` when level-up feedback matters.
|
||||
pub async fn add_xp_with_levelup(
|
||||
pool: &Pool,
|
||||
profile_id: &str,
|
||||
club_id: &str,
|
||||
xp_to_add: i64,
|
||||
) -> AppResult<Vec<LevelUpEvent>> {
|
||||
let profile = get_active_profile(pool).await?;
|
||||
let old_level = level_for_xp(profile.xp);
|
||||
let new_total_xp = profile.xp + xp_to_add;
|
||||
let new_level = level_for_xp(new_total_xp);
|
||||
|
||||
let now = Utc::now();
|
||||
sqlx::query("UPDATE profiles SET xp = ?, level = ?, updated_at = ? WHERE id = ?")
|
||||
.bind(new_total_xp)
|
||||
.bind(new_level)
|
||||
.bind(now)
|
||||
.bind(profile_id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
let mut events = Vec::new();
|
||||
for lvl in (old_level + 1)..=new_level {
|
||||
let coins = coins_for_level(lvl);
|
||||
let pack = pack_for_level(lvl).map(String::from);
|
||||
|
||||
if coins > 0 {
|
||||
crate::services::club::add_coins(pool, club_id, coins).await?;
|
||||
}
|
||||
if let Some(ref pack_id) = pack {
|
||||
crate::services::pack::grant_pack(pool, club_id, pack_id).await?;
|
||||
}
|
||||
|
||||
tracing::info!(profile_id, new_level = lvl, coins, "level up");
|
||||
events.push(LevelUpEvent { new_level: lvl, coins_granted: coins, pack_granted: pack });
|
||||
}
|
||||
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user