Helper functionality
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
This commit is contained in:
parent
528d7502d1
commit
08e5b87ba4
@ -1,170 +1,98 @@
|
|||||||
/*
|
use hyper::{StatusCode, Response, Body};
|
||||||
With this module we take care of getting all kinds of channel information out to the users
|
use hyper::header::HeaderValue;
|
||||||
Base prefix is "/channels"
|
use mysql_async::{Conn, Pool};
|
||||||
*/
|
use mysql_async::error::Error;
|
||||||
use diesel::{self, prelude::*};
|
use mysql_async::prelude::Queryable;
|
||||||
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};
|
|
||||||
|
|
||||||
const VOICE_CHANNEL: i32 = 1;
|
use crate::sql_traits::DBTrait;
|
||||||
const TEXT_CHANNEL: i32 = 2;
|
|
||||||
|
|
||||||
#[post("/create/<qname>/<perms>/<lim>/<ctype>")]
|
enum ChannelType {
|
||||||
pub fn insert_new_channel(qname: String, perms: i32, lim: i32, ctype: i32, conn: DBConn) ->
|
Voice,
|
||||||
DbError<Json<payload::ChannelIndexed>, err::DbResponse> {
|
Text,
|
||||||
use schema::channels::dsl::*;
|
Undefined
|
||||||
|
}
|
||||||
|
|
||||||
let chan = InsertableChannel {
|
impl ChannelType {
|
||||||
name: qname,
|
fn from_i32(x: i32) -> ChannelType {
|
||||||
permissions: perms,
|
match x {
|
||||||
limit: lim,
|
1 => ChannelType::Voice,
|
||||||
type_: ctype,
|
2 => ChannelType::Text,
|
||||||
};
|
_ => ChannelType::Undefined
|
||||||
let result = diesel::insert_into(channels)
|
}
|
||||||
.values(&chan)
|
}
|
||||||
.execute(&conn.0);
|
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<Vec<Channel>, err::DbResponse> {
|
struct Channel {
|
||||||
use schema::channels::dsl::*;
|
id: i32,
|
||||||
if let Ok(result) = channels.filter(type_.eq(ctype)).load(conn) {
|
name: String,
|
||||||
Ok(result)
|
description: String,
|
||||||
}
|
kind: ChannelType
|
||||||
else {
|
|
||||||
Err(err::DbResponse{err_msg: "/channeels/list/<type> Could not query database"})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/list/voice")]
|
impl DBTrait for Channel {
|
||||||
pub fn get_voice_channels(conn: DBConn) -> AuthResult<Json<Vec<Channel>>, err::DbResponse> {
|
fn from(tup: (i32, String, String, i32)) -> Channel {
|
||||||
let db_result = get_channel_by_type(&conn.0, VOICE_CHANNEL);
|
Channel {
|
||||||
// We have to rewrap the data given to us which is annoying but hey
|
id: tup.0,
|
||||||
// at least its safe.jpg
|
name: tup.1,
|
||||||
if let Ok(data) = db_result {
|
description: tup.2,
|
||||||
Ok(Json(data))
|
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")]
|
async fn get_channels_vec(conn: Conn) -> Result<Vec<Channel>, Error> {
|
||||||
pub fn get_text_chanels(conn: DBConn) -> DbError<Json<Vec<Channel>>, err::DbResponse> {
|
let rows_db = conn.prep_exec(r"SELECT * FROM channels", ()).await?;
|
||||||
let db_result = get_channel_by_type(&conn.0, TEXT_CHANNEL);
|
let (_, rows) = rows_db.map_and_drop(|row| {
|
||||||
if let Ok(data) = db_result {
|
let (id, name, desc, kind): (i32, String, String, i32) = mysql_async::from_row(row);
|
||||||
Ok(Json(data))
|
Channel {
|
||||||
}
|
id: id,
|
||||||
else {
|
name: name,
|
||||||
Err(err::DbResponse{err_msg:"Could not query text channels"})
|
description: desc,
|
||||||
|
kind: ChannelType::from_i32(kind)
|
||||||
}
|
}
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
pub async fn list_channels(pool: &Pool, response: &mut Response<Body>) {
|
||||||
mod channel_tests {
|
if let Ok(conn) = pool.get_conn().await {
|
||||||
use super::*;
|
match get_channels_vec(conn).await {
|
||||||
use rocket;
|
Ok(chans) => {
|
||||||
use rocket::local::Client;
|
*response.status_mut() = StatusCode::OK;
|
||||||
use rocket::http::Status;
|
response.headers_mut().insert("Content-Type",
|
||||||
use serde_json::from_str;
|
HeaderValue::from_static("application/json"));
|
||||||
|
|
||||||
macro_rules! rocket_inst {
|
let mut new_body = String::from("{\"channels\":[");
|
||||||
($func:expr) => {
|
for chan in chans.iter() {
|
||||||
rocket::ignite()
|
let s = format!("{},", chan.as_json_str());
|
||||||
.mount("/channels", routes![$func])
|
new_body.push_str(&s);
|
||||||
.attach(DBConn::fairing())
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,6 +24,8 @@ use auth::AuthReason;
|
|||||||
|
|
||||||
mod routes;
|
mod routes;
|
||||||
mod invites;
|
mod invites;
|
||||||
|
mod sql_traits;
|
||||||
|
mod channels;
|
||||||
|
|
||||||
fn map_qs(query_string_raw: Option<&str>) -> HashMap<&str, &str> {
|
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<Body>, meth: &Method, path: &str, params: &HashMap<&str, &str>) {
|
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) {
|
match (meth, path) {
|
||||||
(&Method::GET, routes::INVITE_JOIN) => {
|
(&Method::GET, routes::INVITE_JOIN) => {
|
||||||
if let Err(_) = invites::join_invite_code(pool, resp, params).await {
|
if let Err(_) = invites::join_invite_code(pool, resp, params).await {
|
||||||
@ -55,9 +58,8 @@ async fn route_dispatcher(pool: &Pool, resp: &mut Response<Body>, meth: &Method,
|
|||||||
*resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
*resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
(&Method::GET, routes::CHANNELS_LIST) => channels::list_channels(pool, resp).await,
|
||||||
*resp.status_mut() = StatusCode::NOT_FOUND;
|
_ => *resp.status_mut() = StatusCode::NOT_FOUND
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
pub const INVITE_JOIN: &'static str = "/invite/join"; // requires @code
|
pub const INVITE_JOIN: &'static str = "/invite/join"; // requires @code
|
||||||
pub const INVITE_CREATE: &'static str = "/invite/create"; // requires none
|
pub const INVITE_CREATE: &'static str = "/invite/create"; // requires none
|
||||||
|
|
||||||
|
pub const CHANNELS_LIST: &'static str = "/channels/list"; // requires none
|
8
server/src/sql_traits.rs
Normal file
8
server/src/sql_traits.rs
Normal file
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user