style(core): apply cargo fmt across routes, services, models, tests
CI / Build, lint & test (push) Successful in 2m19s

Pure rustfmt reflow (import grouping, array/match-arm/call-arg wrapping,
alphabetized module decls, comment realignment). No semantic change:
full-diff and `git diff -w` both confirm logic byte-identical to 271c363;
workspace builds and all 74 core tests pass. Retained pre-existing WIP
brought forward after verification.
This commit is contained in:
funman300
2026-08-20 16:05:41 +00:00
parent 271c3639ed
commit a034e74c16
27 changed files with 436 additions and 273 deletions
+76 -69
View File
@@ -29,79 +29,83 @@ pub fn load_achievement_definitions(data_dir: &str) -> anyhow::Result<Vec<Achiev
}
/// Query the current value for the given trigger metric.
async fn metric_value(pool: &Pool, profile_id: &str, 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 = ?",
)
.bind(profile_id)
.fetch_optional(pool)
.await?
.unwrap_or(0),
async fn metric_value(
pool: &Pool,
profile_id: &str,
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 = ?")
.bind(profile_id)
.fetch_optional(pool)
.await?
.unwrap_or(0)
}
"matches_won" => sqlx::query_scalar(
"SELECT matches_won FROM statistics WHERE profile_id = ?",
)
.bind(profile_id)
.fetch_optional(pool)
.await?
.unwrap_or(0),
"matches_won" => {
sqlx::query_scalar("SELECT matches_won FROM statistics WHERE profile_id = ?")
.bind(profile_id)
.fetch_optional(pool)
.await?
.unwrap_or(0)
}
"goals_scored" => sqlx::query_scalar(
"SELECT goals_scored FROM statistics WHERE profile_id = ?",
)
.bind(profile_id)
.fetch_optional(pool)
.await?
.unwrap_or(0),
"goals_scored" => {
sqlx::query_scalar("SELECT goals_scored FROM statistics WHERE profile_id = ?")
.bind(profile_id)
.fetch_optional(pool)
.await?
.unwrap_or(0)
}
"packs_opened" => sqlx::query_scalar(
"SELECT packs_opened FROM statistics WHERE profile_id = ?",
)
.bind(profile_id)
.fetch_optional(pool)
.await?
.unwrap_or(0),
"packs_opened" => {
sqlx::query_scalar("SELECT packs_opened FROM statistics WHERE profile_id = ?")
.bind(profile_id)
.fetch_optional(pool)
.await?
.unwrap_or(0)
}
"sbcs_completed" => sqlx::query_scalar(
"SELECT sbcs_completed FROM statistics WHERE profile_id = ?",
)
.bind(profile_id)
.fetch_optional(pool)
.await?
.unwrap_or(0),
"sbcs_completed" => {
sqlx::query_scalar("SELECT sbcs_completed FROM statistics WHERE profile_id = ?")
.bind(profile_id)
.fetch_optional(pool)
.await?
.unwrap_or(0)
}
"cards_owned" => sqlx::query_scalar(
"SELECT COUNT(*) FROM owned_cards WHERE club_id = ?",
)
.bind(club_id)
.fetch_one(pool)
.await?,
"cards_owned" => {
sqlx::query_scalar("SELECT COUNT(*) FROM owned_cards WHERE club_id = ?")
.bind(club_id)
.fetch_one(pool)
.await?
}
"level" => sqlx::query_scalar(
"SELECT level FROM profiles WHERE id = ?",
)
.bind(profile_id)
.fetch_optional(pool)
.await?
.unwrap_or(1),
"level" => sqlx::query_scalar("SELECT level FROM profiles WHERE id = ?")
.bind(profile_id)
.fetch_optional(pool)
.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(pool)
.await?,
"objectives_completed" => sqlx::query_scalar(
"SELECT COUNT(*) FROM objective_progress WHERE profile_id = ? AND completed = 1",
)
.bind(profile_id)
.fetch_one(pool)
.await?,
"drafts_completed" => sqlx::query_scalar(
"SELECT COUNT(*) FROM draft_sessions WHERE profile_id = ? AND status = 'completed'",
)
.bind(profile_id)
.fetch_one(pool)
.await?,
"drafts_completed" => sqlx::query_scalar(
"SELECT COUNT(*) FROM draft_sessions WHERE profile_id = ? AND status = 'completed'",
)
.bind(profile_id)
.fetch_one(pool)
.await?,
_ => 0,
};
_ => 0,
};
Ok(v)
}
@@ -165,11 +169,14 @@ pub async fn check_and_unlock(
club_svc::add_coins(pool, club_id, def.reward_coins).await?;
}
let body = format!(
"{} Reward: {} coins.",
def.description, def.reward_coins
);
let _ = notification::create(pool, "achievement", &format!("Achievement: {}", def.title), &body).await;
let body = format!("{} Reward: {} coins.", def.description, def.reward_coins);
let _ = notification::create(
pool,
"achievement",
&format!("Achievement: {}", def.title),
&body,
)
.await;
newly_unlocked.push(def.clone());
}
+11 -8
View File
@@ -1,4 +1,8 @@
use crate::{db::Pool, error::AppResult, services::{club, pack}};
use crate::{
db::Pool,
error::AppResult,
services::{club, pack},
};
use uuid::Uuid;
const STREAK_COINS: [i64; 7] = [500, 650, 800, 1000, 1200, 1500, 2000];
@@ -8,7 +12,7 @@ const STREAK_7_PACK: &str = "silver_pack";
#[derive(Debug, serde::Serialize)]
pub struct CheckinStatus {
pub available: bool,
pub streak_day: i64, // current streak (17 cycle, 0 if never checked in)
pub streak_day: i64, // current streak (17 cycle, 0 if never checked in)
pub next_reward_coins: i64,
pub next_reward_pack: Option<&'static str>,
pub last_checked_in: Option<String>,
@@ -56,11 +60,7 @@ pub async fn get_status(pool: &Pool, profile_id: &str) -> AppResult<CheckinStatu
}
}
pub async fn claim(
pool: &Pool,
profile_id: &str,
club_id: &str,
) -> AppResult<CheckinResult> {
pub async fn claim(pool: &Pool, profile_id: &str, club_id: &str) -> AppResult<CheckinResult> {
let row: Option<(i64, String)> = sqlx::query_as(
"SELECT streak_day, checked_in_at FROM daily_checkins \
WHERE profile_id = ? ORDER BY checked_in_at DESC LIMIT 1",
@@ -82,7 +82,10 @@ pub async fn claim(
}
}
let last_streak = row.as_ref().map(|(s, last_at)| compute_next_streak(*s, last_at)).unwrap_or(1);
let last_streak = row
.as_ref()
.map(|(s, last_at)| compute_next_streak(*s, last_at))
.unwrap_or(1);
let idx = (last_streak - 1).rem_euclid(7) as usize;
let coins = STREAK_COINS[idx];
let pack_def = if idx == 6 { Some(STREAK_7_PACK) } else { None };
+21 -14
View File
@@ -184,24 +184,32 @@ pub async fn pick_card(
let (new_candidates, new_status, reward_coins, reward_pack_id, squad_rating, completed_at) =
if all_filled {
let (coins, pack, avg) = compute_reward(card_db, &picks);
(None, "completed".to_string(), coins, pack, avg, Some(chrono::Utc::now().to_rfc3339()))
(
None,
"completed".to_string(),
coins,
pack,
avg,
Some(chrono::Utc::now().to_rfc3339()),
)
} else {
let min_overall = difficulty_min_overall(&session.difficulty);
let next_pos = &pick_order[next_index];
let next_candidates =
pick_candidates(card_db, next_pos, min_overall, CANDIDATES_PER_SLOT);
{
let candidates_json = serde_json::to_string(&next_candidates)
.map_err(|e| AppError::Internal(anyhow::anyhow!("serialization failed: {e}")))?;
(
Some(candidates_json),
"active".to_string(),
0,
None,
0,
None,
)
}
let candidates_json = serde_json::to_string(&next_candidates).map_err(|e| {
AppError::Internal(anyhow::anyhow!("serialization failed: {e}"))
})?;
(
Some(candidates_json),
"active".to_string(),
0,
None,
0,
None,
)
}
};
let picks_json = serde_json::to_string(&picks)
@@ -294,8 +302,7 @@ async fn fetch_session(pool: &Pool, profile_id: &str, session_id: &str) -> AppRe
}
fn render_session(session: &DraftSession, card_db: &CardDb) -> serde_json::Value {
let pick_order: Vec<String> =
serde_json::from_str(&session.pick_order).unwrap_or_default();
let pick_order: Vec<String> = serde_json::from_str(&session.pick_order).unwrap_or_default();
let picks: Vec<String> = serde_json::from_str(&session.picks).unwrap_or_default();
let candidates: Vec<String> = session
.current_candidates
+5 -1
View File
@@ -26,7 +26,11 @@ pub async fn get_active_session(
.map_err(Into::into)
}
pub async fn get_session(pool: &Pool, session_id: &str, profile_id: &str) -> AppResult<FutChampsSession> {
pub async fn get_session(
pool: &Pool,
session_id: &str,
profile_id: &str,
) -> AppResult<FutChampsSession> {
sqlx::query_as::<_, FutChampsSession>(&format!(
"{SESSION_SELECT} WHERE id = ? AND profile_id = ?"
))
+10 -5
View File
@@ -149,7 +149,9 @@ pub async fn buy_listing(
.await?
.rows_affected();
if claimed == 0 {
return Err(AppError::NotFound("listing not found or already sold".into()));
return Err(AppError::NotFound(
"listing not found or already sold".into(),
));
}
if let Err(e) = club::spend_coins(pool, club_id, listing.price).await {
let _ = sqlx::query("UPDATE market_listings SET sold = 0 WHERE id = ?")
@@ -308,9 +310,10 @@ pub async fn get_listings_by_seller(
let with_cards = listings
.into_iter()
.filter_map(|l| {
card_db
.get(&l.card_id)
.map(|card| MarketListingWithCard { listing: l, card: card.clone() })
card_db.get(&l.card_id).map(|card| MarketListingWithCard {
listing: l,
card: card.clone(),
})
})
.collect();
Ok(with_cards)
@@ -326,7 +329,9 @@ pub async fn cancel_listing(pool: &Pool, club_id: &str, listing_id: &str) -> App
.bind(club_id)
.fetch_optional(pool)
.await?
.ok_or_else(|| AppError::NotFound(format!("listing '{listing_id}' not found or already sold")))?;
.ok_or_else(|| {
AppError::NotFound(format!("listing '{listing_id}' not found or already sold"))
})?;
sqlx::query("DELETE FROM market_listings WHERE id = ?")
.bind(listing_id)
+75 -21
View File
@@ -7,13 +7,14 @@ use crate::{
match_result::{Match, MatchRewardResult, SubmitMatchRequest},
objective::ObjectiveDefinition,
},
services::{achievement, card_db::CardDb, club, notification, objective, profile, season as season_svc, statistics},
services::{
achievement, card_db::CardDb, club, notification, objective, profile, season as season_svc,
statistics,
},
};
use rand::{seq::SliceRandom, Rng};
const FORMATIONS: &[&str] = &[
"4-3-3", "4-4-2", "4-2-3-1", "4-1-2-1-2", "3-5-2", "5-3-2",
];
const FORMATIONS: &[&str] = &["4-3-3", "4-4-2", "4-2-3-1", "4-1-2-1-2", "3-5-2", "5-3-2"];
/// Generate a random AI opponent squad for Squad Battles.
///
@@ -27,23 +28,53 @@ pub fn generate_opponent(card_db: &CardDb, difficulty: &str) -> serde_json::Valu
let (min_overall, names): (u8, &[&str]) = match difficulty {
"professional" => (
70,
&["Athletic CF", "City Wanderers", "The Rovers", "United Select", "Blue Stars FC"],
&[
"Athletic CF",
"City Wanderers",
"The Rovers",
"United Select",
"Blue Stars FC",
],
),
"world_class" => (
78,
&["Elite Stars FC", "Champions Select", "Premier XI", "Galaxy United", "Titan FC"],
&[
"Elite Stars FC",
"Champions Select",
"Premier XI",
"Galaxy United",
"Titan FC",
],
),
"legendary" => (
85,
&["Legends United", "Ultimate XI", "Gold Standard FC", "The Icons", "Heritage FC"],
&[
"Legends United",
"Ultimate XI",
"Gold Standard FC",
"The Icons",
"Heritage FC",
],
),
"ultimate" => (
90,
&["Apex XI", "Pantheon FC", "Gods of FUT", "Invincibles Select", "Eternal XI"],
&[
"Apex XI",
"Pantheon FC",
"Gods of FUT",
"Invincibles Select",
"Eternal XI",
],
),
_ => (
55,
&["Amateur Town FC", "Sunday League XI", "Park FC", "Village Stars", "Reserve XI"],
&[
"Amateur Town FC",
"Sunday League XI",
"Park FC",
"Village Stars",
"Reserve XI",
],
),
};
@@ -59,7 +90,11 @@ pub fn generate_opponent(card_db: &CardDb, difficulty: &str) -> serde_json::Valu
let mut indices: Vec<usize> = (0..pool.len()).collect();
indices.shuffle(&mut rng);
let cards: Vec<_> = indices.into_iter().take(11).map(|i| pool[i].clone()).collect();
let cards: Vec<_> = indices
.into_iter()
.take(11)
.map(|i| pool[i].clone())
.collect();
let squad_rating = if cards.is_empty() {
0
@@ -147,11 +182,18 @@ pub async fn process_match(
for ev in &level_ups {
let body = if let Some(ref pack) = ev.pack_granted {
format!("You reached level {}! Reward: {} coins + {pack}.", ev.new_level, ev.coins_granted)
format!(
"You reached level {}! Reward: {} coins + {pack}.",
ev.new_level, ev.coins_granted
)
} else {
format!("You reached level {}! Reward: {} coins.", ev.new_level, ev.coins_granted)
format!(
"You reached level {}! Reward: {} coins.",
ev.new_level, ev.coins_granted
)
};
let _ = notification::create(pool, "level_up", &format!("Level {}!", ev.new_level), &body).await;
let _ = notification::create(pool, "level_up", &format!("Level {}!", ev.new_level), &body)
.await;
}
statistics::record_match(
@@ -195,15 +237,20 @@ pub async fn process_match(
.find(|d| &d.id == obj_id)
.map(|d| d.title.as_str())
.unwrap_or(obj_id.as_str());
let body = format!("\"{}\" is now complete. Claim your reward in Objectives.", display_name);
let _ = notification::create(pool, "objective_complete", "Objective complete!", &body).await;
let body = format!(
"\"{}\" is now complete. Claim your reward in Objectives.",
display_name
);
let _ =
notification::create(pool, "objective_complete", "Objective complete!", &body).await;
}
// Decrement loan matches remaining for squad starters; collect expired loan IDs.
let expired_loans = process_loan_expiry(pool, club_id, &req.squad_id).await?;
for owned_id in &expired_loans {
let body = format!("Loan card (id: {owned_id}) has expired and been removed from your club.");
let body =
format!("Loan card (id: {owned_id}) has expired and been removed from your club.");
let _ = notification::create(pool, "loan_expired", "Loan card expired", &body).await;
}
@@ -218,14 +265,21 @@ pub async fn process_match(
SeasonResult::Relegated => "Relegated",
SeasonResult::Maintained => "Maintained",
};
let body = format!("{direction} — now in Division {}. Rewards: {} coins{}.", se.new_division, se.coins_awarded, se.pack_awarded.as_deref().map(|p| format!(" + {p}")).unwrap_or_default());
let body = format!(
"{direction} — now in Division {}. Rewards: {} coins{}.",
se.new_division,
se.coins_awarded,
se.pack_awarded
.as_deref()
.map(|p| format!(" + {p}"))
.unwrap_or_default()
);
let _ = notification::create(pool, "season_end", "Season complete!", &body).await;
}
let achievements_unlocked =
achievement::check_and_unlock(pool, ach_defs, profile_id, club_id)
.await
.unwrap_or_default();
let achievements_unlocked = achievement::check_and_unlock(pool, ach_defs, profile_id, club_id)
.await
.unwrap_or_default();
Ok(MatchRewardResult {
match_record,
+1 -4
View File
@@ -67,10 +67,7 @@ pub async fn increment_metric(
) -> AppResult<Vec<String>> {
let mut completed_ids = Vec::new();
for def in defs
.iter()
.filter(|d| d.metric.as_str() == metric)
{
for def in defs.iter().filter(|d| d.metric.as_str() == metric) {
let existing = sqlx::query_as::<_, ObjectiveProgress>(
"SELECT id, profile_id, objective_id, current, completed, claimed, updated_at FROM objective_progress WHERE profile_id = ? AND objective_id = ?"
)
+9 -10
View File
@@ -73,14 +73,13 @@ pub async fn open_pack(
// Atomically claim the pack before minting any cards: only one concurrent opener
// flips opened 0->1, so a double-open cannot mint the reward twice (duplication).
let claimed = sqlx::query(
"UPDATE packs SET opened = 1 WHERE id = ? AND club_id = ? AND opened = 0",
)
.bind(pack_id)
.bind(club_id)
.execute(pool)
.await?
.rows_affected();
let claimed =
sqlx::query("UPDATE packs SET opened = 1 WHERE id = ? AND club_id = ? AND opened = 0")
.bind(pack_id)
.bind(club_id)
.execute(pool)
.await?
.rows_affected();
if claimed == 0 {
return Err(AppError::BadRequest("pack already opened".into()));
}
@@ -134,8 +133,8 @@ pub async fn open_pack(
}
}
let card_ids_json = serde_json::to_string(&cards.iter().map(|c| &c.id).collect::<Vec<_>>())
.unwrap_or_default();
let card_ids_json =
serde_json::to_string(&cards.iter().map(|c| &c.id).collect::<Vec<_>>()).unwrap_or_default();
let now = chrono::Utc::now().to_rfc3339();
sqlx::query("UPDATE packs SET opened_cards = ?, opened_at = ? WHERE id = ?")
+5 -1
View File
@@ -100,7 +100,11 @@ pub async fn add_xp_with_levelup(
}
tracing::info!(profile_id, new_level = lvl, coins, "level up");
events.push(LevelUpEvent { new_level: lvl, coins_granted: coins, pack_granted: pack });
events.push(LevelUpEvent {
new_level: lvl,
coins_granted: coins,
pack_granted: pack,
});
}
Ok(events)
+13 -9
View File
@@ -20,9 +20,11 @@ pub async fn get_or_create(pool: &Pool, profile_id: &str) -> AppResult<Season> {
.bind(&now)
.execute(pool)
.await?;
fetch(pool, profile_id)
.await?
.ok_or_else(|| AppError::Internal(anyhow::anyhow!("season row missing immediately after insert")))
fetch(pool, profile_id).await?.ok_or_else(|| {
AppError::Internal(anyhow::anyhow!(
"season row missing immediately after insert"
))
})
}
async fn fetch(pool: &Pool, profile_id: &str) -> AppResult<Option<Season>> {
@@ -68,9 +70,11 @@ pub async fn record_match(
.execute(pool)
.await?;
let season = fetch(pool, profile_id)
.await?
.ok_or_else(|| AppError::Internal(anyhow::anyhow!("season row missing after record_match update")))?;
let season = fetch(pool, profile_id).await?.ok_or_else(|| {
AppError::Internal(anyhow::anyhow!(
"season row missing after record_match update"
))
})?;
if !season.is_complete() {
return Ok((season, None));
@@ -145,9 +149,9 @@ pub async fn record_match(
pack_awarded: pack_id.map(String::from),
};
let updated = fetch(pool, profile_id)
.await?
.ok_or_else(|| AppError::Internal(anyhow::anyhow!("season row missing after season rollover")))?;
let updated = fetch(pool, profile_id).await?.ok_or_else(|| {
AppError::Internal(anyhow::anyhow!("season row missing after season rollover"))
})?;
Ok((updated, Some(summary)))
}
+8 -10
View File
@@ -16,14 +16,12 @@ pub const MAX_TRAINING_BONUS: i64 = 3;
pub const POSITION_CHANGE_COST: i64 = 500;
async fn fetch_owned(pool: &Pool, owned_card_id: &str, club_id: &str) -> AppResult<OwnedCard> {
sqlx::query_as::<_, OwnedCard>(&format!(
"{OWNED_CARD_SELECT} WHERE id = ? AND club_id = ?"
))
.bind(owned_card_id)
.bind(club_id)
.fetch_optional(pool)
.await?
.ok_or_else(|| AppError::NotFound("owned card not found".into()))
sqlx::query_as::<_, OwnedCard>(&format!("{OWNED_CARD_SELECT} WHERE id = ? AND club_id = ?"))
.bind(owned_card_id)
.bind(club_id)
.fetch_optional(pool)
.await?
.ok_or_else(|| AppError::NotFound("owned card not found".into()))
}
/// Apply a chemistry style to an owned card.
@@ -64,8 +62,8 @@ pub async fn change_position(
new_position: &str,
) -> AppResult<OwnedCard> {
let valid_positions = [
"GK", "RB", "LB", "CB", "RWB", "LWB", "CDM", "CM", "CAM", "RM", "LM", "RW", "LW",
"CF", "ST",
"GK", "RB", "LB", "CB", "RWB", "LWB", "CDM", "CM", "CAM", "RM", "LM", "RW", "LW", "CF",
"ST",
];
if !valid_positions.contains(&new_position) {
return Err(AppError::BadRequest(format!(