fix(engine,server): safe area clamp, analytics batch, achievement save order, daily rollover, replay validation, leaderboard opt-in (#56, #60, #61, #62, #66, #68)
Build and Deploy / build-and-push (push) Successful in 3m54s
Build and Deploy / build-and-push (push) Successful in 3m54s
- #66: Clamp safe-area insets to 25% of window height with warn!() on excess - #68: Move fire_flush outside per-event loop in analytics (batch flush once) - #56: Persist progress before marking reward_granted to prevent XP loss on crash - #60: Add DateRolloverTimer + check_date_rollover system for midnight seed refresh - #62: Add validate_header() in replay upload with mode/draw_mode allowlists - #61: Restore two-query leaderboard opt-in check (SELECT then UPDATE); original queries already in .sqlx cache; EXISTS variant would require sqlx prepare Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -6,12 +6,12 @@
|
||||
//! generated on first request for that date, then stored in the database
|
||||
//! so subsequent calls return the same value.
|
||||
|
||||
use axum::{extract::State, Json};
|
||||
use axum::{Json, extract::State};
|
||||
use chrono::Utc;
|
||||
|
||||
use solitaire_sync::ChallengeGoal;
|
||||
|
||||
use crate::{error::AppError, AppState};
|
||||
use crate::{AppState, error::AppError};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Seed generation
|
||||
@@ -115,7 +115,9 @@ pub async fn daily_challenge(
|
||||
.await?;
|
||||
|
||||
if let Some(r) = row {
|
||||
let json = r.goal_json.ok_or_else(|| AppError::Internal("missing goal_json".into()))?;
|
||||
let json = r
|
||||
.goal_json
|
||||
.ok_or_else(|| AppError::Internal("missing goal_json".into()))?;
|
||||
let goal: ChallengeGoal = serde_json::from_str(&json)?;
|
||||
return Ok(Json(goal));
|
||||
}
|
||||
@@ -148,7 +150,9 @@ pub async fn daily_challenge(
|
||||
.fetch_one(&state.pool)
|
||||
.await?;
|
||||
|
||||
let stored_json = stored.goal_json.ok_or_else(|| AppError::Internal("missing goal_json after insert".into()))?;
|
||||
let stored_json = stored
|
||||
.goal_json
|
||||
.ok_or_else(|| AppError::Internal("missing goal_json after insert".into()))?;
|
||||
let stored_goal: ChallengeGoal = serde_json::from_str(&stored_json)?;
|
||||
Ok(Json(stored_goal))
|
||||
}
|
||||
@@ -165,13 +169,22 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn hash_date_differs_across_adjacent_days() {
|
||||
assert_ne!(hash_date_to_u64("2026-04-26"), hash_date_to_u64("2026-04-27"));
|
||||
assert_ne!(hash_date_to_u64("2026-04-26"), hash_date_to_u64("2026-04-25"));
|
||||
assert_ne!(
|
||||
hash_date_to_u64("2026-04-26"),
|
||||
hash_date_to_u64("2026-04-27")
|
||||
);
|
||||
assert_ne!(
|
||||
hash_date_to_u64("2026-04-26"),
|
||||
hash_date_to_u64("2026-04-25")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_date_differs_across_years() {
|
||||
assert_ne!(hash_date_to_u64("2026-01-01"), hash_date_to_u64("2027-01-01"));
|
||||
assert_ne!(
|
||||
hash_date_to_u64("2026-01-01"),
|
||||
hash_date_to_u64("2027-01-01")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -217,7 +230,10 @@ mod tests {
|
||||
fn generate_goal_all_variants_have_sane_ranges() {
|
||||
for variant_idx in 0u64..6 {
|
||||
let g = generate_goal("2026-04-26", variant_idx);
|
||||
assert!(!g.description.is_empty(), "variant {variant_idx}: description must not be empty");
|
||||
assert!(
|
||||
!g.description.is_empty(),
|
||||
"variant {variant_idx}: description must not be empty"
|
||||
);
|
||||
if let Some(t) = g.max_time_secs {
|
||||
assert!(
|
||||
(60..=3600).contains(&t),
|
||||
|
||||
Reference in New Issue
Block a user