diff --git a/server/src/main.rs b/server/src/main.rs
index e692829..ac9636f 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -1,6 +1,8 @@
extern crate chrono;
+extern crate clap;
extern crate dotenv;
extern crate getrandom;
+extern crate bcrypt;
extern crate base64;
use std::net::SocketAddr;
@@ -16,7 +18,11 @@ use hyper::{
service::{make_service_fn, service_fn}
};
use mysql_async::Pool;
+use mysql_async::prelude::Queryable;
+
use dotenv::dotenv;
+use clap::{Arg, App};
+use chrono::Utc;
mod auth;
use auth::AuthReason;
@@ -24,12 +30,19 @@ use auth::AuthReason;
mod routes;
mod invites;
mod channels;
+
mod members;
+use members::Member;
+
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
, 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;
@@ -109,11 +122,82 @@ async fn shutdown_signal() {
.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
+ }
+
+}
+
+// we're only marking this as async because
+fn parse_options(eflags: u16) -> (u16, Option) {
+ use std::env::set_var;
+ let mut potential_owner: Option = None;
+
+ 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 let Some(db_url) = args.value_of("db-url") {
+ set_var("DATABASE_URL", db_url);
+ }
+
+ if let Some(name) = args.value_of("create-owner") {
+ use getrandom::getrandom;
+
+ let mut raw: Vec = vec![0;64];
+ getrandom(&mut raw).unwrap();
+ let raw_key = base64::encode_config(raw, base64::URL_SAFE);
+ // this we can store in our db as a full string on its own
+ let secret = bcrypt::hash(raw_key, auth::BCRYPT_COST).unwrap();
+
+ potential_owner = Some(Member {
+ // id field is rather meaninglyless since the db takes care of this for us
+ id: 0,
+ secret: secret.to_string(),
+ name: name.to_string(),
+ joindate: Utc::now().timestamp(),
+ status: 0,
+ permissions: std::u64::MAX
+ });
+ }
+ return (eflags, potential_owner);
+}
+
+
+
#[tokio::main]
async fn main() -> Result<(), u16>{
- const NO_ERR: u16 = 0;
- const CONFIG_ERR: u16 = 1;
- const SHUTDOWN_ERR: u16 = 2;
let mut main_ret: u16 = 0;
// setting up environment variables
@@ -124,21 +208,30 @@ async fn main() -> Result<(), u16>{
main_ret |= CONFIG_ERR;
}
}
- if main_ret == NO_ERR {
- 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 {
- main_ret |= SHUTDOWN_ERR;
- eprintln!("Server shutdown error: {}", e);
+ let (opt_res, potential_owner) = parse_options(main_ret);
+ main_ret = opt_res; // pull out the return value from the option parsing stage
+ // create a new owner account if we need to
+ if let Some(owner) = potential_owner {
+ let p = Pool::new(&env::var("DATABASE_URL").unwrap());
+ // TODO: move this logic over to members.rs
+ if let Ok(conn) = p.get_conn().await {
+ let db_res = conn.prep_exec(
+ "INSERT INTO members (secret, name, joindate, status, permissions)
+ VALUES(:secret, :name, :joindate, :status, :permissions)",
+ mysql_async::params!{
+ "secret" => owner.secret,
+ "name" => owner.name,
+ "joindate" => owner.joindate,
+ "status" => owner.status,
+ "permissions" => owner.permissions
+ }).await;
}
}
+ 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}}");}