Initial commit: OpenFUT Core

Offline Ultimate Team backend — game-independent REST API.

- 19 API endpoints: auth, profiles, clubs, cards, packs, squads,
  objectives, SBCs, match rewards, NPC market, statistics
- Axum + SQLite + SQLx with full migrations
- Weighted pack generator, SBC validation engine
- JSON-driven mod data (cards, packs, objectives, SBCs)
- 5 integration tests passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-25 14:52:06 -07:00
commit 1ffe0ffa9f
60 changed files with 6152 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
use axum::{
extract::{Query, State},
Json,
};
use serde::Deserialize;
use serde_json::{json, Value};
use crate::{
app::AppState,
error::AppResult,
models::card::OwnedCard,
services::{club as club_svc, profile as profile_svc},
};
#[derive(Debug, Deserialize)]
pub struct CardQuery {
pub rarity: Option<String>,
pub position: Option<String>,
}
pub async fn get_cards(
State(state): State<AppState>,
Query(query): Query<CardQuery>,
) -> AppResult<Json<Value>> {
let cards = state
.card_db
.all()
.into_iter()
.filter(|c| {
query
.rarity
.as_ref()
.map(|r| format!("{:?}", c.rarity).to_lowercase() == r.to_lowercase())
.unwrap_or(true)
&& query
.position
.as_ref()
.map(|p| c.position.to_lowercase() == p.to_lowercase())
.unwrap_or(true)
})
.collect::<Vec<_>>();
Ok(Json(json!({ "cards": cards, "total": cards.len() })))
}
pub async fn get_collection(State(state): State<AppState>) -> 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 owned = sqlx::query_as::<_, OwnedCard>(
"SELECT id, club_id, card_id, is_loan, loan_matches_remaining, acquired_at FROM owned_cards WHERE club_id = ?"
)
.bind(&club.id)
.fetch_all(&state.pool)
.await?;
let with_defs: Vec<Value> = owned
.iter()
.filter_map(|o| {
state.card_db.get(&o.card_id).map(|def| {
json!({
"owned_card_id": o.id,
"is_loan": o.is_loan,
"loan_matches_remaining": o.loan_matches_remaining,
"acquired_at": o.acquired_at,
"card": def,
})
})
})
.collect();
Ok(Json(
json!({ "collection": with_defs, "total": with_defs.len() }),
))
}