use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] pub struct Squad { pub id: String, pub club_id: String, pub name: String, pub formation: String, pub created_at: String, pub updated_at: String, } impl Squad { pub fn new( club_id: impl Into, name: impl Into, formation: impl Into, ) -> Self { let now = chrono::Utc::now().to_rfc3339(); Self { id: Uuid::new_v4().to_string(), club_id: club_id.into(), name: name.into(), formation: formation.into(), created_at: now.clone(), updated_at: now, } } } #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] pub struct SquadPlayer { pub id: String, pub squad_id: String, pub owned_card_id: String, pub position_index: i64, pub is_captain: bool, pub is_on_bench: bool, } #[derive(Debug, Deserialize)] pub struct SaveSquadRequest { pub squad_id: Option, pub name: Option, pub formation: Option, pub players: Vec, } #[derive(Debug, Deserialize)] pub struct SquadPlayerInput { pub owned_card_id: String, pub position_index: i64, pub is_captain: bool, pub is_on_bench: bool, } /// A complete squad, as a game client sends it. /// /// # Why replacement rather than edits /// /// Retail FIFA 17 sends the WHOLE squad on every save — roughly 2 KB carrying /// every slot, item id and kit number — and a user swapping two players /// produced nine changed slots across two saves. Slot deltas therefore do not /// describe what the user did, and any attempt to derive `swap_players` or /// `move_player` from them would be inventing intent the wire never carried. /// /// So the only honest semantic operation is: *this is the squad now*. /// /// Empty slots are simply absent from `slots`; a client that models an empty /// slot as a zero item id must drop it at the adapter boundary rather than /// sending a player Core would have to special-case. #[derive(Debug, Clone, Default)] pub struct SquadReplacement { pub name: Option, pub formation: Option, pub slots: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct SlotAssignment { pub owned_card_id: String, /// Core's slot numbering. The adapter maps the game's numbering onto it. pub slot: i64, pub is_captain: bool, pub is_on_bench: bool, } /// Outcome of a replacement. /// /// Carries the server's own evaluation and, separately, any disagreement with /// what the client claimed — never a merged value. #[derive(Debug, Clone)] pub struct SquadReplaced { pub squad: Squad, pub slots_written: usize, pub evaluation: crate::services::squad_rules::SquadEvaluation, pub client_disagreements: Vec, }