use mysql_async::{params, Pool, Conn}; use mysql_async::prelude::Queryable; use mysql_async::error::Error as SqlError; use async_trait::async_trait; use serde::Serialize; use crate::{Response, no_conn, sql_err}; use crate::{UBigInt, BigInt, VarChar}; use crate::common::FromDB; #[allow(dead_code)] #[derive(Serialize)] pub struct Message { pub id: UBigInt, pub time: BigInt, pub content: VarChar, pub author_id: UBigInt, pub channel_id: UBigInt } #[async_trait] impl FromDB for Message { type Row = Option<(UBigInt, BigInt, VarChar, UBigInt, UBigInt)>; async fn get(p: &Pool, id: UBigInt) -> Response { //! Typically used as the backend to the .update(...) method to //! pick out a message to later edit if let Ok(conn) = p.get_conn().await { let q = "SELECT id, time, content, author_id, channel_id WHERE id = :id"; let result: Result<(Conn, Self::Row), SqlError> = conn.first_exec(q, params!{"id" => id}).await; if let Ok((_, row)) = result { return match row { Some(row) => Response::Row(Self { id, time: row.1, content: row.2, author_id: row.3, channel_id: row.4 }), None => Response::Empty } } return Response::Other(sql_err!("Message::FromDB::get")); } return Response::Other(no_conn!("Message")); } async fn update(p: &Pool, row: Self) -> Response { //! Primarily used by users to edit previous comments // NOTE: we only allow the changing of content in this since // no other column has good reason to be modified if let Ok(conn) = p.get_conn().await { let q = "UPDATE messages SET content = :content WHERE id = :id"; let result: Result = conn.drop_exec(q, params!{"id" => row.id, "content" => row.content}).await; return match result { Ok(_) => Response::Success, Err(_) => Response::Other(sql_err!("Message::FromDB::update")) } } return Response::Other(no_conn!("Message::FromDB::update")) } async fn delete(p: &Pool, id: UBigInt) -> Response { //! Deletes a single message //! Typically used by normal users/bots to remove unwanted comments if let Ok(conn) = p.get_conn().await { let q = "DELETE FROM messages WHERE id = :id"; let result: Result = conn.drop_exec(q, params!{"id" => id}).await; return match result { Ok(_) => Response::Success, Err(_) => Response::Other(sql_err!("Message::FromDB::delete")) } } return Response::Other(no_conn!("Message::FromDB::update")) } async fn filter(p: &Pool, (time, channel_id): (BigInt, UBigInt)) -> Response { //! FIlter happens via unix_timestamp and channel_id respectively if let Ok(conn) = p.get_conn().await { let q = "SELECT id, time, content, author_id"; if let Ok(query)= conn.prep_exec(q, params!{"time" => time, "cid" => channel_id}).await { let mapping_r = query.map_and_drop(|row| { let (id, time, content, author_id): (UBigInt, BigInt, VarChar, UBigInt) = mysql_async::from_row(row); Message { id, time, content, author_id, channel_id } }).await; match mapping_r { Ok((_, messages)) => Response::Set(messages), Err(_) => Response::Other(sql_err!("db::Message::filter")) } } else { return Response::Empty; } } else { return Response::Other(no_conn!("db::Message::filter")); } } } impl Message { pub async fn send(p: &Pool, content: &str, cid: UBigInt, uid: UBigInt) -> Result, SqlError> { //! @returns on_sucess -> empty //! @returns on_failure Err(SqlErr) use chrono::Utc; let conn = p.get_conn().await?; let q = "INSERT INTO messages (time, content, author_id, channel_id) VALUES (:time, :content, :author, :channel)"; let now = Utc::now().timestamp(); let res = conn.prep_exec(q, params!{ "time" => now, "content" => content, "author" => uid, "channel" => cid }).await; if let Err(e) = res { return match e { SqlError::Server(err) => { if err.code == 1452 { return Ok(Response::RestrictedInput("Channel not found".into())) } else { Ok(Response::Other(sql_err!("db::messages::send"))) } }, _ => Ok(Response::Other(sql_err!("db::messages::send"))) } } // all good response else { return Ok(Response::Empty); } } pub async fn get_time_range(p: &Pool, channel_id: UBigInt, start: BigInt, end: BigInt) -> Result, SqlError> { //! @returns on success : Set(Vec) //! @returns on userfail: RestrictedInput(message) //! @returns on error : Err(SqlError) if start >= end { Ok(Response::RestrictedInput("Invalid start/end parameters".into())) } else { let conn = p.get_conn().await?; let q = "SELECT id, time, content, author_id FROM messages WHERE channel_id = :channel AND time >= :start AND time < :end"; let select_result = conn.prep_exec( q, params!{ "start" => start, "end" => end, "channel" => channel_id }).await?; let(_conn, messages) = select_result.map_and_drop(|row| { type Tuple = (UBigInt, BigInt, String, UBigInt); let (id, time, content, author_id): Tuple = mysql_async::from_row(row); Self { id, time, content, author_id, channel_id } }).await?; Ok(Response::Set(messages)) } } pub async fn get_from_id(p: &Pool, channel_id: UBigInt, start: UBigInt, limit: Option) -> Result, SqlError> { //! @returns on success : Set(Vec) //! @returns on failure : Err(SqlError) let conn = p.get_conn().await?; const MAX: u64 = 1000; let limit = if let Some(limit) = limit{ match limit { 1 ..= MAX => limit, _ => MAX } } else { MAX // messages at a time }; let q = "SELECT id, time, content, author_id FROM messages WHERE channel_id = :channel AND id >= :start LIMIT :limit"; let params = params!{ "channel" => channel_id, "start" => start, "limit" => limit }; let select_result = conn.prep_exec(q, params).await?; let (_conn, messages) = select_result.map_and_drop(|row| { type Tuple = (UBigInt, BigInt, String, UBigInt); let (id, time, content, author_id): Tuple = mysql_async::from_row(row); Self { id, time, content, author_id, channel_id } }).await?; Ok(Response::Set(messages)) } }