Renaming project to json-api for clarity sake

This commit is contained in:
shockrah
2021-01-24 13:34:17 -08:00
parent 84c865e194
commit b67bb6105f
52 changed files with 83 additions and 2 deletions

1576
json-api/db/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

17
json-api/db/Cargo.toml Normal file
View File

@@ -0,0 +1,17 @@
[package]
name = "db"
version = "0.1.0"
authors = ["shockrah <alejandros714@protonmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
mysql_async = "0.23.1"
redis = { version = "0.18.0", features = ["tokio-comp"] }
async-trait = "0.1.40"
serde_json = "1.0"
serde = { version = "1.0.117", features = [ "derive" ] }
chrono = "0.4.0"

35
json-api/db/src/auth.rs Normal file
View File

@@ -0,0 +1,35 @@
use mysql_async::{params, Pool};
use mysql_async::prelude::Queryable;
use mysql_async::error::Error;
use crate::UBigInt;
async fn update_jwt(p: &Pool, id: UBigInt, token: &str) -> Result<(), Error> {
let conn = p.get_conn().await?;
let _ = conn.drop_exec("UPDATE jwt SET token = :tk WHERE id = :id",
params!{"tk" => token, "id" => id}).await?;
Ok(())
}
pub async fn add_jwt(p: &Pool, id: UBigInt, token: &str) -> Result<(), Error> {
let conn = p.get_conn().await?;
let q = "INSERT INTO jwt (id, token) VALUES (:id, :tk)";
return match conn.prep_exec(q, params!{"tk" => token, "id" => id}).await {
Ok(_) => Ok(()),
Err(_) => update_jwt(p, id, token).await // attempt to refresh token
};
}
pub async fn listed_jwt(p: &Pool, id: UBigInt, token_given: &str) -> Result<bool, Error> {
// only checks if the given token is listed somewhere in the db
let conn = p.get_conn().await?;
let q = "SELECT token FROM jwt WHERE id = :id";
// if id.token == return true
let (_, db_t): (_, Option<String>) =
conn.first_exec(q, params!{"id" => id}).await?;
return match db_t {
Some(token_db) => Ok(token_db == token_given), // probably pointless check but its not that expensive so its stays as a sanity check
None => Ok(false)
};
}

198
json-api/db/src/channels.rs Normal file
View File

@@ -0,0 +1,198 @@
use mysql_async::{params, Pool, Conn};
use mysql_async::prelude::Queryable;
use mysql_async::error::Error as SqlError;
use async_trait::async_trait;
use crate::{VarChar, UBigInt, Integer};
use crate::common::FromDB;
use crate::{sql_err, no_conn, Response};
use serde::Serialize;
#[derive(Serialize)]
pub struct Channel {
pub id: UBigInt,
pub name: VarChar,
pub description: Option<VarChar>,
pub kind: Integer
}
pub const VOICE_CHANNEL: Integer = 1;
pub const TEXT_CHANNEL: Integer = 2;
#[async_trait]
impl FromDB<Channel, Integer> for Channel {
// id name desc kind
type Row = Option<(UBigInt, VarChar, Option<VarChar>, Integer)>;
async fn get(p: &Pool, id: UBigInt) -> Response<Channel> {
//! Retrieves a Full single Channel row from the DB or fails in a
//! fashion described by crate::Response<Channel>
//! @param p -> SqlPool
//! @param id -> UBigInt
//! @return on_success -> Response::Row(Channel)
//! @return on_fail -> Response::{Empty, Other<String>}
if let Ok(conn) = p.get_conn().await {
let q = "SELECT id, name, description, kind FROM channels 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(Channel {
id: id,
name: row.1,
description: row.2,
kind: row.3
}),
None => Response::Empty
}
}
return Response::Other(no_conn!("Invite::FromDB::get fetch failed"));
}
return Response::Other(no_conn!("Invite::FromDB::get"));
}
async fn update(p: &Pool, row: Self) -> Response<Channel> {
//! Updates a whole single based on a given Row Of Channel Type
//! @param p -> SqlPool
//! @param row -> Channel
//! @return on_success -> Response::Success
//! @return on_failure -> Response::Other
if let Ok(conn) = p.get_conn().await {
let q = "UPDATE channels
SET name = :name, description = :desc, kind = :kind
WHERE id = :id";
let result: Result<Conn, SqlError> =
conn.drop_exec(q, params!{
"id" => row.id,
"name" => row.name,
"desc" => row.description,
"kind" => row.kind
}).await;
return match result {
Ok(_) => Response::Success,
Err(_) => Response::Other(sql_err!("Invite::FromDB::update Update failed"))
}
}
return Response::Other(no_conn!("Invite::FromDB::get connection failed"));
}
async fn delete(p: &Pool, id: UBigInt) -> Response<Channel> {
//! Deletes channel given UBigInt as the row key
//! @param p -> SqlPool
//! @param id -> UBigInt
//! @return on_success -> Response::Success
//! @return on_failure -> Response::Other
if let Ok(conn) = p.get_conn().await {
let q = "DELETE FROM channels WHERE id = :id";
let result: Result<Conn, SqlError> =
conn.drop_exec(q, params!{"id" => id}).await;
return match result {
Ok(_) => Response::Success,
Err(sql) => Response::Other(sql_err!(sql))
}
}
else {
return Response::Other(no_conn!("Member::FromDB::delete"))
}
}
async fn filter(p: &Pool, kind: Integer) -> Response<Channel> {
//! @returns -> on success : Response::Set(Vec<Channel>)
//! @returns -> on empty set : Response::Set(EmptyVector)
//! @params -> on fail : Response::Other
// NOTE: used for mapping datasets to vectors
let map_rows = |row| {
let (id, name, desc, k): (UBigInt, VarChar, Option<VarChar>, Integer) =
mysql_async::from_row(row);
Channel {
id: id,
name: name,
description: desc,
kind: k
}
};
return match (p.get_conn().await, kind) {
// Filter for text/voice channels specifically
(Ok(conn), VOICE_CHANNEL..=TEXT_CHANNEL) => { // @NOTE: voice channel and text_channel are literally 1 & 2 respectively
let q = "SELECT id, name, description, kind FROM channels WHERE kind = :kind";
let q_result = conn.prep_exec(q, params!{"kind" => kind}).await;
if let Ok(res) = q_result {
let mapping_result = res.map_and_drop(map_rows).await;
return match mapping_result {
Ok((_, channels)) => Response::Set(channels),
Err(_) => Response::Other(sql_err!("db::Channels::filter @with params"))
};
}
else {
Response::Other(sql_err!(""))
}
},
/*
* Here we are attempting to get all the channels with no filters applied
* This should fetch everything basically in our channels registry
*/
(Ok(conn), _) => {
let q = "SELECT id, name, description, kind FROM channels";
if let Ok(query) = conn.prep_exec(q, ()).await {
let mapping_r = query.map_and_drop(map_rows).await;
return match mapping_r {
Ok((_, channels)) => Response::Set(channels),
Err(_) => Response::Other(sql_err!("db::Channels::filter @no params"))
};
}
else {
Response::Other(sql_err!("db::Channels::filter @no params @no initial query"))
}
},
(Err(_), _) => {Response::Other(no_conn!("Channel::FromDB::filter"))}
}
}
}
impl Channel {
pub async fn add(p: &Pool, name: &str, desc: &str, kind: Integer) -> Response<Self> {
//! @returns on success -> Response::Row<Channel>
//! @returns on partial success -> Response::Empty
//! @returns on failure -> Response::Other
if let Ok(conn) = p.get_conn().await {
let q = "INSERT INTO channels (name, description, kind) VALUES (:n, :d, :k)";
let insert_result = conn.drop_exec(q, params!{
"n" => name,
"d" => desc,
"k" => kind
}).await;
if let Ok(conn) = insert_result {
// This is only kosher because names are enforced as unique by sql
let q = "SELECT id FROM channels WHERE name = :name";
let fetch_result : Result<(Conn, Option<UBigInt>), SqlError> =
conn.first_exec(q, params!{"name" => name}).await;
return match fetch_result {
Ok((_, id_opt)) => {
if let Some(id) = id_opt {
Response::Row(Channel {
id: id,
name: name.into(),
description: Some(desc.into()),
kind: kind
})
}
else { Response::Empty }
},
Err(_) => Response::Empty
};
}
else {
return Response::RestrictedInput(
"Could not add channel, make sure the name is unique and not longer than 256 bytes".into());
}
}
return Response::Other(no_conn!("db::channels::add"))
}
}

53
json-api/db/src/common.rs Normal file
View File

@@ -0,0 +1,53 @@
use mysql_async::Pool;
use async_trait::async_trait;
use crate::Response;
use crate::UBigInt;
#[macro_export]
macro_rules! no_conn {
($spec:literal) => {
format!("[ CON Error ] : {}", $spec)
}
}
#[macro_export]
macro_rules! sql_err {
($spec:literal) => {
format!("[ SQL Error ] : {}", $spec)
};
// Using this mostly to pull in sql err types from lib to outside world for logging
($exp:expr) => {
format!("[ SQL Error ] : {}", $exp)
}
}
#[macro_export]
macro_rules! sql_err_log {
($spec:expr) => {
println!($spec);
}
}
/*
* NOTE: pay attention to work on async in traits for rust
* Each of one these funcs will implicitly do a single heap allocation which
* for our case is fine though ideally we don't
*
* As soon as we finda way around that we should depecrate `#[async_trait`
* for the performance
* */
#[async_trait]
pub trait FromDB<T, FilterType> {
type Row;
async fn get(p: &Pool, id: UBigInt) -> Response<T>;
async fn update(p: &Pool, row: T) -> Response<T>;
async fn delete(p: &Pool, id: UBigInt) -> Response<T>;
async fn filter(p: &Pool, filter_val: FilterType) -> Response<T>;
}

116
json-api/db/src/invites.rs Normal file
View File

@@ -0,0 +1,116 @@
use mysql_async::{params, Pool, Conn};
use mysql_async::prelude::Queryable;
use mysql_async::error::Error as SqlError;
use async_trait::async_trait;
use crate::{UBigInt, BigInt};
use crate::common::FromDB;
use crate::{Response, no_conn};
#[allow(dead_code)]
pub struct Invite {
pub id: BigInt,
pub uses: Option<BigInt>,
pub expires: bool
}
#[async_trait]
impl FromDB<Invite, bool> for Invite {
type Row = Option<(BigInt, Option<BigInt>, bool)>;
async fn get(p: &Pool, id: UBigInt) -> Response<Self> {
// NOTE: cast is required for this as `id` is used as unix timestamp
let id: BigInt = id as BigInt;
if id <= 0 {
return Response::Empty;
}
if let Ok(conn) = p.get_conn().await {
let q = "SELECT id, uses, expires FROM invites 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: id as BigInt,
uses: row.1,
expires: row.2
}),
None => Response::Empty
}
}
}
return Response::Empty;
}
async fn update(p: &Pool, row: Self) -> Response<Self> {
let q = r#"UPDATE invites
SET uses = :uses, expires: :exp
WHERE id = :id
"#;
// forcibly udpate even if theres nothing there
// this way we avoid doing an extra network hit
if row.id <= 0 {
return Response::Empty;
}
if let Ok(conn) = p.get_conn().await {
let result: Result<Conn, SqlError> =
conn.drop_exec(q, params!{
"id" => row.id,
"uses" => row.uses,
"exp" => row.expires
}).await;
return match result {
Ok(_) => Response::Success,
Err(_) => Response::Other(format!("Could not update entry {}", row.id))
}
}
return Response::Empty;
}
async fn delete(p: &Pool, id: UBigInt) -> Response<Self> {
if id <= 0 { // really lame "assertion" that each method has to use for invites since they all internally use
return Response::Empty;
}
if let Ok(conn) = p.get_conn().await {
let q = "DELETE FROM invites WHERE id = :id";
let result: Result<Conn, SqlError> =
conn.drop_exec(q, params!{"id" => id as BigInt}).await;
return match result {
Ok(_) => Response::Success,
Err(_) => Response::Other(format!("Could not delete {}", id))
}
}
return Response::Success;
}
async fn filter(p: &Pool, expirey_flag: bool) -> Response<Self> {
if let Ok(conn) = p.get_conn().await {
let q = "SELECT id, uses, expires FROM invites WHERE expires = :exp";
if let Ok(query) = conn.prep_exec(q, params!{"exp" => expirey_flag}).await {
let mapping_r = query.map_and_drop(|row| {
let (id, uses): (BigInt, Option<BigInt>) = mysql_async::from_row(row);
Invite {
id: id,
uses: uses,
expires: expirey_flag
}
}).await;
return match mapping_r {
Ok((_, invites)) => Response::Set(invites),
Err(_) => Response::Empty
}
}
else {
return Response::Other(no_conn!("db::Invite::filter"));
}
}
else {
return Response::Other(no_conn!("db::Invites::filter"));
}
}
}

45
json-api/db/src/lib.rs Normal file
View File

@@ -0,0 +1,45 @@
extern crate serde;
pub mod member;
pub mod common;
pub mod invites;
pub mod channels;
pub mod messages;
pub mod auth;
use std::vec::Vec;
pub type BigInt = i64;
pub type UBigInt = u64;
pub type Integer = i32;
pub type UInteger = u32;
pub type VarChar = String;
pub enum Response<T> {
// A set of rows collected
Set(Vec<T>),
// Single row collected
Row(T),
// Nothing was found -> for select/update/delete's
Empty,
// Generic success for things like update/delete's
Success,
// Mask 500's with probable user generated errors like duplicate keys being added
// or data that doesn't fit in column or something
RestrictedInput(String),
// Not sure what this will be used for but maybe its useful at some point
Other(String)
}
#[macro_export]
macro_rules! fetch_row {
($table:literal, $col:literal, $key:expr, $rtype:ty,$conn:expr) => {
let lib_str = format!("SELECT * FROM {} WHERE {} = :{}", $table, $col, $col);
let (_, $rtype) = $conn.first_exec!(lib_str, sql_params{
$col => $key,
});
}
}

211
json-api/db/src/member.rs Normal file
View File

@@ -0,0 +1,211 @@
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, Integer, VarChar};
use crate::common::{FromDB};
#[derive(Debug, Serialize)]
pub struct Member {
pub id: UBigInt,
pub secret: VarChar,
pub name: VarChar,
pub joindate: BigInt,
pub status: Integer,
pub permissions: UBigInt,
}
pub const STATUS_ONLINE: Integer = 0;
pub const STATUS_OFFLINE: Integer = 1;
pub const STATUS_AWAY: Integer = 2;
pub const STATUS_DO_NOT_DISTURB: Integer = 3;
/*
*
* conn = getconn
* result = conn.query
* return response based on result
*
*/
#[async_trait]
impl FromDB<Member, Integer> for Member {
type Row = Option<(VarChar, VarChar, BigInt, Integer, UBigInt)>;
async fn get(p: &Pool, id: UBigInt) -> Response<Self> {
if let Ok(conn) = p.get_conn().await {
let q = "SELECT secret, name, joindate, status, permissions FROM members WHERE id = :id";
let db_res : Result<(Conn, Self::Row), SqlError>
= conn.first_exec(q, params!{"id" => id}).await;
if let Ok((_, row)) = db_res {
return match row {
Some(row) => Response::Row(Self {
id: id,
secret: row.0,
name: row.1,
joindate: row.2,
status: row.3,
permissions: row.4
}),
None => Response::Empty
}
}
return Response::Other(sql_err!("Fetch failed"));
}
return Response::Other(no_conn!("Member::FromDB::get"));
}
async fn update(p: &Pool, row: Member) -> Response<Self> {
let q = r#"UPDATE members
SET status = :status, permissions = :perms, name = :name
WHERE id = :id"#;
if let Ok(conn) = p.get_conn().await {
let query = conn.drop_exec(q, params!{
"id" => row.id,
"status" => row.status,
"name" => row.name,
"perms" => row.permissions
}).await;
return match query {
Ok(_) => Response::Success,
Err(err) => Response::Other(sql_err!(err))
}
}
return Response::Other(no_conn!("db::Member::update"));
}
async fn delete(p: &Pool, id: UBigInt) -> Response<Self> {
if let Ok(conn) = p.get_conn().await {
let q = "DELETE from members WHERE id = :id";
let db_result: Result<Conn, SqlError>
= conn.drop_exec(q, params!{"id" => id}).await;
match Member::get(p, id).await {
Response::Row(_) => {
if let Ok(conn) = db_result {
return match conn.prep_exec("", params!{"id" => id}).await {
Ok(_) => Response::Success,
Err(_) => Response::Other(sql_err!("Member::FromDB::delete"))
}
}
return Response::Success
},
Response::Empty => return Response::Empty,
_ => return Response::Other(sql_err!("Member::FromDB::delete | another stupid get happened delid this"))
}
}
return Response::Empty;
}
async fn filter(p: &Pool, status: Integer) -> Response<Self> {
//! @params status
return match (p.get_conn().await, status) {
(Ok(conn), STATUS_ONLINE) | (Ok(conn), STATUS_OFFLINE) |
(Ok(conn), STATUS_AWAY) | (Ok(conn), STATUS_DO_NOT_DISTURB) => {
// TODO: Allow people to query somewhere in the middle of this set
// i.e. we should be able to get user 100 -> 150 instead of just the
// first 1000 people
let q =
"SELECT id, name, status, permissions FROM memebrs
WHERE status = :stat LIMIT 1000"; // high limit for now
if let Ok(query) = conn.prep_exec(q, params!{"stat" => status}).await {
let mapping_r = query.map_and_drop(|row| {
let (id, name, status, permissions): (UBigInt, VarChar, Integer, UBigInt) =
mysql_async::from_row(row);
Member {
id: id,
secret: "".into(), // no show for obv reasons
name: name,
joindate: 0, // doesn't matter
status: status,
permissions: permissions
}
}).await;
match mapping_r {
Ok((_, members)) => Response::Set(members),
Err(_) => Response::Other(sql_err!("db::Members::filter"))
}
}
else {
Response::Other(sql_err!("db::Members::filter"))
}
}
_ => Response::Other(sql_err!("err"))
}
}
}
impl Member {
pub async fn add(p: &Pool, name: &str, secret: &str, perms: u64) -> Result<Response<Self>, SqlError> {
//! @param {pool} p
//! @param {&str} name of new user
//! @param {&str} encrypted secret : userland auth module should have a function for this
//! @param {u64} permissions mask
//! @returns : on_succes => Ok(Response<Member>)
//! @returns : on_partial_succes => Ok(Response<Member>)
//! @returns : on_failure => Err(SomeBS)
use chrono::Utc;
let conn = p.get_conn().await?;
//name
//perms
//secret
let now: BigInt = Utc::now().timestamp();
let conn = conn.drop_exec(
"INSERT INTO members(secret, name, joindate, status, permissions)
VALUES(:secret, :name, :joindate, :status, :permissions)",
mysql_async::params!{
"secret" => secret.clone(),
"name" => name.clone(),
"joindate" => now,
"status" => STATUS_ONLINE,
"permissions" => perms
}).await?;
let (_, opt_id): (Conn, Option<UBigInt>) = conn.first_exec(
"SELECT id FROM members WHERE secret = :secret",
params!{
"secret" => secret.clone()
}).await?;
if let Some(id) = opt_id {
return Ok(Response::Row(Self {
id: id,
secret: secret.into(),
name: name.into(),
joindate: now,
status: STATUS_ONLINE,
permissions: perms
}))
}
return Ok(Response::Empty);
}
pub async fn update_perms(p: &Pool, uid: UBigInt, permissions: UBigInt) -> Result<UBigInt, SqlError> {
//! @return on_sucess Ok(NewPermisionsMask)
//!
let conn = p.get_conn().await?;
conn.drop_exec(
"UPDATE members SET permissions = :perms WHERE id = :id",
params!{
"id" => uid,
"perms" => permissions
}).await?;
Ok(permissions)
}
}

245
json-api/db/src/messages.rs Normal file
View File

@@ -0,0 +1,245 @@
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
}
const MAX_MESSAGES: u64 = 1000;
#[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, limit: Option<u64>) -> 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 limit = if let Some(limit) = limit {
match limit {
1 ..= MAX_MESSAGES => limit,
_ => MAX_MESSAGES
}
} else {
MAX_MESSAGES
};
let q = "SELECT id, time, content, author_id FROM messages
WHERE channel_id = :channel AND time >= :start AND time < :end
LIMIT :limit";
let select_result = conn.prep_exec(
q, params!{
"start" => start,
"end" => end,
"channel" => channel_id,
"limit" => limit
}).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?;
let limit = if let Some(limit) = limit{
match limit {
1 ..= MAX_MESSAGES => limit,
_ => MAX_MESSAGES
}
} else {
MAX_MESSAGES // 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))
}
}