feat: in-game theme store — server catalog + verified downloads + install UI
Test / test (pull_request) Successful in 10m15s
Test / test (pull_request) Successful in 10m15s
Phase 1+2 of the theme-store roadmap: a free catalog served by
solitaire_server and an in-app browse/install flow, making custom
themes installable on Android for the first time (the manual
drop-a-zip flow can't reach the app-private themes dir there).
- solitaire_sync: ThemeCatalogEntry/ThemeCatalogResponse wire types
(additive module; SyncPayload and SyncProvider untouched)
- solitaire_server: THEME_STORE_DIR scan at startup (meta-only
theme.ron parse, sha256, 20 MiB cap, best-effort per archive);
public GET /api/themes, /api/themes/{id}/download, /{id}/preview;
compose volume + README_SERVER docs
- solitaire_data: ThemeStoreClient — catalog fetch + download with
mandatory size/sha256 verification before bytes are released
- solitaire_engine: ThemeStorePlugin — 'Browse theme store' button in
Settings → Cosmetic, modal catalog (leaderboard-style rebuild),
download on AsyncComputeTaskPool, atomic .tmp+rename write into
user_theme_dir, then the existing hardened import_theme pipeline and
an in-place registry refresh
New deps: sha2 (workspace, server+data); ron/zip reused in server;
serde_json added to solitaire_sync dev-deps for DTO round-trip tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,12 +31,13 @@
|
||||
//! echo "new_password" | ./solitaire_server --reset-password alice
|
||||
//! ```
|
||||
|
||||
use solitaire_server::{AppState, build_router};
|
||||
use solitaire_server::{AppState, build_router, theme_store};
|
||||
use sqlx::{SqlitePool, sqlite::SqliteConnectOptions};
|
||||
use std::{
|
||||
io::{self, BufRead},
|
||||
net::SocketAddr,
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
const JWT_SECRET_MIN_BYTES: usize = 32;
|
||||
@@ -142,7 +143,19 @@ async fn run_server() {
|
||||
|
||||
tracing::info!("database ready at {db_url}");
|
||||
|
||||
let state = AppState { pool, jwt_secret };
|
||||
// Theme store: optional; a missing directory just means an empty
|
||||
// catalog. Scanned once — add themes, then restart to publish.
|
||||
let theme_store_dir = std::env::var(theme_store::THEME_STORE_DIR_ENV)
|
||||
.unwrap_or_else(|_| theme_store::DEFAULT_THEME_STORE_DIR.into());
|
||||
let theme_store = Arc::new(theme_store::ThemeStore::scan(std::path::Path::new(
|
||||
&theme_store_dir,
|
||||
)));
|
||||
|
||||
let state = AppState {
|
||||
pool,
|
||||
jwt_secret,
|
||||
theme_store,
|
||||
};
|
||||
let app = build_router(state);
|
||||
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
||||
|
||||
Reference in New Issue
Block a user