
InviteRow now has some utilities built into it to help with translating values from various types needed throughout the codebase +from_tuple -> because mysql is used to grab tuples before structs +as_json_str -> because we respond primarily with json payloads list_channels is considered a main entry point from the dispather and thus handles errors itself
109 lines
3.4 KiB
Rust
109 lines
3.4 KiB
Rust
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<Body>, 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<Body>) -> Result<Response<Body>, 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);
|
|
}
|
|
}
|