26b8e9efef
CI / Build, lint & test (push) Failing after 1m33s
FUT Champions (Weekend League): - POST /fut-champs/start — open a new 30-match week (one active session at a time) - GET /fut-champs — current session status with matches_remaining - POST /fut-champs/:id/result — record a match; auto-completes and computes tier at 30 - POST /fut-champs/:id/claim — claim coins + pack reward (idempotent guard) - GET /fut-champs/history — past sessions newest-first - 11 reward tiers: Elite (27+ wins, 50k coins + icon pack) down to Bronze 1 (0 wins, 250 coins) Division Rivals: - POST /rivals/claim-weekly — claim weekly reward scaled by current division - Week counter is monotonic (offline-safe, no real-time calendar dependency) - Division 1-3 get a bonus pack alongside coins Migration 0008 adds fut_champs_sessions table and two columns to seasons. 12 new integration tests — all 67 pass, clippy clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
109 lines
2.9 KiB
Rust
109 lines
2.9 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
pub const MAX_MATCHES: i64 = 30;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
|
|
pub struct FutChampsSession {
|
|
pub id: String,
|
|
pub profile_id: String,
|
|
pub week_number: i64,
|
|
pub matches_played: i64,
|
|
pub wins: i64,
|
|
pub draws: i64,
|
|
pub losses: i64,
|
|
pub status: String,
|
|
pub reward_tier: Option<String>,
|
|
pub reward_coins: Option<i64>,
|
|
pub reward_pack_id: Option<String>,
|
|
pub reward_claimed: bool,
|
|
pub started_at: String,
|
|
pub completed_at: Option<String>,
|
|
}
|
|
|
|
impl FutChampsSession {
|
|
pub fn matches_remaining(&self) -> i64 {
|
|
(MAX_MATCHES - self.matches_played).max(0)
|
|
}
|
|
|
|
pub fn is_complete(&self) -> bool {
|
|
self.status == "completed"
|
|
}
|
|
}
|
|
|
|
/// Reward tier based on wins out of 30 matches — mirrors FUT 23 structure.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
pub enum ChampsTier {
|
|
Elite,
|
|
WorldClass,
|
|
Gold3,
|
|
Gold2,
|
|
Gold1,
|
|
Silver3,
|
|
Silver2,
|
|
Silver1,
|
|
Bronze3,
|
|
Bronze2,
|
|
Bronze1,
|
|
}
|
|
|
|
impl ChampsTier {
|
|
pub fn from_wins(wins: i64) -> Self {
|
|
match wins {
|
|
27.. => Self::Elite,
|
|
23..=26 => Self::WorldClass,
|
|
20..=22 => Self::Gold3,
|
|
17..=19 => Self::Gold2,
|
|
14..=16 => Self::Gold1,
|
|
11..=13 => Self::Silver3,
|
|
8..=10 => Self::Silver2,
|
|
5..=7 => Self::Silver1,
|
|
2..=4 => Self::Bronze3,
|
|
1 => Self::Bronze2,
|
|
_ => Self::Bronze1,
|
|
}
|
|
}
|
|
|
|
pub fn label(&self) -> &'static str {
|
|
match self {
|
|
Self::Elite => "Elite",
|
|
Self::WorldClass => "World Class",
|
|
Self::Gold3 => "Gold 3",
|
|
Self::Gold2 => "Gold 2",
|
|
Self::Gold1 => "Gold 1",
|
|
Self::Silver3 => "Silver 3",
|
|
Self::Silver2 => "Silver 2",
|
|
Self::Silver1 => "Silver 1",
|
|
Self::Bronze3 => "Bronze 3",
|
|
Self::Bronze2 => "Bronze 2",
|
|
Self::Bronze1 => "Bronze 1",
|
|
}
|
|
}
|
|
|
|
pub fn reward_coins(&self) -> i64 {
|
|
match self {
|
|
Self::Elite => 50_000,
|
|
Self::WorldClass => 30_000,
|
|
Self::Gold3 => 15_000,
|
|
Self::Gold2 => 10_000,
|
|
Self::Gold1 => 7_500,
|
|
Self::Silver3 => 5_000,
|
|
Self::Silver2 => 3_500,
|
|
Self::Silver1 => 2_000,
|
|
Self::Bronze3 => 1_000,
|
|
Self::Bronze2 => 500,
|
|
Self::Bronze1 => 250,
|
|
}
|
|
}
|
|
|
|
pub fn reward_pack(&self) -> Option<&'static str> {
|
|
match self {
|
|
Self::Elite => Some("icon_pack"),
|
|
Self::WorldClass => Some("rare_gold_pack"),
|
|
Self::Gold3 | Self::Gold2 | Self::Gold1 => Some("gold_pack"),
|
|
Self::Silver3 | Self::Silver2 | Self::Silver1 => Some("silver_pack"),
|
|
Self::Bronze3 => Some("bronze_pack"),
|
|
Self::Bronze2 | Self::Bronze1 => None,
|
|
}
|
|
}
|
|
}
|