feat: JWT auth, /api prefix, SPA serving, OpenAPI, lean main.rs

- auth: register/login/refresh/logout w/ JWT+Argon2, protected mutations
- domain: User, RefreshSession, auth ports, Unauthorized/Forbidden errors
- presentation: context/state/factory/errors/extractors/openapi modules
- routes behind /api, SPA served from root w/ fallback
- OpenAPI Scalar at /docs
- frontend ssr:false, single-binary Dockerfile
This commit is contained in:
2026-07-11 21:28:52 +02:00
parent 13031347cc
commit 7bd27d9b9c
50 changed files with 1604 additions and 213 deletions

View File

@@ -1,14 +1,19 @@
use std::env;
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct AppConfig {
pub database_url: String,
pub host: String,
pub port: u16,
pub cors_origins: CorsOrigins,
pub jwt_secret: String,
pub jwt_ttl_seconds: u64,
pub refresh_ttl_seconds: u64,
pub allow_registration: bool,
pub spa_dir: String,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum CorsOrigins {
Any,
List(Vec<String>),
@@ -40,11 +45,34 @@ impl AppConfig {
),
};
let jwt_secret = env::var("JWT_SECRET").expect("JWT_SECRET env var is required");
let jwt_ttl_seconds = env::var("JWT_TTL_SECONDS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(900);
let refresh_ttl_seconds = env::var("REFRESH_TTL_SECONDS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(2_592_000);
let allow_registration = env::var("ALLOW_REGISTRATION")
.map(|v| v == "true" || v == "1")
.unwrap_or(false);
let spa_dir = env::var("SPA_DIR").unwrap_or_else(|_| "./app/build/client".into());
Self {
database_url,
host,
port,
cors_origins,
jwt_secret,
jwt_ttl_seconds,
refresh_ttl_seconds,
allow_registration,
spa_dir,
}
}