ccfeb055e5
- Introduce AppState { pool, jwt_secret } so JWT_SECRET is loaded once in
main() and any missing value is a fatal startup error rather than a 500
on the first request. All four env::var("JWT_SECRET") call sites in
auth.rs and middleware.rs are replaced with state.jwt_secret.
- build_test_router embeds the fixed test secret so integration tests do
not need to set JWT_SECRET in the environment.
- Add tracing::warn! in login (invalid password) and register (username
taken) to surface brute-force attempts in production logs.
- Fix daily-challenge race condition: after INSERT OR IGNORE, re-SELECT
the persisted row so concurrent requests both return the winner's data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
65 lines
2.2 KiB
Rust
65 lines
2.2 KiB
Rust
//! Solitaire Quest sync server entry point.
|
|
//!
|
|
//! Reads configuration from environment variables (via `dotenvy`), initialises
|
|
//! the SQLite database, runs migrations, then starts the Axum HTTP server.
|
|
//!
|
|
//! ## Required environment variables
|
|
//!
|
|
//! | Variable | Description |
|
|
//! |----------------|---------------------------------------------------|
|
|
//! | `DATABASE_URL` | SQLite connection string, e.g. `sqlite://sol.db` |
|
|
//! | `JWT_SECRET` | HS256 signing secret (min 32 chars recommended) |
|
|
//!
|
|
//! ## Optional
|
|
//!
|
|
//! | Variable | Default | Description |
|
|
//! |---------------|---------|-------------------------------|
|
|
//! | `SERVER_PORT` | `8080` | TCP port to listen on |
|
|
|
|
use solitaire_server::{build_router, AppState};
|
|
use sqlx::SqlitePool;
|
|
use std::net::SocketAddr;
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
// Load .env file if present (silently ignored when absent).
|
|
dotenvy::dotenv().ok();
|
|
|
|
// Initialise structured logging.
|
|
tracing_subscriber::fmt::init();
|
|
|
|
let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
|
// Load JWT_SECRET once at startup — a missing secret is a fatal configuration error.
|
|
let jwt_secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
|
|
let port: u16 = std::env::var("SERVER_PORT")
|
|
.unwrap_or_else(|_| "8080".into())
|
|
.parse()
|
|
.expect("SERVER_PORT must be a valid port number");
|
|
|
|
// Connect to SQLite and run pending migrations.
|
|
let pool = SqlitePool::connect(&db_url)
|
|
.await
|
|
.expect("failed to connect to database");
|
|
|
|
sqlx::migrate!("./migrations")
|
|
.run(&pool)
|
|
.await
|
|
.expect("database migration failed");
|
|
|
|
tracing::info!("database ready at {db_url}");
|
|
|
|
let state = AppState { pool, jwt_secret };
|
|
let app = build_router(state);
|
|
|
|
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
|
tracing::info!("listening on {addr}");
|
|
|
|
let listener = tokio::net::TcpListener::bind(addr)
|
|
.await
|
|
.expect("failed to bind TCP listener");
|
|
|
|
axum::serve(listener, app)
|
|
.await
|
|
.expect("server error");
|
|
}
|