renamed server/ to api/ since this is really only the api portion of the typical fc server
This commit is contained in:
225
server-api/src/main.rs
Normal file
225
server-api/src/main.rs
Normal file
@@ -0,0 +1,225 @@
|
||||
extern crate chrono;
|
||||
extern crate clap;
|
||||
extern crate dotenv;
|
||||
extern crate getrandom;
|
||||
extern crate bcrypt;
|
||||
extern crate base64;
|
||||
extern crate serde;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::convert::Infallible; // our main dispatcher basically never fails hence why we use this
|
||||
use std::env::{self, set_var};
|
||||
|
||||
use tokio;
|
||||
use hyper::{
|
||||
self,
|
||||
Server,
|
||||
Response, Request, Body,
|
||||
Method, StatusCode,
|
||||
service::{make_service_fn, service_fn}
|
||||
};
|
||||
use mysql_async::Pool;
|
||||
|
||||
use dotenv::dotenv;
|
||||
use clap::{Arg, App};
|
||||
|
||||
mod auth;
|
||||
use auth::AuthReason;
|
||||
|
||||
mod routes;
|
||||
mod invites;
|
||||
mod channels;
|
||||
|
||||
mod members;
|
||||
|
||||
mod messages;
|
||||
mod http_params;
|
||||
mod perms;
|
||||
mod db_types;
|
||||
|
||||
const NO_ERR: u16 = 0;
|
||||
const CONFIG_ERR: u16 = 1;
|
||||
const SHUTDOWN_ERR: u16 = 2;
|
||||
|
||||
async fn route_dispatcher(pool: &Pool, resp: &mut Response<Body>, meth: &Method, path: &str, params: serde_json::Value) {
|
||||
// At some point we should have some way of hiding this obnoxious complexity
|
||||
use routes::resolve_dynamic_route;
|
||||
match (meth, path) {
|
||||
(&Method::GET, routes::INVITE_JOIN) => {
|
||||
if let Err(_) = invites::route_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,
|
||||
(&Method::POST, routes::CHANNELS_CREATE) => channels::create_channel(pool, resp, params).await,
|
||||
(&Method::POST, routes::CHANNELS_DELETE) => channels::delete_channel(pool, resp, params).await,
|
||||
|
||||
(&Method::POST, routes::MESSAGE_SEND) => messages::send_message(pool, resp, params).await,
|
||||
_ => {
|
||||
// We attempt dynamic routes as fallback for a few reasons
|
||||
// 1. theres less of these than there are the static routes
|
||||
// 2. because of the above and that this is wholly more expensive than static routse
|
||||
// we can justify putting in tons of branches since we're likely to:
|
||||
// far jump here, lose cache, and still be be network bound
|
||||
// Computatinoal bounds are really of no concern with this api since
|
||||
// we're not doing any heavy calculations at any point
|
||||
if let Some(route) = resolve_dynamic_route(path) {
|
||||
*resp.status_mut() = StatusCode::OK;
|
||||
println!("\tStatic part: {}", route.base);
|
||||
println!("\tDynamic part: {}", route.dynamic);
|
||||
}
|
||||
else {
|
||||
println!("\tNOT FOUND: {}: {}", meth, path);
|
||||
*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 (parts, mut body) = request.into_parts();
|
||||
let method = parts.method;
|
||||
let path = parts.uri.path();
|
||||
println!("{}: {}", method, path);
|
||||
|
||||
let params_res = http_params::parse_params(&mut body).await;
|
||||
|
||||
if let Ok(mut params) = params_res {
|
||||
let pool = Pool::new(&env::var("DATABASE_URL").unwrap());
|
||||
if let Ok(auth_result) = auth::wall_entry(path, &pool, &mut params).await {
|
||||
// Deal with permissions errors at this point
|
||||
match auth_result {
|
||||
OpenAuth | Good => route_dispatcher(&pool, &mut response, &method, path, params).await,
|
||||
NoKey => {
|
||||
println!("\tAUTH: NoKey/BadKey");
|
||||
*response.status_mut() = StatusCode::UNAUTHORIZED
|
||||
},
|
||||
}
|
||||
}
|
||||
else {
|
||||
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
else {
|
||||
println!("\tPARSER: Parameter parsing failed");
|
||||
*response.status_mut() = StatusCode::BAD_REQUEST;
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn shutdown_signal() {
|
||||
tokio::signal::ctrl_c()
|
||||
.await
|
||||
.expect("Failed to capture ctrl-c signal");
|
||||
}
|
||||
|
||||
async fn start_server(ecode: u16) -> u16 {
|
||||
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);
|
||||
return ecode | SHUTDOWN_ERR;
|
||||
}
|
||||
else {
|
||||
return ecode
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), u16>{
|
||||
|
||||
let mut main_ret: u16 = 0;
|
||||
let d_result = dotenv();
|
||||
|
||||
// check for a database_url before the override we get from the cmd line
|
||||
if let Err(_d) = d_result {
|
||||
if let Err(_e) = env::var("DATABASE_URL") {
|
||||
main_ret |= CONFIG_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
let args = App::new("Freechat Server")
|
||||
.version("0.1")
|
||||
.author("shockrah")
|
||||
.about("Decentralized chat system")
|
||||
.arg(Arg::with_name("db-url")
|
||||
.short("d")
|
||||
.long("db-url")
|
||||
.value_name("DATABASE URL")
|
||||
.help("Sets the DATABASE URL via an environment variable")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("create-owner")
|
||||
.short("c")
|
||||
.long("create-owner")
|
||||
.value_name("Owner")
|
||||
.help("Creates an account with full permissions in the SQL database."))
|
||||
.arg(Arg::with_name("server")
|
||||
.short("s")
|
||||
.long("server")
|
||||
.help("Starts the API server"))
|
||||
.get_matches();
|
||||
|
||||
|
||||
if args.args.len() == 0 {
|
||||
println!("Freechat Server 0.1
|
||||
shockrah
|
||||
Decentralized chat system
|
||||
|
||||
USAGE:
|
||||
freechat-server [FLAGS] [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-s, --server Starts the API server
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-c, --create-owner <Owner> Creates an account with full permissions in the SQL database.
|
||||
-d, --db-url <DATABASE URL> Sets the DATABASE URL via an environment variable");
|
||||
}
|
||||
|
||||
if let Some(db_url) = args.value_of("db-url") {
|
||||
set_var("DATABASE_URL", db_url);
|
||||
}
|
||||
|
||||
if let Some(owner_name) = args.value_of("create-owner") {
|
||||
let p = Pool::new(&env::var("DATABASE_URL").unwrap());
|
||||
println!("Creating owner {{ {} }}...", owner_name);
|
||||
if let Ok(owner) = members::insert_new_member(&p, owner_name.to_string(), std::u64::MAX).await {
|
||||
println!("{}", serde_json::to_string(&owner).unwrap());
|
||||
}
|
||||
p.disconnect();
|
||||
}
|
||||
|
||||
if args.is_present("server") {
|
||||
if main_ret == NO_ERR {
|
||||
main_ret = start_server(main_ret).await;
|
||||
}
|
||||
}
|
||||
|
||||
if main_ret != 0 {
|
||||
// dumb as heck loggin method here
|
||||
if main_ret & CONFIG_ERR != 0 {println!("ERROR: Config was not setup properly => Missing {{DATABASE_URL}}");}
|
||||
if main_ret & SHUTDOWN_ERR != 0 {println!("ERROR: Couldn't shutdown gracefully");}
|
||||
Err(main_ret)
|
||||
}
|
||||
else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user