about summary refs log blame commit diff
path: root/src/main.rs
blob: 4036d46b230ea4e6d14fb1c8e15ff0e071f2a275 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
                              
             
                                                    
 
                                                                 


                                                                          
                                             
 
                                   
                    
                                           
         
                   
                                                                     


















                                                                                      









                                                                                     
         




                                                                                                                            
                                                                         
                                                                                  

                                                                      
                                                                  
                                             



                                                                          
                             





                                                                                                                     
                                                               
             






                                                       
                                         
                                                     
                                               



















































































                                                                                                    


                                                   
 
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);
    }
}