Files
OpenFUT-Core/src/routes/draft.rs
T
OpenFUT Agent f70cf4415c wip(core): preserve local broad Core refactor + inventory service
Divergent development line off origin/main (11a811d): a broad refactor across
routes/services/models/app + a large integration_test expansion (+1000), plus an
untracked game-independent inventory query service and Docker files. Preserved
verbatim before moving the canonical Core checkout to the committed migration
trunk (66c88fb). Reconciling this refactor with the migration trunk is a separate
user decision; nothing here is lost.
2026-08-13 18:34:00 +00:00

100 lines
3.1 KiB
Rust

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>,
Query(query): Query<DraftQuery>,
) -> AppResult<Json<Value>> {
let profile = profile_svc::get_active_profile(&state.pool).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>,
Path(session_id): Path<String>,
) -> AppResult<Json<Value>> {
let profile = profile_svc::get_active_profile(&state.pool).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>,
Path(session_id): Path<String>,
Json(req): Json<PickRequest>,
) -> AppResult<Json<Value>> {
let profile = profile_svc::get_active_profile(&state.pool).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>,
Path(session_id): Path<String>,
) -> AppResult<Json<Value>> {
let profile = profile_svc::get_active_profile(&state.pool).await?;
let result = draft_svc::abandon_draft(&state.pool, &profile.id, &session_id).await?;
Ok(Json(result))
}