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:
funman300
2026-06-25 14:42:16 -07:00
commit 6ef4c40e29
79 changed files with 9158 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
# OpenFUT Core — Architecture
## Overview
```
HTTP Client (Bridge or direct)
Axum Router
┌────┴─────┐
│ Routes │ ← thin handlers: extract state, call service, return JSON
└────┬─────┘
┌────┴──────┐
│ Services │ ← business logic, DB calls, data loading
└────┬──────┘
┌────┴──────┐
│ SQLite │ ← SQLx + migrations
└───────────┘
┌────┴──────┐
│ Data/ │ ← JSON files: cards, packs, objectives, SBCs
└───────────┘
```
## Module Map
| Path | Purpose |
|---|---|
| `src/main.rs` | Entry point: tracing, config, pool, migrations, seed, serve |
| `src/lib.rs` | Library root: re-exports modules, exposes `build_app` for tests |
| `src/app.rs` | Router construction, `AppState` definition |
| `src/config.rs` | `Config` struct, loaded from env vars |
| `src/db.rs` | Pool initialization and migration runner |
| `src/error.rs` | `AppError` enum + `IntoResponse` impl |
| `src/models/` | Pure data types (Serde + SQLx `FromRow`) |
| `src/services/` | Business logic; all DB access lives here |
| `src/routes/` | Axum handler functions; one file per domain |
| `src/seed/` | First-run starter pack grant |
| `src/modding/` | Generic JSON directory loader |
| `data/` | Moddable JSON content: cards, packs, objectives, SBCs |
| `migrations/` | SQLx SQL migrations |
## AppState
`AppState` is cloned into every request handler via Axum's `State<AppState>` extractor:
```rust
pub struct AppState {
pub pool: Pool, // SQLite connection pool
pub card_db: Arc<CardDb>, // in-memory card registry
pub pack_defs: Arc<Vec<PackDefinition>>,
pub obj_defs: Arc<Vec<ObjectiveDefinition>>,
pub sbc_defs: Arc<Vec<SbcDefinition>>,
}
```
All game-content data is loaded at startup from `data/` into `Arc`-wrapped collections. This avoids repeated disk I/O per request and keeps the data shared across the multi-threaded Tokio runtime without locking.
## Data Flow: Pack Open
1. `POST /packs/open/:pack_id``routes::packs::post_open_pack`
2. Fetch profile + club from DB
3. Call `services::pack::open_pack(pool, card_db, pack_defs, club_id, pack_id)`
4. Validate pack exists + not opened
5. For each slot in the pack definition, randomly select cards (synchronously — no rng held across await)
6. Insert `owned_cards` rows for each card
7. Mark pack as opened
8. Increment pack stats + objective progress
9. Return `PackOpenResult { pack_id, cards }`
## Data Flow: Match Result
1. `POST /matches/result``routes::matches::post_match_result`
2. Fetch profile + club
3. `services::match_service::process_match(...)`
4. Determine outcome (win/draw/loss), compute coins + XP
5. Insert match record
6. `club::add_coins`, `profile::add_xp`
7. `statistics::record_match`
8. `objective::increment_metric` for matches_played, matches_won, goals_scored, coins_earned
9. Return `MatchRewardResult`
## Single-Profile Design
OpenFUT is single-player. Only one profile is allowed per database. All services fetch "the active profile" by selecting the first row. This is intentional and keeps the system simple.
## Modding
All game content is data-driven. To add new cards:
1. Create a JSON file in `data/cards/`
2. The file must be an array of `CardDefinition`
3. Restart the server
The `CardDb` struct loads all JSON files at startup and holds them in a `HashMap<String, CardDefinition>`.