extern crate chrono; extern crate dotenv; extern crate getrandom; extern crate base64; use std::net::SocketAddr; use std::convert::Infallible; // our main dispatcher basically never fails hence why we use this use std::env; use std::collections::HashMap; use tokio; use hyper::{ self, Server, Response, Request, Body, Method, StatusCode, service::{make_service_fn, service_fn} }; use mysql_async::Pool; use dotenv::dotenv; mod auth; use auth::AuthReason; mod routes; mod invites; mod sql_traits; mod channels; fn map_qs(query_string_raw: Option<&str>) -> HashMap<&str, &str> { /* * Parse a given query string and build a hashmap from it */ let mut map: HashMap<&str, &str> = HashMap::new(); if let Some(qs) = query_string_raw { let raw_pairs: Vec<&str> = qs.split('&').collect(); for raw_pair in raw_pairs.iter() { let sub_segment: Vec<&str> = raw_pair.split('=').collect(); match sub_segment.len() { 2 => map.insert(sub_segment[0], sub_segment[1]), _ => continue }; } } map } async fn route_dispatcher(pool: &Pool, resp: &mut Response, meth: &Method, path: &str, params: &HashMap<&str, &str>) { // At some point we should have some way of hiding this obnoxious complexity match (meth, path) { (&Method::GET, routes::INVITE_JOIN) => { if let Err(_) = invites::join_invite_code(pool, resp, params).await { *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; } }, (&Method::GET, routes::INVITE_CREATE) => { if let Err(_) = invites::create_invite(pool, resp).await { *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; } }, (&Method::GET, routes::CHANNELS_LIST) => channels::list_channels(pool, resp).await, _ => *resp.status_mut() = StatusCode::NOT_FOUND } } async fn main_responder(request: Request) -> Result, hyper::Error>{ use AuthReason::*; let mut response = Response::new(Body::empty()); let method = request.method(); let path = request.uri().path(); let params = map_qs(request.uri().query()); let pool = Pool::new(&env::var("DATABASE_URL").unwrap()); // some more information in the response would be great right about here if let Ok(auth_result) = auth::wall_entry(path, &pool, ¶ms).await { match auth_result { OpenAuth | Good => route_dispatcher(&pool, &mut response, &method, path, ¶ms).await, LimitPassed => *response.status_mut() = StatusCode::UNAUTHORIZED, NoKey => *response.status_mut() = StatusCode::UNAUTHORIZED, } } else { *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; } Ok(response) } async fn shutdown_signal() { tokio::signal::ctrl_c() .await .expect("Failed to capture ctrl-c signal"); } #[tokio::main] async fn main() { dotenv().ok(); println!("Servering on localhost:8888"); let addr = SocketAddr::from(([127,0,0,1], 8888)); let service = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(main_responder)) }); let server = Server::bind(&addr).serve(service); let graceful_shutdown = server.with_graceful_shutdown(shutdown_signal()); if let Err(e) = graceful_shutdown.await { eprintln!("Server shutdown error: {}", e); } }