diff --git a/server/src/channels.rs b/server/src/channels.rs index 35c6fba..41268fe 100644 --- a/server/src/channels.rs +++ b/server/src/channels.rs @@ -1,170 +1,98 @@ -/* - With this module we take care of getting all kinds of channel information out to the users - Base prefix is "/channels" -*/ -use diesel::{self, prelude::*}; -use rocket_contrib::json::Json; -use std::vec::Vec; -use crate::models::{InsertableChannel, Channel}; -use crate::auth::AuthResult; -use crate::{DBConn, schema, payload}; -use crate::err::{self, DbError}; +use hyper::{StatusCode, Response, Body}; +use hyper::header::HeaderValue; +use mysql_async::{Conn, Pool}; +use mysql_async::error::Error; +use mysql_async::prelude::Queryable; -const VOICE_CHANNEL: i32 = 1; -const TEXT_CHANNEL: i32 = 2; +use crate::sql_traits::DBTrait; -#[post("/create////")] -pub fn insert_new_channel(qname: String, perms: i32, lim: i32, ctype: i32, conn: DBConn) -> - DbError, err::DbResponse> { - use schema::channels::dsl::*; +enum ChannelType { + Voice, + Text, + Undefined +} - let chan = InsertableChannel { - name: qname, - permissions: perms, - limit: lim, - type_: ctype, - }; - let result = diesel::insert_into(channels) - .values(&chan) - .execute(&conn.0); +impl ChannelType { + fn from_i32(x: i32) -> ChannelType { + match x { + 1 => ChannelType::Voice, + 2 => ChannelType::Text, + _ => ChannelType::Undefined + } + } + fn as_i32(&self) -> i32 { + match self { + ChannelType::Voice => 1, + ChannelType::Text => 2, + ChannelType::Undefined => 3, - match result { - Ok(_d) => Ok(Json(payload::ChannelIndexed{name:chan.name, ctype:chan.type_})), - Err(_e) => Err(err::DbResponse{err_msg:"Could not create channel"}) + } } } -pub fn get_channel_by_type(conn: &MysqlConnection, ctype: i32/*short for channel type*/) -> Result, err::DbResponse> { - use schema::channels::dsl::*; - if let Ok(result) = channels.filter(type_.eq(ctype)).load(conn) { - Ok(result) - } - else { - Err(err::DbResponse{err_msg: "/channeels/list/ Could not query database"}) - } - +struct Channel { + id: i32, + name: String, + description: String, + kind: ChannelType } -#[get("/list/voice")] -pub fn get_voice_channels(conn: DBConn) -> AuthResult>, err::DbResponse> { - let db_result = get_channel_by_type(&conn.0, VOICE_CHANNEL); - // We have to rewrap the data given to us which is annoying but hey - // at least its safe.jpg - if let Ok(data) = db_result { - Ok(Json(data)) +impl DBTrait for Channel { + fn from(tup: (i32, String, String, i32)) -> Channel { + Channel { + id: tup.0, + name: tup.1, + description: tup.2, + kind: ChannelType::from_i32(tup.3) + } } - else { - Err(err::DbResponse{err_msg:"Could not query voice channels"}) + 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; } } -#[get("/list/text")] -pub fn get_text_chanels(conn: DBConn) -> DbError>, err::DbResponse> { - let db_result = get_channel_by_type(&conn.0, TEXT_CHANNEL); - if let Ok(data) = db_result { - Ok(Json(data)) - } - else { - Err(err::DbResponse{err_msg:"Could not query text channels"}) - } +async fn get_channels_vec(conn: Conn) -> Result, 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): (i32, String, String, i32) = mysql_async::from_row(row); + Channel { + id: id, + name: name, + description: desc, + kind: ChannelType::from_i32(kind) + } + }).await?; + + Ok(rows) } -#[cfg(test)] -mod channel_tests { - use super::*; - use rocket; - use rocket::local::Client; - use rocket::http::Status; - use serde_json::from_str; +pub async fn list_channels(pool: &Pool, response: &mut Response) { + 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")); + + 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("]}"); - macro_rules! rocket_inst { - ($func:expr) => { - rocket::ignite() - .mount("/channels", routes![$func]) - .attach(DBConn::fairing()) + *response.body_mut() = Body::from(new_body); + }, + Err(_) => { + *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + } } } - - #[test] - fn generate_channels_test() { - use diesel::mysql::MysqlConnection; - use schema::channels::dsl::*; - let conn = MysqlConnection::establish("mysql://freechat_dev:password@localhost:3306/freechat") - .unwrap(); - - let app = rocket_inst!(insert_new_channel); - let client = Client::new(app).expect("bad rocket instance"); - - let text_names = vec!["tone", "ttwo", "tthre"]; - for i in text_names.iter() { - let uri = format!("/channels/create/{}/{}/{}/{}", i, 0, 0, TEXT_CHANNEL); - let mut response = client.post(uri).dispatch(); - - assert_eq!(response.status(), Status::Ok); - - let body = response.body_string().unwrap(); - let data: crate::payload::ChannelIndexed = from_str(&body).unwrap(); - assert_eq!(data.name, i.to_string()); - } - - let voice_names = vec!["voiceone", "voicetwo", "voicethre"]; - for i in voice_names.iter() { - let uri = format!("/channels/create/{}/{}/{}/{}", i, 0, 0, VOICE_CHANNEL); - let mut response = client.post(uri).dispatch(); - - assert_eq!(response.status(), Status::Ok); - - let body = response.body_string().unwrap(); - let data: crate::payload::ChannelIndexed = from_str(&body).unwrap(); - assert_eq!(data.name, i.to_string()); - } - let _ = diesel::delete(channels).filter(id.is_not_null()) - .execute(&conn) - .expect("bro what the hell delete * from channels where id is not null;"); - } - - fn generate_dummy_channels() { - let app = rocket_inst!(insert_new_channel); - let client = Client::new(app).expect("bad rocket instance"); - - let text_names = vec!["tone", "ttwo", "tthre"]; - for i in text_names.iter() { - let uri = format!("/channels/create/{}/{}/{}/{}", i, 0, 0, TEXT_CHANNEL); - let mut response = client.post(uri).dispatch(); - - assert_eq!(response.status(), Status::Ok); - - let body = response.body_string().unwrap(); - let data: crate::payload::ChannelIndexed = from_str(&body).unwrap(); - assert_eq!(data.name, i.to_string()); - } - - let voice_names = vec!["voiceone", "voicetwo", "voicethre"]; - for i in voice_names.iter() { - let uri = format!("/channels/create/{}/{}/{}/{}", i, 0, 0, VOICE_CHANNEL); - let mut response = client.post(uri).dispatch(); - - assert_eq!(response.status(), Status::Ok); - - let body = response.body_string().unwrap(); - let data: crate::payload::ChannelIndexed = from_str(&body).unwrap(); - assert_eq!(data.name, i.to_string()); - } - } - - #[test] - fn test_text_channels_get() { - generate_dummy_channels(); - let app = rocket_inst!(get_voice_channels); - - let client = Client::new(app).expect("bad rocket instance"); - let mut response = client.get("/channels/text").dispatch(); - let _body: String = response.body_string().unwrap(); - - assert_eq!(response.status(), Status::Ok); - assert_ne!(response.status(), Status::NotFound); - - println!("succ me") - } -} +} \ No newline at end of file diff --git a/server/src/main.rs b/server/src/main.rs index 2e91f49..f8dec17 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -24,6 +24,8 @@ use auth::AuthReason; mod routes; mod invites; +mod sql_traits; +mod channels; fn map_qs(query_string_raw: Option<&str>) -> HashMap<&str, &str> { /* @@ -44,6 +46,7 @@ fn map_qs(query_string_raw: Option<&str>) -> HashMap<&str, &str> { } 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 { @@ -55,9 +58,8 @@ async fn route_dispatcher(pool: &Pool, resp: &mut Response, meth: &Method, *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; } }, - _ => { - *resp.status_mut() = StatusCode::NOT_FOUND; - } + (&Method::GET, routes::CHANNELS_LIST) => channels::list_channels(pool, resp).await, + _ => *resp.status_mut() = StatusCode::NOT_FOUND } } diff --git a/server/src/routes.rs b/server/src/routes.rs index 9a3c431..0158a15 100644 --- a/server/src/routes.rs +++ b/server/src/routes.rs @@ -1,2 +1,4 @@ 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 \ No newline at end of file diff --git a/server/src/sql_traits.rs b/server/src/sql_traits.rs new file mode 100644 index 0000000..e2dc4e7 --- /dev/null +++ b/server/src/sql_traits.rs @@ -0,0 +1,8 @@ +// Module with some basic traits we supply for things that go in + out of +// mysql_async + + +pub trait DBTrait { + fn from(db_tup: (i32, String, String, i32)) -> Self; + fn as_json_str(&self) -> String; +} \ No newline at end of file