Files
OpenFUT/openfut-core/docs/architecture.md
T
funman300 6ef4c40e29 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>
2026-06-25 14:42:16 -07:00

3.6 KiB

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:

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_idroutes::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/resultroutes::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>.