use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::error::Error; use std::sync; use warp::Filter; use crate::domain; pub mod http_tpl; mod util; /* * POST /domain/config (domain, config, secret, init?) -> token? * GET /domain/config (domain) -> config * GET /domains */ type Handlebars<'a> = sync::Arc>; struct RenderContext<'a, DM> where DM: domain::manager::Manager, { domain_manager: sync::Arc, target_cname: sync::Arc, passphrase: sync::Arc, handlebars: Handlebars<'a>, query_args: HashMap, } // TODO make this use an io::Write, rather than warp::Reply fn render<'a, T>(handlebars: Handlebars<'a>, name: &'a str, value: T) -> impl warp::Reply where T: Serialize, { // TODO deal with 404 let render = handlebars .render(name, &value) .unwrap_or_else(|err| err.to_string()); let content_type = mime_guess::from_path(name) .first_or_octet_stream() .to_string(); let reply = warp::reply::html(render); warp::reply::with_header(reply, "Content-Type", content_type) } fn render_page<'a, T, DM>( render_ctx: RenderContext<'a, DM>, name: String, data: T, ) -> impl warp::Reply where T: Serialize, DM: domain::manager::Manager, { #[derive(Serialize)] struct Presenter { page_name: String, query_args: HashMap, data: T, } let presenter = Presenter { page_name: name, query_args: render_ctx.query_args, data, }; render(render_ctx.handlebars, "/base.html", presenter) } pub fn new( manager: DM, target_cname: domain::Name, passphrase: String, ) -> Result< impl warp::Filter + Clone + 'static, Box, > where DM: domain::manager::Manager + 'static, { let manager = sync::Arc::new(manager); let target_cname = sync::Arc::new(target_cname); let passphrase = sync::Arc::new(passphrase); let hbs = sync::Arc::new(self::http_tpl::get()?); let with_render_ctx = warp::any() .and(warp::query::>()) .map(move |query_args: HashMap| RenderContext { domain_manager: manager.clone(), target_cname: target_cname.clone(), passphrase: passphrase.clone(), handlebars: hbs.clone(), query_args, }); let static_dir = warp::get() .and(with_render_ctx.clone()) .and(warp::path("static")) .and(warp::path::full()) .map( |render_ctx: RenderContext<'_, DM>, full: warp::path::FullPath| { render(render_ctx.handlebars, full.as_str(), ()) }, ); let index = warp::get() .and(with_render_ctx.clone()) .and(warp::path::end()) .map(|render_ctx: RenderContext<'_, DM>| { render_page(render_ctx, String::from("/index.html"), ()) }); #[derive(Deserialize)] struct DomainGetNewRequest { domain: domain::Name, } #[derive(Serialize)] struct DomainGetNewResponse { domain: domain::Name, config: Option, } let domain_get = warp::get() .and(with_render_ctx.clone()) .and(warp::path!("domain.html")) .and(warp::query::()) .and(warp::query::()) .map( |render_ctx: RenderContext<'_, DM>, req: DomainGetNewRequest, domain_config: util::ConfigFromURL| { match render_ctx.domain_manager.get_config(&req.domain) { Ok(_config) => panic!("TODO"), Err(domain::manager::GetConfigError::NotFound) => render_page( render_ctx, String::from("/domain_get_new.html"), DomainGetNewResponse { domain: req.domain, config: domain_config.try_into().expect("TODO"), }, ), Err(domain::manager::GetConfigError::Unexpected(e)) => { panic!("{}", e) // TODO } } }, ); #[derive(Deserialize)] struct DomainPostRequest { _init: bool, domain: domain::Name, passphrase: String, } let domain_post = warp::post() .and(with_render_ctx.clone()) .and(warp::path!("domain.html")) .and(warp::query::()) .and(warp::query::()) .map( |render_ctx: RenderContext<'_, DM>, req: DomainPostRequest, domain_config: util::ConfigFromURL| { if req.passphrase != render_ctx.passphrase.as_str() { panic!("TODO") } //if req.init { #[derive(Serialize)] struct Response { domain: domain::Name, config: domain::config::Config, target_cname: domain::Name, challenge_token: String, } let config: Option = domain_config.try_into().expect("TODO"); let config = config.expect("TODO"); let config_hash = config.hash().expect("TODO"); let target_cname = (*render_ctx.target_cname).clone(); return render_page( render_ctx, String::from("/domain_post_init.html"), Response { domain: req.domain, config: config, target_cname: target_cname, challenge_token: config_hash, }, ); //} }, ); Ok(static_dir.or(index).or(domain_get).or(domain_post)) }