Phase 10: card upgrade system (chemistry styles, position change, training)
CI / Build, lint & test (push) Failing after 1m37s

- GET /chemistry-styles — lists 18 FUT-style chemistry styles with stat boost breakdowns
- POST /collection/:id/chemistry-style — apply a style (Shadow, Hunter, Anchor, etc.)
- POST /collection/:id/position — override a player's position for 500 coins
- POST /collection/:id/training — add +1/+2/+3 OVR training bonus (max +3 total)
- GET /collection now includes chemistry_style, position_override, training_bonus,
  effective_overall and effective_position fields per owned card
- Migration 0007 adds three columns to owned_cards with safe defaults
- 10 new integration tests — all 55 pass, clippy clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-25 17:02:44 -07:00
parent a749fba93c
commit 19c7b9989d
15 changed files with 617 additions and 6 deletions
+22 -1
View File
@@ -2,6 +2,7 @@ use crate::middleware::{limit_concurrency, MakeRequestUuid};
use crate::{
config::Config,
db::Pool,
models::chemistry_style::{load_chemistry_styles, ChemistryStyle},
models::event::EventDefinition,
models::objective::{ObjectiveDefinition, ObjectiveType},
models::pack::PackDefinition,
@@ -35,6 +36,7 @@ pub struct AppState {
pub obj_defs: Arc<Vec<ObjectiveDefinition>>,
pub sbc_defs: Arc<Vec<SbcDefinition>>,
pub event_defs: Arc<Vec<EventDefinition>>,
pub chem_styles: Arc<Vec<ChemistryStyle>>,
}
pub async fn build(pool: Pool, cfg: Config) -> Result<Router> {
@@ -43,14 +45,16 @@ pub async fn build(pool: Pool, cfg: Config) -> Result<Router> {
let obj_defs = Arc::new(load_objective_definitions(&cfg.data_dir)?);
let sbc_defs = Arc::new(load_sbc_definitions(&cfg.data_dir)?);
let event_defs = Arc::new(load_event_definitions(&cfg.data_dir)?);
let chem_styles = Arc::new(load_chemistry_styles(&cfg.data_dir)?);
tracing::info!(
"Loaded {} cards, {} packs, {} objectives, {} SBCs, {} events",
"Loaded {} cards, {} packs, {} objectives, {} SBCs, {} events, {} chemistry styles",
card_db.cards.len(),
pack_defs.len(),
obj_defs.len(),
sbc_defs.len(),
event_defs.len(),
chem_styles.len(),
);
let state = AppState {
@@ -60,6 +64,7 @@ pub async fn build(pool: Pool, cfg: Config) -> Result<Router> {
obj_defs: obj_defs.clone(),
sbc_defs,
event_defs: event_defs.clone(),
chem_styles,
};
// Background task: refresh NPC market at startup and every 24 hours
@@ -130,6 +135,22 @@ pub async fn build(pool: Pool, cfg: Config) -> Result<Router> {
"/collection/:owned_card_id",
delete(routes::cards::delete_owned_card),
)
.route(
"/collection/:owned_card_id/chemistry-style",
post(routes::upgrades::post_apply_chemistry_style),
)
.route(
"/collection/:owned_card_id/position",
post(routes::upgrades::post_change_position),
)
.route(
"/collection/:owned_card_id/training",
post(routes::upgrades::post_apply_training),
)
.route(
"/chemistry-styles",
get(routes::upgrades::get_chemistry_styles),
)
.route("/packs", get(routes::packs::get_packs))
.route("/packs/history", get(routes::packs::get_pack_history))
.route("/packs/buy", post(routes::packs::post_buy_pack))