AES-256-GCM secret storage keyed by MASTER_KEY, SMTP settings with test mail (lettre), persisted job runs with log and status, JobRunner with per-kind concurrency guard, 6-field cron schedules with defaults, scheduler loop. Settings and Jobs pages in the UI. FAKE_HOST mode for dev machines. Tests: 30 application, 8 infrastructure, 23 API, 16 Vitest, 6 Playwright. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
29 lines
1017 B
Rust
29 lines
1017 B
Rust
use api::{build_app, AppState, Config};
|
|
use tracing_subscriber::EnvFilter;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
let _ = dotenvy::dotenv();
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()))
|
|
.init();
|
|
let cfg = Config::from_env()?;
|
|
if let Some(dir) = cfg
|
|
.database_url
|
|
.strip_prefix("sqlite://")
|
|
.and_then(|p| p.split('?').next())
|
|
.and_then(|p| std::path::Path::new(p).parent())
|
|
{
|
|
std::fs::create_dir_all(dir)?;
|
|
}
|
|
let pool = infrastructure::connect(&cfg.database_url).await?;
|
|
let state = AppState::new(cfg.clone(), pool)?;
|
|
state.bootstrap().await?;
|
|
state.start_scheduler();
|
|
let listener = tokio::net::TcpListener::bind(cfg.bind).await?;
|
|
tracing::info!("listening on http://{}", cfg.bind);
|
|
let app = build_app(state).into_make_service_with_connect_info::<std::net::SocketAddr>();
|
|
axum::serve(listener, app).await?;
|
|
Ok(())
|
|
}
|