diff --git a/src/app.rs b/src/app.rs index 14ea7b4..1c1ffb9 100644 --- a/src/app.rs +++ b/src/app.rs @@ -130,6 +130,8 @@ pub async fn build(pool: Pool, cfg: Config) -> Result { .route("/health", get(routes::health::get_health)) .route("/formations", get(routes::health::get_formations)) .route("/auth/local", post(routes::auth::post_auth_local)) + .route("/auth/status", get(routes::auth::get_auth_status)) + .route("/auth/reset", post(routes::auth::post_auth_reset)) .route("/profile", get(routes::profile::get_profile)) .route("/club", get(routes::club::get_club)) .route("/club", put(routes::club::put_club)) diff --git a/src/routes/auth.rs b/src/routes/auth.rs index dc79589..1c451a7 100644 --- a/src/routes/auth.rs +++ b/src/routes/auth.rs @@ -28,3 +28,54 @@ pub async fn post_auth_local( "message": "Welcome to OpenFUT FC! Your club has been created." }))) } + +/// 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) -> AppResult> { + let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM profiles") + .fetch_one(&state.pool) + .await?; + Ok(Json(json!({ "has_profile": count > 0 }))) +} + +/// 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) -> AppResult> { + // 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", + "fut_champs_sessions", + "sbc_submissions", + "objective_progress", + "position_goals", + "statistics", + "seasons", + "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}")) + .execute(&state.pool) + .await?; + } + + tracing::info!("Full game reset performed"); + Ok(Json(json!({ + "reset": true, + "message": "All progress has been wiped. Call POST /auth/local to start a new club." + }))) +} diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 2dcc9c8..1f3d320 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1679,3 +1679,62 @@ async fn test_achievement_grants_coins() { // Should have gained match coins + at least the first_match achievement reward (500) assert!(coins_after > coins_before + 400, "expected achievement coin reward in total"); } + +// ── Phase 21: Onboarding / Reset ───────────────────────────────────────────── + +#[tokio::test] +async fn test_auth_status_before_profile() { + let app = build_test_app().await; + let (status, json) = json_get(&app, "/auth/status").await; + assert_eq!(status, StatusCode::OK); + assert_eq!(json["has_profile"], false); +} + +#[tokio::test] +async fn test_auth_status_after_profile() { + let app = build_test_app().await; + auth(&app, "StatusPlayer").await; + let (status, json) = json_get(&app, "/auth/status").await; + assert_eq!(status, StatusCode::OK); + assert_eq!(json["has_profile"], true); +} + +#[tokio::test] +async fn test_auth_reset_clears_profile() { + let app = build_test_app().await; + auth(&app, "ResetPlayer").await; + + // Play a match to generate some state + json_post(&app, "/matches/result", serde_json::json!({ + "squad_id": "dummy", "opponent_name": "Bot", + "goals_for": 1, "goals_against": 0, "mode": "squad_battles" + })).await; + + // Reset + let (status, json) = json_post(&app, "/auth/reset", serde_json::json!({})).await; + assert_eq!(status, StatusCode::OK, "{json}"); + assert_eq!(json["reset"], true); + + // Profile should be gone + let (s, _) = json_get(&app, "/profile").await; + assert_eq!(s, StatusCode::NOT_FOUND, "profile should not exist after reset"); + + // Status should reflect no profile + let (_, status_json) = json_get(&app, "/auth/status").await; + assert_eq!(status_json["has_profile"], false); +} + +#[tokio::test] +async fn test_auth_reset_allows_new_profile() { + let app = build_test_app().await; + auth(&app, "FirstProfile").await; + + json_post(&app, "/auth/reset", serde_json::json!({})).await; + + // Should be able to create a new profile after reset + let (status, json) = json_post(&app, "/auth/local", + serde_json::json!({ "username": "NewProfile" }) + ).await; + assert_eq!(status, StatusCode::OK, "{json}"); + assert_eq!(json["profile"]["username"], "NewProfile"); +}