+ Adding db-lib::common::try_error_passthrough

This is more heavily used in Message::send as a way of discerning "real" server errors
from those that are caused  by user input. The name itself might not be super fitting either

* Moving code for inserting textual messages into its own function
This splits up the logic a bit for Message::send but this segment of logic is also
much simpler than that of file upload

* Flattening Message::send overall
Keeping this function as flat as can be is crucial as it is one of the heavier+most
important funcs in the whole JSON API codebase

Files are now also generated correctly and asynchronously
This commit is contained in:
shockrah 2021-04-21 16:08:23 -07:00
parent 52676cdd1f
commit ef73b39f4f
3 changed files with 105 additions and 82 deletions

View File

@ -36,10 +36,8 @@ impl Channel {
}
pub async fn add(p: &Pool, name: &str, description: &str, kind: Integer)
-> Result<Response<Self>, SqlError> {
-> Result<Response<Channel>, SqlError> {
//! @returns on success -> Response::Row<Channel>
//! @returns on partial success -> Response::Empty
//! @returns on failure -> Response::Other
//! @returns on user failure -> Response::RestrictedInput(msg)
// bounds are literally [1, 2]

View File

@ -1,3 +1,6 @@
use crate::Response;
use mysql_async::Error;
#[macro_export]
macro_rules! no_conn {
($spec:literal) => {
@ -24,3 +27,19 @@ macro_rules! sql_err_log {
}
}
pub fn try_error_passthrough<T>(err: Error) -> Result<Response<T>, Error> {
// Some user input _will_ cause sql to complain about things like foreign key
// constraints
// In order to translate 500's into more precise 400's we use this function
// (sparingly)
match &err {
Error::Server(se) => {
if se.code == 1452 {
return Ok(Response::RestrictedInput(format!("Invalid key value given")))
}
return Err(err);
},
_ => Err(err)
}
}

View File

@ -11,6 +11,7 @@ use mysql_async::Error as SqlError;
use crate::Response;
use crate::{UBigInt, BigInt};
use crate::{Message, UserMessage};
use crate::common;
use rand::RngCore;
@ -31,92 +32,97 @@ impl Message {
}
pub async fn send(p: &Pool, content: &str, content_type: &str, cid: UBigInt, uid: UBigInt) -> Result<Response<Self>, SqlError> {
//! @returns on_sucess -> empty
//! @returns on_failure Err(SqlErr)
async fn insert_text(p: &Pool, content: &str, cid: UBigInt, uid: UBigInt) -> Result<Response<Self>, SqlError> {
// TODO: make this check not compare against something so hardcoded and
if content.len() > 4_000 {
return Ok(Response::RestrictedInput("Text larger than 4000 bytes".into()))
} else {
let mut conn = p.get_conn().await?;
let q = "INSERT INTO messages (id, time, content, content_type, author_id, channel_id)
VALUES(:id, :time, :content, 'text/plain', :uid, :chan_id)";
let mut conn = p.get_conn().await?;
let q = "INSERT INTO messages
(id, time, content, content_type, author_id, channel_id)
VALUES (id, :time, :content, :ctype, :author, :channel)";
let id: u64 = rand::rngs::OsRng.next_u64();
let now: i64 = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("System time `NOW` failed")
.as_millis() as i64;
let p = params!{
"id" => id,
"time" => now,
"content" => content,
"uid" => uid,
"chan_id" => cid
};
conn.exec_drop(q, p).await?;
let msg = Message::new(id, now, content, "text/plain", uid, cid);
return Ok(Response::Row(msg))
}
}
let id: u64 = rand::rngs::OsRng.next_u64();
let now: i64 = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("System time `NOW` failed")
.as_millis() as i64;
match content_type {
"text/plain" => {
if content.len() > 4_000 {
Ok(Response::RestrictedInput("Large text not allowed".into()))
async fn save_file(filename: String, data: &[u8], msg: Message) -> Response<Message> {
match fs::File::create(filename).await {
Ok(mut file) => {
if let Ok(_) = file.write_all(data).await {
Response::Row(msg)
} else {
let res = conn.exec_drop(q, params!{
"id" => id,
"time" => now,
"content" => content,
"ctype" => content_type,
"author" => uid,
"channel" => cid
}).await;
match Ok(res) {
Ok(_) => {
let msg = Message::new(id, now, content, content_type, uid, cid);
Ok(Response::Row(msg))
},
Err(e) => Err(e)
}
}
},
_ => {
/*
* Amazing hardcoded limit on binary content_length
* TODO: make this not hardcoded
* This really should be configurable by someone somewhere
* The best way of doing this honestly it probably through some kind of lazy static
* we can set an env var from the cli frontend for that lz_static to read
*/
if content.len() > 10_000_000 {
Ok(Response::RestrictedInput("Large data not allowed".into()))
} else {
let extension = match content_type {
"image/png" => "png",
"image/jpeg" | "image/jpg" => "jpg",
"application/webm" => "webm",
"application/mp4" => "mp4",
"application/mp3" => "mp3",
_ => panic!("Bad file type sent to db layer {}", content_type)
};
let content_ref = format!("{cid}-{time}.{ext}", cid=cid, time=now, ext=extension);
let res = conn.exec_drop(q, params!{
"id" => id,
"time" => now,
"content" => &content_ref, // store a ref to a file instead of the actual payload
"ctype" => content_type,
"author" => uid,
"channel" => cid
}).await;
if let Ok(_) = res {
// now save the data to disk
match fs::File::create(content_ref).await {
Ok(mut file) => {
file.write_all(content.as_bytes()).await.expect("Failed to write, but the ref is saved");
let msg = Message::new(id, now, content, content_type, uid, cid);
Ok(Response::Row(msg))
},
Err(_) => {
Ok(Response::Other("Saved ref but couldn't save file data".into()))
},
}
} else {
Ok(Response::Success)
}
Response::Empty
}
}
Err(e) => {
let msg = format!("db::Message::save_file {}", e);
Response::Other(msg)
}
}
}
pub async fn send(p: &Pool, content: &str, content_type: &str, cid: UBigInt, uid: UBigInt) -> Result<Response<Self>, SqlError> {
//! @returns on_sucess -> Ok(Response::Row<Message>)
//! @returns on_failure Err(SqlErr)
if content_type == "text/plain" {
match Self::insert_text(p, content, cid, uid).await {
Ok(pass) => Ok(pass),
Err(e) => common::try_error_passthrough(e)
}
} else {
if content.len() > 10_000_000 {
return Ok(Response::RestrictedInput("Large data not allowed".into()))
}
let extension = match content_type {
"image/png" => "png",
"image/jpeg" | "image/jpg" => "jpg",
"application/webm" => "webm",
"application/mp4" => "mp4",
"application/mp3" => "mp3",
_ => panic!("Bad file type sent to db layer {}", content_type)
};
let q = "INSERT INTO messages (id, time, content, content_type, author_id, channel_id)
VALUES(:id, :time, :fname, :ctype, :author, :channel)";
let id: u64 = rand::rngs::OsRng.next_u64();
let filename = format!("{}.{}", id, extension);
let now: i64 = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("System time `NOW` failed")
.as_millis() as i64;
let mut conn = p.get_conn().await?;
let insert_res = conn.exec_drop(q, params!{
"id" => id,
"time" => now,
"ctype" => content_type,
"fname" => &filename,
"author" => uid,
"channel" => cid
}).await;
match insert_res {
Ok(_) => {
let msg = Message::new(id, now, &filename, content_type, uid, cid);
Ok(Message::save_file(filename, content.as_bytes(), msg).await)
},
Err(e) => common::try_error_passthrough(e)
}
}
}
pub async fn get_time_range(p: &Pool, channel_id: UBigInt, start: BigInt, end: BigInt, limit: Option<u64>) -> Result<Response<UserMessage>, SqlError> {