style(core): cargo fmt match/club agent additions
CI / Build, lint & test (push) Successful in 4m1s

This commit is contained in:
funman300
2026-08-20 17:59:02 +00:00
parent 5f9f556af8
commit 2fb835200f
4 changed files with 108 additions and 83 deletions
+60 -59
View File
@@ -5,10 +5,10 @@ use crate::{
services::{club as club_svc, notification},
};
use anyhow::Context;
use std::path::Path;
use uuid::Uuid;
use sqlx::{Sqlite, Transaction};
use std::collections::{HashMap, HashSet};
use std::path::Path;
use uuid::Uuid;
pub fn load_achievement_definitions(data_dir: &str) -> anyhow::Result<Vec<AchievementDefinition>> {
let dir = Path::new(data_dir).join("achievements");
@@ -196,67 +196,68 @@ async fn metric_value_tx(
club_id: &str,
trigger: &str,
) -> AppResult<i64> {
let v: i64 = match trigger {
"matches_played" => {
sqlx::query_scalar("SELECT matches_played FROM statistics WHERE profile_id = ?")
let v: i64 =
match trigger {
"matches_played" => {
sqlx::query_scalar("SELECT matches_played FROM statistics WHERE profile_id = ?")
.bind(profile_id)
.fetch_optional(&mut **tx)
.await?
.unwrap_or(0)
}
"matches_won" => {
sqlx::query_scalar("SELECT matches_won FROM statistics WHERE profile_id = ?")
.bind(profile_id)
.fetch_optional(&mut **tx)
.await?
.unwrap_or(0)
}
"goals_scored" => {
sqlx::query_scalar("SELECT goals_scored FROM statistics WHERE profile_id = ?")
.bind(profile_id)
.fetch_optional(&mut **tx)
.await?
.unwrap_or(0)
}
"packs_opened" => {
sqlx::query_scalar("SELECT packs_opened FROM statistics WHERE profile_id = ?")
.bind(profile_id)
.fetch_optional(&mut **tx)
.await?
.unwrap_or(0)
}
"sbcs_completed" => {
sqlx::query_scalar("SELECT sbcs_completed FROM statistics WHERE profile_id = ?")
.bind(profile_id)
.fetch_optional(&mut **tx)
.await?
.unwrap_or(0)
}
"cards_owned" => {
sqlx::query_scalar("SELECT COUNT(*) FROM owned_cards WHERE club_id = ?")
.bind(club_id)
.fetch_one(&mut **tx)
.await?
}
"level" => sqlx::query_scalar("SELECT level FROM profiles WHERE id = ?")
.bind(profile_id)
.fetch_optional(&mut **tx)
.await?
.unwrap_or(0)
}
"matches_won" => {
sqlx::query_scalar("SELECT matches_won FROM statistics WHERE profile_id = ?")
.bind(profile_id)
.fetch_optional(&mut **tx)
.await?
.unwrap_or(0)
}
"goals_scored" => {
sqlx::query_scalar("SELECT goals_scored FROM statistics WHERE profile_id = ?")
.bind(profile_id)
.fetch_optional(&mut **tx)
.await?
.unwrap_or(0)
}
"packs_opened" => {
sqlx::query_scalar("SELECT packs_opened FROM statistics WHERE profile_id = ?")
.bind(profile_id)
.fetch_optional(&mut **tx)
.await?
.unwrap_or(0)
}
"sbcs_completed" => {
sqlx::query_scalar("SELECT sbcs_completed FROM statistics WHERE profile_id = ?")
.bind(profile_id)
.fetch_optional(&mut **tx)
.await?
.unwrap_or(0)
}
"cards_owned" => {
sqlx::query_scalar("SELECT COUNT(*) FROM owned_cards WHERE club_id = ?")
.bind(club_id)
.fetch_one(&mut **tx)
.await?
}
"level" => sqlx::query_scalar("SELECT level FROM profiles WHERE id = ?")
.unwrap_or(1),
"objectives_completed" => sqlx::query_scalar(
"SELECT COUNT(*) FROM objective_progress WHERE profile_id = ? AND completed = 1",
)
.bind(profile_id)
.fetch_optional(&mut **tx)
.await?
.unwrap_or(1),
"objectives_completed" => sqlx::query_scalar(
"SELECT COUNT(*) FROM objective_progress WHERE profile_id = ? AND completed = 1",
)
.bind(profile_id)
.fetch_one(&mut **tx)
.await?,
"drafts_completed" => sqlx::query_scalar(
"SELECT COUNT(*) FROM draft_sessions WHERE profile_id = ? AND status = 'completed'",
)
.bind(profile_id)
.fetch_one(&mut **tx)
.await?,
_ => 0,
};
.fetch_one(&mut **tx)
.await?,
"drafts_completed" => sqlx::query_scalar(
"SELECT COUNT(*) FROM draft_sessions WHERE profile_id = ? AND status = 'completed'",
)
.bind(profile_id)
.fetch_one(&mut **tx)
.await?,
_ => 0,
};
Ok(v)
}
+9 -13
View File
@@ -140,14 +140,12 @@ const OWNED_SELECT: &str = "SELECT id, club_id, card_id, is_loan, loan_matches_r
/// The club's most-recently-updated squad id (its "active" squad), matching the
/// selection `squad::get_squad` uses, or `None` when the club has no squad yet.
pub async fn active_squad_id(pool: &Pool, club_id: &str) -> AppResult<Option<String>> {
Ok(
sqlx::query_scalar::<_, String>(
"SELECT id FROM squads WHERE club_id = ? ORDER BY updated_at DESC LIMIT 1",
)
.bind(club_id)
.fetch_optional(pool)
.await?,
Ok(sqlx::query_scalar::<_, String>(
"SELECT id FROM squads WHERE club_id = ? ORDER BY updated_at DESC LIMIT 1",
)
.bind(club_id)
.fetch_optional(pool)
.await?)
}
/// The owned card assigned as the manager of `club_id`'s active squad, if any.
@@ -182,11 +180,7 @@ pub async fn get_squad_manager_for_squad(
/// belong to `club_id`, so a client can neither manage another club's squad nor
/// assign a card it does not own. One manager per squad (the PK REPLACE), so a
/// re-assignment never accumulates duplicate rows.
pub async fn set_squad_manager(
pool: &Pool,
club_id: &str,
owned_card_id: &str,
) -> AppResult<()> {
pub async fn set_squad_manager(pool: &Pool, club_id: &str, owned_card_id: &str) -> AppResult<()> {
let squad_id = active_squad_id(pool, club_id)
.await?
.ok_or_else(|| AppError::NotFound("club has no squad to assign a manager to".into()))?;
@@ -304,7 +298,9 @@ mod tests {
let (dir, url, pool) = fixture().await;
// SAVE.
set_squad_manager(&pool, "club-a", "mgr").await.expect("assign");
set_squad_manager(&pool, "club-a", "mgr")
.await
.expect("assign");
// RELOAD (same pool).
let got = get_squad_manager(&pool, "club-a").await.unwrap();
assert_eq!(got.as_ref().map(|c| c.id.as_str()), Some("mgr"));
+38 -10
View File
@@ -438,7 +438,9 @@ async fn complete_match_inner(
));
}
if req.match_identity.trim().is_empty() {
return Err(AppError::BadRequest("match_identity must not be empty".into()));
return Err(AppError::BadRequest(
"match_identity must not be empty".into(),
));
}
let result = req.result;
@@ -905,7 +907,11 @@ mod match_completion_tests {
assert_eq!(r.xp_awarded, XP_LOSS);
assert_eq!(r.match_record.outcome, "dnf");
assert_eq!(stat(&fx.pool, "matches_dnf").await, 1);
assert_eq!(stat(&fx.pool, "matches_lost").await, 0, "DNF is not a loss row");
assert_eq!(
stat(&fx.pool, "matches_lost").await,
0,
"DNF is not a loss row"
);
assert_eq!(stat(&fx.pool, "matches_played").await, 1);
assert_eq!(coins(&fx.pool).await, START_COINS + COINS_LOSS);
}
@@ -922,7 +928,11 @@ mod match_completion_tests {
assert_eq!(r.match_record.outcome, "no_contest");
assert_eq!(coins(&fx.pool).await, START_COINS, "no coins for a void");
assert_eq!(xp(&fx.pool).await, 0);
assert_eq!(stat(&fx.pool, "matches_played").await, 0, "not counted as played");
assert_eq!(
stat(&fx.pool, "matches_played").await,
0,
"not counted as played"
);
// Still recorded for history + idempotency.
assert_eq!(count(&fx.pool, "matches").await, 1);
assert_eq!(count(&fx.pool, "match_completions").await, 1);
@@ -1089,7 +1099,11 @@ mod match_completion_tests {
.await
.unwrap();
assert!(retry.applied, "{fp:?} retry-after-fault must apply");
assert_eq!(coins(&fx.pool).await, START_COINS + COINS_WIN, "{fp:?} retry");
assert_eq!(
coins(&fx.pool).await,
START_COINS + COINS_WIN,
"{fp:?} retry"
);
}
}
@@ -1129,9 +1143,16 @@ mod match_completion_tests {
];
let achs = vec![ach("first_win", "matches_won", 1, 50)];
let first = complete_match(&fx.pool, PROFILE, CLUB, &req("m", MatchResultKind::Win, 2, 0), &objs, &achs)
.await
.unwrap();
let first = complete_match(
&fx.pool,
PROFILE,
CLUB,
&req("m", MatchResultKind::Win, 2, 0),
&objs,
&achs,
)
.await
.unwrap();
assert!(first.applied);
assert_eq!(first.objectives_updated.len(), 2);
assert_eq!(first.achievements_unlocked.len(), 1);
@@ -1140,9 +1161,16 @@ mod match_completion_tests {
assert_eq!(count(&fx.pool, "player_achievements").await, 1);
// Replay: no double objectives/achievements/coins.
let replay = complete_match(&fx.pool, PROFILE, CLUB, &req("m", MatchResultKind::Win, 2, 0), &objs, &achs)
.await
.unwrap();
let replay = complete_match(
&fx.pool,
PROFILE,
CLUB,
&req("m", MatchResultKind::Win, 2, 0),
&objs,
&achs,
)
.await
.unwrap();
assert!(!replay.applied);
assert!(replay.objectives_updated.is_empty());
assert!(replay.achievements_unlocked.is_empty());
+1 -1
View File
@@ -6,9 +6,9 @@ use crate::{
},
};
use anyhow::Context;
use sqlx::{Sqlite, Transaction};
use std::path::Path;
use uuid::Uuid;
use sqlx::{Sqlite, Transaction};
pub fn load_objective_definitions(data_dir: &str) -> anyhow::Result<Vec<ObjectiveDefinition>> {
let dir = Path::new(data_dir).join("objectives");