Files
OpenFUT-Core/src/routes/packs.rs
T
2026-08-07 12:03:21 -07:00

151 lines
4.8 KiB
Rust

use crate::extractors::GameId;
use axum::{
extract::{Path, State},
Json,
};
use serde::Deserialize;
use serde_json::{json, Value};
use crate::{
app::AppState,
error::AppResult,
models::pack::PackOpenResult,
services::{club as club_svc, objective, pack as pack_svc, profile as profile_svc, statistics},
};
#[derive(Deserialize)]
pub struct BuyPackRequest {
pub pack_definition_id: String,
}
pub async fn post_buy_pack(
State(state): State<AppState>,
game: GameId,
Json(req): Json<BuyPackRequest>,
) -> 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 pack = pack_svc::buy_pack(
&state.pool,
&state.pack_defs,
&club.id,
&req.pack_definition_id,
)
.await?;
Ok(Json(
json!({ "pack": pack, "message": "Pack purchased successfully" }),
))
}
/// List all purchasable pack definitions with their coin costs.
pub async fn get_pack_store(State(state): State<AppState>) -> AppResult<Json<Value>> {
let store: Vec<Value> = state
.pack_defs
.iter()
.map(|d| {
let total_cards: u8 = d.slots.iter().map(|s| s.count).sum();
json!({
"id": d.id,
"name": d.name,
"description": d.description,
"cost_coins": d.cost_coins,
"total_cards": total_cards,
})
})
.collect();
Ok(Json(json!({ "packs": store })))
}
pub async fn get_packs(State(state): State<AppState>, game: GameId) -> 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 packs = pack_svc::get_unopened_packs(&state.pool, &club.id).await?;
let with_defs: Vec<Value> = packs
.iter()
.map(|p| {
let def = state.pack_defs.iter().find(|d| d.id == p.definition_id);
json!({
"pack_id": p.id,
"definition_id": p.definition_id,
"name": def.map(|d| &d.name),
"description": def.map(|d| &d.description),
"created_at": p.created_at,
})
})
.collect();
Ok(Json(json!({ "packs": with_defs })))
}
/// Return recently opened packs with the card IDs they contained.
pub async fn get_pack_history(State(state): State<AppState>, game: GameId) -> 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 opened = sqlx::query_as::<_, crate::models::pack::Pack>(
"SELECT id, club_id, definition_id, opened, created_at, opened_cards, opened_at \
FROM packs WHERE club_id = ? AND opened = 1 ORDER BY opened_at DESC LIMIT 50",
)
.bind(&club.id)
.fetch_all(&state.pool)
.await?;
let history: Vec<Value> = opened
.iter()
.map(|p| {
let def = state.pack_defs.iter().find(|d| d.id == p.definition_id);
// Expand card_ids into full card definitions
let cards: Vec<Value> = p
.opened_cards
.as_deref()
.and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
.unwrap_or_default()
.iter()
.map(|id| {
json!({
"card_id": id,
"card": state.card_db.get(id),
})
})
.collect();
json!({
"pack_id": p.id,
"definition_id": p.definition_id,
"name": def.map(|d| &d.name),
"opened_at": p.opened_at,
"cards": cards,
})
})
.collect();
Ok(Json(json!({ "history": history, "total": history.len() })))
}
pub async fn post_open_pack(
State(state): State<AppState>,
game: GameId,
Path(pack_id): Path<String>,
) -> AppResult<Json<PackOpenResult>> {
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 result = pack_svc::open_pack(
&state.pool,
&state.card_db,
&state.pack_defs,
&club.id,
&pack_id,
)
.await?;
statistics::increment_packs_opened(&state.pool, &profile.id).await?;
objective::increment_metric(&state.pool, &profile.id, &state.obj_defs, "packsopened", 1)
.await?;
let _ = crate::services::achievement::check_and_unlock(
&state.pool, &state.achievement_defs, &profile.id, &club.id,
).await;
Ok(Json(result))
}