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<String> = env::var("MEDIA_ENDPOINT").ok();
let internal_token: Option<String> = 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::<Option<()>>(&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);
}
}