Files
OpenFUT-Core/src/routes/draft.rs
T
funman300 a034e74c16
CI / Build, lint & test (push) Successful in 2m19s
style(core): apply cargo fmt across routes, services, models, tests
Pure rustfmt reflow (import grouping, array/match-arm/call-arg wrapping,
alphabetized module decls, comment realignment). No semantic change:
full-diff and `git diff -w` both confirm logic byte-identical to 271c363;
workspace builds and all 74 core tests pass. Retained pre-existing WIP
brought forward after verification.
2026-08-20 16:05:41 +00:00

105 lines
3.3 KiB
Rust

use crate::extractors::GameId;
use axum::{
extract::{Path, Query, State},
Json,
};
use serde::Deserialize;
use serde_json::{json, Value};
use crate::{
app::AppState,
error::AppResult,
services::{club as club_svc, draft as draft_svc, match_service, profile as profile_svc},
};
#[derive(Deserialize)]
pub struct DraftQuery {
pub difficulty: Option<String>,
}
/// Legacy endpoint: returns a pre-built random draft squad (no session state).
pub async fn get_draft_squad(
State(state): State<AppState>,
Query(query): Query<DraftQuery>,
) -> AppResult<Json<Value>> {
let difficulty = query.difficulty.as_deref().unwrap_or("beginner");
let generated = match_service::generate_opponent(&state.card_db, difficulty);
Ok(Json(json!({
"mode": "draft",
"difficulty": difficulty,
"squad_rating": generated["squad_rating"],
"formation": generated["formation"],
"cards": generated["cards"],
})))
}
/// Start a new stateful FUT-style draft session.
///
/// Returns a session ID and the 5 candidates for the first position (GK).
/// The caller picks one candidate, then calls `POST /draft/sessions/:id/pick`
/// until all 11 slots are filled.
pub async fn post_draft_start(
State(state): State<AppState>,
game: GameId,
Query(query): Query<DraftQuery>,
) -> AppResult<Json<Value>> {
let profile = profile_svc::get_active_profile(&state.pool, game.as_str()).await?;
let difficulty = query.difficulty.as_deref().unwrap_or("professional");
let session =
draft_svc::start_draft(&state.pool, &state.card_db, &profile.id, difficulty).await?;
Ok(Json(session))
}
/// Get the current state of a draft session.
pub async fn get_draft_session(
State(state): State<AppState>,
game: GameId,
Path(session_id): Path<String>,
) -> AppResult<Json<Value>> {
let profile = profile_svc::get_active_profile(&state.pool, game.as_str()).await?;
let session =
draft_svc::get_draft(&state.pool, &state.card_db, &profile.id, &session_id).await?;
Ok(Json(session))
}
#[derive(Deserialize)]
pub struct PickRequest {
pub card_id: String,
}
/// Pick one card from the current candidates list.
///
/// Advances the session to the next position.
/// When the last pick is made the session status changes to "completed"
/// and rewards (coins + optional pack) are granted automatically.
pub async fn post_draft_pick(
State(state): State<AppState>,
game: GameId,
Path(session_id): Path<String>,
Json(req): Json<PickRequest>,
) -> AppResult<Json<Value>> {
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 session = draft_svc::pick_card(
&state.pool,
&state.card_db,
&club.id,
&profile.id,
&session_id,
&req.card_id,
)
.await?;
Ok(Json(session))
}
/// Abandon an active draft session. No rewards are granted.
pub async fn post_draft_abandon(
State(state): State<AppState>,
game: GameId,
Path(session_id): Path<String>,
) -> AppResult<Json<Value>> {
let profile = profile_svc::get_active_profile(&state.pool, game.as_str()).await?;
let result = draft_svc::abandon_draft(&state.pool, &profile.id, &session_id).await?;
Ok(Json(result))
}