Renaming project to json-api for clarity sake
This commit is contained in:
1576
json-api/db/Cargo.lock
generated
Normal file
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
17
json-api/db/Cargo.toml
Normal 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
35
json-api/db/src/auth.rs
Normal 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
198
json-api/db/src/channels.rs
Normal 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
53
json-api/db/src/common.rs
Normal 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
116
json-api/db/src/invites.rs
Normal 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
45
json-api/db/src/lib.rs
Normal 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
211
json-api/db/src/member.rs
Normal 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
245
json-api/db/src/messages.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user