use log::{debug, error, info}; use std::env; use http_types::Url; use warp::{Filter, host::Authority, path::FullPath}; #[tokio::main] async fn main() -> Result<(), kittybox::database::StorageError> { // TODO json logging in the future? let logger_env = env_logger::Env::new().filter_or("RUST_LOG", "info"); env_logger::init_from_env(logger_env); info!("Starting the kittybox server..."); let backend_uri: String; match env::var("BACKEND_URI") { Ok(val) => { debug!("Backend URI: {}", val); backend_uri = val } Err(_) => { error!("BACKEND_URI is not set, cannot find a database"); std::process::exit(1); } }; let token_endpoint: Url; match env::var("TOKEN_ENDPOINT") { Ok(val) => { debug!("Token endpoint: {}", val); match Url::parse(&val) { Ok(val) => token_endpoint = val, _ => { error!("Token endpoint URL cannot be parsed, aborting."); std::process::exit(1) } } } Err(_) => { error!("TOKEN_ENDPOINT is not set, will not be able to authorize users!"); std::process::exit(1) } } let authorization_endpoint: Url; match env::var("AUTHORIZATION_ENDPOINT") { Ok(val) => { debug!("Auth endpoint: {}", val); match Url::parse(&val) { Ok(val) => authorization_endpoint = val, _ => { error!("Authorization endpoint URL cannot be parsed, aborting."); std::process::exit(1) } } } Err(_) => { error!("AUTHORIZATION_ENDPOINT is not set, will not be able to confirm token and ID requests using IndieAuth!"); std::process::exit(1) } } let media_endpoint: Option = env::var("MEDIA_ENDPOINT").ok(); let internal_token: Option = env::var("KITTYBOX_INTERNAL_TOKEN").ok(); let cookie_secret: String = match env::var("COOKIE_SECRET").ok() { Some(value) => value, None => { if let Ok(filename) = env::var("COOKIE_SECRET_FILE") { /*use async_std::io::ReadExt; let mut file = async_std::fs::File::open(filename).await?; let mut temp_string = String::new(); file.read_to_string(&mut temp_string).await?; temp_string*/ todo!() } else { error!("COOKIE_SECRET or COOKIE_SECRET_FILE is not set, will not be able to log in users securely!"); std::process::exit(1); } } }; let host: std::net::SocketAddr = match env::var("SERVE_AT") .ok() .unwrap_or_else(|| "0.0.0.0:8080".to_string()) .parse() { Ok(addr) => addr, Err(e) => { error!("Cannot parse SERVE_AT: {}", e); std::process::exit(1); } }; if backend_uri.starts_with("redis") { println!("The Redis backend is deprecated."); std::process::exit(1); } else if backend_uri.starts_with("file") { let database = { let folder = backend_uri.strip_prefix("file://").unwrap(); let path = std::path::PathBuf::from(folder); kittybox::database::FileStorage::new(path).await? }; // TODO interpret HEAD let homepage = kittybox::util::require_host() .and(warp::get()) .and(warp::path::end()) // TODO fetch content from the database // TODO parse content-type and determine appropriate response .map(|host| format!("front page for {}!", host)); let micropub = warp::path("micropub") .and(warp::path::end() .and(warp::get() .and(kittybox::micropub::query(database)) .or(warp::post() .and(kittybox::util::require_host()) .map(|host| "micropub post!")) .or(warp::options() .map(|| warp::reply::json::>(&None)) // TODO: why doesn't this work? // .map(warp::reply::with::header("Allow", "GET, POST")) .map(|reply| warp::reply::with_header(reply, "Allow", "GET, POST")) )) .or(warp::get() .and(warp::path("client")) .and(warp::path::end()) .map(|| kittybox::MICROPUB_CLIENT))); let media = warp::path("media") .and(warp::path::end() .and(kittybox::util::require_host()) .map(|host| "media endpoint?...") .or(kittybox::util::require_host() .and(warp::path::param()) .map(|host: Authority, path: String| format!("media file {}", path)))); // TODO remember how login logic works because I forgor let login = warp::path("login") .and(warp::path("callback") .map(|| "callback!") // TODO form on GET and handler on POST .or(warp::path::end().map(|| "login page!"))); // TODO prettier error response let coffee = warp::path("coffee") .map(|| warp::reply::with_status("I'm a teapot!", warp::http::StatusCode::IM_A_TEAPOT)); // TODO interpret HEAD let static_files = warp::get() .and(warp::path!("static" / String)) .map(|path| path); // TODO interpret HEAD let catchall = warp::get() .and(kittybox::util::require_host()) .and(warp::path::full()) .map(|host: Authority, path: FullPath| host.to_string() + path.as_str() + ".json") // TODO fetch content from the database // TODO parse content-type and determine appropriate response ; let health = warp::path("health").and(warp::path::end()).map(|| "OK"); // TODO instrumentation middleware (see metrics.rs for comments) //let metrics = warp::path("metrics").and(warp::path::end()).map(kittybox::metrics::gather); let app = homepage .or(login) .or(static_files) .or(coffee) .or(health) .or(micropub) .or(media) .or(catchall) ; let server = warp::serve(app); // TODO use warp::Server::bind_with_graceful_shutdown info!("Listening on {:?}", host); server.bind(host).await; Ok(()) } else { println!("Unknown backend, not starting."); std::process::exit(1); } }