use std::sync::{Arc, Mutex}; use axum::routing::{get, post}; use axum::Router; use rusqlite::Connection; use tower_http::cors::CorsLayer; use crate::handlers; use crate::rate_limit::RateLimiter; pub struct AppState { pub db: Mutex, pub notify_email: String, pub mail_domain: String, pub qa_reply_domain: String, pub rate_limiter: RateLimiter, pub webhook_secret: String, } pub async fn run() -> Result<(), Box> { let db_path = std::env::var("QA_DB_PATH").unwrap_or_else(|_| "qa.db".to_string()); let notify_email = std::env::var("QA_NOTIFY_EMAIL").expect("QA_NOTIFY_EMAIL must be set"); let mail_domain = std::env::var("QA_MAIL_DOMAIN").unwrap_or_else(|_| "extremist.software".to_string()); let qa_reply_domain = std::env::var("QA_REPLY_DOMAIN").unwrap_or_else(|_| mail_domain.clone()); let webhook_secret = std::env::var("WEBHOOK_SECRET").expect("WEBHOOK_SECRET must be set"); let listen_address = std::env::var("QA_LISTEN_ADDRESS").unwrap_or_else(|_| "127.0.0.1".to_string()); let listen_port = std::env::var("QA_LISTEN_PORT").unwrap_or_else(|_| "3003".to_string()); let listen_target = format!("{listen_address}:{listen_port}"); let conn = Connection::open(&db_path)?; conn.execute_batch("PRAGMA journal_mode=WAL;")?; conn.execute_batch( "CREATE TABLE IF NOT EXISTS questions ( id INTEGER PRIMARY KEY AUTOINCREMENT, question TEXT NOT NULL, answer TEXT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), answered_at TEXT );", )?; let state = Arc::new(AppState { db: Mutex::new(conn), notify_email, mail_domain, qa_reply_domain, rate_limiter: RateLimiter::new(5, 3600), webhook_secret, }); let app = Router::new() .route( "/api/questions", get(handlers::get_questions).post(handlers::post_question), ) .route("/api/questions/stats", get(handlers::get_question_stats)) .route("/qa/rss.xml", get(handlers::get_question_rss)) .route("/api/webhook", post(handlers::webhook)) .layer(CorsLayer::permissive()) .with_state(state); let listener = tokio::net::TcpListener::bind(&listen_target).await?; println!("Listening on {listen_target}"); axum::serve(listener, app).await?; Ok(()) }