renamed server/ to api/ since this is really only the api portion of the typical fc server

This commit is contained in:
shockrah
2020-08-22 15:52:37 -07:00
parent afcc03959a
commit 0822be3d20
35 changed files with 0 additions and 0 deletions

71
server-api/src/auth.rs Normal file
View File

@@ -0,0 +1,71 @@
use mysql_async::Pool;
use mysql_async::prelude::{params, Queryable};
use crate::db_types::{UBigInt, VarChar};
use crate::routes;
pub const BCRYPT_COST: u32 = 14;
pub enum AuthReason {
Good, //passed regular check
OpenAuth, // route does not require auth
NoKey,
}
fn open_route(path: &str) -> bool {
return path == routes::INVITE_JOIN
}
pub async fn wall_entry(path: &str, pool: &Pool, params: &mut serde_json::Value) -> Result<AuthReason, mysql_async::error::Error> {
use serde_json::json;
// Start by Checking if the api key is in our keystore
if open_route(path) {
Ok(AuthReason::OpenAuth)
}
else {
if let Some(key) = params.get("secret") {
let key_str = key.as_str();
let conn = pool.get_conn().await?;
// (id, name, secret)
type RowType = Option<(UBigInt, VarChar)>;
let db_result: Result<(_, RowType), mysql_async::error::Error> = conn
.first_exec(r"SELECT id, name FROM members WHERE secret = :secret ", mysql_async::params!{ "secret" => key_str})
.await;
match db_result {
Ok((_, row)) => {
match row{
Some(user_row) => {
params["userid"] = json!(user_row.0);
params["username"] = json!(user_row.1);
Ok(AuthReason::Good)
},
None => Ok(AuthReason::NoKey)
}
}
Err(e) => {
println!("\tIssue fetching auth data {:?}", e);
Ok(AuthReason::NoKey)
}
}
//let (_con, row): (_, Option<(UBigInt, VarChar)>) = conn
// .first_exec(r"SELECT userid, name FROM keys WHERE secret = :secret ", mysql_async::params!{ "secret" => key})
// .await;
}
else {
Ok(AuthReason::NoKey)
}
}
}
pub fn generate_secret() -> String {
/*
* Generates a url-safe-plaintext secret for our db
* */
use getrandom::getrandom;
use base64::{encode_config, URL_SAFE};
let mut buf: Vec<u8> = vec![0;64];
getrandom(&mut buf);
encode_config(buf,URL_SAFE)
}

219
server-api/src/channels.rs Normal file
View File

@@ -0,0 +1,219 @@
use std::borrow::Cow;
use hyper::{StatusCode, Response, Body};
use hyper::header::HeaderValue;
use mysql_async::{Conn, Pool};
use mysql_async::error::Error;
use mysql_async::prelude::{params, Queryable};
use serde_json::Value;
use crate::db_types::{UBigInt, VarChar, Integer};
#[derive(Debug)]
pub enum ChannelType {
Voice,
Text,
Undefined
}
impl ChannelType {
// These funcs are mainly here to help translation from mysql
pub fn from_i32(x: i32) -> ChannelType {
match x {
1 => ChannelType::Voice,
2 => ChannelType::Text,
_ => ChannelType::Undefined
}
}
pub fn as_i32(&self) -> i32 {
match self {
ChannelType::Voice => 1,
ChannelType::Text => 2,
ChannelType::Undefined => 3,
}
}
// whole ass function exists because serde_json is a walking pos
pub fn from_i64_opt(x: Option<i64>) -> ChannelType {
if let Some(i) = x {
match i {
1 => ChannelType::Voice,
2 => ChannelType::Text,
_ => ChannelType::Undefined
}
}
else {
ChannelType::Undefined
}
}
}
// Primary way of interpretting sql data on our channels table
pub struct Channel {
id: u64,
name: String,
description: String,
kind: ChannelType
}
#[derive(Debug)]
struct InsertableChannel {
name: String,
kind: ChannelType
}
impl Channel {
/*
* When our sql library queries things we generally get back tuples rather reasily
* we can use this method to get something that makes more sense
*/
fn from_tup(tup: (UBigInt, VarChar, Option<VarChar>, Integer)) -> Channel {
let desc = match tup.2 {
Some(val) => val,
None => "None".into()
};
Channel {
id: tup.0,
name: tup.1,
description: desc,
kind: ChannelType::from_i32(tup.3)
}
}
/*
* When responding with some channel data to the client we use json
* this itemizes a single struct as the following(without the pretty output)
* {
* "id": id<i32>,
* "name": "<some name here>",
* "description": Option<"<description here>">,
* "kind": kind<i32>
* }
*/
fn as_json_str(&self) -> String {
let mut base = String::from("{");
base.push_str(&format!("\"id\":{},", self.id));
base.push_str(&format!("\"name\":\"{}\",", self.name));
base.push_str(&format!("\"description\":\"{}\",", self.description));
base.push_str(&format!("\"kind\":{}}}", self.kind.as_i32()));
return base;
}
}
/*
* Forwarding SQL errors as we can handle those error in caller site for this leaf function
* On success we back a Vec<Channel> which we can JSON'ify later and use as a response
*/
async fn get_channels_vec(conn: Conn) -> Result<Vec<Channel>, Error> {
let rows_db = conn.prep_exec(r"SELECT * FROM channels", ()).await?;
let (_, rows) = rows_db.map_and_drop(|row| {
let (id, name, desc, kind): (UBigInt, VarChar, Option<VarChar>, Integer) = mysql_async::from_row(row);
Channel::from_tup((id, name, desc, kind))
}).await?;
Ok(rows)
}
pub async fn list_channels(pool: &Pool, response: &mut Response<Body>) {
/*
* Primary dispatcher for dealing with the CHANNELS_LIST route
* For the most part this function will have a lot more error handling as it
* should know what kind of issues its child functions will have
*/
if let Ok(conn) = pool.get_conn().await {
match get_channels_vec(conn).await {
Ok(chans) => {
*response.status_mut() = StatusCode::OK;
response.headers_mut().insert("Content-Type",
HeaderValue::from_static("application/json"));
// At this point we build the content of our response body
// which is a json payload hence why there is weird string manipulation
// because we're trying to avoid dependancy issues and serializing things ourselves
let mut new_body = String::from("{\"channels\":[");
for chan in chans.iter() {
let s = format!("{},", chan.as_json_str());
new_body.push_str(&s);
}
if new_body.ends_with(',') {new_body.pop();}
new_body.push_str("]}");
*response.body_mut() = Body::from(new_body);
},
Err(_) => {
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
}
}
}
else {
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
}
}
async fn insert_channel(pool: &Pool, name: &str, desc: &str, kind: i64) -> Result<(), Error>{
let conn = pool.get_conn().await?;
conn.prep_exec(
"INSERT INTO channels (name, description, kind) VALUES (:name, :description, :kind)",
params!{"name" => name, "kind" => kind, "description" => desc}).await?;
Ok(())
}
pub async fn create_channel(pool: &Pool, response: &mut Response<Body>, params: Value) {
/*
* Create a channel base on a few parameters that may or may not be there
*/
// Theres an extra un-needed unwrap to be cut out from this proc
// specifically with the desc parameter
let req_params: (Option<&str>, Option<&str>, Option<i64>) =
match (params.get("name"), params.get("description"), params.get("kind")) {
(Some(name), Some(desc), Some(kind)) => (name.as_str(), desc.as_str(), kind.as_i64()),
(Some(name), None, Some(kind)) => (name.as_str(), Some("No Description"), kind.as_i64()),
_ => (None, None, None)
};
match req_params {
(Some(name), Some(desc), Some(kind)) => {
match insert_channel(pool, name, desc, kind).await {
// Server Errors are generally _ok_ to reveal in body I suppose
Err(Error::Server(se)) => {
*response.status_mut() = StatusCode::BAD_REQUEST;
let b = format!("Server code: {}\nServer Message:{}", se.code, se.message);
*response.body_mut() = Body::from(b);
},
// generic errors get a 500
Err(_) => {
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
}
// Nothing to do when things go right
_ => {}
}
},
// basically one of the parameter gets failed so we bail on all of this
_ => *response.status_mut() = StatusCode::BAD_REQUEST
}
}
async fn db_delete_channel(pool: &Pool, name: &Value) -> Result<(), Error> {
let conn = pool.get_conn().await?;
conn.prep_exec(r"DELETE FROM channels WHERE name = :name", params!{"name" => name.as_str().unwrap()}).await?;
Ok(())
}
pub async fn delete_channel(pool: &Pool, response: &mut Response<Body>, params: Value) {
// make sure we have the right parameters provided
if let Some(name) = params.get("name") {
match db_delete_channel(pool, name).await {
Ok(_) => *response.status_mut() = StatusCode::OK,
Err(e) => {
println!("delete_chanel sql error :\n{}", e);
}
}
}
else {
*response.status_mut() = StatusCode::BAD_REQUEST;
}
}

View File

@@ -0,0 +1,13 @@
pub type Integer = i32;
pub type UInteger = u32;
pub type UBigInt = u64;
pub type BigInt = i64;
pub type VarChar = String;
pub enum DbError {
BadParam(&'static str),
Connection(&'static str),
Internal
}

View File

@@ -0,0 +1,16 @@
use serde_json::{self, Value};
use hyper::body::to_bytes;
use hyper::Body;
use std::u8;
pub async fn parse_params(body_raw: &mut Body) -> Result<Value, serde_json::error::Error> {
let bytes: &[u8] = &*to_bytes(body_raw).await.unwrap(); // rarely fails
let values: Value;
if bytes.len() == 0 {
values = serde_json::from_str("{}")?;
}
else {
values = serde_json::from_slice(bytes)?;
}
Ok(values)
}

123
server-api/src/invites.rs Normal file
View File

@@ -0,0 +1,123 @@
use serde_json::Value;
use mysql_async;
use mysql_async::{Conn, Pool};
use mysql_async::error::Error;
use mysql_async::prelude::{params, Queryable};
use hyper::{Response, Body, StatusCode};
use chrono::Utc;
use rand::random;
struct InviteRow {
id: u64,
expires: u64,
uses: i32,
}
/*
* Error handling:
* All errors raisable from this module come from mysql_async and thus
* are of the enum mysql_async::error::Error
*/
impl InviteRow {
pub fn new() -> InviteRow {
let dt = Utc::now() + chrono::Duration::minutes(30);
// TODO:[maybe] ensure no collisions by doing a quick database check here
let invite = InviteRow {
id: random::<u64>(), // hopefully there won't ever be collision with this size of pool
uses: 1, // default/hardcorded for now
expires: dt.timestamp() as u64
};
invite
}
pub fn from_tuple(tup: (u64, u64, i32)) -> InviteRow {
InviteRow {
id: tup.0,
expires: tup.1,
uses: tup.2,
}
}
pub fn as_json_str(&self) -> String {
let id = format!("\"id\":{}", self.id);
let expires = format!("\"expires\":{}", self.expires);
let uses = format!("\"uses\":{}", self.uses);
let mut data = String::from("{");
data.push_str(&format!("{},", id));
data.push_str(&format!("{},", expires));
data.push_str(&format!("{}}}", uses));
data
}
}
async fn get_invite_by_code(pool: &Pool, value: Option<&str>) -> Result<Option<InviteRow>, Error> {
if let Some(val) = value {
let conn = pool.get_conn().await?;
let db_row_result: (Conn, Option<(u64, u64, i32)>) = conn
.first_exec(r"SELECT * FROM", mysql_async::params!{"code"=>val})
.await?;
if let Some(tup) = db_row_result.1 {
Ok(Some(InviteRow::from_tuple(tup)))
}
else {
// basically nothing was found but nothing bad happened
Ok(None)
}
}
// again db didn't throw a fit but we don't have a good input
else {Ok(None)}
}
async fn record_invite_usage(pool: &Pool, data: &InviteRow) -> Result<(), Error>{
/*
* By this this is called we really don't care about what happens as we've
* already been querying the db and the likely hood of this seriously failing
* is low enough to write a wall of text and not a wall of error handling code
*/
let conn = pool.get_conn().await?;
let _db_result = conn
.prep_exec(r"UPDATE invites SET uses = :uses WHERE id = :id", mysql_async::params!{
"uses" => data.uses - 1,
"id" => data.id
}).await?;
Ok(())
}
pub async fn route_join_invite_code(pool: &Pool, response: &mut Response<Body>, params: Value) -> Result<(), Error> {
// First check that the code is there
if let Some(code) = params.get("code") {
if let Some(row) = get_invite_by_code(pool, code.as_str()).await? {
// since we have a row make sure the invite is valid
let now = Utc::now().timestamp() as u64;
// usable and expires in the future
if row.uses > 0 && row.expires > now {
record_invite_usage(pool, &row).await?;
// TODO: assign some actual data to the body
*response.status_mut() = StatusCode::OK;
}
}
}
else{
*response.status_mut() = StatusCode::BAD_REQUEST;
}
Ok(())
}
pub async fn create_invite(pool: &Pool, response: &mut Response<Body>) -> Result<(), Error> {
let invite = InviteRow::new();
let conn = pool.get_conn().await?;
conn.prep_exec(r"INSERT INTO invites (id, expires, uses) VALUES (:id, :expires, :uses",
mysql_async::params!{
"id" => invite.id,
"expires" => invite.expires,
"uses" => invite.uses,
}).await?;
*response.body_mut() = Body::from(invite.as_json_str());
*response.status_mut() = StatusCode::OK;
Ok(())
}

225
server-api/src/main.rs Normal file
View 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(())
}
}

98
server-api/src/members.rs Normal file
View File

@@ -0,0 +1,98 @@
use chrono::Utc;
use hyper::{Body, Response, StatusCode};
use hyper::header::{HeaderName, HeaderValue};
use mysql_async::{Conn, Pool, error::Error as MySqlError};
use mysql_async::prelude::{params, Queryable};
use serde_json::Value;
use serde::Serialize;
use crate::db_types::{UBigInt, BigInt, Integer, VarChar};
#[derive(Serialize)]
pub struct Member {
pub id: UBigInt,
pub secret: VarChar,
pub name: VarChar,
pub joindate: BigInt,
pub status: Integer,
pub permissions: UBigInt,
}
struct InsertableMember<'n> {
name: &'n str,
permissions: u64,
}
impl<'n> InsertableMember<'n> {
fn new(name: &'n str) -> InsertableMember<'n> {
use crate::perms::{JOIN_VOICE, SEND_MESSAGES};
let now: BigInt = Utc::now().timestamp_millis();
let default_perms = JOIN_VOICE | SEND_MESSAGES;
InsertableMember {
name: name,
permissions: default_perms,
}
}
}
pub async fn insert_new_member(p: &Pool, name: VarChar, perms: u64) -> Result<Member, MySqlError> {
use crate::auth::generate_secret;
let conn: Conn = p.get_conn().await?;
let secret: String = generate_secret();
let now: BigInt = Utc::now().timestamp();
let conn = conn.drop_exec(
"INSERT INTO members(secret, name, joindate, status, permissions)
VALUES(:secret, :name, :joindate, :status, :permissions)",
mysql_async::params!{
"secret" => secret.clone(),
"name" => name.clone(),
"joindate" => now,
"status" => 0,
"permissions" => perms
}).await?;
// now pull back the user from our db and return that row
let db_row_result: (Conn, Option<UBigInt>) = conn.first_exec(
"SELECT id FROM members WHERE secret = :secret",
params!{
"secret" => secret.clone()
}).await?;
Ok(Member {
id: db_row_result.1.unwrap(), // if we made it this far this shouldn't fail (i hope)
secret: secret,
name: name,
joindate: now,
status: 0,
permissions: perms
})
}
async fn general_new_user(p: &Pool, resp: &mut Response<Body>, params: Value) {
/*
* @name: string => desired default name
*/
use crate::perms;
let default_name = serde_json::json!("NewUser");
let name = params.get("name")
.unwrap_or(&default_name)
.as_str().unwrap_or("NewUser");
let pre_mem = InsertableMember::new(name);
match insert_new_member(p, name.to_string(), perms::GENERAL_NEW).await {
Ok(new_member) => {
*resp.status_mut() = StatusCode::OK;
let json_hdr_name = HeaderName::from_static("Content-Type");
let json_hdr_val = HeaderValue::from_static("application/json");
resp.headers_mut().insert(json_hdr_name, json_hdr_val);
*resp.body_mut() = Body::from(serde_json::to_string(&new_member).unwrap());
},
Err(_) => {
*resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
*resp.body_mut() = Body::from("Could not process input");
}
}
}

View File

@@ -0,0 +1,77 @@
use std::borrow::Cow;
use mysql_async::{Pool, params};
use mysql_async::prelude::{Queryable};
use mysql_async::error::Error;
use hyper::{Response, Body, StatusCode};
use serde_json::Value;
use chrono::Utc;
use crate::db_types::{UBigInt};
pub async fn insert_message(pool: &Pool, content: &Value, channel_name: &Value, author_id: UBigInt)
-> Result<(), Error>{
match (content.as_str(), channel_name.as_str()) {
(Some(content), Some(channel)) => {
let conn = pool.get_conn().await?;
let time = Utc::now().timestamp();
conn.prep_exec(
r"INSERT INTO messages
(time, content, author_id, channel_name)
VALUES(:time, :content, :author, :channel)",
params!{
"time" => time,
"content" => content,
"author" => author_id,
"channel" => channel
}).await?;
Ok(())
}
_ => {
let e = Cow::from("Required parameter missing");
Err(Error::Other(e))
}
}
}
pub async fn send_message(pool: &Pool, response: &mut Response<Body>, params: Value) {
/*
* @content: expecting string type
* @id: expecting the channel id that we're posting data to
*/
let content_r = params.get("content");
let channel_name_r = params.get("channel");
// auth module guarantees this will be there in the correct form
let author = params.get("userid")
.unwrap().as_u64().unwrap();
match (content_r, channel_name_r) {
(Some(content), Some(channel_name)) => {
match insert_message(pool, content, channel_name, author).await {
Ok(_) => *response.status_mut() = StatusCode::OK,
Err(err) => {
use mysql_async::error::Error::{Server};
println!("\tDB Error::send_message: {:?}", err);
// doing this to avoid client confusion as some input does cause sql errors
if let Server(se) = err {
if se.code == 1452 {
*response.status_mut() = StatusCode::BAD_REQUEST;
*response.body_mut() = Body::from(format!("{} does not exist", channel_name));
}
else {
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
}
}
else {
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
}
}
}
},
_ => {
*response.status_mut() = StatusCode::BAD_REQUEST;
*response.body_mut() = Body::from("content/channel missing from json parameters");
}
}
}

4
server-api/src/perms.rs Normal file
View File

@@ -0,0 +1,4 @@
pub const JOIN_VOICE:u64 = 1;
pub const SEND_MESSAGES:u64 = 2;
pub const GENERAL_NEW: u64 = JOIN_VOICE | SEND_MESSAGES;

39
server-api/src/routes.rs Normal file
View File

@@ -0,0 +1,39 @@
pub const INVITE_JOIN: &'static str = "/invite/join"; // requires @code
pub const INVITE_CREATE: &'static str = "/invite/create"; // requires none
pub const CHANNELS_LIST: &'static str = "/channels/list"; // requires none
pub const CHANNELS_CREATE: &'static str = "/channels/create"; // requires @name @kind
pub const CHANNELS_DELETE: &'static str = "/channels/delete"; // requires @name
pub const MESSAGE_SEND: &'static str = "/message/send"; // requires @content
const DYNAMIC_ROUTE_BASES: [&'static str;1] = [
"/invites",
];
pub struct DynRoute {
pub base: String,
pub dynamic: String,
}
pub fn resolve_dynamic_route(uri: &str) -> Option<DynRoute> {
let mut valid = false;
let mut base_ref = "";
for base in DYNAMIC_ROUTE_BASES.iter() {
if uri.starts_with(base) {
valid = true;
base_ref = base;
break;
}
}
if valid {
Some(DynRoute {
base: base_ref.into(),
dynamic: uri.to_string().replace(base_ref, "")
})
}
else {
None
}
}