pub mod auth; pub mod db; pub mod errors; pub mod realtime; pub mod routes; use crate::errors::APIError; pub use crate::routes::users; // etc use axum::{ Extension, Json, Router, extract::DefaultBodyLimit, http::{Method, header}, routing::get, }; use serde_json::json; use std::sync::Arc; use std::{path::PathBuf, time::Duration}; use tower_governor::{GovernorLayer, governor::GovernorConfigBuilder}; use tower_http::{ cors::{Any, CorsLayer}, trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}, }; use tracing::Level; const MAX_USERNAME_LENGTH: usize = 35; const MAX_ROOM_NAME_LENGTH: usize = 35; const MAX_UPLOAD_SIZE: usize = 5 * 1024 * 1024; // Not actually used for now pub struct AppConfig { pub avatar_dir: PathBuf, pub prohibit_registration: bool, } #[derive(clap::Parser, Debug)] #[command(author, version, about, long_about = None)] pub struct Cli { /// Server port #[arg(short, long, default_value = "8080")] pub port: String, /// Database URL #[arg(short, long, default_value = "0.0.0.0:5432")] pub database: String, /// Data directory path #[arg(short = 'D', long, default_value = "/var/lib/frangipane")] pub data_dir: String, /// Whether to disable user registration #[arg(short, long)] pub no_registration: bool, /// Verbose mode #[arg(short, long)] pub verbose: bool, } /// Create the app with all routes, middleware and extensions pub fn create_app( db_pool: sqlx::PgPool, config: Arc, messages: realtime::RealtimeMessages, voice: realtime::RealTimeVoices, ) -> Router { let governor_conf = GovernorConfigBuilder::default() .burst_size(20) .per_millisecond(250) .finish() .unwrap(); let governor_limiter = governor_conf.limiter().clone(); let voice_clone = voice.clone(); // A separate background task to clean up let interval = Duration::from_secs(60); std::thread::spawn(move || { loop { std::thread::sleep(interval); // tracing::info!("rate limiting storage size: {}", governor_limiter.len()); governor_limiter.retain_recent(); voice_clone.retain_active(); } }); let cors = CorsLayer::new() .allow_origin(Any) .allow_methods([Method::GET, Method::POST]) .allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE]); Router::new() .route("/version", get(get_version)) .merge(routes::users::routes()) .merge(routes::rooms::routes()) .merge(routes::messages::routes()) .merge(routes::voice::routes()) .merge(routes::friends::routes()) .merge(routes::ws::routes()) .layer(Extension(db_pool)) .layer(Extension(messages)) .layer(Extension(voice)) .layer(Extension(config)) .layer(GovernorLayer::new(governor_conf)) .layer(cors) .layer(DefaultBodyLimit::max(1024 * 5 * 100)) .layer( TraceLayer::new_for_http() .make_span_with(DefaultMakeSpan::new().level(Level::DEBUG)) .on_request(()) .on_response(DefaultOnResponse::new().level(Level::DEBUG)), ) } /// Public route to get current version async fn get_version() -> Result, APIError> { const VERSION: &str = env!("CARGO_PKG_VERSION"); Ok(Json(json!({ "version": VERSION }))) }