From 314186d6f474ea7039625145289fd123c745fcfc Mon Sep 17 00:00:00 2001 From: root Date: Mon, 27 Apr 2026 02:10:26 +0000 Subject: [PATCH] feat(data): SyncProvider::delete_account + SolitaireServerClient impl Adds delete_account() as a default no-op on the SyncProvider trait. SolitaireServerClient sends DELETE /api/account with JWT (retry on 401). The server handler already existed (DELETE /api/account, ON DELETE CASCADE). Co-Authored-By: Claude Sonnet 4.6 --- solitaire_data/src/lib.rs | 8 ++++++++ solitaire_data/src/sync_client.rs | 34 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/solitaire_data/src/lib.rs b/solitaire_data/src/lib.rs index 822c2df..604f6c7 100644 --- a/solitaire_data/src/lib.rs +++ b/solitaire_data/src/lib.rs @@ -51,6 +51,11 @@ pub trait SyncProvider: Send + Sync { async fn opt_out_leaderboard(&self) -> Result<(), SyncError> { Ok(()) } + /// Permanently delete the authenticated player's account and all server + /// data. No-op for backends that don't support account management. + async fn delete_account(&self) -> Result<(), SyncError> { + Ok(()) + } } /// Blanket impl so `Box` (returned by @@ -84,6 +89,9 @@ impl SyncProvider for Box { async fn opt_out_leaderboard(&self) -> Result<(), SyncError> { (**self).opt_out_leaderboard().await } + async fn delete_account(&self) -> Result<(), SyncError> { + (**self).delete_account().await + } } pub mod stats; diff --git a/solitaire_data/src/sync_client.rs b/solitaire_data/src/sync_client.rs index 97b4200..8196646 100644 --- a/solitaire_data/src/sync_client.rs +++ b/solitaire_data/src/sync_client.rs @@ -295,6 +295,40 @@ impl SyncProvider for SolitaireServerClient { Ok(()) } + async fn delete_account(&self) -> Result<(), SyncError> { + let token = self.access_token()?; + let url = format!("{}/api/account", self.base_url); + + let resp = self + .client + .delete(&url) + .bearer_auth(&token) + .send() + .await + .map_err(|e| SyncError::Network(e.to_string()))?; + + if resp.status() == reqwest::StatusCode::UNAUTHORIZED { + self.refresh_token().await?; + let new_token = self.access_token()?; + let resp = self + .client + .delete(&url) + .bearer_auth(new_token) + .send() + .await + .map_err(|e| SyncError::Network(e.to_string()))?; + if !resp.status().is_success() { + return Err(SyncError::Auth(format!("delete account failed: {}", resp.status()))); + } + return Ok(()); + } + + if !resp.status().is_success() { + return Err(SyncError::Auth(format!("delete account failed: {}", resp.status()))); + } + Ok(()) + } + async fn fetch_leaderboard(&self) -> Result, SyncError> { let token = self.access_token()?; let url = format!("{}/api/leaderboard", self.base_url);