Initial commit: OpenFUT Core + Bridge scaffold
Two-repo monorepo for an offline FUT backend inspired by SPT. openfut-core: game-independent REST API backend (Axum, SQLite, SQLx) - 19 API endpoints: auth, profiles, clubs, cards, packs, squads, objectives, SBCs, match rewards, NPC market, statistics - JSON-driven card/pack/objective/SBC data (fully moddable) - SQLx migrations, weighted pack generator, SBC validation engine - 5 integration tests passing openfut-bridge: FIFA 23 traffic proxy + reverse-engineering scaffold - Catch-all HTTP proxy with request capture to captures/ - Known-route mapper (FUT paths → Core API calls) - Placeholder responses for unknown endpoints - Admin endpoints: captures, unknown endpoint list - 4 unit tests passing cargo fmt ✓ cargo clippy -D warnings ✓ cargo test ✓ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
use crate::{
|
||||
db::Pool,
|
||||
error::{AppError, AppResult},
|
||||
models::squad::{SaveSquadRequest, Squad, SquadPlayer},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn get_squad(pool: &Pool, club_id: &str) -> AppResult<(Squad, Vec<SquadPlayer>)> {
|
||||
let squad = sqlx::query_as::<_, Squad>(
|
||||
"SELECT id, club_id, name, formation, created_at, updated_at FROM squads WHERE club_id = ? ORDER BY created_at DESC LIMIT 1"
|
||||
)
|
||||
.bind(club_id)
|
||||
.fetch_optional(pool)
|
||||
.await?
|
||||
.ok_or_else(|| AppError::NotFound("no squad found for this club".into()))?;
|
||||
|
||||
let players = sqlx::query_as::<_, SquadPlayer>(
|
||||
"SELECT id, squad_id, owned_card_id, position_index, is_captain, is_on_bench FROM squad_players WHERE squad_id = ?"
|
||||
)
|
||||
.bind(&squad.id)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
Ok((squad, players))
|
||||
}
|
||||
|
||||
pub async fn save_squad(pool: &Pool, club_id: &str, req: &SaveSquadRequest) -> AppResult<Squad> {
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
|
||||
let existing = sqlx::query_scalar::<_, Option<String>>(
|
||||
"SELECT id FROM squads WHERE club_id = ? ORDER BY created_at DESC LIMIT 1",
|
||||
)
|
||||
.bind(club_id)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
let squad_id = if let Some(id) = existing {
|
||||
sqlx::query("UPDATE squads SET name = COALESCE(?, name), formation = COALESCE(?, formation), updated_at = ? WHERE id = ?")
|
||||
.bind(req.name.as_deref())
|
||||
.bind(req.formation.as_deref())
|
||||
.bind(&now)
|
||||
.bind(&id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
sqlx::query("DELETE FROM squad_players WHERE squad_id = ?")
|
||||
.bind(&id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
id
|
||||
} else {
|
||||
let squad = Squad::new(
|
||||
club_id,
|
||||
req.name.as_deref().unwrap_or("My Squad"),
|
||||
req.formation.as_deref().unwrap_or("4-4-2"),
|
||||
);
|
||||
sqlx::query(
|
||||
"INSERT INTO squads (id, club_id, name, formation, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)"
|
||||
)
|
||||
.bind(&squad.id)
|
||||
.bind(&squad.club_id)
|
||||
.bind(&squad.name)
|
||||
.bind(&squad.formation)
|
||||
.bind(&squad.created_at)
|
||||
.bind(&squad.updated_at)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
squad.id
|
||||
};
|
||||
|
||||
for player in &req.players {
|
||||
let sp_id = Uuid::new_v4().to_string();
|
||||
sqlx::query(
|
||||
"INSERT INTO squad_players (id, squad_id, owned_card_id, position_index, is_captain, is_on_bench) VALUES (?, ?, ?, ?, ?, ?)"
|
||||
)
|
||||
.bind(&sp_id)
|
||||
.bind(&squad_id)
|
||||
.bind(&player.owned_card_id)
|
||||
.bind(player.position_index)
|
||||
.bind(player.is_captain)
|
||||
.bind(player.is_on_bench)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let squad = sqlx::query_as::<_, Squad>(
|
||||
"SELECT id, club_id, name, formation, created_at, updated_at FROM squads WHERE id = ?",
|
||||
)
|
||||
.bind(&squad_id)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
Ok(squad)
|
||||
}
|
||||
Reference in New Issue
Block a user