diff --git a/src/api_data/conversation_message_api.rs b/src/api_data/conversation_message_api.rs index 055638b..99e68d3 100644 --- a/src/api_data/conversation_message_api.rs +++ b/src/api_data/conversation_message_api.rs @@ -5,31 +5,86 @@ //! @author Pierre HUBERT use serde::Serialize; +use serde_json::Value; -use crate::data::conversation_message::ConversationMessage; +use crate::data::conversation_message::{ConversationMessage, ConversationServerMessageType}; use crate::utils::user_data_utils::user_data_url; #[derive(Serialize)] -#[allow(non_snake_case)] +struct ConversationMessageFileAPI { + url: String, + size: u64, + name: String, + thumbnail: Option, + r#type: String, +} + +#[derive(Serialize)] pub struct ConversationMessageAPI { - ID: u64, - convID: u64, - ID_user: u64, + id: u64, + conv_id: u64, + user_id: Option, time_insert: u64, - message: String, - image_path: Option, + message: Option, + file: Option, + server_message: Option, } impl ConversationMessageAPI { /// Turn a conversation message into an API entry pub fn new(msg: &ConversationMessage) -> ConversationMessageAPI { + let file = match &msg.file { + None => None, + Some(file) => Some(ConversationMessageFileAPI { + url: user_data_url(&file.path.clone()), + size: file.size, + name: file.name.clone(), + thumbnail: file.thumbnail.clone(), + r#type: file.r#type.clone(), + }) + }; + + let server_message = match &msg.server_message { + None => None, + Some(msg) => { + let mut value = serde_json::Map::new(); + + match msg { + ConversationServerMessageType::UserCreatedConversation(user) => { + value.insert("type".to_string(), Value::from("user_created_conv")); + value.insert("user_id".to_string(), Value::from(user.id())); + } + + ConversationServerMessageType::UserAddedAnotherUserToConversation(msg) => { + value.insert("type".to_string(), Value::from("user_added_another")); + value.insert("user_who_added".to_string(), Value::from(msg.user_who_added.id())); + value.insert("user_added".to_string(), Value::from(msg.user_added.id())); + } + + ConversationServerMessageType::UserLeftConversation(u) => { + value.insert("type".to_string(), Value::from("user_left")); + value.insert("user_id".to_string(), Value::from(u.id())); + } + + ConversationServerMessageType::UserRemovedFromConversation(msg) => { + value.insert("type".to_string(), Value::from("user_removed_another")); + value.insert("user_who_removed".to_string(), Value::from(msg.user_who_removed.id())); + value.insert("user_removed".to_string(), Value::from(msg.user_removed.id())); + } + } + + Some(serde_json::Value::Object(value)) + } + }; + ConversationMessageAPI { - ID: msg.id, - convID: msg.conv_id, - ID_user: msg.user_id.id(), + id: msg.id, + conv_id: msg.conv_id, + user_id: msg.user_id.clone().map(|u| u.id()), time_insert: msg.time_sent, - message: msg.message.clone().unwrap_or(String::new()), - image_path: msg.image_path.as_ref().map(|f| user_data_url(f)), + message: msg.message.clone(), + file, + server_message, } } diff --git a/src/data/account_export.rs b/src/data/account_export.rs index d19c0d1..5c9772a 100644 --- a/src/data/account_export.rs +++ b/src/data/account_export.rs @@ -61,7 +61,7 @@ impl AccountExport { // Conversation messages for (_, conv_messages) in &self.conversation_messages { - conv_messages.iter().for_each(|f| { set.insert(f.user_id.clone()); }) + conv_messages.iter().for_each(|f| { set.extend(f.referenced_users_id()); }) } Ok(set) diff --git a/src/data/conversation_message.rs b/src/data/conversation_message.rs index 5a6212b..b9183ea 100644 --- a/src/data/conversation_message.rs +++ b/src/data/conversation_message.rs @@ -2,15 +2,141 @@ //! //! Information about a single conversation message +use std::collections::HashSet; + +use crate::data::error::{ExecError, Res}; use crate::data::user::UserID; +pub type ConvMessageID = u64; + +#[derive(Debug)] +pub struct ConversationMessageFile { + pub path: String, + pub size: u64, + pub name: String, + pub thumbnail: Option, + pub r#type: String, +} + +#[derive(Debug)] +pub struct UserAddedAnotherUserToConversation { + pub user_who_added: UserID, + pub user_added: UserID, +} + +#[derive(Debug)] +pub struct UserRemovedAnotherUserToConversation { + pub user_who_removed: UserID, + pub user_removed: UserID, +} + +#[derive(Debug)] +pub enum ConversationServerMessageType { + UserCreatedConversation(UserID), + UserAddedAnotherUserToConversation(UserAddedAnotherUserToConversation), + UserLeftConversation(UserID), + UserRemovedFromConversation(UserRemovedAnotherUserToConversation), +} + +impl ConversationServerMessageType { + pub fn to_db(&self) -> String { + let info = match self { + ConversationServerMessageType::UserCreatedConversation(u) => ("user_created_conv", Some(u.clone()), None), + ConversationServerMessageType::UserAddedAnotherUserToConversation(msg) => ("user_added_another_user", Some(msg.user_who_added.clone()), Some(msg.user_added.clone())), + ConversationServerMessageType::UserLeftConversation(u) => ("user_left", Some(u.clone()), None), + ConversationServerMessageType::UserRemovedFromConversation(msg) => ("user_removed", Some(msg.user_who_removed.clone()), Some(msg.user_removed.clone())), + }; + + format!( + "{}-{}-{}", + info.0, + info.1.map(|u| u.id()).unwrap_or(0), + info.2.map(|u| u.id()).unwrap_or(0) + ) + } + + pub fn from_db(str: &str) -> Res { + let split: Vec<&str> = str.split("-").collect(); + + if split.len() != 3 { + return Err(ExecError::boxed_new("Invalid ConversationSerMessageType")); + } + + let id_1 = split[1].parse::()?; + let id_2 = split[2].parse::()?; + let info = ( + split[0], + match id_1 { + 0 => None, + id => Some(UserID::new(id)) + }, + match id_2 { + 0 => None, + id => Some(UserID::new(id)) + }, + ); + + match info { + ("user_created_conv", Some(user_id), _) => Ok(Self::UserCreatedConversation(user_id)), + + ("user_added_another_user", Some(user_id), Some(user_2)) => Ok(Self::UserAddedAnotherUserToConversation(UserAddedAnotherUserToConversation { + user_who_added: user_id, + user_added: user_2, + })), + + ("user_left", Some(user_id), _) => Ok(Self::UserLeftConversation(user_id)), + + ("user_removed", Some(user_id), Some(user_2)) => Ok(Self::UserRemovedFromConversation(UserRemovedAnotherUserToConversation { + user_who_removed: user_id, + user_removed: user_2, + })), + + _ => Err(ExecError::boxed_new("Unknown server message type!")) + } + } +} + /// Information about a single conversation message #[derive(Debug)] pub struct ConversationMessage { - pub id: u64, + pub id: ConvMessageID, pub time_sent: u64, pub conv_id: u64, - pub user_id: UserID, + pub user_id: Option, pub message: Option, - pub image_path: Option, + pub server_message: Option, + pub file: Option, +} + +impl ConversationMessage { + /// Get the entire list of referenced users in a conversation message + pub fn referenced_users_id(&self) -> HashSet { + let mut users = HashSet::new(); + + if let Some(user_id) = &self.user_id { + users.insert(user_id.clone()); + } + + if let Some(srv_msg) = &self.server_message { + match srv_msg { + ConversationServerMessageType::UserCreatedConversation(user) => { + users.insert(user.clone()); + } + ConversationServerMessageType::UserAddedAnotherUserToConversation(msg) => { + users.insert(msg.user_who_added.clone()); + users.insert(msg.user_added.clone()); + } + + ConversationServerMessageType::UserLeftConversation(user) => { + users.insert(user.clone()); + } + ConversationServerMessageType::UserRemovedFromConversation(msg) => { + users.insert(msg.user_who_removed.clone()); + users.insert(msg.user_removed.clone()); + } + } + } + + users + } } \ No newline at end of file diff --git a/src/helpers/conversations_helper.rs b/src/helpers/conversations_helper.rs index f61e606..b6c57fe 100644 --- a/src/helpers/conversations_helper.rs +++ b/src/helpers/conversations_helper.rs @@ -2,9 +2,9 @@ //! //! @author Pierre Hubert -use crate::constants::database_tables_names::{CONV_LIST_TABLE, CONV_MESSAGES_TABLE, CONV_MEMBERS_TABLE}; +use crate::constants::database_tables_names::{CONV_LIST_TABLE, CONV_MEMBERS_TABLE, CONV_MESSAGES_TABLE}; use crate::data::conversation::Conversation; -use crate::data::conversation_message::ConversationMessage; +use crate::data::conversation_message::{ConversationMessage, ConversationMessageFile, ConversationServerMessageType}; use crate::data::error::{ExecError, Res, ResultBoxError}; use crate::data::new_conversation::NewConversation; use crate::data::new_conversation_message::NewConversationMessage; @@ -14,7 +14,7 @@ use crate::helpers::{database, events_helper}; use crate::helpers::database::InsertQuery; use crate::helpers::events_helper::Event; use crate::utils::date_utils::time; -use crate::utils::user_data_utils::user_data_path; +use crate::utils::user_data_utils::delete_user_data_file_if_exists; /// Create a new conversation. This method returns the ID of the created conversation pub fn create(conv: &NewConversation) -> ResultBoxError { @@ -377,11 +377,12 @@ pub fn update_message_content(msg_id: u64, new_content: &str) -> ResultBoxError< /// Remove a message from a conversation pub fn delete_message(msg: &ConversationMessage) -> ResultBoxError<()> { - // Delete associated image (if any) - if let Some(img) = &msg.image_path { - let path = user_data_path(img.as_ref()); - if path.exists() { - std::fs::remove_file(path)?; + // Delete associated files + if let Some(file) = &msg.file { + delete_user_data_file_if_exists(&file.path)?; + + if let Some(thumb) = &file.thumbnail { + delete_user_data_file_if_exists(thumb)?; } } @@ -523,12 +524,39 @@ fn db_to_conversation_info(row: &database::RowResult) -> ResultBoxError ResultBoxError { + let user_id = match row.is_null("user_id")? { + true => None, + false => Some(row.get_user_id("user_id")?) + }; + + let file = match row.is_null_or_empty("filepath")? { + true => None, + false => Some(ConversationMessageFile { + path: row.get_str("filepath")?, + size: row.get_u64("file_size")?, + name: row.get_str("file_name")?, + thumbnail: row.get_optional_str("file_thumbnail")?, + r#type: row.get_str("file_type")?, + }) + }; + + let server_message = match &user_id { + Some(_) => None, + None => Some(ConversationServerMessageType::from_db(&row.get_str("message")?)?) + }; + + let message = match server_message { + None => row.get_optional_str("message")?, + Some(_) => None, + }; + Ok(ConversationMessage { id: row.get_u64("id")?, - time_sent: row.get_u64("time_insert")?, + time_sent: row.get_u64("time_sent")?, conv_id: row.get_u64("conv_id")?, - user_id: row.get_user_id("user_id")?, - message: row.get_optional_str("message")?, - image_path: row.get_optional_str("image_path")?, + user_id, + message, + server_message, + file, }) } \ No newline at end of file diff --git a/src/helpers/database.rs b/src/helpers/database.rs index 032cbae..b2e7a02 100644 --- a/src/helpers/database.rs +++ b/src/helpers/database.rs @@ -380,6 +380,15 @@ impl<'a> RowResult<'a> { } } + /// Check out whether a given value is null or empty or not + pub fn is_null_or_empty(&self, name: &str) -> ResultBoxError { + if self.is_null(name)? { + return Ok(true); + } + + Ok(self.get_str(name)?.is_empty()) + } + /// Get an optional string => Set to None if string is null / empty pub fn get_optional_str(&self, name: &str) -> ResultBoxError> { match self.is_null(name)? { diff --git a/src/main.rs b/src/main.rs index aad276a..6cca797 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use comunic_server::{cleanup_thread, server}; +use comunic_server::api_data::conversation_message_api::ConversationMessageAPI; use comunic_server::data::config::{conf, Config}; -use comunic_server::helpers::database; +use comunic_server::helpers::{conversations_helper, database}; #[actix_rt::main] async fn main() -> std::io::Result<()> { @@ -18,6 +19,10 @@ async fn main() -> std::io::Result<()> { // Start cleanup thread cleanup_thread::start().expect("Failed to start cleanup thread!"); + let msg = conversations_helper::get_last_messages(120, 100).unwrap(); + println!("{:#?}", msg); + println!("{:#?}", serde_json::to_string(&ConversationMessageAPI::for_list(&msg))); + // Start the server server::start_server(conf()).await } diff --git a/src/utils/user_data_utils.rs b/src/utils/user_data_utils.rs index edbb7cc..2e69501 100644 --- a/src/utils/user_data_utils.rs +++ b/src/utils/user_data_utils.rs @@ -7,7 +7,7 @@ use std::path::{Path, PathBuf}; use serde_json::to_string; use crate::data::config::conf; -use crate::data::error::{ExecError, ResultBoxError}; +use crate::data::error::{ExecError, Res, ResultBoxError}; use crate::data::user::UserID; use crate::utils::crypt_utils::rand_str; @@ -61,4 +61,13 @@ pub fn generate_new_user_data_file_name(dir: &Path, ext: &str) -> ResultBoxError return Ok(dir.join(&file)); } } +} + +/// Delete a file from user data, if it exists +pub fn delete_user_data_file_if_exists(path: &str) -> Res { + let path = user_data_path(path.as_ref()); + if path.exists() { + std::fs::remove_file(path)?; + } + Ok(()) } \ No newline at end of file