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.
This commit is contained in:
funman300
2026-08-18 01:01:13 +00:00
parent 31ab4a683e
commit 637a21eac1
2 changed files with 59 additions and 7 deletions
+6 -6
View File
@@ -12,6 +12,7 @@ use crate::{
models::card::OwnedCard,
services::{
club as club_svc,
economy as economy_svc,
inventory::{self, OwnedItemQuery, OwnedItemView},
profile as profile_svc,
},
@@ -184,12 +185,11 @@ pub async fn delete_owned_card(
let coins = quick_sell_coins(card.overall);
sqlx::query("DELETE FROM owned_cards WHERE id = ?")
.bind(&owned_card_id)
.execute(&state.pool)
.await?;
club_svc::add_coins(&state.pool, &club.id, coins).await?;
// Delegate to the economy authority rather than hand-rolling DELETE + add_coins:
// that pair ran on the pool with NO transaction (a failed credit left the card
// destroyed for nothing) and it skipped `squad_players`, whose FK onto
// `owned_cards(id)` made quick-selling a squadded card fail with SQLite 787.
economy_svc::sell_item(&state.pool, &club.id, &owned_card_id, coins).await?;
Ok(Json(json!({
"quick_sold": owned_card_id,