Phase 25: division leaderboard, market trade history
CI / Build, lint & test (push) Failing after 2m10s

- Market: record buy/sell history in market_history table; expose via
  GET /market/trade-history (last 30 events, newest first)
- Division: GET /division/leaderboard returns 10-club table with 9 seeded
  NPC entries + player row, sorted by pts; stable within a season
- rand feature small_rng enabled in Cargo.toml for SmallRng use
- 3 new integration tests (leaderboard count, sort order, empty trade history)
- Core: 96 tests passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-25 19:13:10 -07:00
parent 956bfe7a73
commit e438b58d88
8 changed files with 253 additions and 6 deletions
+72 -4
View File
@@ -160,14 +160,37 @@ pub async fn buy_listing(
.execute(pool)
.await?;
card_db
let card = card_db
.get(&listing.card_id)
.cloned()
.ok_or_else(|| AppError::NotFound("card definition not found".into()))
.ok_or_else(|| AppError::NotFound("card definition not found".into()))?;
// Record trade history (best-effort — never abort the buy on failure)
let _ = sqlx::query(
"INSERT INTO market_history (id, club_id, card_id, card_name, card_overall, \
trade_type, price, traded_at) VALUES (?,?,?,?,?,?,?,?)",
)
.bind(Uuid::new_v4().to_string())
.bind(club_id)
.bind(&card.id)
.bind(&card.name)
.bind(card.overall as i64)
.bind("buy")
.bind(listing.price)
.bind(chrono::Utc::now().to_rfc3339())
.execute(pool)
.await;
Ok(card)
}
pub async fn sell_card(pool: &Pool, club_id: &str, req: &SellCardRequest) -> AppResult<i64> {
let _owned = sqlx::query_as::<_, crate::models::card::OwnedCard>(
pub async fn sell_card(
pool: &Pool,
card_db: &CardDb,
club_id: &str,
req: &SellCardRequest,
) -> AppResult<i64> {
let owned = sqlx::query_as::<_, crate::models::card::OwnedCard>(
"SELECT id, club_id, card_id, is_loan, loan_matches_remaining, acquired_at, \
chemistry_style, position_override, training_bonus \
FROM owned_cards WHERE id = ? AND club_id = ?",
@@ -185,9 +208,54 @@ pub async fn sell_card(pool: &Pool, club_id: &str, req: &SellCardRequest) -> App
let coins = (req.price as f64 * 0.4) as i64;
let new_balance = club::add_coins(pool, club_id, coins).await?;
// Record trade history
if let Some(card) = card_db.get(&owned.card_id) {
let _ = sqlx::query(
"INSERT INTO market_history (id, club_id, card_id, card_name, card_overall, \
trade_type, price, traded_at) VALUES (?,?,?,?,?,?,?,?)",
)
.bind(Uuid::new_v4().to_string())
.bind(club_id)
.bind(&card.id)
.bind(&card.name)
.bind(card.overall as i64)
.bind("sell")
.bind(coins)
.bind(chrono::Utc::now().to_rfc3339())
.execute(pool)
.await;
}
Ok(new_balance)
}
/// Return the last 30 buy/sell events for this club, newest first.
pub async fn get_trade_history(pool: &Pool, club_id: &str) -> AppResult<Vec<serde_json::Value>> {
let rows = sqlx::query(
"SELECT card_name, card_overall, trade_type, price, traded_at \
FROM market_history WHERE club_id = ? ORDER BY traded_at DESC LIMIT 30",
)
.bind(club_id)
.fetch_all(pool)
.await?;
use sqlx::Row;
let trades = rows
.iter()
.map(|r| {
serde_json::json!({
"card_name": r.get::<String, _>("card_name"),
"card_overall": r.get::<i64, _>("card_overall"),
"trade_type": r.get::<String, _>("trade_type"),
"price": r.get::<i64, _>("price"),
"traded_at": r.get::<String, _>("traded_at"),
})
})
.collect();
Ok(trades)
}
fn price_for_card(overall: u8) -> i64 {
match overall {
85..=u8::MAX => 50_000,