use crate::extractors::GameId; use axum::{ extract::{Path, State}, Json, }; use serde::Deserialize; use serde_json::{json, Value}; use crate::{ app::AppState, error::AppResult, services::{club as club_svc, profile as profile_svc, upgrades as upgrade_svc}, }; /// GET /chemistry-styles — list all available styles with their stat boosts. pub async fn get_chemistry_styles(State(state): State) -> AppResult> { Ok(Json(json!({ "chemistry_styles": *state.chem_styles, "total": state.chem_styles.len(), }))) } #[derive(Deserialize)] pub struct ApplyChemStyleRequest { pub style_id: String, } /// POST /collection/:owned_card_id/chemistry-style pub async fn post_apply_chemistry_style( State(state): State, game: GameId, Path(owned_card_id): Path, Json(req): Json, ) -> AppResult> { let profile = profile_svc::get_active_profile(&state.pool, game.as_str()).await?; let club = club_svc::get_club_by_profile(&state.pool, &profile.id).await?; let updated = upgrade_svc::apply_chemistry_style( &state.pool, &club.id, &owned_card_id, &req.style_id, &state.chem_styles, ) .await?; let style = state .chem_styles .iter() .find(|s| s.id == updated.chemistry_style); Ok(Json(json!({ "owned_card_id": updated.id, "card_id": updated.card_id, "chemistry_style": updated.chemistry_style, "style_details": style, }))) } #[derive(Deserialize)] pub struct ChangePositionRequest { pub position: String, } /// POST /collection/:owned_card_id/position — costs 500 coins. pub async fn post_change_position( State(state): State, game: GameId, Path(owned_card_id): Path, Json(req): Json, ) -> AppResult> { let profile = profile_svc::get_active_profile(&state.pool, game.as_str()).await?; let club = club_svc::get_club_by_profile(&state.pool, &profile.id).await?; let updated = upgrade_svc::change_position( &state.pool, &club.id, &owned_card_id, &req.position, ) .await?; let card_def = state.card_db.get(&updated.card_id); Ok(Json(json!({ "owned_card_id": updated.id, "card_id": updated.card_id, "original_position": card_def.map(|c| &c.position), "position_override": updated.position_override, "cost_coins": upgrade_svc::POSITION_CHANGE_COST, }))) } #[derive(Deserialize)] pub struct ApplyTrainingRequest { /// OVR points to add. Must be 1–3. Total capped at +3. pub boost: i64, } /// POST /collection/:owned_card_id/training — applies a training boost (up to +3 OVR total). pub async fn post_apply_training( State(state): State, game: GameId, Path(owned_card_id): Path, Json(req): Json, ) -> AppResult> { let profile = profile_svc::get_active_profile(&state.pool, game.as_str()).await?; let club = club_svc::get_club_by_profile(&state.pool, &profile.id).await?; let updated = upgrade_svc::apply_training(&state.pool, &club.id, &owned_card_id, req.boost).await?; let card_def = state.card_db.get(&updated.card_id); let base_overall = card_def.map(|c| c.overall as i64).unwrap_or(0); Ok(Json(json!({ "owned_card_id": updated.id, "card_id": updated.card_id, "base_overall": base_overall, "training_bonus": updated.training_bonus, "effective_overall": base_overall + updated.training_bonus, "max_bonus": upgrade_svc::MAX_TRAINING_BONUS, }))) }