feat(club): ownership-backed squad manager assignment
Add a generic, durable squad->manager assignment (migration 0023
squad_managers) so a manager persists across squad save, reload, and
server restart, backed by authoritative Core owned_cards.
- squad_managers(squad_id PK, owned_card_id, updated_at) with ON DELETE
CASCADE on both FKs: quick-selling the manager auto-clears the
assignment (no resurrection); one manager per squad (no duplicates).
- club::{get,set,clear}_squad_manager validate club ownership of both the
squad and the card, and re-check ownership on read (defends against a
stale row left by a market transfer).
- GET/PUT /club/manager routes expose the assignment; FIFA wire meaning
stays in the adapter.
Tests: persistence across reload+restart (headline), reassignment
replace/no-duplicate, clear/no-resurrection, cascade on quick-sell,
foreign-card rejection.
This commit is contained in:
@@ -124,3 +124,42 @@ pub async fn get_milestones(State(state): State<AppState>, game: GameId) -> AppR
|
||||
"club_level": club.level,
|
||||
})))
|
||||
}
|
||||
|
||||
/// The owned card assigned as the active squad's manager, or `null`. Generic:
|
||||
/// Core returns the ownership-backed assignment; the FIFA 17 adapter shapes the
|
||||
/// manager wire item from it (itemType/contract/chemistry are adapter concerns).
|
||||
pub async fn get_squad_manager(
|
||||
State(state): State<AppState>,
|
||||
game: GameId,
|
||||
) -> AppResult<Json<Value>> {
|
||||
let profile = profile_svc::get_active_profile(&state.pool, game.as_str()).await?;
|
||||
let club = club_svc::get_club_by_profile(&state.pool, &profile.id).await?;
|
||||
let manager = club_svc::get_squad_manager(&state.pool, &club.id).await?;
|
||||
Ok(Json(json!({ "manager": manager })))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SetManagerRequest {
|
||||
/// The owned card to assign as manager, or `null`/absent to clear it.
|
||||
pub owned_card_id: Option<String>,
|
||||
}
|
||||
|
||||
/// Assign (or, with a null/absent `owned_card_id`, clear) the active squad's
|
||||
/// manager. Fail-closed: the card must be owned by this club and the club must
|
||||
/// have a squad. Returns the resulting assignment.
|
||||
pub async fn put_squad_manager(
|
||||
State(state): State<AppState>,
|
||||
game: GameId,
|
||||
Json(req): Json<SetManagerRequest>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
let profile = profile_svc::get_active_profile(&state.pool, game.as_str()).await?;
|
||||
let club = club_svc::get_club_by_profile(&state.pool, &profile.id).await?;
|
||||
match req.owned_card_id {
|
||||
Some(owned_card_id) => {
|
||||
club_svc::set_squad_manager(&state.pool, &club.id, &owned_card_id).await?
|
||||
}
|
||||
None => club_svc::clear_squad_manager(&state.pool, &club.id).await?,
|
||||
}
|
||||
let manager = club_svc::get_squad_manager(&state.pool, &club.id).await?;
|
||||
Ok(Json(json!({ "manager": manager })))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user