GET /auth/status returns { has_profile: true/false } without erroring,
so the dashboard can check on load whether an onboarding flow is needed.
POST /auth/reset wipes every user-data table (profiles, clubs, owned_cards,
packs, squads, matches, statistics, achievements, notifications, seasons,
market_listings, sbc_submissions, draft_sessions, fut_champs_sessions,
objective_progress, position_goals, events, settings) in reverse-dependency
order, leaving the schema intact. A fresh POST /auth/local creates a new
club on the clean slate.
4 new tests: status before/after profile creation, reset clears profile
and allows a new one. Core now at 86 tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -130,6 +130,8 @@ pub async fn build(pool: Pool, cfg: Config) -> Result<Router> {
|
|||||||
.route("/health", get(routes::health::get_health))
|
.route("/health", get(routes::health::get_health))
|
||||||
.route("/formations", get(routes::health::get_formations))
|
.route("/formations", get(routes::health::get_formations))
|
||||||
.route("/auth/local", post(routes::auth::post_auth_local))
|
.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("/profile", get(routes::profile::get_profile))
|
||||||
.route("/club", get(routes::club::get_club))
|
.route("/club", get(routes::club::get_club))
|
||||||
.route("/club", put(routes::club::put_club))
|
.route("/club", put(routes::club::put_club))
|
||||||
|
|||||||
@@ -28,3 +28,54 @@ pub async fn post_auth_local(
|
|||||||
"message": "Welcome to OpenFUT FC! Your club has been created."
|
"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<AppState>) -> AppResult<Json<Value>> {
|
||||||
|
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<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",
|
||||||
|
"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."
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1679,3 +1679,62 @@ async fn test_achievement_grants_coins() {
|
|||||||
// Should have gained match coins + at least the first_match achievement reward (500)
|
// 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");
|
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");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user