funman300 637a21eac1 fix(economy): quick-selling a squadded card failed with a FOREIGN KEY error
Found while implementing sale settlement, and reproduced before fixing:

  sell_item(pool, "club", "item-x", 250)
    -> Database(SqliteError { code: 787, message: "FOREIGN KEY constraint failed" })

squad_players.owned_card_id is a FK onto owned_cards(id) and db.rs enables
foreign_keys, so DELETEing an item that sits in any lineup is refused outright.
services::economy::sell_item never cleared the lineup, and this is the LIVE FIFA 17
quick-sell path (host economy_store -> econ.sell_item -> POST /economy/sell-item).
Selling a card that happens to be in your squad is ordinary, not an edge case.

sell_item now evicts the item from every lineup first, inside its existing
transaction, so a wrong-owner sale rolls the eviction back with everything else
(asserted, not assumed).

Also folds routes/cards.rs::delete_owned_card into the same authority. It
hand-rolled DELETE + club::add_coins directly on the pool, which had BOTH defects:
no transaction, so a failed credit destroyed the card for nothing, and the same
missing squad eviction. Its response shape is unchanged and the pre-existing route
test still passes.

Tests: selling_a_squadded_item_succeeds_and_frees_the_slot (the repro) and
a_rejected_quick_sell_leaves_the_lineup_intact. Core 55 lib + 123 integration pass,
clippy clean.

Not deployed — production is mid live-test.
2026-08-18 01:01:13 +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%