679f147c6a
CI / Build, lint & test (push) Failing after 1m19s
18 data-driven achievements (achievements.json) across 8 trigger categories: matches_played, matches_won, goals_scored, packs_opened, sbcs_completed, cards_owned, level, objectives_completed, drafts_completed. Rarities span common → epic. Coin rewards range from 500 (first_match) to 6000 (win_50). check_and_unlock() queries the relevant metric from existing tables, skips already-earned achievements via INSERT OR IGNORE, grants coin rewards, and fires a persistent notification per unlock. Trigger values are cached per call to avoid redundant DB round-trips for same-trigger achievements. Checks run automatically after every match result (all triggers), every pack open (packs_opened), and every successful SBC submission (sbcs_completed). GET /achievements returns all definitions annotated with unlocked/unlocked_at, plus earned and total counts. POST /matches/result response gains an achievements_unlocked array (empty when nothing new unlocked). AppState gains achievement_defs (Arc<Vec<AchievementDefinition>>) loaded from data/achievements/**/*.json at startup — same pattern as obj_defs. 5 new tests: list endpoint, first_match unlock, first_win unlock, no-dup guard, coin reward verification. Core now at 82 tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
56 lines
1.4 KiB
Rust
56 lines
1.4 KiB
Rust
use axum::{
|
|
extract::{Path, State},
|
|
Json,
|
|
};
|
|
use serde_json::{json, Value};
|
|
|
|
use crate::{
|
|
app::AppState,
|
|
error::{AppError, AppResult},
|
|
models::sbc::{SbcResult, SubmitSbcRequest},
|
|
services::{club as club_svc, profile as profile_svc, sbc as sbc_svc},
|
|
};
|
|
|
|
pub async fn get_sbcs(State(state): State<AppState>) -> AppResult<Json<Value>> {
|
|
Ok(Json(json!({ "sbcs": state.sbc_defs })))
|
|
}
|
|
|
|
pub async fn get_sbc(
|
|
State(state): State<AppState>,
|
|
Path(sbc_id): Path<String>,
|
|
) -> AppResult<Json<Value>> {
|
|
let sbc = state
|
|
.sbc_defs
|
|
.iter()
|
|
.find(|s| s.id == sbc_id)
|
|
.ok_or_else(|| AppError::NotFound(format!("SBC '{sbc_id}' not found")))?;
|
|
Ok(Json(json!({ "sbc": sbc })))
|
|
}
|
|
|
|
pub async fn post_sbc_submit(
|
|
State(state): State<AppState>,
|
|
Json(req): Json<SubmitSbcRequest>,
|
|
) -> AppResult<Json<SbcResult>> {
|
|
let profile = profile_svc::get_active_profile(&state.pool).await?;
|
|
let club = club_svc::get_club_by_profile(&state.pool, &profile.id).await?;
|
|
|
|
let result = sbc_svc::submit_sbc(
|
|
&state.pool,
|
|
&state.card_db,
|
|
&state.sbc_defs,
|
|
&state.obj_defs,
|
|
&profile.id,
|
|
&club.id,
|
|
&req,
|
|
)
|
|
.await?;
|
|
|
|
if result.passed {
|
|
let _ = crate::services::achievement::check_and_unlock(
|
|
&state.pool, &state.achievement_defs, &profile.id, &club.id,
|
|
).await;
|
|
}
|
|
|
|
Ok(Json(result))
|
|
}
|