wip: checkpoint multi-game core work
This commit is contained in:
+85
-28
@@ -1,9 +1,11 @@
|
||||
use axum::{extract::State, Json};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::{
|
||||
app::AppState,
|
||||
error::AppResult,
|
||||
error::{AppError, AppResult},
|
||||
extractors::GameId,
|
||||
models::{club::Club, profile::CreateProfileRequest},
|
||||
seed,
|
||||
services::{club as club_svc, profile as profile_svc},
|
||||
@@ -11,11 +13,12 @@ use crate::{
|
||||
|
||||
pub async fn post_auth_local(
|
||||
State(state): State<AppState>,
|
||||
game: GameId,
|
||||
Json(req): Json<CreateProfileRequest>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
let username = req.username.unwrap_or_else(|| "Player 1".into());
|
||||
|
||||
let profile = profile_svc::create_profile(&state.pool, &username).await?;
|
||||
let profile = profile_svc::create_profile(&state.pool, &username, game.as_str()).await?;
|
||||
|
||||
let club = Club::new(&profile.id, "OpenFUT FC", 5000);
|
||||
club_svc::create_club(&state.pool, &club).await?;
|
||||
@@ -31,51 +34,105 @@ pub async fn post_auth_local(
|
||||
|
||||
/// GET /auth/status — lightweight check: does a profile exist?
|
||||
/// Returns 200 `{ "has_profile": true/false }` without erroring.
|
||||
pub async fn get_auth_status(State(state): State<AppState>) -> AppResult<Json<Value>> {
|
||||
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM profiles")
|
||||
pub async fn get_auth_status(State(state): State<AppState>, game: GameId) -> AppResult<Json<Value>> {
|
||||
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM profiles WHERE game_id = ?")
|
||||
.bind(game.as_str())
|
||||
.fetch_one(&state.pool)
|
||||
.await?;
|
||||
Ok(Json(json!({ "has_profile": count > 0 })))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ResetRequest {
|
||||
pub confirm: Option<String>,
|
||||
}
|
||||
|
||||
/// POST /auth/reset — wipe all game data and start fresh.
|
||||
/// Deletes every user-data table in dependency order. The schema tables
|
||||
/// (migrations) are left intact; calling POST /auth/local afterwards
|
||||
/// creates a new profile.
|
||||
pub async fn post_auth_reset(State(state): State<AppState>) -> AppResult<Json<Value>> {
|
||||
// Delete in reverse-dependency order to satisfy FK constraints
|
||||
// (SQLite FK enforcement is opt-in, but we follow the order anyway)
|
||||
let tables = [
|
||||
"player_achievements",
|
||||
"notifications",
|
||||
/// Requires `{"confirm":"reset"}` in the request body as a safeguard against
|
||||
/// accidental or unauthenticated calls. Deletes every user-data table in
|
||||
/// dependency order; schema (migrations) is preserved.
|
||||
pub async fn post_auth_reset(
|
||||
State(state): State<AppState>,
|
||||
game: GameId,
|
||||
Json(req): Json<ResetRequest>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
if req.confirm.as_deref() != Some("reset") {
|
||||
return Err(AppError::BadRequest(
|
||||
r#"include {"confirm":"reset"} in the request body to confirm data wipe"#.into(),
|
||||
));
|
||||
}
|
||||
|
||||
// Multi-game: reset ONLY this game's profile subtree so a FIFA 17 reset never
|
||||
// wipes FIFA 23 (and vice versa). Resolve the game's profile + its clubs, then
|
||||
// delete their dependent rows in reverse-dependency order.
|
||||
let profile_id: Option<String> = sqlx::query_scalar(
|
||||
"SELECT id FROM profiles WHERE game_id = ? ORDER BY created_at ASC LIMIT 1",
|
||||
)
|
||||
.bind(game.as_str())
|
||||
.fetch_optional(&state.pool)
|
||||
.await?;
|
||||
|
||||
let Some(profile_id) = profile_id else {
|
||||
return Ok(Json(json!({
|
||||
"reset": true,
|
||||
"message": "nothing to reset for this game"
|
||||
})));
|
||||
};
|
||||
|
||||
let club_ids: Vec<String> = sqlx::query_scalar("SELECT id FROM clubs WHERE profile_id = ?")
|
||||
.bind(&profile_id)
|
||||
.fetch_all(&state.pool)
|
||||
.await?;
|
||||
|
||||
for club_id in &club_ids {
|
||||
sqlx::query(
|
||||
"DELETE FROM squad_players WHERE squad_id IN (SELECT id FROM squads WHERE club_id = ?)",
|
||||
)
|
||||
.bind(club_id)
|
||||
.execute(&state.pool)
|
||||
.await?;
|
||||
for table in ["squads", "owned_cards", "packs", "market_history"] {
|
||||
sqlx::query(&format!("DELETE FROM {table} WHERE club_id = ?"))
|
||||
.bind(club_id)
|
||||
.execute(&state.pool)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
for table in [
|
||||
"fut_champs_sessions",
|
||||
"sbc_submissions",
|
||||
"objective_progress",
|
||||
"position_goals",
|
||||
"statistics",
|
||||
"seasons",
|
||||
"season_history",
|
||||
"matches",
|
||||
"market_listings",
|
||||
"squad_players",
|
||||
"squads",
|
||||
"owned_cards",
|
||||
"packs",
|
||||
"events",
|
||||
"draft_sessions",
|
||||
"settings",
|
||||
"clubs",
|
||||
"profiles",
|
||||
];
|
||||
|
||||
for table in &tables {
|
||||
sqlx::query(&format!("DELETE FROM {table}"))
|
||||
"daily_checkins",
|
||||
] {
|
||||
sqlx::query(&format!("DELETE FROM {table} WHERE profile_id = ?"))
|
||||
.bind(&profile_id)
|
||||
.execute(&state.pool)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tracing::info!("Full game reset performed");
|
||||
sqlx::query("DELETE FROM clubs WHERE profile_id = ?")
|
||||
.bind(&profile_id)
|
||||
.execute(&state.pool)
|
||||
.await?;
|
||||
sqlx::query("DELETE FROM profiles WHERE id = ?")
|
||||
.bind(&profile_id)
|
||||
.execute(&state.pool)
|
||||
.await?;
|
||||
|
||||
// NOTE (follow-up): settings (global key/value), events (shared content),
|
||||
// market_listings (keyed by seller_name, includes the shared NPC market),
|
||||
// notifications and player_achievements are not yet game-scoped and are left
|
||||
// intact. They need a game/profile key before a per-game reset can cover them.
|
||||
tracing::info!(game = %game.as_str(), "Per-game reset performed");
|
||||
Ok(Json(json!({
|
||||
"reset": true,
|
||||
"message": "All progress has been wiped. Call POST /auth/local to start a new club."
|
||||
"message": "All progress for this game has been wiped. Call POST /auth/local to start a new club."
|
||||
})))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user