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::(), // 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, 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, 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) -> 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(()) }