freechat/server-api/db/src/messages.rs
shockrah 01320899a3 Base implementation of new /message/from_id route
! Requires unit testing
! Written with max response length in mind, (still ignores mem limits howevr)
2021-01-19 22:26:53 -08:00

234 lines
8.0 KiB
Rust

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<Message, (BigInt, UBigInt)> for Message {
type Row = Option<(UBigInt, BigInt, VarChar, UBigInt, UBigInt)>;
async fn get(p: &Pool, id: UBigInt) -> Response<Self> {
//! 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<Self> {
//! 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, SqlError> =
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<Self> {
//! 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, SqlError> =
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<Self> {
//! 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<Response<Self>, 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<Response<Self>, SqlError> {
//! @returns on success : Set(Vec<Messages>)
//! @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<UBigInt>) -> Result<Response<Self>, SqlError> {
//! @returns on success : Set(Vec<Messages>)
//! @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))
}
}