From 6061d3e3c1caf4df05dab0fe1611a51a30bdd0b7 Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Sat, 29 Jun 2024 15:23:45 -0400 Subject: [PATCH] All DB functions, Admin starter, Need auth --- server/src/main.rs | 7 +- server/src/server.rs | 244 ++++++++++++++++++++++++++++++++++++++----- server/src/types.rs | 24 +++-- 3 files changed, 238 insertions(+), 37 deletions(-) diff --git a/server/src/main.rs b/server/src/main.rs index dbd5b2c..976c1c1 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -34,7 +34,9 @@ async fn main() -> anyhow::Result<()> { "CREATE TABLE IF NOT EXISTS room ( id SERIAL, room_id VARCHAR(255) NOT NULL, - name VARCHAR(255) NOT NULL + name VARCHAR(255) NOT NULL, + admin_only_send BOOL NOT NULL, + admin_only_view BOOL NOT NULL );" ).execute(&db_pool).await?; @@ -43,7 +45,8 @@ async fn main() -> anyhow::Result<()> { id SERIAL, user_id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, - online BOOL NOT NULL + online BOOL NOT NULL, + admin BOOL NOT NULL );" ).execute(&db_pool).await?; diff --git a/server/src/server.rs b/server/src/server.rs index 8f3a3f0..bb578c9 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -1,16 +1,16 @@ use std::net::SocketAddr; - -use futures::future; -use futures::future::Ready; -use sqlx::{MySql, Pool}; +use chrono::{DateTime, Utc}; +use sqlx::{Error, MySql, Pool, Row}; +use sqlx::mysql::MySqlRow; use tarpc::context::Context; -use tarpc::server::incoming::Incoming; -use crate::types::{ErrorCode, Message, MessageData, RealmChat, Room, User}; +use crate::types::{Edit, ErrorCode, Message, MessageData, Reaction, RealmChat, Redaction, Reply, Room, User}; +use crate::types::ErrorCode::*; #[derive(Clone)] pub struct RealmChatServer { pub socket: SocketAddr, pub db_pool: Pool, + pub typing_users: Vec<(u32, u32)> //NOTE: userid, roomid } impl RealmChatServer { @@ -18,17 +18,18 @@ impl RealmChatServer { RealmChatServer { socket, db_pool, + typing_users: Vec::new(), } } } impl RealmChat for RealmChatServer { - fn test(self, context: Context, name: String) -> Ready { - future::ready(format!("Hello, {name}!")) + async fn test(self, _: Context, name: String) -> String { + format!("Hello, {name}!") } - async fn send_message(self, context: Context, message: Message) -> Result { - //TODO: verify authentication somehow + async fn send_message(self, _: Context, message: Message) -> Result { + //TODO: verify authentication somehow for edits and redactions let result = match &message.data { MessageData::Text(text) => { @@ -69,43 +70,232 @@ impl RealmChat for RealmChatServer { } } - async fn start_typing(self, context: Context) -> ErrorCode { + async fn start_typing(self, _: Context) -> ErrorCode { //TODO: auth for all of these todo!() } - async fn stop_typing(self, context: Context) -> ErrorCode { + async fn stop_typing(self, _: Context) -> ErrorCode { todo!() } - async fn keep_typing(self, context: Context) -> ErrorCode { + async fn keep_typing(self, _: Context) -> ErrorCode { todo!() } - async fn get_message_from_guid(self, context: Context, guid: String) -> Result { + async fn get_message_from_id(self, _: Context, id: u32) -> Result { + //TODO: Auth for admin room + let result = sqlx::query( + "SELECT * FROM message INNER JOIN room ON message.room = room.id INNER JOIN user ON message.user = user.id WHERE message.id = ?" + ).bind(id).fetch_one(&self.db_pool).await; + + match result { + Ok(row) => { + self.dbmessage_to_message(row) + }, + Err(_) => { + Err(NotFound) + }, + } + } + + async fn get_messages_since(self, _: Context, time: DateTime) -> Result, ErrorCode> { + //TODO: Auth for admin rooms todo!() } - async fn get_messages_since(self, context: Context, time: u64) -> Result, ErrorCode> { - todo!() + async fn get_rooms(self, _: Context) -> Result, ErrorCode> { + //TODO: Auth for admin rooms! + let result = sqlx::query("SELECT * FROM room").fetch_all(&self.db_pool).await; + let mut rooms: Vec = Vec::new(); + + match result { + Ok(rows) => { + for row in rows { + let room = self.dbroom_to_room(row); + if let Some(err) = room.clone().err() { + return Err(err) + } + rooms.push(room.unwrap()); + } + Ok(rooms) + }, + Err(_) => { + Err(Error) + }, + } } - async fn get_rooms(self, context: Context) -> Result, ErrorCode> { - todo!() + async fn get_room(self, _: Context, roomid: String) -> Result { + //TODO: Auth for admin rooms! + let result = sqlx::query("SELECT * FROM room WHERE room_id = ?").bind(roomid).fetch_one(&self.db_pool).await; + + match result { + Ok(row) => { self.dbroom_to_room(row) }, + Err(_) => Err(NotFound), + } } - async fn get_room(self, context: Context, roomid: String) -> Result { - todo!() + async fn get_user(self, _: Context, userid: String) -> Result { + let result = sqlx::query("SELECT * FROM user WHERE user_id = ?").bind(userid).fetch_one(&self.db_pool).await; + + match result { + Ok(row) => { self.dbuser_to_user(row) }, + Err(_) => Err(NotFound), + } } - async fn get_user(self, context: Context, userid: String) -> Result { - todo!() - } + async fn get_users(self, _: Context, get_only_online: bool) -> Result, ErrorCode> { + let mut query = sqlx::query("SELECT * FROM user"); + if get_only_online { + query = sqlx::query("SELECT * FROM user WHERE online = true"); + } + + let result = query.fetch_all(&self.db_pool).await; + let mut users: Vec = Vec::new(); - async fn get_joined_users(self, context: Context) -> Result, ErrorCode> { - todo!() + match result { + Ok(rows) => { + for row in rows { + let user = self.dbuser_to_user(row); + if let Some(err) = user.clone().err() { + return Err(err) + } + users.push(user.unwrap()) + } + Ok(users) + }, + Err(_) => { + Err(Error) + }, + } } +} - async fn get_online_users(self, context: Context) -> Result, ErrorCode> { - todo!() +impl RealmChatServer { + fn dbroom_to_room(&self, row: MySqlRow) -> Result { + let id: Result = row.try_get("id"); + let roomid: Result = row.try_get("user_id"); + let name: Result = row.try_get("name"); + let admin_only_send: Result = row.try_get("admin_only_send"); + let admin_only_view: Result = row.try_get("admin_only_view"); + + if id.is_err() { + return Err(FailedToUnwrapDB) + } + + Ok(Room { + id: id.unwrap(), + roomid: roomid.unwrap(), + name: name.unwrap(), + admin_only_send: admin_only_send.unwrap(), + admin_only_view: admin_only_view.unwrap(), + }) + } + + fn dbuser_to_user(&self, row: MySqlRow) -> Result { + let id: Result = row.try_get("id"); + let userid: Result = row.try_get("user_id"); + let name: Result = row.try_get("name"); + let online: Result = row.try_get("online"); + let admin: Result = row.try_get("admin"); + + if id.is_err() { + return Err(FailedToUnwrapDB) + } + + Ok(User { + id: id.unwrap(), + userid: userid.unwrap(), + name: name.unwrap(), + online: online.unwrap(), + admin: admin.unwrap(), + }) + } + + fn dbmessage_to_message(&self, row: MySqlRow) -> Result { //NOTE: Query results passed in should have a join + let result: Result<&str, Error> = row.try_get("type"); + let type_enum: &str = match result { + Ok(string) => { string } + Err(_) => { "" } + }; + + if type_enum == "" { + return Err(FailedToUnwrapDB) + } + + let id: u32 = row.try_get("message.id").unwrap(); + let timestamp: DateTime = row.try_get("timestamp").unwrap(); + + let room = Room { + id: row.try_get("room").unwrap(), + roomid: row.try_get("room_id").unwrap(), + name: row.try_get("room.name").unwrap(), + admin_only_send: row.try_get("admin_only_send").unwrap(), + admin_only_view: row.try_get("admin_only_view").unwrap(), + }; + + let user = User { + id: row.try_get("user.id").unwrap(), + userid: row.try_get("user_id").unwrap(), + name: row.try_get("user.name").unwrap(), + online: row.try_get("online").unwrap(), + admin: row.try_get("admin").unwrap(), + }; + + match type_enum { + "text" => { + let text: String = row.try_get("msgText").unwrap(); + Ok(Message { + id, timestamp, user, room, + data: MessageData::Text(text), + }) + } + "attachment" => { + todo!() + } + "reply" => { + let text: &str = row.try_get("msgText").unwrap(); + let referencing_id: u32 = row.try_get("referencingID").unwrap(); + Ok(Message { + id, timestamp, user, room, + data: MessageData::Reply(Reply { + referencing_id, + text: text.to_string(), + }), + }) + } + "edit" => { + let text: &str = row.try_get("msgText").unwrap(); + let referencing_id: u32 = row.try_get("referencingID").unwrap(); + Ok(Message { + id, timestamp, user, room, + data: MessageData::Edit(Edit { + referencing_id, + text: text.to_string(), + }), + }) + } + "reaction" => { + let emoji: &str = row.try_get("emoji").unwrap(); + let referencing_id: u32 = row.try_get("referencingID").unwrap(); + Ok(Message { + id, timestamp, user, room, + data: MessageData::Reaction(Reaction { + referencing_id, + emoji: emoji.to_string(), + }), + }) + } + "redaction" => { + let referencing_id: u32 = row.try_get("referencingID").unwrap(); + Ok(Message { + id, timestamp, user, room, + data: MessageData::Redaction(Redaction { + referencing_id, + }), + }) + } + _ => { Err(FailedToUnwrapDB) } + } } } \ No newline at end of file diff --git a/server/src/types.rs b/server/src/types.rs index 0b15099..714ca99 100644 --- a/server/src/types.rs +++ b/server/src/types.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, TimeZone, Utc}; use tarpc::serde::{Deserialize, Serialize}; #[tarpc::service] @@ -11,16 +12,20 @@ pub trait RealmChat { async fn keep_typing() -> ErrorCode; //NOTE: If a keep alive hasn't been received in 5 seconds, stop typing //NOTE: Any user can call, if they are in the server - async fn get_message_from_guid(guid: String) -> Result; - async fn get_messages_since(time: u64) -> Result, ErrorCode>; + async fn get_message_from_id(id: u32) -> Result; + async fn get_messages_since(time: DateTime) -> Result, ErrorCode>; async fn get_rooms() -> Result, ErrorCode>; async fn get_room(roomid: String) -> Result; async fn get_user(userid: String) -> Result; - async fn get_joined_users() -> Result, ErrorCode>; - async fn get_online_users() -> Result, ErrorCode>; + async fn get_users(get_only_online: bool) -> Result, ErrorCode>; //TODO: Admin access only! // async fn create_room() -> Result; + // delete room + // delete any message + // kick user + // ban user + // unban user } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -29,12 +34,13 @@ pub enum ErrorCode { Error, Unauthorized, NotFound, + FailedToUnwrapDB, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Message { pub id: u32, - pub timestamp: u64, //TODO: Change to a real time for SQL + pub timestamp: DateTime, //TODO: Does the database already have timestamps for us? pub user: User, pub room: Room, pub data: MessageData, @@ -48,7 +54,7 @@ pub enum MessageData { Reply(Reply), Edit(Edit), //NOTE: Have to be the owner of the referencing_guid Reaction(Reaction), - Redaction(Redaction), + Redaction(Redaction), //NOTE: Have to be the owner of the referencing_guid } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -85,7 +91,8 @@ pub struct User { pub userid: String, pub name: String, pub online: bool, - //TODO + pub admin: bool, + //TODO: auth stuff needed, should be Option } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -93,6 +100,7 @@ pub struct Room { pub id: u32, pub roomid: String, pub name: String, - //TODO + pub admin_only_send: bool, + pub admin_only_view: bool, }