funman300 9f3c545c46 feat(import): generic transactional profile-import service + CLI
Core performs a GAME-AGNOSTIC transactional profile import; all FIFA17 semantics
(manifest parse, wire ids, resourceId/nextItemId, squad extension v1,
CardDefinitionId/OwnedItemId choice) stay in openfut-import-fifa17. Core sees
only opaque ids and opaque extension bytes.

services::import::apply_profile_import(pool, card_db, ProfileImportRequest):
- ONE SQLite transaction installs profile + club + all owned cards + canonical
  squad + one opaque game extension; commits together or not at all.
- Definition preflight (pre-tx): every owned card_id MUST resolve in loaded
  production content, so ownership never points at absent content.
- Squad all-or-nothing (pre-tx): every active-squad OwnedItemId MUST be in the
  imported ownership set.
- OwnedItemId uniqueness + generic extension bounds enforced pre-tx.
- Core computes the canonical squad fingerprint itself (never adapter-supplied)
  and persists the extension atomically, exactly as the live squad-write path.

Rerun identity via profiles.import_fingerprint (migration 0018, nullable):
- identical source_fingerprint on an already-imported game -> idempotent no-op;
- differing token -> fail (needs explicit update mode);
- pre-existing non-imported profile -> never clobbered.
So a crash after identity-seeding re-runs cleanly with no cleanup/reminting.

CLI: 'openfut-core import <request.json>' loads production content packs, parses
a generic request, applies. squad_fingerprint made pub(crate) for reuse.

8 import-service tests (happy path, idempotent rerun, fingerprint mismatch,
missing-definition no-write, squad-not-owned, dup OwnedItemId, non-imported
clobber guard, empty-owned). clippy -D warnings clean; full suite 159 green.
2026-08-12 20:01:27 +00:00
2026-06-25 14:54:51 -07:00
2026-06-25 14:54:51 -07:00
2026-06-25 14:54:51 -07:00
2026-06-25 14:54:51 -07:00

OpenFUT Core

Offline Ultimate Team backend — game-independent.

OpenFUT Core is the heart of the OpenFUT project: a fully offline, single-player FUT-style backend written in Rust. It is deliberately decoupled from any specific game, though it is designed to power a FIFA 23 offline experience.


What it does

  • Creates and manages local player profiles and clubs
  • Manages coins, XP, and progression
  • Generates packs from weighted JSON definitions
  • Tracks your full card collection
  • Squad builder with formations and chemistry (chemistry calculations: WIP)
  • Objectives engine (daily, weekly, lifetime, milestone)
  • SBC (Squad Building Challenge) engine with JSON-defined challenges
  • Match result processing with coin and XP rewards
  • NPC transfer market with daily refreshes
  • Statistics tracking
  • Fully moddable via JSON data files

Tech Stack

  • Rust + Axum (HTTP framework)
  • Tokio (async runtime)
  • SQLite + SQLx (database + migrations)
  • Serde (JSON data layer)
  • tower-http (middleware: CORS, tracing)

Quick Start

# Build
cargo build --release

# Run (creates openfut.db in current directory)
./target/release/openfut-core

# Or with custom config
DATABASE_URL=sqlite://./myclub.db LISTEN_ADDR=127.0.0.1:8080 ./target/release/openfut-core

Environment Variables

Variable Default Description
LISTEN_ADDR 127.0.0.1:8080 Address to listen on
DATABASE_URL sqlite://openfut.db SQLite database path
DATA_DIR data Path to JSON data files

API Routes

Method Path Description
GET /health Health check
POST /auth/local Create first-run profile + club
GET /profile Get active profile
GET /club Get active club with coins
GET /cards Browse all card definitions
GET /collection Get owned cards
GET /packs List unopened packs
POST /packs/open/:pack_id Open a pack
GET /squad Get active squad
POST /squad Save squad
GET /objectives List objectives with progress
POST /matches/result Submit match result + receive rewards
GET /sbc List SBC definitions
POST /sbc/submit Submit SBC solution
GET /market Browse NPC transfer market
POST /market/buy Buy listing
POST /market/sell Quick-sell card
POST /market/refresh Refresh NPC listings
GET /statistics Get match/pack/SBC stats

First Run

# Create your profile
curl -X POST http://localhost:8080/auth/local \
  -H 'Content-Type: application/json' \
  -d '{"username": "Player 1"}'

# Check your club (5000 coins + a gold pack waiting)
curl http://localhost:8080/club

# Open your starter pack
curl -X POST http://localhost:8080/packs/open/<pack_id>

# Submit a match win
curl -X POST http://localhost:8080/matches/result \
  -H 'Content-Type: application/json' \
  -d '{"squad_id":"any","opponent_name":"Beginner AI","goals_for":3,"goals_against":0,"mode":"squad_battles"}'

Modding

All game content lives in data/. Drop JSON files into the appropriate folder and restart.

data/
  cards/      ← CardDefinition[]
  packs/      ← PackDefinition[]
  objectives/ ← ObjectiveDefinition[]
  sbcs/       ← SbcDefinition[]
  events/     ← (future)

See docs/modding.md for schema reference.


Development

cargo fmt
cargo clippy -- -D warnings
cargo test

License

MIT — see LICENSE

S
Description
Offline Ultimate Team backend — game-independent core
Readme 634 MiB
Languages
Rust 100%